anotherim-desktop/plugins/windows-notification/api/src/shortcutcreator.cpp

159 lines
4.5 KiB
C++

#include "shortcutcreator.h"
#include "win32.hpp"
#include "converter.hpp"
#include "ginvoke.hpp"
#include <objbase.h> // COM stuff
#include <shlobj.h> // IShellLink
#include <propvarutil.h> // InitPropVariantFromString
#include <propkey.h> // PKEY_AppUserModel_ID
#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 <memory>
namespace dyn
{
inline auto load_module(const wchar_t *const path, const char *const dbgnym)
{
const auto mod = ::LoadLibraryW(path);
if (mod)
return mod;
const win32_error e{};
g_warning("failed to load %s", dbgnym);
throw e;
}
template<typename T>
inline T &load_symbol(
const wchar_t *const mod_path, const char *const mod_dbgnym,
const char *const symbol)
{
const auto p = reinterpret_cast<T *>(
::GetProcAddress(load_module(mod_path, mod_dbgnym), symbol));
if (p)
return *p;
const win32_error e{};
g_warning("couldn't find %s in %s", symbol, mod_dbgnym);
throw e;
}
#define dyn_load_symbol(mod_name, symbol) \
::dyn::load_symbol<decltype(::symbol)>(L ## mod_name, mod_name, #symbol)
// PropVariantToString is a pain to use, and
// MinGW 6.0.0 doesn't have libpropsys.a in the first place;
// MinGW 9.0.0 doesn't have PropVariantToStringAlloc in its libpropsys.a.
// So...
constexpr auto PropVariantToStringAlloc = [](const auto &... arg)
{
static const auto &f =
dyn_load_symbol("propsys.dll", PropVariantToStringAlloc);
return f(arg...);
};
}
namespace {
#define checked(func, args) \
if (const auto hr = ((func)args); FAILED(hr)) \
{ \
g_warning("%s%s failed: hresult 0x%08" PRIX32, \
#func, #args, static_cast<std::uint32_t>(hr)); \
winrt::throw_hresult(hr); \
}
struct property
{
property() noexcept : var{} {}
explicit property(const std::wstring &value)
{
checked(::InitPropVariantFromString,(value.c_str(), &var));
}
~property()
{
if (const auto hr = ::PropVariantClear(&var); FAILED(hr))
g_critical("PropVariantClear failed: hresult 0x%08" PRIX32,
static_cast<std::uint32_t>(hr));
}
auto str() const
{
wchar_t *str;
checked(dyn::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 exe_path = GetExePath();
const auto shortcut_path = GetEnv(L"APPDATA")
+ LR"(\Microsoft\Windows\Start Menu\)"
+ sview_to_wstr(menu_rel_path) + L".lnk";
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)))
{
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}));
std::array<wchar_t, MAX_PATH+1> targ_path;
checked(lnk->GetPath,(targ_path.data(), targ_path.size(), nullptr, 0));
if (targ_path.data() != exe_path)
checked(lnk->SetPath,(exe_path.c_str()));
}
else
{
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
{
return g_try_invoke(
ImplEnsureAumiddedShortcutExists, R"(Programs\Dino)", aumid);
}
}