Skip to content
Open
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
149 changes: 95 additions & 54 deletions terminatorlib/plugins/save_last_session_layout.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,123 @@
import os
import signal
import sys
import sys

# Fix imports when testing this file directly
if __name__ == '__main__':
sys.path.append( os.path.join(os.path.dirname(__file__), "../.."))

from gi.repository import GLib

from terminatorlib.config import Config
import terminatorlib.plugin as plugin
from terminatorlib.util import get_config_dir, err, dbg, gerr
from terminatorlib.util import dbg
from terminatorlib.terminator import Terminator
from terminatorlib import util


# AVAILABLE must contain a list of all the classes that you want exposed
AVAILABLE = ['SaveLastSessionLayout']


class SaveLastSessionLayout(plugin.Plugin):
capabilities = ['session']
"""Plugin to automatically save the current session layout on close.

config = None
conf_file = os.path.join(get_config_dir(),"save_last_session_cwd")
Comment thread
henricook marked this conversation as resolved.
conf_sessions = []
emit_close_count = 0
This plugin saves the terminal layout (tabs, splits, working directories)
when Terminator closes, allowing restoration via the Layouts menu.

The implementation uses event-driven signal connections:
- Connects to each terminal's 'pre-close-term' signal to trigger saves
- Connects to each Notebook's 'page-added' signal to catch new tabs
- Uses deferred setup via GLib.idle_add to avoid blocking the UI
"""
capabilities = ['session']

def __init__(self):
dbg("SaveLastSessionLayout Init")
self.connect_signals()
self.connected_terminals = set()
self.connected_notebooks = set()
self.save_triggered = False
dbg("SaveLastSessionLayout: init")
# Defer all setup to avoid blocking the preferences UI
GLib.idle_add(self.deferred_connect)

def connect_to_terminal(self, terminal):
"""Connect to a terminal's pre-close-term signal if not already connected."""
if terminal not in self.connected_terminals:
dbg("SaveLastSessionLayout: connecting to terminal %s" % id(terminal))
terminal.connect('pre-close-term', self.on_close, None)
self.connected_terminals.add(terminal)

def connect_to_notebooks(self):
"""Scan all windows and connect to any new notebooks."""
terminator = Terminator()
for window in terminator.windows:
child = window.get_child()
# Only connect to Notebook widgets (they have 'append_page' method)
if child is not None and hasattr(child, 'append_page'):
if child not in self.connected_notebooks:
dbg("SaveLastSessionLayout: connecting to notebook %s" % id(child))
child.connect('page-added', self.on_page_added)
self.connected_notebooks.add(child)

def on_page_added(self, notebook, child, page_num):
"""Handle new page added to notebook - connect to any new terminals."""
dbg("SaveLastSessionLayout: page added to notebook")
# New tabs are usually single terminals - connect directly if possible
if hasattr(child, 'connect') and child not in self.connected_terminals:
try:
child.connect('pre-close-term', self.on_close, None)
self.connected_terminals.add(child)
except TypeError:
# Not a terminal widget, ignore
pass

def deferred_connect(self):
"""Deferred setup - runs once after startup completes via idle_add."""
dbg("SaveLastSessionLayout: deferred connect running")

# OS signal handlers for shutdown scenarios
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGHUP, self.signal_handler)

#not used, but capability can be used to load automatically
def load_session_layout(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
dbg("SaveLastSessionLayout load layout")
terminator = Terminator()
util.spawn_new_terminator(terminator.origcwd, ['-u', '-l', 'SaveLastSessionLayout'])
terminator = Terminator()

def save_session_layout(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
# Connect to all existing terminals
for terminal in terminator.terminals:
self.connect_to_terminal(terminal)

config = Config()
terminator = Terminator()
current_layout = terminator.describe_layout(save_cwd = True)
dbg("SaveLastSessionLayout: save layout(%s)" % current_layout)
res = config.replace_layout("SaveLastSessionLayout", current_layout)
if (not res):
r = config.add_layout("SaveLastSessionLayout", current_layout)
config.save()
return True
# Connect to all existing notebooks for new tab detection
self.connect_to_notebooks()

def signal_handler(self,signum, frame):
return False # Don't repeat - run only once

# Not used, but capability can be used to load automatically
def load_session_layout(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
"""Load the saved session layout in a new Terminator instance."""
dbg("SaveLastSessionLayout: load layout")
terminator = Terminator()
util.spawn_new_terminator(terminator.origcwd, ['-u', '-l', 'SaveLastSessionLayout'])

def save_session_layout(self):
"""Save the current layout to config."""
config = Config()
terminator = Terminator()
current_layout = terminator.describe_layout(save_cwd=True)
dbg("SaveLastSessionLayout: save layout(%s)" % current_layout)
res = config.replace_layout("SaveLastSessionLayout", current_layout)
if not res:
config.add_layout("SaveLastSessionLayout", current_layout)
config.save()
return True

def signal_handler(self, signum, frame):
"""Handle OS signals (SIGTERM, SIGHUP)."""
signame = signal.Signals(signum).name
dbg('signal handler called:signal %s (%s)' %
(signame, signum))
dbg('SaveLastSessionLayout: signal handler called: %s (%s)' % (signame, signum))
self.save_session_layout()

def connect_signals(self):
dbg("SaveLastSessionLayout connect_signals")

signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGCHLD, self.signal_handler)
Comment thread
henricook marked this conversation as resolved.
signal.signal(signal.SIGHUP, self.signal_handler)

n = 0
for term in Terminator().terminals:
dbg("SaveLastSessionLayout connect_signals to term num:(%d)" % n)
n = n + 1
# event close-term works, and does not require an additional
# event but has a race condition when
# there is only one terminal we are unable to get the
# describe_layout section

#term.connect('close-term', self.close, None)
term.connect('pre-close-term', self.close, None)

#Can connect signal from terminal
#term.connect('load-layout', self.load_session_layout, None)

def close(self, term, event, arg1 = None):
if (self.emit_close_count == 0):
self.emit_close_count = self.emit_close_count + 1
self.save_session_layout("", "")

def on_close(self, terminal, event, arg1=None):
"""Handle terminal pre-close-term signal."""
if not self.save_triggered:
self.save_triggered = True
dbg("SaveLastSessionLayout: terminal closing, saving layout")
self.save_session_layout()