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..78faa10 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, e.g. with Flatpak. +# 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..12a3062 100644 --- a/doc/xsuspender.1 +++ b/doc/xsuspender.1 @@ -202,6 +202,13 @@ 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 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 .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..bb56ca3 --- /dev/null +++ b/src/proc.c @@ -0,0 +1,88 @@ +#include "proc.h" +#include +#include +#include "rule.h" + + +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 +proc_t* +proc_by_pid (pid_t pid) +{ + pid_t arr[2] = {pid, 0}; + PROCTAB *pt = openproc(PROC_FILLARG | PROC_FILLSTAT | PROC_PID, arr); + proc_t *result = readproc(pt, NULL); + closeproc(pt); + return result; +} + + +static +pid_t +process_name_get_pid (char *process_name) +{ + // Types match readproc.c + 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))) { + 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 + pid_t 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; + } + } + } + + closeproc(pt); + return cand_pid; +} + + +pid_t +xsus_window_get_pid (WnckWindow *window) +{ + Rule *rule = xsus_window_get_rule (window); + char *process_name = rule ? rule->process_name : NULL; + 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); +} 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);