Add UNO API for custom Jump Lists

Allows adding custom jump list categories to Windows Task Bar

Change-Id: I13b6c3ad5de386cf74e2b346f10889bc46a8ad4e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131540
Tested-by: Jenkins
Reviewed-by: Samuel Mehrbrodt <samuel.mehrbrodt@allotropia.de>
This commit is contained in:
Samuel Mehrbrodt 2022-03-14 10:04:17 +01:00
parent ab9422621b
commit 7efd22c912
10 changed files with 631 additions and 0 deletions

View file

@ -414,6 +414,7 @@ $(eval $(call gb_Helper_register_libraries_for_install,OOOLIBS,ooo, \
i18npool \
i18nsearch \
$(if $(ENABLE_JAVA),jdbc) \
$(if $(filter WNT,$(OS)),jumplist) \
$(if $(ENABLE_LDAP),ldapbe2) \
$(if $(filter WNT,$(OS)),WinUserInfoBe) \
localebe1 \

View file

@ -630,6 +630,7 @@ certain functionality.
@li @c scripting
@li @c scripting.provider
@li @c shell
@li @c shell.jumplist
@li @c stoc
@li @c store
@li @c svg

View file

@ -3639,6 +3639,13 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/system,\
XSimpleMailMessage2 \
XSystemShellExecute \
))
$(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/system/windows,\
JumpList \
JumpListItem \
XJumpList \
))
$(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/table,\
BorderLine \
BorderLine2 \

View file

@ -0,0 +1,33 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __com_sun_star_system_windows_JumpList_idl__
#define __com_sun_star_system_windows_JumpList_idl__
module com { module sun { module star { module system { module windows {
interface XJumpList;
/** Specifies a Jump List service. Allows to add custom commands to the Windows Jump List.
@since LibreOffice 7.4
@see com::sun::star::system::windows::XJumpList
*/
service JumpList : XJumpList;
}; }; }; }; };
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __com_sun_star_system_windows_JumpListItem_idl__
#define __com_sun_star_system_windows_JumpListItem_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/IllegalArgumentException.idl>
#include <com/sun/star/uno/Sequence.idl>
module com { module sun { module star { module system { module windows {
/** Specifies an item for com::sun::star::system::windows::XJumpList .
@since LibreOffice 7.4
*/
struct JumpListItem
{
/** Item name. Appears in the JumpList.
Has to be unique per category.
Must not include embedded NULs ('\\0'). */
string name;
/** Item description, appears as tooltip.
Must not include embedded NULs ('\\0').
*/
string description;
/** Arguments to be passed to LibreOffice.
This can be a file to be loaded, or any command line parameter supported by LibreOffice, and any combination of the two.
Add multiple arguments separated by space.
Must not include embedded NULs ('\\0').
*/
string arguments;
/** Icon to be displayed along the name.
Must be a local path name like `C:\\path\\to\\icon.ico`.
Icon must be in ICO format.
*/
string iconPath;
};
}; }; }; }; };
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View file

@ -0,0 +1,125 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __com_sun_star_system_windows_XJumpList_idl__
#define __com_sun_star_system_windows_XJumpList_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/IllegalArgumentException.idl>
#include <com/sun/star/system/SystemShellExecuteException.idl>
module com { module sun { module star { module system { module windows {
/** Specifies an interface for adding custom jump lists to the task bar (Windows only)
@since LibreOffice 7.4
*/
interface XJumpList: com::sun::star::uno::XInterface
{
/** Add (or update) a jump list category.
Note that it is only possible to have one jump list category per `application`.
When there is already a jump list for the given `application`,
that jump list will be cleared, and the new `category` and `jumpListItems` will be added.
@param category
Specifies the category name. It will appear as the title of the custom jump list.
Must not include embedded NULs ('\\0')
@param jumpListItems
Specifies a list of com::sun::star::system::JumpListItem.
Must contain at least one item.
These will be added as entries below the category name in the custom jump list.
Make sure you don't add items which the user has removed before
(check the result of `getRemovedItems` before updating a category).
If you try to add items which the user removed before,
they will be silently ignored and not added to the list.
@param application
Used to map the jump list to the correct application. Use one of the following values:
<ul>
<li>Writer</li>
<li>Calc</li>
<li>Impress</li>
<li>Draw</li>
<li>Math</li>
<li>Base</li>
<li>Startcenter</li>
</ul>
"Startcenter" will map to the generic "LibreOffice" icon.
@throws com::sun::star::lang::IllegalArgumentException
When one of the following applies:
<ul>
<li>`category` is empty</li>
<li>`jumpListItems` is empty or contains only items which were removed by the user</li>
<li>`application` is invalid</li>
</ul>
*/
void appendCategory( [in] string category,
[in] sequence<com::sun::star::system::windows::JumpListItem> jumpListItems,
[in] string application )
raises( ::com::sun::star::lang::IllegalArgumentException );
/** Delete a jump list category
@param application
Used to map the jump list to the correct application. Use one of the following values:
<ul>
<li>Writer</li>
<li>Calc</li>
<li>Impress</li>
<li>Draw</li>
<li>Math</li>
<li>Base</li>
<li>Startcenter</li>
</ul>
"Startcenter" will map to the generic "LibreOffice" icon.
@throws com::sun::star::lang::IllegalArgumentException
When `application` is invalid
*/
void deleteCategory( [in] string application )
raises( ::com::sun::star::lang::IllegalArgumentException );
/** Returns items that were removed from the jump list by the user.
`appendCategory` will ignore items which were removed by the user before.
Use this method to learn which items were removed by the user.
@param application
Used to map the jump list to the correct application. Use one of the following values:
<ul>
<li>Writer</li>
<li>Calc</li>
<li>Impress</li>
<li>Draw</li>
<li>Math</li>
<li>Base</li>
<li>Startcenter</li>
</ul>
"Startcenter" will map to the generic "LibreOffice" icon.
@return List of removed items.
*/
sequence<com::sun::star::system::windows::JumpListItem> getRemovedItems([in] string application);
};
}; }; }; }; };
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

37
shell/Library_jumplist.mk Executable file
View file

@ -0,0 +1,37 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
$(eval $(call gb_Library_Library,jumplist))
$(eval $(call gb_Library_use_sdk_api,jumplist))
$(eval $(call gb_Library_use_libraries,jumplist,\
comphelper \
cppu \
cppuhelper \
sal \
))
ifeq ($(OS),WNT)
$(eval $(call gb_Library_use_system_win32_libs,jumplist,\
ole32 \
Propsys \
shell32 \
))
$(eval $(call gb_Library_set_componentfile,jumplist,shell/source/win32/jumplist/jumplist,services))
$(eval $(call gb_Library_add_exception_objects,jumplist,\
shell/source/win32/jumplist/JumpList \
))
endif # OS
# vim: set noet sw=4 ts=4:

View file

@ -51,6 +51,7 @@ $(eval $(call gb_Module_add_targets,shell,\
Executable_senddoc \
Library_smplmail \
Library_wininetbe \
Library_jumplist \
Executable_spsupp_helper \
))

View file

@ -0,0 +1,354 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <sal/config.h>
#include <algorithm>
#include <cassert>
#include <cppuhelper/basemutex.hxx>
#include <cppuhelper/compbase.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
#include <o3tl/runtimetooustring.hxx>
#include <o3tl/safeCoInitUninit.hxx>
#include <osl/file.hxx>
#include <osl/mutex.hxx>
#include <osl/process.h>
#include <sal/log.hxx>
#include <systools/win32/comtools.hxx>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/system/windows/JumpListItem.hpp>
#include <com/sun/star/system/windows/XJumpList.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <prewin.h>
#include <Shlobj.h>
#include <propkey.h>
#include <propvarutil.h>
#include <postwin.h>
using namespace comphelper;
using namespace cppu;
using namespace css;
using namespace css::uno;
using namespace css::lang;
using namespace css::system::windows;
using namespace osl;
using namespace sal::systools;
class JumpListImpl : public BaseMutex, public WeakComponentImplHelper<XJumpList, XServiceInfo>
{
Reference<XComponentContext> m_xContext;
public:
explicit JumpListImpl(const Reference<XComponentContext>& xContext);
~JumpListImpl();
// XJumpList
virtual void SAL_CALL appendCategory(const OUString& sCategory,
const Sequence<JumpListItem>& aJumpListItems,
const OUString& sApplication) override;
virtual void SAL_CALL deleteCategory(const OUString& sApplication) override;
virtual Sequence<JumpListItem> SAL_CALL getRemovedItems(const OUString& sApplication) override;
// XServiceInfo
virtual OUString SAL_CALL getImplementationName() override;
virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
};
JumpListImpl::JumpListImpl(const Reference<XComponentContext>& xContext)
: WeakComponentImplHelper(m_aMutex)
, m_xContext(xContext)
{
}
JumpListImpl::~JumpListImpl() {}
namespace
{
// Determines if the provided IShellLinkItem is listed in the array of items that the user has removed
bool lcl_isItemInArray(COMReference<IShellLinkW> pShellLinkItem,
COMReference<IObjectArray> poaRemoved)
{
UINT nItems;
ThrowIfFailed(poaRemoved->GetCount(&nItems), "GetCount failed.");
COMReference<IShellLinkW> pShellLinkItemCompare;
for (UINT i = 0; i < nItems; i++)
{
if (!SUCCEEDED(poaRemoved->GetAt(i, IID_PPV_ARGS(&pShellLinkItemCompare))))
continue;
PROPVARIANT propvar;
COMReference<IPropertyStore> pps(pShellLinkItem, COM_QUERY_THROW);
ThrowIfFailed(pps->GetValue(PKEY_Title, &propvar), "GetValue failed.");
OUString title(o3tl::toU(PropVariantToStringWithDefault(propvar, L"")));
COMReference<IPropertyStore> ppsCompare(pShellLinkItemCompare, COM_QUERY_THROW);
ThrowIfFailed(ppsCompare->GetValue(PKEY_Title, &propvar), "GetValue failed.");
OUString titleCompare(o3tl::toU(PropVariantToStringWithDefault(propvar, L"")));
PropVariantClear(&propvar);
if (title == titleCompare)
return true;
}
return false;
}
}
void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory,
const Sequence<JumpListItem>& aJumpListItems,
const OUString& sApplication)
{
if (sCategory.isEmpty())
{
throw IllegalArgumentException("Parameter 'category' must not be empty",
static_cast<OWeakObject*>(this), 1);
}
if (sApplication != "Writer" && sApplication != "Calc" && sApplication != "Impress"
&& sApplication != "Draw" && sApplication != "Math" && sApplication != "Base"
&& sApplication != "Startcenter")
{
throw IllegalArgumentException(
"Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
"'Math', 'Base', 'Startcenter'.",
static_cast<OWeakObject*>(this), 1);
}
OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication);
try
{
COMReference<ICustomDestinationList> aDestinationList(CLSID_DestinationList, nullptr,
CLSCTX_INPROC_SERVER);
aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr()));
UINT min_slots;
COMReference<IObjectArray> removed;
ThrowIfFailed(aDestinationList->BeginList(&min_slots, IID_PPV_ARGS(&removed)),
"BeginList failed");
OUString sofficeURL;
OUString sofficePath;
oslProcessError err = osl_getExecutableFile(&sofficeURL.pData);
FileBase::getSystemPathFromFileURL(sofficeURL, sofficePath);
if (err != osl_Process_E_None)
{
SAL_WARN("shell.jumplist", "osl_getExecutableFile failed");
return;
}
// We need to run soffice.exe, not soffice.bin
sofficePath = sofficePath.replaceFirst("soffice.bin", "soffice.exe");
COMReference<IObjectCollection> aCollection(CLSID_EnumerableObjectCollection, nullptr,
CLSCTX_INPROC_SERVER);
for (auto item : aJumpListItems)
{
if (item.name.isEmpty())
continue;
try
{
COMReference<IShellLinkW> pShellLinkItem(CLSID_ShellLink, nullptr,
CLSCTX_INPROC_SERVER);
{
COMReference<IPropertyStore> pps(pShellLinkItem, COM_QUERY_THROW);
PROPVARIANT propvar;
ThrowIfFailed(
InitPropVariantFromString(o3tl::toW(item.name.getStr()), &propvar),
"InitPropVariantFromString failed.");
ThrowIfFailed(pps->SetValue(PKEY_Title, propvar), "SetValue failed.");
ThrowIfFailed(pps->Commit(), "Commit failed.");
PropVariantClear(&propvar);
}
ThrowIfFailed(
pShellLinkItem->SetDescription(o3tl::toW(item.description.getStr())),
OString("Setting description '" + item.description.toUtf8() + "' failed."));
ThrowIfFailed(pShellLinkItem->SetPath(o3tl::toW(sofficePath.getStr())),
OString("Setting path '" + sofficePath.toUtf8() + "' failed."));
ThrowIfFailed(
pShellLinkItem->SetArguments(o3tl::toW(item.arguments.getStr())),
OString("Setting arguments '" + item.arguments.toUtf8() + "' failed."));
ThrowIfFailed(
pShellLinkItem->SetIconLocation(o3tl::toW(item.iconPath.getStr()), 0),
OString("Setting icon path '" + item.iconPath.toUtf8() + "' failed."));
if (lcl_isItemInArray(pShellLinkItem, removed))
{
SAL_INFO("shell.jumplist", "Ignoring item '"
<< item.name
<< "' (was removed by user). See output of "
"XJumpList::getRemovedItems().");
continue;
}
aCollection->AddObject(pShellLinkItem);
}
catch (const ComError& e)
{
SAL_WARN("shell.jumplist", e.what());
continue;
}
}
COMReference<IObjectArray> pObjectArray(aCollection, COM_QUERY_THROW);
UINT nItems;
ThrowIfFailed(pObjectArray->GetCount(&nItems), "GetCount failed.");
if (nItems == 0)
{
throw IllegalArgumentException(
"No valid items given. `jumpListItems` is either empty, or contains only items "
"which were removed by the user. See `XJumpList::getRemovedItems()`.",
static_cast<OWeakObject*>(this), 1);
}
ThrowIfFailed(aDestinationList->AppendCategory(o3tl::toW(sCategory.getStr()), pObjectArray),
"AppendCategory failed.");
ThrowIfFailed(aDestinationList->CommitList(), "CommitList failed.");
}
catch (const ComError& e)
{
SAL_WARN("shell.jumplist", e.what());
}
}
void SAL_CALL JumpListImpl::deleteCategory(const OUString& sApplication)
{
if (sApplication != "Writer" && sApplication != "Calc" && sApplication != "Impress"
&& sApplication != "Draw" && sApplication != "Math" && sApplication != "Base"
&& sApplication != "Startcenter")
{
throw IllegalArgumentException(
"Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
"'Math', 'Base', 'Startcenter'.",
static_cast<OWeakObject*>(this), 1);
}
OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication);
try
{
COMReference<ICustomDestinationList> aDestinationList(CLSID_DestinationList, nullptr,
CLSCTX_INPROC_SERVER);
aDestinationList->DeleteList(o3tl::toW(sApplicationID.getStr()));
}
catch (const ComError& e)
{
SAL_WARN("shell.jumplist", e.what());
}
}
Sequence<JumpListItem> SAL_CALL JumpListImpl::getRemovedItems(const OUString& sApplication)
{
if (sApplication != "Writer" && sApplication != "Calc" && sApplication != "Impress"
&& sApplication != "Draw" && sApplication != "Math" && sApplication != "Base"
&& sApplication != "Startcenter")
{
throw IllegalArgumentException(
"Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
"'Math', 'Base', 'Startcenter'.",
static_cast<OWeakObject*>(this), 1);
}
OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication);
std::vector<JumpListItem> removedItems;
try
{
COMReference<ICustomDestinationList> aDestinationList(CLSID_DestinationList, nullptr,
CLSCTX_INPROC_SERVER);
aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr()));
COMReference<IObjectArray> removed;
ThrowIfFailed(aDestinationList->GetRemovedDestinations(IID_PPV_ARGS(&removed)),
"GetRemovedDestinations failed");
UINT removed_count;
if (SUCCEEDED(removed->GetCount(&removed_count) && (removed_count > 0)))
{
JumpListItem item;
COMReference<IShellLinkW> pShellLinkItem;
for (UINT i = 0; i < removed_count; ++i)
{
if (SUCCEEDED(removed->GetAt(i, IID_PPV_ARGS(&pShellLinkItem))))
{
COMReference<IPropertyStore> propertyStore(pShellLinkItem, COM_QUERY_THROW);
PROPVARIANT propvar;
ThrowIfFailed(propertyStore->GetValue(PKEY_Title, &propvar),
"GetValue failed.");
item.name = o3tl::toU(PropVariantToStringWithDefault(propvar, L""));
ThrowIfFailed(propertyStore->GetValue(PKEY_Link_Arguments, &propvar),
"GetValue failed.");
item.arguments = o3tl::toU(PropVariantToStringWithDefault(propvar, L""));
PropVariantClear(&propvar);
wchar_t itemDesc[MAX_PATH];
ThrowIfFailed(pShellLinkItem->GetDescription(
itemDesc, std::extent<decltype(itemDesc)>::value),
"GetDescription failed.");
item.description = o3tl::toU(itemDesc);
wchar_t path[MAX_PATH];
int icon_index;
ThrowIfFailed(pShellLinkItem->GetIconLocation(
path, std::extent<decltype(path)>::value, &icon_index),
"GetIconLocation failed.");
item.iconPath = o3tl::toU(path);
removedItems.emplace_back(item);
}
}
}
}
catch (const ComError& e)
{
SAL_WARN("shell.jumplist", e.what());
}
return containerToSequence(removedItems);
}
// XServiceInfo
OUString SAL_CALL JumpListImpl::getImplementationName()
{
return "com.sun.star.system.windows.JumpListImpl";
}
sal_Bool SAL_CALL JumpListImpl::supportsService(const OUString& ServiceName)
{
return cppu::supportsService(this, ServiceName);
}
Sequence<OUString> SAL_CALL JumpListImpl::getSupportedServiceNames()
{
return { "com.sun.star.system.windows.JumpList" };
}
extern "C" SAL_DLLPUBLIC_EXPORT XInterface*
shell_JumpListExec_get_implementation(XComponentContext* context, Sequence<Any> const&)
{
return acquire(new JumpListImpl(context));
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
-->
<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
xmlns="http://openoffice.org/2010/uno-components">
<implementation name="com.sun.star.system.windows.JumpListImpl"
constructor="shell_JumpListExec_get_implementation">
<service name="com.sun.star.system.windows.JumpList"/>
</implementation>
</component>