From abc0f4e8cf7debe06cbaf99310151ad2ac0e37da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9sir=C3=A9=20Werner=20Menrath?= Date: Sat, 6 Sep 2025 12:56:18 +0200 Subject: [PATCH] feat: Inhibit screensaver during backup operations Adds logic to prevent the screensaver or screen lock from activating while a backup is in progress. - Uses a D-Bus call to `org.freedesktop.ScreenSaver.Inhibit` to request a lock. - The lock is activated when a backup starts. - The lock is reliably released when the backup finishes, fails, or is cancelled. --- core/backup_manager.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/core/backup_manager.py b/core/backup_manager.py index be2e462..dab68d5 100644 --- a/core/backup_manager.py +++ b/core/backup_manager.py @@ -28,6 +28,59 @@ class BackupManager: self.is_system_process = False self.app = app self.encryption_manager = EncryptionManager(logger, app) + self.inhibit_cookie = None + + def _inhibit_screensaver(self): + """Prevents the screensaver and auto-suspend during a backup.""" + if not shutil.which("gdbus"): + self.logger.log("gdbus command not found, cannot inhibit screensaver.") + return + + try: + self.logger.log("Attempting to inhibit screensaver and power management.") + command = [ + "gdbus", "call", "--session", "--dest", "org.freedesktop.ScreenSaver", + "--object-path", "/org/freedesktop/ScreenSaver", + "--method", "org.freedesktop.ScreenSaver.Inhibit", + "Py-Backup", "Backup in progress" + ] + result = subprocess.run(command, capture_output=True, text=True, check=True) + # Output is like "(uint32 12345,)", we need to extract the number. + match = re.search(r'uint32\s+(\d+)', result.stdout) + if match: + self.inhibit_cookie = int(match.group(1)) + self.logger.log(f"Successfully inhibited screensaver with cookie {self.inhibit_cookie}") + else: + self.logger.log(f"Could not parse inhibit cookie from gdbus output: {result.stdout}") + except FileNotFoundError: + self.logger.log("gdbus command not found, cannot inhibit screensaver.") + except subprocess.CalledProcessError as e: + self.logger.log(f"Failed to inhibit screensaver. D-Bus call failed: {e.stderr}") + except Exception as e: + self.logger.log(f"An unexpected error occurred while inhibiting screensaver: {e}") + + def _uninhibit_screensaver(self): + """Releases the screensaver and auto-suspend lock.""" + if self.inhibit_cookie is None: + return + if not shutil.which("gdbus"): + self.logger.log("gdbus command not found, cannot uninhibit screensaver.") + return + + try: + self.logger.log(f"Attempting to uninhibit screensaver with cookie {self.inhibit_cookie}") + command = [ + "gdbus", "call", "--session", "--dest", "org.freedesktop.ScreenSaver", + "--object-path", "/org/freedesktop/ScreenSaver", + "--method", "org.freedesktop.ScreenSaver.UnInhibit", + str(self.inhibit_cookie) + ] + subprocess.run(command, capture_output=True, text=True, check=True) + self.logger.log("Successfully uninhibited screensaver.") + except Exception as e: + self.logger.log(f"Failed to uninhibit screensaver: {e}") + finally: + self.inhibit_cookie = None def cancel_and_delete_privileged_backup(self, delete_path: str): """Cancels a running system backup and deletes the target directory in one atomic pkexec call.""" @@ -169,6 +222,7 @@ rm -f '{info_file_path}' def start_backup(self, queue, source_path: str, dest_path: str, is_system: bool, is_dry_run: bool = False, exclude_files: Optional[List[Path]] = None, source_size: int = 0, is_compressed: bool = False, is_encrypted: bool = False, mode: str = "incremental", password: Optional[str] = None, key_file: Optional[str] = None): self.is_system_process = is_system + self._inhibit_screensaver() thread = threading.Thread(target=self._run_backup_path, args=( queue, source_path, dest_path, is_system, is_dry_run, exclude_files, source_size, is_compressed, is_encrypted, mode, password, key_file)) thread.daemon = True @@ -370,6 +424,7 @@ set -e finally: if is_encrypted and mount_point: self.encryption_manager.cleanup_encrypted_backup(base_dest_path) + self._uninhibit_screensaver() self.process = None def _create_info_file(self, pybackup_dir: str, backup_name: str, source_size: int, is_encrypted: bool):