From ef708f563ec168f5eaaa36350f72ee2f20222e63 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Mon, 15 Dec 2014 21:57:09 -0800 Subject: [PATCH] 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. --- .gitignore | 7 +- DIST/with-mdc.deploy | 240 +---------------------- app/CiderPress.rc | 8 +- app/EditAssocDialog.cpp | 3 + app/Main.cpp | 9 +- app/MyApp.cpp | 25 +-- app/Registry.cpp | 410 +++++++++++++++++++++++++--------------- app/Registry.h | 63 ++++-- app/StdAfx.h | 4 +- 9 files changed, 336 insertions(+), 433 deletions(-) diff --git a/.gitignore b/.gitignore index 9960631..d42cb76 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/DIST/with-mdc.deploy b/DIST/with-mdc.deploy index 850c797..db4dd0d 100644 --- a/DIST/with-mdc.deploy +++ b/DIST/with-mdc.deploy @@ -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 diff --git a/app/CiderPress.rc b/app/CiderPress.rc index ce55dcc..0fccf72 100644 --- a/app/CiderPress.rc +++ b/app/CiderPress.rc @@ -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 diff --git a/app/EditAssocDialog.cpp b/app/EditAssocDialog.cpp index 653a1d3..699dcb0 100644 --- a/app/EditAssocDialog.cpp +++ b/app/EditAssocDialog.cpp @@ -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); diff --git a/app/Main.cpp b/app/Main.cpp index 3b36097..7725cf0 100644 --- a/app/Main.cpp +++ b/app/Main.cpp @@ -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); diff --git a/app/MyApp.cpp b/app/MyApp.cpp index 007a5ea..54acb9e 100644 --- a/app/MyApp.cpp +++ b/app/MyApp.cpp @@ -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 } diff --git a/app/Registry.cpp b/app/Registry.cpp index 1ee1acd..f8de594 100644 --- a/app/Registry.cpp +++ b/app/Registry.cpp @@ -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)) { diff --git a/app/Registry.h b/app/Registry.h index f7d1c4e..47a3b16 100644 --- a/app/Registry.h +++ b/app/Registry.h @@ -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\\shell\open\. + * This requires burrowing down into + * HKEY_CURRENT_USER\\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; diff --git a/app/StdAfx.h b/app/StdAfx.h index 5d5ffef..6b12869 100644 --- a/app/StdAfx.h +++ b/app/StdAfx.h @@ -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