mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-26 17:49:21 +00:00
ef708f563e
The DeployMaster installer issue prevents the user from seeing more than nine of the 18 file extensions that CiderPress wants to handle, and I don't want to go stomping on file associations without some way to disable the behavior. So this returns to the previous behavior, where CiderPress directly manages the file associations. The CiderPress app is not able to modify HKEY_LOCAL_MACHINE (which it used to do via HKEY_CLASSES_ROOT) on recent versions of Windows -- tested in Win7, but it probably broke with Vista. So now we do everything in HKEY_CURRENT_USER. This works, more or less. We're not looking at the Windows shell overrides, which are made in yet another set of registry entries, so there are multiple reasons why the values reported by the Edit Associations dialog may now be inaccurate. I still favor eliminating the dialog as a long-term strategy. I took the opportunity to do some code cleanup in the registry code. I also added calls to SHChangeNotify() to tell the Windows shell when file associations change, so Windows Explorer windows get updated promptly.
737 lines
23 KiB
C++
737 lines
23 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* Windows Registry operations.
|
|
*/
|
|
#include "stdafx.h"
|
|
#ifdef CAN_UPDATE_FILE_ASSOC
|
|
#include "Registry.h"
|
|
#include "Main.h"
|
|
#include "MyApp.h"
|
|
|
|
#define kCompanyName L"faddenSoft"
|
|
|
|
#if 0
|
|
#define kRegAppName L"CiderPress"
|
|
#define kRegExeName L"CiderPress.exe"
|
|
|
|
static const WCHAR kRegKeyCPKVersions[] = L"vrs";
|
|
static const WCHAR kRegKeyCPKExpire[] = L"epr";
|
|
|
|
/*
|
|
* Application path. Add two keys:
|
|
*
|
|
* (default) = FullPathName
|
|
* Full pathname of the executable file.
|
|
* Path = Path
|
|
* The $PATH that will be in effect when the program starts (but only if
|
|
* launched from the Windows explorer).
|
|
*/
|
|
static const WCHAR kAppKeyBase[] =
|
|
L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" kRegExeName;
|
|
|
|
/*
|
|
* Local settings. App stuff goes in the per-user key, registration info is
|
|
* in the per-machine key.
|
|
*/
|
|
static const WCHAR kMachineSettingsBaseKey[] =
|
|
L"HKEY_LOCAL_MACHINE\\SOFTWARE\\" kCompanyName L"\\" kRegAppName;
|
|
static const WCHAR kUserSettingsBaseKey[] =
|
|
L"HKEY_CURRENT_USER\\Software\\" kCompanyName L"\\" kRegAppName;
|
|
|
|
static const WCHAR kRegKeyCPKStr[] = L"CPK";
|
|
#endif
|
|
|
|
/*
|
|
* ProgID fields.
|
|
*
|
|
* See http://msdn.microsoft.com/en-us/library/windows/desktop/cc144152%28v=vs.85%29.aspx
|
|
*/
|
|
|
|
static const WCHAR kDefaultIcon[] = L"DefaultIcon";
|
|
static const WCHAR kFriendlyTypeName[] = L"FriendlyTypeName";
|
|
static const WCHAR kInfoTip[] = L"InfoTip";
|
|
|
|
static const WCHAR kShellOpenCommand[] = L"\\shell\\open\\command";
|
|
|
|
|
|
/*
|
|
* ProgID key names.
|
|
*
|
|
* We used to open HKEY_CLASSES_ROOT, which provides a "merged" view of
|
|
* HKEY_LOCAL_MACHINE\Software\Classes and HKEY_CURRENT_USER\Software\Classes.
|
|
* The HKLM entries provided defaults for all users on the machine, while the
|
|
* HKCU entries were specific to the current user.
|
|
*
|
|
* It appears that Windows no longer likes it when executables other than the
|
|
* app installer (which can run privileged) mess with HKLM, so we just work
|
|
* with HKCU directly now.
|
|
*/
|
|
static const WCHAR kProgIdKeyNuFX[] = L"CiderPress.NuFX.4";
|
|
static const WCHAR kProgIdKeyDiskImage[] = L"CiderPress.DiskImage.4";
|
|
static const WCHAR kProgIdKeyBinaryII[] = L"CiderPress.BinaryII.4";
|
|
|
|
/* file associations go here under HKCU */
|
|
static const WCHAR kFileAssocBase[] = L"Software\\Classes";
|
|
|
|
/*
|
|
* Table of file type associations. They will appear in the UI in the same
|
|
* order that they appear here, so try to maintain alphabetical order.
|
|
*/
|
|
const MyRegistry::FileTypeAssoc MyRegistry::kFileTypeAssoc[] = {
|
|
{ L".2MG", kProgIdKeyDiskImage },
|
|
{ L".APP", kProgIdKeyDiskImage },
|
|
{ L".BNY", kProgIdKeyBinaryII },
|
|
{ L".BQY", kProgIdKeyBinaryII },
|
|
{ L".BSE", kProgIdKeyNuFX },
|
|
{ L".BXY", kProgIdKeyNuFX },
|
|
{ L".D13", kProgIdKeyDiskImage },
|
|
{ L".DDD", kProgIdKeyDiskImage },
|
|
{ L".DO", kProgIdKeyDiskImage },
|
|
{ L".DSK", kProgIdKeyDiskImage },
|
|
{ L".FDI", kProgIdKeyDiskImage },
|
|
{ L".HDV", kProgIdKeyDiskImage },
|
|
{ L".IMG", kProgIdKeyDiskImage },
|
|
{ L".NIB", kProgIdKeyDiskImage },
|
|
{ L".PO", kProgIdKeyDiskImage },
|
|
{ L".SDK", kProgIdKeyDiskImage },
|
|
{ L".SEA", kProgIdKeyNuFX },
|
|
{ L".SHK", kProgIdKeyNuFX },
|
|
// { L".DC", kProgIdKeyDiskImage },
|
|
// { L".DC6", kProgIdKeyDiskImage },
|
|
// { L".GZ", kProgIdKeyDiskImage },
|
|
// { L".NB2", kProgIdKeyDiskImage },
|
|
// { L".RAW", kProgIdKeyDiskImage },
|
|
// { L".ZIP", kProgIdKeyDiskImage },
|
|
};
|
|
|
|
#if 0
|
|
static const struct {
|
|
const char* user;
|
|
const char* reg;
|
|
} gBadKeys[] = {
|
|
{ "Nimrod Bonehead", "CP1-68C069-62CC9444" },
|
|
{ "Connie Tan", "CP1-877B2C-A428FFD6" },
|
|
};
|
|
#endif
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* One-time install/uninstall
|
|
* ==========================================================================
|
|
*/
|
|
|
|
void MyRegistry::OneTimeInstall(void) const
|
|
{
|
|
/* start by stomping on our ProgIDs */
|
|
LOGI(" Removing ProgIDs");
|
|
RegDeleteKeyHKCU(kProgIdKeyNuFX);
|
|
RegDeleteKeyHKCU(kProgIdKeyDiskImage);
|
|
RegDeleteKeyHKCU(kProgIdKeyBinaryII);
|
|
|
|
/* configure the ProgIDs */
|
|
FixBasicSettings();
|
|
|
|
HKEY hClassesKey = NULL;
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
/* configure extensions */
|
|
for (int i = 0; i < NELEM(kFileTypeAssoc); i++) {
|
|
HKEY hExtKey;
|
|
LSTATUS res = RegOpenKeyEx(hClassesKey, kFileTypeAssoc[i].ext, 0,
|
|
KEY_READ, &hExtKey);
|
|
if (res == ERROR_SUCCESS) {
|
|
LOGI(" Found existing HKCU\\%ls\\'%ls', leaving alone",
|
|
kFileAssocBase, kFileTypeAssoc[i].ext);
|
|
RegCloseKey(hExtKey);
|
|
} else if (res == ERROR_FILE_NOT_FOUND) {
|
|
OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].progId);
|
|
} else {
|
|
LOGW(" Got error %lu opening HKCU\\%ls\\'%ls', leaving alone",
|
|
res, kFileAssocBase, kFileTypeAssoc[i].ext);
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hClassesKey);
|
|
}
|
|
|
|
void MyRegistry::OneTimeUninstall(void) const
|
|
{
|
|
/* drop any associations we hold */
|
|
int i;
|
|
for (i = 0; i < NELEM(kFileTypeAssoc); i++) {
|
|
CString ext, handler;
|
|
bool ours;
|
|
|
|
GetFileAssoc(i, &ext, &handler, &ours);
|
|
if (ours) {
|
|
DisownExtension(ext);
|
|
}
|
|
}
|
|
|
|
/* remove our ProgIDs */
|
|
LOGI(" Removing ProgIDs");
|
|
RegDeleteKeyHKCU(kProgIdKeyNuFX);
|
|
RegDeleteKeyHKCU(kProgIdKeyDiskImage);
|
|
RegDeleteKeyHKCU(kProgIdKeyBinaryII);
|
|
}
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* Shareware registration logic
|
|
* ==========================================================================
|
|
*/
|
|
|
|
/* [removed] */
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* Windows shell game
|
|
* ==========================================================================
|
|
*/
|
|
|
|
const WCHAR* MyRegistry::GetAppRegistryKey(void) const
|
|
{
|
|
return kCompanyName;
|
|
}
|
|
|
|
bool MyRegistry::IsOurProgId(const WCHAR* progIdKeyName) const
|
|
{
|
|
return (wcsicmp(progIdKeyName, kProgIdKeyNuFX) == 0 ||
|
|
wcsicmp(progIdKeyName, kProgIdKeyDiskImage) == 0 ||
|
|
wcsicmp(progIdKeyName, kProgIdKeyBinaryII) == 0);
|
|
}
|
|
|
|
void MyRegistry::FixBasicSettings(void) const
|
|
{
|
|
/*
|
|
* Fix the basic registry settings, e.g. our ProgID classes.
|
|
*
|
|
* We don't overwrite values that already exist. We want to hold on to the
|
|
* installer's settings, which should get whacked if the program is
|
|
* uninstalled or reinstalled. This is here for "installer-less" environments
|
|
* and to cope with registry damage.
|
|
*/
|
|
|
|
const WCHAR* exeName = gMyApp.GetExeFileName();
|
|
ASSERT(exeName != NULL && wcslen(exeName) > 0);
|
|
|
|
LOGI("Fixing any missing file type ProgID entries in registry");
|
|
|
|
ConfigureProgId(kProgIdKeyNuFX, L"NuFX Archive (CiderPress)", exeName, 1);
|
|
ConfigureProgId(kProgIdKeyBinaryII, L"Binary II (CiderPress)", exeName, 2);
|
|
ConfigureProgId(kProgIdKeyDiskImage, L"Disk Image (CiderPress)", exeName, 3);
|
|
}
|
|
|
|
void MyRegistry::ConfigureProgId(const WCHAR* progIdKeyName, const WCHAR* descr,
|
|
const WCHAR* exeName, int iconIdx) const
|
|
{
|
|
LOGI(" ConfigureProgId '%ls' for '%ls'", progIdKeyName, exeName);
|
|
|
|
HKEY hClassesKey = NULL;
|
|
HKEY hAppKey = NULL;
|
|
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
LOGW(" ConfigureProgId failed to open HKCU");
|
|
return;
|
|
}
|
|
|
|
DWORD disp;
|
|
LONG result;
|
|
if ((result = RegCreateKeyEx(hClassesKey, progIdKeyName, 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hAppKey, &disp)) == ERROR_SUCCESS)
|
|
{
|
|
if (disp == REG_CREATED_NEW_KEY) {
|
|
LOGD(" Created new key for %ls", progIdKeyName);
|
|
} else if (disp == REG_OPENED_EXISTING_KEY) {
|
|
LOGD(" Opened existing key for %ls", progIdKeyName);
|
|
} else {
|
|
LOGD(" Odd RegCreateKeyEx result 0x%lx", disp);
|
|
}
|
|
|
|
ConfigureProgIdCommand(hAppKey, descr, exeName);
|
|
|
|
// Configure default entry and "friendly" type name. The friendly
|
|
// name takes precedence (as tested on Win7), but the default entry
|
|
// is set for backward compatibility.
|
|
if (RegSetValueEx(hAppKey, L"", 0, REG_SZ, (const BYTE*) descr,
|
|
(wcslen(descr)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
|
|
{
|
|
LOGW(" WARNING: unable to set ProgID default to '%ls'", descr);
|
|
}
|
|
if (RegSetValueEx(hAppKey, kFriendlyTypeName, 0, REG_SZ,
|
|
(const BYTE*) descr,
|
|
(wcslen(descr)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
|
|
{
|
|
LOGW(" WARNING: unable to set ProgID friendly name to '%ls'", descr);
|
|
}
|
|
//if (RegSetValueEx(hAppKey, kInfoTip, 0, REG_SZ,
|
|
// (const BYTE*) infoTip,
|
|
// (wcslen(infoTip)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
|
|
//{
|
|
// LOGW(" WARNING: unable to set ProgID info tip to '%ls'", infoTip);
|
|
//}
|
|
|
|
// Configure the default icon field. This is a DefaultIcon entry
|
|
// with a (Default) name.
|
|
HKEY hIconKey = NULL;
|
|
if (RegCreateKeyEx(hAppKey, kDefaultIcon, 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hIconKey, NULL) == ERROR_SUCCESS)
|
|
{
|
|
DWORD type, size;
|
|
unsigned char buf[512];
|
|
long res;
|
|
|
|
size = sizeof(buf); // size in bytes
|
|
res = RegQueryValueEx(hIconKey, L"", NULL, &type, buf, &size);
|
|
if (res == ERROR_SUCCESS && size > 1) {
|
|
LOGI(" Icon for '%ls' already exists, not altering",
|
|
progIdKeyName);
|
|
} else {
|
|
CString iconStr;
|
|
iconStr.Format(L"%ls,%d", exeName, iconIdx);
|
|
|
|
if (RegSetValueEx(hIconKey, L"", 0, REG_SZ,
|
|
(const BYTE*)(LPCWSTR) iconStr,
|
|
wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS)
|
|
{
|
|
LOGI(" Set icon for '%ls' to '%ls'", progIdKeyName,
|
|
(LPCWSTR) iconStr);
|
|
} else {
|
|
LOGW(" WARNING: unable to set DefaultIcon for '%ls' to '%ls'",
|
|
progIdKeyName, (LPCWSTR) iconStr);
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hIconKey);
|
|
} else {
|
|
LOGW(" WARNING: couldn't set up DefaultIcon for '%ls'", progIdKeyName);
|
|
}
|
|
} else {
|
|
LOGW(" WARNING: couldn't create ProgId='%ls' (err=%ld)",
|
|
progIdKeyName, result);
|
|
}
|
|
|
|
RegCloseKey(hAppKey);
|
|
RegCloseKey(hClassesKey);
|
|
}
|
|
|
|
void MyRegistry::ConfigureProgIdCommand(HKEY hAppKey, const WCHAR* descr,
|
|
const WCHAR* exeName) const
|
|
{
|
|
HKEY hShellKey, hOpenKey, hCommandKey;
|
|
DWORD dw;
|
|
|
|
ASSERT(hAppKey != NULL);
|
|
ASSERT(descr != NULL);
|
|
ASSERT(exeName != NULL);
|
|
hShellKey = hOpenKey = hCommandKey = NULL;
|
|
|
|
// TODO: I believe we can do this with a single call -- it should create
|
|
// the intermediate path elements for us.
|
|
if (RegCreateKeyEx(hAppKey, L"shell", 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hShellKey, &dw) == ERROR_SUCCESS)
|
|
{
|
|
if (RegCreateKeyEx(hShellKey, L"open", 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hOpenKey, &dw) == ERROR_SUCCESS)
|
|
{
|
|
if (RegCreateKeyEx(hOpenKey, L"command", 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hCommandKey, &dw) == ERROR_SUCCESS)
|
|
{
|
|
DWORD type, size;
|
|
WCHAR buf[MAX_PATH+8];
|
|
long res;
|
|
|
|
size = sizeof(buf); // size in bytes
|
|
res = RegQueryValueEx(hCommandKey, L"", NULL, &type, (LPBYTE) buf,
|
|
&size);
|
|
if (res == ERROR_SUCCESS && size > 1) {
|
|
LOGI(" Command already exists, not altering ('%ls')", buf);
|
|
} else {
|
|
CString openCmd;
|
|
|
|
openCmd.Format(L"\"%ls\" \"%%1\"", exeName);
|
|
if (RegSetValueEx(hCommandKey, L"", 0, REG_SZ,
|
|
(LPBYTE)(LPCWSTR) openCmd,
|
|
wcslen(openCmd) * sizeof(WCHAR)) == ERROR_SUCCESS)
|
|
{
|
|
LOGI(" Set command to '%ls'", (LPCWSTR) openCmd);
|
|
} else {
|
|
LOGW(" WARNING: unable to set open cmd '%ls'",
|
|
(LPCWSTR) openCmd);
|
|
}
|
|
}
|
|
RegCloseKey(hCommandKey);
|
|
}
|
|
RegCloseKey(hOpenKey);
|
|
}
|
|
RegCloseKey(hShellKey);
|
|
}
|
|
}
|
|
|
|
|
|
int MyRegistry::GetNumFileAssocs(void) const
|
|
{
|
|
return NELEM(kFileTypeAssoc);
|
|
}
|
|
|
|
void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler,
|
|
bool* pOurs) const
|
|
{
|
|
/*
|
|
* We check to see if the file extension is associated with one of our
|
|
* ProgID keys. We don't bother to check whether the ProgID keys
|
|
* are still associated with CiderPress, since nobody should be
|
|
* messing with those.
|
|
*
|
|
* BUG: we should be checking to see what the shell actually does to
|
|
* take into account the overrides that users can set.
|
|
*/
|
|
|
|
ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));
|
|
|
|
*pExt = kFileTypeAssoc[idx].ext;
|
|
*pHandler = L"";
|
|
*pOurs = false;
|
|
|
|
HKEY hClassesKey = NULL;
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
LOGW("GetFileAssoc failed to open HKCU");
|
|
return;
|
|
}
|
|
|
|
CString progIdKeyName;
|
|
HKEY hExtKey = NULL;
|
|
|
|
long res = RegOpenKeyEx(hClassesKey, *pExt, 0, KEY_READ, &hExtKey);
|
|
if (res == ERROR_SUCCESS) {
|
|
WCHAR buf[MAX_PATH]; // somewhat arbitrary
|
|
DWORD size = sizeof(buf); // size in bytes
|
|
DWORD type;
|
|
|
|
res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE)buf, &size);
|
|
if (res == ERROR_SUCCESS) {
|
|
LOGD(" GetFileAssoc %d got '%ls'", idx, buf);
|
|
progIdKeyName = buf;
|
|
|
|
if (GetAssocAppName(progIdKeyName, pHandler) != 0)
|
|
*pHandler = progIdKeyName;
|
|
} else {
|
|
LOGW("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt);
|
|
}
|
|
} else {
|
|
LOGW(" RegOpenKeyEx failed on '%ls'", (LPCWSTR) *pExt);
|
|
}
|
|
|
|
if (!pHandler->IsEmpty()) {
|
|
*pOurs = IsOurProgId(progIdKeyName);
|
|
}
|
|
|
|
RegCloseKey(hExtKey);
|
|
RegCloseKey(hClassesKey);
|
|
}
|
|
|
|
int MyRegistry::GetAssocAppName(const CString& progIdKeyName, CString* pCmd) const
|
|
{
|
|
HKEY hAppKey = NULL;
|
|
int result = -1;
|
|
|
|
HKEY hClassesKey = NULL;
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
LOGW("GetAssocAppName failed to open HKCU");
|
|
return result;
|
|
}
|
|
|
|
CString keyName = progIdKeyName + kShellOpenCommand;
|
|
|
|
long res = RegOpenKeyEx(hClassesKey, keyName, 0, KEY_READ, &hAppKey);
|
|
if (res == ERROR_SUCCESS) {
|
|
WCHAR buf[260];
|
|
DWORD type;
|
|
DWORD size = sizeof(buf); // size in bytes
|
|
|
|
res = RegQueryValueEx(hAppKey, L"", NULL, &type, (LPBYTE) buf, &size);
|
|
if (res == ERROR_SUCCESS) {
|
|
CString cmd(buf);
|
|
int pos;
|
|
|
|
/* cut it down to just the EXE name */
|
|
ReduceToToken(&cmd);
|
|
|
|
pos = cmd.ReverseFind('\\');
|
|
if (pos != -1 && pos != cmd.GetLength()-1) {
|
|
cmd = cmd.Right(cmd.GetLength() - pos -1);
|
|
}
|
|
|
|
*pCmd = cmd;
|
|
result = 0;
|
|
} else {
|
|
LOGW("Unable to open %ls for '%ls'", (LPCWSTR) kShellOpenCommand,
|
|
(LPCWSTR) progIdKeyName);
|
|
}
|
|
} else {
|
|
CString errBuf;
|
|
GetWin32ErrorString(res, &errBuf);
|
|
|
|
LOGW("Unable to open ProgId key '%ls' (%ls)",
|
|
(LPCWSTR) keyName, (LPCWSTR) errBuf);
|
|
}
|
|
|
|
RegCloseKey(hAppKey);
|
|
RegCloseKey(hClassesKey);
|
|
return result;
|
|
}
|
|
|
|
void MyRegistry::ReduceToToken(CString* pStr) const
|
|
{
|
|
WCHAR* argv[1];
|
|
int argc = 1;
|
|
WCHAR* mangle = wcsdup(*pStr);
|
|
|
|
VectorizeString(mangle, argv, &argc);
|
|
|
|
if (argc == 1)
|
|
*pStr = argv[0];
|
|
|
|
free(mangle);
|
|
}
|
|
|
|
int MyRegistry::SetFileAssoc(int idx, bool wantIt) const
|
|
{
|
|
/*
|
|
* Set the state of a file association. There are four possibilities:
|
|
*
|
|
* - We own it, we want to keep owning it: do nothing.
|
|
* - We don't own it, we want to keep not owning it: do nothing.
|
|
* - We own it, we don't want it anymore: remove ".xxx" entry.
|
|
* - We don't own it, we want to own it: remove ".xxx" entry and replace it.
|
|
*
|
|
* Returns 0 on success, nonzero on failure.
|
|
*/
|
|
|
|
const WCHAR* ext;
|
|
bool weOwnIt;
|
|
int result = 0;
|
|
|
|
ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));
|
|
|
|
ext = kFileTypeAssoc[idx].ext;
|
|
weOwnIt = GetAssocState(ext);
|
|
LOGI("SetFileAssoc: ext='%ls' own=%d want=%d", ext, weOwnIt, wantIt);
|
|
|
|
if (weOwnIt && !wantIt) {
|
|
/* reset it */
|
|
LOGI(" SetFileAssoc: clearing '%ls'", ext);
|
|
result = DisownExtension(ext);
|
|
} else if (!weOwnIt && wantIt) {
|
|
/* take it */
|
|
LOGI(" SetFileAssoc: taking '%ls'", ext);
|
|
result = OwnExtension(ext, kFileTypeAssoc[idx].progId);
|
|
} else {
|
|
LOGI(" SetFileAssoc: do nothing with '%ls'", ext);
|
|
/* do nothing */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool MyRegistry::GetAssocState(const WCHAR* ext) const
|
|
{
|
|
WCHAR buf[260];
|
|
HKEY hExtKey = NULL;
|
|
int res;
|
|
bool result = false;
|
|
|
|
HKEY hClassesKey = NULL;
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
LOGW("GetAssocState failed to open HKCU");
|
|
return result;
|
|
}
|
|
|
|
res = RegOpenKeyEx(hClassesKey, ext, 0, KEY_READ, &hExtKey);
|
|
if (res == ERROR_SUCCESS) {
|
|
DWORD type;
|
|
DWORD size = sizeof(buf); // size in bytes
|
|
res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE) buf, &size);
|
|
if (res == ERROR_SUCCESS && type == REG_SZ) {
|
|
/* compare it to known ProgID values */
|
|
LOGD(" Found '%ls', testing '%ls'", ext, buf);
|
|
if (IsOurProgId((WCHAR*)buf))
|
|
result = true;
|
|
}
|
|
RegCloseKey(hExtKey);
|
|
}
|
|
RegCloseKey(hClassesKey);
|
|
|
|
return result;
|
|
}
|
|
|
|
int MyRegistry::DisownExtension(const WCHAR* ext) const
|
|
{
|
|
ASSERT(ext != NULL);
|
|
ASSERT(ext[0] == '.');
|
|
if (ext == NULL || wcslen(ext) < 2)
|
|
return -1;
|
|
|
|
if (RegDeleteKeyHKCU(ext) == ERROR_SUCCESS) {
|
|
LOGI(" HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
|
|
} else {
|
|
LOGW(" Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MyRegistry::OwnExtension(const WCHAR* ext, const WCHAR* progIdKeyName) const
|
|
{
|
|
ASSERT(ext != NULL);
|
|
ASSERT(ext[0] == '.');
|
|
if (ext == NULL || wcslen(ext) < 2)
|
|
return -1;
|
|
|
|
HKEY hClassesKey = NULL;
|
|
HKEY hExtKey = NULL;
|
|
int result = -1;
|
|
|
|
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
|
|
goto bail;
|
|
}
|
|
|
|
// delete the old key (which might be a hierarchy)
|
|
DWORD res = RegDeleteKeyNT(hClassesKey, ext);
|
|
if (res == ERROR_SUCCESS) {
|
|
LOGI(" HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
|
|
} else if (res == ERROR_FILE_NOT_FOUND) {
|
|
LOGI(" No HKCU\\%ls\\%ls subtree to delete", kFileAssocBase, ext);
|
|
} else {
|
|
LOGW(" Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
|
|
goto bail;
|
|
}
|
|
|
|
// set the new key
|
|
DWORD dw;
|
|
if (RegCreateKeyEx(hClassesKey, ext, 0, REG_NONE,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
|
|
&hExtKey, &dw) == ERROR_SUCCESS)
|
|
{
|
|
// default entry gets the ProgID key name
|
|
res = RegSetValueEx(hExtKey, L"", 0, REG_SZ, (LPBYTE) progIdKeyName,
|
|
(wcslen(progIdKeyName)+1) * sizeof(WCHAR));
|
|
if (res == ERROR_SUCCESS) {
|
|
LOGI(" Set '%ls' to '%ls'", ext, progIdKeyName);
|
|
result = 0;
|
|
} else {
|
|
LOGW("Failed setting '%ls' to '%ls' (res=%d)",
|
|
ext, progIdKeyName, res);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
bail:
|
|
RegCloseKey(hExtKey);
|
|
RegCloseKey(hClassesKey);
|
|
return result;
|
|
}
|
|
|
|
DWORD MyRegistry::OpenHKCUSoftwareClasses(HKEY* phKey) const
|
|
{
|
|
DWORD result = RegOpenKeyEx(HKEY_CURRENT_USER, kFileAssocBase, 0,
|
|
KEY_READ, phKey);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOGW("Unable to open HKEY_CURRENT_USER \\ '%ls' for reading",
|
|
(LPCWSTR) kFileAssocBase);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DWORD MyRegistry::RegDeleteKeyHKCU(const WCHAR* partialKeyName) const
|
|
{
|
|
HKEY hClassesKey;
|
|
DWORD result;
|
|
|
|
result = OpenHKCUSoftwareClasses(&hClassesKey);
|
|
if (result != ERROR_SUCCESS) { return result; }
|
|
|
|
result = RegDeleteKeyNT(hClassesKey, partialKeyName);
|
|
RegCloseKey(hClassesKey);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOGW("RegDeleteKeyNT failed (err=%lu)", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// (This comes from the MSDN sample sources.)
|
|
//
|
|
// Recursively delete a key and any sub-keys.
|
|
//
|
|
// The sample code makes no attempt to check or recover from partial
|
|
// deletions.
|
|
//
|
|
// A registry key that is opened by an application can be deleted
|
|
// without error by another application in both Windows 95 and
|
|
// Windows NT. This is by design.
|
|
//
|
|
#define MAX_KEY_LENGTH 256 // not in any header I can find ++ATM
|
|
DWORD MyRegistry::RegDeleteKeyNT(HKEY hStartKey, LPCTSTR pKeyName) const
|
|
{
|
|
DWORD dwRtn, dwSubKeyLength;
|
|
LPTSTR pSubKey = NULL;
|
|
TCHAR szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic.
|
|
HKEY hKey;
|
|
|
|
LOGD("RegDeleteKeyNT %p '%ls'", hStartKey, (LPCWSTR) pKeyName);
|
|
|
|
// Do not allow NULL or empty key name
|
|
if ( pKeyName && lstrlen(pKeyName))
|
|
{
|
|
if( (dwRtn=RegOpenKeyEx(hStartKey,pKeyName,
|
|
0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey )) == ERROR_SUCCESS)
|
|
{
|
|
while (dwRtn == ERROR_SUCCESS )
|
|
{
|
|
dwSubKeyLength = MAX_KEY_LENGTH;
|
|
dwRtn=RegEnumKeyEx(
|
|
hKey,
|
|
0, // always index zero, because we're deleting it
|
|
szSubKey,
|
|
&dwSubKeyLength,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if(dwRtn == ERROR_NO_MORE_ITEMS)
|
|
{
|
|
dwRtn = RegDeleteKey(hStartKey, pKeyName);
|
|
break;
|
|
}
|
|
else if(dwRtn == ERROR_SUCCESS)
|
|
dwRtn=RegDeleteKeyNT(hKey, szSubKey);
|
|
}
|
|
RegCloseKey(hKey);
|
|
// Do not save return code because error
|
|
// has already occurred
|
|
}
|
|
}
|
|
else
|
|
dwRtn = ERROR_BADKEY;
|
|
|
|
return dwRtn;
|
|
}
|
|
#endif
|