rewrite shortcut management code with RAII, error logging and exceptions

It actually works now.
This commit is contained in:
mjk 2021-03-06 02:13:57 +00:00 committed by LAGonauta
parent f193948f4e
commit eeda464ca9
2 changed files with 71 additions and 133 deletions

View file

@ -1,18 +0,0 @@
#pragma once
template<typename T>
struct ComPtr
{
T *p{};
~ComPtr() { if (p != nullptr) p->Release(); }
T &operator*() const { return *p; }
T **operator&() const { return &p; }
T **operator&() { return &p; }
T *operator->() const { return p; }
template<typename U>
HRESULT As( U **const pp ) const
{ return p->QueryInterface(pp); }
};

View file

@ -1,151 +1,107 @@
#include <shlobj.h>
#include <memory>
#include "shortcutcreator.h" #include "shortcutcreator.h"
#include "win32.hpp" #include "win32.hpp"
#include "converter.hpp" #include "converter.hpp"
#include "ginvoke.hpp" #include "ginvoke.hpp"
#ifdef UNICODE
#define _UNICODE
#endif
#include <tchar.h> // magic
#include <objbase.h> // COM stuff #include <objbase.h> // COM stuff
#include <shlobj.h> // IShellLink #include <shlobj.h> // IShellLink
#include <propvarutil.h> // InitPropVariantFromString #include <propvarutil.h> // InitPropVariantFromString
#include <propkey.h> // PKEY_AppUserModel_ID #include <propkey.h> // PKEY_AppUserModel_ID
#include <cstdio> // 'cause iostreams are bloat #include <winrt/base.h> // At least one COM header must have been previously
#include <filesystem> // included, for `winrt::create_instance` to work with the `GUID` type.
#include "ComPtr.hpp" #include <memory>
// Not available in MINGW headers for some reason namespace {
PSSTDAPI PropVariantToString(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch);
int32_t InstallShortcut(const std::wstring& exe_path, const std::wstring& aumid, const std::wstring& shortcut_path) #define checked(func, args) \
{ if (const auto hr = ((func)args); FAILED(hr)) \
ComPtr<IShellLink> shellLink; { \
auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); g_warning("%s%s failed: hresult %#08" PRIX32, \
#func, #args, static_cast<std::uint32_t>(hr)); \
if (SUCCEEDED(hr)) winrt::throw_hresult(hr); \
{
hr = shellLink->SetPath(exe_path.c_str());
if (SUCCEEDED(hr))
{
hr = shellLink->SetArguments(TEXT(""));
if (SUCCEEDED(hr))
{
hr = shellLink->SetWorkingDirectory(exe_path.c_str());
if (SUCCEEDED(hr))
{
ComPtr<IPropertyStore> propertyStore;
hr = shellLink.As(&propertyStore);
if (SUCCEEDED(hr))
{
PROPVARIANT appIdPropVar;
hr = InitPropVariantFromString(aumid.c_str(), &appIdPropVar);
if (SUCCEEDED(hr))
{
hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
if (SUCCEEDED(hr))
{
hr = propertyStore->Commit();
if (SUCCEEDED(hr))
{
ComPtr<IPersistFile> persistFile;
hr = shellLink.As(&persistFile);
if (SUCCEEDED(hr))
{
hr = persistFile->Save(shortcut_path.c_str(), TRUE);
}
}
}
PropVariantClear(&appIdPropVar);
}
}
}
}
}
} }
return hr;
}
int32_t ValidateShortcut(const std::wstring& shortcut_path, const std::wstring& currentAumid) struct property
{ {
bool wasChanged = false; property() noexcept : var{} {}
ComPtr<IShellLink> shellLink; explicit property(const std::wstring &value)
auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
if (SUCCEEDED(hr))
{ {
ComPtr<IPersistFile> persistFile; checked(::InitPropVariantFromString,(value.c_str(), &var));
hr = shellLink.As(&persistFile);
if (SUCCEEDED(hr))
{
hr = persistFile->Load(shortcut_path.c_str(), STGM_READWRITE);
if (SUCCEEDED(hr))
{
ComPtr<IPropertyStore> propertyStore;
hr = shellLink.As(&propertyStore);
if (SUCCEEDED(hr))
{
PROPVARIANT appIdPropVar;
hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar);
if (SUCCEEDED(hr))
{
std::array<wchar_t, MAX_PATH> AUMI;
hr = PropVariantToString(appIdPropVar, AUMI.data(), AUMI.size());
if (FAILED(hr) || currentAumid != std::wstring(AUMI.data()))
{
// AUMI Changed for the same app, let's update the current value! =)
wasChanged = true;
PropVariantClear(&appIdPropVar);
hr = InitPropVariantFromString(currentAumid.c_str(), &appIdPropVar);
if (SUCCEEDED(hr))
{
hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
if (SUCCEEDED(hr))
{
hr = propertyStore->Commit();
if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty()))
{
hr = persistFile->Save(shortcut_path.c_str(), TRUE);
}
}
}
}
PropVariantClear(&appIdPropVar);
}
}
}
}
} }
return hr;
}
static bool ImplEnsureAumiddedShortcutExists( ~property()
const std::string_view menu_rel_path, const std::string_view aumid) {
if (const auto hr = ::PropVariantClear(&var); FAILED(hr))
g_critical("PropVariantClear failed: hresult %#08" PRIX32,
static_cast<std::uint32_t>(hr));
}
auto str() const
{
wchar_t *str;
checked(::PropVariantToStringAlloc,(var, &str));
return std::unique_ptr
<wchar_t, decltype(&::CoTaskMemFree)>
{ str, &::CoTaskMemFree };
}
operator const PROPVARIANT &() const noexcept { return var; }
operator PROPVARIANT *() noexcept { return &var; }
private:
PROPVARIANT var;
};
bool ImplEnsureAumiddedShortcutExists(
const std::string_view menu_rel_path, const std::string_view narrow_aumid)
{ {
const auto waumid = sview_to_wstr(aumid); const auto aumid = sview_to_wstr(narrow_aumid);
if (waumid.empty()) if (aumid.empty())
{ {
return false; return false;
} }
const auto path = GetEnv(L"APPDATA") + LR"(\Microsoft\Windows\Start Menu\)" const auto exe_path = GetExePath();
const auto shortcut_path = GetEnv(L"APPDATA")
+ LR"(\Microsoft\Windows\Start Menu\)"
+ sview_to_wstr(menu_rel_path) + L".lnk"; + sview_to_wstr(menu_rel_path) + L".lnk";
if (!std::filesystem::exists(path))
const auto lnk = winrt::create_instance<IShellLinkW>(CLSID_ShellLink);
const auto file = lnk.as<IPersistFile>();
const auto store = lnk.as<IPropertyStore>();
if (SUCCEEDED(file->Load(shortcut_path.c_str(), STGM_READWRITE)))
{ {
return SUCCEEDED(InstallShortcut(GetExePath(), waumid, path)); property aumid_prop;
checked(store->GetValue,(PKEY_AppUserModel_ID, aumid_prop));
if (aumid_prop.str().get() != aumid)
checked(store->SetValue,(PKEY_AppUserModel_ID, property{aumid}));
} }
else else
{ {
return SUCCEEDED(ValidateShortcut(path, waumid)); checked(store->SetValue,(PKEY_AppUserModel_ID, property{aumid}));
checked(lnk->SetPath,(exe_path.c_str()));
} }
checked(store->Commit,());
if (file->IsDirty() != S_FALSE) // not the same as `== S_OK`
{
constexpr auto set_file_as_current = TRUE;
checked(file->Save,(shortcut_path.c_str(), set_file_as_current));
}
return true;
} }
#undef checked
} // nameless namespace
extern "C" extern "C"
{ {
gboolean EnsureAumiddedShortcutExists(const gchar *const aumid) noexcept gboolean EnsureAumiddedShortcutExists(const gchar *const aumid) noexcept