File indexing completed on 2024-05-19 04:25:10

0001 /*
0002  * SPDX-FileCopyrightText: 2022 Alvin Wong <alvin@alvinhc.com>
0003  * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "KisWindowsPackageUtils.h"
0009 
0010 #include <array>
0011 
0012 // XXX: needs to go first because under MinGW
0013 // clangd gets really confused and errors on missing
0014 // definition of WINAPI_FAMILY_PARTITION
0015 #include <windows.h>
0016 
0017 #if defined __has_include
0018 #if __has_include(<appmodel.h>)
0019 #include <appmodel.h>
0020 #define HAS_APPMODEL_H
0021 #endif
0022 #endif
0023 
0024 #if defined HAS_APPMODEL_H
0025 // ---
0026 // GetCurrentPackageFamilyName
0027 // appmodel.h / Kernel32.dll / Windows 8
0028 // ---
0029 using pGetCurrentPackageFamilyName_t = decltype(&GetCurrentPackageFamilyName);
0030 
0031 // ---
0032 // GetCurrentPackageFullName
0033 // appmodel.h / Kernel32.dll / Windows 8
0034 // ---
0035 using pGetCurrentPackageFullName_t = decltype(&GetCurrentPackageFullName);
0036 #else
0037 // ---
0038 // GetCurrentPackageFamilyName
0039 // appmodel.h / Kernel32.dll / Windows 8
0040 // ---
0041 using pGetCurrentPackageFamilyName_t = LONG(WINAPI *)(UINT32 *packageFamilyNameLength, PWSTR packageFamilyName);
0042 
0043 // ---
0044 // GetCurrentPackageFullName
0045 // appmodel.h / Kernel32.dll / Windows 8
0046 // ---
0047 using pGetCurrentPackageFullName_t = LONG(WINAPI *)(UINT32 *packageFullNameLength, PWSTR packageFullName);
0048 #endif
0049 
0050 #include <shlobj.h>
0051 
0052 #include <QDebug>
0053 #include <QLibrary>
0054 #include <QString>
0055 
0056 #ifndef PACKAGE_FULL_NAME_MAX_LENGTH
0057 constexpr int PACKAGE_FULL_NAME_MAX_LENGTH = 127;
0058 #endif
0059 
0060 #ifndef APPMODEL_ERROR_NO_PACKAGE
0061 constexpr LONG APPMODEL_ERROR_NO_PACKAGE = 15700;
0062 #endif
0063 
0064 // Flag for `KNOWN_FOLDER_FLAG`, introduced in Win 10 ver 1703, which when
0065 // used within a Desktop Bridge process, will cause the API to return the
0066 // redirected target of the locations.
0067 //
0068 // ---
0069 // KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET
0070 // shlobj_core.h / Windows 10 v1703
0071 // ---
0072 #ifndef KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET
0073 constexpr int KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET = 0x00040000;
0074 #endif
0075 
0076 struct AppmodelFunctions {
0077     pGetCurrentPackageFamilyName_t getCurrentPackageFamilyName{};
0078     pGetCurrentPackageFullName_t getCurrentPackageFullName{};
0079     QLibrary dllKernel32;
0080 
0081     template<typename T, typename U>
0082     inline T cast_to_function(U v) noexcept
0083     {
0084         return reinterpret_cast<T>(reinterpret_cast<void *>(v));
0085     }
0086 
0087     static const AppmodelFunctions &instance()
0088     {
0089         static const AppmodelFunctions s{};
0090         return s;
0091     }
0092 
0093     AppmodelFunctions()
0094         : dllKernel32("kernel32.dll")
0095     {
0096         getCurrentPackageFamilyName =
0097             cast_to_function<pGetCurrentPackageFamilyName_t>(dllKernel32.resolve("GetCurrentPackageFamilyName"));
0098         getCurrentPackageFullName =
0099             cast_to_function<pGetCurrentPackageFullName_t>(dllKernel32.resolve("GetCurrentPackageFullName"));
0100     }
0101 
0102     ~AppmodelFunctions() = default;
0103 };
0104 
0105 namespace KisWindowsPackageUtils
0106 {
0107 
0108 bool isRunningInPackage()
0109 {
0110     return tryGetCurrentPackageFamilyName(nullptr);
0111 }
0112 
0113 bool tryGetCurrentPackageFamilyName(QString *outName)
0114 {
0115     if (!AppmodelFunctions::instance().getCurrentPackageFamilyName) {
0116         // We are probably on Windows 7 or earlier.
0117         return false;
0118     }
0119 
0120     std::array<WCHAR, PACKAGE_FULL_NAME_MAX_LENGTH + 1> name{}; // includes null terminator
0121     UINT32 nameLength = name.size();
0122     const LONG result = AppmodelFunctions::instance().getCurrentPackageFamilyName(&nameLength, name.data());
0123     if (result == APPMODEL_ERROR_NO_PACKAGE) {
0124         // Process not running from a package.
0125         return false;
0126     }
0127     if (result == ERROR_INSUFFICIENT_BUFFER) {
0128         // This shouldn't happen!
0129         qWarning() << "GetCurrentPackageFamilyName returned "
0130                       "ERROR_INSUFFICIENT_BUFFER, required length is"
0131                    << nameLength;
0132         if (outName) {
0133             *outName = QString();
0134         }
0135         return true;
0136     }
0137     if (result != ERROR_SUCCESS) {
0138         qWarning() << "GetCurrentPackageFamilyName returned unexpected error code:" << result;
0139         return false;
0140     }
0141 
0142     if (outName) {
0143         // Sanity check
0144         if (nameLength > name.size()) {
0145             qWarning() << "GetCurrentPackageFamilyName returned a length "
0146                           "exceeding the buffer size:"
0147                        << nameLength;
0148             nameLength = name.size();
0149         }
0150         // Exclude null terminator
0151         if (nameLength > 0 && name.at(nameLength - 1) == L'\0') {
0152             nameLength -= 1;
0153         }
0154         *outName = QString::fromWCharArray(name.data(), static_cast<int>(nameLength));
0155     }
0156     return true;
0157 }
0158 
0159 bool tryGetCurrentPackageFullName(QString *outName)
0160 {
0161     if (!AppmodelFunctions::instance().getCurrentPackageFullName) {
0162         // We are probably on Windows 7 or earlier.
0163         return false;
0164     }
0165 
0166     std::array<WCHAR, PACKAGE_FULL_NAME_MAX_LENGTH + 1> name{}; // includes null terminator
0167     UINT32 nameLength = name.size();
0168     const LONG result = AppmodelFunctions::instance().getCurrentPackageFullName(&nameLength, name.data());
0169     if (result == APPMODEL_ERROR_NO_PACKAGE) {
0170         // Process not running from a package.
0171         return false;
0172     }
0173     if (result == ERROR_INSUFFICIENT_BUFFER) {
0174         // This shouldn't happen!
0175         qWarning() << "GetCurrentPackageFullName returned "
0176                       "ERROR_INSUFFICIENT_BUFFER, required length is"
0177                    << nameLength;
0178         if (outName) {
0179             *outName = QString();
0180         }
0181         return true;
0182     }
0183     if (result != ERROR_SUCCESS) {
0184         qWarning() << "GetCurrentPackageFullName returned unexpected error code:" << result;
0185         return false;
0186     }
0187 
0188     if (outName) {
0189         // Sanity check
0190         if (nameLength > name.size()) {
0191             qWarning() << "GetCurrentPackageFullName returned a length "
0192                           "exceeding the buffer size:"
0193                        << nameLength;
0194             nameLength = name.size();
0195         }
0196         // Exclude null terminator
0197         if (nameLength > 0 && name.at(nameLength - 1) == L'\0') {
0198             nameLength -= 1;
0199         }
0200         *outName = QString::fromWCharArray(name.data(), static_cast<int>(nameLength));
0201     }
0202     return true;
0203 }
0204 
0205 QString getPackageRoamingAppDataLocation()
0206 {
0207     PWSTR path = nullptr;
0208     HRESULT result =
0209         SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET, nullptr, &path);
0210     if (result != S_OK) {
0211         qWarning() << "SHGetKnownFolderPath returned error HRESULT:" << result;
0212         return {};
0213     }
0214     if (!path) {
0215         qWarning() << "SHGetKnownFolderPath did not return a path";
0216         return {};
0217     }
0218     QString appData = QString::fromWCharArray(path);
0219     CoTaskMemFree(path);
0220     return appData;
0221 }
0222 
0223 } /* namespace KisWindowsPackageUtils */