Back to index

lightning-sunbird  0.9+nobinonly
nsWindowsHooksUtil.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Bill Law     <law@netscape.com>
00024  *   Dean Tessman <dean_tessman@hotmail.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 #include <windows.h>
00040 #include <stdlib.h>
00041 #include <string.h>
00042 
00043 #include "nsString.h"
00044 #include "nsIStringBundle.h"
00045 #include "nsDirectoryService.h"
00046 #include "nsAppDirectoryServiceDefs.h"
00047 #include "nsNativeCharsetUtils.h"
00048 
00049 #ifdef MOZ_XUL_APP
00050 #include "nsNativeAppSupportWin.h"
00051 #else
00052 #include "nsINativeAppSupportWin.h"
00053 #include "nsICmdLineHandler.h"
00054 #endif
00055 
00056 #define MOZ_HWND_BROADCAST_MSG_TIMEOUT 5000
00057 #define MOZ_CLIENT_BROWSER_KEY "Software\\Clients\\StartMenuInternet"
00058 
00059 // Where Mozilla stores its own registry values.
00060 const char * const mozillaKeyName = "Software\\Mozilla\\Desktop";
00061 
00062 static const char shortcutSuffix[] = " -osint -url \"%1\"";
00063 static const char chromeSuffix[] = " -osint -chrome \"%1\"";
00064 static const char iconSuffix[] = ",0";
00065 
00066 // Returns the (fully-qualified) name of this executable.
00067 static nsCString thisApplication() {
00068     static nsCAutoString result;
00069 
00070     if ( result.IsEmpty() ) {
00071         char buffer[MAX_PATH] = { 0 };
00072        DWORD len = ::GetModuleFileName( NULL, buffer, sizeof buffer );
00073         len = ::GetShortPathName( buffer, buffer, sizeof buffer );
00074     
00075         result = buffer;
00076         ToUpperCase(result);
00077     }
00078 
00079     return result;
00080 }
00081 
00082 // Returns the "short" name of this application (in upper case).  This is for
00083 // use as a StartMenuInternet value.
00084 static nsCString shortAppName() {
00085     static nsCAutoString result;
00086     
00087     if ( result.IsEmpty() ) { 
00088         // Find last backslash in thisApplication().
00089         nsCAutoString thisApp( thisApplication() );
00090         PRInt32 n = thisApp.RFind( "\\" );
00091         if ( n != kNotFound ) {
00092             // Use what comes after the last backslash.
00093             result = (const char*)thisApp.get() + n + 1;
00094         } else {
00095             // Use entire string.
00096             result = thisApp;
00097         }
00098     }
00099 
00100     return result;
00101 }
00102 
00103 static PRBool IsNT()
00104 {
00105     static PRBool sInitialized = PR_FALSE;
00106     static PRBool sIsNT = PR_FALSE;
00107 
00108     if (!sInitialized) {
00109         OSVERSIONINFO osversion;
00110         ::ZeroMemory(&osversion, sizeof(OSVERSIONINFO));
00111         osversion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
00112         if (::GetVersionEx(&osversion) && 
00113             osversion.dwPlatformId == VER_PLATFORM_WIN32_NT)
00114             sIsNT = PR_TRUE;
00115         sInitialized = PR_TRUE;
00116     }
00117 
00118     return sIsNT;
00119 }
00120 
00121 // RegistryEntry
00122 //
00123 // Generic registry entry (no saving of previous values).  Each is comprised of:
00124 //      o A base HKEY
00125 //      o A subkey name.
00126 //      o An optional value name (empty for the "default" value).
00127 //      o The registry setting we'd like this entry to have when set.
00128 struct RegistryEntry {
00129     HKEY        baseKey;   // e.g., HKEY_CURRENT_USER
00130     PRBool      isNull;    // i.e., should use ::RegDeleteValue
00131     nsCString   keyName;   // Key name.
00132     nsCString   valueName; // Value name (can be empty, which implies NULL).
00133     nsCString   setting;   // What we set it to (UTF-8). This had better be 
00134                            // nsString to avoid extra string copies, but
00135                            // this code is not performance-critical. 
00136 
00137     RegistryEntry( HKEY baseKey, const char* keyName, const char* valueName, const char* setting )
00138         : baseKey( baseKey ), isNull( setting == 0 ), keyName( keyName ), valueName( valueName ), setting( setting ? setting : "" ) {
00139     }
00140 
00141     PRBool     isAlreadySet() const;
00142     nsresult   set();
00143     nsresult   reset();
00144     nsCString  currentSetting( PRBool *currentUndefined = 0 ) const;
00145 
00146     // Return value name in proper form for passing to ::Reg functions
00147     // (i.e., emptry string is converted to a NULL pointer).
00148     const char* valueNameArg() const {
00149         return valueName.IsEmpty() ? NULL : valueName.get();
00150     }
00151 
00152     nsCString  fullName() const;
00153 };
00154 
00155 // BoolRegistryEntry
00156 // 
00157 // These are used to store the "windows integration" preferences.
00158 // You can query the value via operator void* (i.e., if ( boolPref )... ).
00159 // These are stored under HKEY_LOCAL_MACHINE\Software\Mozilla\Desktop.
00160 // Set sets the stored value to "1".  Reset deletes it (which implies 0).
00161 struct BoolRegistryEntry : public RegistryEntry {
00162     BoolRegistryEntry( const char *name )
00163         : RegistryEntry( HKEY_LOCAL_MACHINE, mozillaKeyName, name, "1" ) {
00164     }
00165     operator PRBool();
00166 };
00167 
00168 // SavedRegistryEntry
00169 //
00170 // Like a plain RegistryEntry, but set/reset save/restore the
00171 // it had before we set it.
00172 struct SavedRegistryEntry : public RegistryEntry {
00173     SavedRegistryEntry( HKEY baseKey, const char *keyName, const char *valueName, const char *setting )
00174         : RegistryEntry( baseKey, keyName, valueName, setting ) {
00175     }
00176     nsresult set();
00177     nsresult reset();
00178 };
00179 
00180 // ProtocolRegistryEntry
00181 //
00182 // For setting entries for a given Internet Shortcut protocol.
00183 // The key name is calculated as
00184 // HKEY_LOCAL_MACHINE\Software\Classes\protocol\shell\open\command.
00185 // The setting is this executable (with appropriate suffix).
00186 // Set/reset are trickier in this case.
00187 struct ProtocolRegistryEntry : public SavedRegistryEntry {
00188     nsCString protocol;
00189     ProtocolRegistryEntry( const char* protocol )
00190         : SavedRegistryEntry( HKEY_LOCAL_MACHINE, "", "", thisApplication().get() ),
00191           protocol( protocol ) {
00192         keyName = "Software\\Classes\\";
00193         keyName += protocol;
00194         keyName += "\\shell\\open\\command";
00195 
00196         // Append appropriate suffix to setting.
00197         if ( this->protocol.Equals( "chrome" ) || this->protocol.Equals( "MozillaXUL" ) ) {
00198             // Use "-chrome" command line flag.
00199             setting += chromeSuffix;
00200         } else {
00201             // Use standard "-url" command line flag.
00202             setting += shortcutSuffix;
00203         }
00204     }
00205     nsresult set();
00206     nsresult reset();
00207 };
00208 
00209 // ProtocolIconRegistryEntry
00210 //
00211 // For setting icon entries for a given Internet Shortcut protocol.
00212 // The key name is calculated as
00213 // HKEY_LOCAL_MACHINE\Software\Classes\protocol\DefaultIcon.
00214 // The setting is this executable (with appropriate suffix).
00215 struct ProtocolIconRegistryEntry : public SavedRegistryEntry {
00216     nsCString protocol;
00217     ProtocolIconRegistryEntry( const char* aprotocol )
00218         : SavedRegistryEntry( HKEY_LOCAL_MACHINE, "", "", thisApplication().get() ),
00219           protocol( aprotocol ) {
00220         keyName = NS_LITERAL_CSTRING("Software\\Classes\\") + protocol + NS_LITERAL_CSTRING("\\DefaultIcon");
00221 
00222         // Append appropriate suffix to setting.
00223         setting += iconSuffix;
00224     }
00225 };
00226 
00227 // DDERegistryEntry
00228 //
00229 // Like a protocol registry entry, but for the shell\open\ddeexec subkey.
00230 //
00231 // We need to remove this subkey entirely to ensure we work properly with
00232 // various programs on various platforms (see Bugzilla bugs 59078, 58770, etc.).
00233 //
00234 // We don't try to save everything, though.  We do save the known useful info
00235 // under the ddeexec subkey:
00236 //     ddeexec\@
00237 //     ddeexec\NoActivateHandler
00238 //     ddeexec\Application\@
00239 //     ddeexec\Topic\@
00240 //
00241 // set/reset save/restore these values and remove/restore the ddeexec subkey
00242 struct DDERegistryEntry : public SavedRegistryEntry {
00243     DDERegistryEntry( const char *protocol )
00244         : SavedRegistryEntry( HKEY_LOCAL_MACHINE, "", "", 0 ),
00245           activate( HKEY_LOCAL_MACHINE, "", "NoActivateHandler", 0 ),
00246           app( HKEY_LOCAL_MACHINE, "", "", 0 ),
00247           topic( HKEY_LOCAL_MACHINE, "", "", 0 ) {
00248         // Derive keyName from protocol.
00249         keyName = "Software\\Classes\\";
00250         keyName += protocol;
00251         keyName += "\\shell\\open\\ddeexec";
00252         // Set subkey names.
00253         activate.keyName = keyName;
00254         app.keyName = keyName;
00255         app.keyName += "\\Application";
00256         topic.keyName = keyName;
00257         topic.keyName += "\\Topic";
00258     }
00259     nsresult set();
00260     nsresult reset();
00261     SavedRegistryEntry activate, app, topic;
00262 };
00263 
00264 // FileTypeRegistryEntry
00265 //
00266 // For setting entries relating to a file extension (or extensions).
00267 // This object itself is for the "file type" associated with the extension.
00268 // Set/reset manage the mapping from extension to the file type, as well.
00269 // The description is taken from defDescKey if available. Otherwise desc 
00270 // is used.
00271 struct FileTypeRegistryEntry : public ProtocolRegistryEntry {
00272     nsCString fileType;
00273     const char **ext;
00274     nsCString desc;
00275     nsCString defDescKey;
00276     nsCString iconFile;
00277     FileTypeRegistryEntry ( const char **ext, const char *fileType, 
00278         const char *desc, const char *defDescKey, const char *iconFile )
00279         : ProtocolRegistryEntry( fileType ),
00280           fileType( fileType ),
00281           ext( ext ),
00282           desc( desc ),
00283           defDescKey(defDescKey),
00284           iconFile(iconFile) {
00285     }
00286     nsresult set();
00287     nsresult reset();
00288 };
00289 
00290 // EditableFileTypeRegistryEntry
00291 //
00292 // Extends FileTypeRegistryEntry by setting an additional handler for an "edit" command.
00293 struct EditableFileTypeRegistryEntry : public FileTypeRegistryEntry {
00294     EditableFileTypeRegistryEntry( const char **ext, const char *fileType, 
00295         const char *desc, const char *defDescKey, const char *iconFile )
00296         : FileTypeRegistryEntry( ext, fileType, desc, defDescKey, iconFile ) {
00297     }
00298     nsresult set();
00299 };
00300 
00301 // Generate the "full" name of this registry entry.
00302 nsCString RegistryEntry::fullName() const {
00303     nsCString result;
00304     if ( baseKey == HKEY_CURRENT_USER ) {
00305         result = "HKEY_CURRENT_USER\\";
00306     } else if ( baseKey == HKEY_LOCAL_MACHINE ) {
00307         result = "HKEY_LOCAL_MACHINE\\";
00308     } else {
00309         result = "\\";
00310     }
00311     result += keyName;
00312     if ( !valueName.IsEmpty() ) {
00313         result += "[";
00314         result += valueName;
00315         result += "]";
00316     }
00317     return result;
00318 }
00319 
00320 // Tests whether registry entry already has desired setting.
00321 PRBool RegistryEntry::isAlreadySet() const {
00322     PRBool result = FALSE;
00323 
00324     nsCAutoString current( currentSetting() );
00325 
00326     result = ( current == setting );
00327 
00328     return result;
00329 }
00330 
00331 // Gives registry entry the desired setting.
00332 nsresult RegistryEntry::set() {
00333 #ifdef DEBUG_law
00334 if ( isNull && setting.IsEmpty() ) printf( "Deleting %s\n", fullName().get() );
00335 else printf( "Setting %s=%s\n", fullName().get(), setting.get() );
00336 #endif
00337     nsresult result = NS_ERROR_FAILURE;
00338 
00339     HKEY   key;
00340     LONG   rc = ::RegOpenKey( baseKey, keyName.get(), &key );
00341 
00342     // See if key doesn't exist yet...
00343     if ( rc == ERROR_FILE_NOT_FOUND ) {
00344         rc = ::RegCreateKey( baseKey, keyName.get(), &key );
00345     }
00346     if ( rc == ERROR_SUCCESS ) {
00347         if ( isNull && setting.IsEmpty() ) {
00348             // This means we need to actually remove the value, not merely set it to an
00349             // empty string.
00350             rc = ::RegDeleteValue( key, valueNameArg() );
00351             if ( rc == ERROR_SUCCESS ) {
00352                 result = NS_OK;
00353             }
00354         } else {
00355             NS_ConvertUTF8toUTF16 utf16Setting(setting);
00356             if (!IsNT()) {
00357                 // Get current value to see if it is set properly already.
00358                char buffer[4096] = { 0 };
00359                DWORD len = sizeof buffer;
00360                rc = ::RegQueryValueExA( key, valueNameArg(), NULL, NULL,
00361                     (LPBYTE)buffer, &len );
00362                nsCAutoString cSetting;
00363                NS_CopyUnicodeToNative(utf16Setting, cSetting);
00364                if ( rc != ERROR_SUCCESS || !cSetting.Equals(buffer)) {
00365                    rc = ::RegSetValueExA( key, valueNameArg(), 0, REG_SZ,
00366                         (LPBYTE)cSetting.get(), cSetting.Length() );
00367 #ifdef DEBUG_law
00368                    NS_WARN_IF_FALSE( rc == ERROR_SUCCESS, fullName().get() );
00369 #endif
00370                    if ( rc == ERROR_SUCCESS ) {
00371                       result = NS_OK;
00372                    }
00373                } else {
00374                    // Already has desired setting.
00375                    result = NS_OK;
00376                }
00377            } else {
00378                 // Get current value to see if it is set properly already.
00379                PRUnichar buffer[4096] = { 0 };
00380                DWORD len = sizeof buffer;
00381                NS_ConvertASCIItoUTF16 wValueName(valueNameArg());
00382                rc = ::RegQueryValueExW( key, wValueName.get(), NULL,
00383                     NULL, (LPBYTE)buffer, &len );
00384                if ( rc != ERROR_SUCCESS || !utf16Setting.Equals(buffer) ) {
00385                    rc = ::RegSetValueExW( key, wValueName.get(), 0, REG_SZ,
00386                         (LPBYTE) (utf16Setting.get()),
00387                         utf16Setting.Length() * 2);
00388                    if ( rc == ERROR_SUCCESS ) {
00389                       result = NS_OK;
00390                    }
00391                } else {
00392                    // Already has desired setting.
00393                    result = NS_OK;
00394                }
00395            }  // NT
00396         }
00397         ::RegCloseKey( key );
00398     } else {
00399 #ifdef DEBUG_law
00400 NS_WARN_IF_FALSE( rc == ERROR_SUCCESS, fullName().get() );
00401 #endif
00402     }
00403     return result;
00404 }
00405 
00406 // Get current setting, set new one, then save the previous.
00407 nsresult SavedRegistryEntry::set() {
00408     nsresult rv = NS_OK;
00409     PRBool   currentlyUndefined = PR_TRUE;
00410     nsCAutoString prev( currentSetting( &currentlyUndefined ) );
00411     // See if value is changing.
00412     // We need an extra check for the case where we have an empty entry
00413     // and we need to remove it entirely.
00414     if ( setting != prev || ( !currentlyUndefined && isNull ) ) {
00415         // Set new.
00416         rv = RegistryEntry::set();
00417         if ( NS_SUCCEEDED( rv ) ) {
00418             // Save old.
00419             RegistryEntry tmp( HKEY_LOCAL_MACHINE, "Software\\Mozilla\\Desktop", fullName().get(), prev.get() );
00420             tmp.set();
00421         }
00422     }
00423     return rv;
00424 }
00425 
00426 // setWindowsXP
00427 //
00428 //  We need to:
00429 //    a. Make sure this application is registered as a "Start Menu
00430 //       internet app" under HKLM\Software\Clients\StartMenuInternet.
00431 //    b. Make this app the default "Start Menu internet app" for this
00432 //       user.
00433 static void setWindowsXP() {
00434     // We test for the presence of this subkey as a WindowsXP test.
00435     // We do it this way so that vagueries of future Windows versions
00436     // are handled as best we can.
00437     HKEY key;
00438     nsCAutoString baseKey( NS_LITERAL_CSTRING( "Software\\Clients\\StartMenuInternet" ) );
00439     LONG rc = ::RegOpenKey( HKEY_LOCAL_MACHINE, baseKey.get(), &key );
00440     if ( rc == ERROR_SUCCESS ) {
00441         // OK, this is WindowsXP (or equivalent).  Add this application to the
00442         // set registered as "Start Menu internet apps."  These entries go
00443         // under the subkey MOZILLA.EXE (or whatever the name of this executable is),
00444         // that subkey name is generated by the utility function shortAppName.
00445         if ( rc == ERROR_SUCCESS ) {
00446             // The next 3 go under this subkey.
00447             nsCAutoString subkey( baseKey + NS_LITERAL_CSTRING( "\\" ) + shortAppName() );
00448             // Pretty name.  This goes into the LocalizedString value.  It is the
00449             // name of the executable (preceded by '@'), followed by ",-nnn" where
00450             // nnn is the resource identifier of the string in the .exe.  That value
00451             // comes from nsINativeAppSupportWin.h.
00452             char buffer[ _MAX_PATH + 8 ]; // Path, plus '@', comma, minus, and digits (5)
00453             _snprintf( buffer, sizeof buffer, "@%s,-%d", thisApplication().get(), IDS_STARTMENU_APPNAME );
00454             RegistryEntry tmp_entry1( HKEY_LOCAL_MACHINE, 
00455                            subkey.get(),
00456                            "LocalizedString", 
00457                            buffer );
00458             tmp_entry1.set();
00459             // Default icon (from .exe resource).
00460             RegistryEntry tmp_entry2( HKEY_LOCAL_MACHINE, 
00461                            nsCAutoString( subkey + NS_LITERAL_CSTRING( "\\DefaultIcon" ) ).get(),
00462                            "", 
00463                            nsCAutoString( thisApplication() + NS_LITERAL_CSTRING( ",0" ) ).get() );
00464             tmp_entry2.set();
00465             // Command to open.
00466             RegistryEntry tmp_entry3( HKEY_LOCAL_MACHINE,
00467                            nsCAutoString( subkey + NS_LITERAL_CSTRING( "\\shell\\open\\command" ) ).get(),
00468                            "", 
00469                            thisApplication().get() );
00470             tmp_entry3.set();
00471             // "Properties" verb.  The default value is the text that will appear in the menu.
00472             // The default value under the command subkey is the name of this application, with
00473             // arguments to cause the Preferences window to appear.
00474             nsCOMPtr<nsIStringBundleService> bundleService( do_GetService( "@mozilla.org/intl/stringbundle;1" ) );
00475             nsCOMPtr<nsIStringBundle> bundle;
00476             nsXPIDLString label;
00477             if ( bundleService &&
00478                  NS_SUCCEEDED( bundleService->CreateBundle( "chrome://global-platform/locale/nsWindowsHooks.properties",
00479                                                        getter_AddRefs( bundle ) ) ) &&
00480                  NS_SUCCEEDED( bundle->GetStringFromName( NS_LITERAL_STRING( "prefsLabel" ).get(), getter_Copies( label ) ) ) ) {
00481                 // Set the label that will appear in the start menu context menu.
00482                 RegistryEntry tmp_entry4( HKEY_LOCAL_MACHINE,
00483                                nsCAutoString( subkey + NS_LITERAL_CSTRING( "\\shell\\properties" ) ).get(),
00484                                "", 
00485                                NS_ConvertUCS2toUTF8( label ).get() );
00486                 tmp_entry4.set();
00487             }
00488             RegistryEntry tmp_entry5( HKEY_LOCAL_MACHINE,
00489                            nsCAutoString( subkey + NS_LITERAL_CSTRING( "\\shell\\properties\\command" ) ).get(),
00490                            "", nsCAutoString( thisApplication() + 
00491                                                NS_LITERAL_CSTRING(" -chrome \"chrome://communicator/content/pref/pref.xul\"") ).get()
00492                           );
00493             tmp_entry5.set();
00494 
00495             // Now we need to select our application as the default start menu internet application.
00496             // This is accomplished by first trying to store our subkey name in 
00497             // HKLM\Software\Clients\StartMenuInternet's default value.  See
00498             // http://support.microsoft.com/directory/article.asp?ID=KB;EN-US;Q297878 for detail.
00499             SavedRegistryEntry hklmAppEntry( HKEY_LOCAL_MACHINE, baseKey.get(), "", shortAppName().get() );
00500             hklmAppEntry.set();
00501             // That may or may not have worked (depending on whether we have sufficient access).
00502             if ( hklmAppEntry.currentSetting() == hklmAppEntry.setting ) {
00503                 // We've set the hklm entry, so we can delete the one under hkcu.
00504                 SavedRegistryEntry tmp_entry6( HKEY_CURRENT_USER, baseKey.get(), "", 0 );
00505                 tmp_entry6.set();
00506             } else {
00507                 // All we can do is set the default start menu internet app for this user.
00508                 SavedRegistryEntry tmp_entry7( HKEY_CURRENT_USER, baseKey.get(), "", shortAppName().get() );
00509             }
00510             // Notify the system of the changes.
00511             ::SendMessageTimeout( HWND_BROADCAST,
00512                                   WM_SETTINGCHANGE,
00513                                   0,
00514                                   (LPARAM)MOZ_CLIENT_BROWSER_KEY,
00515                                   SMTO_NORMAL|SMTO_ABORTIFHUNG,
00516                                   MOZ_HWND_BROADCAST_MSG_TIMEOUT,
00517                                   NULL);
00518         }
00519     }
00520 }
00521 
00522 // Set this entry and its corresponding DDE entry.  The DDE entry
00523 // must be turned off to stop Windows from trying to use DDE.
00524 nsresult ProtocolRegistryEntry::set() {
00525     // If the protocol is http, then we have to do special stuff.
00526     // We must take care of this first because setting the "protocol entry"
00527     // for http will cause WindowsXP to do stuff automatically for us,
00528     // thereby making it impossible for us to propertly reset.
00529     if ( protocol.EqualsLiteral( "http" ) ) {
00530         setWindowsXP();
00531     }
00532 
00533     // Set this entry.
00534     nsresult rv = SavedRegistryEntry::set();
00535 
00536     // Save and set corresponding DDE entry(ies).
00537     DDERegistryEntry( protocol.get() ).set();
00538     // Set icon.
00539     ProtocolIconRegistryEntry( protocol.get() ).set();
00540 
00541     return rv;
00542 }
00543 
00544 // Not being a "saved" entry, we can't restore, so just delete it.
00545 nsresult RegistryEntry::reset() {
00546     HKEY key;
00547     LONG rc = ::RegOpenKey( baseKey, keyName.get(), &key );
00548     if ( rc == ERROR_SUCCESS ) {
00549         rc = ::RegDeleteValue( key, valueNameArg() );
00550 #ifdef DEBUG_law
00551 if ( rc == ERROR_SUCCESS ) printf( "Deleting key=%s\n", (const char*)fullName().get() );
00552 #endif
00553     }
00554     return NS_OK;
00555 }
00556 
00557 // Resets registry entry to the saved value (if there is one).  We first
00558 // ensure that we still "own" that entry (by comparing its value to what
00559 // we would set it to).
00560 nsresult SavedRegistryEntry::reset() {
00561     nsresult result = NS_OK;
00562 
00563     // Get current setting for this key/value.
00564     nsCAutoString current( currentSetting() );
00565 
00566     // Test if we "own" it.
00567     if ( current == setting ) {
00568         // Unset it, then.  First get saved value it had previously.
00569         PRBool noSavedValue = PR_TRUE;
00570         RegistryEntry saved = RegistryEntry( HKEY_LOCAL_MACHINE, mozillaKeyName, fullName().get(), "" );
00571         // There are 3 cases:
00572         //    - no saved entry
00573         //    - empty saved entry
00574         //    - a non-empty saved entry
00575         // We delete the current entry in the first case, and restore
00576         // the saved entry (empty or otherwise) in the other two.
00577         setting = saved.currentSetting( &noSavedValue );
00578         if ( !setting.IsEmpty() || !noSavedValue ) {
00579             // Set to previous value.
00580             isNull = PR_FALSE; // Since we're resetting and the saved value may be empty, we
00581                                // need to make sure set() doesn't mistakenly delete this entry.
00582             result = RegistryEntry::set();
00583             // Remove saved entry.
00584             saved.reset();
00585         } else {
00586             // No saved value, just delete this entry.
00587             result = RegistryEntry::reset();
00588         }
00589     }
00590 
00591     return result;
00592 }
00593 
00594 // resetWindowsXP
00595 //
00596 // This function undoes "setWindowsXP," more or less.  It only needs to restore the selected
00597 // default Start Menu internet application.  The registration of this application as one of
00598 // the start menu internet apps can remain.  There is no check for the presence of anything
00599 // because the SaveRegistryEntry::reset calls will have no effect if there is no value at that
00600 // location (or, if that value has been changed by another application).
00601 static void resetWindowsXP() {
00602     NS_NAMED_LITERAL_CSTRING( baseKey, "Software\\Clients\\StartMenuInternet" );
00603     // First, try to restore the HKLM setting.  This will fail if either we didn't
00604     // set that, or, if we don't have access).
00605     SavedRegistryEntry tmp_entry8( HKEY_LOCAL_MACHINE, baseKey.get(), "", shortAppName().get() );
00606     tmp_entry8.reset();
00607 
00608     // The HKCU setting is trickier.  We may have set it, but we may also have
00609     // removed it (see setWindowsXP(), above).  We first try to reverse the
00610     // setting.  If we had removed it, then this will fail.
00611     SavedRegistryEntry tmp_entry9( HKEY_CURRENT_USER, baseKey.get(), "", shortAppName().get() );
00612     tmp_entry9.reset();
00613     // Now, try to reverse the removal of this key.  This will fail if there is a  current
00614     // setting, and will only work if this key is unset, and, we have a saved value.
00615     SavedRegistryEntry tmp_entry10( HKEY_CURRENT_USER, baseKey.get(), "", 0 );
00616     tmp_entry10.reset();
00617 
00618     // Notify the system of the changes.
00619     ::SendMessageTimeout( HWND_BROADCAST,
00620                           WM_SETTINGCHANGE,
00621                           0,
00622                           (LPARAM)MOZ_CLIENT_BROWSER_KEY,
00623                           SMTO_NORMAL|SMTO_ABORTIFHUNG,
00624                           MOZ_HWND_BROADCAST_MSG_TIMEOUT,
00625                           NULL);
00626 }
00627 
00628 // Restore this entry and corresponding DDE entry.
00629 nsresult ProtocolRegistryEntry::reset() {
00630     // Restore this entry.
00631     nsresult rv = SavedRegistryEntry::reset();
00632 
00633     // Do same for corresponding DDE entry.
00634     DDERegistryEntry( protocol.get() ).reset();
00635     // Reset icon.
00636     ProtocolIconRegistryEntry( protocol.get() ).reset();
00637 
00638     // For http:, on WindowsXP, we need to do some extra cleanup.
00639     if ( protocol.EqualsLiteral( "http" ) ) {
00640         resetWindowsXP();
00641     }
00642 
00643     return rv;
00644 }
00645 
00646 static DWORD deleteKey( HKEY baseKey, const char *keyName ) {
00647     // Make sure input subkey isn't null.
00648     DWORD rc;
00649     if ( keyName && ::strlen(keyName) ) {
00650         // Open subkey.
00651         HKEY key;
00652         rc = ::RegOpenKeyEx( baseKey,
00653                              keyName,
00654                              0,
00655                              KEY_ENUMERATE_SUB_KEYS | DELETE,
00656                              &key );
00657         // Continue till we get an error or are done.
00658         while ( rc == ERROR_SUCCESS ) {
00659             char subkeyName[_MAX_PATH];
00660             DWORD len = sizeof subkeyName;
00661             // Get first subkey name.  Note that we always get the
00662             // first one, then delete it.  So we need to get
00663             // the first one next time, also.
00664             rc = ::RegEnumKeyEx( key,
00665                                  0,
00666                                  subkeyName,
00667                                  &len,
00668                                  0,
00669                                  0,
00670                                  0,
00671                                  0 );
00672             if ( rc == ERROR_NO_MORE_ITEMS ) {
00673                 // No more subkeys.  Delete the main one.
00674                 rc = ::RegDeleteKey( baseKey, keyName );
00675                 break;
00676             } else if ( rc == ERROR_SUCCESS ) {
00677                 // Another subkey, delete it, recursively.
00678                 rc = deleteKey( key, subkeyName );
00679             }
00680         }
00681         // Close the key we opened.
00682         ::RegCloseKey( key );
00683     } else {
00684         rc = ERROR_BADKEY;
00685     }
00686     return rc;
00687 }
00688 
00689 // Set the "dde" entry by deleting the main ddexec subkey
00690 // under HKLM\Software\Classes<protocol>\shell\open.
00691 // We "set" the various subkeys in order to preserve useful
00692 // information.
00693 nsresult DDERegistryEntry::set() {
00694     nsresult rv = SavedRegistryEntry::set();
00695     rv = activate.set();
00696     rv = app.set();
00697     rv = topic.set();
00698     // We've saved what we can.  Now recurse through this key and
00699     // subkeys.  This is necessary due to the fact that
00700     // ::RegDeleteKey won't work on WinNT (and Win2k?) if there are
00701     // subkeys.
00702     if ( deleteKey( baseKey, keyName.get() ) != ERROR_SUCCESS ) {
00703         rv = NS_ERROR_FAILURE;
00704     }
00705     return rv;
00706 }
00707 
00708 // Reset the main (ddeexec) value but also the Application and Topic.
00709 // We reset the app/topic even though we no longer set them.  This
00710 // handles cases where the user installed a prior version, and then
00711 // upgraded.
00712 nsresult DDERegistryEntry::reset() {
00713     nsresult rv = SavedRegistryEntry::reset();
00714     rv = activate.reset();
00715     rv = app.reset();
00716     rv = topic.reset();
00717     return rv;
00718 }
00719 
00720 // Return current setting for this registry entry.
00721 // Optionally, the caller can ask that a boolean be set to indicate whether
00722 // the registry value is undefined.  This flag can be used to distinguish
00723 // between not defined at all versus simply empty (both of which return an
00724 // empty string).
00725 nsCString RegistryEntry::currentSetting( PRBool *currentlyUndefined ) const {
00726     nsCString result;
00727 
00728     if ( currentlyUndefined ) {
00729         *currentlyUndefined = PR_TRUE;
00730     }
00731 
00732     HKEY   key;
00733     LONG   rc = ::RegOpenKey( baseKey, keyName.get(), &key );
00734     if ( rc == ERROR_SUCCESS ) {
00735         if (!IsNT()) {
00736             char buffer[4096] = { 0 };
00737             DWORD len = sizeof buffer;
00738             rc = ::RegQueryValueExA( key, valueNameArg(), NULL, NULL,
00739                  (LPBYTE)buffer, &len );
00740             if ( rc == ERROR_SUCCESS ) {
00741                 nsAutoString uResult;
00742                 NS_CopyNativeToUnicode(nsDependentCString(buffer), uResult);
00743                 CopyUTF16toUTF8(uResult, result);
00744                 if ( currentlyUndefined ) {
00745                     *currentlyUndefined = PR_FALSE; // Indicate entry is present
00746                 }
00747             }
00748         } else {
00749             PRUnichar buffer[4096] = { 0 };
00750             DWORD len = sizeof buffer;
00751             rc = ::RegQueryValueExW( key,
00752                  NS_ConvertASCIItoUTF16(valueNameArg()).get(), NULL, NULL,
00753                  (LPBYTE)buffer, &len );
00754             if ( rc == ERROR_SUCCESS ) {
00755                 CopyUTF16toUTF8(buffer, result);
00756                 if ( currentlyUndefined ) {
00757                     *currentlyUndefined = PR_FALSE; // Indicate entry is present
00758                 }
00759             }
00760         }
00761         ::RegCloseKey( key );
00762     }
00763 
00764     return result;
00765 }
00766 
00767 // For each file extension, map it to this entry's file type.
00768 // Set the file type so this application opens files of that type.
00769 nsresult FileTypeRegistryEntry::set() {
00770     nsresult rv = NS_OK;
00771 
00772     // Set file extensions.
00773     for ( int i = 0; NS_SUCCEEDED( rv ) && ext[i]; i++ ) {
00774         nsCAutoString thisExt( "Software\\Classes\\" );
00775         thisExt += ext[i];
00776         rv = SavedRegistryEntry( HKEY_LOCAL_MACHINE, thisExt.get(), "", fileType.get() ).set();
00777     }
00778 
00779     // If OK, set file type opener.
00780     if ( NS_SUCCEEDED( rv ) ) {
00781         rv = ProtocolRegistryEntry::set();
00782 
00783         // If we just created this file type entry, set description and default icon.
00784         if ( NS_SUCCEEDED( rv ) ) {
00785             nsCAutoString iconFileToUse( "%1" );
00786             nsCAutoString descKey( "Software\\Classes\\" );
00787             descKey += protocol;
00788             RegistryEntry descEntry( HKEY_LOCAL_MACHINE, descKey.get(), NULL, "" );
00789             if ( descEntry.currentSetting().IsEmpty() ) {
00790                 nsCAutoString defaultDescKey( "Software\\Classes\\" );
00791                 defaultDescKey += defDescKey;
00792                 RegistryEntry defaultDescEntry( HKEY_LOCAL_MACHINE, defaultDescKey.get(), NULL, "" );
00793 
00794                 descEntry.setting = defaultDescEntry.currentSetting();
00795                 if ( descEntry.setting.IsEmpty() )
00796                     descEntry.setting = desc;
00797                 descEntry.set();
00798             }
00799             nsCAutoString iconKey( "Software\\Classes\\" );
00800             iconKey += protocol;
00801             iconKey += "\\DefaultIcon";
00802 
00803             if ( !iconFile.Equals(iconFileToUse) ) {
00804             iconFileToUse = thisApplication() + NS_LITERAL_CSTRING( ",0" );
00805 
00806             // Check to see if there's an icon file name associated with this extension.
00807             // If there is, then we need to use this icon file.  If not, then we default
00808             // to the main app's icon.
00809             if ( !iconFile.IsEmpty() ) {
00810                 nsCOMPtr<nsIFile> iconFileSpec;
00811                 PRBool            flagExists;
00812 
00813                 // Use the directory service to get the path to the chrome folder.  The
00814                 // icons will be located in [chrome dir]\icons\default.
00815                 // The abs path to the icon has to be in the short filename
00816                 // format, else it won't work under win9x systems.
00817                 rv = NS_GetSpecialDirectory( NS_APP_CHROME_DIR, getter_AddRefs( iconFileSpec ) );
00818                 if ( NS_SUCCEEDED( rv ) ) {
00819                     iconFileSpec->AppendNative( NS_LITERAL_CSTRING( "icons" ) );
00820                     iconFileSpec->AppendNative( NS_LITERAL_CSTRING( "default" ) );
00821                     iconFileSpec->AppendNative( iconFile );
00822 
00823                     // Check to make sure that the icon file exists on disk.
00824                     iconFileSpec->Exists( &flagExists );
00825                     if ( flagExists ) {
00826                         rv = iconFileSpec->GetNativePath( iconFileToUse );
00827                         if ( NS_SUCCEEDED( rv ) ) {
00828                             TCHAR buffer[MAX_PATH];
00829                             DWORD len;
00830 
00831                             // Get the short path name (8.3) format for iconFileToUse
00832                             // else it won't work under win9x.
00833                             len = ::GetShortPathName( iconFileToUse.get(), buffer, sizeof buffer );
00834                             NS_ASSERTION ( (len > 0) && ( len < sizeof buffer ), "GetShortPathName failed!" );
00835                             iconFileToUse.Assign( buffer );
00836                             iconFileToUse.Append( NS_LITERAL_CSTRING( ",0" ) );
00837                         }
00838                         }
00839                     }
00840                 }
00841             }
00842 
00843             RegistryEntry iconEntry( HKEY_LOCAL_MACHINE, iconKey.get(), NULL, iconFileToUse.get() );
00844             if( !iconEntry.currentSetting().Equals( iconFileToUse ) )
00845                 iconEntry.set();
00846         }
00847     }
00848 
00849     return rv;
00850 }
00851 
00852 // Basically, the inverse of set().
00853 // First, reset the opener for this entry's file type.
00854 // Then, reset the file type associated with each extension.
00855 nsresult FileTypeRegistryEntry::reset() {
00856     nsresult rv = ProtocolRegistryEntry::reset();
00857 
00858     for ( int i = 0; ext[ i ]; i++ ) {
00859         nsCAutoString thisExt( "Software\\Classes\\" );
00860         thisExt += ext[i];
00861         (void)SavedRegistryEntry( HKEY_LOCAL_MACHINE, thisExt.get(), "", fileType.get() ).reset();
00862     }
00863 
00864     return rv;
00865 }
00866 
00867 // Do inherited set() and also set key for edit (with -edit option).
00868 //
00869 // Note: We make the rash assumption that we "own" this filetype (aka "protocol").
00870 // If we ever start commandeering some other file type then this may have to be
00871 // rethought.  The solution is to override reset() and undo this (and make the
00872 // "edit" entry a SavedRegistryEntry).
00873 nsresult EditableFileTypeRegistryEntry::set() {
00874     nsresult rv = FileTypeRegistryEntry::set();
00875 #ifndef MOZ_XUL_APP
00876     if ( NS_SUCCEEDED( rv ) ) {
00877         // only set this if we support "-edit" on the command-line
00878         nsCOMPtr<nsICmdLineHandler> editorService =
00879             do_GetService( "@mozilla.org/commandlinehandler/general-startup;1?type=edit", &rv );
00880         if ( NS_SUCCEEDED( rv) ) {
00881             nsCAutoString editKey( "Software\\Classes\\" );
00882             editKey += protocol;
00883             editKey += "\\shell\\edit\\command";
00884             nsCAutoString editor( thisApplication() );
00885             editor += " -edit \"%1\"";
00886             rv = RegistryEntry( HKEY_LOCAL_MACHINE, editKey.get(), "", editor.get() ).set();
00887         }
00888     }
00889 #endif
00890     return rv;
00891 }
00892 
00893 // Convert current registry setting to boolean.
00894 BoolRegistryEntry::operator PRBool() {
00895     return currentSetting().Equals( "1" ) ? PR_TRUE : PR_FALSE;
00896 }