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 "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
|
||||||
|
|
Loading…
Reference in a new issue