blob: 55c99f465434592f0d8b723b9ffad833c232c3f1 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Ivan Vizir <define-true-false@yandex.com>
** 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 <QtCore/qglobal.h>
#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 <QtCore/qdir.h>
#include <QtCore/qdebug.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qregularexpression.h>
#include <QtCore/qt_windows.h>
#include <propvarutil.h>
#include "qwinfunctions.h"
#include "qwinfunctions_p.h"
#include "winpropkey_p.h"
#include <shobjidl.h>
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<arguments.size(); ++i) {
QString tmp = arguments.at(i);
// Quotes are escaped and their preceding backslashes are doubled.
tmp.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\""));
if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
int i = tmp.length();
while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\'))
--i;
tmp.insert(i, QLatin1Char('"'));
tmp.prepend(QLatin1Char('"'));
}
args += QLatin1Char(' ') + 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() + QLatin1Char('/')
+ 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<void **>(&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<QWinJumpListItem *> &items)
{
IObjectCollection *collection = toComCollection(items);
if (collection) {
HRESULT hresult = pDestList->AddUserTasks(collection);
if (FAILED(hresult))
QWinJumpListPrivate::warning("AddUserTasks", hresult);
collection->Release();
}
}
QList<QWinJumpListItem *> QWinJumpListPrivate::fromComCollection(IObjectArray *array)
{
QList<QWinJumpListItem *> 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<void **>(&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<void **>(&shellItem)))) {
jumplistItem = fromIShellItem(shellItem);
shellItem->Release();
} else if (SUCCEEDED(collectionItem->QueryInterface(qIID_IShellLinkW, reinterpret_cast<void **>(&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<QWinJumpListItem *> &list)
{
if (list.isEmpty())
return nullptr;
IObjectCollection *collection = nullptr;
HRESULT hresult = CoCreateInstance(qCLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, qIID_IObjectCollection, reinterpret_cast<void **>(&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<void **>(&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<void **>(&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<quintptr>(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<void **>(&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<wchar_t> buffer(qt_qstringToNullTerminated(item->filePath()));
SHCreateItemFromParsingName(buffer.data(), nullptr, qIID_IShellItem2, reinterpret_cast<void **>(&shellitem));
return shellitem;
}
IShellLinkW *QWinJumpListPrivate::makeSeparatorShellItem()
{
IShellLinkW *separator;
HRESULT res = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, qIID_IShellLinkW, reinterpret_cast<void **>(&separator));
if (FAILED(res))
return nullptr;
IPropertyStore *properties;
res = separator->QueryInterface(qIID_IPropertyStore, reinterpret_cast<void **>(&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<void **>(&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(QLatin1Char(' '), QLatin1Char('_'));
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<QWinJumpList *>(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<QWinJumpList *>(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<QWinJumpList *>(this);
that->d_func()->tasks = QWinJumpListCategoryPrivate::create(QWinJumpListCategory::Tasks, that);
}
return d->tasks;
}
/*!
Returns the custom categories in the jump list.
*/
QList<QWinJumpListCategory *> 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<QWinJumpListItem *> 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"