rewrite shortcut management code with RAII, error logging and exceptions
It actually works now.
This commit is contained in:
parent
f193948f4e
commit
eeda464ca9
|
@ -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); }
|
||||
};
|
|
@ -1,151 +1,107 @@
|
|||
#include <shlobj.h>
|
||||
#include <memory>
|
||||
|
||||
#include "shortcutcreator.h"
|
||||
#include "win32.hpp"
|
||||
#include "converter.hpp"
|
||||
#include "ginvoke.hpp"
|
||||
|
||||
#ifdef UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
|
||||
#include <tchar.h> // magic
|
||||
#include <objbase.h> // COM stuff
|
||||
#include <shlobj.h> // IShellLink
|
||||
#include <propvarutil.h> // InitPropVariantFromString
|
||||
#include <propkey.h> // PKEY_AppUserModel_ID
|
||||
#include <cstdio> // 'cause iostreams are bloat
|
||||
#include <filesystem>
|
||||
#include <winrt/base.h> // At least one COM header must have been previously
|
||||
// included, for `winrt::create_instance` to work with the `GUID` type.
|
||||
|
||||
#include "ComPtr.hpp"
|
||||
#include <memory>
|
||||
|
||||
// Not available in MINGW headers for some reason
|
||||
PSSTDAPI PropVariantToString(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch);
|
||||
namespace {
|
||||
|
||||
int32_t InstallShortcut(const std::wstring& exe_path, const std::wstring& aumid, const std::wstring& shortcut_path)
|
||||
{
|
||||
ComPtr<IShellLink> shellLink;
|
||||
auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
|
||||
|
||||
if (SUCCEEDED(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;
|
||||
#define checked(func, args) \
|
||||
if (const auto hr = ((func)args); FAILED(hr)) \
|
||||
{ \
|
||||
g_warning("%s%s failed: hresult %#08" PRIX32, \
|
||||
#func, #args, static_cast<std::uint32_t>(hr)); \
|
||||
winrt::throw_hresult(hr); \
|
||||
}
|
||||
|
||||
int32_t ValidateShortcut(const std::wstring& shortcut_path, const std::wstring& currentAumid)
|
||||
struct property
|
||||
{
|
||||
bool wasChanged = false;
|
||||
property() noexcept : var{} {}
|
||||
|
||||
ComPtr<IShellLink> shellLink;
|
||||
auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
explicit property(const std::wstring &value)
|
||||
{
|
||||
ComPtr<IPersistFile> persistFile;
|
||||
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;
|
||||
checked(::InitPropVariantFromString,(value.c_str(), &var));
|
||||
}
|
||||
|
||||
static bool ImplEnsureAumiddedShortcutExists(
|
||||
const std::string_view menu_rel_path, const std::string_view aumid)
|
||||
~property()
|
||||
{
|
||||
const auto waumid = sview_to_wstr(aumid);
|
||||
if (waumid.empty())
|
||||
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 aumid = sview_to_wstr(narrow_aumid);
|
||||
if (aumid.empty())
|
||||
{
|
||||
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";
|
||||
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
|
||||
{
|
||||
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"
|
||||
{
|
||||
gboolean EnsureAumiddedShortcutExists(const gchar *const aumid) noexcept
|
||||
|
|
Loading…
Reference in a new issue