mirror of
https://github.com/reactos/reactos.git
synced 2024-07-30 16:18:43 +00:00
[CONSRV]
Continue to move some functions to condrv. svn path=/trunk/; revision=59319
This commit is contained in:
parent
4eb56a46a9
commit
2f42f9205b
|
@ -12,7 +12,6 @@ list(APPEND SOURCE
|
||||||
alias.c
|
alias.c
|
||||||
coninput.c
|
coninput.c
|
||||||
conoutput.c
|
conoutput.c
|
||||||
text.c
|
|
||||||
console.c
|
console.c
|
||||||
frontendctl.c
|
frontendctl.c
|
||||||
handle.c
|
handle.c
|
||||||
|
@ -22,6 +21,7 @@ list(APPEND SOURCE
|
||||||
condrv/conoutput.c
|
condrv/conoutput.c
|
||||||
condrv/console.c
|
condrv/console.c
|
||||||
condrv/graphics.c
|
condrv/graphics.c
|
||||||
|
condrv/text.c
|
||||||
frontends/input.c
|
frontends/input.c
|
||||||
frontends/gui/guiterm.c
|
frontends/gui/guiterm.c
|
||||||
frontends/gui/guisettings.c
|
frontends/gui/guisettings.c
|
||||||
|
|
|
@ -152,10 +152,10 @@ ClearLineBuffer(PTEXTMODE_SCREEN_BUFFER Buff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static __inline BOOLEAN ConioGetIntersection(
|
static __inline BOOLEAN
|
||||||
SMALL_RECT* Intersection,
|
ConioGetIntersection(OUT PSMALL_RECT Intersection,
|
||||||
SMALL_RECT* Rect1,
|
IN PSMALL_RECT Rect1,
|
||||||
SMALL_RECT* Rect2)
|
IN PSMALL_RECT Rect2)
|
||||||
{
|
{
|
||||||
if ( ConioIsRectEmpty(Rect1) ||
|
if ( ConioIsRectEmpty(Rect1) ||
|
||||||
ConioIsRectEmpty(Rect2) ||
|
ConioIsRectEmpty(Rect2) ||
|
||||||
|
@ -170,18 +170,18 @@ static __inline BOOLEAN ConioGetIntersection(
|
||||||
}
|
}
|
||||||
|
|
||||||
ConioInitRect(Intersection,
|
ConioInitRect(Intersection,
|
||||||
max(Rect1->Top, Rect2->Top),
|
max(Rect1->Top , Rect2->Top ),
|
||||||
max(Rect1->Left, Rect2->Left),
|
max(Rect1->Left , Rect2->Left ),
|
||||||
min(Rect1->Bottom, Rect2->Bottom),
|
min(Rect1->Bottom, Rect2->Bottom),
|
||||||
min(Rect1->Right, Rect2->Right));
|
min(Rect1->Right , Rect2->Right ));
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static __inline BOOLEAN ConioGetUnion(
|
static __inline BOOLEAN
|
||||||
SMALL_RECT* Union,
|
ConioGetUnion(OUT PSMALL_RECT Union,
|
||||||
SMALL_RECT* Rect1,
|
IN PSMALL_RECT Rect1,
|
||||||
SMALL_RECT* Rect2)
|
IN PSMALL_RECT Rect2)
|
||||||
{
|
{
|
||||||
if (ConioIsRectEmpty(Rect1))
|
if (ConioIsRectEmpty(Rect1))
|
||||||
{
|
{
|
||||||
|
@ -202,17 +202,20 @@ static __inline BOOLEAN ConioGetUnion(
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ConioInitRect(Union,
|
ConioInitRect(Union,
|
||||||
min(Rect1->Top, Rect2->Top),
|
min(Rect1->Top , Rect2->Top ),
|
||||||
min(Rect1->Left, Rect2->Left),
|
min(Rect1->Left , Rect2->Left ),
|
||||||
max(Rect1->Bottom, Rect2->Bottom),
|
max(Rect1->Bottom, Rect2->Bottom),
|
||||||
max(Rect1->Right, Rect2->Right));
|
max(Rect1->Right , Rect2->Right ));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VOID FASTCALL
|
static VOID FASTCALL
|
||||||
ConioComputeUpdateRect(PTEXTMODE_SCREEN_BUFFER Buff, SMALL_RECT* UpdateRect, PCOORD Start, UINT Length)
|
ConioComputeUpdateRect(IN PTEXTMODE_SCREEN_BUFFER Buff,
|
||||||
|
IN OUT PSMALL_RECT UpdateRect,
|
||||||
|
IN PCOORD Start,
|
||||||
|
IN UINT Length)
|
||||||
{
|
{
|
||||||
if (Buff->ScreenBufferSize.X <= Start->X + Length)
|
if (Buff->ScreenBufferSize.X <= Start->X + Length)
|
||||||
{
|
{
|
||||||
|
@ -244,9 +247,9 @@ ConioComputeUpdateRect(PTEXTMODE_SCREEN_BUFFER Buff, SMALL_RECT* UpdateRect, PCO
|
||||||
*/
|
*/
|
||||||
static VOID FASTCALL
|
static VOID FASTCALL
|
||||||
ConioMoveRegion(PTEXTMODE_SCREEN_BUFFER ScreenBuffer,
|
ConioMoveRegion(PTEXTMODE_SCREEN_BUFFER ScreenBuffer,
|
||||||
SMALL_RECT* SrcRegion,
|
PSMALL_RECT SrcRegion,
|
||||||
SMALL_RECT* DstRegion,
|
PSMALL_RECT DstRegion,
|
||||||
SMALL_RECT* ClipRegion,
|
PSMALL_RECT ClipRegion,
|
||||||
CHAR_INFO FillChar)
|
CHAR_INFO FillChar)
|
||||||
{
|
{
|
||||||
int Width = ConioRectWidth(SrcRegion);
|
int Width = ConioRectWidth(SrcRegion);
|
||||||
|
@ -597,208 +600,66 @@ ConioWriteConsole(PCONSOLE Console,
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NTSTATUS
|
|
||||||
DoWriteConsole(IN PCSR_API_MESSAGE ApiMessage,
|
|
||||||
IN PCSR_THREAD ClientThread,
|
|
||||||
IN BOOL CreateWaitBlock OPTIONAL);
|
|
||||||
|
|
||||||
// Wait function CSR_WAIT_FUNCTION
|
|
||||||
static BOOLEAN
|
|
||||||
WriteConsoleThread(IN PLIST_ENTRY WaitList,
|
|
||||||
IN PCSR_THREAD WaitThread,
|
|
||||||
IN PCSR_API_MESSAGE WaitApiMessage,
|
|
||||||
IN PVOID WaitContext,
|
|
||||||
IN PVOID WaitArgument1,
|
|
||||||
IN PVOID WaitArgument2,
|
|
||||||
IN ULONG WaitFlags)
|
|
||||||
{
|
|
||||||
NTSTATUS Status;
|
|
||||||
|
|
||||||
DPRINT("WriteConsoleThread - WaitContext = 0x%p, WaitArgument1 = 0x%p, WaitArgument2 = 0x%p, WaitFlags = %lu\n", WaitContext, WaitArgument1, WaitArgument2, WaitFlags);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we are notified of the process termination via a call
|
|
||||||
* to CsrNotifyWaitBlock triggered by CsrDestroyProcess or
|
|
||||||
* CsrDestroyThread, just return.
|
|
||||||
*/
|
|
||||||
if (WaitFlags & CsrProcessTerminating)
|
|
||||||
{
|
|
||||||
Status = STATUS_THREAD_IS_TERMINATING;
|
|
||||||
goto Quit;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = DoWriteConsole(WaitApiMessage,
|
|
||||||
WaitThread,
|
|
||||||
FALSE);
|
|
||||||
|
|
||||||
Quit:
|
|
||||||
if (Status != STATUS_PENDING)
|
|
||||||
{
|
|
||||||
WaitApiMessage->Status = Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Status == STATUS_PENDING ? FALSE : TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static NTSTATUS
|
|
||||||
DoWriteConsole(IN PCSR_API_MESSAGE ApiMessage,
|
|
||||||
IN PCSR_THREAD ClientThread,
|
|
||||||
IN BOOL CreateWaitBlock OPTIONAL)
|
|
||||||
{
|
|
||||||
NTSTATUS Status = STATUS_SUCCESS;
|
|
||||||
PCONSOLE_WRITECONSOLE WriteConsoleRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteConsoleRequest;
|
|
||||||
PCONSOLE Console;
|
|
||||||
PTEXTMODE_SCREEN_BUFFER Buff;
|
|
||||||
PVOID Buffer;
|
|
||||||
DWORD Written = 0;
|
|
||||||
ULONG Length;
|
|
||||||
|
|
||||||
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(ClientThread->Process), WriteConsoleRequest->OutputHandle, &Buff, GENERIC_WRITE, FALSE);
|
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
|
||||||
|
|
||||||
Console = Buff->Header.Console;
|
|
||||||
|
|
||||||
// if (Console->PauseFlags & (PAUSED_FROM_KEYBOARD | PAUSED_FROM_SCROLLBAR | PAUSED_FROM_SELECTION))
|
|
||||||
if (Console->PauseFlags && Console->UnpauseEvent != NULL)
|
|
||||||
{
|
|
||||||
if (CreateWaitBlock)
|
|
||||||
{
|
|
||||||
if (!CsrCreateWait(&Console->WriteWaitQueue,
|
|
||||||
WriteConsoleThread,
|
|
||||||
ClientThread,
|
|
||||||
ApiMessage,
|
|
||||||
NULL,
|
|
||||||
NULL))
|
|
||||||
{
|
|
||||||
/* Fail */
|
|
||||||
ConSrvReleaseScreenBuffer(Buff, FALSE);
|
|
||||||
return STATUS_NO_MEMORY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wait until we un-pause the console */
|
|
||||||
Status = STATUS_PENDING;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (WriteConsoleRequest->Unicode)
|
|
||||||
{
|
|
||||||
Buffer = WriteConsoleRequest->Buffer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Length = MultiByteToWideChar(Console->OutputCodePage, 0,
|
|
||||||
(PCHAR)WriteConsoleRequest->Buffer,
|
|
||||||
WriteConsoleRequest->NrCharactersToWrite,
|
|
||||||
NULL, 0);
|
|
||||||
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR));
|
|
||||||
if (Buffer)
|
|
||||||
{
|
|
||||||
MultiByteToWideChar(Console->OutputCodePage, 0,
|
|
||||||
(PCHAR)WriteConsoleRequest->Buffer,
|
|
||||||
WriteConsoleRequest->NrCharactersToWrite,
|
|
||||||
(PWCHAR)Buffer, Length);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Status = STATUS_NO_MEMORY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Buffer)
|
|
||||||
{
|
|
||||||
if (NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
Status = ConioWriteConsole(Console,
|
|
||||||
Buff,
|
|
||||||
Buffer,
|
|
||||||
WriteConsoleRequest->NrCharactersToWrite,
|
|
||||||
TRUE);
|
|
||||||
if (NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
Written = WriteConsoleRequest->NrCharactersToWrite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!WriteConsoleRequest->Unicode)
|
|
||||||
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteConsoleRequest->NrCharactersWritten = Written;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConSrvReleaseScreenBuffer(Buff, FALSE);
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* PUBLIC SERVER APIS *********************************************************/
|
/* PUBLIC SERVER APIS *********************************************************/
|
||||||
|
|
||||||
CSR_API(SrvReadConsoleOutput)
|
NTSTATUS NTAPI
|
||||||
|
ConDrvReadConsoleOutput(IN PCONSOLE Console,
|
||||||
|
IN PTEXTMODE_SCREEN_BUFFER Buffer,
|
||||||
|
IN BOOL Unicode,
|
||||||
|
OUT PCHAR_INFO CharInfo/*Buffer*/,
|
||||||
|
IN PCOORD BufferSize,
|
||||||
|
IN PCOORD BufferCoord,
|
||||||
|
IN OUT PSMALL_RECT ReadRegion)
|
||||||
{
|
{
|
||||||
PCONSOLE_READOUTPUT ReadOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ReadOutputRequest;
|
|
||||||
PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
|
|
||||||
PCHAR_INFO CharInfo;
|
|
||||||
PCHAR_INFO CurCharInfo;
|
PCHAR_INFO CurCharInfo;
|
||||||
PTEXTMODE_SCREEN_BUFFER Buff;
|
|
||||||
SHORT SizeX, SizeY;
|
SHORT SizeX, SizeY;
|
||||||
NTSTATUS Status;
|
SMALL_RECT CapturedReadRegion;
|
||||||
COORD BufferSize;
|
|
||||||
COORD BufferCoord;
|
|
||||||
SMALL_RECT ReadRegion;
|
|
||||||
SMALL_RECT ScreenRect;
|
SMALL_RECT ScreenRect;
|
||||||
DWORD i;
|
DWORD i;
|
||||||
PCHAR_INFO Ptr;
|
PCHAR_INFO Ptr;
|
||||||
LONG X, Y;
|
LONG X, Y;
|
||||||
UINT CodePage;
|
UINT CodePage;
|
||||||
|
|
||||||
DPRINT("SrvReadConsoleOutput\n");
|
if (Console == NULL || Buffer == NULL || CharInfo == NULL ||
|
||||||
|
BufferSize == NULL || BufferCoord == NULL || ReadRegion == NULL)
|
||||||
CharInfo = ReadOutputRequest->CharInfo;
|
|
||||||
ReadRegion = ReadOutputRequest->ReadRegion;
|
|
||||||
BufferSize = ReadOutputRequest->BufferSize;
|
|
||||||
BufferCoord = ReadOutputRequest->BufferCoord;
|
|
||||||
|
|
||||||
if (!CsrValidateMessageBuffer(ApiMessage,
|
|
||||||
(PVOID*)&ReadOutputRequest->CharInfo,
|
|
||||||
BufferSize.X * BufferSize.Y,
|
|
||||||
sizeof(CHAR_INFO)))
|
|
||||||
{
|
{
|
||||||
return STATUS_INVALID_PARAMETER;
|
return STATUS_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = ConSrvGetTextModeBuffer(ProcessData, ReadOutputRequest->OutputHandle, &Buff, GENERIC_READ, TRUE);
|
/* Validity check */
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
ASSERT(Console == Buffer->Header.Console);
|
||||||
|
|
||||||
|
CapturedReadRegion = *ReadRegion;
|
||||||
|
|
||||||
/* FIXME: Is this correct? */
|
/* FIXME: Is this correct? */
|
||||||
CodePage = ProcessData->Console->OutputCodePage;
|
CodePage = Console->OutputCodePage;
|
||||||
|
|
||||||
SizeY = min(BufferSize.Y - BufferCoord.Y, ConioRectHeight(&ReadRegion));
|
SizeX = min(BufferSize->X - BufferCoord->X, ConioRectWidth(&CapturedReadRegion));
|
||||||
SizeX = min(BufferSize.X - BufferCoord.X, ConioRectWidth(&ReadRegion));
|
SizeY = min(BufferSize->Y - BufferCoord->Y, ConioRectHeight(&CapturedReadRegion));
|
||||||
ReadRegion.Bottom = ReadRegion.Top + SizeY;
|
CapturedReadRegion.Right = CapturedReadRegion.Left + SizeX;
|
||||||
ReadRegion.Right = ReadRegion.Left + SizeX;
|
CapturedReadRegion.Bottom = CapturedReadRegion.Top + SizeY;
|
||||||
|
|
||||||
ConioInitRect(&ScreenRect, 0, 0, Buff->ScreenBufferSize.Y, Buff->ScreenBufferSize.X);
|
ConioInitRect(&ScreenRect, 0, 0, Buffer->ScreenBufferSize.Y, Buffer->ScreenBufferSize.X);
|
||||||
if (!ConioGetIntersection(&ReadRegion, &ScreenRect, &ReadRegion))
|
if (!ConioGetIntersection(&CapturedReadRegion, &ScreenRect, &CapturedReadRegion))
|
||||||
{
|
{
|
||||||
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0, Y = ReadRegion.Top; Y < ReadRegion.Bottom; ++i, ++Y)
|
for (i = 0, Y = CapturedReadRegion.Top; Y < CapturedReadRegion.Bottom; ++i, ++Y)
|
||||||
{
|
{
|
||||||
CurCharInfo = CharInfo + (i * BufferSize.X);
|
CurCharInfo = CharInfo + (i * BufferSize->X);
|
||||||
|
|
||||||
Ptr = ConioCoordToPointer(Buff, ReadRegion.Left, Y);
|
Ptr = ConioCoordToPointer(Buffer, CapturedReadRegion.Left, Y);
|
||||||
for (X = ReadRegion.Left; X < ReadRegion.Right; ++X)
|
for (X = CapturedReadRegion.Left; X < CapturedReadRegion.Right; ++X)
|
||||||
{
|
{
|
||||||
if (ReadOutputRequest->Unicode)
|
if (Unicode)
|
||||||
{
|
{
|
||||||
CurCharInfo->Char.UnicodeChar = Ptr->Char.UnicodeChar;
|
CurCharInfo->Char.UnicodeChar = Ptr->Char.UnicodeChar;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// ConsoleUnicodeCharToAnsiChar(ProcessData->Console, &CurCharInfo->Char.AsciiChar, &Ptr->Char.UnicodeChar);
|
// ConsoleUnicodeCharToAnsiChar(Console, &CurCharInfo->Char.AsciiChar, &Ptr->Char.UnicodeChar);
|
||||||
WideCharToMultiByte(CodePage, 0, &Ptr->Char.UnicodeChar, 1,
|
WideCharToMultiByte(CodePage, 0, &Ptr->Char.UnicodeChar, 1,
|
||||||
&CurCharInfo->Char.AsciiChar, 1, NULL, NULL);
|
&CurCharInfo->Char.AsciiChar, 1, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
@ -808,81 +669,64 @@ CSR_API(SrvReadConsoleOutput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
ReadRegion->Left = CapturedReadRegion.Left;
|
||||||
|
ReadRegion->Top = CapturedReadRegion.Top ;
|
||||||
ReadOutputRequest->ReadRegion.Right = ReadRegion.Left + SizeX - 1;
|
ReadRegion->Right = CapturedReadRegion.Left + SizeX - 1;
|
||||||
ReadOutputRequest->ReadRegion.Bottom = ReadRegion.Top + SizeY - 1;
|
ReadRegion->Bottom = CapturedReadRegion.Top + SizeY - 1;
|
||||||
ReadOutputRequest->ReadRegion.Left = ReadRegion.Left;
|
|
||||||
ReadOutputRequest->ReadRegion.Top = ReadRegion.Top;
|
|
||||||
|
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSR_API(SrvWriteConsoleOutput)
|
NTSTATUS NTAPI
|
||||||
|
ConDrvWriteConsoleOutput(IN PCONSOLE Console,
|
||||||
|
IN PTEXTMODE_SCREEN_BUFFER Buffer,
|
||||||
|
IN BOOL Unicode,
|
||||||
|
IN PCHAR_INFO CharInfo/*Buffer*/,
|
||||||
|
IN PCOORD BufferSize,
|
||||||
|
IN PCOORD BufferCoord,
|
||||||
|
IN OUT PSMALL_RECT WriteRegion)
|
||||||
{
|
{
|
||||||
PCONSOLE_WRITEOUTPUT WriteOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteOutputRequest;
|
|
||||||
PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
|
|
||||||
SHORT i, X, Y, SizeX, SizeY;
|
SHORT i, X, Y, SizeX, SizeY;
|
||||||
PCONSOLE Console;
|
|
||||||
PTEXTMODE_SCREEN_BUFFER Buff;
|
|
||||||
SMALL_RECT ScreenBuffer;
|
SMALL_RECT ScreenBuffer;
|
||||||
PCHAR_INFO CurCharInfo;
|
PCHAR_INFO CurCharInfo;
|
||||||
SMALL_RECT WriteRegion;
|
SMALL_RECT CapturedWriteRegion;
|
||||||
PCHAR_INFO CharInfo;
|
|
||||||
COORD BufferCoord;
|
|
||||||
COORD BufferSize;
|
|
||||||
NTSTATUS Status;
|
|
||||||
PCHAR_INFO Ptr;
|
PCHAR_INFO Ptr;
|
||||||
|
|
||||||
DPRINT("SrvWriteConsoleOutput\n");
|
if (Console == NULL || Buffer == NULL || CharInfo == NULL ||
|
||||||
|
BufferSize == NULL || BufferCoord == NULL || WriteRegion == NULL)
|
||||||
BufferSize = WriteOutputRequest->BufferSize;
|
|
||||||
BufferCoord = WriteOutputRequest->BufferCoord;
|
|
||||||
CharInfo = WriteOutputRequest->CharInfo;
|
|
||||||
|
|
||||||
if (!CsrValidateMessageBuffer(ApiMessage,
|
|
||||||
(PVOID*)&WriteOutputRequest->CharInfo,
|
|
||||||
BufferSize.X * BufferSize.Y,
|
|
||||||
sizeof(CHAR_INFO)))
|
|
||||||
{
|
{
|
||||||
return STATUS_INVALID_PARAMETER;
|
return STATUS_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = ConSrvGetTextModeBuffer(ProcessData,
|
/* Validity check */
|
||||||
WriteOutputRequest->OutputHandle,
|
ASSERT(Console == Buffer->Header.Console);
|
||||||
&Buff,
|
|
||||||
GENERIC_WRITE,
|
|
||||||
TRUE);
|
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
|
||||||
|
|
||||||
Console = Buff->Header.Console;
|
CapturedWriteRegion = *WriteRegion;
|
||||||
|
|
||||||
WriteRegion = WriteOutputRequest->WriteRegion;
|
SizeX = min(BufferSize->X - BufferCoord->X, ConioRectWidth(&CapturedWriteRegion));
|
||||||
|
SizeY = min(BufferSize->Y - BufferCoord->Y, ConioRectHeight(&CapturedWriteRegion));
|
||||||
SizeY = min(BufferSize.Y - BufferCoord.Y, ConioRectHeight(&WriteRegion));
|
CapturedWriteRegion.Right = CapturedWriteRegion.Left + SizeX - 1;
|
||||||
SizeX = min(BufferSize.X - BufferCoord.X, ConioRectWidth(&WriteRegion));
|
CapturedWriteRegion.Bottom = CapturedWriteRegion.Top + SizeY - 1;
|
||||||
WriteRegion.Bottom = WriteRegion.Top + SizeY - 1;
|
|
||||||
WriteRegion.Right = WriteRegion.Left + SizeX - 1;
|
|
||||||
|
|
||||||
/* Make sure WriteRegion is inside the screen buffer */
|
/* Make sure WriteRegion is inside the screen buffer */
|
||||||
ConioInitRect(&ScreenBuffer, 0, 0, Buff->ScreenBufferSize.Y - 1, Buff->ScreenBufferSize.X - 1);
|
ConioInitRect(&ScreenBuffer, 0, 0, Buffer->ScreenBufferSize.Y - 1, Buffer->ScreenBufferSize.X - 1);
|
||||||
if (!ConioGetIntersection(&WriteRegion, &ScreenBuffer, &WriteRegion))
|
if (!ConioGetIntersection(&CapturedWriteRegion, &ScreenBuffer, &CapturedWriteRegion))
|
||||||
{
|
{
|
||||||
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
/*
|
||||||
|
* It is okay to have a WriteRegion completely outside
|
||||||
/* It is okay to have a WriteRegion completely outside the screen buffer.
|
* the screen buffer. No data is written then.
|
||||||
No data is written then. */
|
*/
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0, Y = WriteRegion.Top; Y <= WriteRegion.Bottom; i++, Y++)
|
for (i = 0, Y = CapturedWriteRegion.Top; Y <= CapturedWriteRegion.Bottom; i++, Y++)
|
||||||
{
|
{
|
||||||
CurCharInfo = CharInfo + (i + BufferCoord.Y) * BufferSize.X + BufferCoord.X;
|
CurCharInfo = CharInfo + (i + BufferCoord->Y) * BufferSize->X + BufferCoord->X;
|
||||||
|
|
||||||
Ptr = ConioCoordToPointer(Buff, WriteRegion.Left, Y);
|
Ptr = ConioCoordToPointer(Buffer, CapturedWriteRegion.Left, Y);
|
||||||
for (X = WriteRegion.Left; X <= WriteRegion.Right; X++)
|
for (X = CapturedWriteRegion.Left; X <= CapturedWriteRegion.Right; X++)
|
||||||
{
|
{
|
||||||
if (WriteOutputRequest->Unicode)
|
if (Unicode)
|
||||||
{
|
{
|
||||||
Ptr->Char.UnicodeChar = CurCharInfo->Char.UnicodeChar;
|
Ptr->Char.UnicodeChar = CurCharInfo->Char.UnicodeChar;
|
||||||
}
|
}
|
||||||
|
@ -896,41 +740,20 @@ CSR_API(SrvWriteConsoleOutput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConioDrawRegion(Console, &WriteRegion);
|
ConioDrawRegion(Console, &CapturedWriteRegion);
|
||||||
|
|
||||||
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
WriteRegion->Left = CapturedWriteRegion.Left;
|
||||||
|
WriteRegion->Top = CapturedWriteRegion.Top ;
|
||||||
WriteOutputRequest->WriteRegion.Right = WriteRegion.Left + SizeX - 1;
|
WriteRegion->Right = CapturedWriteRegion.Left + SizeX - 1;
|
||||||
WriteOutputRequest->WriteRegion.Bottom = WriteRegion.Top + SizeY - 1;
|
WriteRegion->Bottom = CapturedWriteRegion.Top + SizeY - 1;
|
||||||
WriteOutputRequest->WriteRegion.Left = WriteRegion.Left;
|
|
||||||
WriteOutputRequest->WriteRegion.Top = WriteRegion.Top;
|
|
||||||
|
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSR_API(SrvWriteConsole)
|
NTSTATUS NTAPI
|
||||||
|
ConDrvWriteConsole(IN PCONSOLE Console)
|
||||||
{
|
{
|
||||||
NTSTATUS Status;
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
PCONSOLE_WRITECONSOLE WriteConsoleRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteConsoleRequest;
|
|
||||||
|
|
||||||
DPRINT("SrvWriteConsole\n");
|
|
||||||
|
|
||||||
if (!CsrValidateMessageBuffer(ApiMessage,
|
|
||||||
(PVOID)&WriteConsoleRequest->Buffer,
|
|
||||||
WriteConsoleRequest->BufferSize,
|
|
||||||
sizeof(BYTE)))
|
|
||||||
{
|
|
||||||
return STATUS_INVALID_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = DoWriteConsole(ApiMessage,
|
|
||||||
CsrGetClientThread(),
|
|
||||||
TRUE);
|
|
||||||
|
|
||||||
if (Status == STATUS_PENDING)
|
|
||||||
*ReplyCode = CsrReplyPending;
|
|
||||||
|
|
||||||
return Status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CSR_API(SrvReadConsoleOutputString)
|
CSR_API(SrvReadConsoleOutputString)
|
|
@ -269,7 +269,771 @@ CSR_API(SrvSetConsoleActiveScreenBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* CSR THREADS FOR WriteConsole ***********************************************/
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
DoWriteConsole(IN PCSR_API_MESSAGE ApiMessage,
|
||||||
|
IN PCSR_THREAD ClientThread,
|
||||||
|
IN BOOL CreateWaitBlock OPTIONAL);
|
||||||
|
|
||||||
|
// Wait function CSR_WAIT_FUNCTION
|
||||||
|
static BOOLEAN
|
||||||
|
WriteConsoleThread(IN PLIST_ENTRY WaitList,
|
||||||
|
IN PCSR_THREAD WaitThread,
|
||||||
|
IN PCSR_API_MESSAGE WaitApiMessage,
|
||||||
|
IN PVOID WaitContext,
|
||||||
|
IN PVOID WaitArgument1,
|
||||||
|
IN PVOID WaitArgument2,
|
||||||
|
IN ULONG WaitFlags)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
DPRINT("WriteConsoleThread - WaitContext = 0x%p, WaitArgument1 = 0x%p, WaitArgument2 = 0x%p, WaitFlags = %lu\n", WaitContext, WaitArgument1, WaitArgument2, WaitFlags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are notified of the process termination via a call
|
||||||
|
* to CsrNotifyWaitBlock triggered by CsrDestroyProcess or
|
||||||
|
* CsrDestroyThread, just return.
|
||||||
|
*/
|
||||||
|
if (WaitFlags & CsrProcessTerminating)
|
||||||
|
{
|
||||||
|
Status = STATUS_THREAD_IS_TERMINATING;
|
||||||
|
goto Quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = DoWriteConsole(WaitApiMessage,
|
||||||
|
WaitThread,
|
||||||
|
FALSE);
|
||||||
|
|
||||||
|
Quit:
|
||||||
|
if (Status != STATUS_PENDING)
|
||||||
|
{
|
||||||
|
WaitApiMessage->Status = Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Status == STATUS_PENDING ? FALSE : TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
DoWriteConsole(IN PCSR_API_MESSAGE ApiMessage,
|
||||||
|
IN PCSR_THREAD ClientThread,
|
||||||
|
IN BOOL CreateWaitBlock OPTIONAL)
|
||||||
|
{
|
||||||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||||||
|
PCONSOLE_WRITECONSOLE WriteConsoleRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteConsoleRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
PVOID Buffer;
|
||||||
|
DWORD Written = 0;
|
||||||
|
ULONG Length;
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(ClientThread->Process), WriteConsoleRequest->OutputHandle, &Buff, GENERIC_WRITE, FALSE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
// if (Console->PauseFlags & (PAUSED_FROM_KEYBOARD | PAUSED_FROM_SCROLLBAR | PAUSED_FROM_SELECTION))
|
||||||
|
if (Console->PauseFlags && Console->UnpauseEvent != NULL)
|
||||||
|
{
|
||||||
|
if (CreateWaitBlock)
|
||||||
|
{
|
||||||
|
if (!CsrCreateWait(&Console->WriteWaitQueue,
|
||||||
|
WriteConsoleThread,
|
||||||
|
ClientThread,
|
||||||
|
ApiMessage,
|
||||||
|
NULL,
|
||||||
|
NULL))
|
||||||
|
{
|
||||||
|
/* Fail */
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, FALSE);
|
||||||
|
return STATUS_NO_MEMORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait until we un-pause the console */
|
||||||
|
Status = STATUS_PENDING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (WriteConsoleRequest->Unicode)
|
||||||
|
{
|
||||||
|
Buffer = WriteConsoleRequest->Buffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Length = MultiByteToWideChar(Console->OutputCodePage, 0,
|
||||||
|
(PCHAR)WriteConsoleRequest->Buffer,
|
||||||
|
WriteConsoleRequest->NrCharactersToWrite,
|
||||||
|
NULL, 0);
|
||||||
|
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR));
|
||||||
|
if (Buffer)
|
||||||
|
{
|
||||||
|
MultiByteToWideChar(Console->OutputCodePage, 0,
|
||||||
|
(PCHAR)WriteConsoleRequest->Buffer,
|
||||||
|
WriteConsoleRequest->NrCharactersToWrite,
|
||||||
|
(PWCHAR)Buffer, Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Status = STATUS_NO_MEMORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer)
|
||||||
|
{
|
||||||
|
if (NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
Status = ConioWriteConsole(Console,
|
||||||
|
Buff,
|
||||||
|
Buffer,
|
||||||
|
WriteConsoleRequest->NrCharactersToWrite,
|
||||||
|
TRUE);
|
||||||
|
if (NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
Written = WriteConsoleRequest->NrCharactersToWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WriteConsoleRequest->Unicode)
|
||||||
|
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteConsoleRequest->NrCharactersWritten = Written;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, FALSE);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* TEXT OUTPUT APIS ***********************************************************/
|
/* TEXT OUTPUT APIS ***********************************************************/
|
||||||
|
|
||||||
|
NTSTATUS NTAPI
|
||||||
|
ConDrvReadConsoleOutput(IN PCONSOLE Console,
|
||||||
|
IN PTEXTMODE_SCREEN_BUFFER Buffer,
|
||||||
|
IN BOOL Unicode,
|
||||||
|
OUT PCHAR_INFO CharInfo/*Buffer*/,
|
||||||
|
IN PCOORD BufferSize,
|
||||||
|
IN PCOORD BufferCoord,
|
||||||
|
IN OUT PSMALL_RECT ReadRegion);
|
||||||
|
CSR_API(SrvReadConsoleOutput)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_READOUTPUT ReadOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ReadOutputRequest;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buffer;
|
||||||
|
|
||||||
|
DPRINT("SrvReadConsoleOutput\n");
|
||||||
|
|
||||||
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
||||||
|
(PVOID*)&ReadOutputRequest->CharInfo,
|
||||||
|
ReadOutputRequest->BufferSize.X * ReadOutputRequest->BufferSize.Y,
|
||||||
|
sizeof(CHAR_INFO)))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
|
||||||
|
ReadOutputRequest->OutputHandle,
|
||||||
|
&Buffer, GENERIC_READ, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Status = ConDrvReadConsoleOutput(Buffer->Header.Console,
|
||||||
|
Buffer,
|
||||||
|
ReadOutputRequest->Unicode,
|
||||||
|
ReadOutputRequest->CharInfo,
|
||||||
|
&ReadOutputRequest->BufferSize,
|
||||||
|
&ReadOutputRequest->BufferCoord,
|
||||||
|
&ReadOutputRequest->ReadRegion);
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buffer, TRUE);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS NTAPI
|
||||||
|
ConDrvWriteConsoleOutput(IN PCONSOLE Console,
|
||||||
|
IN PTEXTMODE_SCREEN_BUFFER Buffer,
|
||||||
|
IN BOOL Unicode,
|
||||||
|
IN PCHAR_INFO CharInfo/*Buffer*/,
|
||||||
|
IN PCOORD BufferSize,
|
||||||
|
IN PCOORD BufferCoord,
|
||||||
|
IN OUT PSMALL_RECT WriteRegion);
|
||||||
|
CSR_API(SrvWriteConsoleOutput)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_WRITEOUTPUT WriteOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteOutputRequest;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buffer;
|
||||||
|
|
||||||
|
DPRINT("SrvWriteConsoleOutput\n");
|
||||||
|
|
||||||
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
||||||
|
(PVOID*)&WriteOutputRequest->CharInfo,
|
||||||
|
WriteOutputRequest->BufferSize.X * WriteOutputRequest->BufferSize.Y,
|
||||||
|
sizeof(CHAR_INFO)))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
|
||||||
|
WriteOutputRequest->OutputHandle,
|
||||||
|
&Buffer, GENERIC_WRITE, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Status = ConDrvWriteConsoleOutput(Buffer->Header.Console,
|
||||||
|
Buffer,
|
||||||
|
WriteOutputRequest->Unicode,
|
||||||
|
WriteOutputRequest->CharInfo,
|
||||||
|
&WriteOutputRequest->BufferSize,
|
||||||
|
&WriteOutputRequest->BufferCoord,
|
||||||
|
&WriteOutputRequest->WriteRegion);
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buffer, TRUE);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvWriteConsole)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_WRITECONSOLE WriteConsoleRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteConsoleRequest;
|
||||||
|
|
||||||
|
DPRINT("SrvWriteConsole\n");
|
||||||
|
|
||||||
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
||||||
|
(PVOID)&WriteConsoleRequest->Buffer,
|
||||||
|
WriteConsoleRequest->BufferSize,
|
||||||
|
sizeof(BYTE)))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = DoWriteConsole(ApiMessage,
|
||||||
|
CsrGetClientThread(),
|
||||||
|
TRUE);
|
||||||
|
|
||||||
|
if (Status == STATUS_PENDING)
|
||||||
|
*ReplyCode = CsrReplyPending;
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0000
|
||||||
|
|
||||||
|
CSR_API(SrvReadConsoleOutputString)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_READOUTPUTCODE ReadOutputCodeRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ReadOutputCodeRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
USHORT CodeType;
|
||||||
|
SHORT Xpos, Ypos;
|
||||||
|
PVOID ReadBuffer;
|
||||||
|
DWORD i;
|
||||||
|
ULONG CodeSize;
|
||||||
|
PCHAR_INFO Ptr;
|
||||||
|
|
||||||
|
DPRINT("SrvReadConsoleOutputString\n");
|
||||||
|
|
||||||
|
CodeType = ReadOutputCodeRequest->CodeType;
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
CodeSize = sizeof(CHAR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_UNICODE:
|
||||||
|
CodeSize = sizeof(WCHAR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
CodeSize = sizeof(WORD);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
||||||
|
(PVOID*)&ReadOutputCodeRequest->pCode.pCode,
|
||||||
|
ReadOutputCodeRequest->NumCodesToRead,
|
||||||
|
CodeSize))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
|
||||||
|
ReadOutputCodeRequest->OutputHandle,
|
||||||
|
&Buff,
|
||||||
|
GENERIC_READ,
|
||||||
|
TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
ReadBuffer = ReadOutputCodeRequest->pCode.pCode;
|
||||||
|
Xpos = ReadOutputCodeRequest->ReadCoord.X;
|
||||||
|
Ypos = (ReadOutputCodeRequest->ReadCoord.Y + Buff->VirtualY) % Buff->ScreenBufferSize.Y;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MSDN (ReadConsoleOutputAttribute and ReadConsoleOutputCharacter) :
|
||||||
|
*
|
||||||
|
* If the number of attributes (resp. characters) to be read from extends
|
||||||
|
* beyond the end of the specified screen buffer row, attributes (resp.
|
||||||
|
* characters) are read from the next row. If the number of attributes
|
||||||
|
* (resp. characters) to be read from extends beyond the end of the console
|
||||||
|
* screen buffer, attributes (resp. characters) up to the end of the console
|
||||||
|
* screen buffer are read.
|
||||||
|
*
|
||||||
|
* TODO: Do NOT loop up to NumCodesToRead, but stop before
|
||||||
|
* if we are going to overflow...
|
||||||
|
*/
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, Xpos, Ypos); // Doesn't work
|
||||||
|
for (i = 0; i < min(ReadOutputCodeRequest->NumCodesToRead, Buff->ScreenBufferSize.X * Buff->ScreenBufferSize.Y); ++i)
|
||||||
|
{
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, Xpos, Ypos); // Doesn't work either
|
||||||
|
Ptr = &Buff->Buffer[Xpos + Ypos * Buff->ScreenBufferSize.X];
|
||||||
|
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
ConsoleUnicodeCharToAnsiChar(Console, (PCHAR)ReadBuffer, &Ptr->Char.UnicodeChar);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_UNICODE:
|
||||||
|
*(PWCHAR)ReadBuffer = Ptr->Char.UnicodeChar;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
*(PWORD)ReadBuffer = Ptr->Attributes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ReadBuffer = (PVOID)((ULONG_PTR)ReadBuffer + CodeSize);
|
||||||
|
// ++Ptr;
|
||||||
|
|
||||||
|
Xpos++;
|
||||||
|
|
||||||
|
if (Xpos == Buff->ScreenBufferSize.X)
|
||||||
|
{
|
||||||
|
Xpos = 0;
|
||||||
|
Ypos++;
|
||||||
|
|
||||||
|
if (Ypos == Buff->ScreenBufferSize.Y)
|
||||||
|
{
|
||||||
|
Ypos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch (CodeType)
|
||||||
|
// {
|
||||||
|
// case CODE_UNICODE:
|
||||||
|
// *(PWCHAR)ReadBuffer = 0;
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case CODE_ASCII:
|
||||||
|
// *(PCHAR)ReadBuffer = 0;
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case CODE_ATTRIBUTE:
|
||||||
|
// *(PWORD)ReadBuffer = 0;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
ReadOutputCodeRequest->EndCoord.X = Xpos;
|
||||||
|
ReadOutputCodeRequest->EndCoord.Y = (Ypos - Buff->VirtualY + Buff->ScreenBufferSize.Y) % Buff->ScreenBufferSize.Y;
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
|
||||||
|
ReadOutputCodeRequest->CodesRead = (DWORD)((ULONG_PTR)ReadBuffer - (ULONG_PTR)ReadOutputCodeRequest->pCode.pCode) / CodeSize;
|
||||||
|
// <= ReadOutputCodeRequest->NumCodesToRead
|
||||||
|
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvWriteConsoleOutputString)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_WRITEOUTPUTCODE WriteOutputCodeRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteOutputCodeRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
USHORT CodeType;
|
||||||
|
PVOID ReadBuffer = NULL;
|
||||||
|
PWCHAR tmpString = NULL;
|
||||||
|
DWORD X, Y, Length; // , Written = 0;
|
||||||
|
ULONG CodeSize;
|
||||||
|
SMALL_RECT UpdateRect;
|
||||||
|
PCHAR_INFO Ptr;
|
||||||
|
|
||||||
|
DPRINT("SrvWriteConsoleOutputString\n");
|
||||||
|
|
||||||
|
CodeType = WriteOutputCodeRequest->CodeType;
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
CodeSize = sizeof(CHAR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_UNICODE:
|
||||||
|
CodeSize = sizeof(WCHAR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
CodeSize = sizeof(WORD);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
||||||
|
(PVOID*)&WriteOutputCodeRequest->pCode.pCode,
|
||||||
|
WriteOutputCodeRequest->Length,
|
||||||
|
CodeSize))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
|
||||||
|
WriteOutputCodeRequest->OutputHandle,
|
||||||
|
&Buff,
|
||||||
|
GENERIC_WRITE,
|
||||||
|
TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
if (CodeType == CODE_ASCII)
|
||||||
|
{
|
||||||
|
/* Convert the ASCII string into Unicode before writing it to the console */
|
||||||
|
Length = MultiByteToWideChar(Console->OutputCodePage, 0,
|
||||||
|
WriteOutputCodeRequest->pCode.AsciiChar,
|
||||||
|
WriteOutputCodeRequest->Length,
|
||||||
|
NULL, 0);
|
||||||
|
tmpString = ReadBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR));
|
||||||
|
if (ReadBuffer)
|
||||||
|
{
|
||||||
|
MultiByteToWideChar(Console->OutputCodePage, 0,
|
||||||
|
WriteOutputCodeRequest->pCode.AsciiChar,
|
||||||
|
WriteOutputCodeRequest->Length,
|
||||||
|
(PWCHAR)ReadBuffer, Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Status = STATUS_NO_MEMORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* For CODE_UNICODE or CODE_ATTRIBUTE, we are already OK */
|
||||||
|
ReadBuffer = WriteOutputCodeRequest->pCode.pCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReadBuffer == NULL || !NT_SUCCESS(Status)) goto Cleanup;
|
||||||
|
|
||||||
|
X = WriteOutputCodeRequest->Coord.X;
|
||||||
|
Y = (WriteOutputCodeRequest->Coord.Y + Buff->VirtualY) % Buff->ScreenBufferSize.Y;
|
||||||
|
Length = WriteOutputCodeRequest->Length;
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, X, Y); // Doesn't work
|
||||||
|
// Ptr = &Buff->Buffer[X + Y * Buff->ScreenBufferSize.X]; // May work
|
||||||
|
|
||||||
|
while (Length--)
|
||||||
|
{
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, X, Y); // Doesn't work either
|
||||||
|
Ptr = &Buff->Buffer[X + Y * Buff->ScreenBufferSize.X];
|
||||||
|
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
case CODE_UNICODE:
|
||||||
|
Ptr->Char.UnicodeChar = *(PWCHAR)ReadBuffer;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
Ptr->Attributes = *(PWORD)ReadBuffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ReadBuffer = (PVOID)((ULONG_PTR)ReadBuffer + CodeSize);
|
||||||
|
// ++Ptr;
|
||||||
|
|
||||||
|
// Written++;
|
||||||
|
if (++X == Buff->ScreenBufferSize.X)
|
||||||
|
{
|
||||||
|
X = 0;
|
||||||
|
|
||||||
|
if (++Y == Buff->ScreenBufferSize.Y)
|
||||||
|
{
|
||||||
|
Y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((PCONSOLE_SCREEN_BUFFER)Buff == Console->ActiveBuffer)
|
||||||
|
{
|
||||||
|
ConioComputeUpdateRect(Buff, &UpdateRect, &WriteOutputCodeRequest->Coord,
|
||||||
|
WriteOutputCodeRequest->Length);
|
||||||
|
ConioDrawRegion(Console, &UpdateRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOutputCodeRequest->EndCoord.X = X;
|
||||||
|
// WriteOutputCodeRequest->EndCoord.Y = (Y + Buff->ScreenBufferSize.Y - Buff->VirtualY) % Buff->ScreenBufferSize.Y;
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
if (tmpString)
|
||||||
|
RtlFreeHeap(RtlGetProcessHeap(), 0, tmpString);
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
|
||||||
|
// WriteOutputCodeRequest->NrCharactersWritten = Written;
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvFillConsoleOutput)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_FILLOUTPUTCODE FillOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.FillOutputRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
DWORD X, Y, Length; // , Written = 0;
|
||||||
|
USHORT CodeType;
|
||||||
|
PVOID Code = NULL;
|
||||||
|
PCHAR_INFO Ptr;
|
||||||
|
SMALL_RECT UpdateRect;
|
||||||
|
|
||||||
|
DPRINT("SrvFillConsoleOutput\n");
|
||||||
|
|
||||||
|
CodeType = FillOutputRequest->CodeType;
|
||||||
|
if ( (CodeType != CODE_ASCII ) &&
|
||||||
|
(CodeType != CODE_UNICODE ) &&
|
||||||
|
(CodeType != CODE_ATTRIBUTE) )
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
|
||||||
|
FillOutputRequest->OutputHandle,
|
||||||
|
&Buff,
|
||||||
|
GENERIC_WRITE,
|
||||||
|
TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
/* On-place conversion from the ASCII char to the UNICODE char */
|
||||||
|
ConsoleAnsiCharToUnicodeChar(Console, &FillOutputRequest->Code.UnicodeChar, &FillOutputRequest->Code.AsciiChar);
|
||||||
|
/* Fall through */
|
||||||
|
case CODE_UNICODE:
|
||||||
|
Code = &FillOutputRequest->Code.UnicodeChar;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
Code = &FillOutputRequest->Code.Attribute;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
X = FillOutputRequest->Coord.X;
|
||||||
|
Y = (FillOutputRequest->Coord.Y + Buff->VirtualY) % Buff->ScreenBufferSize.Y;
|
||||||
|
Length = FillOutputRequest->Length;
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, X, Y); // Doesn't work
|
||||||
|
// Ptr = &Buff->Buffer[X + Y * Buff->ScreenBufferSize.X]; // May work
|
||||||
|
|
||||||
|
while (Length--)
|
||||||
|
{
|
||||||
|
// Ptr = ConioCoordToPointer(Buff, X, Y); // Doesn't work either
|
||||||
|
Ptr = &Buff->Buffer[X + Y * Buff->ScreenBufferSize.X];
|
||||||
|
|
||||||
|
switch (CodeType)
|
||||||
|
{
|
||||||
|
case CODE_ASCII:
|
||||||
|
case CODE_UNICODE:
|
||||||
|
Ptr->Char.UnicodeChar = *(PWCHAR)Code;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CODE_ATTRIBUTE:
|
||||||
|
Ptr->Attributes = *(PWORD)Code;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// ++Ptr;
|
||||||
|
|
||||||
|
// Written++;
|
||||||
|
if (++X == Buff->ScreenBufferSize.X)
|
||||||
|
{
|
||||||
|
X = 0;
|
||||||
|
|
||||||
|
if (++Y == Buff->ScreenBufferSize.Y)
|
||||||
|
{
|
||||||
|
Y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((PCONSOLE_SCREEN_BUFFER)Buff == Console->ActiveBuffer)
|
||||||
|
{
|
||||||
|
ConioComputeUpdateRect(Buff, &UpdateRect, &FillOutputRequest->Coord,
|
||||||
|
FillOutputRequest->Length);
|
||||||
|
ConioDrawRegion(Console, &UpdateRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
/*
|
||||||
|
Length = FillOutputRequest->Length;
|
||||||
|
FillOutputRequest->NrCharactersWritten = Length;
|
||||||
|
*/
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvGetConsoleScreenBufferInfo)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_GETSCREENBUFFERINFO ScreenBufferInfoRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ScreenBufferInfoRequest;
|
||||||
|
// PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
PCONSOLE_SCREEN_BUFFER_INFO pInfo = &ScreenBufferInfoRequest->Info;
|
||||||
|
|
||||||
|
DPRINT("SrvGetConsoleScreenBufferInfo\n");
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process), ScreenBufferInfoRequest->OutputHandle, &Buff, GENERIC_READ, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
// Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
pInfo->dwSize = Buff->ScreenBufferSize;
|
||||||
|
pInfo->dwCursorPosition = Buff->CursorPosition;
|
||||||
|
pInfo->wAttributes = Buff->ScreenDefaultAttrib;
|
||||||
|
pInfo->srWindow.Left = Buff->ViewOrigin.X;
|
||||||
|
pInfo->srWindow.Top = Buff->ViewOrigin.Y;
|
||||||
|
pInfo->srWindow.Right = Buff->ViewOrigin.X + Buff->ViewSize.X - 1;
|
||||||
|
pInfo->srWindow.Bottom = Buff->ViewOrigin.Y + Buff->ViewSize.Y - 1;
|
||||||
|
pInfo->dwMaximumWindowSize = Buff->ScreenBufferSize; // TODO: Refine the computation
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvSetConsoleTextAttribute)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_SETTEXTATTRIB SetTextAttribRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.SetTextAttribRequest;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
|
||||||
|
DPRINT("SrvSetConsoleTextAttribute\n");
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process), SetTextAttribRequest->OutputHandle, &Buff, GENERIC_WRITE, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Buff->ScreenDefaultAttrib = SetTextAttribRequest->Attrib;
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvSetConsoleScreenBufferSize)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
PCONSOLE_SETSCREENBUFFERSIZE SetScreenBufferSizeRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.SetScreenBufferSizeRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process), SetScreenBufferSizeRequest->OutputHandle, &Buff, GENERIC_WRITE, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
Status = ConioResizeBuffer(Console, Buff, SetScreenBufferSizeRequest->Size);
|
||||||
|
if (NT_SUCCESS(Status)) ConioResizeTerminal(Console);
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSR_API(SrvScrollConsoleScreenBuffer)
|
||||||
|
{
|
||||||
|
PCONSOLE_SCROLLSCREENBUFFER ScrollScreenBufferRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ScrollScreenBufferRequest;
|
||||||
|
PCONSOLE Console;
|
||||||
|
PTEXTMODE_SCREEN_BUFFER Buff;
|
||||||
|
SMALL_RECT ScreenBuffer;
|
||||||
|
SMALL_RECT SrcRegion;
|
||||||
|
SMALL_RECT DstRegion;
|
||||||
|
SMALL_RECT UpdateRegion;
|
||||||
|
SMALL_RECT ScrollRectangle;
|
||||||
|
SMALL_RECT ClipRectangle;
|
||||||
|
NTSTATUS Status;
|
||||||
|
HANDLE OutputHandle;
|
||||||
|
BOOLEAN UseClipRectangle;
|
||||||
|
COORD DestinationOrigin;
|
||||||
|
CHAR_INFO FillChar;
|
||||||
|
|
||||||
|
DPRINT("SrvScrollConsoleScreenBuffer\n");
|
||||||
|
|
||||||
|
OutputHandle = ScrollScreenBufferRequest->OutputHandle;
|
||||||
|
UseClipRectangle = ScrollScreenBufferRequest->UseClipRectangle;
|
||||||
|
DestinationOrigin = ScrollScreenBufferRequest->DestinationOrigin;
|
||||||
|
FillChar = ScrollScreenBufferRequest->Fill;
|
||||||
|
|
||||||
|
Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process), OutputHandle, &Buff, GENERIC_WRITE, TRUE);
|
||||||
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
|
Console = Buff->Header.Console;
|
||||||
|
|
||||||
|
ScrollRectangle = ScrollScreenBufferRequest->ScrollRectangle;
|
||||||
|
|
||||||
|
/* Make sure source rectangle is inside the screen buffer */
|
||||||
|
ConioInitRect(&ScreenBuffer, 0, 0, Buff->ScreenBufferSize.Y - 1, Buff->ScreenBufferSize.X - 1);
|
||||||
|
if (!ConioGetIntersection(&SrcRegion, &ScreenBuffer, &ScrollRectangle))
|
||||||
|
{
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the source was clipped on the left or top, adjust the destination accordingly */
|
||||||
|
if (ScrollRectangle.Left < 0)
|
||||||
|
{
|
||||||
|
DestinationOrigin.X -= ScrollRectangle.Left;
|
||||||
|
}
|
||||||
|
if (ScrollRectangle.Top < 0)
|
||||||
|
{
|
||||||
|
DestinationOrigin.Y -= ScrollRectangle.Top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UseClipRectangle)
|
||||||
|
{
|
||||||
|
ClipRectangle = ScrollScreenBufferRequest->ClipRectangle;
|
||||||
|
if (!ConioGetIntersection(&ClipRectangle, &ClipRectangle, &ScreenBuffer))
|
||||||
|
{
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClipRectangle = ScreenBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConioInitRect(&DstRegion,
|
||||||
|
DestinationOrigin.Y,
|
||||||
|
DestinationOrigin.X,
|
||||||
|
DestinationOrigin.Y + ConioRectHeight(&SrcRegion) - 1,
|
||||||
|
DestinationOrigin.X + ConioRectWidth(&SrcRegion) - 1);
|
||||||
|
|
||||||
|
if (!ScrollScreenBufferRequest->Unicode)
|
||||||
|
ConsoleAnsiCharToUnicodeChar(Console, &FillChar.Char.UnicodeChar, &FillChar.Char.AsciiChar);
|
||||||
|
|
||||||
|
ConioMoveRegion(Buff, &SrcRegion, &DstRegion, &ClipRectangle, FillChar);
|
||||||
|
|
||||||
|
if ((PCONSOLE_SCREEN_BUFFER)Buff == Console->ActiveBuffer)
|
||||||
|
{
|
||||||
|
ConioGetUnion(&UpdateRegion, &SrcRegion, &DstRegion);
|
||||||
|
if (ConioGetIntersection(&UpdateRegion, &UpdateRegion, &ClipRectangle))
|
||||||
|
{
|
||||||
|
/* Draw update region */
|
||||||
|
ConioDrawRegion(Console, &UpdateRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConSrvReleaseScreenBuffer(Buff, TRUE);
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
Loading…
Reference in a new issue