From 2067264d1ba029eefa1dbc8f4d24db008a1a7c4f Mon Sep 17 00:00:00 2001 From: Erik Hahn Date: Wed, 20 Oct 2021 22:24:24 +0200 Subject: [PATCH 1/4] ENH: Add process_name key to support broken/missing _NET_WM_PID --- README.md | 3 +- data/xsuspender.conf | 5 ++ doc/xsuspender.1 | 6 +++ src/CMakeLists.txt | 5 +- src/config.c | 7 +++ src/config.h | 2 + src/entry.c | 5 +- src/events.c | 7 +-- src/proc.c | 113 +++++++++++++++++++++++++++++++++++++++++++ src/proc.h | 7 +++ src/rule.c | 5 ++ src/rule.h | 2 + src/xsuspender.c | 7 +-- 13 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 src/proc.c create mode 100644 src/proc.h diff --git a/README.md b/README.md index 6899773..6a4a33d 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,11 @@ Install binary package for your GNU/Linux distribution: #### From Source ```bash -# Install dependencies, namely GLib, Libwnck, procps +# Install dependencies, namely GLib, Libwnck, libprocps, procps # on Debian / Ubuntu / Mint: sudo apt install libglib2.0-dev \ libwnck-3-dev \ + libprocps-dev \ procps \ make cmake gcc pkg-config diff --git a/data/xsuspender.conf b/data/xsuspender.conf index c514998..92900d3 100644 --- a/data/xsuspender.conf +++ b/data/xsuspender.conf @@ -34,6 +34,11 @@ # # Also suspend descendant processes that match this regex. # suspend_subtree_pattern = . # +# # Assume that all windows matching this rule belong to the +# # process with this name. This is a workaround for +# # missing or incorrect _NET_WM_PID values. +# process_name = ... +# # # Whether to apply the rule only when on battery power. # only_on_battery = true # diff --git a/doc/xsuspender.1 b/doc/xsuspender.1 index a74f450..f086e28 100644 --- a/doc/xsuspender.1 +++ b/doc/xsuspender.1 @@ -202,6 +202,12 @@ This setting only applies when .B send_signals is also true. .TP +.BR process_name +If provided, XSuspender will ignore _NET_WM_PID and instead detect +the process a window matching this rule belongs to by looking for +a process with this name. There should be only one process with this +name, not including its child processes. +.TP .BR exec_suspend .TQ .BR exec_resume diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd29962..19f18b1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,12 +2,13 @@ find_package (PkgConfig REQUIRED) pkg_check_modules (GLIB REQUIRED glib-2.0) pkg_check_modules (WNCK REQUIRED libwnck-3.0) +pkg_check_modules (PROCPS REQUIRED libprocps) add_definitions (-DWNCK_I_KNOW_THIS_IS_UNSTABLE -D_POSIX_SOURCE -DG_LOG_DOMAIN="xsuspender") -add_compile_options (${GLIB_CFLAGS} ${WNCK_CFLAGS}) +add_compile_options (${GLIB_CFLAGS} ${WNCK_CFLAGS} ${PROCPS_CFLAGS}) file (GLOB SOURCES "*.[ch]") add_executable (${PROJECT_NAME} ${SOURCES}) @@ -15,6 +16,6 @@ add_executable (${PROJECT_NAME} ${SOURCES}) # pkg-config recipe for libwnck may be a bit overzealos; trim linked libs to essentials set (CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed -Wl,--no-undefined") -target_link_libraries (${PROJECT_NAME} PRIVATE ${GLIB_LDFLAGS} ${WNCK_LDFLAGS}) +target_link_libraries (${PROJECT_NAME} PRIVATE ${GLIB_LDFLAGS} ${WNCK_LDFLAGS} ${PROCPS_LDFLAGS}) install (TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/src/config.c b/src/config.c index 0540801..8da4eab 100644 --- a/src/config.c +++ b/src/config.c @@ -145,6 +145,9 @@ read_section (GKeyFile *file, str = g_key_file_get_value (file, section, CONFIG_KEY_SUBTREE_PATTERN, &err); if (! is_error (&err)) reassign_str (&rule->subtree_pattern, str); + str = g_key_file_get_value (file, section, CONFIG_KEY_PROCESS_NAME, &err); + if ( !is_error (&err)) reassign_str (&rule->process_name, str); + char **argv; argv = parse_command (file, section, CONFIG_KEY_EXEC_SUSPEND, &err); @@ -190,6 +193,7 @@ debug_print_rule (Rule *rule) "only_on_battery = %d\n" "send_signals = %d\n" "subtree_pattern = %s\n" + "process_name = %s\n" "downclock_on_battery = %d\n" "exec_suspend = %s\n" "exec_resume = %s\n", @@ -202,6 +206,7 @@ debug_print_rule (Rule *rule) rule->only_on_battery, rule->send_signals, rule->subtree_pattern, + rule->process_name, rule->downclock_on_battery, (rule->exec_suspend ? rule->exec_suspend[2] : NULL), (rule->exec_resume ? rule->exec_resume[2] : NULL) @@ -222,6 +227,8 @@ parse_config () .subtree_pattern = NULL, + .process_name = NULL, + .downclock_on_battery = 0, .delay = 10, diff --git a/src/config.h b/src/config.h index 2a63e2e..4754b92 100644 --- a/src/config.h +++ b/src/config.h @@ -17,6 +17,7 @@ #define CONFIG_KEY_AUTO_ON_BATTERY "auto_suspend_on_battery" #define CONFIG_KEY_SEND_SIGNALS "send_signals" #define CONFIG_KEY_SUBTREE_PATTERN "suspend_subtree_pattern" +#define CONFIG_KEY_PROCESS_NAME "process_name" #define CONFIG_KEY_DOWNCLOCK_ON_BATTERY "downclock_on_battery" #define CONFIG_KEY_WM_NAME_CONTAINS "match_wm_name_contains" #define CONFIG_KEY_WM_CLASS_CONTAINS "match_wm_class_contains" @@ -26,6 +27,7 @@ CONFIG_KEY_AUTO_ON_BATTERY, \ CONFIG_KEY_SEND_SIGNALS, \ CONFIG_KEY_SUBTREE_PATTERN, \ + CONFIG_KEY_PROCESS_NAME, \ CONFIG_KEY_DOWNCLOCK_ON_BATTERY, \ CONFIG_KEY_SUSPEND_DELAY, \ CONFIG_KEY_RESUME_EVERY, \ diff --git a/src/entry.c b/src/entry.c index 183e7a7..67d788d 100644 --- a/src/entry.c +++ b/src/entry.c @@ -3,6 +3,7 @@ #include #include +#include "proc.h" WindowEntry* xsus_window_entry_new (WnckWindow *window, @@ -10,7 +11,7 @@ xsus_window_entry_new (WnckWindow *window, { WindowEntry *entry = g_malloc (sizeof (WindowEntry)); entry->rule = rule; - entry->pid = wnck_window_get_pid (window); + entry->pid = xsus_window_get_pid (window); entry->xid = wnck_window_get_xid (window); entry->wm_name = g_strdup (wnck_window_get_name (window)); return entry; @@ -44,7 +45,7 @@ xsus_entry_find_for_window_rule (WnckWindow *window, { // If suspending by signals, find entry by PID ... if (rule->send_signals) { - pid_t pid = wnck_window_get_pid (window); + pid_t pid = xsus_window_get_pid (window); for (; list; list = list->next) { WindowEntry *entry = list->data; if (entry->pid == pid) diff --git a/src/events.c b/src/events.c index 5a8a2b8..ce2ed93 100644 --- a/src/events.c +++ b/src/events.c @@ -8,6 +8,7 @@ #include "entry.h" #include "macros.h" +#include "proc.h" void @@ -69,7 +70,7 @@ windows_are_same_process (WnckWindow *w1, Rule *rule; return (WNCK_IS_WINDOW (w1) && WNCK_IS_WINDOW (w2) && - wnck_window_get_pid (w1) == wnck_window_get_pid (w2) && + xsus_window_get_pid (w1) == xsus_window_get_pid (w2) && (rule = xsus_window_get_rule (w1)) && rule->send_signals && rule == xsus_window_get_rule (w2)); @@ -104,7 +105,7 @@ pid_t window_entry_get_pid (WindowEntry *entry) { WnckWindow *window = wnck_window_get (entry->xid); - return window && wnck_window_get_pid (window) == entry->pid ? entry->pid : 0; + return window && xsus_window_get_pid (window) == entry->pid ? entry->pid : 0; } @@ -411,7 +412,7 @@ on_update_downclocked_processes () continue; // Skip any windows/PIDs we already know about - pid_t pid = wnck_window_get_pid (window); + pid_t pid = xsus_window_get_pid (window); if (g_hash_table_contains (old_pids, GINT_TO_POINTER (pid)) || g_hash_table_contains (new_pids, GINT_TO_POINTER (pid))) continue; diff --git a/src/proc.c b/src/proc.c new file mode 100644 index 0000000..a86bccc --- /dev/null +++ b/src/proc.c @@ -0,0 +1,113 @@ +#include "proc.h" +#include +#include +#include "rule.h" + + +typedef struct state { + GTree *procs; + char *process_name; + // Types match readproc.h + int cand_pid; + unsigned long long cand_start_time; // NOLINT(runtime/int) +} state; + + +static +int +compare_ints (gconstpointer a, gconstpointer b, __attribute__((unused)) gpointer data) +{ + return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); +} + + +static +gboolean +cmdline_matches (char **cmdline, char *s) +{ + if (cmdline == NULL) + return FALSE; + + char *basename = g_path_get_basename (cmdline[0]); + gboolean result = g_strcmp0 (basename, s) == 0; + g_free (basename); + return result; +} + + +static +gboolean +traverse_procs (__attribute__((unused)) gpointer pid, proc_t *proc, state *state) +{ + int cand_pid = 0; + unsigned long long cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) + + for (proc_t *cur = proc; cur; cur = g_tree_lookup(state->procs, GINT_TO_POINTER(cur->ppid))) { + if (cmdline_matches(cur->cmdline, state->process_name)) { + cand_pid = cur->tid; + cand_start_time = cur->start_time; + } + } + + if (cand_pid != 0 && state->cand_pid != cand_pid) { + if (state->cand_pid != 0) + g_warning("Multiple processes named '%s': %u and %u", + state->process_name, state->cand_pid, cand_pid); + + if (cand_start_time < state->cand_start_time || + (cand_start_time == state->cand_start_time && + cand_pid < state->cand_pid)) { + // Update + state->cand_start_time = cand_start_time; + state->cand_pid = cand_pid; + } + } + + return FALSE; +} + + +static +GTree* +get_processes () +{ + GTree *result = g_tree_new_full(compare_ints, NULL, NULL, (GDestroyNotify) freeproc); + PROCTAB *pt = openproc (PROC_FILLARG | PROC_FILLSTAT); + proc_t *proc; + + while ((proc = readproc (pt, NULL))) + g_tree_insert (result, GINT_TO_POINTER(proc->tid), proc); + + closeproc (pt); + return result; +} + + +static +int +process_name_get_pid (char *process_name) +{ + GTree *procs = get_processes(); + + state state; + state.procs = procs; + state.process_name = process_name; + state.cand_pid = 0; + state.cand_start_time = ULLONG_MAX; + + g_tree_foreach(procs, (GTraverseFunc) traverse_procs, &state); + g_tree_destroy (procs); + return state.cand_pid; +} + + +int +xsus_window_get_pid (WnckWindow *window) +{ + Rule *rule = xsus_window_get_rule (window); + char *process_name = rule ? rule->process_name : NULL; + int pid_by_process_name = process_name ? process_name_get_pid(process_name) : 0; + // Prefer getting the PID by process name so we can override _NET_WM_PID if it's wrong + // (e.g. when using flatpak) + return pid_by_process_name ? pid_by_process_name : wnck_window_get_pid(window); +} diff --git a/src/proc.h b/src/proc.h new file mode 100644 index 0000000..2f7fe4f --- /dev/null +++ b/src/proc.h @@ -0,0 +1,7 @@ +#ifndef XSUSPENDER_PROC_H +#define XSUSPENDER_PROC_H +#include + +int xsus_window_get_pid (WnckWindow *window); + +#endif // XSUSPENDER_PROC_H diff --git a/src/rule.c b/src/rule.c index 2a5d1f3..95546db 100644 --- a/src/rule.c +++ b/src/rule.c @@ -4,6 +4,7 @@ #include #include "xsuspender.h" +#include "proc.h" Rule* xsus_rule_copy (Rule *orig) @@ -15,6 +16,8 @@ xsus_rule_copy (Rule *orig) rule->needle_wm_class = g_strdup (orig->needle_wm_class); rule->needle_wm_class_group = g_strdup (orig->needle_wm_class_group); + rule->process_name = g_strdup (orig->process_name); + rule->exec_suspend = g_strdupv (orig->exec_suspend); rule->exec_resume = g_strdupv (orig->exec_resume); @@ -29,6 +32,8 @@ xsus_rule_free (Rule *rule) g_free (rule->needle_wm_class_group); g_free (rule->needle_wm_name); + g_free (rule->process_name); + g_strfreev (rule->exec_suspend); g_strfreev (rule->exec_resume); diff --git a/src/rule.h b/src/rule.h index ef97499..34fc7c4 100644 --- a/src/rule.h +++ b/src/rule.h @@ -16,6 +16,8 @@ typedef struct Rule { char* subtree_pattern; + char* process_name; + guint16 delay; guint16 resume_every; guint16 resume_for; diff --git a/src/xsuspender.c b/src/xsuspender.c index b4de11e..889ea24 100644 --- a/src/xsuspender.c +++ b/src/xsuspender.c @@ -13,6 +13,7 @@ #include "events.h" #include "exec.h" #include "macros.h" +#include "proc.h" #include "rule.h" @@ -108,7 +109,7 @@ xsus_window_resume (WnckWindow *window) if ((entry = xsus_entry_find_for_window_rule (window, rule, queued_entries))) { g_debug ("Removing window %#lx (%d) from suspension queue: %s", wnck_window_get_xid (window), - wnck_window_get_pid (window), + xsus_window_get_pid (window), wnck_window_get_name (window)); queued_entries = g_slist_remove (queued_entries, entry); xsus_window_entry_free (entry); @@ -119,7 +120,7 @@ xsus_window_resume (WnckWindow *window) if ((entry = xsus_entry_find_for_window_rule (window, rule, suspended_entries))) { g_debug ("Resuming window %#lx (%d): %s", wnck_window_get_xid (window), - wnck_window_get_pid (window), + xsus_window_get_pid (window), wnck_window_get_name (window)); xsus_signal_continue (entry); return; @@ -150,7 +151,7 @@ xsus_window_suspend (WnckWindow *window) g_debug ("Suspending window in %ds: %#lx (%d): %s", rule->delay, wnck_window_get_xid (window), - wnck_window_get_pid (window), + xsus_window_get_pid (window), wnck_window_get_name (window)); WindowEntry *entry = xsus_window_entry_new (window, rule); xsus_window_entry_enqueue (entry, rule->delay); From 72abbe0647a8d379e5a51540f0f47396598a95cc Mon Sep 17 00:00:00 2001 From: Erik Hahn Date: Thu, 21 Oct 2021 16:51:49 +0200 Subject: [PATCH 2/4] Iterate directly over pt --- src/proc.c | 111 +++++++++++++++++++++-------------------------------- 1 file changed, 43 insertions(+), 68 deletions(-) diff --git a/src/proc.c b/src/proc.c index a86bccc..d5fc5d8 100644 --- a/src/proc.c +++ b/src/proc.c @@ -4,23 +4,6 @@ #include "rule.h" -typedef struct state { - GTree *procs; - char *process_name; - // Types match readproc.h - int cand_pid; - unsigned long long cand_start_time; // NOLINT(runtime/int) -} state; - - -static -int -compare_ints (gconstpointer a, gconstpointer b, __attribute__((unused)) gpointer data) -{ - return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); -} - - static gboolean cmdline_matches (char **cmdline, char *s) @@ -36,49 +19,13 @@ cmdline_matches (char **cmdline, char *s) static -gboolean -traverse_procs (__attribute__((unused)) gpointer pid, proc_t *proc, state *state) +proc_t* +proc_by_pid (int pid) { - int cand_pid = 0; - unsigned long long cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) - - for (proc_t *cur = proc; cur; cur = g_tree_lookup(state->procs, GINT_TO_POINTER(cur->ppid))) { - if (cmdline_matches(cur->cmdline, state->process_name)) { - cand_pid = cur->tid; - cand_start_time = cur->start_time; - } - } - - if (cand_pid != 0 && state->cand_pid != cand_pid) { - if (state->cand_pid != 0) - g_warning("Multiple processes named '%s': %u and %u", - state->process_name, state->cand_pid, cand_pid); - - if (cand_start_time < state->cand_start_time || - (cand_start_time == state->cand_start_time && - cand_pid < state->cand_pid)) { - // Update - state->cand_start_time = cand_start_time; - state->cand_pid = cand_pid; - } - } - - return FALSE; -} - - -static -GTree* -get_processes () -{ - GTree *result = g_tree_new_full(compare_ints, NULL, NULL, (GDestroyNotify) freeproc); - PROCTAB *pt = openproc (PROC_FILLARG | PROC_FILLSTAT); - proc_t *proc; - - while ((proc = readproc (pt, NULL))) - g_tree_insert (result, GINT_TO_POINTER(proc->tid), proc); - - closeproc (pt); + int arr[2] = {pid, 0}; + PROCTAB *pt = openproc(PROC_FILLARG | PROC_FILLSTAT | PROC_PID, arr); + proc_t *result = readproc(pt, NULL); + closeproc(pt); return result; } @@ -87,17 +34,45 @@ static int process_name_get_pid (char *process_name) { - GTree *procs = get_processes(); + // Types match readproc.c + int cand_pid = 0; + unsigned long long cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) - state state; - state.procs = procs; - state.process_name = process_name; - state.cand_pid = 0; - state.cand_start_time = ULLONG_MAX; + PROCTAB *pt = openproc(PROC_FILLARG | PROC_FILLSTAT); + proc_t *proc; + + while ((proc = readproc(pt, NULL))) { + int tree_cand_pid = 0; + unsigned long long tree_cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) + + // Some applications use multiple processes with the same name -- find the root one + int ppid; + do { + if (cmdline_matches(proc->cmdline, process_name)) { + tree_cand_pid = proc->tid; + tree_cand_start_time = proc->start_time; + } + ppid = proc->ppid; + freeproc(proc); + } while ((proc = proc_by_pid(ppid))); + + if (tree_cand_pid != 0 && tree_cand_pid != cand_pid) { + if (cand_pid != 0) + g_warning("Multiple processes named '%s': %u and %u", + process_name, cand_pid, tree_cand_pid); + + if (tree_cand_start_time < cand_start_time || + (tree_cand_start_time == cand_start_time && + tree_cand_pid < cand_pid)) { + // Update + cand_pid = tree_cand_pid; + cand_start_time = tree_cand_start_time; + } + } + } - g_tree_foreach(procs, (GTraverseFunc) traverse_procs, &state); - g_tree_destroy (procs); - return state.cand_pid; + closeproc(pt); + return cand_pid; } From fa95e9eadfe445f733663ba9ebdc142d105f0d43 Mon Sep 17 00:00:00 2001 From: Erik Hahn Date: Thu, 21 Oct 2021 23:49:18 +0200 Subject: [PATCH 3/4] Replace int by pid_t where appropriate --- src/proc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/proc.c b/src/proc.c index d5fc5d8..bb56ca3 100644 --- a/src/proc.c +++ b/src/proc.c @@ -20,9 +20,9 @@ cmdline_matches (char **cmdline, char *s) static proc_t* -proc_by_pid (int pid) +proc_by_pid (pid_t pid) { - int arr[2] = {pid, 0}; + pid_t arr[2] = {pid, 0}; PROCTAB *pt = openproc(PROC_FILLARG | PROC_FILLSTAT | PROC_PID, arr); proc_t *result = readproc(pt, NULL); closeproc(pt); @@ -31,22 +31,22 @@ proc_by_pid (int pid) static -int +pid_t process_name_get_pid (char *process_name) { // Types match readproc.c - int cand_pid = 0; + pid_t cand_pid = 0; unsigned long long cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) PROCTAB *pt = openproc(PROC_FILLARG | PROC_FILLSTAT); proc_t *proc; while ((proc = readproc(pt, NULL))) { - int tree_cand_pid = 0; + pid_t tree_cand_pid = 0; unsigned long long tree_cand_start_time = ULLONG_MAX; // NOLINT(runtime/int) // Some applications use multiple processes with the same name -- find the root one - int ppid; + pid_t ppid; do { if (cmdline_matches(proc->cmdline, process_name)) { tree_cand_pid = proc->tid; @@ -76,12 +76,12 @@ process_name_get_pid (char *process_name) } -int +pid_t xsus_window_get_pid (WnckWindow *window) { Rule *rule = xsus_window_get_rule (window); char *process_name = rule ? rule->process_name : NULL; - int pid_by_process_name = process_name ? process_name_get_pid(process_name) : 0; + pid_t pid_by_process_name = process_name ? process_name_get_pid(process_name) : 0; // Prefer getting the PID by process name so we can override _NET_WM_PID if it's wrong // (e.g. when using flatpak) return pid_by_process_name ? pid_by_process_name : wnck_window_get_pid(window); From 0437d2e384fb957ee61a4a54f15ad870de2dd98c Mon Sep 17 00:00:00 2001 From: Erik Hahn Date: Thu, 21 Oct 2021 23:50:24 +0200 Subject: [PATCH 4/4] Add references to Flatpak to man page and example conf --- data/xsuspender.conf | 2 +- doc/xsuspender.1 | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/xsuspender.conf b/data/xsuspender.conf index 92900d3..78faa10 100644 --- a/data/xsuspender.conf +++ b/data/xsuspender.conf @@ -36,7 +36,7 @@ # # # Assume that all windows matching this rule belong to the # # process with this name. This is a workaround for -# # missing or incorrect _NET_WM_PID values. +# # missing or incorrect _NET_WM_PID values, e.g. with Flatpak. # process_name = ... # # # Whether to apply the rule only when on battery power. diff --git a/doc/xsuspender.1 b/doc/xsuspender.1 index f086e28..12a3062 100644 --- a/doc/xsuspender.1 +++ b/doc/xsuspender.1 @@ -205,8 +205,9 @@ is also true. .BR process_name If provided, XSuspender will ignore _NET_WM_PID and instead detect the process a window matching this rule belongs to by looking for -a process with this name. There should be only one process with this -name, not including its child processes. +a process whose process name (cmdline[0]) matches this string verbatim. +There should be only one such process not including its child processes. +This is useful when _NET_WM_PID is incorrect or unset, e.g. with flatpak. .TP .BR exec_suspend .TQ