/**************************************************************************** ** ** Copyright (C) 2016 Ivan Vizir ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWinExtras module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #ifdef Q_CC_MINGW // MinGW: Enable SHCreateItemFromParsingName() # if defined(_WIN32_IE) && _WIN32_IE < 0x0700 // _WIN32_IE_IE70 # undef _WIN32_IE # endif # ifndef _WIN32_IE # define _WIN32_IE 0x0700 # endif #endif // Q_CC_MINGW #include "qwinjumplist.h" #include "qwinjumplist_p.h" #include "qwinjumplistitem.h" #include "qwinjumplistcategory.h" #include "qwinjumplistcategory_p.h" #include "windowsguidsdefs_p.h" #include "winpropkey_p.h" #include #include #include #include #include #include #include "qwinfunctions.h" #include "qwinfunctions_p.h" #include "winpropkey_p.h" #include QT_BEGIN_NAMESPACE /*! \class QWinJumpList \inmodule QtWinExtras \brief The QWinJumpList class represents a transparent wrapper around Windows Jump Lists. \since 5.2 An application can use Jump Lists to provide users with faster access to files or to display shortcuts to tasks or commands. */ /*! \title Application User Model IDs \externalpage http://msdn.microsoft.com/en-us/library/windows/desktop/dd378459%28v=vs.85%29.aspx */ // partial copy of qprocess_win.cpp:qt_create_commandline() static QString createArguments(const QStringList &arguments) { QString args; for (int i=0; i 0 && tmp.at(i - 1) == u'\\') --i; tmp.insert(i, u'"'); tmp.prepend(u'"'); } args += u' ' + tmp; } return args; } void QWinJumpListPrivate::warning(const char *function, HRESULT hresult) { const QString err = QtWin::errorStringFromHresult(hresult); qWarning("QWinJumpList: %s() failed: %#010x, %s.", function, unsigned(hresult), qPrintable(err)); } QString QWinJumpListPrivate::iconsDirPath() { QString iconDirPath = QDir::tempPath() + u'/' + QCoreApplication::applicationName() + QLatin1String("/qt-jl-icons/"); QDir().mkpath(iconDirPath); return iconDirPath; } void QWinJumpListPrivate::invalidate() { Q_Q(QWinJumpList); if (!pDestList) return; if (!dirty) { dirty = true; QMetaObject::invokeMethod(q, "_q_rebuild", Qt::QueuedConnection); } } void QWinJumpListPrivate::_q_rebuild() { if (beginList()) { if (recent && recent->isVisible()) appendKnownCategory(KDC_RECENT); if (frequent && frequent->isVisible()) appendKnownCategory(KDC_FREQUENT); for (QWinJumpListCategory *category : qAsConst(categories)) { if (category->isVisible()) appendCustomCategory(category); } if (tasks && tasks->isVisible()) appendTasks(tasks->items()); commitList(); } dirty = false; } void QWinJumpListPrivate::destroy() { delete recent; recent = nullptr; delete frequent; frequent = nullptr; delete tasks; tasks = nullptr; qDeleteAll(categories); categories.clear(); invalidate(); } bool QWinJumpListPrivate::beginList() { HRESULT hresult = S_OK; if (!identifier.isEmpty()) { wchar_t *id = qt_qstringToNullTerminated(identifier); hresult = pDestList->SetAppID(id); delete[] id; } if (SUCCEEDED(hresult)) { UINT maxSlots = 0; IUnknown *array = nullptr; hresult = pDestList->BeginList(&maxSlots, qIID_IUnknown, reinterpret_cast(&array)); if (array) array->Release(); } if (FAILED(hresult)) QWinJumpListPrivate::warning("BeginList", hresult); return SUCCEEDED(hresult); } bool QWinJumpListPrivate::commitList() { HRESULT hresult = pDestList->CommitList(); if (FAILED(hresult)) QWinJumpListPrivate::warning("CommitList", hresult); return SUCCEEDED(hresult); } void QWinJumpListPrivate::appendKnownCategory(KNOWNDESTCATEGORY category) { HRESULT hresult = pDestList->AppendKnownCategory(category); if (FAILED(hresult)) QWinJumpListPrivate::warning("AppendKnownCategory", hresult); } void QWinJumpListPrivate::appendCustomCategory(QWinJumpListCategory *category) { IObjectCollection *collection = toComCollection(category->items()); if (collection) { wchar_t *title = qt_qstringToNullTerminated(category->title()); HRESULT hresult = pDestList->AppendCategory(title, collection); if (FAILED(hresult)) QWinJumpListPrivate::warning("AppendCategory", hresult); delete[] title; collection->Release(); } } void QWinJumpListPrivate::appendTasks(const QList &items) { IObjectCollection *collection = toComCollection(items); if (collection) { HRESULT hresult = pDestList->AddUserTasks(collection); if (FAILED(hresult)) QWinJumpListPrivate::warning("AddUserTasks", hresult); collection->Release(); } } QList QWinJumpListPrivate::fromComCollection(IObjectArray *array) { QList list; UINT count = 0; array->GetCount(&count); for (UINT i = 0; i < count; ++i) { IUnknown *collectionItem = nullptr; HRESULT hresult = array->GetAt(i, qIID_IUnknown, reinterpret_cast(&collectionItem)); if (FAILED(hresult)) { QWinJumpListPrivate::warning("GetAt", hresult); continue; } IShellItem2 *shellItem = nullptr; IShellLinkW *shellLink = nullptr; QWinJumpListItem *jumplistItem = nullptr; if (SUCCEEDED(collectionItem->QueryInterface(qIID_IShellItem2, reinterpret_cast(&shellItem)))) { jumplistItem = fromIShellItem(shellItem); shellItem->Release(); } else if (SUCCEEDED(collectionItem->QueryInterface(qIID_IShellLinkW, reinterpret_cast(&shellLink)))) { jumplistItem = fromIShellLink(shellLink); shellLink->Release(); } else { qWarning("QWinJumpList: object of unexpected class found"); } collectionItem->Release(); if (jumplistItem) list.append(jumplistItem); } return list; } IObjectCollection *QWinJumpListPrivate::toComCollection(const QList &list) { if (list.isEmpty()) return nullptr; IObjectCollection *collection = nullptr; HRESULT hresult = CoCreateInstance(qCLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, qIID_IObjectCollection, reinterpret_cast(&collection)); if (FAILED(hresult)) { QWinJumpListPrivate::warning("QWinJumpList: failed to instantiate IObjectCollection", hresult); return nullptr; } for (QWinJumpListItem *item : list) { IUnknown *iitem = toICustomDestinationListItem(item); if (iitem) { collection->AddObject(iitem); iitem->Release(); } } return collection; } QWinJumpListItem *QWinJumpListPrivate::fromIShellLink(IShellLinkW *link) { auto *item = new QWinJumpListItem(QWinJumpListItem::Link); IPropertyStore *linkProps; link->QueryInterface(qIID_IPropertyStore, reinterpret_cast(&linkProps)); PROPVARIANT var; linkProps->GetValue(qPKEY_Link_Arguments, &var); item->setArguments(QStringList(QString::fromWCharArray(var.pwszVal))); PropVariantClear(&var); linkProps->Release(); const int buffersize = 2048; wchar_t buffer[buffersize]; link->GetDescription(buffer, INFOTIPSIZE); item->setDescription(QString::fromWCharArray(buffer)); int dummyindex; link->GetIconLocation(buffer, buffersize-1, &dummyindex); item->setIcon(QIcon(QString::fromWCharArray(buffer))); link->GetPath(buffer, buffersize-1, 0, 0); item->setFilePath(QDir::fromNativeSeparators(QString::fromWCharArray(buffer))); return item; } QWinJumpListItem *QWinJumpListPrivate::fromIShellItem(IShellItem2 *shellitem) { auto *item = new QWinJumpListItem(QWinJumpListItem::Destination); wchar_t *strPtr; shellitem->GetDisplayName(SIGDN_FILESYSPATH, &strPtr); item->setFilePath(QDir::fromNativeSeparators(QString::fromWCharArray(strPtr))); CoTaskMemFree(strPtr); return item; } IUnknown *QWinJumpListPrivate::toICustomDestinationListItem(const QWinJumpListItem *item) { switch (item->type()) { case QWinJumpListItem::Destination : return toIShellItem(item); case QWinJumpListItem::Link : return toIShellLink(item); case QWinJumpListItem::Separator : return makeSeparatorShellItem(); default: return nullptr; } } IShellLinkW *QWinJumpListPrivate::toIShellLink(const QWinJumpListItem *item) { IShellLinkW *link = nullptr; HRESULT hresult = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, qIID_IShellLinkW, reinterpret_cast(&link)); if (FAILED(hresult)) { QWinJumpListPrivate::warning("QWinJumpList: failed to instantiate IShellLinkW", hresult); return nullptr; } const QString args = createArguments(item->arguments()); const int iconPathSize = QWinJumpListPrivate::iconsDirPath().size() + int(sizeof(void *)) * 2 + 4; // path + ptr-name-in-hex + .ico const int bufferSize = qMax(args.size(), qMax(item->workingDirectory().size(), qMax(item->description().size(), qMax(item->title().size(), qMax(item->filePath().size(), iconPathSize))))) + 1; auto *buffer = new wchar_t[bufferSize]; if (!item->description().isEmpty()) { qt_qstringToNullTerminated(item->description(), buffer); link->SetDescription(buffer); } qt_qstringToNullTerminated(item->filePath(), buffer); link->SetPath(buffer); if (!item->workingDirectory().isEmpty()) { qt_qstringToNullTerminated(item->workingDirectory(), buffer); link->SetWorkingDirectory(buffer); } qt_qstringToNullTerminated(args, buffer); link->SetArguments(buffer); if (!item->icon().isNull()) { QString iconPath = QWinJumpListPrivate::iconsDirPath() + QString::number(reinterpret_cast(item), 16) + QLatin1String(".ico"); bool iconSaved = item->icon().pixmap(GetSystemMetrics(SM_CXICON)).save(iconPath, "ico"); if (iconSaved) { qt_qstringToNullTerminated(iconPath, buffer); link->SetIconLocation(buffer, 0); } } IPropertyStore *properties; PROPVARIANT titlepv; hresult = link->QueryInterface(qIID_IPropertyStore, reinterpret_cast(&properties)); if (FAILED(hresult)) { link->Release(); return nullptr; } qt_qstringToNullTerminated(item->title(), buffer); InitPropVariantFromString(buffer, &titlepv); properties->SetValue(qPKEY_Title, titlepv); properties->Commit(); properties->Release(); PropVariantClear(&titlepv); delete[] buffer; return link; } IShellItem2 *QWinJumpListPrivate::toIShellItem(const QWinJumpListItem *item) { IShellItem2 *shellitem = nullptr; QScopedArrayPointer buffer(qt_qstringToNullTerminated(item->filePath())); SHCreateItemFromParsingName(buffer.data(), nullptr, qIID_IShellItem2, reinterpret_cast(&shellitem)); return shellitem; } IShellLinkW *QWinJumpListPrivate::makeSeparatorShellItem() { IShellLinkW *separator; HRESULT res = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, qIID_IShellLinkW, reinterpret_cast(&separator)); if (FAILED(res)) return nullptr; IPropertyStore *properties; res = separator->QueryInterface(qIID_IPropertyStore, reinterpret_cast(&properties)); if (FAILED(res)) { separator->Release(); return nullptr; } PROPVARIANT isSeparator; InitPropVariantFromBoolean(TRUE, &isSeparator); properties->SetValue(qPKEY_AppUserModel_IsDestListSeparator, isSeparator); properties->Commit(); properties->Release(); PropVariantClear(&isSeparator); return separator; } /*! Constructs a QWinJumpList with the parent object \a parent. */ QWinJumpList::QWinJumpList(QObject *parent) : QObject(parent), d_ptr(new QWinJumpListPrivate) { Q_D(QWinJumpList); d->q_ptr = this; HRESULT hresult = CoCreateInstance(qCLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, qIID_ICustomDestinationList, reinterpret_cast(&d_ptr->pDestList)); if (FAILED(hresult)) QWinJumpListPrivate::warning("CoCreateInstance", hresult); d->invalidate(); } /*! Destroys the QWinJumpList. */ QWinJumpList::~QWinJumpList() { Q_D(QWinJumpList); if (d->dirty) d->_q_rebuild(); if (d->pDestList) { d->pDestList->Release(); d->pDestList = nullptr; } d->destroy(); } /*! \property QWinJumpList::identifier \brief the jump list identifier Specifies an optional explicit unique identifier for the application jump list. The default value is empty; a system-defined internal identifier is used instead. See \l {Application User Model IDs} on MSDN for further details. \note The identifier cannot have more than \c 128 characters and cannot contain spaces. A too long identifier is automatically truncated to \c 128 characters, and spaces are replaced by underscores. */ QString QWinJumpList::identifier() const { Q_D(const QWinJumpList); return d->identifier; } void QWinJumpList::setIdentifier(const QString &identifier) { Q_D(QWinJumpList); QString id = identifier; id.replace(u' ', u'_'); if (id.size() > 128) id.truncate(128); if (d->identifier != id) { d->identifier = id; d->invalidate(); } } /*! Returns the recent items category in the jump list. */ QWinJumpListCategory *QWinJumpList::recent() const { Q_D(const QWinJumpList); if (!d->recent) { auto *that = const_cast(this); that->d_func()->recent = QWinJumpListCategoryPrivate::create(QWinJumpListCategory::Recent, that); } return d->recent; } /*! Returns the frequent items category in the jump list. */ QWinJumpListCategory *QWinJumpList::frequent() const { Q_D(const QWinJumpList); if (!d->frequent) { auto *that = const_cast(this); that->d_func()->frequent = QWinJumpListCategoryPrivate::create(QWinJumpListCategory::Frequent, that); } return d->frequent; } /*! Returns the tasks category in the jump list. */ QWinJumpListCategory *QWinJumpList::tasks() const { Q_D(const QWinJumpList); if (!d->tasks) { auto *that = const_cast(this); that->d_func()->tasks = QWinJumpListCategoryPrivate::create(QWinJumpListCategory::Tasks, that); } return d->tasks; } /*! Returns the custom categories in the jump list. */ QList QWinJumpList::categories() const { Q_D(const QWinJumpList); return d->categories; } /*! Adds a custom \a category to the jump list. */ void QWinJumpList::addCategory(QWinJumpListCategory *category) { Q_D(QWinJumpList); if (!category) return; QWinJumpListCategoryPrivate::get(category)->jumpList = this; d->categories.append(category); d->invalidate(); } /*! \overload addCategory() Creates a custom category with provided \a title and optional \a items, and adds it to the jump list. */ QWinJumpListCategory *QWinJumpList::addCategory(const QString &title, const QList items) { auto *category = new QWinJumpListCategory(title); for (QWinJumpListItem *item : items) category->addItem(item); addCategory(category); return category; } /*! Clears the jump list. \sa QWinJumpListCategory::clear() */ void QWinJumpList::clear() { Q_D(QWinJumpList); recent()->clear(); frequent()->clear(); if (d->tasks) d->tasks->clear(); for (QWinJumpListCategory *category : qAsConst(d->categories)) category->clear(); d->destroy(); } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, const QWinJumpList *jumplist) { QDebugStateSaver saver(debug); debug.nospace(); debug.noquote(); debug << "QWinJumpList("; if (jumplist) { debug << "(identifier=\"" << jumplist->identifier() << "\", recent=" << jumplist->recent() << ", frequent=" << jumplist->frequent() << ", tasks=" << jumplist->tasks() << ", categories=" << jumplist->categories(); } else { debug << '0'; } debug << ')'; return debug; } #endif // !QT_NO_DEBUG_STREAM QT_END_NAMESPACE #include "moc_qwinjumplist.cpp"