[PATCH 2/2] programs/wineconsole: add several options to fine tune behavior

Eric Pouech eric.pouech at gmail.com
Wed Apr 27 07:52:33 CDT 2022


with modifications on CreateProcess and management of CUI subsystem
processes, it's handy to provide fine control to users when
launching their CUI applications:
- kind of windows (console)
- which std handles
- wait for child termination or not...

Signed-off-by: Eric Pouech <eric.pouech at gmail.com>

---
 programs/wineconsole/wineconsole.c      |  118 ++++++++++++++++++++++---------
 programs/wineconsole/wineconsole.man.in |   65 ++++++++++++++++-
 programs/wineconsole/wineconsole.rc     |   20 +++++
 programs/wineconsole/wineconsole_res.h  |    3 +
 4 files changed, 167 insertions(+), 39 deletions(-)

diff --git a/programs/wineconsole/wineconsole.c b/programs/wineconsole/wineconsole.c
index c703127c238..71fcb1a50d8 100644
--- a/programs/wineconsole/wineconsole.c
+++ b/programs/wineconsole/wineconsole.c
@@ -33,6 +33,28 @@
 WINE_DEFAULT_DEBUG_CHANNEL(console);
 
 static const unsigned int EC_INTERNAL = 255; /* value of exit_code for internal errors */
+static FILE* freport;
+
+static void message( unsigned id, const WCHAR* pmt )
+{
+    WCHAR format[1024], msg[1024];
+    DWORD_PTR args[1];
+    args[0] = (DWORD_PTR)pmt;
+
+    if (LoadStringW( GetModuleHandleW( NULL ), id, format, ARRAY_SIZE(format) ) &&
+        FormatMessageW( FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_STRING,
+                        format, 0, 0, msg, ARRAY_SIZE(msg), (va_list*)args ))
+        fwprintf( freport, L"%s\n", msg );
+    else
+        ERR("Failed to format\n");
+}
+
+static void usage( LPCWSTR option )
+{
+    if (option) message( IDS_CMD_UNKNOWN_OPTION, option );
+    message( IDS_CMD_USAGE, NULL );
+    exit( EC_INTERNAL );
+}
 
 /***********************************************************************
  *           build_command_line
@@ -123,44 +145,72 @@ int wmain( int argc, WCHAR *argv[] )
 {
     STARTUPINFOW startup = { sizeof(startup) };
     PROCESS_INFORMATION info;
-    WCHAR *cmd;
-    DWORD exit_code;
-
-    cmd = argc > 1 ? build_command_line( &argv[1] ) : wcsdup( L"cmd.exe" );
-
-    FreeConsole(); /* make sure we're not connected to inherited console */
-    if (!AllocConsole())
-    {
-        ERR( "failed to allocate console: %lu\n", GetLastError() );
-        return 1;
-    }
+    DWORD cpflags = CREATE_NEW_CONSOLE;
+    BOOL inherit = FALSE, dowait = TRUE;
+    WCHAR *cmdline;
+    int i;
 
-    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;
-
-    if (!CreateProcessW( NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ))
+    freport = stderr;
+    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] != '-')                          break;
+        if (argv[i][1] == '-' && !argv[i][2])           {i++; break;}
+        if (     !wcscmp(argv[i],  L"--detached"))      cpflags = DETACHED_PROCESS;
+        else if (!wcscmp(argv[i],  L"--console"))       cpflags = CREATE_NEW_CONSOLE;
+        else if (!wcscmp(argv[i],  L"--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 if (!wcscmp(argv[i],  L"--dontwait"))      dowait = FALSE;
+        else if (!wcscmp(argv[i],  L"--wait"))          dowait = TRUE;
+        else if (!wcsncmp(argv[i], L"--report=", 9) && argv[i][9])
         {
-            swprintf( buf, len, format, cmd );
-            WriteConsoleW( startup.hStdOutput, buf, wcslen(buf), &len, NULL);
-            while (ReadConsoleInputW( startup.hStdInput, &ir, 1, &len ) && ir.EventType == MOUSE_EVENT);
+            FILE* f = _wfopen(argv[i] + 9, L"w+");
+            if (!f)
+            {
+                message( IDS_CMD_REPORT_OPEN, argv[i] + 9 );
+                exit( EC_INTERNAL );
+            }
+            freport = f;
         }
-        return exit_code;
+        else if (!wcscmp(argv[i], L"--help") || !wcscmp(argv[i], L"-?"))
+            usage( NULL );
+        else usage( argv[i] );
+    }
+    cmdline = i < argc ? build_command_line( &argv[i] ) : wcsdup( L"cmd.exe" );
+    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, TRUE, cpflags, NULL, NULL, &startup, &info ))
+    {
+        WARN( "CreateProcess failed (%ls): %lu\n", cmdline, GetLastError() );
+        message( IDS_CMD_LAUNCH_FAILED, cmdline );
+        return EC_INTERNAL;
     }
-
     CloseHandle( info.hThread );
-    WaitForSingleObject( info.hProcess, INFINITE );
-    return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : GetLastError();
+    if (dowait)
+    {
+        DWORD exit_code;
+        HANDLE hJob;
+        JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo;
+
+        /* set parent and child into the same job: if parent gets killed (unix kill),
+         * child will be terminated as well.
+         */
+        SetConsoleCtrlHandler( NULL, TRUE );
+        hJob = CreateJobObjectW( NULL, NULL );
+        memset( &jobinfo, 0, sizeof(jobinfo) );
+        jobinfo.BasicLimitInformation.LimitFlags =
+            JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
+            JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
+        SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, &jobinfo, sizeof(jobinfo) );
+        AssignProcessToJobObject( hJob, info.hProcess );
+
+        WaitForSingleObject( info.hProcess, INFINITE );
+        return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : EC_INTERNAL;
+    }
+    return 0;
 }
diff --git a/programs/wineconsole/wineconsole.man.in b/programs/wineconsole/wineconsole.man.in
index dac4e62f321..8a71be60564 100644
--- a/programs/wineconsole/wineconsole.man.in
+++ b/programs/wineconsole/wineconsole.man.in
@@ -1,13 +1,70 @@
 .TH WINECONSOLE 1 "November 2010" "@PACKAGE_STRING@" "Wine Programs"
 .SH NAME
-wineconsole \- The Wine console
+wineconsole \- A Wine helper for managing the console used for running applications
 .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.
+
+allows to have fine grain management over console and standard I/O streams used when running an application under Wine.
+.SH OPTIONS
+
+.IP \fB--console\fR
+\fBwineconsole\fR will execute the command in a newly created window.
+
+This is the default when none of the \fB--detached\fR, \fB--headless\fR or \fB--console\fR options is provided.
+.IP \fB--detached\fR
+\fBwineconsole\fR will execute the \fIcommand\fR without being attached to any console.
+.IP \fB--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 ones.
+
+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 streams \fBwineconsole\fR is run with.
+
+.IP \fB--wait\fR
+Wait for \fIcommand\fR to terminate before \fBwineconsole\fR terminates itself.
+
+.IP \fB--dontwait\fR
+Terminate after starting \fIcommand\fR without waiting for it to terminate.
+
+.IP \fB--report=filename\fR
+Write detailed error information into filename. Can be useful either when \fBwineconsole\fR isn't run attached to a console, or when its output stream is already redirected.
+
+.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.
+
+When an error occurs inside \fBwineconsole\fR, 255 is returned as exit status.
+Upon success, in \fB--dontwait\fR mode the exit status is 0, while in \fB--wait\fR mode, the exit status of the \fBwineconsole\fR is the exit status for the \fIcommand\fR.
+
+.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.
+
+.SH EXAMPLES
+
+.IP ./wine
+wineconsole [ \fIcommand\fR ]
+
+Run \fIcommand\fR in a newly created standalone console.
+
+.IP ./wine
+wineconsole --headless --inherit-std [ \fIcommand\fR ] >& log
+
+Run \fIcommand\fR, in an invisible console, having its standard output and error streams redirected into file 'log'.
+
+.IP ./wine
+wineconsole --detached [ \fIcommand\fR ]
+
+Run \fIcommand\fR, without any console (meaning no possible input, and all \fIcommand\fR's information sent to standard output and error streams are lost).
+
 .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..8229cc1fdf8 100644
--- a/programs/wineconsole/wineconsole.rc
+++ b/programs/wineconsole/wineconsole.rc
@@ -25,6 +25,24 @@ LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
 STRINGTABLE
 BEGIN
 
-IDS_CMD_LAUNCH_FAILED      "wineconsole: Starting program %s failed.\nThe command is invalid.\n"
+IDS_CMD_UNKNOWN_OPTION  "Unknown option %1"
+IDS_CMD_USAGE           "Usage: wineconsole [options] [command]\n\
+\n\
+options:\n\
+  --detached            start [command] not being attached to any console\n\
+  --console             start [command] being attached to a newly created console (this is the default)\n\
+  --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\
+  --report=filename     write error reports into filename instead of stderr\n\
+\n\
+  --wait                wait for [command] to terminate before returning\n\
+  --dontwait            terminate as soon as [command] has been started\n\
+\n\
+  [command]:            executable (and optional arguments) to run"
+IDS_CMD_REPORT_OPEN     "Couldn't open file '%1' for reporting."
+IDS_CMD_LAUNCH_FAILED   "wineconsole: Starting program %1 failed.\nThe command is invalid."
 
 END
diff --git a/programs/wineconsole/wineconsole_res.h b/programs/wineconsole/wineconsole_res.h
index cca60ac5857..213b353cc5e 100644
--- a/programs/wineconsole/wineconsole_res.h
+++ b/programs/wineconsole/wineconsole_res.h
@@ -22,4 +22,7 @@
 #include <winuser.h>
 #include <commctrl.h>
 
+#define IDS_CMD_UNKNOWN_OPTION     0x302
+#define IDS_CMD_USAGE              0x303
 #define IDS_CMD_LAUNCH_FAILED      0x304
+#define IDS_CMD_REPORT_OPEN        0x305




More information about the wine-devel mailing list