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