mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-29 08:13:44 +01:00
GPU: detect supported platform graphic API version
This commit is contained in:
@@ -575,6 +575,7 @@ elseif(APPLE)
|
||||
src/detection/displayserver/displayserver_apple.c
|
||||
src/detection/font/font_apple.m
|
||||
src/detection/gpu/gpu_apple.c
|
||||
src/detection/gpu/gpu_apple.m
|
||||
src/detection/host/host_apple.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/icons/icons_nosupport.c
|
||||
@@ -905,6 +906,7 @@ elseif(APPLE)
|
||||
PRIVATE "-framework CoreWLAN"
|
||||
PRIVATE "-framework IOBluetooth"
|
||||
PRIVATE "-framework IOKit"
|
||||
PRIVATE "-framework Metal"
|
||||
PRIVATE "-framework OpenGL"
|
||||
PRIVATE "-framework OpenCL"
|
||||
PRIVATE "-framework SystemConfiguration"
|
||||
|
||||
@@ -62,6 +62,7 @@ const char* detectByOpenGL(FFlist* gpus)
|
||||
ffStrbufInitMove(&gpu->vendor, &result.vendor);
|
||||
ffStrbufInitMove(&gpu->name, &result.renderer);
|
||||
ffStrbufInit(&gpu->driver);
|
||||
ffStrbufInitF(&gpu->platformApi, "OpenGL %s", result.version.chars);
|
||||
gpu->temperature = FF_GPU_TEMP_UNSET;
|
||||
gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
|
||||
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
|
||||
|
||||
@@ -29,6 +29,7 @@ typedef struct FFGPUResult
|
||||
FFstrbuf vendor;
|
||||
FFstrbuf name;
|
||||
FFstrbuf driver;
|
||||
FFstrbuf platformApi;
|
||||
double temperature;
|
||||
int32_t coreCount;
|
||||
double frequency; // Real time clock frequency in GHz
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||
|
||||
const char* ffGpuDetectMetal(FFlist* gpus);
|
||||
|
||||
static double detectGpuTemp(const FFstrbuf* gpuName)
|
||||
{
|
||||
double result = 0;
|
||||
@@ -55,6 +57,8 @@ const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)
|
||||
gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
|
||||
gpu->type = FF_GPU_TYPE_UNKNOWN;
|
||||
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
|
||||
IORegistryEntryGetRegistryEntryID(registryEntry, &gpu->deviceId);
|
||||
ffStrbufInitStatic(&gpu->platformApi, "Metal");
|
||||
|
||||
ffStrbufInit(&gpu->driver); // Ok for both Apple and Intel
|
||||
ffCfDictGetString(properties, CFSTR("CFBundleIdentifier"), &gpu->driver);
|
||||
@@ -103,5 +107,7 @@ const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)
|
||||
}
|
||||
|
||||
IOObjectRelease(iterator);
|
||||
|
||||
ffGpuDetectMetal(gpus);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "gpu.h"
|
||||
|
||||
#import <Metal/MTLDevice.h>
|
||||
|
||||
#ifndef MAC_OS_VERSION_13_0
|
||||
#define MTLGPUFamilyMetal3 ((MTLGPUFamily) 5001)
|
||||
#endif
|
||||
|
||||
const char* ffGpuDetectMetal(FFlist* gpus)
|
||||
{
|
||||
if (@available(macOS 10.15, *))
|
||||
{
|
||||
for (id<MTLDevice> device in MTLCopyAllDevices())
|
||||
{
|
||||
FFGPUResult* gpu = NULL;
|
||||
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
|
||||
{
|
||||
if (x->deviceId == device.registryID || ffStrbufEqualS(&x->name, device.name.UTF8String))
|
||||
{
|
||||
gpu = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!gpu) continue;
|
||||
|
||||
if ([device supportsFamily:MTLGPUFamilyMetal3])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon3])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon2])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon1])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
|
||||
|
||||
gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -221,6 +221,8 @@ static void pciHandleDevice(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist*
|
||||
|
||||
FFGPUResult* gpu = ffListAdd(results);
|
||||
|
||||
ffStrbufInit(&gpu->platformApi);
|
||||
|
||||
ffStrbufInit(&gpu->vendor);
|
||||
pciDetectVendorName(gpu, pci, device);
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist*
|
||||
ffStrbufInit(&gpu->vendor);
|
||||
ffStrbufInitWS(&gpu->name, displayDevice.DeviceString);
|
||||
ffStrbufInit(&gpu->driver);
|
||||
ffStrbufInitStatic(&gpu->platformApi, "Direct3D");
|
||||
gpu->temperature = FF_GPU_TEMP_UNSET;
|
||||
gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
|
||||
gpu->type = FF_GPU_TYPE_UNKNOWN;
|
||||
@@ -114,6 +115,12 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist*
|
||||
}
|
||||
|
||||
ffRegReadUint64(hDirectxKey, L"AdapterLuid", &gpu->deviceId, NULL);
|
||||
|
||||
uint32_t featureLevel = 0;
|
||||
if(ffRegReadUint(hDirectxKey, L"MaxD3D12FeatureLevel", &featureLevel, NULL) && featureLevel)
|
||||
ffStrbufSetF(&gpu->platformApi, "Direct3D 12.%u", (featureLevel & 0x0F00) >> 8);
|
||||
else if(ffRegReadUint(hDirectxKey, L"MaxD3D11FeatureLevel", &featureLevel, NULL) && featureLevel)
|
||||
ffStrbufSetF(&gpu->platformApi, "Direct3D 11.%u", (featureLevel & 0x0F00) >> 8);
|
||||
}
|
||||
else if (!ffRegReadUint64(hKey, L"HardwareInformation.qwMemorySize", &gpu->dedicated.total, NULL))
|
||||
{
|
||||
|
||||
@@ -181,6 +181,7 @@ static const char* detectVulkan(FFVulkanResult* result)
|
||||
|
||||
FFGPUResult* gpu = ffListAdd(&result->gpus);
|
||||
|
||||
ffStrbufInitF(&gpu->platformApi, "Vulkan %u.%u.%u", deviceAPIVersion.major, deviceAPIVersion.minor, deviceAPIVersion.patch);
|
||||
gpu->deviceId = physicalDeviceProperties.properties.deviceID;
|
||||
|
||||
ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define FF_GPU_NUM_FORMAT_ARGS 11
|
||||
#define FF_GPU_NUM_FORMAT_ARGS 12
|
||||
|
||||
static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResult* gpu)
|
||||
{
|
||||
@@ -83,6 +83,7 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu
|
||||
{FF_FORMAT_ARG_TYPE_UINT64, &gpu->dedicated.used},
|
||||
{FF_FORMAT_ARG_TYPE_UINT64, &gpu->shared.total},
|
||||
{FF_FORMAT_ARG_TYPE_UINT64, &gpu->shared.used},
|
||||
{FF_FORMAT_ARG_TYPE_STRBUF, &gpu->platformApi},
|
||||
{FF_FORMAT_ARG_TYPE_DOUBLE, &gpu->frequency},
|
||||
});
|
||||
}
|
||||
@@ -309,6 +310,8 @@ void ffGenerateGPUJsonResult(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_
|
||||
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &gpu->vendor);
|
||||
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "platformApi", &gpu->platformApi);
|
||||
|
||||
if (gpu->frequency == FF_GPU_FREQUENCY_UNSET)
|
||||
yyjson_mut_obj_add_null(doc, obj, "frequency");
|
||||
else
|
||||
@@ -336,6 +339,8 @@ void ffPrintGPUHelpFormat(void)
|
||||
"GPU used dedicated memory",
|
||||
"GPU total shared memory",
|
||||
"GPU used shared memory",
|
||||
"The platform API that GPU supports",
|
||||
"Current frequency in GHz",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ extern "C" {
|
||||
|
||||
#include <string_view>
|
||||
|
||||
static inline void ffStrbufInitWSV(FFstrbuf* result, const std::wstring_view source)
|
||||
{
|
||||
return ffStrbufInitNWS(result, (uint32_t) source.size(), source.data());
|
||||
}
|
||||
|
||||
static inline void ffStrbufSetWSV(FFstrbuf* result, const std::wstring_view source)
|
||||
{
|
||||
return ffStrbufSetNWS(result, (uint32_t) source.size(), source.data());
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include <oaidl.h>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <string_view>
|
||||
|
||||
struct FFWmiVariant: VARIANT
|
||||
{
|
||||
explicit FFWmiVariant() { VariantInit(this); }
|
||||
~FFWmiVariant() { VariantClear(this); }
|
||||
|
||||
FFWmiVariant(const FFWmiVariant&) = delete;
|
||||
FFWmiVariant(FFWmiVariant&&); // don't define it to enforce NRVO optimization
|
||||
|
||||
bool hasValue() {
|
||||
return this->vt != VT_EMPTY;
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return this->hasValue();
|
||||
}
|
||||
|
||||
template <typename T> T get()
|
||||
{
|
||||
// boolean
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
assert(this->vt == VT_BOOL);
|
||||
return this->boolVal != VARIANT_FALSE;
|
||||
}
|
||||
|
||||
// signed
|
||||
else if constexpr (std::is_same_v<T, int8_t>) {
|
||||
assert(this->vt == VT_I1);
|
||||
return this->cVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int16_t>) {
|
||||
assert(vt == VT_I2);
|
||||
return this->iVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int32_t>) {
|
||||
assert(this->vt == VT_I4 || vt == VT_INT);
|
||||
return this->intVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int64_t>) {
|
||||
assert(this->vt == VT_I8);
|
||||
return this->llVal;
|
||||
}
|
||||
|
||||
// unsigned
|
||||
else if constexpr (std::is_same_v<T, uint8_t>) {
|
||||
assert(this->vt == VT_UI1);
|
||||
return this->bVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint16_t>) {
|
||||
assert(this->vt == VT_UI2);
|
||||
return this->uiVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint32_t>) {
|
||||
assert(this->vt == VT_UI4 || vt == VT_UINT);
|
||||
return this->uintVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint64_t>) {
|
||||
assert(this->vt == VT_UI8);
|
||||
return this->ullVal;
|
||||
}
|
||||
|
||||
// decimal
|
||||
else if constexpr (std::is_same_v<T, float>) {
|
||||
assert(this->vt == VT_R4);
|
||||
return this->fltVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>) {
|
||||
assert(this->vt == VT_R8);
|
||||
return this->dblVal;
|
||||
}
|
||||
|
||||
// string
|
||||
else if constexpr (std::is_same_v<T, std::string_view>) {
|
||||
assert(this->vt == VT_LPSTR);
|
||||
return this->pcVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::wstring_view>) {
|
||||
assert(this->vt == VT_BSTR || this->vt == VT_LPWSTR);
|
||||
if (this->vt == VT_LPWSTR)
|
||||
return this->bstrVal;
|
||||
else
|
||||
return { this->bstrVal, SysStringLen(this->bstrVal) };
|
||||
}
|
||||
|
||||
// array signed
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int8_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I1);
|
||||
return std::make_pair((int8_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int16_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I2);
|
||||
return std::make_pair((int16_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int32_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I4);
|
||||
return std::make_pair((int32_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int64_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I8);
|
||||
return std::make_pair((int64_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
|
||||
// array unsigned
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint8_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI1);
|
||||
return std::make_pair((uint8_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint16_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI2);
|
||||
return std::make_pair((uint16_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint32_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI4);
|
||||
return std::make_pair((uint32_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint64_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI8);
|
||||
return std::make_pair((uint64_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else {
|
||||
assert(false && "unsupported type");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
+2
-135
@@ -8,10 +8,9 @@ extern "C" {
|
||||
|
||||
#include <initguid.h>
|
||||
#include <Wbemidl.h>
|
||||
#include <utility>
|
||||
#include <string_view>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
|
||||
#include "variant.hpp"
|
||||
|
||||
enum class FFWmiNamespace {
|
||||
CIMV2,
|
||||
@@ -19,138 +18,6 @@ enum class FFWmiNamespace {
|
||||
LAST,
|
||||
};
|
||||
|
||||
struct FFWmiVariant: VARIANT {
|
||||
explicit FFWmiVariant() { VariantInit(this); }
|
||||
~FFWmiVariant() { VariantClear(this); }
|
||||
|
||||
FFWmiVariant(const FFWmiVariant&) = delete;
|
||||
FFWmiVariant(FFWmiVariant&&); // don't define it to enforce NRVO optimization
|
||||
|
||||
bool hasValue() {
|
||||
return this->vt != VT_EMPTY;
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return this->hasValue();
|
||||
}
|
||||
|
||||
template <typename T> T get()
|
||||
{
|
||||
// boolean
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
assert(this->vt == VT_BOOL);
|
||||
return this->boolVal != VARIANT_FALSE;
|
||||
}
|
||||
|
||||
// signed
|
||||
else if constexpr (std::is_same_v<T, int8_t>) {
|
||||
assert(this->vt == VT_I1);
|
||||
return this->cVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int16_t>) {
|
||||
assert(vt == VT_I2);
|
||||
return this->iVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int32_t>) {
|
||||
assert(this->vt == VT_I4 || vt == VT_INT);
|
||||
return this->intVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int64_t>) {
|
||||
assert(this->vt == VT_I8);
|
||||
return this->llVal;
|
||||
}
|
||||
|
||||
// unsigned
|
||||
else if constexpr (std::is_same_v<T, uint8_t>) {
|
||||
assert(this->vt == VT_UI1);
|
||||
return this->bVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint16_t>) {
|
||||
assert(this->vt == VT_UI2);
|
||||
return this->uiVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint32_t>) {
|
||||
assert(this->vt == VT_UI4 || vt == VT_UINT);
|
||||
return this->uintVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, uint64_t>) {
|
||||
assert(this->vt == VT_UI8);
|
||||
return this->ullVal;
|
||||
}
|
||||
|
||||
// decimal
|
||||
else if constexpr (std::is_same_v<T, float>) {
|
||||
assert(this->vt == VT_R4);
|
||||
return this->fltVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>) {
|
||||
assert(this->vt == VT_R8);
|
||||
return this->dblVal;
|
||||
}
|
||||
|
||||
// string
|
||||
else if constexpr (std::is_same_v<T, std::string_view>) {
|
||||
assert(this->vt == VT_LPSTR);
|
||||
return this->pcVal;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::wstring_view>) {
|
||||
assert(this->vt == VT_BSTR || this->vt == VT_LPWSTR);
|
||||
if (this->vt == VT_LPWSTR)
|
||||
return this->bstrVal;
|
||||
else
|
||||
return { this->bstrVal, SysStringLen(this->bstrVal) };
|
||||
}
|
||||
|
||||
// array signed
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int8_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I1);
|
||||
return std::make_pair((int8_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int16_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I2);
|
||||
return std::make_pair((int16_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int32_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I4);
|
||||
return std::make_pair((int32_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const int64_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_I8);
|
||||
return std::make_pair((int64_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
|
||||
// array unsigned
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint8_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI1);
|
||||
return std::make_pair((uint8_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint16_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI2);
|
||||
return std::make_pair((uint16_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint32_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI4);
|
||||
return std::make_pair((uint32_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::pair<const uint64_t*, uint32_t>>) {
|
||||
assert(this->vt & VT_ARRAY);
|
||||
assert((this->vt & ~VT_ARRAY) == VT_UI8);
|
||||
return std::make_pair((uint64_t*)this->parray->pvData, this->parray->cDims);
|
||||
}
|
||||
else {
|
||||
assert(false && "unsupported type");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FFWmiRecord
|
||||
{
|
||||
IWbemClassObject* obj;
|
||||
|
||||
Reference in New Issue
Block a user