From a4cf9e6959e6ae48e2700400ca415ba149af027e Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 14 Aug 2025 22:38:40 +0200 Subject: [PATCH 1/3] Make Process_stateChar available for use by other modules --- Process.c | 4 ++-- Process.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Process.c b/Process.c index 39cb92c89..cf48ad7b2 100644 --- a/Process.c +++ b/Process.c @@ -536,7 +536,7 @@ void Process_writeCommand(const Process* this, int attr, int baseAttr, RichStrin } } -static inline char processStateChar(ProcessState state) { +char Process_stateChar(ProcessState state) { switch (state) { case UNKNOWN: return '?'; case RUNNABLE: return 'U'; @@ -716,7 +716,7 @@ void Process_writeField(const Process* this, RichString* str, RowField field) { case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break; case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break; case STATE: - xSnprintf(buffer, n, "%c ", processStateChar(this->state)); + xSnprintf(buffer, n, "%c ", Process_stateChar(this->state)); switch (this->state) { case RUNNABLE: case RUNNING: diff --git a/Process.h b/Process.h index 38e2711f6..61f79cfa8 100644 --- a/Process.h +++ b/Process.h @@ -37,7 +37,7 @@ typedef enum Tristate_ { /* Core process states (shared by platforms) * NOTE: The enum has an ordering that is important! - * See processStateChar in process.c for ProcessSate -> letter mapping */ + * See Process_stateChar in process.c for ProcessSate -> letter mapping */ typedef enum ProcessState_ { UNKNOWN = 1, RUNNABLE, @@ -335,6 +335,8 @@ void Process_makeCommandStr(Process* this, const struct Settings_ *settings); void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str); +char Process_stateChar(ProcessState state); + void Process_updateCPUFieldWidths(float percentage); #endif From 36a33aa4a8822e9b4e3c0386a1ba20172a6c1ee6 Mon Sep 17 00:00:00 2001 From: Allison Date: Thu, 14 Aug 2025 10:51:29 -0300 Subject: [PATCH 2/3] Added support for the '-S/--state' command-line option to filter processes by state. This exposes the internal Process state character conversion function so it can be reused for the filter string processing. Multiple characters are allowed for the filter and a process is displayed if any of the provided state characters matches the state of the process currently processed. Co-authored-by: Allison Co-authored-by: BenBE --- CommandLine.c | 36 +++++++++++++++++++++++++++++++++++- Process.c | 8 ++++++++ Settings.c | 1 + Settings.h | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CommandLine.c b/CommandLine.c index 6e52f7a54..a605eacd2 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -54,6 +54,7 @@ static void printHelpFlag(const char* name) { "-C --no-color Use a monochrome color scheme\n" "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n" "-F --filter=FILTER Show only the commands matching the given filter\n" + "-S --state=STATESCHARS Show only the states matching the given states\n" "-h --help Print this help screen\n" "-H --highlight-changes[=DELAY] Highlight new and old processes\n", name); #ifdef HAVE_GETMOUSE @@ -78,6 +79,7 @@ static void printHelpFlag(const char* name) { typedef struct CommandLineSettings_ { Hashtable* pidMatchList; char* commFilter; + char* stateFilter; uid_t userId; int sortKey; int delay; @@ -98,6 +100,7 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin *flags = (CommandLineSettings) { .pidMatchList = NULL, .commFilter = NULL, + .stateFilter = NULL, .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2)) .sortKey = 0, .delay = -1, @@ -128,6 +131,7 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin {"tree", no_argument, 0, 't'}, {"pid", required_argument, 0, 'p'}, {"filter", required_argument, 0, 'F'}, + {"state", required_argument, 0, 'S'}, {"highlight-changes", optional_argument, 0, 'H'}, {"readonly", no_argument, 0, 128}, PLATFORM_LONG_OPTIONS @@ -136,7 +140,7 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin int opt, opti = 0; /* Parse arguments */ - while ((opt = getopt_long(argc, argv, "hVMCs:td:n:u::Up:F:H::", long_opts, &opti))) { + while ((opt = getopt_long(argc, argv, "hVMCs:td:n:u::Up:F:H::S:", long_opts, &opti))) { if (opt == EOF) break; @@ -255,6 +259,33 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin } free_and_xStrdup(&flags->commFilter, optarg); break; + + case 'S': + assert(optarg); + if (optarg[0] == '\0') { + fprintf(stderr, "Error: state filter cannot be empty.\n"); + return STATUS_ERROR_EXIT; + } + + for (char* c = optarg; *c != '\0'; c++) { + bool valid = false; + + for (ProcessState s = UNKNOWN; s <= SLEEPING; s++) { + if (*c == Process_stateChar(s)) { + valid = true; + break; + } + } + + if (!valid) { + fprintf(stderr, "Error: invalid state filter value \"%s\".\n", optarg); + return STATUS_ERROR_EXIT; + } + } + + free_and_xStrdup(&flags->stateFilter, optarg); + break; + case 'H': { const char* delay = optarg; if (!delay && optind < argc && argv[optind] != NULL && @@ -382,6 +413,9 @@ int CommandLine_run(int argc, char** argv) { .hideSelection = false, .hideMeters = false, }; + if (flags.stateFilter) { + free_and_xStrdup(&settings->stateFilter, flags.stateFilter); + } MainPanel_setState(panel, &state); if (flags.commFilter) diff --git a/Process.c b/Process.c index cf48ad7b2..f1db43e2f 100644 --- a/Process.c +++ b/Process.c @@ -860,6 +860,14 @@ static bool Process_matchesFilter(const Process* this, const Table* table) { if (pt->pidMatchList && !Hashtable_get(pt->pidMatchList, Process_getThreadGroup(this))) return true; + const char* stateFilter = host->settings->stateFilter; + if (stateFilter) { + char stateChar = Process_stateChar(this->state); + if (!strchr(stateFilter, stateChar)) { + return true; + } + } + return false; } diff --git a/Settings.c b/Settings.c index ae5402fba..7dea1afa4 100644 --- a/Settings.c +++ b/Settings.c @@ -49,6 +49,7 @@ static void Settings_deleteScreens(Settings* this) { } void Settings_delete(Settings* this) { + free(this->stateFilter); free(this->filename); free(this->initialFilename); Settings_deleteColumns(this); diff --git a/Settings.h b/Settings.h index 01e808e86..13aca4691 100644 --- a/Settings.h +++ b/Settings.h @@ -109,6 +109,7 @@ typedef struct Settings_ { #ifdef HAVE_LIBHWLOC bool topologyAffinity; #endif + char* stateFilter; bool changed; uint64_t lastUpdate; From f0002ffc5315b34f2323f97be0a30c3a879206f1 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 14 Aug 2025 22:50:24 +0200 Subject: [PATCH 3/3] Allow for spaces/comma in the argument This creates a small lookup table to speed up the check (O(n+m) instead of O(n*m)). --- CommandLine.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/CommandLine.c b/CommandLine.c index a605eacd2..59448b2b3 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -267,20 +267,23 @@ static CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettin return STATUS_ERROR_EXIT; } - for (char* c = optarg; *c != '\0'; c++) { - bool valid = false; + bool valid_states[256] = { false }; + for (ProcessState s = UNKNOWN; s <= SLEEPING; s++) { + char c = Process_stateChar(s); + valid_states[(int)c] = true; + } - for (ProcessState s = UNKNOWN; s <= SLEEPING; s++) { - if (*c == Process_stateChar(s)) { - valid = true; - break; - } - } + bool valid_arg = true; + for (char* c = optarg; *c != '\0' && valid_arg; c++) { + if (*c == ',' || isspace(*c)) + continue; - if (!valid) { - fprintf(stderr, "Error: invalid state filter value \"%s\".\n", optarg); - return STATUS_ERROR_EXIT; - } + valid_arg &= valid_states[(int)*c]; + } + + if (!valid_arg) { + fprintf(stderr, "Error: invalid state filter value \"%s\".\n", optarg); + return STATUS_ERROR_EXIT; } free_and_xStrdup(&flags->stateFilter, optarg);