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 */