├── docs ├── img │ └── example.png └── examples │ └── example1.gdbinit ├── splitmind ├── __init__.py ├── models.py ├── thinker │ └── pwndbg.py ├── mind.py └── splitter │ └── tmux.py ├── gdbinit.py ├── LICENSE ├── .gitignore └── README.md /docs/img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerdna-regeiz/splitmind/HEAD/docs/img/example.png -------------------------------------------------------------------------------- /splitmind/__init__.py: -------------------------------------------------------------------------------- 1 | from .thinker.pwndbg import Pwndbg 2 | from .splitter.tmux import Tmux 3 | from .mind import Mind 4 | -------------------------------------------------------------------------------- /splitmind/models.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from collections import namedtuple 3 | 4 | 5 | class Split(ABC, namedtuple('_Split', ['id','tty', 'display', 'settings'])): 6 | """Represents a split capable of displaying information. 7 | Must be copyable without sideeffects""" 8 | @abstractmethod 9 | def size(self): 10 | pass 11 | -------------------------------------------------------------------------------- /gdbinit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import sys 7 | from os import path 8 | 9 | 10 | directory, file = path.split(__file__) 11 | directory = path.expanduser(directory) 12 | directory = path.abspath(directory) 13 | 14 | sys.path.append(directory) 15 | 16 | import splitmind # isort:skip 17 | -------------------------------------------------------------------------------- /docs/examples/example1.gdbinit: -------------------------------------------------------------------------------- 1 | source /home/user/pwndbg/gdbinit.py 2 | set context-clear-screen on 3 | set follow-fork-mode parent 4 | 5 | source /home/user/splitmind/gdbinit.py 6 | python 7 | import splitmind 8 | (splitmind.Mind() 9 | .tell_splitter(show_titles=True) 10 | .tell_splitter(set_title="Main") 11 | .right(display="backtrace", size="25%") 12 | .above(of="main", display="disasm", size="80%", banner="top") 13 | .show("code", on="disasm", banner="none") 14 | .right(cmd='tty; tail -f /dev/null', size="65%", clearing=False) 15 | .tell_splitter(set_title='Input / Output') 16 | .above(display="stack", size="75%") 17 | .above(display="legend", size="25") 18 | .show("regs", on="legend") 19 | .below(of="backtrace", cmd="ipython", size="30%") 20 | ).build(nobanner=True) 21 | end 22 | set context-code-lines 30 23 | set context-source-code-lines 30 24 | set context-sections "regs args code disasm stack backtrace" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrej Zieger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /splitmind/thinker/pwndbg.py: -------------------------------------------------------------------------------- 1 | import copy 2 | try: 3 | import pwndbg 4 | from pwndbg.commands.context import contextoutput, output, clear_screen 5 | except ImportError as err: 6 | # Most likely not run from gdb but something else.. 7 | pass 8 | 9 | class Pwndbg(): 10 | def banners(self, splits): 11 | panes = splits 12 | for tty in set(pane.tty for pane in panes if pane.settings.get("clearing", True)): 13 | with open(tty,"w") as out: 14 | clear_screen(out) 15 | for pane in [p for p in panes if p.display is not None]: 16 | sec = pane.display 17 | size = pane.size() 18 | with open(pane.tty,"w") as out: 19 | b = pwndbg.ui.banner(sec, target=out, width=size[0]) 20 | out.write(b+"\n") 21 | out.flush() 22 | 23 | def setup(self, splits, nobanner=None): 24 | """Sets up pwndbg to display sections in the given splits using display == section""" 25 | if nobanner is not None: 26 | for split in splits: 27 | if "banner" not in split.settings: 28 | split.settings["banner"] = False 29 | for split in [s for s in splits if s.display is not None and s.tty is not None]: 30 | contextoutput(split.display, split.tty, True, **split.settings) 31 | self.banners(s for s in splits if s.tty is not None) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /splitmind/mind.py: -------------------------------------------------------------------------------- 1 | from .splitter.tmux import Tmux 2 | from .thinker.pwndbg import Pwndbg 3 | 4 | 5 | class Mind(): 6 | """A builder to create a splitmind. 7 | It splits always on the last created split if no 'of' is given or an other split is selected. 8 | To split the original starting point use select(None) or use an 'of' which is not defined yet. 9 | Further kwargs are always passed as is the the underlying splitter to be able to have splitter 10 | specific additional functionality. Parameters not consumed by the splitter are passed as split 11 | settings to the thinker 12 | """ 13 | def __init__(self, splitter=Tmux, thinker=Pwndbg): 14 | if callable(splitter): 15 | splitter = splitter() 16 | if callable(thinker): 17 | thinker = thinker() 18 | self.splitter = splitter 19 | self.thinker = thinker 20 | self.last = None 21 | 22 | 23 | def left (self, *args, of=None, display=None, **kwargs): 24 | """Creates a split left of the current split. 25 | :param str|split of : use this split instead of current 26 | :param str display : the section to be displayed here 27 | :param various args : further args are passed to the splitting cmd 28 | :param dict kwargs : further keyword args are passed to the splitter method""" 29 | self.last = self.splitter.left(*args, of=of or self.last, display=display, **kwargs) 30 | return self 31 | def right(self, *args, of=None, display=None, **kwargs): 32 | """Creates a split right of the current split. 33 | :param str|split of : use this split instead of current 34 | :param str display : the section to be displayed here 35 | :param various args : further args are passed to the splitting cmd 36 | :param dict kwargs : further keyword args are passed to the splitter method""" 37 | self.last = self.splitter.right(*args, of=of or self.last, display=display, **kwargs) 38 | return self 39 | def above(self, *args, of=None, display=None, **kwargs): 40 | """Creates a split above of the current split. 41 | :param str|split of : use this split instead of current 42 | :param str display : the section to be displayed here 43 | :param various args : further args are passed to the splitting cmd 44 | :param dict kwargs : further keyword args are passed to the splitter method""" 45 | self.last = self.splitter.above(*args, of=of or self.last, display=display, **kwargs) 46 | return self 47 | def below(self, *args, of=None, display=None, **kwargs): 48 | """Creates a split below of the current split. 49 | :param str|split of : use this split instead of current 50 | :param str display : the section to be displayed here 51 | :param various args : further args are passed to the splitting cmd 52 | :param dict kwargs : further keyword args are passed to the splitter method""" 53 | self.last = self.splitter.below(*args, of=of or self.last, display=display, **kwargs) 54 | return self 55 | def show(self, display, on=None, **kwargs): 56 | """Does not create a split but tells to display given section on some already created split. 57 | :param str|split on : which split to be used 58 | :param str display : the section to be displayed here 59 | :param dict kwargs : further keyword args are passed to the splitter method""" 60 | self.last = self.splitter.show(on=on or self.last, display=display, **kwargs) 61 | return self 62 | def select(self, display): 63 | """Selects the given display to continue from there. 64 | Use None for the main split""" 65 | if display is None: 66 | self.last = None 67 | else: 68 | self.last = self.splitter.get(display) 69 | return self 70 | 71 | def tell_splitter(self, target=None, **kwargs): 72 | """Tells the splitter to configure according to the passed keyword arguments. 73 | Which arguments are available and what happens entirely depends on the implementation of the 74 | splitter""" 75 | if target is None: 76 | target = self.last 77 | self.splitter.do(target=target, **kwargs) 78 | return self 79 | 80 | def build(self, **kwargs): 81 | """Builds the splitmind, by telling the thinker where to put his thoughts 82 | :param dict kwagrs : passed to thinker setup to applie thinker specific value 83 | """ 84 | self.splitter.finish(**kwargs) 85 | self.thinker.setup(self.splitter.splits(), **kwargs) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # splitmind 2 | 3 | `splitmind` helps to setup a layout of splits to organize presented information. 4 | 5 | Currently only `gdb` with `pwndbg` as information provider is supported and `tmux` for splitting. 6 | It relies on the ability to ouput section of information to different tty. 7 | 8 | 9 | ![Example Image](docs/img/example.png) 10 | *[Example configuration](docs/examples/example1.gdbinit)* 11 | 12 | Note above example uses splitmind and following configuration: 13 | 14 | ## Install 15 | 16 | ```shell 17 | git clone https://github.com/jerdna-regeiz/splitmind 18 | echo "source $PWD/splitmind/gdbinit.py" >> ~/.gdbinit 19 | ``` 20 | 21 | It is not showing anything yet. You have to configure your layout yourself. 22 | As as start, put this into your gdbinit 23 | 24 | ```python 25 | python 26 | import splitmind 27 | (splitmind.Mind() 28 | .below(display="backtrace") 29 | .right(display="stack") 30 | .right(display="regs") 31 | .right(of="main", display="disasm") 32 | .show("legend", on="disasm") 33 | ).build() 34 | end 35 | ``` 36 | 37 | ## Documentation 38 | 39 | Currently splitmind can only be used with Tmux and Pwndbg, but it is designed to be able to include 40 | furthe input and output. 41 | 42 | Conceptually there are two abstractions working together: 43 | * **Splitter**, which setup the actual splits and provide the neccesary output files (tty, files, 44 | sockets,...) 45 | * **Thinker** that generate content to be handed to the output, which must be made aware of the 46 | splits (or rather the tty, files, sockets, ...) 47 | 48 | A third is used as glue: the **Mind**, which works as an easy interface to connect a splitter and a 49 | thinker. It works as a builder, creating the splits using the splitter and when finished handing the 50 | generated splits to the thinker. The **Mind** is most likely the only interface you need. 51 | 52 | 53 | ### Mind 54 | ```python 55 | Mind(self, splitter=, thinker=) 56 | ``` 57 | A builder to create a splitmind. 58 | It splits always on the last created split if no 'of' is given or an other split is selected. 59 | To split the original starting point use select(None) or use an 'of' which is not defined yet. 60 | Further kwargs are always passed as is the the underlying splitter to be able to have splitter 61 | specific additional functionality. Parameters not consumed by the splitter are passed as split 62 | settings to the thinker 63 | 64 | #### left 65 | ```python 66 | Mind.left(self, *args, of=None, display=None, **kwargs) 67 | ``` 68 | Creates a split left of the current split. 69 | :param str|split of : use this split instead of current 70 | :param str display : the section to be displayed here 71 | :param various args : further args are passed to the splitting cmd 72 | :param dict kwargs : further keyword args are passed to the splitter method 73 | #### right 74 | ```python 75 | Mind.right(self, *args, of=None, display=None, **kwargs) 76 | ``` 77 | Creates a split right of the current split. 78 | :param str|split of : use this split instead of current 79 | :param str display : the section to be displayed here 80 | :param various args : further args are passed to the splitting cmd 81 | :param dict kwargs : further keyword args are passed to the splitter method 82 | #### above 83 | ```python 84 | Mind.above(self, *args, of=None, display=None, **kwargs) 85 | ``` 86 | Creates a split above of the current split. 87 | :param str|split of : use this split instead of current 88 | :param str display : the section to be displayed here 89 | :param various args : further args are passed to the splitting cmd 90 | :param dict kwargs : further keyword args are passed to the splitter method 91 | #### below 92 | ```python 93 | Mind.below(self, *args, of=None, display=None, **kwargs) 94 | ``` 95 | Creates a split below of the current split. 96 | :param str|split of : use this split instead of current 97 | :param str display : the section to be displayed here 98 | :param various args : further args are passed to the splitting cmd 99 | :param dict kwargs : further keyword args are passed to the splitter method 100 | #### show 101 | ```python 102 | Mind.show(self, display, on=None, **kwargs) 103 | ``` 104 | Does not create a split but tells to display given section on some already created split. 105 | :param str|split on : which split to be used 106 | :param str display : the section to be displayed here 107 | :param dict kwargs : further keyword args are passed to the splitter method 108 | #### select 109 | ```python 110 | Mind.select(self, display) 111 | ``` 112 | Selects the given display to continue from there. 113 | Use None for the main split 114 | #### tell_splitter 115 | ```python 116 | Mind.tell_splitter(self, **kwargs) 117 | ``` 118 | Tells the splitter to configure according to the passed keyword arguments. 119 | Which arguments are available and what happens entirely depends on the implementation of the 120 | splitter 121 | #### build 122 | ```python 123 | Mind.build(self, **kwargs) 124 | ``` 125 | Builds the splitmind, by telling the thinker where to put his thoughts 126 | :param dict kwagrs : passed to thinker setup to applie thinker specific value 127 | 128 | 129 | ## TMUX 130 | 131 | Tmux does handle the splits using `split-window`. Further `*args` are directly passed to the tmux 132 | call. Tmux supports following additional and optional keywords: 133 | - `cmd : str`: The command to run in the created split 134 | - `use_stdin : boolean`: sets up the split to be able to receive content as stdin to the given cmd 135 | - `size : str`: gives a size to the new split (as lines or as percentage) 136 | 137 | Splits can be created without display to start running arbitrary commands aswell. 138 | 139 | Example: 140 | 141 | ```python 142 | python 143 | import splitmind 144 | (splitmind.Mind() 145 | .below(display="backtrace") 146 | .right(display="stack", cmd="grep rax", use_stdin=True) 147 | .right(display="regs") 148 | .below(cmd='sleep 1; htop') 149 | .below(of="stack", cmd='sleep 1; watch ls') 150 | .right(of="main", display="disasm") 151 | .show("legend", on="disasm") 152 | ).build() 153 | end 154 | ``` 155 | 156 | ## Pwndbg 157 | 158 | Currently Pwndbg is the only thinker / content producer available. 159 | It uses the `contextoutput` function to bind context sections to splits with the matching display 160 | name. 161 | 162 | All `split.settings` (keyword arguments not used by the splitter i.e. tmux) are passed as keyword 163 | arguments to `contextoutput` 164 | 165 | With the `build` one can specify following options: 166 | * **nobanner** boolean: Banners of all configured outputs will be hidden. Same effect as specifying 167 | banner=False on every split. 168 | 169 | ## Creating new splitter 170 | 171 | You like screen? Please go ahead and create a splitter for it (and please submit a pullrequest). 172 | 173 | Writing a new splitter is easy, just take a look at `splitmind.splitter.tmux`. 174 | It just takes `left/right/above/below()`, as well as `show()`,`get()`, `splits()` and `close()` to 175 | be implemented. (ABC class will be comming soon) 176 | 177 | ## Creating a new thinker 178 | 179 | You don't use pwndbg, but have an other case where a splitted layout with automatic tty setup comes 180 | in handy? Yeah! Please look at `splitmind.thnker.pwndbg`, it is even simpler than splitters are, as 181 | they only require a `setup(splits)` method which will then do all the initialization of the content 182 | creation process/programm. 183 | 184 | ## FAQ 185 | 186 | * **How do I create a split containing the input/output of the program debugged by gdb?** 187 | ```python 188 | (splitmind.Mind() 189 | .above(cmd='tty; tail -f /dev/null', clearing=False) 190 | ).build() 191 | ``` 192 | Creating a pane which (important) does not clear, shows the used tty and then just reads /dev/null. 193 | Tailing /dev/null is important, so that the tty is not bothered at all by the running process. 194 | 195 | Then in `gdb` issue `tty /dev/pts/` with the shown tty. This will use the newly created 196 | pane as input/output of the debugged process. Just ignore the warning. 197 | -------------------------------------------------------------------------------- /splitmind/splitter/tmux.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import os 3 | import time 4 | from subprocess import check_output, CalledProcessError 5 | 6 | from splitmind.models import Split 7 | 8 | class TmuxSplit(Split): 9 | def size(self): 10 | return tmux_pane_size(self) 11 | 12 | def read_tmux_output(res, delimiter=':'): 13 | try: 14 | res = res.decode("utf-8") 15 | except: 16 | pass 17 | return res.strip().split(delimiter) 18 | 19 | def tmux_split(*args, target=None, display=None, cmd="/bin/cat -", use_stdin=False, size=None, 20 | **kwargs): 21 | """ 22 | Parameters 23 | ---------- 24 | use_stdin : boolean 25 | If set to true, it will output to the stdin of the given command. 26 | But it is not as easy as you might think: Most of the time one would get a tty as proc/fd/* 27 | which is rather unsupportive to write to (hint: it won't go to stdin of the process). 28 | Therefore the command will be prepended with (cat)| wo have an other with a pipe as output 29 | to which we may write 30 | size : the size of the new split 31 | pecify the size of the new pane in lines (for vertical split) or in cells 32 | (for horizontal split), or as a percentage if ending with % 33 | """ 34 | args = list(args) 35 | if target is not None: 36 | args += ["-t", target.id] 37 | if size is not None: 38 | args += ["-p", size[:-1]] if size.endswith("%") else ["-l", size] 39 | fd = "#{pane_tty}" if not use_stdin else "/proc/#{pane_pid}/fd/0" 40 | if use_stdin: 41 | cmd = "(cat)|"+cmd 42 | res = check_output('tmux split-window -P -d -F'.split(" ") 43 | + ["#{pane_id}:"+fd] + list(args)+ [cmd]) 44 | return TmuxSplit(*read_tmux_output(res), display, kwargs) 45 | 46 | def tmux_kill(paneid): 47 | try: 48 | check_output(['tmux','kill-pane','-t',paneid]) 49 | except CalledProcessError as err: 50 | print(err) 51 | 52 | def tmux_pane_size(pane): 53 | res = check_output(['tmux','display','-p','-F', '#{pane_width}:#{pane_height}','-t',pane.id]) 54 | return [int(x) for x in read_tmux_output(res)] 55 | 56 | 57 | def close_panes(panes): 58 | for pane in set(pane.id for pane in panes): 59 | tmux_kill(pane) 60 | 61 | def tmux_pane_border_status(value): 62 | check_output(['tmux','set' ,'pane-border-status', value]) 63 | 64 | def tmux_pane_title(pane, title): 65 | if pane is None: 66 | check_output(['tmux','select-pane','-T',title]) 67 | else: 68 | check_output(['tmux','select-pane','-T',title,'-t',pane.id]) 69 | 70 | def tmux_window_options(): 71 | return read_tmux_output(check_output(['tmux', 'show-options', '-w']), delimiter="\n") 72 | 73 | class DummyTmux(): 74 | def __new__(cls, *_args, **kwargs): 75 | print("Error: Splitmind-Tmux can only be used when running under tmux") 76 | return super(DummyTmux, cls).__new__(cls) 77 | 78 | def __init__(self, *_args, **kwargs): 79 | print("Error: Splitmind-Tmux can only be used when running under tmux") 80 | 81 | def __getattr__(self, name): 82 | return lambda *_, **_kw: None if name in [k for k,x in Tmux.__dict__.items() if callable(x)] else None 83 | 84 | def splits(self): 85 | return [] 86 | 87 | class Tmux(): 88 | def __new__(cls, *_args, **_kwargs): 89 | if not "TMUX_PANE" in os.environ: 90 | return DummyTmux.__new__(DummyTmux) 91 | return super(Tmux, cls).__new__(cls) 92 | 93 | def __init__(self, cmd="/bin/cat -"): 94 | self.cmd = cmd 95 | self.panes = [TmuxSplit(os.environ["TMUX_PANE"], None, "main", {}) ] 96 | self._saved_tmux_options = tmux_window_options() 97 | if not [o for o in self._saved_tmux_options if o.startswith("pane-border-status")]: 98 | self._saved_tmux_options.append("pane-border-status off") 99 | atexit.register(self.close) 100 | 101 | def get(self, display): 102 | """Gets a split by the name of the display, or None if name is not found. 103 | If multiple panes got the same display name, it will return the first""" 104 | if isinstance(display, Split): 105 | return display 106 | try: 107 | return [p for p in self.panes if p.display == display][0] 108 | except IndexError: 109 | return None 110 | 111 | def show(self, display, on=None, **kwargs): 112 | """Tells to display on an other split as well""" 113 | if isinstance(on, str): 114 | on = self.get(on) 115 | split = on._replace(display=display, settings=on.settings.copy()) 116 | split.settings.update(kwargs) 117 | self.panes.append(split) 118 | if display: 119 | tmux_pane_title(split, 120 | ", ".join([sp.display for sp in self.panes if sp.tty == split.tty])) 121 | return split 122 | 123 | def split(self, *args, target=None, display=None, cmd=None, use_stdin=None, **kwargs): 124 | """ 125 | Splits the tmux pane and associates the cmd & display with the new pane 126 | Parameters 127 | ---------- 128 | use_stdin : boolean 129 | If set to true, it will output to the stdin of the given command. 130 | But it is not as easy as you might think: Most of the time one would get a tty as proc/fd/* 131 | which is rather unsupportive to write to (hint: it won't go to stdin of the process). 132 | Therefore the command will be prepended with (cat)| wo have an other with a pipe as output 133 | to which we may write 134 | """ 135 | if isinstance(target, str): 136 | target = self.get(target) 137 | split = tmux_split(*args, target=target, display=display, cmd=cmd or self.cmd, 138 | use_stdin=use_stdin, **kwargs) 139 | if display: 140 | tmux_pane_title(split, display) 141 | self.panes.append(split) 142 | return split 143 | 144 | def left (self, *args, of=None, display=None, **kwargs): 145 | return self.split("-hb", *args, target=of, display=display, **kwargs) 146 | def right(self, *args, of=None, display=None, **kwargs): 147 | return self.split("-h", *args, target=of, display=display, **kwargs) 148 | def above(self, *args, of=None, display=None, **kwargs): 149 | return self.split("-vb", *args, target=of, display=display, **kwargs) 150 | def below(self, *args, of=None, display=None, **kwargs): 151 | return self.split("-v", *args, target=of, display=display, **kwargs) 152 | def splits(self): 153 | return self.panes 154 | def close(self): 155 | close_panes(self.panes[1:]) 156 | #restore options 157 | for option in [o for o in self._saved_tmux_options if o]: 158 | check_output(["tmux", "set"] + option.split(" ")) 159 | 160 | def finish(self, **kwargs): 161 | """Finishes the splitting.""" 162 | # tmux <2.6 does select the target pane. Later versions stay with the current. 163 | # To have consistent behaviour, we select the main pane after splitting 164 | check_output(['tmux', 'select-pane', "-t", os.environ["TMUX_PANE"]]) 165 | 166 | 167 | def do(self, show_titles=None, set_title=None, target=None): 168 | """Tells tmux to do something. This is called by tell_splitter in Mind 169 | All actions are only done if according parameter is not None 170 | Parameters 171 | ---------- 172 | show_titles : boolean|str 173 | If set to true or top, it will display pane titles in the top border 174 | If set to bottom, it will display pane titles in the bottom border 175 | If set to false, it will hide the titles in the border 176 | set_title : string 177 | Sets the title of a given target or if target is None of the current split 178 | target : string|split 179 | The target of actions. Either a string with display name or a ready split or None for 180 | the current split 181 | """ 182 | if show_titles is not None: 183 | tmux_pane_border_status({"bottom":"bottom", False:"off"}.get(show_titles, "top")) 184 | if set_title is not None: 185 | tmux_pane_title(self.get(target), set_title) 186 | 187 | 188 | --------------------------------------------------------------------------------