xfreerdp: optimize bitmap updates
This commit is contained in:
parent
a3cdcc1641
commit
45d2bab95d
@ -882,7 +882,7 @@ BOOL xf_post_connect(freerdp *instance)
|
||||
|
||||
ZeroMemory(&gcv, sizeof(gcv));
|
||||
|
||||
if(xfc->modifierMap)
|
||||
if (xfc->modifierMap)
|
||||
XFreeModifiermap(xfc->modifierMap);
|
||||
|
||||
xfc->modifierMap = XGetModifierMapping(xfc->display);
|
||||
@ -900,7 +900,7 @@ BOOL xf_post_connect(freerdp *instance)
|
||||
(char*) xfc->primary_buffer, xfc->width, xfc->height, xfc->scanline_pad, 0);
|
||||
xfc->bmp_codec_none = (BYTE *) malloc(64 * 64 * 4);
|
||||
|
||||
if (xfc->settings->SoftwareGdi)
|
||||
if (settings->SoftwareGdi)
|
||||
{
|
||||
instance->update->BeginPaint = xf_sw_begin_paint;
|
||||
instance->update->EndPaint = xf_sw_end_paint;
|
||||
@ -915,13 +915,14 @@ BOOL xf_post_connect(freerdp *instance)
|
||||
|
||||
pointer_cache_register_callbacks(instance->update);
|
||||
|
||||
if (!xfc->settings->SoftwareGdi)
|
||||
if (!settings->SoftwareGdi)
|
||||
{
|
||||
glyph_cache_register_callbacks(instance->update);
|
||||
brush_cache_register_callbacks(instance->update);
|
||||
bitmap_cache_register_callbacks(instance->update);
|
||||
offscreen_cache_register_callbacks(instance->update);
|
||||
palette_cache_register_callbacks(instance->update);
|
||||
instance->update->BitmapUpdate = xf_gdi_bitmap_update;
|
||||
}
|
||||
|
||||
instance->context->rail = rail_new(instance->settings);
|
||||
@ -1477,22 +1478,29 @@ void xf_TerminateEventHandler(rdpContext *context, TerminateEventArgs *e)
|
||||
|
||||
static void xf_ScalingFactorChangeEventHandler(rdpContext *context, ScalingFactorChangeEventArgs *e)
|
||||
{
|
||||
xfContext *xfc = (xfContext *) context;
|
||||
xfContext* xfc = (xfContext*) context;
|
||||
|
||||
xfc->settings->ScalingFactor += e->ScalingFactor;
|
||||
if(xfc->settings->ScalingFactor > 1.2)
|
||||
|
||||
if (xfc->settings->ScalingFactor > 1.2)
|
||||
xfc->settings->ScalingFactor = 1.2;
|
||||
if(xfc->settings->ScalingFactor < 0.8)
|
||||
|
||||
if (xfc->settings->ScalingFactor < 0.8)
|
||||
xfc->settings->ScalingFactor = 0.8;
|
||||
|
||||
xfc->currentWidth = xfc->originalWidth * xfc->settings->ScalingFactor;
|
||||
xfc->currentHeight = xfc->originalHeight * xfc->settings->ScalingFactor;
|
||||
|
||||
xf_transform_window(xfc);
|
||||
|
||||
{
|
||||
ResizeWindowEventArgs ev;
|
||||
EventArgsInit(&ev, "xfreerdp");
|
||||
ev.width = (int) xfc->originalWidth * xfc->settings->ScalingFactor;
|
||||
ev.height = (int) xfc->originalHeight * xfc->settings->ScalingFactor;
|
||||
PubSub_OnResizeWindow(((rdpContext *) xfc)->pubSub, xfc, &ev);
|
||||
PubSub_OnResizeWindow(((rdpContext*) xfc)->pubSub, xfc, &ev);
|
||||
}
|
||||
|
||||
xf_draw_screen_scaled(xfc, 0, 0, 0, 0, FALSE);
|
||||
}
|
||||
|
||||
@ -1513,9 +1521,10 @@ static void xfreerdp_client_global_uninit()
|
||||
|
||||
static int xfreerdp_client_start(rdpContext *context)
|
||||
{
|
||||
xfContext *xfc = (xfContext *) context;
|
||||
rdpSettings *settings = context->settings;
|
||||
if(!settings->ServerHostname)
|
||||
xfContext* xfc = (xfContext *) context;
|
||||
rdpSettings* settings = context->settings;
|
||||
|
||||
if (!settings->ServerHostname)
|
||||
{
|
||||
DEBUG_WARN( "error: server hostname was not specified with /v:<server>[:port]\n");
|
||||
return -1;
|
||||
@ -1525,11 +1534,11 @@ static int xfreerdp_client_start(rdpContext *context)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xfreerdp_client_stop(rdpContext *context)
|
||||
static int xfreerdp_client_stop(rdpContext* context)
|
||||
{
|
||||
xfContext *xfc = (xfContext *) context;
|
||||
assert(NULL != context);
|
||||
if(context->settings->AsyncInput)
|
||||
xfContext* xfc = (xfContext*) context;
|
||||
|
||||
if (context->settings->AsyncInput)
|
||||
{
|
||||
wMessageQueue *queue;
|
||||
queue = freerdp_get_message_queue(context->instance, FREERDP_INPUT_MESSAGE_QUEUE);
|
||||
@ -1540,19 +1549,23 @@ static int xfreerdp_client_stop(rdpContext *context)
|
||||
{
|
||||
xfc->disconnect = TRUE;
|
||||
}
|
||||
if(xfc->thread)
|
||||
|
||||
if (xfc->thread)
|
||||
{
|
||||
CloseHandle(xfc->thread);
|
||||
xfc->thread = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xfreerdp_client_new(freerdp *instance, rdpContext *context)
|
||||
static int xfreerdp_client_new(freerdp* instance, rdpContext* context)
|
||||
{
|
||||
xfContext *xfc;
|
||||
rdpSettings *settings;
|
||||
xfc = (xfContext *) instance->context;
|
||||
xfContext* xfc;
|
||||
rdpSettings* settings;
|
||||
|
||||
xfc = (xfContext*) instance->context;
|
||||
|
||||
instance->PreConnect = xf_pre_connect;
|
||||
instance->PostConnect = xf_post_connect;
|
||||
instance->PostDisconnect = xf_post_disconnect;
|
||||
@ -1560,27 +1573,36 @@ static int xfreerdp_client_new(freerdp *instance, rdpContext *context)
|
||||
instance->VerifyCertificate = xf_verify_certificate;
|
||||
instance->LogonErrorInfo = xf_logon_error_info;
|
||||
context->channels = freerdp_channels_new();
|
||||
|
||||
settings = instance->settings;
|
||||
xfc->settings = instance->context->settings;
|
||||
|
||||
PubSub_SubscribeTerminate(context->pubSub, (pTerminateEventHandler) xf_TerminateEventHandler);
|
||||
PubSub_SubscribeScalingFactorChange(context->pubSub, (pScalingFactorChangeEventHandler) xf_ScalingFactorChangeEventHandler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xfreerdp_client_free(freerdp *instance, rdpContext *context)
|
||||
static void xfreerdp_client_free(freerdp* instance, rdpContext* context)
|
||||
{
|
||||
xfContext *xfc = (xfContext *) context;
|
||||
if(context)
|
||||
xfContext* xfc = (xfContext*) context;
|
||||
|
||||
if (context)
|
||||
{
|
||||
xf_window_free(xfc);
|
||||
if(xfc->bmp_codec_none)
|
||||
|
||||
if (xfc->bmp_codec_none)
|
||||
free(xfc->bmp_codec_none);
|
||||
if(xfc->display)
|
||||
|
||||
if (xfc->bitmap_buffer)
|
||||
_aligned_free(xfc->bitmap_buffer);
|
||||
|
||||
if (xfc->display)
|
||||
XCloseDisplay(xfc->display);
|
||||
}
|
||||
}
|
||||
|
||||
int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS *pEntryPoints)
|
||||
int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
|
||||
{
|
||||
pEntryPoints->Version = 1;
|
||||
pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
|
||||
|
@ -285,6 +285,108 @@ Pixmap xf_mono_bitmap_new(xfContext* xfc, int width, int height, BYTE* data)
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
void xf_gdi_bitmap_update(rdpContext* context, BITMAP_UPDATE* bitmapUpdate)
|
||||
{
|
||||
int status;
|
||||
int nXDst;
|
||||
int nYDst;
|
||||
int nXSrc;
|
||||
int nYSrc;
|
||||
int nWidth;
|
||||
int nHeight;
|
||||
UINT32 index;
|
||||
XImage* image;
|
||||
BYTE* pSrcData;
|
||||
BYTE* pDstData;
|
||||
UINT32 SrcSize;
|
||||
BOOL compressed;
|
||||
UINT32 SrcFormat;
|
||||
UINT32 bitsPerPixel;
|
||||
UINT32 bytesPerPixel;
|
||||
BITMAP_DATA* bitmap;
|
||||
rdpCodecs* codecs = context->codecs;
|
||||
xfContext* xfc = (xfContext*) context;
|
||||
|
||||
for (index = 0; index < bitmapUpdate->number; index++)
|
||||
{
|
||||
bitmap = &(bitmapUpdate->rectangles[index]);
|
||||
|
||||
nXSrc = 0;
|
||||
nYSrc = 0;
|
||||
|
||||
nXDst = bitmap->destLeft;
|
||||
nYDst = bitmap->destTop;
|
||||
|
||||
nWidth = bitmap->width;
|
||||
nHeight = bitmap->height;
|
||||
|
||||
pSrcData = bitmap->bitmapDataStream;
|
||||
SrcSize = bitmap->bitmapLength;
|
||||
|
||||
compressed = bitmap->compressed;
|
||||
bitsPerPixel = bitmap->bitsPerPixel;
|
||||
bytesPerPixel = (bitsPerPixel + 7) / 8;
|
||||
|
||||
SrcFormat = gdi_get_pixel_format(bitsPerPixel, TRUE);
|
||||
|
||||
if (xfc->bitmap_size < (nWidth * nHeight * 4))
|
||||
{
|
||||
xfc->bitmap_size = nWidth * nHeight * 4;
|
||||
xfc->bitmap_buffer = (BYTE*) _aligned_realloc(xfc->bitmap_buffer, xfc->bitmap_size, 16);
|
||||
|
||||
if (!xfc->bitmap_buffer)
|
||||
return;
|
||||
}
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
pDstData = xfc->bitmap_buffer;
|
||||
|
||||
if (bitsPerPixel < 32)
|
||||
{
|
||||
freerdp_client_codecs_prepare(codecs, FREERDP_CODEC_INTERLEAVED);
|
||||
|
||||
status = interleaved_decompress(codecs->interleaved, pSrcData, SrcSize, bitsPerPixel,
|
||||
&pDstData, PIXEL_FORMAT_XRGB32, nWidth * 4, 0, 0, nWidth, nHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
freerdp_client_codecs_prepare(codecs, FREERDP_CODEC_PLANAR);
|
||||
|
||||
status = planar_decompress(codecs->planar, pSrcData, SrcSize, &pDstData,
|
||||
PIXEL_FORMAT_XRGB32_VF, nWidth * 4, 0, 0, nWidth, nHeight);
|
||||
}
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
DEBUG_WARN("xf_gdi_bitmap_update: bitmap decompression failure\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pSrcData = xfc->bitmap_buffer;
|
||||
}
|
||||
|
||||
xf_lock_x11(xfc, FALSE);
|
||||
|
||||
XSetFunction(xfc->display, xfc->gc, GXcopy);
|
||||
|
||||
image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
|
||||
(char*) pSrcData, nWidth, nHeight, xfc->scanline_pad, 0);
|
||||
|
||||
nWidth = bitmap->destRight - bitmap->destLeft + 1; /* clip width */
|
||||
nHeight = bitmap->destBottom - bitmap->destTop + 1; /* clip height */
|
||||
|
||||
XPutImage(xfc->display, xfc->primary, xfc->gc,
|
||||
image, 0, 0, nXDst, nYDst, nWidth, nHeight);
|
||||
|
||||
XFree(image);
|
||||
|
||||
gdi_InvalidateRegion(xfc->hdc, nXDst, nYDst, nWidth, nHeight);
|
||||
|
||||
xf_unlock_x11(xfc, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void xf_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette)
|
||||
{
|
||||
xfContext* xfc = (xfContext*) context;
|
||||
|
@ -26,5 +26,6 @@
|
||||
#include "xfreerdp.h"
|
||||
|
||||
void xf_gdi_register_update_callbacks(rdpUpdate* update);
|
||||
void xf_gdi_bitmap_update(rdpContext* context, BITMAP_UPDATE* bitmapUpdate);
|
||||
|
||||
#endif /* __XF_GDI_H */
|
||||
|
@ -113,6 +113,8 @@ struct xf_context
|
||||
BOOL cursorHidden;
|
||||
|
||||
HGDI_DC hdc;
|
||||
UINT32 bitmap_size;
|
||||
BYTE* bitmap_buffer;
|
||||
BYTE* primary_buffer;
|
||||
REGION16 invalidRegion;
|
||||
BOOL inGfxFrame;
|
||||
|
@ -32,8 +32,8 @@ struct _BITMAP_INTERLEAVED_CONTEXT
|
||||
{
|
||||
BOOL Compressor;
|
||||
|
||||
UINT32 FlipSize;
|
||||
BYTE* FlipBuffer;
|
||||
UINT32 TempSize;
|
||||
BYTE* TempBuffer;
|
||||
};
|
||||
|
||||
FREERDP_API int interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pSrcData, UINT32 SrcSize, int bpp,
|
||||
|
@ -298,6 +298,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
FREERDP_API UINT32 gdi_rop3_code(BYTE code);
|
||||
FREERDP_API UINT32 gdi_get_pixel_format(UINT32 bitsPerPixel, BOOL vFlip);
|
||||
FREERDP_API BYTE* gdi_get_bitmap_pointer(HGDI_DC hdcBmp, int x, int y);
|
||||
FREERDP_API BYTE* gdi_get_brush_pointer(HGDI_DC hdcBrush, int x, int y);
|
||||
FREERDP_API int gdi_is_mono_pixel_set(BYTE* data, int x, int y, int width);
|
||||
|
@ -238,9 +238,11 @@ static INLINE UINT32 ExtractRunLength(UINT32 code, BYTE* pbOrderHdr, UINT32* adv
|
||||
int interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pSrcData, UINT32 SrcSize, int bpp,
|
||||
BYTE** ppDstData, DWORD DstFormat, int nDstStep, int nXDst, int nYDst, int nWidth, int nHeight)
|
||||
{
|
||||
int status;
|
||||
BOOL vFlip;
|
||||
int scanline;
|
||||
BYTE* pDstData;
|
||||
UINT32 SrcFormat;
|
||||
UINT32 BufferSize;
|
||||
int dstBitsPerPixel;
|
||||
int dstBytesPerPixel;
|
||||
@ -258,72 +260,81 @@ int interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pSrcDa
|
||||
scanline = nWidth * 3;
|
||||
BufferSize = scanline * nHeight;
|
||||
|
||||
if (vFlip)
|
||||
{
|
||||
if (BufferSize > interleaved->FlipSize)
|
||||
{
|
||||
interleaved->FlipBuffer = _aligned_realloc(interleaved->FlipBuffer, BufferSize, 16);
|
||||
interleaved->FlipSize = BufferSize;
|
||||
}
|
||||
SrcFormat = PIXEL_FORMAT_RGB24_VF;
|
||||
|
||||
if (!interleaved->FlipBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress24to24(pSrcData, SrcSize, interleaved->FlipBuffer, scanline, nWidth, nHeight);
|
||||
freerdp_bitmap_flip(interleaved->FlipBuffer, pDstData, scanline, nHeight);
|
||||
}
|
||||
else
|
||||
if ((SrcFormat == DstFormat) && !nXDst && !nYDst && (scanline == nDstStep))
|
||||
{
|
||||
RleDecompress24to24(pSrcData, SrcSize, pDstData, scanline, nWidth, nHeight);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (BufferSize > interleaved->TempSize)
|
||||
{
|
||||
interleaved->TempBuffer = _aligned_realloc(interleaved->TempBuffer, BufferSize, 16);
|
||||
interleaved->TempSize = BufferSize;
|
||||
}
|
||||
|
||||
if (!interleaved->TempBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress24to24(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nWidth, nHeight);
|
||||
|
||||
status = freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst,
|
||||
nWidth, nHeight, interleaved->TempBuffer, SrcFormat, scanline, 0, 0);
|
||||
}
|
||||
else if ((bpp == 16) || (bpp == 15))
|
||||
{
|
||||
scanline = nWidth * 2;
|
||||
BufferSize = scanline * nHeight;
|
||||
|
||||
if (vFlip)
|
||||
{
|
||||
if (BufferSize > interleaved->FlipSize)
|
||||
{
|
||||
interleaved->FlipBuffer = _aligned_realloc(interleaved->FlipBuffer, BufferSize, 16);
|
||||
interleaved->FlipSize = BufferSize;
|
||||
}
|
||||
SrcFormat = (bpp == 16) ? PIXEL_FORMAT_RGB16_VF : PIXEL_FORMAT_RGB15_VF;
|
||||
|
||||
if (!interleaved->FlipBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress16to16(pSrcData, SrcSize, interleaved->FlipBuffer, scanline, nWidth, nHeight);
|
||||
freerdp_bitmap_flip(interleaved->FlipBuffer, pDstData, scanline, nHeight);
|
||||
}
|
||||
else
|
||||
if ((SrcFormat == DstFormat) && !nXDst && !nYDst && (scanline == nDstStep))
|
||||
{
|
||||
RleDecompress16to16(pSrcData, SrcSize, pDstData, scanline, nWidth, nHeight);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (BufferSize > interleaved->TempSize)
|
||||
{
|
||||
interleaved->TempBuffer = _aligned_realloc(interleaved->TempBuffer, BufferSize, 16);
|
||||
interleaved->TempSize = BufferSize;
|
||||
}
|
||||
|
||||
if (!interleaved->TempBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress16to16(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nWidth, nHeight);
|
||||
|
||||
status = freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst,
|
||||
nWidth, nHeight, interleaved->TempBuffer, SrcFormat, scanline, 0, 0);
|
||||
}
|
||||
else if (bpp == 8)
|
||||
{
|
||||
scanline = nWidth;
|
||||
BufferSize = scanline * nHeight;
|
||||
|
||||
if (vFlip)
|
||||
{
|
||||
if (BufferSize > interleaved->FlipSize)
|
||||
{
|
||||
interleaved->FlipBuffer = _aligned_realloc(interleaved->FlipBuffer, BufferSize, 16);
|
||||
interleaved->FlipSize = BufferSize;
|
||||
}
|
||||
SrcFormat = PIXEL_FORMAT_RGB8_VF;
|
||||
|
||||
if (!interleaved->FlipBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress8to8(pSrcData, SrcSize, interleaved->FlipBuffer, scanline, nWidth, nHeight);
|
||||
freerdp_bitmap_flip(interleaved->FlipBuffer, pDstData, scanline, nHeight);
|
||||
}
|
||||
else
|
||||
if ((SrcFormat == DstFormat) && !nXDst && !nYDst && (scanline == nDstStep))
|
||||
{
|
||||
RleDecompress8to8(pSrcData, SrcSize, pDstData, scanline, nWidth, nHeight);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (BufferSize > interleaved->TempSize)
|
||||
{
|
||||
interleaved->TempBuffer = _aligned_realloc(interleaved->TempBuffer, BufferSize, 16);
|
||||
interleaved->TempSize = BufferSize;
|
||||
}
|
||||
|
||||
if (!interleaved->TempBuffer)
|
||||
return -1;
|
||||
|
||||
RleDecompress8to8(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nWidth, nHeight);
|
||||
|
||||
status = freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst,
|
||||
nWidth, nHeight, interleaved->TempBuffer, SrcFormat, scanline, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -341,8 +352,8 @@ BITMAP_INTERLEAVED_CONTEXT* bitmap_interleaved_context_new(BOOL Compressor)
|
||||
|
||||
if (interleaved)
|
||||
{
|
||||
interleaved->FlipSize = 64 * 64 * 3;
|
||||
interleaved->FlipBuffer = _aligned_malloc(interleaved->FlipSize, 16);
|
||||
interleaved->TempSize = 64 * 64 * 3;
|
||||
interleaved->TempBuffer = _aligned_malloc(interleaved->TempSize, 16);
|
||||
}
|
||||
|
||||
return interleaved;
|
||||
@ -353,7 +364,7 @@ void bitmap_interleaved_context_free(BITMAP_INTERLEAVED_CONTEXT* interleaved)
|
||||
if (!interleaved)
|
||||
return;
|
||||
|
||||
_aligned_free(interleaved->FlipBuffer);
|
||||
_aligned_free(interleaved->TempBuffer);
|
||||
|
||||
free(interleaved);
|
||||
}
|
||||
|
@ -529,9 +529,9 @@ void gdi_bitmap_update(rdpContext* context, BITMAP_UPDATE* bitmapUpdate)
|
||||
|
||||
SrcFormat = gdi_get_pixel_format(bitsPerPixel, TRUE);
|
||||
|
||||
if (gdi->bitmap_size < (nWidth * nHeight * bytesPerPixel))
|
||||
if (gdi->bitmap_size < (nWidth * nHeight * 4))
|
||||
{
|
||||
gdi->bitmap_size = nWidth * nHeight * bytesPerPixel;
|
||||
gdi->bitmap_size = nWidth * nHeight * 4;
|
||||
gdi->bitmap_buffer = (BYTE*) _aligned_realloc(gdi->bitmap_buffer, gdi->bitmap_size, 16);
|
||||
|
||||
if (!gdi->bitmap_buffer)
|
||||
@ -547,7 +547,7 @@ void gdi_bitmap_update(rdpContext* context, BITMAP_UPDATE* bitmapUpdate)
|
||||
freerdp_client_codecs_prepare(codecs, FREERDP_CODEC_INTERLEAVED);
|
||||
|
||||
status = interleaved_decompress(codecs->interleaved, pSrcData, SrcSize, bitsPerPixel,
|
||||
&pDstData, PIXEL_FORMAT_XRGB32, nWidth * 4, 0, 0, nWidth, nHeight);
|
||||
&pDstData, SrcFormat, nWidth * bytesPerPixel, 0, 0, nWidth, nHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user