File indexing completed on 2024-05-12 04:58:12

0001 /* ============================================================
0002 * Copyright (C) 2012-2017  S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
0003 * This file is part of Falkon - Qt web browser 2010-2014
0004 * by  David Rosca <nowrep@gmail.com>
0005 *
0006 * This program is free software: you can redistribute it and/or modify
0007 * it under the terms of the GNU General Public License as published by
0008 * the Free Software Foundation, either version 3 of the License, or
0009 * (at your option) any later version.
0010 *
0011 * This program is distributed in the hope that it will be useful,
0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 * GNU General Public License for more details.
0015 *
0016 * You should have received a copy of the GNU General Public License
0017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018 * ============================================================ */
0019 
0020 #include "registerqappassociation.h"
0021 #include "mainapplication.h"
0022 #include "browserwindow.h"
0023 
0024 #include "ShlObj.h"
0025 #include <QMessageBox>
0026 #include <QStringList>
0027 #include <QSettings>
0028 #include <QDir>
0029 
0030 RegisterQAppAssociation::RegisterQAppAssociation(QObject* parent) :
0031     QObject(parent)
0032 {
0033     setPerMachineRegisteration(false);
0034 }
0035 
0036 RegisterQAppAssociation::RegisterQAppAssociation(const QString &appRegisteredName, const QString &appPath, const QString &appIcon,
0037         const QString &appDesc, QObject* parent)
0038     : QObject(parent)
0039 {
0040     setPerMachineRegisteration(false);
0041     setAppInfo(appRegisteredName, appPath, appIcon, appDesc);
0042 }
0043 
0044 RegisterQAppAssociation::~RegisterQAppAssociation()
0045 {
0046 }
0047 
0048 void RegisterQAppAssociation::addCapability(const QString &assocName, const QString &progId,
0049         const QString &desc, const QString &iconPath, AssociationType type)
0050 {
0051     _assocDescHash.insert(progId, QPair<QString, QString>(desc, QDir::toNativeSeparators(iconPath)));
0052     switch (type) {
0053     case FileAssociation:
0054         _fileAssocHash.insert(assocName, progId);
0055         break;
0056     case UrlAssociation:
0057         _urlAssocHash.insert(assocName, progId);
0058         break;
0059 
0060     default:
0061         break;
0062     }
0063 }
0064 
0065 void RegisterQAppAssociation::removeCapability(const QString &assocName)
0066 {
0067     _fileAssocHash.remove(assocName);
0068     _urlAssocHash.remove(assocName);
0069 }
0070 
0071 void RegisterQAppAssociation::setAppInfo(const QString &appRegisteredName, const QString &appPath,
0072         const QString &appIcon, const QString &appDesc)
0073 {
0074     _appRegisteredName = appRegisteredName;
0075     _appPath = QDir::toNativeSeparators(appPath);
0076     _appIcon = QDir::toNativeSeparators(appIcon);
0077     _appDesc = appDesc;
0078 }
0079 
0080 bool RegisterQAppAssociation::isPerMachineRegisteration()
0081 {
0082     return (_UserRootKey == QSL("HKEY_LOCAL_MACHINE"));
0083 }
0084 
0085 void RegisterQAppAssociation::setPerMachineRegisteration(bool enable)
0086 {
0087     if (enable) {
0088         _UserRootKey = QSL("HKEY_LOCAL_MACHINE");
0089     }
0090     else {
0091         _UserRootKey = QSL("HKEY_CURRENT_USER");
0092     }
0093 }
0094 
0095 bool RegisterQAppAssociation::registerAppCapabilities()
0096 {
0097     if (!isVistaOrNewer()) {
0098         return true;
0099     }
0100     // Vista and newer
0101     QSettings regLocalMachine(QSL("HKEY_LOCAL_MACHINE"), QSettings::NativeFormat);
0102     QString capabilitiesKey = regLocalMachine.value(QSL("Software/RegisteredApplications/") + _appRegisteredName).toString();
0103 
0104     if (capabilitiesKey.isEmpty()) {
0105         regLocalMachine.setValue(QSL("Software/RegisteredApplications/") + _appRegisteredName,
0106                                  QString(QSL("Software\\") + _appRegisteredName + QSL("\\Capabilities")));
0107         capabilitiesKey = regLocalMachine.value(QSL("Software/RegisteredApplications/") + _appRegisteredName).toString();
0108 
0109         if (capabilitiesKey.isEmpty()) {
0110             QMessageBox::warning(mApp->getWindow(), tr("Warning!"),
0111                                  tr("There are some problems. Please, reinstall Falkon.\n"
0112                                     "Maybe relaunch with administrator right do a magic for you! ;)"));
0113             return false;
0114         }
0115     }
0116 
0117     capabilitiesKey.replace(QSL("\\"), QSL("/"));
0118 
0119     QHash<QString, QPair<QString, QString> >::const_iterator it = _assocDescHash.constBegin();
0120     while (it != _assocDescHash.constEnd()) {
0121         createProgId(it.key());
0122         ++it;
0123     }
0124 
0125     regLocalMachine.setValue(capabilitiesKey + QSL("/ApplicationDescription"), _appDesc);
0126     regLocalMachine.setValue(capabilitiesKey + QSL("/ApplicationIcon"), _appIcon);
0127     regLocalMachine.setValue(capabilitiesKey + QSL("/ApplicationName"), _appRegisteredName);
0128 
0129     QHash<QString, QString>::const_iterator i = _fileAssocHash.constBegin();
0130     while (i != _fileAssocHash.constEnd()) {
0131         regLocalMachine.setValue(capabilitiesKey + QSL("/FileAssociations/") + i.key(), i.value());
0132         ++i;
0133     }
0134 
0135     i = _urlAssocHash.constBegin();
0136     while (i != _urlAssocHash.constEnd()) {
0137         regLocalMachine.setValue(capabilitiesKey + QSL("/URLAssociations/") + i.key(), i.value());
0138         ++i;
0139     }
0140     regLocalMachine.setValue(capabilitiesKey + QSL("/Startmenu/StartMenuInternet"), _appPath);
0141 
0142     return true;
0143 }
0144 
0145 bool RegisterQAppAssociation::isVistaOrNewer()
0146 {
0147     return (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA &&
0148             QSysInfo::windowsVersion() <= QSysInfo::WV_NT_based);
0149 }
0150 
0151 bool RegisterQAppAssociation::isWin10OrNewer()
0152 {
0153     return (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10 &&
0154             QSysInfo::windowsVersion() <= QSysInfo::WV_NT_based);
0155 }
0156 
0157 void RegisterQAppAssociation::registerAssociation(const QString &assocName, AssociationType type)
0158 {
0159     if (isVistaOrNewer()) { // Vista and newer
0160 #ifndef __MINGW32__
0161         IApplicationAssociationRegistration* pAAR;
0162 
0163         HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
0164                                       NULL,
0165                                       CLSCTX_INPROC,
0166                                       __uuidof(IApplicationAssociationRegistration),
0167                                       (void**)&pAAR);
0168         if (SUCCEEDED(hr)) {
0169             switch (type) {
0170             case FileAssociation:
0171                 hr = pAAR->SetAppAsDefault(_appRegisteredName.toStdWString().c_str(),
0172                                            assocName.toStdWString().c_str(),
0173                                            AT_FILEEXTENSION);
0174                 break;
0175             case UrlAssociation: {
0176                 QSettings regCurrentUserRoot(QSL("HKEY_CURRENT_USER"), QSettings::NativeFormat);
0177                 QString currentUrlDefault =
0178                     regCurrentUserRoot.value(QSL("Software/Microsoft/Windows/Shell/Associations/UrlAssociations/")
0179                                              + assocName + QSL("/UserChoice/Progid")).toString();
0180                 hr = pAAR->SetAppAsDefault(_appRegisteredName.toStdWString().c_str(),
0181                                            assocName.toStdWString().c_str(),
0182                                            AT_URLPROTOCOL);
0183                 if (SUCCEEDED(hr) &&
0184                     !currentUrlDefault.isEmpty() &&
0185                     currentUrlDefault != _urlAssocHash.value(assocName)
0186                    ) {
0187                     regCurrentUserRoot.setValue(QSL("Software/Classes")
0188                                                 + assocName
0189                                                 + QSL("/shell/open/command/backup_progid"), currentUrlDefault);
0190                 }
0191             }
0192             break;
0193 
0194             default:
0195                 break;
0196             }
0197 
0198             pAAR->Release();
0199         }
0200 #endif // #ifndef __MINGW32__
0201     }
0202     else { // Older than Vista
0203         QSettings regUserRoot(_UserRootKey, QSettings::NativeFormat);
0204         regUserRoot.beginGroup(QSL("Software/Classes"));
0205         QSettings regClassesRoot(QSL("HKEY_CLASSES_ROOT"), QSettings::NativeFormat);
0206         switch (type) {
0207         case FileAssociation: {
0208             QString progId = _fileAssocHash.value(assocName);
0209             createProgId(progId);
0210             QString currentDefault = regClassesRoot.value(assocName + QSL("/Default")).toString();
0211             if (!currentDefault.isEmpty() &&
0212                 currentDefault != progId &&
0213                 regUserRoot.value(assocName + QSL("/backup_val")).toString() != progId
0214                ) {
0215                 regUserRoot.setValue(assocName + QSL("/backup_val"), currentDefault);
0216             }
0217             regUserRoot.setValue(assocName + QSL("/."), progId);
0218         }
0219         break;
0220         case UrlAssociation: {
0221             QString progId = _urlAssocHash.value(assocName);
0222             createProgId(progId);
0223             QString currentDefault = regClassesRoot.value(assocName + QSL("/shell/open/command/Default")).toString();
0224             QString command = QSL("\"") + _appPath + QSL("\" \"%1\"");
0225             if (!currentDefault.isEmpty() &&
0226                 currentDefault != command &&
0227                 regUserRoot.value(assocName + QSL("/shell/open/command/backup_val")).toString() != command
0228                ) {
0229                 regUserRoot.setValue(assocName + QSL("/shell/open/command/backup_val"), currentDefault);
0230             }
0231 
0232             regUserRoot.setValue(assocName + QSL("/shell/open/command/."), command);
0233             regUserRoot.setValue(assocName + QSL("/URL Protocol"), QSL(""));
0234             break;
0235         }
0236         default:
0237             break;
0238         }
0239         regUserRoot.endGroup();
0240     }
0241 }
0242 
0243 void RegisterQAppAssociation::registerAllAssociation()
0244 {
0245     if (isVistaOrNewer() && !registerAppCapabilities()) {
0246         return;
0247     }
0248 
0249     QHash<QString, QString>::const_iterator i = _fileAssocHash.constBegin();
0250     while (i != _fileAssocHash.constEnd()) {
0251         registerAssociation(i.key(), FileAssociation);
0252         ++i;
0253     }
0254 
0255     i = _urlAssocHash.constBegin();
0256     while (i != _urlAssocHash.constEnd()) {
0257         registerAssociation(i.key(), UrlAssociation);
0258         ++i;
0259     }
0260 
0261     if (!isVistaOrNewer()) {
0262 #ifndef __MINGW32__
0263         // On Windows Vista or newer for updating icons 'pAAR->SetAppAsDefault()'
0264         // calls 'SHChangeNotify()'. Thus, we just need care about older Windows.
0265         SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSHNOWAIT, 0 , 0);
0266 #endif
0267     }
0268 }
0269 
0270 bool RegisterQAppAssociation::showNativeDefaultAppSettingsUi()
0271 {
0272     if (!isVistaOrNewer()) {
0273         return false;
0274     }
0275 
0276 #ifdef _WIN32_WINNT_WIN8
0277     IApplicationActivationManager* pActivator;
0278     HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager,
0279                                   nullptr,
0280                                   CLSCTX_INPROC,
0281                                   IID_IApplicationActivationManager,
0282                                   (void**)&pActivator);
0283 
0284     if (!SUCCEEDED(hr)) {
0285         return false;
0286     }
0287 
0288     DWORD pid;
0289     hr = pActivator->ActivateApplication(
0290         L"windows.immersivecontrolpanel_cw5n1h2txyewy" // appUserModelId of "Settings"
0291         L"!microsoft.windows.immersivecontrolpanel",   //  in Windows Store
0292         L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
0293 
0294     if (!SUCCEEDED(hr)) {
0295         return false;
0296     }
0297 
0298     // Do not check error because we could at least open
0299     // the "Default apps" setting.
0300     pActivator->ActivateApplication(
0301         L"windows.immersivecontrolpanel_cw5n1h2txyewy"
0302         L"!microsoft.windows.immersivecontrolpanel",
0303         L"page=SettingsPageAppsDefaults"
0304         L"&target=SystemSettings_DefaultApps_Browser", AO_NONE, &pid);
0305 
0306     pActivator->Release();
0307 #else // Vista or Win7
0308     IApplicationAssociationRegistrationUI* pAARUI = NULL;
0309 
0310     HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistrationUI,
0311                                   NULL, CLSCTX_INPROC, __uuidof(IApplicationAssociationRegistrationUI),
0312                                   reinterpret_cast< void** > (&pAARUI));
0313 
0314     if (!SUCCEEDED(hr)) {
0315         return false;
0316     }
0317 
0318     hr = pAARUI->LaunchAdvancedAssociationUI(reinterpret_cast<LPCWSTR>(_appRegisteredName.utf16()));
0319     pAARUI->Release();
0320 #endif // _WIN32_WINNT_WIN8
0321 
0322     return true;
0323 }
0324 
0325 void RegisterQAppAssociation::createProgId(const QString &progId)
0326 {
0327     QSettings regUserRoot(_UserRootKey, QSettings::NativeFormat);
0328     regUserRoot.beginGroup(QSL("Software/Classes"));
0329     QPair<QString, QString> pair = _assocDescHash.value(progId);
0330     regUserRoot.setValue(progId + QSL("/."), pair.first);
0331     regUserRoot.setValue(progId + QSL("/shell/."), QSL("open"));
0332     regUserRoot.setValue(progId + QSL("/DefaultIcon/."), pair.second);
0333     regUserRoot.setValue(progId + QSL("/shell/open/command/."), QString(QSL("\"") + _appPath + QSL("\" \"%1\"")));
0334     regUserRoot.endGroup();
0335 }
0336 
0337 bool RegisterQAppAssociation::isDefaultApp(const QString &assocName, AssociationType type)
0338 {
0339     if (isVistaOrNewer()) {
0340         QSettings regCurrentUserRoot(QSL("HKEY_CURRENT_USER"), QSettings::NativeFormat);
0341         switch (type) {
0342         case FileAssociation: {
0343             regCurrentUserRoot.beginGroup(QSL("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts"));
0344             if (regCurrentUserRoot.childGroups().contains(assocName, Qt::CaseInsensitive)) {
0345                 return (_fileAssocHash.value(assocName)
0346                         == regCurrentUserRoot.value(assocName + QSL("/UserChoice/Progid")));
0347             }
0348             else {
0349                 regCurrentUserRoot.endGroup();
0350                 return false;
0351             }
0352             break;
0353         }
0354         case UrlAssociation: {
0355             regCurrentUserRoot.beginGroup(QSL("Software/Microsoft/Windows/Shell/Associations/UrlAssociations"));
0356             if (regCurrentUserRoot.childGroups().contains(assocName, Qt::CaseInsensitive)) {
0357                 return (_urlAssocHash.value(assocName)
0358                         == regCurrentUserRoot.value(assocName + QSL("/UserChoice/Progid")));
0359             }
0360             else {
0361                 regCurrentUserRoot.endGroup();
0362                 return false;
0363             }
0364         }
0365         break;
0366 
0367         default:
0368             break;
0369         }
0370     }
0371     else {
0372         QSettings regClassesRoot(QSL("HKEY_CLASSES_ROOT"), QSettings::NativeFormat);
0373         {
0374             if (!regClassesRoot.childGroups().contains(assocName, Qt::CaseInsensitive)) {
0375                 return false;
0376             }
0377         }
0378         switch (type) {
0379         case FileAssociation: {
0380             return (_fileAssocHash.value(assocName)
0381                     == regClassesRoot.value(assocName + QSL("/Default")));
0382         }
0383         break;
0384         case UrlAssociation: {
0385             QString currentDefault = regClassesRoot.value(assocName + QSL("/shell/open/command/Default")).toString();
0386             currentDefault.remove(QSL("\""));
0387             currentDefault.remove(QSL("%1"));
0388             currentDefault = currentDefault.trimmed();
0389             return (_appPath == currentDefault);
0390         }
0391         break;
0392 
0393         default:
0394             break;
0395         }
0396     }
0397 
0398     return false;
0399 }
0400 
0401 bool RegisterQAppAssociation::isDefaultForAllCapabilities()
0402 {
0403     bool result = true;
0404     QHash<QString, QString>::const_iterator i = _fileAssocHash.constBegin();
0405     while (i != _fileAssocHash.constEnd()) {
0406         bool res = isDefaultApp(i.key(), FileAssociation);
0407         result &= res;
0408         ++i;
0409     }
0410 
0411     i = _urlAssocHash.constBegin();
0412     while (i != _urlAssocHash.constEnd()) {
0413         bool res = isDefaultApp(i.key(), UrlAssociation);
0414         result &= res;
0415         ++i;
0416     }
0417     return result;
0418 }