mirror of
https://github.com/ValveSoftware/Proton.git
synced 2025-05-29 14:57:49 +03:00
vrclient: Initialize OpenVR registry vulkan extensions.
CW-Bug-Id: #24510
This commit is contained in:
parent
4ee2942625
commit
146b3d0803
@ -222,6 +222,7 @@ unique_structs = []
|
|||||||
|
|
||||||
UNIX_FUNCS = [
|
UNIX_FUNCS = [
|
||||||
'vrclient_init',
|
'vrclient_init',
|
||||||
|
'vrclient_init_registry',
|
||||||
'vrclient_HmdSystemFactory',
|
'vrclient_HmdSystemFactory',
|
||||||
'vrclient_VRClientCoreFactory',
|
'vrclient_VRClientCoreFactory',
|
||||||
'vrclient_unload',
|
'vrclient_unload',
|
||||||
|
@ -26,6 +26,7 @@ extern char *json_convert_startup_info(const char *startup_info);
|
|||||||
extern char *json_convert_paths(const char *input);
|
extern char *json_convert_paths(const char *input);
|
||||||
|
|
||||||
extern NTSTATUS vrclient_init( void *args );
|
extern NTSTATUS vrclient_init( void *args );
|
||||||
|
extern NTSTATUS vrclient_init_registry( void *args );
|
||||||
extern NTSTATUS vrclient_unload( void *args );
|
extern NTSTATUS vrclient_unload( void *args );
|
||||||
extern NTSTATUS vrclient_HmdSystemFactory( void *args );
|
extern NTSTATUS vrclient_HmdSystemFactory( void *args );
|
||||||
extern NTSTATUS vrclient_VRClientCoreFactory( void *args );
|
extern NTSTATUS vrclient_VRClientCoreFactory( void *args );
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
#include <winternl.h>
|
#include <winternl.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WINE_VK_HOST
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#pragma makedep unix
|
#pragma makedep unix
|
||||||
@ -18,6 +22,11 @@ VkPhysicalDevice_T *(*p_get_native_VkPhysicalDevice)( VkPhysicalDevice_T * );
|
|||||||
VkPhysicalDevice_T *(*p_get_wrapped_VkPhysicalDevice)( VkInstance_T *, VkPhysicalDevice_T * );
|
VkPhysicalDevice_T *(*p_get_wrapped_VkPhysicalDevice)( VkInstance_T *, VkPhysicalDevice_T * );
|
||||||
VkQueue_T *(*p_get_native_VkQueue)( VkQueue_T * );
|
VkQueue_T *(*p_get_native_VkQueue)( VkQueue_T * );
|
||||||
|
|
||||||
|
static PFN_vkCreateInstance p_vkCreateInstance;
|
||||||
|
static PFN_vkDestroyInstance p_vkDestroyInstance;
|
||||||
|
static PFN_vkEnumeratePhysicalDevices p_vkEnumeratePhysicalDevices;
|
||||||
|
static PFN_vkGetPhysicalDeviceProperties p_vkGetPhysicalDeviceProperties;
|
||||||
|
|
||||||
static void *get_winevulkan_unixlib( HMODULE winevulkan )
|
static void *get_winevulkan_unixlib( HMODULE winevulkan )
|
||||||
{
|
{
|
||||||
UINT64 unix_funcs;
|
UINT64 unix_funcs;
|
||||||
@ -42,6 +51,8 @@ static void *get_winevulkan_unixlib( HMODULE winevulkan )
|
|||||||
return dlopen( info.dli_fname, RTLD_NOW );
|
return dlopen( info.dli_fname, RTLD_NOW );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *vulkan;
|
||||||
|
|
||||||
static BOOL load_vk_unwrappers( HMODULE winevulkan )
|
static BOOL load_vk_unwrappers( HMODULE winevulkan )
|
||||||
{
|
{
|
||||||
void *unix_handle;
|
void *unix_handle;
|
||||||
@ -56,7 +67,8 @@ static BOOL load_vk_unwrappers( HMODULE winevulkan )
|
|||||||
if (!(p_##name = (decltype(p_##name))dlsym( unix_handle, "__wine_" #name ))) \
|
if (!(p_##name = (decltype(p_##name))dlsym( unix_handle, "__wine_" #name ))) \
|
||||||
{ \
|
{ \
|
||||||
ERR( "%s not found.\n", #name ); \
|
ERR( "%s not found.\n", #name ); \
|
||||||
return FALSE; \
|
dlclose( unix_handle ); \
|
||||||
|
return FALSE; \
|
||||||
}
|
}
|
||||||
|
|
||||||
LOAD_FUNC( get_native_VkDevice )
|
LOAD_FUNC( get_native_VkDevice )
|
||||||
@ -67,12 +79,230 @@ static BOOL load_vk_unwrappers( HMODULE winevulkan )
|
|||||||
|
|
||||||
#undef LOAD_FUNC
|
#undef LOAD_FUNC
|
||||||
|
|
||||||
dlclose(unix_handle);
|
dlclose( unix_handle );
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL load_vulkan(void)
|
||||||
|
{
|
||||||
|
if (!(vulkan = dlopen( "libvulkan.so.1", RTLD_NOW )) &&
|
||||||
|
!(vulkan = dlopen( "libvulkan.so", RTLD_NOW )))
|
||||||
|
{
|
||||||
|
ERR( "Unable to open libvulkan.so.1\n" );
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LOAD_FUNC( name ) \
|
||||||
|
if (!(p_##name = (decltype(p_##name))dlsym( vulkan, #name ))) \
|
||||||
|
{ \
|
||||||
|
ERR( "%s not found.\n", #name ); \
|
||||||
|
dlclose( vulkan ); \
|
||||||
|
return FALSE; \
|
||||||
|
}
|
||||||
|
|
||||||
|
LOAD_FUNC( vkCreateInstance )
|
||||||
|
LOAD_FUNC( vkDestroyInstance )
|
||||||
|
LOAD_FUNC( vkEnumeratePhysicalDevices )
|
||||||
|
LOAD_FUNC( vkGetPhysicalDeviceProperties )
|
||||||
|
|
||||||
|
#undef LOAD_FUNC
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *vrclient;
|
static void *vrclient;
|
||||||
|
|
||||||
|
static UINT asciiz_to_unicode( WCHAR *dst, const char *src )
|
||||||
|
{
|
||||||
|
WCHAR *p = dst;
|
||||||
|
while ((*p++ = *src++));
|
||||||
|
return (p - dst) * sizeof(WCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL set_reg_value( HKEY hkey, const WCHAR *name, UINT type, const void *value, DWORD count )
|
||||||
|
{
|
||||||
|
unsigned short name_size = name ? lstrlenW( name ) * sizeof(WCHAR) : 0;
|
||||||
|
UNICODE_STRING nameW = { name_size, name_size, (WCHAR *)name };
|
||||||
|
return !NtSetValueKey( hkey, &nameW, 0, type, value, count );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_reg_ascii_dword( HKEY hkey, const char *name, DWORD value )
|
||||||
|
{
|
||||||
|
WCHAR nameW[64], valueW[128];
|
||||||
|
asciiz_to_unicode( nameW, name );
|
||||||
|
set_reg_value( hkey, nameW, REG_DWORD, &value, sizeof(value) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_reg_ascii_str( HKEY hkey, const char *name, const char *value )
|
||||||
|
{
|
||||||
|
size_t len = strlen( value ) + 1;
|
||||||
|
WCHAR nameW[64], *valueW;
|
||||||
|
|
||||||
|
if (!(valueW = (WCHAR *)malloc( len * sizeof(*valueW) ))) return;
|
||||||
|
asciiz_to_unicode( valueW, value );
|
||||||
|
asciiz_to_unicode( nameW, name );
|
||||||
|
set_reg_value( hkey, nameW, REG_SZ, valueW, len * sizeof(*valueW) );
|
||||||
|
free( valueW );
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_extensions( const char *extensions, char ***list )
|
||||||
|
{
|
||||||
|
uint32_t count = 0, o = 0;
|
||||||
|
char *ptr, *str, **entry;
|
||||||
|
|
||||||
|
*list = NULL;
|
||||||
|
if (!(ptr = str = strdup( extensions ))) return 0;
|
||||||
|
for (ptr = str; *ptr;) if (*ptr++ == ' ') count++;
|
||||||
|
if (ptr != str) count++;
|
||||||
|
|
||||||
|
*list = entry = (char **)malloc( (count + 1) * sizeof(*list) );
|
||||||
|
for (*entry++ = ptr = str; *ptr;) if (*ptr++ == ' ') { ptr[-1] = 0; *entry++ = ptr; }
|
||||||
|
*entry = NULL;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS vrclient_init_registry( void *args )
|
||||||
|
{
|
||||||
|
struct vrclient_init_registry_params *params = (struct vrclient_init_registry_params *)args;
|
||||||
|
VkApplicationInfo app_info =
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||||
|
.pApplicationName = "proton_vrhelper",
|
||||||
|
.applicationVersion = 1,
|
||||||
|
.pEngineName = "proton_vrhelper",
|
||||||
|
.engineVersion = 1,
|
||||||
|
.apiVersion = VK_MAKE_VERSION(1, 1, 0),
|
||||||
|
};
|
||||||
|
VkInstanceCreateInfo inst_info =
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||||
|
.pApplicationInfo = &app_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t error, instance_extensions_count, device_count;
|
||||||
|
u_IVRClientCore_IVRClientCore_003 *client_core;
|
||||||
|
u_IVRCompositor_IVRCompositor_028 *compositor;
|
||||||
|
VkPhysicalDevice *phys_devices = NULL;
|
||||||
|
VkPhysicalDeviceProperties prop = {};
|
||||||
|
char *buffer = NULL, *new_buffer;
|
||||||
|
char *xr_inst_ext, *xr_dev_ext;
|
||||||
|
VkInstance vk_instance = NULL;
|
||||||
|
BOOL vr_initialized = FALSE;
|
||||||
|
char **instance_extensions;
|
||||||
|
HMODULE hvulkan = NULL;
|
||||||
|
DWORD vr_status = ~0u;
|
||||||
|
unsigned int length;
|
||||||
|
HMODULE hwineopenxr;
|
||||||
|
void *lib_vrclient;
|
||||||
|
void *unix_handle;
|
||||||
|
DWORD hmd_present;
|
||||||
|
int return_code;
|
||||||
|
unsigned int i;
|
||||||
|
VkResult res;
|
||||||
|
|
||||||
|
TRACE( "Starting VR info initialization.\n" );
|
||||||
|
|
||||||
|
if (!load_vulkan()) return 0;
|
||||||
|
|
||||||
|
if (!(client_core = (u_IVRClientCore_IVRClientCore_003 *)p_VRClientCoreFactory( "IVRClientCore_003", &return_code )))
|
||||||
|
{
|
||||||
|
ERR( "Could not get IVRClientCore, error %d.\n", return_code );
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Without overriding the app_key vrclient waits 2 seconds for a valid appkey before returning. */
|
||||||
|
if ((error = client_core->Init( VRApplication_Background, NULL )))
|
||||||
|
{
|
||||||
|
if (error == VRInitError_Init_NoServerForBackgroundApp) TRACE( "VR server is not available.\n" );
|
||||||
|
else ERR( "VR init failed, error %u.\n", error );
|
||||||
|
client_core = NULL;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
hmd_present = !!client_core->BIsHmdPresent();
|
||||||
|
TRACE( "hmd_present %#x.\n", hmd_present );
|
||||||
|
set_reg_ascii_dword( params->vr_key, "is_hmd_present", hmd_present );
|
||||||
|
|
||||||
|
if (!(compositor = (u_IVRCompositor_IVRCompositor_028 *)client_core->GetGenericInterface( "IVRCompositor_028", &error )))
|
||||||
|
{
|
||||||
|
ERR( "Could not get compositor, error %u.\n", error );
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = compositor->GetVulkanInstanceExtensionsRequired( nullptr, 0 );
|
||||||
|
if (!(buffer = (char *)malloc( length ))) goto failed;
|
||||||
|
*buffer = 0;
|
||||||
|
compositor->GetVulkanInstanceExtensionsRequired( buffer, length );
|
||||||
|
TRACE( "Instance extensions %s.\n", buffer );
|
||||||
|
set_reg_ascii_str( params->vr_key, "openvr_vulkan_instance_extensions", buffer );
|
||||||
|
|
||||||
|
inst_info.enabledExtensionCount = parse_extensions( buffer, &instance_extensions );
|
||||||
|
inst_info.ppEnabledExtensionNames = instance_extensions;
|
||||||
|
res = p_vkCreateInstance( &inst_info, NULL, &vk_instance );
|
||||||
|
if (instance_extensions) free( instance_extensions[0] );
|
||||||
|
free( instance_extensions );
|
||||||
|
free( buffer );
|
||||||
|
|
||||||
|
if (res != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
ERR( "Could not create VK instance, res %d.\n", res );
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((res = p_vkEnumeratePhysicalDevices( vk_instance, &device_count, NULL )) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
ERR( "Could not enumerate physical devices, res %d.\n", res );
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if (!(phys_devices = (VkPhysicalDevice *)malloc( device_count * sizeof(*phys_devices) ))) goto failed;
|
||||||
|
if ((res = p_vkEnumeratePhysicalDevices( vk_instance, &device_count, phys_devices )) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
ERR( "Could not enumerate physical devices, res %d.\n", res );
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < device_count; ++i)
|
||||||
|
{
|
||||||
|
char name[256], *buffer;
|
||||||
|
|
||||||
|
p_vkGetPhysicalDeviceProperties( phys_devices[i], &prop );
|
||||||
|
if (prop.apiVersion < VK_MAKE_VERSION( 1, 1, 0 ))
|
||||||
|
{
|
||||||
|
TRACE( "Skipping Vulkan 1.0 adapter %s.\n", prop.deviceName );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = compositor->GetVulkanDeviceExtensionsRequired( phys_devices[i], nullptr, 0 );
|
||||||
|
if (!(buffer = (char *)malloc( length ))) goto failed;
|
||||||
|
*buffer = 0;
|
||||||
|
compositor->GetVulkanDeviceExtensionsRequired( phys_devices[i], buffer, length );
|
||||||
|
|
||||||
|
sprintf( name, "PCIID:%04x:%04x", prop.vendorID, prop.deviceID );
|
||||||
|
TRACE( "%s: %s.\n", name, buffer );
|
||||||
|
set_reg_ascii_str( params->vr_key, name, buffer );
|
||||||
|
|
||||||
|
free( buffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
free( phys_devices );
|
||||||
|
p_vkDestroyInstance( vk_instance, NULL );
|
||||||
|
|
||||||
|
client_core->Cleanup();
|
||||||
|
params->_ret = true;
|
||||||
|
dlclose( vulkan );
|
||||||
|
vulkan = NULL;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
free( phys_devices );
|
||||||
|
if (vk_instance) p_vkDestroyInstance( vk_instance, NULL );
|
||||||
|
if (client_core) client_core->Cleanup();
|
||||||
|
dlclose( vulkan );
|
||||||
|
vulkan = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
NTSTATUS vrclient_init( void *args )
|
NTSTATUS vrclient_init( void *args )
|
||||||
{
|
{
|
||||||
struct vrclient_init_params *params = (struct vrclient_init_params *)args;
|
struct vrclient_init_params *params = (struct vrclient_init_params *)args;
|
||||||
@ -103,8 +333,7 @@ NTSTATUS vrclient_init( void *args )
|
|||||||
|
|
||||||
#undef LOAD_FUNC
|
#undef LOAD_FUNC
|
||||||
|
|
||||||
if (!load_vk_unwrappers( params->winevulkan ))
|
if (!load_vk_unwrappers( params->winevulkan )) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
params->_ret = true;
|
params->_ret = true;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -53,6 +53,12 @@ struct vrclient_init_params
|
|||||||
char *unix_path;
|
char *unix_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct vrclient_init_registry_params
|
||||||
|
{
|
||||||
|
int8_t _ret;
|
||||||
|
HKEY vr_key;
|
||||||
|
};
|
||||||
|
|
||||||
struct vrclient_HmdSystemFactory_params
|
struct vrclient_HmdSystemFactory_params
|
||||||
{
|
{
|
||||||
void *_ret;
|
void *_ret;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
extern "C" const unixlib_entry_t __wine_unix_call_funcs[] =
|
extern "C" const unixlib_entry_t __wine_unix_call_funcs[] =
|
||||||
{
|
{
|
||||||
vrclient_init,
|
vrclient_init,
|
||||||
|
vrclient_init_registry,
|
||||||
vrclient_HmdSystemFactory,
|
vrclient_HmdSystemFactory,
|
||||||
vrclient_VRClientCoreFactory,
|
vrclient_VRClientCoreFactory,
|
||||||
vrclient_unload,
|
vrclient_unload,
|
||||||
|
@ -29222,6 +29222,7 @@ struct IVRTrackedCamera_IVRTrackedCamera_006_GetCameraTrackingSpace_params
|
|||||||
enum unix_funcs
|
enum unix_funcs
|
||||||
{
|
{
|
||||||
unix_vrclient_init,
|
unix_vrclient_init,
|
||||||
|
unix_vrclient_init_registry,
|
||||||
unix_vrclient_HmdSystemFactory,
|
unix_vrclient_HmdSystemFactory,
|
||||||
unix_vrclient_VRClientCoreFactory,
|
unix_vrclient_VRClientCoreFactory,
|
||||||
unix_vrclient_unload,
|
unix_vrclient_unload,
|
||||||
|
@ -208,10 +208,49 @@ static BOOL set_vr_status( HKEY key, DWORD value )
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static HMODULE vrclient;
|
||||||
|
|
||||||
|
static DWORD WINAPI initialize_vr_data( void *arg )
|
||||||
|
{
|
||||||
|
struct vrclient_init_registry_params params = {.vr_key = arg};
|
||||||
|
HKEY vr_key = arg;
|
||||||
|
HMODULE openxr;
|
||||||
|
|
||||||
|
VRCLIENT_CALL( vrclient_init_registry, ¶ms );
|
||||||
|
|
||||||
|
if (!params._ret)
|
||||||
|
{
|
||||||
|
ERR( "Failed to initialize VR info.\n" );
|
||||||
|
set_vr_status( vr_key, -1 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(openxr = LoadLibraryW( L"wineopenxr" )))
|
||||||
|
WARN( "Could not load wineopenxr, err %u.\n", GetLastError() );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BOOL (CDECL * init)(void);
|
||||||
|
|
||||||
|
if ((init = (void *)GetProcAddress( openxr, "wineopenxr_init_registry" ))) init();
|
||||||
|
else ERR( "Failed to find wineopenxr_init_registry export\n" );
|
||||||
|
|
||||||
|
FreeLibrary( openxr );
|
||||||
|
}
|
||||||
|
|
||||||
|
set_vr_status( vr_key, 1 );
|
||||||
|
WINE_TRACE( "Completed VR info initialization.\n" );
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey( vr_key );
|
||||||
|
|
||||||
|
FreeLibraryAndExitThread( vrclient, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
BOOL CDECL vrclient_init_registry(void)
|
BOOL CDECL vrclient_init_registry(void)
|
||||||
{
|
{
|
||||||
WCHAR pathW[PATH_MAX];
|
WCHAR pathW[PATH_MAX];
|
||||||
LSTATUS status;
|
LSTATUS status;
|
||||||
|
HANDLE thread;
|
||||||
HKEY vr_key;
|
HKEY vr_key;
|
||||||
DWORD disp;
|
DWORD disp;
|
||||||
|
|
||||||
@ -261,8 +300,17 @@ BOOL CDECL vrclient_init_registry(void)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetModuleHandleExA( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (void *)initialize_vr_data, &vrclient );
|
||||||
|
if (!(thread = CreateThread( NULL, 0, initialize_vr_data, vr_key, 0, NULL )))
|
||||||
|
{
|
||||||
|
WINE_ERR( "Could not create thread, error %u.\n", GetLastError() );
|
||||||
|
FreeLibrary( vrclient );
|
||||||
|
RegCloseKey( vr_key );
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
CloseHandle( thread );
|
||||||
|
|
||||||
TRACE( "Initialized OpenVR registry entries\n" );
|
TRACE( "Initialized OpenVR registry entries\n" );
|
||||||
RegCloseKey( vr_key );
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +101,10 @@ typedef struct VkQueue_T VkQueue_T;
|
|||||||
#define VRRenderModelError_InvalidArg 300
|
#define VRRenderModelError_InvalidArg 300
|
||||||
#define VRRenderModelError_InvalidTexture 400
|
#define VRRenderModelError_InvalidTexture 400
|
||||||
|
|
||||||
|
#define VRApplication_Background 3
|
||||||
|
|
||||||
|
#define VRInitError_Init_NoServerForBackgroundApp 121
|
||||||
|
|
||||||
#define VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff 2
|
#define VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff 2
|
||||||
|
|
||||||
enum EVRSubmitFlags
|
enum EVRSubmitFlags
|
||||||
|
Loading…
x
Reference in New Issue
Block a user