mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Command: extends useStdErr to useOutput
This commit is contained in:
@@ -4,6 +4,9 @@ Changes:
|
||||
* LocalIP flags on Windows now describe the interface itself — its operational status, its media connection state and its type, for example `ETHERNET_CSMACD`, `IEEE80211` or `SOFTWARE_LOOPBACK` — instead of the `IP_ADAPTER_*` bits.
|
||||
* libpulse and libddcutil are no longer used on Linux. Package managers should remove them from their fastfetch package.
|
||||
* QuickJS format scripting has been removed (deprecated in v2.69.0). Migrate scripts to Lua if necessary. (General)
|
||||
* The Command module option `useStdErr` has been replaced with `useOutput`, which selects which of the command's output streams to capture: `"stdout"`, `"stderr"` or `"both"`.
|
||||
* It defaults to `"both"`, so a command that logs to stderr now has that output captured instead of discarded.
|
||||
* Set it to `"stdout"` or `"stderr"` to restore the old single-stream behavior. (Command)
|
||||
|
||||
Features:
|
||||
* Added Sound detection on Android. (Sound, Android)
|
||||
|
||||
+18
-4
@@ -2381,10 +2381,24 @@
|
||||
"description": "Command text to execute",
|
||||
"type": "string"
|
||||
},
|
||||
"useStdErr": {
|
||||
"description": "Whether to use stderr instead of stdout for command output",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"useOutput": {
|
||||
"description": "Which of the command's output streams to capture\nThe stream that is not captured is discarded",
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"const": "stdout",
|
||||
"description": "Capture stdout only and discard stderr"
|
||||
},
|
||||
{
|
||||
"const": "stderr",
|
||||
"description": "Capture stderr only and discard stdout"
|
||||
},
|
||||
{
|
||||
"const": "both",
|
||||
"description": "Capture both stdout and stderr"
|
||||
}
|
||||
],
|
||||
"default": "both"
|
||||
},
|
||||
"parallel": {
|
||||
"description": "Whether to execute the command in parallel with other commands\nThis can improve performance when using multiple commands, but may cause issues with some commands",
|
||||
|
||||
@@ -62,7 +62,7 @@ static inline int ffPipe2(int* readEnd, int* writeEnd, int flags) {
|
||||
}
|
||||
|
||||
// Not thread-safe
|
||||
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
|
||||
const char* ffProcessSpawn(char* const argv[], FFProcessOutputType useOutput, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
|
||||
FF_AUTO_CLOSE_FD int pipeRead = -1;
|
||||
FF_AUTO_CLOSE_FD int pipeWrite = -1;
|
||||
if (ffPipe2(&pipeRead, &pipeWrite, O_CLOEXEC) == -1) {
|
||||
@@ -72,6 +72,10 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
|
||||
|
||||
pid_t childPid = -1;
|
||||
int nullFile = ffGetNullFD();
|
||||
// Each stream is wired to the pipe when its bit is set and to the null device otherwise, so an
|
||||
// unselected stream can neither mix into the captured text nor fill the pipe and block the child.
|
||||
int stdoutFd = (useOutput & FF_PROCESS_OUTPUT_STDOUT_BIT) ? pipeWrite : nullFile;
|
||||
int stderrFd = (useOutput & FF_PROCESS_OUTPUT_STDERR_BIT) ? pipeWrite : nullFile;
|
||||
|
||||
#if !(__ANDROID__ || __OpenBSD__)
|
||||
|
||||
@@ -83,8 +87,8 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
|
||||
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
posix_spawn_file_actions_init(&file_actions);
|
||||
posix_spawn_file_actions_adddup2(&file_actions, pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&file_actions, nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&file_actions, stdoutFd, STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&file_actions, stderrFd, STDERR_FILENO);
|
||||
if (ffIsValidNativeFD(stdinFd)) {
|
||||
posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO);
|
||||
}
|
||||
@@ -149,8 +153,8 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
|
||||
|
||||
if (childPid == 0) {
|
||||
// Child process
|
||||
dup2(pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
|
||||
dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
|
||||
dup2(stdoutFd, STDOUT_FILENO);
|
||||
dup2(stderrFd, STDERR_FILENO);
|
||||
if (ffIsValidNativeFD(stdinFd)) {
|
||||
dup2(stdinFd, STDIN_FILENO);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ static wchar_t* createChildEnvironment(void) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
|
||||
const char* ffProcessSpawn(char* const argv[], FFProcessOutputType useOutput, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
|
||||
const int32_t timeout = instance.config.general.processingTimeout;
|
||||
|
||||
wchar_t pipeName[32];
|
||||
@@ -127,13 +127,12 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
|
||||
.cb = sizeof(siStartInfo),
|
||||
.dwFlags = STARTF_USESTDHANDLES,
|
||||
};
|
||||
if (useStdErr) {
|
||||
siStartInfo.hStdOutput = ffGetNullFD();
|
||||
siStartInfo.hStdError = hChildPipeWrite;
|
||||
} else {
|
||||
siStartInfo.hStdOutput = hChildPipeWrite;
|
||||
siStartInfo.hStdError = ffGetNullFD();
|
||||
}
|
||||
|
||||
HANDLE nullFile = ffGetNullFD();
|
||||
// A stream wired to the pipe is captured; the other goes to the null device so it cannot mix
|
||||
// into the captured text or fill the pipe and block the child. Both may share the pipe handle.
|
||||
siStartInfo.hStdOutput = (useOutput & FF_PROCESS_OUTPUT_STDOUT_BIT) ? hChildPipeWrite : nullFile;
|
||||
siStartInfo.hStdError = (useOutput & FF_PROCESS_OUTPUT_STDERR_BIT) ? hChildPipeWrite : nullFile;
|
||||
if (ffIsValidNativeFD(stdinFd)) {
|
||||
siStartInfo.hStdInput = stdinFd;
|
||||
}
|
||||
|
||||
+14
-3
@@ -17,18 +17,29 @@ typedef struct FFProcessHandle {
|
||||
#endif
|
||||
} FFProcessHandle;
|
||||
|
||||
// Which of the child's two output streams are wired to the pipe that `ffProcessReadOutput()` drains.
|
||||
// The stream that is not selected is pointed at the null device, so it can neither interleave with
|
||||
// the captured text nor fill the pipe and block the child. This is a bitfield rather than a value
|
||||
// because `BOTH` is a combination: each backend sets up the child's two stdio slots independently,
|
||||
// testing one bit each.
|
||||
typedef enum FFProcessOutputType: uint8_t {
|
||||
FF_PROCESS_OUTPUT_STDOUT_BIT = 1 << 0,
|
||||
FF_PROCESS_OUTPUT_STDERR_BIT = 1 << 1,
|
||||
FF_PROCESS_OUTPUT_BOTH = FF_PROCESS_OUTPUT_STDOUT_BIT | FF_PROCESS_OUTPUT_STDERR_BIT,
|
||||
} FFProcessOutputType;
|
||||
|
||||
// `stdinFd` is the fd the child process gets as its stdin. Pass `ffGetNullFD()` to detach it from
|
||||
// our stdin, or `FF_PROCESS_INHERIT_STDIN` to inherit ours, which is what the helpers below do.
|
||||
// Detaching matters on Android, where `/system/bin/cmd` forwards its stdin over binder to the
|
||||
// service: the kernel rejects the whole transaction when that fd is a terminal, and the tool then
|
||||
// only reports `Failure calling service <name>: Failed transaction`.
|
||||
#define FF_PROCESS_INHERIT_STDIN ((FFNativeFD) -1)
|
||||
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinFd, FFProcessHandle* outHandle);
|
||||
const char* ffProcessSpawn(char* const argv[], FFProcessOutputType useOutput, FFNativeFD stdinFd, FFProcessHandle* outHandle);
|
||||
const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer); // Destroys handle internally
|
||||
|
||||
static inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const argv[]) {
|
||||
FFProcessHandle handle;
|
||||
const char* error = ffProcessSpawn(argv, false, FF_PROCESS_INHERIT_STDIN, &handle);
|
||||
const char* error = ffProcessSpawn(argv, FF_PROCESS_OUTPUT_STDOUT_BIT, FF_PROCESS_INHERIT_STDIN, &handle);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
@@ -42,7 +53,7 @@ static inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const ar
|
||||
|
||||
static inline const char* ffProcessAppendStdErr(FFstrbuf* buffer, char* const argv[]) {
|
||||
FFProcessHandle handle;
|
||||
const char* error = ffProcessSpawn(argv, true, FF_PROCESS_INHERIT_STDIN, &handle);
|
||||
const char* error = ffProcessSpawn(argv, FF_PROCESS_OUTPUT_STDERR_BIT, FF_PROCESS_INHERIT_STDIN, &handle);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ static const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* hand
|
||||
options->text.chars,
|
||||
nullptr }
|
||||
: (char* const[]) { options->shell.chars, options->text.chars, nullptr },
|
||||
options->useStdErr,
|
||||
options->useOutput,
|
||||
FF_PROCESS_INHERIT_STDIN,
|
||||
handle);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ static bool detectWithCommand(FFDisplayServerResult* ds, char* const argv[], con
|
||||
// transaction when that fd is a terminal, which it is whenever fastfetch runs in a terminal.
|
||||
// Detaching the child from our stdin is only needed here, so the low level API is called instead
|
||||
// of `ffProcessAppendStdOut`.
|
||||
if (ffProcessSpawn(argv, false, ffGetNullFD(), &handle) != nullptr) {
|
||||
if (ffProcessSpawn(argv, FF_PROCESS_OUTPUT_STDOUT_BIT, ffGetNullFD(), &handle) != nullptr) {
|
||||
return false; // Neither command is available on every Android version
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,24 @@ void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "useStdErr")) {
|
||||
options->useStdErr = yyjson_get_bool(val);
|
||||
options->useOutput = yyjson_get_bool(val) ? FF_PROCESS_OUTPUT_STDERR_BIT : FF_PROCESS_OUTPUT_STDOUT_BIT;
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "useStdErr is deprecated, use useOutput instead");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "useOutput")) {
|
||||
int value;
|
||||
const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
|
||||
{ "stdout", FF_PROCESS_OUTPUT_STDOUT_BIT },
|
||||
{ "stderr", FF_PROCESS_OUTPUT_STDERR_BIT },
|
||||
{ "both", FF_PROCESS_OUTPUT_BOTH },
|
||||
{},
|
||||
});
|
||||
if (error) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
|
||||
} else {
|
||||
options->useOutput = (FFProcessOutputType) value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -110,7 +127,19 @@ void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc,
|
||||
yyjson_mut_obj_add_strbuf(doc, module, "shell", &options->shell);
|
||||
yyjson_mut_obj_add_strbuf(doc, module, "param", &options->param);
|
||||
yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text);
|
||||
yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr);
|
||||
switch (options->useOutput) {
|
||||
case FF_PROCESS_OUTPUT_STDOUT_BIT:
|
||||
yyjson_mut_obj_add_str(doc, module, "useOutput", "stdout");
|
||||
break;
|
||||
case FF_PROCESS_OUTPUT_STDERR_BIT:
|
||||
yyjson_mut_obj_add_str(doc, module, "useOutput", "stderr");
|
||||
break;
|
||||
case FF_PROCESS_OUTPUT_BOTH:
|
||||
yyjson_mut_obj_add_str(doc, module, "useOutput", "both");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel);
|
||||
yyjson_mut_obj_add_bool(doc, module, "splitLines", options->splitLines);
|
||||
}
|
||||
@@ -168,7 +197,9 @@ void ffInitCommandOptions(FFCommandOptions* options) {
|
||||
#endif
|
||||
);
|
||||
ffStrbufInit(&options->text);
|
||||
options->useStdErr = false;
|
||||
// Capture both streams by default: most tools log to stdout or stderr without saying which, and
|
||||
// the previous single-stream default silently dropped one of them.
|
||||
options->useOutput = FF_PROCESS_OUTPUT_BOTH;
|
||||
options->parallel = true;
|
||||
options->splitLines = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/option.h"
|
||||
#include "common/processing.h" // FFProcessOutputType
|
||||
|
||||
typedef struct FFCommandOptions {
|
||||
FFModuleArgs moduleArgs;
|
||||
@@ -8,7 +9,7 @@ typedef struct FFCommandOptions {
|
||||
FFstrbuf shell;
|
||||
FFstrbuf param;
|
||||
FFstrbuf text;
|
||||
bool useStdErr;
|
||||
FFProcessOutputType useOutput;
|
||||
bool parallel;
|
||||
bool splitLines;
|
||||
} FFCommandOptions;
|
||||
|
||||
Reference in New Issue
Block a user