From 246d879dcf6860f39a69316a19158b14662aebc8 Mon Sep 17 00:00:00 2001 From: Paul Hooijenga Date: Thu, 20 Oct 2022 13:01:40 +0200 Subject: [PATCH] Include names of processes in PromptQuitDialog --- guake/dialogs.py | 13 ++++-- guake/globals.py | 1 + guake/guake_app.py | 10 ++++- guake/notebook.py | 41 ++++++++++--------- guake/tests/test_utils.py | 6 +++ guake/utils.py | 14 +++++++ ...t-quit-process-names-32a4e1b37eb36037.yaml | 6 +++ 7 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/prompt-quit-process-names-32a4e1b37eb36037.yaml diff --git a/guake/dialogs.py b/guake/dialogs.py index 24df429d3..5f4a30941 100644 --- a/guake/dialogs.py +++ b/guake/dialogs.py @@ -64,15 +64,20 @@ def __init__(self, parent, procs, tabs, notebooks): else: notebooks_str = "" - if procs == 0: + if not procs: proc_str = _("There are no processes running") - elif procs == 1: + elif len(procs) == 1: proc_str = _("There is a process still running") else: - proc_str = _("There are {0} processes still running").format(procs) + proc_str = _("There are {0} processes still running").format(len(procs)) + + if procs: + proc_list = "\n\n" + "\n".join(f"{name} ({pid})" for pid, name in procs) + else: + proc_list = "" self.set_markup(primary_msg) - self.format_secondary_markup(f"{proc_str}{tab_str}{notebooks_str}.") + self.format_secondary_markup(f"{proc_str}{tab_str}{notebooks_str}.{proc_list}") def quit(self): """Run the "are you sure" dialog for quitting Guake""" diff --git a/guake/globals.py b/guake/globals.py index 3410a3347..49e0d5d50 100644 --- a/guake/globals.py +++ b/guake/globals.py @@ -70,6 +70,7 @@ def is_run_from_git_workdir(): ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT = range(3) ALIGN_TOP, ALIGN_BOTTOM = range(2) ALWAYS_ON_PRIMARY = -1 +PROMPT_NEVER, PROMPT_PROCESSES, PROMPT_ALWAYS = range(3) # TODO this is not as fancy as as it could be # pylint: disable=anomalous-backslash-in-string diff --git a/guake/guake_app.py b/guake/guake_app.py index 874afb0dc..cb662c372 100644 --- a/guake/guake_app.py +++ b/guake/guake_app.py @@ -54,6 +54,8 @@ from guake.dialogs import PromptQuitDialog from guake.globals import MAX_TRANSPARENCY from guake.globals import NAME +from guake.globals import PROMPT_ALWAYS +from guake.globals import PROMPT_PROCESSES from guake.globals import TABS_SESSION_SCHEMA_VERSION from guake.gsettings import GSettingHandler from guake.keybindings import Keybindings @@ -906,13 +908,17 @@ def accel_search_terminal(self, *args): def accel_quit(self, *args): """Callback to prompt the user whether to quit Guake or not.""" - procs = self.notebook_manager.get_running_fg_processes_count() + procs = self.notebook_manager.get_running_fg_processes() tabs = self.notebook_manager.get_n_pages() notebooks = self.notebook_manager.get_n_notebooks() prompt_cfg = self.settings.general.get_boolean("prompt-on-quit") prompt_tab_cfg = self.settings.general.get_int("prompt-on-close-tab") # "Prompt on tab close" config overrides "prompt on quit" config - if prompt_cfg or (prompt_tab_cfg == 1 and procs > 0) or (prompt_tab_cfg == 2): + if ( + prompt_cfg + or (prompt_tab_cfg == PROMPT_PROCESSES and procs) + or (prompt_tab_cfg == PROMPT_ALWAYS) + ): log.debug("Remaining procs=%r", procs) if PromptQuitDialog(self.window, procs, tabs, notebooks).quit(): log.info("Quitting Guake") diff --git a/guake/notebook.py b/guake/notebook.py index 627d0b808..2bb976f45 100644 --- a/guake/notebook.py +++ b/guake/notebook.py @@ -25,9 +25,12 @@ from guake.callbacks import MenuHideCallback from guake.callbacks import NotebookScrollCallback from guake.dialogs import PromptQuitDialog +from guake.globals import PROMPT_ALWAYS +from guake.globals import PROMPT_PROCESSES from guake.menus import mk_notebook_context_menu from guake.prefs import PrefsDialog from guake.utils import gdk_is_x11_display +from guake.utils import get_process_name from guake.utils import save_tabs_when_changed import gi @@ -247,15 +250,15 @@ def get_terminals(self): terminals += page.get_terminals() return terminals - def get_running_fg_processes_count(self): - fg_proc_count = 0 + def get_running_fg_processes(self): + processes = [] for page in self.iter_pages(): - fg_proc_count += self.get_running_fg_processes_count_page(self.page_num(page)) - return fg_proc_count + processes += self.get_running_fg_processes_page(page) + return processes - def get_running_fg_processes_count_page(self, index): - total_procs = 0 - for terminal in self.get_terminals_for_page(index): + def get_running_fg_processes_page(self, page): + processes = [] + for terminal in page.get_terminals(): pty = terminal.get_pty() if not pty: continue @@ -265,14 +268,13 @@ def get_running_fg_processes_count_page(self, index): fgpid = posix.tcgetpgrp(fdpty) log.debug("found running pid: %s", fgpid) if fgpid not in (-1, term_pid): - total_procs += 1 + processes.append((fgpid, get_process_name(fgpid))) except OSError: log.debug( "Cannot retrieve any pid from terminal %s, looks like it is already dead", - index, + terminal, ) - return 0 - return total_procs + return processes def has_page(self): return self.get_n_pages() > 0 @@ -296,16 +298,17 @@ def delete_page(self, page_num, kill=True, prompt=0): if page_num >= self.get_n_pages() or page_num < 0: log.error("Can not delete page %s no such index", page_num) return + + page = self.get_nth_page(page_num) # TODO NOTEBOOK it would be nice if none of the "ui" stuff # (PromptQuitDialog) would be in here - procs = self.get_running_fg_processes_count_page(page_num) - if prompt == 2 or (prompt == 1 and procs > 0): + procs = self.get_running_fg_processes_page(page) + if prompt == PROMPT_ALWAYS or (prompt == PROMPT_PROCESSES and procs): # TODO NOTEBOOK remove call to guake if not PromptQuitDialog(self.guake.window, procs, -1, None).close_tab(): return - page = self.get_nth_page(page_num) - for terminal in self.get_terminals_for_page(page_num): + for terminal in page.get_terminals(): if kill: terminal.kill() terminal.destroy() @@ -609,8 +612,8 @@ def get_n_pages(self): def get_n_notebooks(self): return len(self.notebooks.keys()) - def get_running_fg_processes_count(self): - r_fg_c = 0 + def get_running_fg_processes(self): + processes = [] for k in self.notebooks: - r_fg_c += self.notebooks[k].get_running_fg_processes_count() - return r_fg_c + processes += self.notebooks[k].get_running_fg_processes() + return processes diff --git a/guake/tests/test_utils.py b/guake/tests/test_utils.py index 457d764a1..a0f5d7140 100644 --- a/guake/tests/test_utils.py +++ b/guake/tests/test_utils.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # pylint: disable=redefined-outer-name +import os from guake.utils import FileManager +from guake.utils import get_process_name def test_file_manager(fs): @@ -37,3 +39,7 @@ def test_file_manager_clear(fs): assert fm.read("/foo/bar") == "test" fm.clear() assert fm.read("/foo/bar") == "changed" + + +def test_process_name(): + assert get_process_name(os.getpid()) diff --git a/guake/utils.py b/guake/utils.py index b2a645f46..dc07c80e6 100644 --- a/guake/utils.py +++ b/guake/utils.py @@ -22,6 +22,7 @@ import enum import logging import os +import re import subprocess import time import yaml @@ -524,3 +525,16 @@ def draw(self, widget, cr): cr.paint() cr.restore() + + +def get_process_name(pid): + stat_file = f"/proc/{pid}/stat" + try: + with open(stat_file, "r", encoding="utf-8") as fp: + status = fp.read() + except IOError as ex: + log.debug("Unable to read %s: %s", stat_file, ex) + status = "" + + match = re.match(r"\d+ \(([^)]+)\)", status) + return match.group(1) if match else None diff --git a/releasenotes/notes/prompt-quit-process-names-32a4e1b37eb36037.yaml b/releasenotes/notes/prompt-quit-process-names-32a4e1b37eb36037.yaml new file mode 100644 index 000000000..50931549a --- /dev/null +++ b/releasenotes/notes/prompt-quit-process-names-32a4e1b37eb36037.yaml @@ -0,0 +1,6 @@ +release_summary: > + The "are you sure you want to close" dialog will now include names of any running processes. + +features: + - | + - Include names of any processes in PromptQuitDialog, closes #256