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
Release
@ -11,6 +11,9 @@ CP.opensdf
CP.v12.suo
*.aps
# installer binary
DIST/Setup*.exe
# linux binaries
*.o
diskimg/libdiskimg.a
@ -19,6 +22,6 @@ diskimg/libhfs/libhfs.a
# ctags
tags
# generic
# VIM
*~
*.swp

View File

@ -321,241 +321,7 @@ TRUE
-
-
-
18
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"
0
FALSE
FALSE
FALSE
@ -569,11 +335,11 @@ http://www.deploymaster.com/dotnetfx.html
TRUE
FALSE
TRUE
FALSE
TRUE
C:\Src\ciderpress\Release\CiderPress.exe
-install
C:\Src\ciderpress\Release\CiderPress.exe
-uninstall
TRUE

View File

@ -261,11 +261,13 @@ BEGIN
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,72,144,8
CONTROL "Show spaces as &underscores",IDC_PREF_SPACES_TO_UNDER,
"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,
"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,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,129,145,10
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,159,145,10
END
IDD_PREF_COMPRESSION DIALOGEX 0, 0, 212, 212

View File

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

View File

@ -561,7 +561,7 @@ LONG MainWindow::OnLateInit(UINT, LONG)
*/
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
* time to bail out.
@ -579,12 +579,10 @@ LONG MainWindow::OnLateInit(UINT, LONG)
case MyRegistry::kRegInvalid:
MessageBox(result, appName, MB_OK|MB_ICONINFORMATION);
LOGI("FORCING REG");
#if 0
if (EnterRegDialog::GetRegInfo(this) != 0) {
result = "";
goto fail;
}
#endif
SetCPTitle(); // update title bar with new reg info
break;
case MyRegistry::kRegFailed:
@ -597,7 +595,7 @@ LONG MainWindow::OnLateInit(UINT, LONG)
result = confused;
goto fail;
}
#endif /*CAN_UPDATE_FILE_ASSOC*/
#endif
/*
* 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[] pPS->fGeneralPage.fOurAssociations;
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);

View File

@ -87,28 +87,13 @@ BOOL MyApp::InitInstance(void)
LogModuleLocation(L"riched20.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
// 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");
#endif
//LOGI("Registry key is '%ls'", m_pszRegistryKey);
//LOGI("Profile name is '%ls'", m_pszProfileName);
@ -130,10 +115,12 @@ BOOL MyApp::InitInstance(void)
if (wcscmp(m_lpCmdLine, L"-install") == 0) {
LOGI("Invoked with INSTALL flag");
fRegistry.OneTimeInstall();
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
exit(0);
} else if (wcscmp(m_lpCmdLine, L"-uninstall") == 0) {
LOGI("Invoked with UNINSTALL flag");
fRegistry.OneTimeUninstall();
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
exit(1); // tell DeployMaster to continue with uninstall
}

View File

@ -12,9 +12,11 @@
#include "Main.h"
#include "MyApp.h"
#define kCompanyName L"faddenSoft"
#if 0
#define kRegAppName L"CiderPress"
#define kRegExeName L"CiderPress.exe"
#define kCompanyName L"faddenSoft"
static const WCHAR kRegKeyCPKVersions[] = L"vrs";
static const WCHAR kRegKeyCPKExpire[] = L"epr";
@ -40,62 +42,73 @@ static const WCHAR kMachineSettingsBaseKey[] =
static const WCHAR kUserSettingsBaseKey[] =
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";
#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.
*/
#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[] = {
{ L".2MG", kAppIDDiskImage },
{ L".APP", kAppIDDiskImage },
{ L".BNY", kAppIDBinaryII },
{ L".BQY", kAppIDBinaryII },
{ L".BSE", kAppIDNuFX },
{ L".BXY", kAppIDNuFX },
{ L".D13", kAppIDDiskImage },
{ L".DDD", kAppIDDiskImage },
{ L".DO", kAppIDDiskImage },
{ L".DSK", kAppIDDiskImage },
{ L".FDI", kAppIDDiskImage },
{ L".HDV", kAppIDDiskImage },
{ L".IMG", kAppIDDiskImage },
{ L".NIB", kAppIDDiskImage },
{ L".PO", kAppIDDiskImage },
{ L".SDK", kAppIDDiskImage },
{ L".SEA", kAppIDNuFX },
{ L".SHK", kAppIDNuFX },
// { L".DC", kAppIDDiskImage },
// { L".DC6", kAppIDDiskImage },
// { L".GZ", kAppIDDiskImage },
// { L".NB2", kAppIDDiskImage },
// { L".RAW", kAppIDDiskImage },
// { L".ZIP", kAppIDDiskImage },
{ 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;
@ -103,6 +116,7 @@ static const struct {
{ "Nimrod Bonehead", "CP1-68C069-62CC9444" },
{ "Connie Tan", "CP1-877B2C-A428FFD6" },
};
#endif
/*
@ -113,32 +127,38 @@ static const struct {
void MyRegistry::OneTimeInstall(void) const
{
/* start by stomping on our appIDs */
LOGI(" Removing appIDs");
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII);
/* start by stomping on our ProgIDs */
LOGI(" Removing ProgIDs");
RegDeleteKeyHKCU(kProgIdKeyNuFX);
RegDeleteKeyHKCU(kProgIdKeyDiskImage);
RegDeleteKeyHKCU(kProgIdKeyBinaryII);
/* configure the appIDs */
/* configure the ProgIDs */
FixBasicSettings();
HKEY hClassesKey = NULL;
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
return;
}
/* configure extensions */
int i, res;
for (i = 0; i < NELEM(kFileTypeAssoc); i++) {
for (int i = 0; i < NELEM(kFileTypeAssoc); i++) {
HKEY hExtKey;
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, kFileTypeAssoc[i].ext, 0,
LSTATUS res = RegOpenKeyEx(hClassesKey, kFileTypeAssoc[i].ext, 0,
KEY_READ, &hExtKey);
if (res == ERROR_SUCCESS) {
LOGI(" Found existing HKCR\\'%ls', leaving alone",
kFileTypeAssoc[i].ext);
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].appID);
OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].progId);
} else {
LOGI(" Got error %ld opening HKCR\\'%ls', leaving alone",
res, kFileTypeAssoc[i].ext);
LOGW(" Got error %lu opening HKCU\\%ls\\'%ls', leaving alone",
res, kFileAssocBase, kFileTypeAssoc[i].ext);
}
}
RegCloseKey(hClassesKey);
}
void MyRegistry::OneTimeUninstall(void) const
@ -155,11 +175,11 @@ void MyRegistry::OneTimeUninstall(void) const
}
}
/* remove our appIDs */
LOGI(" Removing appIDs");
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage);
RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII);
/* remove our ProgIDs */
LOGI(" Removing ProgIDs");
RegDeleteKeyHKCU(kProgIdKeyNuFX);
RegDeleteKeyHKCU(kProgIdKeyDiskImage);
RegDeleteKeyHKCU(kProgIdKeyBinaryII);
}
@ -182,17 +202,17 @@ const WCHAR* MyRegistry::GetAppRegistryKey(void) const
return kCompanyName;
}
bool MyRegistry::IsOurAppID(const WCHAR* id) const
bool MyRegistry::IsOurProgId(const WCHAR* progIdKeyName) const
{
return (wcsicmp(id, kAppIDNuFX) == 0 ||
wcsicmp(id, kAppIDDiskImage) == 0 ||
wcsicmp(id, kAppIDBinaryII) == 0);
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 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
* 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();
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);
ConfigureAppID(kAppIDBinaryII, L"Binary II (CiderPress)", exeName, 2);
ConfigureAppID(kAppIDDiskImage, L"Disk Image (CiderPress)", exeName, 3);
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::ConfigureAppID(const WCHAR* appID, const WCHAR* descr,
void MyRegistry::ConfigureProgId(const WCHAR* progIdKeyName, const WCHAR* descr,
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 hIconKey = NULL;
DWORD dw;
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
LOGW(" ConfigureProgId failed to open HKCU");
return;
}
DWORD disp;
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,
&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,
REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
&hIconKey, &dw) == ERROR_SUCCESS)
&hIconKey, NULL) == ERROR_SUCCESS)
{
DWORD type, size;
unsigned char buf[512];
@ -237,7 +294,8 @@ void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr,
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", appID);
LOGI(" Icon for '%ls' already exists, not altering",
progIdKeyName);
} else {
CString iconStr;
iconStr.Format(L"%ls,%d", exeName, iconIdx);
@ -246,25 +304,28 @@ void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr,
(const BYTE*)(LPCWSTR) iconStr,
wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS)
{
LOGI(" Set icon for '%ls' to '%ls'", appID,
LOGI(" Set icon for '%ls' to '%ls'", progIdKeyName,
(LPCWSTR) iconStr);
} else {
LOGW(" WARNING: unable to set DefaultIcon for '%ls' to '%ls'",
appID, (LPCWSTR) iconStr);
progIdKeyName, (LPCWSTR) iconStr);
}
}
RegCloseKey(hIconKey);
} else {
LOGW("WARNING: couldn't set up DefaultIcon for '%ls'", appID);
LOGW(" WARNING: couldn't set up DefaultIcon for '%ls'", progIdKeyName);
}
} 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(hClassesKey);
}
void MyRegistry::ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr,
void MyRegistry::ConfigureProgIdCommand(HKEY hAppKey, const WCHAR* descr,
const WCHAR* exeName) const
{
HKEY hShellKey, hOpenKey, hCommandKey;
@ -275,12 +336,8 @@ void MyRegistry::ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr,
ASSERT(exeName != NULL);
hShellKey = hOpenKey = hCommandKey = NULL;
if (RegSetValueEx(hAppKey, L"", 0, REG_SZ, (const BYTE*) descr,
wcslen(descr) * sizeof(WCHAR)) != ERROR_SUCCESS)
{
LOGI(" WARNING: unable to set description to '%ls'", descr);
}
// 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)
@ -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
* application ID strings. We don't bother to check whether the appID
* strings are still associated with CiderPress, since nobody should be
* 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
@ -344,56 +401,64 @@ void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler,
*/
ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));
long res;
*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;
res = RegOpenKeyEx(HKEY_CLASSES_ROOT, *pExt, 0, KEY_READ, &hExtKey);
long res = RegOpenKeyEx(hClassesKey, *pExt, 0, KEY_READ, &hExtKey);
if (res == ERROR_SUCCESS) {
WCHAR buf[260];
DWORD type;
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) {
LOGI(" Got '%ls'", buf);
appID = buf;
LOGD(" GetFileAssoc %d got '%ls'", idx, buf);
progIdKeyName = buf;
if (GetAssocAppName(appID, pHandler) != 0)
*pHandler = appID;
if (GetAssocAppName(progIdKeyName, pHandler) != 0)
*pHandler = progIdKeyName;
} else {
LOGI("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt);
LOGW("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt);
}
} else {
LOGW(" RegOpenKeyEx failed on '%ls'", (LPCWSTR) *pExt);
}
*pOurs = false;
if (pHandler->IsEmpty()) {
*pHandler = kNoAssociation;
} else {
*pOurs = IsOurAppID(appID);
if (!pHandler->IsEmpty()) {
*pOurs = IsOurProgId(progIdKeyName);
}
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;
long res;
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) {
WCHAR buf[260];
DWORD type;
DWORD size = sizeof(buf); // size in bytes
@ -413,18 +478,19 @@ int MyRegistry::GetAssocAppName(const CString& appID, CString* pCmd) const
*pCmd = cmd;
result = 0;
} else {
LOGW("Unable to open shell\\open\\command for '%ls'",
(LPCWSTR) appID);
LOGW("Unable to open %ls for '%ls'", (LPCWSTR) kShellOpenCommand,
(LPCWSTR) progIdKeyName);
}
} else {
CString errBuf;
GetWin32ErrorString(res, &errBuf);
LOGW("Unable to open AppID key '%ls' (%ls)",
LOGW("Unable to open ProgId key '%ls' (%ls)",
(LPCWSTR) keyName, (LPCWSTR) errBuf);
}
RegCloseKey(hAppKey);
RegCloseKey(hClassesKey);
return result;
}
@ -472,7 +538,7 @@ int MyRegistry::SetFileAssoc(int idx, bool wantIt) const
} else if (!weOwnIt && wantIt) {
/* take it */
LOGI(" SetFileAssoc: taking '%ls'", ext);
result = OwnExtension(ext, kFileTypeAssoc[idx].appID);
result = OwnExtension(ext, kFileTypeAssoc[idx].progId);
} else {
LOGI(" SetFileAssoc: do nothing with '%ls'", ext);
/* do nothing */
@ -488,19 +554,26 @@ bool MyRegistry::GetAssocState(const WCHAR* ext) const
int res;
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) {
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 appID values */
LOGI(" Found '%ls', testing '%ls'", ext, buf);
if (IsOurAppID((WCHAR*)buf))
/* 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;
}
@ -512,62 +585,99 @@ int MyRegistry::DisownExtension(const WCHAR* ext) const
if (ext == NULL || wcslen(ext) < 2)
return -1;
if (RegDeleteKeyNT(HKEY_CLASSES_ROOT, ext) == ERROR_SUCCESS) {
LOGI(" HKCR\\%ls subtree deleted", ext);
if (RegDeleteKeyHKCU(ext) == ERROR_SUCCESS) {
LOGI(" HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
} else {
LOGW(" Failed deleting HKCR\\'%ls'", ext);
LOGW(" Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
return -1;
}
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[0] == '.');
if (ext == NULL || wcslen(ext) < 2)
return -1;
HKEY hClassesKey = NULL;
HKEY hExtKey = NULL;
DWORD dw;
int res, result = -1;
int result = -1;
/* delete the old key (which might be a hierarchy) */
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);
if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
goto bail;
}
/* set the new key */
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, ext, 0, REG_NONE,
// 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)
{
res = RegSetValueEx(hExtKey, L"", 0, REG_SZ,
(LPBYTE) appID, wcslen(appID) * sizeof(WCHAR));
// 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, appID);
LOGI(" Set '%ls' to '%ls'", ext, progIdKeyName);
result = 0;
} 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;
}
}
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.
//
@ -583,6 +693,8 @@ DWORD MyRegistry::RegDeleteKeyNT(HKEY hStartKey, LPCTSTR pKeyName) const
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))
{

View File

@ -20,6 +20,7 @@ public:
MyRegistry(void) {}
~MyRegistry(void) {}
/*
typedef enum RegStatus {
kRegUnknown = 0,
kRegNotSet, // unregistered
@ -28,13 +29,14 @@ public:
kRegInvalid, // registration present, but invalid (!)
kRegFailed, // error occurred during registration
} RegStatus;
*/
/*
* This is called immediately after installation finishes.
*
* 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
* 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.
*/
void OneTimeInstall(void) const;
@ -43,10 +45,10 @@ public:
* 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
* 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.
*
* We definitely want to strip out our appIDs.
* We definitely want to strip out our ProgIDs.
*/
void OneTimeUninstall(void) const;
@ -63,12 +65,12 @@ public:
/*
* Return the application's registry key. This is used as the argument to
* CWinApp::SetRegistryKey(). The GetProfile{Int,String} calls combine this
* (in m_pszRegistryKey) with the app name (in m_pszProfileName) and prepend
* "HKEY_CURRENT_USER\Software\".
* (in m_pszRegistryKey) with the app name (in m_pszProfileName) and
* prepends "HKEY_CURRENT_USER\Software\".
*/
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;
/*
@ -78,6 +80,10 @@ public:
/*
* 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,
bool* pOurs) const;
@ -92,35 +98,45 @@ public:
private:
typedef struct FileTypeAssoc {
const WCHAR* ext; // e.g. ".SHK"
const WCHAR* appID; // e.g. "CiderPress.NuFX"
const WCHAR* progId; // e.g. "CiderPress.NuFX.4"
} FileTypeAssoc;
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;
/*
* Set up the current key's default (which is used as the explorer
* description) and put the "Open" command in "...\shell\open\command".
* Puts 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;
/*
* 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.
@ -148,7 +164,18 @@ private:
*
* 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;

View File

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