[PATCH 1/4] programs/wineconsole: select console type and/or std handles for child process
Eric Pouech
eric.pouech at gmail.com
Fri Apr 15 07:48:24 CDT 2022
Signed-off-by: Eric Pouech <eric.pouech at gmail.com>
---
programs/wineconsole/wineconsole.c | 192 ++++++++++++++++++++++++++-----
programs/wineconsole/wineconsole.man.in | 40 ++++++
programs/wineconsole/wineconsole.rc | 12 ++
programs/wineconsole/wineconsole_res.h | 2
4 files changed, 212 insertions(+), 34 deletions(-)
diff --git a/programs/wineconsole/wineconsole.c b/programs/wineconsole/wineconsole.c
index 1924bf791e6..714332f6c24 100644
--- a/programs/wineconsole/wineconsole.c
+++ b/programs/wineconsole/wineconsole.c
@@ -32,50 +32,180 @@
WINE_DEFAULT_DEBUG_CHANNEL(console);
-int WINAPI wWinMain( HINSTANCE inst, HINSTANCE prev, WCHAR *cmdline, INT show )
+static const unsigned int EC_INTERNAL = 255; /* value of exit_code for internal errors */
+
+static void usage(LPCWSTR option)
{
- STARTUPINFOW startup = { sizeof(startup) };
- PROCESS_INFORMATION info;
- WCHAR *cmd = cmdline;
- DWORD exit_code;
+ WCHAR tmp[1024];
+
+ if (option)
+ {
+ LoadStringW( GetModuleHandleW( NULL ), IDS_CMD_UNKNOWN_OPTION, tmp, ARRAY_SIZE(tmp) );
+ fwprintf(stderr, tmp, option);
+ }
+ LoadStringW( GetModuleHandleW( NULL ), IDS_CMD_USAGE, tmp, ARRAY_SIZE(tmp) );
+ fprintf(stderr, "%ls\n", tmp);
+ exit( EC_INTERNAL );
+}
+
+/***********************************************************************
+ * build_command_line
+ *
+ * Build the command line of a process from the argv array.
+ * (copied from dlls/ntdll/unix/env.c)
+ *
+ * We must quote and escape characters so that the argv array can be rebuilt
+ * from the command line:
+ * - spaces and tabs must be quoted
+ * 'a b' -> '"a b"'
+ * - quotes must be escaped
+ * '"' -> '\"'
+ * - if '\'s are followed by a '"', they must be doubled and followed by '\"',
+ * resulting in an odd number of '\' followed by a '"'
+ * '\"' -> '\\\"'
+ * '\\"' -> '\\\\\"'
+ * - '\'s are followed by the closing '"' must be doubled,
+ * resulting in an even number of '\' followed by a '"'
+ * ' \' -> '" \\"'
+ * ' \\' -> '" \\\\"'
+ * - '\'s that are not followed by a '"' can be left as is
+ * 'a\b' == 'a\b'
+ * 'a\\b' == 'a\\b'
+ */
+static WCHAR *build_command_line( WCHAR **wargv )
+{
+ int len;
+ WCHAR **arg, *ret;
+ LPWSTR p;
+
+ len = 1;
+ for (arg = wargv; *arg; arg++) len += 3 + 2 * wcslen( *arg );
+ if (!(ret = malloc( len * sizeof(WCHAR) ))) return NULL;
+
+ p = ret;
+ for (arg = wargv; *arg; arg++)
+ {
+ BOOL has_space, has_quote;
+ int i, bcount;
+ WCHAR *a;
- static WCHAR default_cmd[] = L"cmd";
+ /* check for quotes and spaces in this argument (first arg is always quoted) */
+ has_space = (arg == wargv) || !**arg || wcschr( *arg, ' ' ) || wcschr( *arg, '\t' );
+ has_quote = wcschr( *arg, '"' ) != NULL;
- FreeConsole(); /* make sure we're not connected to inherited console */
- if (!AllocConsole())
+ /* now transfer it to the command line */
+ if (has_space) *p++ = '"';
+ if (has_quote || has_space)
+ {
+ bcount = 0;
+ for (a = *arg; *a; a++)
+ {
+ if (*a == '\\') bcount++;
+ else
+ {
+ if (*a == '"') /* double all the '\\' preceding this '"', plus one */
+ for (i = 0; i <= bcount; i++) *p++ = '\\';
+ bcount = 0;
+ }
+ *p++ = *a;
+ }
+ }
+ else
+ {
+ wcscpy( p, *arg );
+ p += wcslen( p );
+ }
+ if (has_space)
+ {
+ /* Double all the '\' preceding the closing quote */
+ for (i = 0; i < bcount; i++) *p++ = '\\';
+ *p++ = '"';
+ }
+ *p++ = ' ';
+ }
+ if (p > ret) p--; /* remove last space */
+ *p = 0;
+ if (p - ret >= 32767)
{
- ERR( "failed to allocate console: %lu\n", GetLastError() );
- return 1;
+ ERR( "command line too long (%Iu)\n", p - ret );
+ exit( EC_INTERNAL );
}
+ return ret;
+}
+
+static int report_failure( STARTUPINFOW *si, LPCWSTR cmd )
+{
+ WCHAR format[256], *buf;
+ DWORD len;
- if (!*cmd) cmd = default_cmd;
+ WARN( "CreateProcess failed: %lu\n", GetLastError() );
+ LoadStringW( GetModuleHandleW( NULL ), IDS_CMD_LAUNCH_FAILED, format, ARRAY_SIZE(format) );
+ len = wcslen( format ) + wcslen( cmd );
+ if ((buf = malloc( len * sizeof(WCHAR) )))
+ {
+ swprintf( buf, len, format, cmd );
+ if (si)
+ {
+ INPUT_RECORD ir;
+ WriteConsoleW( si->hStdOutput, buf, wcslen(buf), &len, NULL);
+ while (ReadConsoleInputW( si->hStdInput, &ir, 1, &len ) && ir.EventType == MOUSE_EVENT);
+ }
+ else fprintf(stderr, "%ls\n", buf);
+ }
+ return EC_INTERNAL;
+}
- startup.dwFlags = STARTF_USESTDHANDLES;
- startup.hStdInput = CreateFileW( L"CONIN$", GENERIC_READ | GENERIC_WRITE, 0, NULL,
- OPEN_EXISTING, 0, 0 );
- startup.hStdOutput = CreateFileW( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL,
- OPEN_EXISTING, 0, 0 );
- startup.hStdError = startup.hStdOutput;
+int wmain( int argc, WCHAR *argv[] )
+{
+ STARTUPINFOW startup = { sizeof(startup) };
+ PROCESS_INFORMATION info;
+ DWORD cpflags = 0;
+ BOOL inherit = FALSE;
+ DWORD exit_code;
+ WCHAR *cmdline;
+ int i;
- if (!CreateProcessW( NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ))
+ for (i = 1; i < argc; i++)
{
- WCHAR format[256], *buf;
- INPUT_RECORD ir;
- DWORD len;
- exit_code = GetLastError();
- WARN( "CreateProcess failed: %lu\n", exit_code );
- LoadStringW( GetModuleHandleW( NULL ), IDS_CMD_LAUNCH_FAILED, format, ARRAY_SIZE(format) );
- len = wcslen( format ) + wcslen( cmd );
- if ((buf = malloc( len * sizeof(WCHAR) )))
+ if (argv[i][0] != '-' || argv[i][1] != '-') break;
+ if (!argv[i][2]) {i++; break;}
+ if ( !wcscmp(argv[i], L"--mode=detached")) cpflags = DETACHED_PROCESS;
+ else if (!wcscmp(argv[i], L"--mode=console")) cpflags = CREATE_NEW_CONSOLE;
+ else if (!wcscmp(argv[i], L"--mode=headless")) cpflags = CREATE_NO_WINDOW;
+ else if (!wcscmp(argv[i], L"--console-std")) inherit = FALSE;
+ else if (!wcscmp(argv[i], L"--inherit-std")) inherit = TRUE;
+ else usage(argv[i]);
+ }
+ cmdline = i < argc ? build_command_line(&argv[i]) : wcsdup(L"cmd.exe");
+ /* if at least one option is passed, don't use old mode */
+ if (i > 1 && !cpflags) cpflags = CREATE_NEW_CONSOLE;
+ if (!cpflags) /* keep old behavior in place */
+ {
+ FreeConsole();
+ if (!AllocConsole())
{
- swprintf( buf, len, format, cmd );
- WriteConsoleW( startup.hStdOutput, buf, wcslen(buf), &len, NULL);
- while (ReadConsoleInputW( startup.hStdInput, &ir, 1, &len ) && ir.EventType == MOUSE_EVENT);
+ ERR( "failed to allocate console: %lu\n", GetLastError() );
+ return EC_INTERNAL;
}
- return exit_code;
+
+ startup.dwFlags |= STARTF_USESTDHANDLES;
+ startup.hStdInput = CreateFileW( L"CONIN$", GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, 0, 0 );
+ startup.hStdOutput = CreateFileW( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, 0, 0 );
+ startup.hStdError = startup.hStdOutput;
+ }
+ else if (inherit)
+ {
+ startup.dwFlags |= STARTF_USESTDHANDLES;
+ startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE );
+ startup.hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE );
+ startup.hStdError = GetStdHandle( STD_ERROR_HANDLE );
}
+ if (!CreateProcessW( NULL, cmdline, NULL, NULL, FALSE, cpflags, NULL, NULL, &startup, &info ))
+ return report_failure( cpflags ? NULL : &startup, cmdline );
CloseHandle( info.hThread );
WaitForSingleObject( info.hProcess, INFINITE );
- return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : GetLastError();
+ return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : EC_INTERNAL;
}
diff --git a/programs/wineconsole/wineconsole.man.in b/programs/wineconsole/wineconsole.man.in
index dac4e62f321..1eb60ec100f 100644
--- a/programs/wineconsole/wineconsole.man.in
+++ b/programs/wineconsole/wineconsole.man.in
@@ -3,11 +3,45 @@
wineconsole \- The Wine console
.SH SYNOPSIS
.B wineconsole
-.RI [ command "] "
+.RI "[ " options " ] [ " command " ] "
.SH DESCRIPTION
.B wineconsole
-is the Wine console manager, used to run console commands and applications. It allows running the
-console in a newly made window.
+is the Wine console manager, used to run console commands and applications.
+
+It allows to have fine grain management over console and standard I/O streams used when running an application.
+.SH OPTIONS
+
+.IP \fB--mode=console\fR
+\fBwineconsole\fR will execute the command in a newly created window.
+
+This is the default when none of the \fB--mode=\fR options is provided.
+.IP \fB--mode=detached\fR
+\fBwineconsole\fR will execute the \fIcommand\fR without being attached to any console.
+.IP \fB--mode=headless\fR
+\fBwineconsole\fR will execute the \fIcommand\fR attached to an invisible console.
+.IP \fB--console-std\fR
+The \fIcommand\fR's standard I/O streams will be mapped to the console designed by \fB--mode=\fR option.
+
+This is the default when neither \fB--console-std\fR nor \fB--inherit-std\fR is provided.
+.IP \fB--inherit-std\fR
+The \fIcommand\fR's standard I/O streams will be mapped to the standard Unix streams of \fBwineconsole\fR.
+
+.IP \fIcommand\fR
+The name of the executable to run, potentially followed by its arguments, with same meaning and syntax than using \fBwine\fR.
+
+If this part is omitted, than \fBcmd.exe\fR is run without arguments.
+
+\fBwineconsole\fR waits for the \fIcommand\fR to terminate before exiting.
+
+The exit status of the \fBwineconsole\fR is the exit status for the \fIcommand\fR, except when an error internal to \fBwineconsole\fR occurs, and 255 is returned.
+
+.SH NOTES
+Consoles are only of interest when the \fIcommand\fR executable belongs to the CUI subsystem.
+
+Using \fBwineconsole\fR overrides default Wine console creation when invoked from regular shell or script.
+
+This default console acts as a real console from the Windows environment, while inhering standard input streams.
+
.SH BUGS
Bugs can be reported on the
.UR https://bugs.winehq.org
diff --git a/programs/wineconsole/wineconsole.rc b/programs/wineconsole/wineconsole.rc
index 6a61dd31ede..bea205935e6 100644
--- a/programs/wineconsole/wineconsole.rc
+++ b/programs/wineconsole/wineconsole.rc
@@ -25,6 +25,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
+IDS_CMD_UNKNOWN_OPTION "Unknown option %s\n"
+IDS_CMD_USAGE "Usage: wineconsole [options] [command]\n\
+\n\
+options:\n\
+ --mode=detached start [command] not being attached to any console\n\
+ --mode=console start [command] being attached to a newly created console (this is the default)\n\
+ --mode=headless start [command] being attached to a newly created yet not visible console\n\
+\n\
+ --console-std ensures standard I/O streams of command are mapped to the console (this is the default)\n\
+ --inherit-std ensures standard I/O streams of command are mapped to the ones which wineconsole is run with\n\
+\n\
+ [command]: executable (and optional arguments) to run\n"
IDS_CMD_LAUNCH_FAILED "wineconsole: Starting program %s failed.\nThe command is invalid.\n"
END
diff --git a/programs/wineconsole/wineconsole_res.h b/programs/wineconsole/wineconsole_res.h
index cca60ac5857..e5a8cb39d86 100644
--- a/programs/wineconsole/wineconsole_res.h
+++ b/programs/wineconsole/wineconsole_res.h
@@ -22,4 +22,6 @@
#include <winuser.h>
#include <commctrl.h>
+#define IDS_CMD_UNKNOWN_OPTION 0x302
+#define IDS_CMD_USAGE 0x303
#define IDS_CMD_LAUNCH_FAILED 0x304
More information about the wine-devel
mailing list