mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
update: support stream(file stdin) input mode for server CLI
Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux.
This commit is contained in:
parent
26dc63af6b
commit
465b2b8f14
|
|
@ -6,6 +6,61 @@
|
||||||
#include "..\ServerLogger.h"
|
#include "..\ServerLogger.h"
|
||||||
#include "..\vendor\linenoise\linenoise.h"
|
#include "..\vendor\linenoise\linenoise.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool UseStreamInputMode()
|
||||||
|
{
|
||||||
|
const char *mode = getenv("SERVER_CLI_INPUT_MODE");
|
||||||
|
if (mode != NULL)
|
||||||
|
{
|
||||||
|
return _stricmp(mode, "stream") == 0
|
||||||
|
|| _stricmp(mode, "stdin") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WaitForStdinReadable(HANDLE stdinHandle, DWORD waitMs)
|
||||||
|
{
|
||||||
|
if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD fileType = GetFileType(stdinHandle);
|
||||||
|
if (fileType == FILE_TYPE_PIPE)
|
||||||
|
{
|
||||||
|
DWORD available = 0;
|
||||||
|
if (!PeekNamedPipe(stdinHandle, NULL, 0, NULL, &available, NULL))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return available > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType == FILE_TYPE_CHAR)
|
||||||
|
{
|
||||||
|
// console/pty char handles are often not waitable across Wine+Docker.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD waitResult = WaitForSingleObject(stdinHandle, waitMs);
|
||||||
|
if (waitResult == WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (waitResult == WAIT_TIMEOUT)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace ServerRuntime
|
namespace ServerRuntime
|
||||||
{
|
{
|
||||||
// C-style completion callback bridge requires a static instance pointer.
|
// C-style completion callback bridge requires a static instance pointer.
|
||||||
|
|
@ -49,6 +104,7 @@ namespace ServerRuntime
|
||||||
linenoiseRequestStop();
|
linenoiseRequestStop();
|
||||||
if (m_inputThread.joinable())
|
if (m_inputThread.joinable())
|
||||||
{
|
{
|
||||||
|
CancelSynchronousIo((HANDLE)m_inputThread.native_handle());
|
||||||
m_inputThread.join();
|
m_inputThread.join();
|
||||||
}
|
}
|
||||||
linenoiseSetCompletionCallback(NULL);
|
linenoiseSetCompletionCallback(NULL);
|
||||||
|
|
@ -68,6 +124,21 @@ namespace ServerRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerCliInput::RunInputLoop()
|
void ServerCliInput::RunInputLoop()
|
||||||
|
{
|
||||||
|
if (UseStreamInputMode())
|
||||||
|
{
|
||||||
|
LogInfo("console", "CLI input mode: stream(file stdin)");
|
||||||
|
RunStreamInputLoop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunLinenoiseLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use linenoise for interactive console input, with line editing and history support
|
||||||
|
*/
|
||||||
|
void ServerCliInput::RunLinenoiseLoop()
|
||||||
{
|
{
|
||||||
while (m_running)
|
while (m_running)
|
||||||
{
|
{
|
||||||
|
|
@ -83,17 +154,111 @@ namespace ServerRuntime
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line[0] != 0 && m_engine != NULL)
|
EnqueueLine(line);
|
||||||
{
|
|
||||||
// Keep local history and forward command for main-thread execution.
|
|
||||||
linenoiseHistoryAdd(line);
|
|
||||||
m_engine->EnqueueCommandLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
linenoiseFree(line);
|
linenoiseFree(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use file-based stdin reading instead of linenoise when requested or when stdin is not a console/pty (e.g. piped input or non-interactive docker)
|
||||||
|
*/
|
||||||
|
void ServerCliInput::RunStreamInputLoop()
|
||||||
|
{
|
||||||
|
HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
LogWarn("console", "stream input mode requested but STDIN handle is unavailable; falling back to linenoise.");
|
||||||
|
RunLinenoiseLoop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
bool skipNextLf = false;
|
||||||
|
|
||||||
|
printf("server> ");
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
while (m_running)
|
||||||
|
{
|
||||||
|
int readable = WaitForStdinReadable(stdinHandle, 50);
|
||||||
|
if (readable <= 0)
|
||||||
|
{
|
||||||
|
Sleep(10);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ch = 0;
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
if (!ReadFile(stdinHandle, &ch, 1, &bytesRead, NULL) || bytesRead == 0)
|
||||||
|
{
|
||||||
|
Sleep(10);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipNextLf && ch == '\n')
|
||||||
|
{
|
||||||
|
skipNextLf = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '\r' || ch == '\n')
|
||||||
|
{
|
||||||
|
if (ch == '\r')
|
||||||
|
{
|
||||||
|
skipNextLf = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skipNextLf = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.empty())
|
||||||
|
{
|
||||||
|
EnqueueLine(line.c_str());
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("server> ");
|
||||||
|
fflush(stdout);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipNextLf = false;
|
||||||
|
|
||||||
|
if ((unsigned char)ch == 3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((unsigned char)ch == 8 || (unsigned char)ch == 127)
|
||||||
|
{
|
||||||
|
if (!line.empty())
|
||||||
|
{
|
||||||
|
line.resize(line.size() - 1);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isprint((unsigned char)ch) && line.size() < 4096)
|
||||||
|
{
|
||||||
|
line.push_back(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCliInput::EnqueueLine(const char *line)
|
||||||
|
{
|
||||||
|
if (line == NULL || line[0] == 0 || m_engine == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep local history and forward command for main-thread execution.
|
||||||
|
linenoiseHistoryAdd(line);
|
||||||
|
m_engine->EnqueueCommandLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
void ServerCliInput::CompletionThunk(const char *line, linenoiseCompletions *completions)
|
void ServerCliInput::CompletionThunk(const char *line, linenoiseCompletions *completions)
|
||||||
{
|
{
|
||||||
// Static thunk forwards callback into instance state.
|
// Static thunk forwards callback into instance state.
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ namespace ServerRuntime
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RunInputLoop();
|
void RunInputLoop();
|
||||||
|
void RunLinenoiseLoop();
|
||||||
|
void RunStreamInputLoop();
|
||||||
|
void EnqueueLine(const char *line);
|
||||||
static void CompletionThunk(const char *line, linenoiseCompletions *completions);
|
static void CompletionThunk(const char *line, linenoiseCompletions *completions);
|
||||||
void BuildCompletions(const char *line, linenoiseCompletions *completions);
|
void BuildCompletions(const char *line, linenoiseCompletions *completions);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ services:
|
||||||
WINEARCH: win64
|
WINEARCH: win64
|
||||||
WINEPREFIX: /var/opt/wineprefix64
|
WINEPREFIX: /var/opt/wineprefix64
|
||||||
WINEDEBUG: -all
|
WINEDEBUG: -all
|
||||||
|
# linux require use file stdin
|
||||||
|
SERVER_CLI_INPUT_MODE: ${SERVER_CLI_INPUT_MODE:-stream}
|
||||||
# minimum required virtual screen
|
# minimum required virtual screen
|
||||||
XVFB_DISPLAY: ${XVFB_DISPLAY:-:99}
|
XVFB_DISPLAY: ${XVFB_DISPLAY:-:99}
|
||||||
XVFB_SCREEN: ${XVFB_SCREEN:-720x1280x16}
|
XVFB_SCREEN: ${XVFB_SCREEN:-720x1280x16}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ services:
|
||||||
WINEARCH: win64
|
WINEARCH: win64
|
||||||
WINEPREFIX: /var/opt/wineprefix64
|
WINEPREFIX: /var/opt/wineprefix64
|
||||||
WINEDEBUG: -all
|
WINEDEBUG: -all
|
||||||
|
# linux require use file stdin
|
||||||
|
SERVER_CLI_INPUT_MODE: ${SERVER_CLI_INPUT_MODE:-stream}
|
||||||
# minimum required virtual screen
|
# minimum required virtual screen
|
||||||
XVFB_DISPLAY: ${XVFB_DISPLAY:-:99}
|
XVFB_DISPLAY: ${XVFB_DISPLAY:-:99}
|
||||||
XVFB_SCREEN: ${XVFB_SCREEN:-64x64x16}
|
XVFB_SCREEN: ${XVFB_SCREEN:-64x64x16}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue