1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <sal/config.h>
15 #include <cppuhelper/basemutex.hxx>
16 #include <cppuhelper/compbase.hxx>
17 #include <comphelper/sequence.hxx>
18 #include <cppuhelper/supportsservice.hxx>
19 #include <o3tl/char16_t2wchar_t.hxx>
20 #include <o3tl/runtimetooustring.hxx>
21 #include <o3tl/safeCoInitUninit.hxx>
22 #include <osl/file.hxx>
23 #include <osl/mutex.hxx>
24 #include <osl/process.h>
25 #include <sal/log.hxx>
26 #include <systools/win32/comtools.hxx>
28 #include <com/sun/star/lang/IllegalArgumentException.hpp>
29 #include <com/sun/star/lang/XServiceInfo.hpp>
30 #include <com/sun/star/system/windows/JumpListItem.hpp>
31 #include <com/sun/star/system/windows/XJumpList.hpp>
32 #include <com/sun/star/uno/XComponentContext.hpp>
33 #include <com/sun/star/util/InvalidStateException.hpp>
38 #include <propvarutil.h>
41 using namespace comphelper
;
44 using namespace css::uno
;
45 using namespace css::lang
;
46 using namespace css::system::windows
;
47 using namespace css::util
;
49 using namespace sal::systools
;
53 class JumpListImpl
: public BaseMutex
, public WeakComponentImplHelper
<XJumpList
, XServiceInfo
>
55 Reference
<XComponentContext
> m_xContext
;
56 COMReference
<ICustomDestinationList
> m_aDestinationList
;
57 COMReference
<IObjectArray
> m_aRemoved
;
61 explicit JumpListImpl(const Reference
<XComponentContext
>& xContext
);
64 virtual void SAL_CALL
beginList(const OUString
& sApplication
) override
;
65 virtual void SAL_CALL
appendCategory(const OUString
& sCategory
,
66 const Sequence
<JumpListItem
>& aJumpListItems
) override
;
67 virtual void SAL_CALL
addTasks(const Sequence
<JumpListItem
>& aJumpListItems
) override
;
68 virtual void SAL_CALL
showRecentFiles() override
;
69 virtual void SAL_CALL
showFrequentFiles() override
;
70 virtual void SAL_CALL
commitList() override
;
71 virtual void SAL_CALL
abortList() override
;
72 virtual void SAL_CALL
deleteList(const OUString
& sApplication
) override
;
73 virtual Sequence
<JumpListItem
> SAL_CALL
getRemovedItems(const OUString
& sApplication
) override
;
76 virtual OUString SAL_CALL
getImplementationName() override
;
77 virtual sal_Bool SAL_CALL
supportsService(const OUString
& ServiceName
) override
;
78 virtual Sequence
<OUString
> SAL_CALL
getSupportedServiceNames() override
;
81 JumpListImpl::JumpListImpl(const Reference
<XComponentContext
>& xContext
)
82 : WeakComponentImplHelper(m_aMutex
)
83 , m_xContext(xContext
)
84 , m_aDestinationList(CLSID_DestinationList
, nullptr, CLSCTX_INPROC_SERVER
)
89 // Determines if the provided IShellLinkItem is listed in the array of items that the user has removed
90 bool lcl_isItemInArray(COMReference
<IShellLinkW
> pShellLinkItem
,
91 COMReference
<IObjectArray
> poaRemoved
)
94 ThrowIfFailed(poaRemoved
->GetCount(&nItems
), "GetCount failed.");
96 COMReference
<IShellLinkW
> pShellLinkItemCompare
;
97 for (UINT i
= 0; i
< nItems
; i
++)
99 if (!SUCCEEDED(poaRemoved
->GetAt(i
, IID_PPV_ARGS(&pShellLinkItemCompare
))))
103 COMReference
<IPropertyStore
> pps(pShellLinkItem
, COM_QUERY_THROW
);
104 ThrowIfFailed(pps
->GetValue(PKEY_Title
, &propvar
), "GetValue failed.");
105 OUString
title(o3tl::toU(PropVariantToStringWithDefault(propvar
, L
"")));
107 COMReference
<IPropertyStore
> ppsCompare(pShellLinkItemCompare
, COM_QUERY_THROW
);
108 ThrowIfFailed(ppsCompare
->GetValue(PKEY_Title
, &propvar
), "GetValue failed.");
109 OUString
titleCompare(o3tl::toU(PropVariantToStringWithDefault(propvar
, L
"")));
110 PropVariantClear(&propvar
);
112 if (title
== titleCompare
)
120 void SAL_CALL
JumpListImpl::beginList(const OUString
& sApplication
)
123 throw InvalidStateException("There is already a list open. Close it with 'commitList'");
125 if (sApplication
!= "Writer" && sApplication
!= "Calc" && sApplication
!= "Impress"
126 && sApplication
!= "Draw" && sApplication
!= "Math" && sApplication
!= "Base"
127 && sApplication
!= "Startcenter")
129 throw IllegalArgumentException(
130 "Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
131 "'Math', 'Base', 'Startcenter'.",
132 static_cast<OWeakObject
*>(this), 1);
134 OUString
sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication
);
138 m_aDestinationList
->SetAppID(o3tl::toW(sApplicationID
.getStr()));
142 ThrowIfFailed(m_aDestinationList
->BeginList(&min_slots
, IID_PPV_ARGS(&m_aRemoved
)),
146 catch (const ComError
& e
)
148 SAL_WARN("shell.jumplist", e
.what());
152 void SAL_CALL
JumpListImpl::appendCategory(const OUString
& sCategory
,
153 const Sequence
<JumpListItem
>& aJumpListItems
)
156 throw InvalidStateException("No list open. Open it with 'beginList'");
158 if (sCategory
.isEmpty())
160 throw IllegalArgumentException("Parameter 'category' must not be empty",
161 static_cast<OWeakObject
*>(this), 1);
167 OUString sofficePath
;
168 oslProcessError err
= osl_getExecutableFile(&sofficeURL
.pData
);
169 FileBase::getSystemPathFromFileURL(sofficeURL
, sofficePath
);
170 if (err
!= osl_Process_E_None
)
172 SAL_WARN("shell.jumplist", "osl_getExecutableFile failed");
175 // We need to run soffice.exe, not soffice.bin
176 sofficePath
= sofficePath
.replaceFirst("soffice.bin", "soffice.exe");
178 COMReference
<IObjectCollection
> aCollection(CLSID_EnumerableObjectCollection
, nullptr,
179 CLSCTX_INPROC_SERVER
);
181 for (auto const& item
: aJumpListItems
)
183 if (item
.name
.isEmpty())
187 COMReference
<IShellLinkW
> pShellLinkItem(CLSID_ShellLink
, nullptr,
188 CLSCTX_INPROC_SERVER
);
191 COMReference
<IPropertyStore
> pps(pShellLinkItem
, COM_QUERY_THROW
);
195 InitPropVariantFromString(o3tl::toW(item
.name
.getStr()), &propvar
),
196 "InitPropVariantFromString failed.");
198 ThrowIfFailed(pps
->SetValue(PKEY_Title
, propvar
), "SetValue failed.");
200 ThrowIfFailed(pps
->Commit(), "Commit failed.");
202 PropVariantClear(&propvar
);
205 pShellLinkItem
->SetDescription(o3tl::toW(item
.description
.getStr())),
206 Concat2View("Setting description '" + item
.description
.toUtf8() + "' failed."));
208 ThrowIfFailed(pShellLinkItem
->SetPath(o3tl::toW(sofficePath
.getStr())),
209 Concat2View("Setting path '" + sofficePath
.toUtf8() + "' failed."));
212 pShellLinkItem
->SetArguments(o3tl::toW(item
.arguments
.getStr())),
213 Concat2View("Setting arguments '" + item
.arguments
.toUtf8() + "' failed."));
216 pShellLinkItem
->SetIconLocation(o3tl::toW(item
.iconPath
.getStr()), 0),
217 Concat2View("Setting icon path '" + item
.iconPath
.toUtf8() + "' failed."));
219 if (lcl_isItemInArray(pShellLinkItem
, m_aRemoved
))
221 SAL_INFO("shell.jumplist", "Ignoring item '"
223 << "' (was removed by user). See output of "
224 "XJumpList::getRemovedItems().");
227 aCollection
->AddObject(pShellLinkItem
);
229 catch (const ComError
& e
)
231 SAL_WARN("shell.jumplist", e
.what());
236 COMReference
<IObjectArray
> pObjectArray(aCollection
, COM_QUERY_THROW
);
238 ThrowIfFailed(pObjectArray
->GetCount(&nItems
), "GetCount failed.");
241 throw IllegalArgumentException(
242 "No valid items given. `jumpListItems` is either empty, or contains only items "
243 "which were removed by the user. See `XJumpList::getRemovedItems()`.",
244 static_cast<OWeakObject
*>(this), 1);
248 m_aDestinationList
->AppendCategory(o3tl::toW(sCategory
.getStr()), pObjectArray
),
249 "AppendCategory failed.");
251 catch (const ComError
& e
)
253 SAL_WARN("shell.jumplist", e
.what());
257 void SAL_CALL
JumpListImpl::addTasks(const Sequence
<JumpListItem
>& aJumpListItems
)
260 throw InvalidStateException("No list open. Open it with 'beginList'");
265 OUString sofficePath
;
266 oslProcessError err
= osl_getExecutableFile(&sofficeURL
.pData
);
267 FileBase::getSystemPathFromFileURL(sofficeURL
, sofficePath
);
268 if (err
!= osl_Process_E_None
)
270 SAL_WARN("shell.jumplist", "osl_getExecutableFile failed");
273 // We need to run soffice.exe, not soffice.bin
274 sofficePath
= sofficePath
.replaceFirst("soffice.bin", "soffice.exe");
276 COMReference
<IObjectCollection
> aCollection(CLSID_EnumerableObjectCollection
, nullptr,
277 CLSCTX_INPROC_SERVER
);
279 for (auto const& item
: aJumpListItems
)
281 if (item
.name
.isEmpty())
285 COMReference
<IShellLinkW
> pShellLinkItem(CLSID_ShellLink
, nullptr,
286 CLSCTX_INPROC_SERVER
);
289 COMReference
<IPropertyStore
> pps(pShellLinkItem
, COM_QUERY_THROW
);
293 InitPropVariantFromString(o3tl::toW(item
.name
.getStr()), &propvar
),
294 "InitPropVariantFromString failed.");
296 ThrowIfFailed(pps
->SetValue(PKEY_Title
, propvar
), "SetValue failed.");
298 ThrowIfFailed(pps
->Commit(), "Commit failed.");
300 PropVariantClear(&propvar
);
303 pShellLinkItem
->SetDescription(o3tl::toW(item
.description
.getStr())),
304 Concat2View("Setting description '" + item
.description
.toUtf8() + "' failed."));
306 ThrowIfFailed(pShellLinkItem
->SetPath(o3tl::toW(sofficePath
.getStr())),
307 Concat2View("Setting path '" + sofficePath
.toUtf8() + "' failed."));
310 pShellLinkItem
->SetArguments(o3tl::toW(item
.arguments
.getStr())),
311 Concat2View("Setting arguments '" + item
.arguments
.toUtf8() + "' failed."));
314 pShellLinkItem
->SetIconLocation(o3tl::toW(item
.iconPath
.getStr()), 0),
315 Concat2View("Setting icon path '" + item
.iconPath
.toUtf8() + "' failed."));
317 aCollection
->AddObject(pShellLinkItem
);
319 catch (const ComError
& e
)
321 SAL_WARN("shell.jumplist", e
.what());
326 COMReference
<IObjectArray
> pObjectArray(aCollection
, COM_QUERY_THROW
);
328 ThrowIfFailed(pObjectArray
->GetCount(&nItems
), "GetCount failed.");
331 throw IllegalArgumentException("No valid items given. `jumpListItems` is empty.",
332 static_cast<OWeakObject
*>(this), 1);
335 ThrowIfFailed(m_aDestinationList
->AddUserTasks(pObjectArray
), "AddUserTasks failed.");
337 catch (const ComError
& e
)
339 SAL_WARN("shell.jumplist", e
.what());
343 void SAL_CALL
JumpListImpl::showRecentFiles()
346 throw InvalidStateException("No list open. Open it with 'beginList'");
350 ThrowIfFailed(m_aDestinationList
->AppendKnownCategory(KDC_RECENT
),
351 "AppendKnownCategory(KDC_RECENT) failed.");
353 catch (const ComError
& e
)
355 SAL_WARN("shell.jumplist", e
.what());
359 void SAL_CALL
JumpListImpl::showFrequentFiles()
362 throw InvalidStateException("No list open. Open it with 'beginList'");
366 ThrowIfFailed(m_aDestinationList
->AppendKnownCategory(KDC_FREQUENT
),
367 "AppendKnownCategory(KDC_FREQUENT) failed.");
369 catch (const ComError
& e
)
371 SAL_WARN("shell.jumplist", e
.what());
375 void SAL_CALL
JumpListImpl::commitList()
378 throw InvalidStateException("No list open. Open it with 'beginList'");
382 ThrowIfFailed(m_aDestinationList
->CommitList(), "CommitList failed.");
383 m_isListOpen
= false;
385 catch (const ComError
& e
)
387 SAL_WARN("shell.jumplist", e
.what());
391 void SAL_CALL
JumpListImpl::abortList()
394 throw InvalidStateException("No list open.");
398 ThrowIfFailed(m_aDestinationList
->AbortList(), "AbortList failed.");
399 m_isListOpen
= false;
401 catch (const ComError
& e
)
403 SAL_WARN("shell.jumplist", e
.what());
407 void SAL_CALL
JumpListImpl::deleteList(const OUString
& sApplication
)
410 throw InvalidStateException("You are in a list building session. Close it with "
411 "'commitList', or abort with 'abortList'");
413 if (sApplication
!= "Writer" && sApplication
!= "Calc" && sApplication
!= "Impress"
414 && sApplication
!= "Draw" && sApplication
!= "Math" && sApplication
!= "Base"
415 && sApplication
!= "Startcenter")
417 throw IllegalArgumentException(
418 "Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
419 "'Math', 'Base', 'Startcenter'.",
420 static_cast<OWeakObject
*>(this), 1);
422 OUString
sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication
);
426 COMReference
<ICustomDestinationList
> aDestinationList(CLSID_DestinationList
, nullptr,
427 CLSCTX_INPROC_SERVER
);
428 aDestinationList
->DeleteList(o3tl::toW(sApplicationID
.getStr()));
430 catch (const ComError
& e
)
432 SAL_WARN("shell.jumplist", e
.what());
436 Sequence
<JumpListItem
> SAL_CALL
JumpListImpl::getRemovedItems(const OUString
& sApplication
)
438 if (sApplication
!= "Writer" && sApplication
!= "Calc" && sApplication
!= "Impress"
439 && sApplication
!= "Draw" && sApplication
!= "Math" && sApplication
!= "Base"
440 && sApplication
!= "Startcenter")
442 throw IllegalArgumentException(
443 "Parameter 'application' must be one of 'Writer', 'Calc', 'Impress', 'Draw', "
444 "'Math', 'Base', 'Startcenter'.",
445 static_cast<OWeakObject
*>(this), 1);
447 OUString
sApplicationID("TheDocumentFoundation.LibreOffice." + sApplication
);
449 std::vector
<JumpListItem
> removedItems
;
452 COMReference
<ICustomDestinationList
> aDestinationList(CLSID_DestinationList
, nullptr,
453 CLSCTX_INPROC_SERVER
);
455 aDestinationList
->SetAppID(o3tl::toW(sApplicationID
.getStr()));
457 COMReference
<IObjectArray
> removed
;
458 ThrowIfFailed(aDestinationList
->GetRemovedDestinations(IID_PPV_ARGS(&removed
)),
459 "GetRemovedDestinations failed");
462 if (SUCCEEDED(removed
->GetCount(&removed_count
) && (removed_count
> 0)))
465 COMReference
<IShellLinkW
> pShellLinkItem
;
466 for (UINT i
= 0; i
< removed_count
; ++i
)
468 if (SUCCEEDED(removed
->GetAt(i
, IID_PPV_ARGS(&pShellLinkItem
))))
470 COMReference
<IPropertyStore
> propertyStore(pShellLinkItem
, COM_QUERY_THROW
);
472 ThrowIfFailed(propertyStore
->GetValue(PKEY_Title
, &propvar
),
474 item
.name
= o3tl::toU(PropVariantToStringWithDefault(propvar
, L
""));
476 ThrowIfFailed(propertyStore
->GetValue(PKEY_Link_Arguments
, &propvar
),
478 item
.arguments
= o3tl::toU(PropVariantToStringWithDefault(propvar
, L
""));
479 PropVariantClear(&propvar
);
481 wchar_t itemDesc
[MAX_PATH
];
482 ThrowIfFailed(pShellLinkItem
->GetDescription(
483 itemDesc
, std::extent
<decltype(itemDesc
)>::value
),
484 "GetDescription failed.");
485 item
.description
= o3tl::toU(itemDesc
);
487 wchar_t path
[MAX_PATH
];
489 ThrowIfFailed(pShellLinkItem
->GetIconLocation(
490 path
, std::extent
<decltype(path
)>::value
, &icon_index
),
491 "GetIconLocation failed.");
492 item
.iconPath
= o3tl::toU(path
);
494 removedItems
.emplace_back(item
);
499 catch (const ComError
& e
)
501 SAL_WARN("shell.jumplist", e
.what());
504 return containerToSequence(removedItems
);
509 OUString SAL_CALL
JumpListImpl::getImplementationName()
511 return "com.sun.star.system.windows.JumpListImpl";
514 sal_Bool SAL_CALL
JumpListImpl::supportsService(const OUString
& ServiceName
)
516 return cppu::supportsService(this, ServiceName
);
519 Sequence
<OUString
> SAL_CALL
JumpListImpl::getSupportedServiceNames()
521 return { "com.sun.star.system.windows.JumpList" };
524 extern "C" SAL_DLLPUBLIC_EXPORT XInterface
*
525 shell_JumpListExec_get_implementation(XComponentContext
* context
, Sequence
<Any
> const&)
527 return acquire(new JumpListImpl(context
));
530 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */