Просмотр исходного кода

Windows: Add sync folders to Explorer's navigation pane

This is only the navigation pane, the SyncRootManager entries aren't handled yet.

This follows the instructions from:
https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx

Issue #5295
Jocelyn Turcotte 8 лет назад
Родитель
Сommit
56e38e6f80

+ 3 - 0
CMakeLists.txt

@@ -213,6 +213,9 @@ add_definitions(-D_UNICODE)
 if( WIN32 )
 add_definitions( -D__USE_MINGW_ANSI_STDIO=1 )
 add_definitions( -DNOMINMAX )
+# Get APIs from from Vista onwards.
+add_definitions( -D_WIN32_WINNT=0x0600)
+add_definitions( -DWINVER=0x0600)
 endif( WIN32 )
 
 include(QtVersionAbstraction)

+ 13 - 0
src/common/utility.h

@@ -28,8 +28,13 @@
 #include <QLoggingCategory>
 #include <QMap>
 #include <QUrl>
+#include <functional>
 #include <memory>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#endif
+
 class QSettings;
 
 namespace OCC {
@@ -187,6 +192,14 @@ namespace Utility {
      * Experimental! Real feature planned for 2.5.
      */
     OCSYNC_EXPORT bool shouldUploadConflictFiles();
+
+#ifdef Q_OS_WIN
+    OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
+    OCSYNC_EXPORT bool registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value);
+    OCSYNC_EXPORT bool registryDeleteKeyTree(HKEY hRootKey, const QString &subKey);
+    OCSYNC_EXPORT bool registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
+    OCSYNC_EXPORT bool registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback);
+#endif
 }
 /** @} */ // \addtogroup
 

+ 165 - 2
src/common/utility_win.cpp

@@ -16,8 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#define _WIN32_WINNT 0x0600
-#define WINVER 0x0600
+#include "asserts.h"
 #include <shlobj.h>
 #include <winbase.h>
 #include <windows.h>
@@ -93,4 +92,168 @@ static inline bool hasDarkSystray_private()
     return true;
 }
 
+QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
+{
+    QVariant value;
+
+    HKEY hKey;
+
+    REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
+    LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
+    ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+    if (result != ERROR_SUCCESS)
+        return value;
+
+    DWORD type = 0, sizeInBytes = 0;
+    result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, nullptr, &sizeInBytes);
+    ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+    if (result == ERROR_SUCCESS) {
+        switch (type) {
+        case REG_DWORD:
+            DWORD dword;
+            Q_ASSERT(sizeInBytes == sizeof(dword));
+            if (RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, reinterpret_cast<LPBYTE>(&dword), &sizeInBytes) == ERROR_SUCCESS) {
+                value = int(dword);
+            }
+            break;
+        case REG_EXPAND_SZ:
+        case REG_SZ: {
+            QString string;
+            string.resize(sizeInBytes / sizeof(QChar));
+            result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, reinterpret_cast<LPBYTE>(string.data()), &sizeInBytes);
+
+            if (result == ERROR_SUCCESS) {
+                int newCharSize = sizeInBytes / sizeof(QChar);
+                // From the doc:
+                // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with
+                // the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS,
+                // the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer.
+                if (string.at(newCharSize - 1) == QChar('\0'))
+                    string.resize(newCharSize - 1);
+                value = string;
+            }
+            break;
+        }
+        default:
+            Q_UNREACHABLE();
+        }
+    }
+    ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+
+    RegCloseKey(hKey);
+    return value;
+}
+
+bool Utility::registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value)
+{
+    HKEY hKey;
+    // KEY_WOW64_64KEY is necessary because CLSIDs are "Redirected and reflected only for CLSIDs that do not specify InprocServer32 or InprocHandler32."
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253%28v=vs.85%29.aspx#redirected__shared__and_reflected_keys_under_wow64
+    // This shouldn't be an issue in our case since we use shell32.dll as InprocServer32, so we could write those registry keys for both 32 and 64bit.
+    // FIXME: Not doing so at the moment means that explorer will show the cloud provider, but 32bit processes' open dialogs (like the ownCloud client itself) won't show it.
+    REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
+    LONG result = RegCreateKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, nullptr, 0, sam, nullptr, &hKey, nullptr);
+    ASSERT(result == ERROR_SUCCESS);
+    if (result != ERROR_SUCCESS)
+        return false;
+
+    result = -1;
+    switch (type) {
+    case REG_DWORD: {
+        DWORD dword = value.toInt();
+        result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, type, reinterpret_cast<const BYTE *>(&dword), sizeof(dword));
+        break;
+    }
+    case REG_EXPAND_SZ:
+    case REG_SZ: {
+        QString string = value.toString();
+        result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, type, reinterpret_cast<const BYTE *>(string.constData()), (string.size() + 1) * sizeof(QChar));
+        break;
+    }
+    default:
+        Q_UNREACHABLE();
+    }
+    ASSERT(result == ERROR_SUCCESS);
+
+    RegCloseKey(hKey);
+    return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryDeleteKeyTree(HKEY hRootKey, const QString &subKey)
+{
+    HKEY hKey;
+    REGSAM sam = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_64KEY;
+    LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
+    ASSERT(result == ERROR_SUCCESS);
+    if (result != ERROR_SUCCESS)
+        return false;
+
+    result = RegDeleteTree(hKey, nullptr);
+    RegCloseKey(hKey);
+    ASSERT(result == ERROR_SUCCESS);
+
+    result |= RegDeleteKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), sam, 0);
+    ASSERT(result == ERROR_SUCCESS);
+
+    return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
+{
+    HKEY hKey;
+    REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
+    LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
+    ASSERT(result == ERROR_SUCCESS);
+    if (result != ERROR_SUCCESS)
+        return false;
+
+    result = RegDeleteValue(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()));
+    ASSERT(result == ERROR_SUCCESS);
+
+    RegCloseKey(hKey);
+    return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback)
+{
+    HKEY hKey;
+    REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
+    LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
+    ASSERT(result == ERROR_SUCCESS);
+    if (result != ERROR_SUCCESS)
+        return false;
+
+    DWORD maxSubKeyNameSize;
+    // Get the largest keyname size once instead of relying each call on ERROR_MORE_DATA.
+    result = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, nullptr, &maxSubKeyNameSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
+    ASSERT(result == ERROR_SUCCESS);
+    if (result != ERROR_SUCCESS) {
+        RegCloseKey(hKey);
+        return false;
+    }
+
+    QString subKeyName;
+    subKeyName.reserve(maxSubKeyNameSize + 1);
+
+    DWORD retCode = ERROR_SUCCESS;
+    for (DWORD i = 0; retCode == ERROR_SUCCESS; ++i) {
+        Q_ASSERT(unsigned(subKeyName.capacity()) > maxSubKeyNameSize);
+        // Make the previously reserved capacity official again.
+        subKeyName.resize(subKeyName.capacity());
+        DWORD subKeyNameSize = subKeyName.size();
+        retCode = RegEnumKeyEx(hKey, i, reinterpret_cast<LPWSTR>(subKeyName.data()), &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
+
+        ASSERT(result == ERROR_SUCCESS || retCode == ERROR_NO_MORE_ITEMS);
+        if (retCode == ERROR_SUCCESS) {
+            // subKeyNameSize excludes the trailing \0
+            subKeyName.resize(subKeyNameSize);
+            // Pass only the sub keyname, not the full path.
+            callback(hKey, subKeyName);
+        }
+    }
+
+    RegCloseKey(hKey);
+    return retCode != ERROR_NO_MORE_ITEMS;
+}
+
 } // namespace OCC

+ 1 - 0
src/gui/CMakeLists.txt

@@ -57,6 +57,7 @@ set(client_SRCS
     ignorelisteditor.cpp
     lockwatcher.cpp
     logbrowser.cpp
+    navigationpanehelper.cpp
     networksettings.cpp
     ocsjob.cpp
     ocssharejob.cpp

+ 3 - 0
src/gui/accountsettings.cpp

@@ -401,6 +401,9 @@ void AccountSettings::slotFolderWizardAccepted()
      */
     definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
 
+    // FIXME: Make this depend on a checkbox in settings.
+    definition.navigationPaneClsid = QUuid::createUuid();
+
     auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList").toStringList();
 
     folderMan->setSyncEnabled(true);

+ 7 - 0
src/gui/folder.cpp

@@ -1075,6 +1075,12 @@ void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder)
     settings.setValue(QLatin1String("targetPath"), folder.targetPath);
     settings.setValue(QLatin1String("paused"), folder.paused);
     settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
+
+    // Happens only on Windows when the explorer integration is enabled.
+    if (!folder.navigationPaneClsid.isNull())
+        settings.setValue(QLatin1String("navigationPaneClsid"), folder.navigationPaneClsid);
+    else
+        settings.remove(QLatin1String("navigationPaneClsid"));
     settings.endGroup();
 }
 
@@ -1088,6 +1094,7 @@ bool FolderDefinition::load(QSettings &settings, const QString &alias,
     folder->targetPath = settings.value(QLatin1String("targetPath")).toString();
     folder->paused = settings.value(QLatin1String("paused")).toBool();
     folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
+    folder->navigationPaneClsid = settings.value(QLatin1String("navigationPaneClsid")).toUuid();
     settings.endGroup();
 
     // Old settings can contain paths with native separators. In the rest of the

+ 6 - 0
src/gui/folder.h

@@ -27,6 +27,7 @@
 
 #include <QObject>
 #include <QStringList>
+#include <QUuid>
 #include <set>
 
 class QThread;
@@ -64,6 +65,8 @@ public:
     bool paused;
     /// whether the folder syncs hidden files
     bool ignoreHiddenFiles;
+    /// The CLSID where this folder appears in registry for the Explorer navigation pane entry.
+    QUuid navigationPaneClsid;
 
     /// Saves the folder definition, creating a new settings group.
     static void save(QSettings &settings, const FolderDefinition &folder);
@@ -135,6 +138,9 @@ public:
      */
     QString remotePath() const;
 
+    void setNavigationPaneClsid(const QUuid &clsid) { _definition.navigationPaneClsid = clsid; }
+    QUuid navigationPaneClsid() const { return _definition.navigationPaneClsid; }
+
     /**
      * remote folder path with server url
      */

+ 6 - 0
src/gui/folderman.cpp

@@ -49,6 +49,7 @@ FolderMan::FolderMan(QObject *parent)
     , _currentSyncFolder(0)
     , _syncEnabled(true)
     , _lockWatcher(new LockWatcher)
+    , _navigationPaneHelper(this)
     , _appRestartRequired(false)
 {
     ASSERT(!_instance);
@@ -894,6 +895,9 @@ Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition
         emit folderSyncStateChange(folder);
         emit folderListChanged(_folderMap);
     }
+
+    _navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
+
     return folder;
 }
 
@@ -1003,6 +1007,8 @@ void FolderMan::removeFolder(Folder *f)
         delete f;
     }
 
+    _navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
+
     emit folderListChanged(_folderMap);
 }
 

+ 3 - 0
src/gui/folderman.h

@@ -22,6 +22,7 @@
 
 #include "folder.h"
 #include "folderwatcher.h"
+#include "navigationpanehelper.h"
 #include "syncfileitem.h"
 
 class TestFolderMan;
@@ -115,6 +116,7 @@ public:
     static QString unescapeAlias(const QString &);
 
     SocketApi *socketApi();
+    NavigationPaneHelper &navigationPaneHelper() { return _navigationPaneHelper; }
 
     /**
      * Check if @a path is a valid path for a new folder considering the already sync'ed items.
@@ -315,6 +317,7 @@ private:
     QTimer _startScheduledSyncTimer;
 
     QScopedPointer<SocketApi> _socketApi;
+    NavigationPaneHelper _navigationPaneHelper;
 
     bool _appRestartRequired;
 

+ 133 - 0
src/gui/navigationpanehelper.cpp

@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "navigationpanehelper.h"
+#include "accountmanager.h"
+#include "folderman.h"
+
+#include <QDir>
+#include <QCoreApplication>
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcNavPane, "gui.folder.navigationpane", QtInfoMsg)
+
+NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
+    : _folderMan(folderMan)
+{
+    _updateCloudStorageRegistryTimer.setSingleShot(true);
+    connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
+}
+
+void NavigationPaneHelper::scheduleUpdateCloudStorageRegistry()
+{
+    // Schedule the update to happen a bit later to avoid doing the update multiple times in a row.
+    if (!_updateCloudStorageRegistryTimer.isActive())
+        _updateCloudStorageRegistryTimer.start(500);
+}
+
+void NavigationPaneHelper::updateCloudStorageRegistry()
+{
+    // Start by looking at every registered namespace extension for the sidebar, and look for an "ApplicationName" value
+    // that matches ours when we saved.
+    QVector<QUuid> entriesToRemove;
+#ifdef Q_OS_WIN
+    Utility::registryWalkSubKeys(
+        HKEY_CURRENT_USER,
+        QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace"),
+        [&entriesToRemove](HKEY key, const QString &subKey) {
+            QVariant appName = Utility::registryGetKeyValue(key, subKey, QStringLiteral("ApplicationName"));
+            if (appName.toString() == QLatin1String(APPLICATION_NAME)) {
+                QUuid clsid{ subKey };
+                Q_ASSERT(!clsid.isNull());
+                entriesToRemove.append(clsid);
+            }
+        });
+#endif
+
+    // Then re-save every folder that has a valid navigationPaneClsid to the registry.
+    // We currently don't distinguish between new and existing CLSIDs, if it's there we just
+    // save over it. We at least need to update the tile in case we are suddently using multiple accounts.
+    foreach (Folder *folder, _folderMan->map()) {
+        if (!folder->navigationPaneClsid().isNull()) {
+            // If it already exists, unmark it for removal, this is a valid sync root.
+            entriesToRemove.removeOne(folder->navigationPaneClsid());
+
+            QString clsidStr = folder->navigationPaneClsid().toString();
+            QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
+            QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
+
+            QString title = folder->shortGuiRemotePathOrAppName();
+            // Write the account name in the sidebar only when using more than one account.
+            if (AccountManager::instance()->accounts().size() > 1)
+                title = title % " - " % folder->accountState()->account()->displayName();
+            QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
+            QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
+
+            qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
+#ifdef Q_OS_WIN
+            // Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
+            // Step 1: Add your CLSID and name your extension
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
+            // Step 2: Set the image for your icon
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
+            // Step 3: Add your extension to the Navigation Pane and make it visible
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
+            // Step 4: Set the location for your extension in the Navigation Pane
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
+            // Step 5: Provide the dll that hosts your extension.
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
+            // Step 6: Define the instance object
+            // Indicate that your namespace extension should function like other file folder structures in File Explorer.
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
+            // Step 7: Provide the file system attributes of the target folder
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
+            // Step 8: Set the path for the sync root
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
+            // Step 9: Set appropriate shell flags
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
+            // Step 10: Set the appropriate flags to control your shell behavior
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
+            // Step 11: Register your extension in the namespace root
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
+            // Step 12: Hide your extension from the Desktop
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
+
+            // For us, to later be able to iterate and find our own namespace entries and associated CLSID.
+            // Use the macro instead of the theme to make sure it matches with the uninstaller.
+            Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
+#else
+            // This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
+            // Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
+            Q_ASSERT(false);
+#endif
+        }
+    }
+
+    // Then remove anything that isn't in our folder list anymore.
+    foreach (auto &clsid, entriesToRemove) {
+        QString clsidStr = clsid.toString();
+        QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
+        QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
+
+        qCInfo(lcNavPane) << "Explorer Cloud storage provider: now unused, removing own CLSID" << clsidStr;
+#ifdef Q_OS_WIN
+        Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPath);
+        Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, namespacePath);
+        Utility::registryDeleteKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr);
+#endif
+    }
+}
+
+} // namespace OCC

+ 42 - 0
src/gui/navigationpanehelper.h

@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+
+#ifndef NAVIGATIONPANEHELPER_H
+#define NAVIGATIONPANEHELPER_H
+
+#include <QObject>
+#include <QTimer>
+
+namespace OCC {
+
+class FolderMan;
+
+class NavigationPaneHelper : public QObject
+{
+    Q_OBJECT
+public:
+    NavigationPaneHelper(FolderMan *folderMan);
+
+    void scheduleUpdateCloudStorageRegistry();
+
+private:
+    void updateCloudStorageRegistry();
+
+    FolderMan *_folderMan;
+    QTimer _updateCloudStorageRegistryTimer;
+};
+
+} // namespace OCC
+#endif // NAVIGATIONPANEHELPER_H

+ 2 - 0
src/gui/owncloudsetupwizard.cpp

@@ -579,6 +579,8 @@ void OwncloudSetupWizard::slotAssistantFinished(int result)
             folderDefinition.localPath = localFolder;
             folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
             folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
+            // FIXME: Make this depend on a checkbox in settings.
+            folderDefinition.navigationPaneClsid = QUuid::createUuid();
 
             auto f = folderMan->addFolder(account, folderDefinition);
             if (f) {

+ 1 - 0
test/CMakeLists.txt

@@ -65,6 +65,7 @@ list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
 list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
 list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
 list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
+list(APPEND FolderMan_SRC ../src/gui/navigationpanehelper.cpp )
 list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
 list(APPEND FolderMan_SRC stub.cpp )
 owncloud_add_test(FolderMan "${FolderMan_SRC}")