Permalink
Fetching contributors…
Cannot retrieve contributors at this time
514 lines (383 sloc) 13.8 KB
# -*- coding: utf-8 -*-
"""Pythonization of the :term:`tmux(1)` session.
libtmux.session
~~~~~~~~~~~~~~~
"""
from __future__ import absolute_import, unicode_literals, with_statement
import logging
import os
from . import exc, formats
from ._compat import text_type
from .common import (
EnvironmentMixin,
TmuxMappingObject,
TmuxRelationalObject,
handle_option_error,
has_version,
session_check_name,
)
from .window import Window
logger = logging.getLogger(__name__)
class Session(TmuxMappingObject, TmuxRelationalObject, EnvironmentMixin):
"""
A :term:`tmux(1)` :term:`session` [#]_.
Holds :class:`Window` objects.
Parameters
----------
server : :class:`Server`
References
----------
.. [#] tmux session. openbsd manpage for TMUX(1).
"When tmux is started it creates a new session with a single window
and displays it on screen..."
"A session is a single collection of pseudo terminals under the
management of tmux. Each session has one or more windows linked to
it."
https://man.openbsd.org/tmux.1#DESCRIPTION. Accessed April 1st, 2018.
"""
#: unique child ID key for :class:`~libtmux.common.TmuxRelationalObject`
child_id_attribute = 'window_id'
#: namespace used :class:`~libtmux.common.TmuxMappingObject`
formatter_prefix = 'session_'
def __init__(self, server=None, **kwargs):
EnvironmentMixin.__init__(self)
self.server = server
if 'session_id' not in kwargs:
raise ValueError('Session requires a `session_id`')
self._session_id = kwargs['session_id']
self.server._update_windows()
@property
def _info(self):
attrs = {'session_id': str(self._session_id)}
def by(val):
for key, value in attrs.items():
try:
if attrs[key] != val[key]:
return False
except KeyError:
return False
return True
try:
return list(filter(by, self.server._sessions))[0]
except IndexError as e:
logger.error(e)
def cmd(self, *args, **kwargs):
"""
Return :meth:`server.cmd`.
Returns
-------
:class:`server.cmd`
Notes
-----
.. versionchanged:: 0.8
Renamed from ``.tmux`` to ``.cmd``.
"""
# if -t is not set in any arg yet
if not any('-t' in text_type(x) for x in args):
# insert -t immediately after 1st arg, as per tmux format
new_args = [args[0]]
new_args += ['-t', self.id]
new_args += args[1:]
args = new_args
return self.server.cmd(*args, **kwargs)
def attach_session(self):
"""Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``."""
proc = self.cmd('attach-session', '-t%s' % self.id)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
def kill_session(self):
"""``$ tmux kill-session``."""
proc = self.cmd('kill-session', '-t%s' % self.id)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
def switch_client(self):
"""
Switch client to this session.
Raises
------
:exc:`exc.LibTmuxException`
"""
proc = self.cmd('switch-client', '-t%s' % self.id)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
def rename_session(self, new_name):
"""
Rename session and return new :class:`Session` object.
Parameters
----------
new_name : str
new session name
Returns
-------
:class:`Session`
Raises
------
:exc:`exc.BadSessionName`
"""
session_check_name(new_name)
proc = self.cmd('rename-session', new_name)
if proc.stderr:
if has_version('2.7') and 'no current client' in proc.stderr:
"""tmux 2.7 raises "no current client" warning on BSD systems.
Should be fixed next release:
- https://www.mail-archive.com/tech@openbsd.org/msg45186.html
- https://marc.info/?l=openbsd-cvs&m=152183263526828&w=2
"""
pass
else:
raise exc.LibTmuxException(proc.stderr)
return self
def new_window(
self,
window_name=None,
start_directory=None,
attach=True,
window_index='',
window_shell=None,
):
"""
Return :class:`Window` from ``$ tmux new-window``.
By default, this will make the window active. For the new window
to be created and not set to current, pass in ``attach=False``.
Parameters
----------
window_name : str, optional
start_directory : str, optional
working directory in which the new window is created.
attach : bool, optional
make new window the current window after creating it, default True.
window_index : str
create the new window at the given index position. Default is empty
string which will create the window in the next available position.
window_shell : str
execute a command on starting the window. The window will close
when the command exits.
.. note::
When this command exits the window will close. This feature is
useful for long-running processes where the closing of the
window upon completion is desired.
Returns
-------
:class:`Window`
"""
wformats = ['session_name', 'session_id'] + formats.WINDOW_FORMATS
tmux_formats = ['#{%s}' % f for f in wformats]
window_args = tuple()
if not attach:
window_args += ('-d',)
window_args += ('-P',)
if start_directory:
# as of 2014-02-08 tmux 1.9-dev doesn't expand ~ in new-window -c.
start_directory = os.path.expanduser(start_directory)
window_args += ('-c%s' % start_directory,)
window_args += ('-F"%s"' % '\t'.join(tmux_formats),) # output
if window_name:
window_args += ('-n%s' % window_name,)
window_args += (
# empty string for window_index will use the first one available
'-t%s:%s'
% (self.id, window_index),
)
if window_shell:
window_args += (window_shell,)
proc = self.cmd('new-window', *window_args)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
window = proc.stdout[0]
window = dict(zip(wformats, window.split('\t')))
# clear up empty dict
window = dict((k, v) for k, v in window.items() if v)
window = Window(session=self, **window)
self.server._update_windows()
return window
def kill_window(self, target_window=None):
"""Close a tmux window, and all panes inside it, ``$ tmux kill-window``
Kill the current window or the window at ``target-window``. removing it
from any sessions to which it is linked.
Parameters
----------
target_window : str, optional
window to kill
"""
if target_window:
if isinstance(target_window, int):
target = '-t%s:%d' % (self.name, target_window)
else:
target = '-t%s' % target_window
proc = self.cmd('kill-window', target)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
self.server._update_windows()
def _list_windows(self):
windows = self.server._update_windows()._windows
windows = [w for w in windows if w['session_id'] == self.id]
return windows
@property
def _windows(self):
"""Property / alias to return :meth:`Session._list_windows`."""
return self._list_windows()
def list_windows(self):
"""Return a list of :class:`Window` from the ``tmux(1)`` session.
Returns
-------
:class:`Window`
"""
windows = [w for w in self._windows if w['session_id'] == self._session_id]
return [Window(session=self, **window) for window in windows]
@property
def windows(self):
"""Property / alias to return :meth:`Session.list_windows`."""
return self.list_windows()
#: Alias :attr:`windows` for :class:`~libtmux.common.TmuxRelationalObject`
children = windows
@property
def attached_window(self):
"""
Return active :class:`Window` object.
Returns
-------
:class:`Window`
"""
active_windows = []
for window in self._windows:
if 'window_active' in window:
# for now window_active is a unicode
if window.get('window_active') == '1':
active_windows.append(Window(session=self, **window))
else:
continue
if len(active_windows) == int(1):
return active_windows[0]
else:
raise exc.LibTmuxException(
'multiple active windows found. %s' % active_windows
)
if len(self._windows) == int(0):
raise exc.LibTmuxException('No Windows')
def select_window(self, target_window):
"""
Return :class:`Window` selected via ``$ tmux select-window``.
Parameters
----------
window : str
``target_window`` also 'last-window' (``-l``), 'next-window'
(``-n``), or 'previous-window' (``-p``)
Returns
-------
:class:`Window`
Notes
-----
.. todo::
assure ``-l``, ``-n``, ``-p`` work.
"""
target = '-t%s' % target_window
proc = self.cmd('select-window', target)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
return self.attached_window
@property
def attached_pane(self):
"""Return active :class:`Pane` object."""
return self.attached_window.attached_pane
def set_option(self, option, value, _global=False):
"""
Set option ``$ tmux set-option <option> <value>``.
Parameters
----------
option : str
the window option. such as 'default-shell'.
value : str, int, or bool
True/False will turn in 'on' and 'off'. You can also enter 'on' or
'off' directly.
_global : bool, optional
check for option globally across all servers (-g)
Raises
------
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
Notes
-----
.. todo::
Needs tests
"""
if isinstance(value, bool) and value:
value = 'on'
elif isinstance(value, bool) and not value:
value = 'off'
tmux_args = tuple()
if _global:
tmux_args += ('-g',)
tmux_args += (option, value)
proc = self.cmd('set-option', *tmux_args)
if isinstance(proc.stderr, list) and len(proc.stderr):
handle_option_error(proc.stderr[0])
def show_options(self, option=None, _global=False):
"""
Return a dict of options for the window.
For familiarity with tmux, the option ``option`` param forwards to pick
a single option, forwarding to :meth:`Session.show_option`.
Parameters
----------
option : str, optional
name of option, e.g. 'visual-silence'. Defaults to None, which
returns all options.
_global : bool, optional
Pass ``-g`` flag for global variable (server-wide)
Returns
-------
:py:obj:`dict`
Notes
-----
Uses ``_global`` for keyword name instead of ``global`` to avoid
colliding with reserved keyword.
"""
tmux_args = tuple()
if _global:
tmux_args += ('-g',)
if option:
return self.show_option(option, _global=_global)
else:
tmux_args += ('show-options',)
session_options = self.cmd(*tmux_args).stdout
session_options = [tuple(item.split(' ')) for item in session_options]
session_options = dict(session_options)
for key, value in session_options.items():
if value.isdigit():
session_options[key] = int(value)
return session_options
def show_option(self, option, _global=False):
"""Return a list of options for the window.
Parameters
----------
option : str
option name
_global : bool, optional
use global option scope, same as ``-g``
Returns
-------
str, int, or bool
Raises
------
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
Notes
-----
Uses ``_global`` for keyword name instead of ``global`` to avoid
colliding with reserved keyword.
Test and return True/False for on/off string.
"""
tmux_args = tuple()
if _global:
tmux_args += ('-g',)
tmux_args += (option,)
cmd = self.cmd('show-options', *tmux_args)
if isinstance(cmd.stderr, list) and len(cmd.stderr):
handle_option_error(cmd.stderr[0])
if not len(cmd.stdout):
return None
option = [item.split(' ') for item in cmd.stdout][0]
if option[1].isdigit():
option = (option[0], int(option[1]))
return option[1]
def __repr__(self):
return "%s(%s %s)" % (self.__class__.__name__, self.id, self.name)