[ros-diffs] [jmorlan] 47693: [KERNEL32], [WIN32CSR] - Implement ReadConsoleW's CONSOLE_READCONSOLE_CONTROL structure. - Make pressing Insert during line input work

jmorlan at svn.reactos.org jmorlan at svn.reactos.org
Tue Jun 8 08:38:15 CEST 2010


Author: jmorlan
Date: Tue Jun  8 08:38:14 2010
New Revision: 47693

URL: http://svn.reactos.org/svn/reactos?rev=47693&view=rev
Log:
[KERNEL32], [WIN32CSR]
- Implement ReadConsoleW's CONSOLE_READCONSOLE_CONTROL structure.
- Make pressing Insert during line input work

Modified:
    trunk/reactos/dll/win32/kernel32/misc/console.c
    trunk/reactos/include/reactos/subsys/csrss/csrss.h
    trunk/reactos/subsystems/win32/csrss/win32csr/coninput.c
    trunk/reactos/subsystems/win32/csrss/win32csr/conio.h
    trunk/reactos/subsystems/win32/csrss/win32csr/conoutput.c
    trunk/reactos/subsystems/win32/csrss/win32csr/guiconsole.c
    trunk/reactos/subsystems/win32/csrss/win32csr/lineinput.c
    trunk/reactos/subsystems/win32/csrss/win32csr/tuiconsole.c

Modified: trunk/reactos/dll/win32/kernel32/misc/console.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/dll/win32/kernel32/misc/console.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/dll/win32/kernel32/misc/console.c [iso-8859-1] (original)
+++ trunk/reactos/dll/win32/kernel32/misc/console.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -1698,37 +1698,45 @@
                PVOID lpBuffer,
                DWORD nNumberOfCharsToRead,
                LPDWORD lpNumberOfCharsRead,
-               PCONSOLE_READCONSOLE_CONTROL lpReserved,
+               PCONSOLE_READCONSOLE_CONTROL pInputControl,
                BOOL bUnicode)
 {
-    PCSR_API_MESSAGE Request;
-    ULONG CsrRequest;
-    NTSTATUS Status;
-    ULONG CharSize, CharsRead = 0;
-
-    CharSize = (bUnicode ? sizeof(WCHAR) : sizeof(CHAR));
-    Request = RtlAllocateHeap(RtlGetProcessHeap(),
-                              0,
-                              max(sizeof(CSR_API_MESSAGE),
-                              CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE) + min(nNumberOfCharsToRead,
-                              CSRSS_MAX_READ_CONSOLE / CharSize) * CharSize));
-    if (Request == NULL)
+    CSR_API_MESSAGE Request;
+    PCSR_CAPTURE_BUFFER CaptureBuffer;
+    ULONG CsrRequest = MAKE_CSR_API(READ_CONSOLE, CSR_CONSOLE);
+    NTSTATUS Status = STATUS_SUCCESS;
+    ULONG CharSize = (bUnicode ? sizeof(WCHAR) : sizeof(CHAR));
+
+    CaptureBuffer = CsrAllocateCaptureBuffer(1, nNumberOfCharsToRead * CharSize);
+    if (CaptureBuffer == NULL)
     {
         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
         return FALSE;
     }
 
-    Request->Status = STATUS_SUCCESS;
-    Request->Data.ReadConsoleRequest.ConsoleHandle = hConsoleInput;
-    Request->Data.ReadConsoleRequest.Unicode = bUnicode;
-    Request->Data.ReadConsoleRequest.FullReadSize = (WORD)nNumberOfCharsToRead;
-    CsrRequest = MAKE_CSR_API(READ_CONSOLE, CSR_CONSOLE);
+    CsrAllocateMessagePointer(CaptureBuffer,
+                              nNumberOfCharsToRead * CharSize,
+                              &Request.Data.ReadConsoleRequest.Buffer);
+
+    Request.Data.ReadConsoleRequest.ConsoleHandle = hConsoleInput;
+    Request.Data.ReadConsoleRequest.Unicode = bUnicode;
+    Request.Data.ReadConsoleRequest.NrCharactersToRead = (WORD)nNumberOfCharsToRead;
+    Request.Data.ReadConsoleRequest.NrCharactersRead = 0;
+    Request.Data.ReadConsoleRequest.CtrlWakeupMask = 0;
+    if (pInputControl && pInputControl->nLength == sizeof(CONSOLE_READCONSOLE_CONTROL))
+    {
+        Request.Data.ReadConsoleRequest.NrCharactersRead = pInputControl->nInitialChars;
+        memcpy(Request.Data.ReadConsoleRequest.Buffer,
+               lpBuffer,
+               pInputControl->nInitialChars * sizeof(WCHAR));
+        Request.Data.ReadConsoleRequest.CtrlWakeupMask = pInputControl->dwCtrlWakeupMask;
+    }
 
     do
     {
-        if (Request->Status == STATUS_PENDING)
+        if (Status == STATUS_PENDING)
         {
-            Status = NtWaitForSingleObject(Request->Data.ReadConsoleRequest.EventHandle,
+            Status = NtWaitForSingleObject(Request.Data.ReadConsoleRequest.EventHandle,
                                            FALSE,
                                            0);
             if (!NT_SUCCESS(Status))
@@ -1738,37 +1746,28 @@
             }
         }
 
-        Request->Data.ReadConsoleRequest.NrCharactersToRead = (WORD)min(nNumberOfCharsToRead, CSRSS_MAX_READ_CONSOLE / CharSize);
-
-        Status = CsrClientCallServer(Request,
-                                     NULL,
-                                     CsrRequest,
-                                     max(sizeof(CSR_API_MESSAGE),
-                                     CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE)
-                                       + Request->Data.ReadConsoleRequest.NrCharactersToRead * CharSize));
-
-        if (!NT_SUCCESS(Status) || !NT_SUCCESS(Status = Request->Status))
+        Status = CsrClientCallServer(&Request, CaptureBuffer, CsrRequest, sizeof(CSR_API_MESSAGE));
+        if (!NT_SUCCESS(Status) || !NT_SUCCESS(Status = Request.Status))
         {
             DPRINT1("CSR returned error in ReadConsole\n");
-            RtlFreeHeap(RtlGetProcessHeap(), 0, Request);
+            CsrFreeCaptureBuffer(CaptureBuffer);
             SetLastErrorByStatus(Status);
             return FALSE;
         }
-
-        nNumberOfCharsToRead -= Request->Data.ReadConsoleRequest.NrCharactersRead;
-        memcpy((PVOID)((ULONG_PTR)lpBuffer + (ULONG_PTR)(CharsRead * CharSize)),
-               Request->Data.ReadConsoleRequest.Buffer,
-               Request->Data.ReadConsoleRequest.NrCharactersRead * CharSize);
-        CharsRead += Request->Data.ReadConsoleRequest.NrCharactersRead;
-    }
-    while (Request->Status == STATUS_PENDING && nNumberOfCharsToRead > 0);
+    }
+    while (Status == STATUS_PENDING);
+
+    memcpy(lpBuffer,
+           Request.Data.ReadConsoleRequest.Buffer,
+           Request.Data.ReadConsoleRequest.NrCharactersRead * CharSize);
 
     if (lpNumberOfCharsRead != NULL)
-    {
-        *lpNumberOfCharsRead = CharsRead;
-    }
-
-    RtlFreeHeap(RtlGetProcessHeap(), 0, Request);
+        *lpNumberOfCharsRead = Request.Data.ReadConsoleRequest.NrCharactersRead;
+
+    if (pInputControl && pInputControl->nLength == sizeof(CONSOLE_READCONSOLE_CONTROL))
+        pInputControl->dwControlKeyState = Request.Data.ReadConsoleRequest.ControlKeyState;
+
+    CsrFreeCaptureBuffer(CaptureBuffer);
 
     return TRUE;
 }
@@ -1791,7 +1790,7 @@
                           lpBuffer,
                           nNumberOfCharsToRead,
                           lpNumberOfCharsRead,
-                          pInputControl,
+                          NULL,
                           FALSE);
 }
 

Modified: trunk/reactos/include/reactos/subsys/csrss/csrss.h
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/include/reactos/subsys/csrss/csrss.h?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/include/reactos/subsys/csrss/csrss.h [iso-8859-1] (original)
+++ trunk/reactos/include/reactos/subsys/csrss/csrss.h [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -70,11 +70,13 @@
 {
    HANDLE ConsoleHandle;
    BOOL Unicode;
-   WORD FullReadSize;
    WORD NrCharactersToRead;
+   WORD NrCharactersRead;
    HANDLE EventHandle;
-   ULONG NrCharactersRead;
-   BYTE Buffer[0];
+   PVOID Buffer;
+   UNICODE_STRING ExeName;
+   DWORD CtrlWakeupMask;
+   DWORD ControlKeyState;
 } CSRSS_READ_CONSOLE, *PCSRSS_READ_CONSOLE;
 
 typedef struct
@@ -515,7 +517,6 @@
 #define CSRSS_MAX_WRITE_CONSOLE                 (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_WRITE_CONSOLE))
 #define CSRSS_MAX_WRITE_CONSOLE_OUTPUT_CHAR     (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_WRITE_CONSOLE_OUTPUT_CHAR))
 #define CSRSS_MAX_WRITE_CONSOLE_OUTPUT_ATTRIB   (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_WRITE_CONSOLE_OUTPUT_ATTRIB))
-#define CSRSS_MAX_READ_CONSOLE                  (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE))
 #define CSRSS_MAX_READ_CONSOLE_OUTPUT_CHAR      (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE_OUTPUT_CHAR))
 #define CSRSS_MAX_READ_CONSOLE_OUTPUT_ATTRIB    (LPC_MAX_DATA_LENGTH - CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE_OUTPUT_ATTRIB))
 

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/coninput.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/coninput.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/coninput.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/coninput.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -37,13 +37,18 @@
 
     CharSize = (Request->Data.ReadConsoleRequest.Unicode ? sizeof(WCHAR) : sizeof(CHAR));
 
-    /* truncate length to CSRSS_MAX_READ_CONSOLE_REQUEST */
-    nNumberOfCharsToRead = min(Request->Data.ReadConsoleRequest.NrCharactersToRead, CSRSS_MAX_READ_CONSOLE / CharSize);
+    nNumberOfCharsToRead = Request->Data.ReadConsoleRequest.NrCharactersToRead;
     Request->Header.u1.s1.TotalLength = sizeof(CSR_API_MESSAGE);
     Request->Header.u1.s1.DataLength = sizeof(CSR_API_MESSAGE) - sizeof(PORT_MESSAGE);
 
     Buffer = (PCHAR)Request->Data.ReadConsoleRequest.Buffer;
     UnicodeBuffer = (PWCHAR)Buffer;
+    if (!Win32CsrValidateBuffer(ProcessData, Buffer, nNumberOfCharsToRead, CharSize))
+        return STATUS_ACCESS_VIOLATION;
+
+    if (Request->Data.ReadConsoleRequest.NrCharactersRead * sizeof(WCHAR) > nNumberOfCharsToRead * CharSize)
+        return STATUS_INVALID_PARAMETER;
+
     Status = ConioLockConsole(ProcessData, Request->Data.ReadConsoleRequest.ConsoleHandle,
                               &Console, GENERIC_READ);
     if (! NT_SUCCESS(Status))
@@ -58,7 +63,7 @@
         if (Console->LineBuffer == NULL)
         {
             /* Starting a new line */
-            Console->LineMaxSize = max(256, Request->Data.ReadConsoleRequest.FullReadSize);
+            Console->LineMaxSize = max(256, nNumberOfCharsToRead);
             Console->LineBuffer = HeapAlloc(Win32CsrApiHeap, 0, Console->LineMaxSize * sizeof(WCHAR));
             if (Console->LineBuffer == NULL)
             {
@@ -66,9 +71,19 @@
                 goto done;
             }
             Console->LineComplete = FALSE;
-            Console->LineSize = 0;
-            Console->LinePos = 0;
             Console->LineUpPressed = FALSE;
+            Console->LineInsertToggle = 0;
+            Console->LineWakeupMask = Request->Data.ReadConsoleRequest.CtrlWakeupMask;
+            Console->LineSize = Request->Data.ReadConsoleRequest.NrCharactersRead;
+            Console->LinePos = Console->LineSize;
+            /* pre-filling the buffer is only allowed in the Unicode API,
+             * so we don't need to worry about conversion */
+            memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
+            if (Console->LineSize == Console->LineMaxSize)
+            {
+                Console->LineComplete = TRUE;
+                Console->LinePos = 0;
+            }
         }
 
         /* If we don't have a complete line yet, process the pending input */
@@ -87,6 +102,7 @@
                     && Input->InputEvent.Event.KeyEvent.bKeyDown)
             {
                 LineInputKeyDown(Console, &Input->InputEvent.Event.KeyEvent);
+                Request->Data.ReadConsoleRequest.ControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
             }
             HeapFree(Win32CsrApiHeap, 0, Input);
         }
@@ -104,10 +120,11 @@
             }
             if (Console->LinePos == Console->LineSize)
             {
+                /* Entire line has been read */
                 HeapFree(Win32CsrApiHeap, 0, Console->LineBuffer);
                 Console->LineBuffer = NULL;
-                Status = STATUS_SUCCESS; /* Entire line has been read */
-            }
+            }
+            Status = STATUS_SUCCESS;
         }
     }
     else
@@ -141,12 +158,6 @@
 done:
     Request->Data.ReadConsoleRequest.NrCharactersRead = i;
     ConioUnlockConsole(Console);
-
-    if (CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE) + i * CharSize > sizeof(CSR_API_MESSAGE))
-    {
-        Request->Header.u1.s1.TotalLength = CSR_API_MESSAGE_HEADER_SIZE(CSRSS_READ_CONSOLE) + i * CharSize;
-        Request->Header.u1.s1.DataLength = Request->Header.u1.s1.TotalLength - sizeof(PORT_MESSAGE);
-    }
 
     return Status;
 }
@@ -355,6 +366,12 @@
             current_entry = current_entry->Flink;
             ConioConsoleCtrlEvent((DWORD)CTRL_C_EVENT, current);
         }
+        if (Console->LineBuffer && !Console->LineComplete)
+        {
+            /* Line input is in progress; end it */
+            Console->LinePos = Console->LineSize = 0;
+            Console->LineComplete = TRUE;
+        }
         return;
     }
 

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/conio.h
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/conio.h?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/conio.h [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/conio.h [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -81,6 +81,8 @@
   WORD LinePos;                         /* current position within line */
   BOOLEAN LineComplete;                 /* user pressed enter, ready to send back to client */
   BOOLEAN LineUpPressed;
+  BOOLEAN LineInsertToggle;             /* replace character over cursor instead of inserting */
+  ULONG LineWakeupMask;                 /* bitmap of which control characters will end line input */
   LIST_ENTRY HistoryBuffers;
   WORD HistoryBufferSize;               /* size for newly created history buffers */
   WORD NumberOfHistoryBuffers;          /* maximum number of history buffers allowed */
@@ -192,6 +194,7 @@
                                     CHAR *Buffer, DWORD Length, BOOL Attrib);
 NTSTATUS FASTCALL CsrInitConsoleScreenBuffer(PCSRSS_CONSOLE Console, PCSRSS_SCREEN_BUFFER Buffer);
 VOID WINAPI ConioDeleteScreenBuffer(PCSRSS_SCREEN_BUFFER Buffer);
+DWORD FASTCALL ConioEffectiveCursorSize(PCSRSS_CONSOLE Console, DWORD Scale);
 
 CSR_API(CsrWriteConsole);
 CSR_API(CsrGetScreenBufferInfo);

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/conoutput.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/conoutput.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/conoutput.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/conoutput.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -865,6 +865,16 @@
     return STATUS_SUCCESS;
 }
 
+DWORD FASTCALL
+ConioEffectiveCursorSize(PCSRSS_CONSOLE Console, DWORD Scale)
+{
+    DWORD Size = (Console->ActiveBuffer->CursorInfo.dwSize * Scale + 99) / 100;
+    /* If line input in progress, perhaps adjust for insert toggle */
+    if (Console->LineBuffer && !Console->LineComplete && Console->LineInsertToggle)
+        return (Size * 2 <= Scale) ? (Size * 2) : (Size / 2);
+    return Size;
+}
+
 CSR_API(CsrGetCursorInfo)
 {
     PCSRSS_SCREEN_BUFFER Buff;

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/guiconsole.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/guiconsole.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/guiconsole.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/guiconsole.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -946,11 +946,7 @@
         if (LeftChar <= CursorX && CursorX <= RightChar &&
                 TopLine <= CursorY && CursorY <= BottomLine)
         {
-            CursorHeight = (GuiData->CharHeight * Buff->CursorInfo.dwSize) / 100;
-            if (CursorHeight < 1)
-            {
-                CursorHeight = 1;
-            }
+            CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
             From = ConioCoordToPointer(Buff, Buff->CurrentX, Buff->CurrentY) + 1;
 
             if (*From != DEFAULT_ATTRIB)

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/lineinput.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/lineinput.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/lineinput.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/lineinput.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -459,6 +459,11 @@
                 LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]);
         }
         return;
+    case VK_INSERT:
+        /* Toggle between insert and overstrike */
+        Console->LineInsertToggle = !Console->LineInsertToggle;
+        ConioSetCursorInfo(Console, Console->ActiveBuffer);
+        return;
     case VK_DELETE:
         /* Remove character to right of cursor */
         if (Pos != Console->LineSize)
@@ -555,7 +560,12 @@
         Console->LineBuffer[Console->LineSize++] = L'\r';
         if (Console->Mode & ENABLE_ECHO_INPUT)
             ConioWriteConsole(Console, Console->ActiveBuffer, "\r", 1, TRUE);
-        if (Console->Mode & ENABLE_PROCESSED_INPUT)
+
+        /* Add \n if processed input. There should usually be room for it,
+         * but an exception to the rule exists: the buffer could have been 
+         * pre-filled with LineMaxSize - 1 characters. */
+        if (Console->Mode & ENABLE_PROCESSED_INPUT &&
+            Console->LineSize < Console->LineMaxSize)
         {
             Console->LineBuffer[Console->LineSize++] = L'\n';
             if (Console->Mode & ENABLE_ECHO_INPUT)
@@ -566,8 +576,21 @@
     }
     else if (KeyEvent->uChar.UnicodeChar != L'\0')
     {
-        /* Normal character */
-        LineInputEdit(Console, 0, 1, &KeyEvent->uChar.UnicodeChar);
+        if (KeyEvent->uChar.UnicodeChar < 0x20 &&
+            Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar))
+        {
+            /* Control key client wants to handle itself (e.g. for tab completion) */
+            Console->LineBuffer[Console->LineSize++] = L' ';
+            Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar;
+            Console->LineComplete = TRUE;
+            Console->LinePos = 0;
+        }
+        else
+        {
+            /* Normal character */
+            BOOL Overstrike = Console->LineInsertToggle && Console->LinePos != Console->LineSize;
+            LineInputEdit(Console, Overstrike, 1, &KeyEvent->uChar.UnicodeChar);
+        }
     }
 }
 

Modified: trunk/reactos/subsystems/win32/csrss/win32csr/tuiconsole.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/subsystems/win32/csrss/win32csr/tuiconsole.c?rev=47693&r1=47692&r2=47693&view=diff
==============================================================================
--- trunk/reactos/subsystems/win32/csrss/win32csr/tuiconsole.c [iso-8859-1] (original)
+++ trunk/reactos/subsystems/win32/csrss/win32csr/tuiconsole.c [iso-8859-1] Tue Jun  8 08:38:14 2010
@@ -208,6 +208,7 @@
 static BOOL WINAPI
 TuiSetCursorInfo(PCSRSS_CONSOLE Console, PCSRSS_SCREEN_BUFFER Buff)
 {
+    CONSOLE_CURSOR_INFO Info;
     DWORD BytesReturned;
 
     if (ActiveConsole->ActiveBuffer != Buff)
@@ -215,9 +216,11 @@
         return TRUE;
     }
 
+    Info.dwSize = ConioEffectiveCursorSize(Console, 100);
+    Info.bVisible = Buff->CursorInfo.bVisible;
+
     if (! DeviceIoControl(ConsoleDeviceHandle, IOCTL_CONSOLE_SET_CURSOR_INFO,
-                          &Buff->CursorInfo, sizeof(Buff->CursorInfo), NULL, 0,
-                          &BytesReturned, NULL))
+                          &Info, sizeof(Info), NULL, 0, &BytesReturned, NULL))
     {
         DPRINT1( "Failed to set cursor info\n" );
         return FALSE;




More information about the Ros-diffs mailing list