Move file association handling back into the app

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.
This commit is contained in:
Andy McFadden 2014-12-15 21:57:09 -08:00
parent 8e53955045
commit ef708f563e
9 changed files with 336 additions and 433 deletions

7
.gitignore vendored
View File

@ -1,4 +1,4 @@
# build product output directories # build product output directories (top-level and component)
Debug Debug
Release Release
@ -11,6 +11,9 @@ CP.opensdf
CP.v12.suo CP.v12.suo
*.aps *.aps
# installer binary
DIST/Setup*.exe
# linux binaries # linux binaries
*.o *.o
diskimg/libdiskimg.a diskimg/libdiskimg.a
@ -19,6 +22,6 @@ diskimg/libhfs/libhfs.a
# ctags # ctags
tags tags
# generic # VIM
*~ *~
*.swp *.swp

View File

@ -321,241 +321,7 @@ TRUE
- -
- -
- -
18 0
TRUE
.2mg
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.app
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.bny
Binary II (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
2
C:\Src\ciderpress\Release\CiderPress.exe
2
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.bqy
Binary II (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
2
C:\Src\ciderpress\Release\CiderPress.exe
2
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.bse
NuFX Archive (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
1
C:\Src\ciderpress\Release\CiderPress.exe
1
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.bxy
NuFX Archive (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
1
C:\Src\ciderpress\Release\CiderPress.exe
1
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.d13
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.ddd
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.do
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.dsk
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.fdi
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.hdv
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
FALSE
.img
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.nib
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.po
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.sdk
Disk Image (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
3
C:\Src\ciderpress\Release\CiderPress.exe
3
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
FALSE
.sea
NuFX Archive (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
1
C:\Src\ciderpress\Release\CiderPress.exe
1
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
TRUE
.shk
NuFX Archive (CiderPress)
C:\Src\ciderpress\Release\CiderPress.exe
1
C:\Src\ciderpress\Release\CiderPress.exe
1
1
Open
Open
C:\Src\ciderpress\Release\CiderPress.exe
C:\Src\ciderpress\Release\CiderPress.exe
"%1"
FALSE FALSE
FALSE FALSE
FALSE FALSE
@ -569,11 +335,11 @@ http://www.deploymaster.com/dotnetfx.html
TRUE TRUE
FALSE FALSE
TRUE TRUE
FALSE TRUE
C:\Src\ciderpress\Release\CiderPress.exe C:\Src\ciderpress\Release\CiderPress.exe
-install -install
C:\Src\ciderpress\Release\CiderPress.exe
-uninstall -uninstall
TRUE TRUE

View File

@ -261,11 +261,13 @@ BEGIN
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,72,144,8 "Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,72,144,8
CONTROL "Show spaces as &underscores",IDC_PREF_SPACES_TO_UNDER, CONTROL "Show spaces as &underscores",IDC_PREF_SPACES_TO_UNDER,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,84,145,10 "Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,84,145,10
GROUPBOX "Miscellaneous",IDC_STATIC,96,102,162,38 GROUPBOX "System",IDC_STATIC,96,101,162,33
PUSHBUTTON "File type &associations...",IDC_PREF_ASSOCIATIONS,104,113,92,14
GROUPBOX "Miscellaneous",IDC_STATIC,96,136,162,38
CONTROL "Strip pathnames when pasting files",IDC_PREF_PASTE_JUNKPATHS, CONTROL "Strip pathnames when pasting files",IDC_PREF_PASTE_JUNKPATHS,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,117,144,10 "Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,147,144,10
CONTROL "Beep when actions complete successfully",IDC_PREF_SUCCESS_BEEP, CONTROL "Beep when actions complete successfully",IDC_PREF_SUCCESS_BEEP,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,129,145,10 "Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,159,145,10
END END
IDD_PREF_COMPRESSION DIALOGEX 0, 0, 212, 212 IDD_PREF_COMPRESSION DIALOGEX 0, 0, 212, 212

View File

@ -79,6 +79,9 @@ void EditAssocDialog::Setup(bool loadAssoc)
bool ours; bool ours;
gMyApp.fRegistry.GetFileAssoc(idx, &ext, &handler, &ours); gMyApp.fRegistry.GetFileAssoc(idx, &ext, &handler, &ours);
if (handler.IsEmpty()) {
handler = L"(no association)";
}
pListView->InsertItem(idx, ext); pListView->InsertItem(idx, ext);
pListView->SetItemText(idx, 1, handler); pListView->SetItemText(idx, 1, handler);

View File

@ -561,7 +561,7 @@ LONG MainWindow::OnLateInit(UINT, LONG)
*/ */
fPreferences.LoadFromRegistry(); fPreferences.LoadFromRegistry();
#ifdef CAN_UPDATE_FILE_ASSOC #if 0
/* /*
* Check to see if we're registered; if we're not, and we've expired, it's * Check to see if we're registered; if we're not, and we've expired, it's
* time to bail out. * time to bail out.
@ -579,12 +579,10 @@ LONG MainWindow::OnLateInit(UINT, LONG)
case MyRegistry::kRegInvalid: case MyRegistry::kRegInvalid:
MessageBox(result, appName, MB_OK|MB_ICONINFORMATION); MessageBox(result, appName, MB_OK|MB_ICONINFORMATION);
LOGI("FORCING REG"); LOGI("FORCING REG");
#if 0
if (EnterRegDialog::GetRegInfo(this) != 0) { if (EnterRegDialog::GetRegInfo(this) != 0) {
result = ""; result = "";
goto fail; goto fail;
} }
#endif
SetCPTitle(); // update title bar with new reg info SetCPTitle(); // update title bar with new reg info
break; break;
case MyRegistry::kRegFailed: case MyRegistry::kRegFailed:
@ -597,7 +595,7 @@ LONG MainWindow::OnLateInit(UINT, LONG)
result = confused; result = confused;
goto fail; goto fail;
} }
#endif /*CAN_UPDATE_FILE_ASSOC*/ #endif
/* /*
* Process command-line options, possibly loading an archive. * Process command-line options, possibly loading an archive.
@ -878,6 +876,9 @@ void MainWindow::ApplyNow(PrefsSheet* pPS)
/* delete them so, if they hit "apply" again, we only update once */ /* delete them so, if they hit "apply" again, we only update once */
delete[] pPS->fGeneralPage.fOurAssociations; delete[] pPS->fGeneralPage.fOurAssociations;
pPS->fGeneralPage.fOurAssociations = NULL; pPS->fGeneralPage.fOurAssociations = NULL;
LOGV("Sending association-change notification to Windows shell");
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
} }
fPreferences.SetPrefBool(kPrQueryImageFormat, pPS->fDiskImagePage.fQueryImageFormat != 0); fPreferences.SetPrefBool(kPrQueryImageFormat, pPS->fDiskImagePage.fQueryImageFormat != 0);

View File

@ -87,28 +87,13 @@ BOOL MyApp::InitInstance(void)
LogModuleLocation(L"riched20.dll"); LogModuleLocation(L"riched20.dll");
LogModuleLocation(L"riched32.dll"); LogModuleLocation(L"riched32.dll");
#if 0
/* find our .INI file by tweaking the EXE path */
char* cp = strrchr(buf, '\\');
if (cp == NULL)
cp = buf;
else
cp++;
if (cp + ::lstrlen(_T("CiderPress.INI")) >= buf+sizeof(buf))
return FALSE;
::lstrcpy(cp, _T("CiderPress.INI"));
free((void*)m_pszProfileName);
m_pszProfileName = strdup(buf);
LOGI("Profile name is '%ls'", m_pszProfileName);
if (!WriteProfileString("SectionOne", "MyEntry", "test"))
LOGI("WriteProfileString failed");
#endif
// This causes functions like SetProfileInt to use the registry rather // This causes functions like SetProfileInt to use the registry rather
// than a .INI file. The registry key is "usually the name of a company". // than a .INI file. The registry key is "usually the name of a company".
#ifdef CAN_UPDATE_FILE_ASSOC
SetRegistryKey(fRegistry.GetAppRegistryKey());
#else
SetRegistryKey(L"faddenSoft"); SetRegistryKey(L"faddenSoft");
#endif
//LOGI("Registry key is '%ls'", m_pszRegistryKey); //LOGI("Registry key is '%ls'", m_pszRegistryKey);
//LOGI("Profile name is '%ls'", m_pszProfileName); //LOGI("Profile name is '%ls'", m_pszProfileName);
@ -130,10 +115,12 @@ BOOL MyApp::InitInstance(void)
if (wcscmp(m_lpCmdLine, L"-install") == 0) { if (wcscmp(m_lpCmdLine, L"-install") == 0) {
LOGI("Invoked with INSTALL flag"); LOGI("Invoked with INSTALL flag");
fRegistry.OneTimeInstall(); fRegistry.OneTimeInstall();
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
exit(0); exit(0);
} else if (wcscmp(m_lpCmdLine, L"-uninstall") == 0) { } else if (wcscmp(m_lpCmdLine, L"-uninstall") == 0) {
LOGI("Invoked with UNINSTALL flag"); LOGI("Invoked with UNINSTALL flag");
fRegistry.OneTimeUninstall(); fRegistry.OneTimeUninstall();
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
exit(1); // tell DeployMaster to continue with uninstall exit(1); // tell DeployMaster to continue with uninstall
} }

View File

@ -12,9 +12,11 @@
#include "Main.h" #include "Main.h"
#include "MyApp.h" #include "MyApp.h"
#define kCompanyName L"faddenSoft"
#if 0
#define kRegAppName L"CiderPress" #define kRegAppName L"CiderPress"
#define kRegExeName L"CiderPress.exe" #define kRegExeName L"CiderPress.exe"
#define kCompanyName L"faddenSoft"
static const WCHAR kRegKeyCPKVersions[] = L"vrs"; static const WCHAR kRegKeyCPKVersions[] = L"vrs";
static const WCHAR kRegKeyCPKExpire[] = L"epr"; static const WCHAR kRegKeyCPKExpire[] = L"epr";
@ -40,62 +42,73 @@ static const WCHAR kMachineSettingsBaseKey[] =
static const WCHAR kUserSettingsBaseKey[] = static const WCHAR kUserSettingsBaseKey[] =
L"HKEY_CURRENT_USER\\Software\\" kCompanyName L"\\" kRegAppName; L"HKEY_CURRENT_USER\\Software\\" kCompanyName L"\\" kRegAppName;
/*
* Set this key + ".XXX" to (Default)=AppID. This associates the file
* type with kRegAppID.
*/
//static const char* kFileExtensionBase = L"HKEY_CLASSES_ROOT";
/*
* Description of data files. Set this key + AppID to 40-char string, e.g.
* (Default)=CompanyName AppName Version DataType
*
* Can also set DefaultIcon = Pathname [,Index]
*/
//static const char* kAppIDBase = L"HKEY_CLASSES_ROOT";
/*
* Put one of these under the AppID to specify the icon for a file type.
*/
static const WCHAR kDefaultIcon[] = L"DefaultIcon";
static const WCHAR kRegKeyCPKStr[] = L"CPK"; 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 * 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. * order that they appear here, so try to maintain alphabetical order.
*/ */
#define kAppIDNuFX L"CiderPress.NuFX"
#define kAppIDDiskImage L"CiderPress.DiskImage"
#define kAppIDBinaryII L"CiderPress.BinaryII"
#define kNoAssociation L"(no association)"
const MyRegistry::FileTypeAssoc MyRegistry::kFileTypeAssoc[] = { const MyRegistry::FileTypeAssoc MyRegistry::kFileTypeAssoc[] = {
{ L".2MG", kAppIDDiskImage }, { L".2MG", kProgIdKeyDiskImage },
{ L".APP", kAppIDDiskImage }, { L".APP", kProgIdKeyDiskImage },
{ L".BNY", kAppIDBinaryII }, { L".BNY", kProgIdKeyBinaryII },
{ L".BQY", kAppIDBinaryII }, { L".BQY", kProgIdKeyBinaryII },
{ L".BSE", kAppIDNuFX }, { L".BSE", kProgIdKeyNuFX },
{ L".BXY", kAppIDNuFX }, { L".BXY", kProgIdKeyNuFX },
{ L".D13", kAppIDDiskImage }, { L".D13", kProgIdKeyDiskImage },
{ L".DDD", kAppIDDiskImage }, { L".DDD", kProgIdKeyDiskImage },
{ L".DO", kAppIDDiskImage }, { L".DO", kProgIdKeyDiskImage },
{ L".DSK", kAppIDDiskImage }, { L".DSK", kProgIdKeyDiskImage },
{ L".FDI", kAppIDDiskImage }, { L".FDI", kProgIdKeyDiskImage },
{ L".HDV", kAppIDDiskImage }, { L".HDV", kProgIdKeyDiskImage },
{ L".IMG", kAppIDDiskImage }, { L".IMG", kProgIdKeyDiskImage },
{ L".NIB", kAppIDDiskImage }, { L".NIB", kProgIdKeyDiskImage },
{ L".PO", kAppIDDiskImage }, { L".PO", kProgIdKeyDiskImage },
{ L".SDK", kAppIDDiskImage }, { L".SDK", kProgIdKeyDiskImage },
{ L".SEA", kAppIDNuFX }, { L".SEA", kProgIdKeyNuFX },
{ L".SHK", kAppIDNuFX }, { L".SHK", kProgIdKeyNuFX },
// { L".DC", kAppIDDiskImage }, // { L".DC", kProgIdKeyDiskImage },
// { L".DC6", kAppIDDiskImage }, // { L".DC6", kProgIdKeyDiskImage },
// { L".GZ", kAppIDDiskImage }, // { L".GZ", kProgIdKeyDiskImage },
// { L".NB2", kAppIDDiskImage }, // { L".NB2", kProgIdKeyDiskImage },
// { L".RAW", kAppIDDiskImage }, // { L".RAW", kProgIdKeyDiskImage },
// { L".ZIP", kAppIDDiskImage }, // { L".ZIP", kProgIdKeyDiskImage },
}; };
#if 0
static const struct { static const struct {
const char* user; const char* user;
const char* reg; const char* reg;
@ -103,6 +116,7 @@ static const struct {
{ "Nimrod Bonehead", "CP1-68C069-62CC9444" }, { "Nimrod Bonehead", "CP1-68C069-62CC9444" },
{ "Connie Tan", "CP1-877B2C-A428FFD6" }, { "Connie Tan", "CP1-877B2C-A428FFD6" },
}; };
#endif
/* /*
@ -113,32 +127,38 @@ static const struct {
void MyRegistry::OneTimeInstall(void) const void MyRegistry::OneTimeInstall(void) const
{ {
/* start by stomping on our appIDs */ /* start by stomping on our ProgIDs */
LOGI(" Removing appIDs"); LOGI(" Removing ProgIDs");
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX); RegDeleteKeyHKCU(kProgIdKeyNuFX);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage); RegDeleteKeyHKCU(kProgIdKeyDiskImage);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII); RegDeleteKeyHKCU(kProgIdKeyBinaryII);
/* configure the appIDs */ /* configure the ProgIDs */
FixBasicSettings(); FixBasicSettings();
HKEY hClassesKey = NULL;
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
return;
}
/* configure extensions */ /* configure extensions */
int i, res; for (int i = 0; i < NELEM(kFileTypeAssoc); i++) {
for (i = 0; i < NELEM(kFileTypeAssoc); i++) {
HKEY hExtKey; HKEY hExtKey;
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, kFileTypeAssoc[i].ext, 0, LSTATUS res = RegOpenKeyEx(hClassesKey, kFileTypeAssoc[i].ext, 0,
KEY_READ, &hExtKey); KEY_READ, &hExtKey);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS) {
LOGI(" Found existing HKCR\\'%ls', leaving alone", LOGI(" Found existing HKCU\\%ls\\'%ls', leaving alone",
kFileTypeAssoc[i].ext); kFileAssocBase, kFileTypeAssoc[i].ext);
RegCloseKey(hExtKey); RegCloseKey(hExtKey);
} else if (res == ERROR_FILE_NOT_FOUND) { } else if (res == ERROR_FILE_NOT_FOUND) {
OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].appID); OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].progId);
} else { } else {
LOGI(" Got error %ld opening HKCR\\'%ls', leaving alone", LOGW(" Got error %lu opening HKCU\\%ls\\'%ls', leaving alone",
res, kFileTypeAssoc[i].ext); res, kFileAssocBase, kFileTypeAssoc[i].ext);
} }
} }
RegCloseKey(hClassesKey);
} }
void MyRegistry::OneTimeUninstall(void) const void MyRegistry::OneTimeUninstall(void) const
@ -155,11 +175,11 @@ void MyRegistry::OneTimeUninstall(void) const
} }
} }
/* remove our appIDs */ /* remove our ProgIDs */
LOGI(" Removing appIDs"); LOGI(" Removing ProgIDs");
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX); RegDeleteKeyHKCU(kProgIdKeyNuFX);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage); RegDeleteKeyHKCU(kProgIdKeyDiskImage);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII); RegDeleteKeyHKCU(kProgIdKeyBinaryII);
} }
@ -182,17 +202,17 @@ const WCHAR* MyRegistry::GetAppRegistryKey(void) const
return kCompanyName; return kCompanyName;
} }
bool MyRegistry::IsOurAppID(const WCHAR* id) const bool MyRegistry::IsOurProgId(const WCHAR* progIdKeyName) const
{ {
return (wcsicmp(id, kAppIDNuFX) == 0 || return (wcsicmp(progIdKeyName, kProgIdKeyNuFX) == 0 ||
wcsicmp(id, kAppIDDiskImage) == 0 || wcsicmp(progIdKeyName, kProgIdKeyDiskImage) == 0 ||
wcsicmp(id, kAppIDBinaryII) == 0); wcsicmp(progIdKeyName, kProgIdKeyBinaryII) == 0);
} }
void MyRegistry::FixBasicSettings(void) const void MyRegistry::FixBasicSettings(void) const
{ {
/* /*
* Fix the basic registry settings, e.g. our AppID classes. * 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 * 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 * installer's settings, which should get whacked if the program is
@ -203,32 +223,69 @@ void MyRegistry::FixBasicSettings(void) const
const WCHAR* exeName = gMyApp.GetExeFileName(); const WCHAR* exeName = gMyApp.GetExeFileName();
ASSERT(exeName != NULL && wcslen(exeName) > 0); ASSERT(exeName != NULL && wcslen(exeName) > 0);
LOGI("Fixing any missing file type AppID entries in registry"); LOGI("Fixing any missing file type ProgID entries in registry");
ConfigureAppID(kAppIDNuFX, L"NuFX Archive (CiderPress)", exeName, 1); ConfigureProgId(kProgIdKeyNuFX, L"NuFX Archive (CiderPress)", exeName, 1);
ConfigureAppID(kAppIDBinaryII, L"Binary II (CiderPress)", exeName, 2); ConfigureProgId(kProgIdKeyBinaryII, L"Binary II (CiderPress)", exeName, 2);
ConfigureAppID(kAppIDDiskImage, L"Disk Image (CiderPress)", exeName, 3); ConfigureProgId(kProgIdKeyDiskImage, L"Disk Image (CiderPress)", exeName, 3);
} }
void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr, void MyRegistry::ConfigureProgId(const WCHAR* progIdKeyName, const WCHAR* descr,
const WCHAR* exeName, int iconIdx) const const WCHAR* exeName, int iconIdx) const
{ {
LOGI(" ConfigureAppID '%ls' for '%ls'", appID, exeName); LOGI(" ConfigureProgId '%ls' for '%ls'", progIdKeyName, exeName);
HKEY hClassesKey = NULL;
HKEY hAppKey = NULL; HKEY hAppKey = NULL;
HKEY hIconKey = NULL;
DWORD dw; if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
LOGW(" ConfigureProgId failed to open HKCU");
return;
}
DWORD disp;
LONG result; LONG result;
if ((result = RegCreateKeyEx(HKEY_CLASSES_ROOT, appID, 0, REG_NONE, if ((result = RegCreateKeyEx(hClassesKey, progIdKeyName, 0, REG_NONE,
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
&hAppKey, &dw)) == ERROR_SUCCESS) &hAppKey, &disp)) == ERROR_SUCCESS)
{ {
ConfigureAppIDSubFields(hAppKey, descr, exeName); 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, if (RegCreateKeyEx(hAppKey, kDefaultIcon, 0, REG_NONE,
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
&hIconKey, &dw) == ERROR_SUCCESS) &hIconKey, NULL) == ERROR_SUCCESS)
{ {
DWORD type, size; DWORD type, size;
unsigned char buf[512]; unsigned char buf[512];
@ -237,7 +294,8 @@ void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr,
size = sizeof(buf); // size in bytes size = sizeof(buf); // size in bytes
res = RegQueryValueEx(hIconKey, L"", NULL, &type, buf, &size); res = RegQueryValueEx(hIconKey, L"", NULL, &type, buf, &size);
if (res == ERROR_SUCCESS && size > 1) { if (res == ERROR_SUCCESS && size > 1) {
LOGI(" Icon for '%ls' already exists, not altering", appID); LOGI(" Icon for '%ls' already exists, not altering",
progIdKeyName);
} else { } else {
CString iconStr; CString iconStr;
iconStr.Format(L"%ls,%d", exeName, iconIdx); iconStr.Format(L"%ls,%d", exeName, iconIdx);
@ -246,25 +304,28 @@ void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr,
(const BYTE*)(LPCWSTR) iconStr, (const BYTE*)(LPCWSTR) iconStr,
wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS) wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS)
{ {
LOGI(" Set icon for '%ls' to '%ls'", appID, LOGI(" Set icon for '%ls' to '%ls'", progIdKeyName,
(LPCWSTR) iconStr); (LPCWSTR) iconStr);
} else { } else {
LOGW(" WARNING: unable to set DefaultIcon for '%ls' to '%ls'", LOGW(" WARNING: unable to set DefaultIcon for '%ls' to '%ls'",
appID, (LPCWSTR) iconStr); progIdKeyName, (LPCWSTR) iconStr);
} }
} }
RegCloseKey(hIconKey);
} else { } else {
LOGW("WARNING: couldn't set up DefaultIcon for '%ls'", appID); LOGW(" WARNING: couldn't set up DefaultIcon for '%ls'", progIdKeyName);
} }
} else { } else {
LOGW("WARNING: couldn't create AppID='%ls' (err=%ld)", appID, result); LOGW(" WARNING: couldn't create ProgId='%ls' (err=%ld)",
progIdKeyName, result);
} }
RegCloseKey(hIconKey);
RegCloseKey(hAppKey); RegCloseKey(hAppKey);
RegCloseKey(hClassesKey);
} }
void MyRegistry::ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr, void MyRegistry::ConfigureProgIdCommand(HKEY hAppKey, const WCHAR* descr,
const WCHAR* exeName) const const WCHAR* exeName) const
{ {
HKEY hShellKey, hOpenKey, hCommandKey; HKEY hShellKey, hOpenKey, hCommandKey;
@ -275,12 +336,8 @@ void MyRegistry::ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr,
ASSERT(exeName != NULL); ASSERT(exeName != NULL);
hShellKey = hOpenKey = hCommandKey = NULL; hShellKey = hOpenKey = hCommandKey = NULL;
if (RegSetValueEx(hAppKey, L"", 0, REG_SZ, (const BYTE*) descr, // TODO: I believe we can do this with a single call -- it should create
wcslen(descr) * sizeof(WCHAR)) != ERROR_SUCCESS) // the intermediate path elements for us.
{
LOGI(" WARNING: unable to set description to '%ls'", descr);
}
if (RegCreateKeyEx(hAppKey, L"shell", 0, REG_NONE, if (RegCreateKeyEx(hAppKey, L"shell", 0, REG_NONE,
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
&hShellKey, &dw) == ERROR_SUCCESS) &hShellKey, &dw) == ERROR_SUCCESS)
@ -335,8 +392,8 @@ void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler,
{ {
/* /*
* We check to see if the file extension is associated with one of our * We check to see if the file extension is associated with one of our
* application ID strings. We don't bother to check whether the appID * ProgID keys. We don't bother to check whether the ProgID keys
* strings are still associated with CiderPress, since nobody should be * are still associated with CiderPress, since nobody should be
* messing with those. * messing with those.
* *
* BUG: we should be checking to see what the shell actually does to * BUG: we should be checking to see what the shell actually does to
@ -344,56 +401,64 @@ void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler,
*/ */
ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc)); ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));
long res;
*pExt = kFileTypeAssoc[idx].ext; *pExt = kFileTypeAssoc[idx].ext;
*pHandler = ""; *pHandler = L"";
*pOurs = false;
CString appID; HKEY hClassesKey = NULL;
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
LOGW("GetFileAssoc failed to open HKCU");
return;
}
CString progIdKeyName;
HKEY hExtKey = NULL; HKEY hExtKey = NULL;
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, *pExt, 0, KEY_READ, &hExtKey); long res = RegOpenKeyEx(hClassesKey, *pExt, 0, KEY_READ, &hExtKey);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS) {
WCHAR buf[260]; WCHAR buf[MAX_PATH]; // somewhat arbitrary
DWORD type;
DWORD size = sizeof(buf); // size in bytes DWORD size = sizeof(buf); // size in bytes
DWORD type;
res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE)buf, &size); res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE)buf, &size);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS) {
LOGI(" Got '%ls'", buf); LOGD(" GetFileAssoc %d got '%ls'", idx, buf);
appID = buf; progIdKeyName = buf;
if (GetAssocAppName(appID, pHandler) != 0) if (GetAssocAppName(progIdKeyName, pHandler) != 0)
*pHandler = appID; *pHandler = progIdKeyName;
} else { } else {
LOGI("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt); LOGW("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt);
} }
} else { } else {
LOGW(" RegOpenKeyEx failed on '%ls'", (LPCWSTR) *pExt); LOGW(" RegOpenKeyEx failed on '%ls'", (LPCWSTR) *pExt);
} }
*pOurs = false; if (!pHandler->IsEmpty()) {
if (pHandler->IsEmpty()) { *pOurs = IsOurProgId(progIdKeyName);
*pHandler = kNoAssociation;
} else {
*pOurs = IsOurAppID(appID);
} }
RegCloseKey(hExtKey); RegCloseKey(hExtKey);
RegCloseKey(hClassesKey);
} }
int MyRegistry::GetAssocAppName(const CString& appID, CString* pCmd) const int MyRegistry::GetAssocAppName(const CString& progIdKeyName, CString* pCmd) const
{ {
CString keyName;
WCHAR buf[260];
HKEY hAppKey = NULL; HKEY hAppKey = NULL;
long res;
int result = -1; int result = -1;
keyName = appID + L"\\shell\\open\\command"; HKEY hClassesKey = NULL;
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
LOGW("GetAssocAppName failed to open HKCU");
return result;
}
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, keyName, 0, KEY_READ, &hAppKey); CString keyName = progIdKeyName + kShellOpenCommand;
long res = RegOpenKeyEx(hClassesKey, keyName, 0, KEY_READ, &hAppKey);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS) {
WCHAR buf[260];
DWORD type; DWORD type;
DWORD size = sizeof(buf); // size in bytes DWORD size = sizeof(buf); // size in bytes
@ -413,18 +478,19 @@ int MyRegistry::GetAssocAppName(const CString& appID, CString* pCmd) const
*pCmd = cmd; *pCmd = cmd;
result = 0; result = 0;
} else { } else {
LOGW("Unable to open shell\\open\\command for '%ls'", LOGW("Unable to open %ls for '%ls'", (LPCWSTR) kShellOpenCommand,
(LPCWSTR) appID); (LPCWSTR) progIdKeyName);
} }
} else { } else {
CString errBuf; CString errBuf;
GetWin32ErrorString(res, &errBuf); GetWin32ErrorString(res, &errBuf);
LOGW("Unable to open AppID key '%ls' (%ls)", LOGW("Unable to open ProgId key '%ls' (%ls)",
(LPCWSTR) keyName, (LPCWSTR) errBuf); (LPCWSTR) keyName, (LPCWSTR) errBuf);
} }
RegCloseKey(hAppKey); RegCloseKey(hAppKey);
RegCloseKey(hClassesKey);
return result; return result;
} }
@ -472,7 +538,7 @@ int MyRegistry::SetFileAssoc(int idx, bool wantIt) const
} else if (!weOwnIt && wantIt) { } else if (!weOwnIt && wantIt) {
/* take it */ /* take it */
LOGI(" SetFileAssoc: taking '%ls'", ext); LOGI(" SetFileAssoc: taking '%ls'", ext);
result = OwnExtension(ext, kFileTypeAssoc[idx].appID); result = OwnExtension(ext, kFileTypeAssoc[idx].progId);
} else { } else {
LOGI(" SetFileAssoc: do nothing with '%ls'", ext); LOGI(" SetFileAssoc: do nothing with '%ls'", ext);
/* do nothing */ /* do nothing */
@ -488,19 +554,26 @@ bool MyRegistry::GetAssocState(const WCHAR* ext) const
int res; int res;
bool result = false; bool result = false;
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_READ, &hExtKey); 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) { if (res == ERROR_SUCCESS) {
DWORD type; DWORD type;
DWORD size = sizeof(buf); // size in bytes DWORD size = sizeof(buf); // size in bytes
res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE) buf, &size); res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE) buf, &size);
if (res == ERROR_SUCCESS && type == REG_SZ) { if (res == ERROR_SUCCESS && type == REG_SZ) {
/* compare it to known appID values */ /* compare it to known ProgID values */
LOGI(" Found '%ls', testing '%ls'", ext, buf); LOGD(" Found '%ls', testing '%ls'", ext, buf);
if (IsOurAppID((WCHAR*)buf)) if (IsOurProgId((WCHAR*)buf))
result = true; result = true;
} }
RegCloseKey(hExtKey); RegCloseKey(hExtKey);
} }
RegCloseKey(hClassesKey);
return result; return result;
} }
@ -512,62 +585,99 @@ int MyRegistry::DisownExtension(const WCHAR* ext) const
if (ext == NULL || wcslen(ext) < 2) if (ext == NULL || wcslen(ext) < 2)
return -1; return -1;
if (RegDeleteKeyNT(HKEY_CLASSES_ROOT, ext) == ERROR_SUCCESS) { if (RegDeleteKeyHKCU(ext) == ERROR_SUCCESS) {
LOGI(" HKCR\\%ls subtree deleted", ext); LOGI(" HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
} else { } else {
LOGW(" Failed deleting HKCR\\'%ls'", ext); LOGW(" Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
return -1; return -1;
} }
return 0; return 0;
} }
int MyRegistry::OwnExtension(const WCHAR* ext, const WCHAR* appID) const int MyRegistry::OwnExtension(const WCHAR* ext, const WCHAR* progIdKeyName) const
{ {
ASSERT(ext != NULL); ASSERT(ext != NULL);
ASSERT(ext[0] == '.'); ASSERT(ext[0] == '.');
if (ext == NULL || wcslen(ext) < 2) if (ext == NULL || wcslen(ext) < 2)
return -1; return -1;
HKEY hClassesKey = NULL;
HKEY hExtKey = NULL; HKEY hExtKey = NULL;
DWORD dw; int result = -1;
int res, result = -1;
/* delete the old key (which might be a hierarchy) */ if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
res = RegDeleteKeyNT(HKEY_CLASSES_ROOT, ext);
if (res == ERROR_SUCCESS) {
LOGI(" HKCR\\%ls subtree deleted", ext);
} else if (res == ERROR_FILE_NOT_FOUND) {
LOGI(" No HKCR\\%ls subtree to delete", ext);
} else {
LOGW(" Failed deleting HKCR\\'%ls'", ext);
goto bail; goto bail;
} }
/* set the new key */ // delete the old key (which might be a hierarchy)
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, ext, 0, REG_NONE, 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, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
&hExtKey, &dw) == ERROR_SUCCESS) &hExtKey, &dw) == ERROR_SUCCESS)
{ {
res = RegSetValueEx(hExtKey, L"", 0, REG_SZ, // default entry gets the ProgID key name
(LPBYTE) appID, wcslen(appID) * sizeof(WCHAR)); res = RegSetValueEx(hExtKey, L"", 0, REG_SZ, (LPBYTE) progIdKeyName,
(wcslen(progIdKeyName)+1) * sizeof(WCHAR));
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS) {
LOGI(" Set '%ls' to '%ls'", ext, appID); LOGI(" Set '%ls' to '%ls'", ext, progIdKeyName);
result = 0; result = 0;
} else { } else {
LOGW("Failed setting '%ls' to '%ls' (res=%d)", ext, appID, res); LOGW("Failed setting '%ls' to '%ls' (res=%d)",
ext, progIdKeyName, res);
goto bail; goto bail;
} }
} }
bail: bail:
RegCloseKey(hExtKey); RegCloseKey(hExtKey);
RegCloseKey(hClassesKey);
return result; 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.) // (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 // The sample code makes no attempt to check or recover from partial
// deletions. // deletions.
// //
@ -583,6 +693,8 @@ DWORD MyRegistry::RegDeleteKeyNT(HKEY hStartKey, LPCTSTR pKeyName) const
TCHAR szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic. TCHAR szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic.
HKEY hKey; HKEY hKey;
LOGD("RegDeleteKeyNT %p '%ls'", hStartKey, (LPCWSTR) pKeyName);
// Do not allow NULL or empty key name // Do not allow NULL or empty key name
if ( pKeyName && lstrlen(pKeyName)) if ( pKeyName && lstrlen(pKeyName))
{ {

View File

@ -20,6 +20,7 @@ public:
MyRegistry(void) {} MyRegistry(void) {}
~MyRegistry(void) {} ~MyRegistry(void) {}
/*
typedef enum RegStatus { typedef enum RegStatus {
kRegUnknown = 0, kRegUnknown = 0,
kRegNotSet, // unregistered kRegNotSet, // unregistered
@ -28,13 +29,14 @@ public:
kRegInvalid, // registration present, but invalid (!) kRegInvalid, // registration present, but invalid (!)
kRegFailed, // error occurred during registration kRegFailed, // error occurred during registration
} RegStatus; } RegStatus;
*/
/* /*
* This is called immediately after installation finishes. * This is called immediately after installation finishes.
* *
* We want to snatch up any unused file type associations. We define them * We want to snatch up any unused file type associations. We define them
* as "unused" if the entry does not exist in the registry at all. A more * as "unused" if the entry does not exist in the registry at all. A more
* thorough installer would also verify that the appID actually existed * thorough installer would also verify that the ProgID actually existed
* and "steal" any apparent orphans, but we can let the user do that manually. * and "steal" any apparent orphans, but we can let the user do that manually.
*/ */
void OneTimeInstall(void) const; void OneTimeInstall(void) const;
@ -43,10 +45,10 @@ public:
* Remove things that the standard uninstall script won't. * Remove things that the standard uninstall script won't.
* *
* We want to un-set any of our file associations. We don't really need to * We want to un-set any of our file associations. We don't really need to
* clean up the ".xxx" entries, because removing their appID entries is enough * clean up the ".xxx" entries, because removing their ProgID entries is enough
* to fry their little brains, but it's probably the right thing to do. * to fry their little brains, but it's probably the right thing to do.
* *
* We definitely want to strip out our appIDs. * We definitely want to strip out our ProgIDs.
*/ */
void OneTimeUninstall(void) const; void OneTimeUninstall(void) const;
@ -63,12 +65,12 @@ public:
/* /*
* Return the application's registry key. This is used as the argument to * Return the application's registry key. This is used as the argument to
* CWinApp::SetRegistryKey(). The GetProfile{Int,String} calls combine this * CWinApp::SetRegistryKey(). The GetProfile{Int,String} calls combine this
* (in m_pszRegistryKey) with the app name (in m_pszProfileName) and prepend * (in m_pszRegistryKey) with the app name (in m_pszProfileName) and
* "HKEY_CURRENT_USER\Software\". * prepends "HKEY_CURRENT_USER\Software\".
*/ */
const WCHAR* GetAppRegistryKey(void) const; const WCHAR* GetAppRegistryKey(void) const;
// Fix basic settings, e.g. HKCR AppID classes. // Fix basic settings, e.g. HKCU ProgID classes.
void FixBasicSettings(void) const; void FixBasicSettings(void) const;
/* /*
@ -78,6 +80,10 @@ public:
/* /*
* Return information on a file association. * Return information on a file association.
*
* For the given index, return the extension, the ProgID key, and an
* indication of whether or not we believe this is ours. If nothing
* is associated with this extension, *pHandler receives an empty string.
*/ */
void GetFileAssoc(int idx, CString* pExt, CString* pHandler, void GetFileAssoc(int idx, CString* pExt, CString* pHandler,
bool* pOurs) const; bool* pOurs) const;
@ -92,35 +98,45 @@ public:
private: private:
typedef struct FileTypeAssoc { typedef struct FileTypeAssoc {
const WCHAR* ext; // e.g. ".SHK" const WCHAR* ext; // e.g. ".SHK"
const WCHAR* appID; // e.g. "CiderPress.NuFX" const WCHAR* progId; // e.g. "CiderPress.NuFX.4"
} FileTypeAssoc; } FileTypeAssoc;
static const FileTypeAssoc kFileTypeAssoc[]; static const FileTypeAssoc kFileTypeAssoc[];
/* /*
* See if an AppID is one we recognize. * See if a ProgID key is one we recognize.
*/ */
bool IsOurAppID(const WCHAR* id) const; bool IsOurProgId(const WCHAR* progIdKeyName) const;
/* /*
* Set up the registry goodies for one appID. * Set up the registry goodies for one ProgID.
*/ */
void ConfigureAppID(const WCHAR* appID, const WCHAR* descr, void ConfigureProgId(const WCHAR* progIdKeyName, const WCHAR* descr,
const WCHAR* exeName, int iconIdx) const; const WCHAR* exeName, int iconIdx) const;
/* /*
* Set up the current key's default (which is used as the explorer * Puts the "Open" command in "...\shell\open\command".
* description) and put the "Open" command in "...\shell\open\command".
*/ */
void ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr, void ConfigureProgIdCommand(HKEY hAppKey, const WCHAR* descr,
const WCHAR* exeName) const; const WCHAR* exeName) const;
/* /*
* Given an application ID, determine the application's name. * Given a ProgID, determine the application's name. The executable
* path will be stripped.
* *
* This requires burrowing down into HKEY_CLASSES_ROOT\<appID>\shell\open\. * This requires burrowing down into
* HKEY_CURRENT_USER\<ProgIdKey>\shell\open\command\.
*
* This does not currently take into account the Windows shell stuff, i.e.
* HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\<.ext>
* (and I'm not really sure we should).
*
* TODO: if we don't find an association in HKCU, do a lookup in HKCR.
* That could get confusing if there's an older CiderPress association
* lurking in HKLM, but it's at least as confusing to see "no
* association" when there's clearly an association.
*/ */
int GetAssocAppName(const CString& appID, CString* pCmd) const; int GetAssocAppName(const CString& progIdKeyName, CString* pCmd) const;
/* /*
* Reduce a compound string to just its first token. * Reduce a compound string to just its first token.
@ -148,7 +164,18 @@ private:
* *
* Returns 0 on success, -1 on error. * Returns 0 on success, -1 on error.
*/ */
int OwnExtension(const WCHAR* ext, const WCHAR* appID) const; int OwnExtension(const WCHAR* ext, const WCHAR* progIdKeyName) const;
/*
* Open HKEY_CURRENT_USER\Software\Classes for reading.
*/
DWORD OpenHKCUSoftwareClasses(HKEY* phKey) const;
/*
* Recursively delete a key in the HKEY_CURRENT_USER\Software\Classes
* hierarchy.
*/
DWORD RegDeleteKeyHKCU(const WCHAR* partialKeyName) const;
DWORD RegDeleteKeyNT(HKEY hStartKey, LPCWSTR pKeyName) const; DWORD RegDeleteKeyNT(HKEY hStartKey, LPCWSTR pKeyName) const;

View File

@ -15,10 +15,12 @@
#pragma once #pragma once
#endif // _MSC_VER > 1000 #endif // _MSC_VER > 1000
// Insert your headers here
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define VC_EXTRALEAN #define VC_EXTRALEAN
// enable file association editing
#define CAN_UPDATE_FILE_ASSOC
#include "targetver.h" #include "targetver.h"
#include <afxwin.h> #include <afxwin.h>