Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4555,6 +4555,7 @@ def _suspend_signal(self) -> None:
def _resume_signal(self) -> None:
"""Signal that the application is being resumed from a suspension."""
self.app_resume_signal.publish(self)
self.refresh(layout=True)

@contextmanager
def suspend(self) -> Iterator[None]:
Expand Down Expand Up @@ -4613,7 +4614,7 @@ def action_suspend_process(self) -> None:
"""Suspend the process into the background.
Note:
On Unix and Unix-like systems a `SIGTSTP` is sent to the
On Unix and Unix-like systems a `SIGSTOP` is sent to the
application's process. Currently on Windows and when running
under Textual Web this is a non-operation.
"""
Expand All @@ -4623,8 +4624,9 @@ def action_suspend_process(self) -> None:
# First, ensure that the suspend signal gets published while
# we're still in application mode.
self._suspend_signal()
# With that out of the way, send the SIGTSTP signal.
os.kill(os.getpid(), signal.SIGTSTP)
self._driver.suspend_application_mode()
self._driver._must_signal_resume = True
Copy link
Author

@pasky pasky Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. this feels like a layering violation, but I wanted a minimal patch and the whole WINDOWS test cuts through the abstraction anyway.

Perhaps .suspend_application_mode() should just take an argument whether to suspend the process and all of action_suspend_process() would be moved to driver-specific code?

os.kill(os.getpid(), signal.SIGSTOP)
# NOTE: There is no call to publish the resume signal here, this
# will be handled by the driver posting a SignalResume event
# (see the event handler on App._resume_signal) above.
Expand Down
22 changes: 10 additions & 12 deletions src/textual/drivers/linux_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,18 @@ def __init__(
signal.signal(signal.SIGCONT, self._sigcont_application)

def _sigtstp_application(self, *_) -> None:
"""Handle a SIGTSTP signal."""
# If we're supposed to auto-restart, that means we need to shut down
# first.
if self._auto_restart:
self.suspend_application_mode()
# Flag that we'll need to signal a resume on successful startup
# again.
self._must_signal_resume = True
# Now send a SIGSTOP to our process to *actually* suspend the
# process.
os.kill(os.getpid(), signal.SIGSTOP)
"""Handle a SIGTSTP signal.
This is called when the process receives SIGTSTP from an external
source (e.g., `kill -TSTP`). We schedule action_suspend_process via
the event loop, avoiding deadlock from calling _key_thread.join()
in signal context.
"""
if self._auto_restart and self._loop is not None:
self._loop.call_soon_threadsafe(self._app.action_suspend_process)

def _sigcont_application(self, *_) -> None:
"""Handle a SICONT application."""
"""Handle a SIGCONT signal."""
if self._auto_restart:
self.resume_application_mode()

Expand Down