├── docs ├── _static │ ├── .gitignore │ └── i3-instant-layout_demo.gif ├── authors.rst ├── changelog.rst ├── license.rst ├── index.rst ├── Makefile └── conf.py ├── src └── i3_instant_layout │ ├── __init__.py │ ├── main.py │ └── layouts.py ├── AUTHORS.rst ├── CHANGELOG.md ├── tox.ini ├── tests └── conftest.py ├── setup.py ├── .coveragerc ├── requirements.txt ├── .gitignore ├── LICENSE.txt ├── .github └── workflows │ └── release.yml ├── setup.cfg └── README.md /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # Empty directory 2 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. _authors: 2 | .. include:: ../AUTHORS.rst 3 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changes: 2 | .. include:: ../CHANGELOG.rst 3 | -------------------------------------------------------------------------------- /src/i3_instant_layout/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = "0.1.13" 3 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | ======= 4 | License 5 | ======= 6 | 7 | .. include:: ../LICENSE.txt 8 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributors 3 | ============ 4 | 5 | * Florian Finkernagel 6 | -------------------------------------------------------------------------------- /docs/_static/i3-instant-layout_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TyberiusPrime/i3-instant-layout/HEAD/docs/_static/i3-instant-layout_demo.gif -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.15 4 | 5 | * added layout MainCenter, vmv 6 | 7 | ## Version 0.14 8 | 9 | initial public release 10 | 11 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = tests/run/*, docs/* 3 | max-line-length = 88 4 | max-complexity = 21 5 | ignore = E501,W504,W503,E402,E203,E713 6 | select = C,E,F,W,B,B901 7 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Dummy conftest.py for i3_autolayout. 4 | 5 | If you don't know what this is for, just leave it empty. 6 | Read more about conftest.py under: 7 | https://pytest.org/latest/plugins.html 8 | """ 9 | 10 | # import pytest 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Setup file for i3-instant-layout. 4 | Use setup.cfg to configure your project. 5 | 6 | This file was generated with PyScaffold 3.2.3. 7 | PyScaffold helps you to put up the scaffold of your new Python project. 8 | Learn more under: https://pyscaffold.org/ 9 | """ 10 | import sys 11 | 12 | from setuptools import setup 13 | 14 | if __name__ == "__main__": 15 | setup() 16 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | source = i3_autolayout 5 | # omit = bad_file.py 6 | 7 | [paths] 8 | source = 9 | src/ 10 | */site-packages/ 11 | 12 | [report] 13 | # Regexes for lines to exclude from consideration 14 | exclude_lines = 15 | # Have to re-enable the standard pragma 16 | pragma: no cover 17 | 18 | # Don't complain about missing debug-only code: 19 | def __repr__ 20 | if self\.debug 21 | 22 | # Don't complain if tests don't hit defensive assertion code: 23 | raise AssertionError 24 | raise NotImplementedError 25 | 26 | # Don't complain if non-runnable code isn't run: 27 | if 0: 28 | if __name__ == .__main__.: 29 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # DEPRECATION WARNING: 3 | # 4 | # The file `requirements.txt` does not influence the package dependencies and 5 | # will not be automatically created in the next version of PyScaffold (v4.x). 6 | # 7 | # Please have look at the docs for better alternatives 8 | # (`Dependency Management` section). 9 | # ============================================================================= 10 | # 11 | # Add your pinned requirements so that they can be easily installed with: 12 | # pip install -r requirements.txt 13 | # Remember to also add them in setup.cfg but unpinned. 14 | # Example: 15 | # numpy==1.13.3 16 | # scipy==1.0 17 | # 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | !.isort.cfg 7 | !setup.cfg 8 | *.orig 9 | *.log 10 | *.pot 11 | __pycache__/* 12 | .cache/* 13 | .*.swp 14 | */.ipynb_checkpoints/* 15 | .DS_Store 16 | 17 | # Project files 18 | .ropeproject 19 | .project 20 | .pydevproject 21 | .settings 22 | .idea 23 | tags 24 | 25 | # Package files 26 | *.egg 27 | *.eggs/ 28 | .installed.cfg 29 | *.egg-info 30 | 31 | # Unittest and coverage 32 | htmlcov/* 33 | .coverage 34 | .tox 35 | junit.xml 36 | coverage.xml 37 | .pytest_cache/ 38 | 39 | # Build and docs folder/files 40 | build/* 41 | dist/* 42 | sdist/* 43 | docs/api/* 44 | docs/_rst/* 45 | docs/_build/* 46 | cover/* 47 | MANIFEST 48 | 49 | # Per-project virtualenvs 50 | .venv*/ 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Florian Finkernagel 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 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: pytest 2 | on: 3 | push: 4 | release: 5 | types: 6 | - published 7 | 8 | 9 | jobs: 10 | build_sdist: 11 | name: Build source distribution 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - uses: actions/setup-python@v2 17 | name: Install Python 18 | with: 19 | python-version: '3.12' 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install setuptools 24 | 25 | - name: Build sdist 26 | run: python setup.py sdist 27 | 28 | - uses: actions/upload-artifact@v4 29 | with: 30 | path: dist/*.tar.gz 31 | 32 | 33 | upload_pypi: 34 | needs: [build_sdist] 35 | runs-on: ubuntu-latest 36 | # upload to PyPI on every tag starting with 'v' 37 | #if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') 38 | # alternatively, to publish when a GitHub Release is created, use the following rule: 39 | if: github.event_name == 'release' && github.event.action == 'published' 40 | steps: 41 | - uses: actions/download-artifact@v4 42 | with: 43 | name: artifact 44 | path: dist 45 | 46 | - uses: pypa/gh-action-pypi-publish@master 47 | with: 48 | user: __token__ 49 | password: ${{ secrets.pypi_password }} 50 | # To test: repository_url: https://test.pypi.org/legacy/ 51 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | i3-instant-layout 3 | ============= 4 | 5 | This is the documentation of **i3-instant-layout**. 6 | 7 | .. note:: 8 | 9 | This is the main page of your project's `Sphinx`_ documentation. 10 | It is formatted in `reStructuredText`_. Add additional pages 11 | by creating rst-files in ``docs`` and adding them to the `toctree`_ below. 12 | Use then `references`_ in order to link them from this page, e.g. 13 | :ref:`authors` and :ref:`changes`. 14 | 15 | It is also possible to refer to the documentation of other Python packages 16 | with the `Python domain syntax`_. By default you can reference the 17 | documentation of `Sphinx`_, `Python`_, `NumPy`_, `SciPy`_, `matplotlib`_, 18 | `Pandas`_, `Scikit-Learn`_. You can add more by extending the 19 | ``intersphinx_mapping`` in your Sphinx's ``conf.py``. 20 | 21 | The pretty useful extension `autodoc`_ is activated by default and lets 22 | you include documentation from docstrings. Docstrings can be written in 23 | `Google style`_ (recommended!), `NumPy style`_ and `classical style`_. 24 | 25 | 26 | Contents 27 | ======== 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | License 33 | Authors 34 | Changelog 35 | Module Reference 36 | 37 | 38 | Indices and tables 39 | ================== 40 | 41 | * :ref:`genindex` 42 | * :ref:`modindex` 43 | * :ref:`search` 44 | 45 | .. _toctree: http://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html 46 | .. _reStructuredText: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html 47 | .. _references: http://www.sphinx-doc.org/en/stable/markup/inline.html 48 | .. _Python domain syntax: http://sphinx-doc.org/domains.html#the-python-domain 49 | .. _Sphinx: http://www.sphinx-doc.org/ 50 | .. _Python: http://docs.python.org/ 51 | .. _Numpy: http://docs.scipy.org/doc/numpy 52 | .. _SciPy: http://docs.scipy.org/doc/scipy/reference/ 53 | .. _matplotlib: https://matplotlib.org/contents.html# 54 | .. _Pandas: http://pandas.pydata.org/pandas-docs/stable 55 | .. _Scikit-Learn: http://scikit-learn.org/stable 56 | .. _autodoc: http://www.sphinx-doc.org/en/stable/ext/autodoc.html 57 | .. _Google style: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings 58 | .. _NumPy style: https://numpydoc.readthedocs.io/en/latest/format.html 59 | .. _classical style: http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists 60 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is used to configure your project. 2 | # Read more about the various options under: 3 | # http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files 4 | 5 | [metadata] 6 | name = i3-instant-layout 7 | description = Automatic 'list based' layouts for the i3 window manager 8 | author = Florian Finkernagel 9 | author-email = tyberius_prime@coonabibba.de 10 | version = 0.1.13 11 | license = mit 12 | long-description = file: README.md 13 | long-description-content-type = text/markdown; charset=UTF-8 14 | url = https://github.com/TyberiusPrime/i3-instant-layout 15 | #project-urls = 16 | #Documentation = https://pyscaffold.org/ 17 | # Change if running only on Windows, Mac or Linux (comma-separated) 18 | platforms = Linux 19 | # Add here all kinds of additional classifiers as defined under 20 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 21 | classifiers = 22 | Development Status :: 4 - Beta 23 | Programming Language :: Python 24 | 25 | [options] 26 | zip_safe = False 27 | packages = find: 28 | include_package_data = True 29 | package_dir = 30 | =src 31 | # DON'T CHANGE THE FOLLOWING LINE! IT WILL BE UPDATED BY PYSCAFFOLD! 32 | # setup_requires = pyscaffold>=3.2a0,<3.3a0 33 | # Add here dependencies of your project (semicolon/line-separated), e.g. 34 | install_requires = 35 | i3ipc>=2.2.1 36 | # The usage of test_requires is discouraged, see `Dependency Management` docs 37 | # tests_require = pytest; pytest-cov 38 | # Require a specific Python version, e.g. Python 2.7 or >= 3.4 39 | python_requires = >=3.6 40 | 41 | [options.packages.find] 42 | where = src 43 | exclude = 44 | tests 45 | 46 | [options.extras_require] 47 | # Add here additional requirements for extra features, to install with: 48 | # `pip install i3-instant-layout[PDF]` like: 49 | # PDF = ReportLab; RXP 50 | # Add here test requirements (semicolon/line-separated) 51 | testing = 52 | pytest 53 | pytest-cov 54 | 55 | [options.entry_points] 56 | # Add here console scripts like: 57 | console_scripts = 58 | i3-instant-layout = i3_instant_layout.main:main 59 | # For example: 60 | # console_scripts = 61 | # fibonacci = i3_instant_layout.skeleton:run 62 | # And any other entry points, for example: 63 | # pyscaffold.cli = 64 | # awesome = pyscaffoldext.awesome.extension:AwesomeExtension 65 | 66 | [test] 67 | # py.test options when running `python setup.py test` 68 | # addopts = --verbose 69 | # extras = True 70 | 71 | [tool:pytest] 72 | # Options for py.test: 73 | # Specify command line options as you would do when invoking py.test directly. 74 | # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml 75 | # in order to write a coverage file that can be read by Jenkins. 76 | addopts = 77 | --cov i3_instant_layout --cov-report term-missing 78 | --verbose 79 | norecursedirs = 80 | dist 81 | build 82 | .tox 83 | testpaths = tests 84 | 85 | [aliases] 86 | dists = bdist_wheel 87 | 88 | [bdist_wheel] 89 | # Use this option if your package is pure-python 90 | universal = 1 91 | 92 | [build_sphinx] 93 | source_dir = docs 94 | build_dir = build/sphinx 95 | 96 | [devpi:upload] 97 | # Options for the devpi: PyPI server and packaging tool 98 | # VCS export must be deactivated since we are using setuptools-scm 99 | no-vcs = 1 100 | formats = bdist_wheel 101 | 102 | [flake8] 103 | # Some sane defaults for the code style checker flake8 104 | exclude = 105 | .tox 106 | build 107 | dist 108 | .eggs 109 | docs/conf.py 110 | 111 | # [pyscaffold] 112 | # PyScaffold's parameters when the project was created. 113 | # This will be used when updating. Do not change! 114 | # version = 3.2.3 115 | # package = i3-instant-layout 116 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = ../build/sphinx/ 9 | AUTODOCDIR = api 10 | AUTODOCBUILD = sphinx-apidoc 11 | PROJECT = i3-instant-layout 12 | MODULEDIR = ../src/i3_instant-layout 13 | 14 | # User-friendly check for sphinx-build 15 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1) 16 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 17 | endif 18 | 19 | # Internal variables. 20 | PAPEROPT_a4 = -D latex_paper_size=a4 21 | PAPEROPT_letter = -D latex_paper_size=letter 22 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 23 | # the i18n builder cannot share the environment and doctrees with the others 24 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 25 | 26 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext doc-requirements 27 | 28 | help: 29 | @echo "Please use \`make ' where is one of" 30 | @echo " html to make standalone HTML files" 31 | @echo " dirhtml to make HTML files named index.html in directories" 32 | @echo " singlehtml to make a single large HTML file" 33 | @echo " pickle to make pickle files" 34 | @echo " json to make JSON files" 35 | @echo " htmlhelp to make HTML files and a HTML help project" 36 | @echo " qthelp to make HTML files and a qthelp project" 37 | @echo " devhelp to make HTML files and a Devhelp project" 38 | @echo " epub to make an epub" 39 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 40 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 41 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 42 | @echo " text to make text files" 43 | @echo " man to make manual pages" 44 | @echo " texinfo to make Texinfo files" 45 | @echo " info to make Texinfo files and run them through makeinfo" 46 | @echo " gettext to make PO message catalogs" 47 | @echo " changes to make an overview of all changed/added/deprecated items" 48 | @echo " xml to make Docutils-native XML files" 49 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 50 | @echo " linkcheck to check all external links for integrity" 51 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 52 | 53 | clean: 54 | rm -rf $(BUILDDIR)/* $(AUTODOCDIR) 55 | 56 | $(AUTODOCDIR): $(MODULEDIR) 57 | mkdir -p $@ 58 | $(AUTODOCBUILD) -f -o $@ $^ 59 | 60 | doc-requirements: $(AUTODOCDIR) 61 | 62 | html: doc-requirements 63 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 64 | @echo 65 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 66 | 67 | dirhtml: doc-requirements 68 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 69 | @echo 70 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 71 | 72 | singlehtml: doc-requirements 73 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 74 | @echo 75 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 76 | 77 | pickle: doc-requirements 78 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 79 | @echo 80 | @echo "Build finished; now you can process the pickle files." 81 | 82 | json: doc-requirements 83 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 84 | @echo 85 | @echo "Build finished; now you can process the JSON files." 86 | 87 | htmlhelp: doc-requirements 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | qthelp: doc-requirements 94 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 95 | @echo 96 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 97 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 98 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(PROJECT).qhcp" 99 | @echo "To view the help file:" 100 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(PROJECT).qhc" 101 | 102 | devhelp: doc-requirements 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $HOME/.local/share/devhelp/$(PROJECT)" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $HOME/.local/share/devhelp/$(PROJEC)" 109 | @echo "# devhelp" 110 | 111 | epub: doc-requirements 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | patch-latex: 117 | find _build/latex -iname "*.tex" | xargs -- \ 118 | sed -i'' 's~includegraphics{~includegraphics\[keepaspectratio,max size={\\textwidth}{\\textheight}\]{~g' 119 | 120 | latex: doc-requirements 121 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 122 | $(MAKE) patch-latex 123 | @echo 124 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 125 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 126 | "(use \`make latexpdf' here to do that automatically)." 127 | 128 | latexpdf: doc-requirements 129 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 130 | $(MAKE) patch-latex 131 | @echo "Running LaTeX files through pdflatex..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | latexpdfja: doc-requirements 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through platex and dvipdfmx..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | text: doc-requirements 142 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 143 | @echo 144 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 145 | 146 | man: doc-requirements 147 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 148 | @echo 149 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 150 | 151 | texinfo: doc-requirements 152 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 153 | @echo 154 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 155 | @echo "Run \`make' in that directory to run these through makeinfo" \ 156 | "(use \`make info' here to do that automatically)." 157 | 158 | info: doc-requirements 159 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 160 | @echo "Running Texinfo files through makeinfo..." 161 | make -C $(BUILDDIR)/texinfo info 162 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 163 | 164 | gettext: doc-requirements 165 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 166 | @echo 167 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 168 | 169 | changes: doc-requirements 170 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 171 | @echo 172 | @echo "The overview file is in $(BUILDDIR)/changes." 173 | 174 | linkcheck: doc-requirements 175 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 176 | @echo 177 | @echo "Link check complete; look for any errors in the above output " \ 178 | "or in $(BUILDDIR)/linkcheck/output.txt." 179 | 180 | doctest: doc-requirements 181 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 182 | @echo "Testing of doctests in the sources finished, look at the " \ 183 | "results in $(BUILDDIR)/doctest/output.txt." 184 | 185 | xml: doc-requirements 186 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 187 | @echo 188 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 189 | 190 | pseudoxml: doc-requirements 191 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 192 | @echo 193 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 194 | -------------------------------------------------------------------------------- /src/i3_instant_layout/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import i3ipc 3 | import datetime 4 | import json 5 | import math 6 | import subprocess 7 | import sys 8 | import tempfile 9 | from pathlib import Path 10 | from . import layouts, __version__ 11 | 12 | 13 | counter_file = Path("~/.local/share/i3-instant-layout/counter.json").expanduser() 14 | counter_file.parent.mkdir(exist_ok=True, parents=True) 15 | 16 | 17 | def append_layout(layout_dict, window_count): 18 | """Apply a layout from this layout class""" 19 | tf = tempfile.NamedTemporaryFile(suffix=".json") 20 | tf.write(json.dumps(layout_dict, indent=4).encode("utf-8")) 21 | tf.flush() 22 | cmd = ["i3-msg", "append_layout", str(Path(tf.name).absolute())] 23 | subprocess.check_call(cmd, stdout=subprocess.PIPE) 24 | tf.close() 25 | 26 | 27 | def nuke_swallow_windows(): 28 | """Remove swallow windows before changing layout""" 29 | to_nuke = set() 30 | 31 | def walk_tree(con): 32 | if con.ipc_data.get("swallows", False): 33 | to_nuke.add(con.ipc_data["window"]) 34 | for d in con.descendants(): 35 | walk_tree(d) 36 | 37 | i3 = i3ipc.Connection() 38 | tree = i3.get_tree().find_focused().workspace() 39 | walk_tree(tree) 40 | for window_id in to_nuke: 41 | subprocess.check_call(["xdotool", "windowclose", str(window_id)]) 42 | 43 | 44 | def get_window_ids(): 45 | """use xprop to list windows on current screen. 46 | 47 | Couldn't find out how to get the right ids from i3ipc. 48 | id is con_id, but we need x11 id 49 | 50 | Sorry, this probably means this won't work on wayland & sway. 51 | """ 52 | 53 | desktop = subprocess.check_output( 54 | ["xprop", "-notype", "-root", "_NET_CURRENT_DESKTOP"] 55 | ).decode("utf-8", errors="replace") 56 | desktop = desktop[desktop.rfind("=") + 2 :].strip() 57 | res = subprocess.check_output( 58 | [ 59 | "xdotool", 60 | "search", 61 | "--all", 62 | "--onlyvisible", 63 | "--desktop", 64 | desktop, 65 | "--class", 66 | "^.*", 67 | ] 68 | ).decode("utf-8", errors="replace") 69 | return res.strip().split("\n") 70 | 71 | 72 | def get_active_window(): 73 | return ( 74 | subprocess.check_output(["xdotool", "getactivewindow"]).decode("utf-8").strip() 75 | ) 76 | 77 | 78 | def focus_window(id): 79 | return subprocess.check_call( 80 | ["i3-msg", f'[id="{id}"]', "focus"], stdout=subprocess.PIPE 81 | ) 82 | # return subprocess.check_call(['xdotool','windowraise', id]) 83 | 84 | 85 | def apply_layout(layout, dry_run=False): 86 | """Actually turn this workspace into this layout""" 87 | active = get_active_window() 88 | windows = get_window_ids() 89 | windows = [active] + [x for x in windows if x != active] 90 | window_count = len(windows) 91 | # we unmap and map all at once for speed. 92 | unmap_cmd = [ 93 | "xdotool", 94 | ] 95 | map_cmd = [ 96 | "xdotool", 97 | ] 98 | t = layout.get_json(window_count) 99 | if isinstance(t, tuple): 100 | layout_dict, remap_order = t 101 | if set(range(window_count)) != set(remap_order): 102 | raise ValueError("Layout returned invalid remap order") 103 | windows = [windows[ii] for ii in remap_order] 104 | else: 105 | layout_dict = t 106 | 107 | if dry_run: 108 | print(json.dumps(layout_dict, indent=4)) 109 | else: 110 | if layout_dict is not False: 111 | append_layout(layout_dict, window_count) 112 | for window_id in windows: 113 | unmap_cmd.append("windowunmap") 114 | map_cmd.append("windowmap") 115 | unmap_cmd.append(str(window_id)) 116 | map_cmd.append(str(window_id)) 117 | 118 | # force i3 to swallow these windows. 119 | subprocess.check_call(unmap_cmd) 120 | subprocess.check_call(map_cmd) 121 | focus_window(active) 122 | 123 | 124 | def load_usage(): 125 | try: 126 | with open(counter_file, "r") as op: 127 | return json.load(op) 128 | except (OSError, ValueError): 129 | return {} 130 | 131 | 132 | def count_usage(layout_name): 133 | usage = load_usage() 134 | if layout_name not in usage: 135 | usage[layout_name] = (0, datetime.datetime.now().timestamp()) 136 | usage[layout_name] = ( 137 | usage[layout_name][0] + 1, 138 | datetime.datetime.now().timestamp(), 139 | ) 140 | 141 | with open(counter_file, "w") as op: 142 | json.dump(usage, op) 143 | 144 | 145 | def list_layouts_in_smart_order(): 146 | """List the layouts in a 'smart' order, 147 | that means most common ones on top (by log10 usage), 148 | within one log10 unit, sorted by most-recently-used""" 149 | usage = load_usage() 150 | sort_me = [] 151 | for layout in layouts.layouts: 152 | if " " in layout.name: 153 | raise ValueError( 154 | f"No spaces in layout names please. Offender: '{layout.name}'" 155 | ) 156 | for alias in [layout.name] + layout.aliases: 157 | usage_count, last_used = usage.get( 158 | alias, (0, datetime.datetime.now().timestamp()) 159 | ) 160 | if alias == layout.name: 161 | desc = alias 162 | else: 163 | desc = f"{alias} ({layout.name})" 164 | sort_me.append( 165 | (-1 * math.ceil(math.log10(usage_count + 1)), -1 * last_used, desc) 166 | ) 167 | sort_me.sort() 168 | for _, _, name in sort_me: 169 | print(name) 170 | 171 | 172 | def print_help(): 173 | print( 174 | """i3-instant-layout applies ready made layouts to i3 workspaces, 175 | based on the numerical position of the windows. 176 | 177 | Call with '--list' to get a list of available layouts (and their aliases). 178 | 179 | Call with --desc to get detailed information about every layout available. 180 | 181 | Call with the name of a layout to apply it to the current workspace. 182 | 183 | Call with '-' to read layout name from stdin. 184 | 185 | Call with 'name --dry-run' to inspect the generated i3 append_layout compatible json. 186 | 187 | Call with --notification + the name of a layout to apply the layout and show a notification that displays the name of the applied layout 188 | 189 | To integrate into i3, add this to your i3/config. 190 | bindsym $mod+Escape exec "i3-instant-layout --list | rofi -dmenu -i | i3-instant-layout -" 191 | 192 | """ 193 | ) 194 | sys.exit(0) 195 | 196 | 197 | def print_desc(): 198 | import textwrap 199 | 200 | for layout_class in layouts.layouts: 201 | print(f"Layout: {layout_class.name}") 202 | print(f"Aliases: {layout_class.aliases}") 203 | print(textwrap.indent(textwrap.dedent(layout_class.description), "\t")) 204 | print("") 205 | print("-" * 80) 206 | print("") 207 | 208 | 209 | def main(): 210 | showNotification = False 211 | if len(sys.argv) == 1 or sys.argv[1] == "--help": 212 | print_help() 213 | elif sys.argv[1] == "--desc": 214 | print_desc() 215 | sys.exit(0) 216 | elif sys.argv[1] == "--version": 217 | print(__version__) 218 | sys.exit(0) 219 | elif sys.argv[1] == "--list": 220 | list_layouts_in_smart_order() 221 | sys.exit(0) 222 | elif sys.argv[1] == "-": 223 | query = sys.stdin.readline().strip() 224 | print(f'query "{query}"') 225 | if not query.strip(): # e.g. rofi cancel 226 | sys.exit(0) 227 | else: 228 | if sys.argv[1] == "--notification": 229 | showNotification = True 230 | query = sys.argv[2] 231 | else: 232 | query = sys.argv[1] 233 | if " " in query: 234 | query = query[: query.find(" ")] 235 | for layout_class in layouts.layouts: 236 | if query == layout_class.name or query in layout_class.aliases: 237 | nuke_swallow_windows() 238 | apply_layout(layout_class(), "--dry-run" in sys.argv) 239 | if showNotification: 240 | subprocess.check_call(["notify-send", "-t", "2000", "Applied layout", layout_class.name]) 241 | count_usage(query) 242 | sys.exit(0) 243 | else: 244 | print("Could not find the requested layout") 245 | sys.exit(1) 246 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i3-instant-layout 2 | 3 | 4 | Automatic 'list based' layouts for the [i3](https://i3wm.org) window manager 5 | 6 | ## Status 7 | I consider this essentially finished. Since I'm no longer using i3 (I'm on niri these days), there will be no further updates from my side. 8 | 9 | ## Animated summary 10 | ![Demo of i3-instant-layout](https://github.com/TyberiusPrime/i3-instant-layout/raw/master/docs/_static/i3-instant-layout_demo.gif "i3-instant-layout demo") 11 | 12 | 13 | ## Description 14 | 15 | This python program drags i3 into the 'managed layouts 16 | tiling window manager world' kicking and screaming. 17 | 18 | What it does is apply a window layout to your current workspace, 19 | like this one: 20 | 21 | ------------- 22 | | | 2 | 23 | | |-----| 24 | | 1 | 3 | 25 | | |-----| 26 | | | 4 | 27 | ------------- 28 | 29 | The big advantage here is that it needs no 'swallow' definitions whatsoever, 30 | it's 'instant' - just add milk, eh, press the button. 31 | 32 | ## Get started 33 | i3-instant-layout depends xdotool which can be installed by your package manager (e.g. `sudo apt-get install xdotool` on Debian or Ubuntu) 34 | 35 | To get started, install with `pip install i3-instant-layout`, or if you prefer, [pipx](https://github.com/pipxproject/pipx) 36 | and add this to your i3 config: 37 | `bindsym $mod+Escape exec "i3-instant-layout --list | rofi -dmenu -i | i3-instant-layout -` (or use the interactive menu of your choice). 38 | 39 | 40 | ## Further information 41 | 42 | Call `i3-instant-layout --help` for full details, or 43 | `i3-instant-layout --desc` for the full list of supported layouts (or see below). 44 | 45 | 46 | ## Helpful tips 47 | 48 | ### How to sort windows 49 | Your current active window is what the tiler will consider the 'main window'. 50 | 51 | To get the other windows in the right order for your layout of choice, 52 | first enable the vStack or hStack layout, sort them, 53 | and the proceed to your layout of choice. 54 | 55 | ### Border styles 56 | 57 | i3-instant-layout must unmap/map the windows (ie. hide them temporarily) for i3 58 | to place them at the right location. 59 | Unfortunatly that appears to consume the border style. 60 | Work around this with a line like this in your i3 config: 61 | ``` 62 | for_window [class="^.*"] border pixel 1 63 | ``` 64 | 65 | 66 | 67 | 68 | ## Available layouts 69 | 70 | Layout: vStack 71 | 72 | Aliases: ['1col', '1c'] 73 | 74 | One column / a vertical stack. 75 | 76 | --------- 77 | | 1 | 78 | --------- 79 | | 2 | 80 | --------- 81 | | 3 | 82 | --------- 83 | 84 | 85 | -------------------------------------------------------------------------------- 86 | 87 | Layout: hStack 88 | 89 | Aliases: ['1row', '1r'] 90 | 91 | One row / a horizontal stack 92 | 93 | ------------- 94 | | | | | 95 | | 1 | 2 | 3 | 96 | | | | | 97 | ------------- 98 | 99 | 100 | -------------------------------------------------------------------------------- 101 | 102 | Layout: v2Stack 103 | 104 | Aliases: ['2col', '2c', '2v'] 105 | 106 | Two columns of stacks 107 | 108 | ------------- 109 | | 1 | 4 | 110 | ------------- 111 | | 2 | 5 | 112 | ------------- 113 | | 3 | 6 | 114 | ------------- 115 | 116 | 117 | -------------------------------------------------------------------------------- 118 | 119 | Layout: h2Stack 120 | 121 | Aliases: ['2row', '2r', '2h'] 122 | 123 | Two rows of stacks 124 | 125 | ------------------- 126 | | 1 | 2 | 3 | 127 | ------------------- 128 | | 4 | 5 | 6 | 129 | ------------------- 130 | 131 | 132 | -------------------------------------------------------------------------------- 133 | 134 | Layout: v3Stack 135 | 136 | Aliases: ['3col', '3c', '3v'] 137 | 138 | Three columns of stacks 139 | 140 | ------------------- 141 | | 1 | 3 | 5 | 142 | ------------------- 143 | | 2 | 4 | 6 | 144 | ------------------- 145 | 146 | 147 | -------------------------------------------------------------------------------- 148 | 149 | Layout: h3Stack 150 | 151 | Aliases: ['3row', '3r', '3h'] 152 | 153 | Three rows of stacks 154 | 155 | ------------------- 156 | | 1 | 2 | 3 | 157 | ------------------- 158 | | 4 | 5 | 6 | 159 | ------------------- 160 | | 7 | 8 | 9 | 161 | ------------------- 162 | 163 | 164 | -------------------------------------------------------------------------------- 165 | 166 | Layout: max 167 | 168 | Aliases: ['maxTabbed'] 169 | 170 | One large container, in tabbed mode. 171 | 172 | --------------- 173 | | | 174 | | 1,2,3,4, | 175 | | | 176 | --------------- 177 | 178 | 179 | -------------------------------------------------------------------------------- 180 | 181 | Layout: mainLeft 182 | 183 | Aliases: ['ml', 'mv', 'MonadTall'] 184 | 185 | One large window to the left at 50%, 186 | all others stacked to the right vertically. 187 | 188 | ------------- 189 | | | 2 | 190 | | |-----| 191 | | 1 | 3 | 192 | | |-----| 193 | | | 4 | 194 | ------------- 195 | 196 | 197 | -------------------------------------------------------------------------------- 198 | 199 | Layout: mainRight 200 | 201 | Aliases: ['mr', 'vm', 'MonadTallFlip'] 202 | 203 | One large window to the right at 50%, 204 | all others stacked to the right vertically. 205 | 206 | ------------- 207 | | 2 | | 208 | |-----| | 209 | | 3 | 1 | 210 | |-----| | 211 | | 4 | | 212 | ------------- 213 | 214 | 215 | -------------------------------------------------------------------------------- 216 | 217 | Layout: MainMainVStack 218 | 219 | Aliases: ['mmv'] 220 | 221 | Two large windows to the left at 30%, 222 | all others stacked to the right vertically. 223 | 224 | ------------------- 225 | | | | 3 | 226 | | | |-----| 227 | | 1 | 2 | 4 | 228 | | | |-----| 229 | | | | 5 | 230 | ------------------- 231 | 232 | 233 | -------------------------------------------------------------------------------- 234 | 235 | Layout: MainVStackMain 236 | 237 | Aliases: ['mvm'] 238 | 239 | Two large windows at 30% to the left and right, 240 | a vstack in the center 241 | 242 | ------------------- 243 | | | 3 | | 244 | | |-----| | 245 | | 1 | 4 | 2 | 246 | | |-----| | 247 | | | 5 | | 248 | ------------------- 249 | 250 | 251 | -------------------------------------------------------------------------------- 252 | 253 | Layout: matrix 254 | 255 | Aliases: [] 256 | 257 | Place windows in a n * n matrix. 258 | 259 | The matrix will place swallow-markers 260 | if you have less than n*n windows. 261 | 262 | N is math.ceil(math.sqrt(window_count)) 263 | 264 | 265 | -------------------------------------------------------------------------------- 266 | 267 | Layout: VerticalTileTop 268 | 269 | Aliases: ['vtt'] 270 | 271 | Large master area (66%) on top, 272 | horizontal stacking below 273 | 274 | 275 | -------------------------------------------------------------------------------- 276 | 277 | Layout: VerticalTileBottom 278 | 279 | Aliases: ['vtb'] 280 | 281 | Large master area (66%) on bottom, 282 | horizontal stacking above 283 | 284 | 285 | -------------------------------------------------------------------------------- 286 | 287 | Layout: NestedRight 288 | 289 | Aliases: ['nr'] 290 | 291 | Nested layout, starting with a full left half. 292 | 293 | 294 | ------------------------- 295 | | | | 296 | | | 2 | 297 | | | | 298 | | 1 |-----------| 299 | | | | 4 | 300 | | | 3 |-----| 301 | | | |5 | 6| 302 | ------------------------- 303 | 304 | 305 | -------------------------------------------------------------------------------- 306 | 307 | Layout: SmartNestedRight 308 | 309 | Aliases: ['snr'] 310 | 311 | Nested layout, starting with a full left half, 312 | but never going below 1/16th of the size. 313 | 314 | 2 windows 315 | ------------------------- 316 | | | | 317 | | | | 318 | | | | 319 | | 1 | 2 | 320 | | | | 321 | | | | 322 | | | | 323 | ------------------------- 324 | 325 | 5 windows 326 | ------------------------- 327 | | | | 328 | | | 2 | 329 | | | | 330 | | 1 |-----------| 331 | | | | 4 | 332 | | | 3 |-----| 333 | | | | 5 | 334 | ------------------------- 335 | 336 | 6 windows 337 | ------------------------- 338 | | | | 339 | | | 2 | 340 | | | | 341 | | 1 |-----------| 342 | | | 3 | 4 | 343 | | |-----|-----| 344 | | | 5 | 6 | 345 | ------------------------- 346 | 347 | 7 windows 348 | ------------------------- 349 | | | | | 350 | | | 2 | 3 | 351 | | | | | 352 | | 1 |-----------| 353 | | | 4 | 5 | 354 | | |-----|-----| 355 | | | 6 | 7 | 356 | ------------------------- 357 | 358 | 359 | 15 windows 360 | ------------------------- 361 | | | 2 | 4 | 6 | 362 | | 1 |-----|-----|-----| 363 | | | 3 | 5 | 7 | 364 | |-----------|-----------| 365 | | 8 | A | C | E | 366 | |-----|-----|-----|-----| 367 | | 9 | B | D | F | 368 | ------------------------- 369 | 370 | Falls back to matrix layout above 16 windows. 371 | 372 | -------------------------------------------------------------------------------- 373 | 374 | Layout: mainCenter 375 | 376 | Aliases: ['mc', 'vmv'] 377 | One large window in the midle at 50%, 378 | all others stacked to the left/right vertically. 379 | 380 | ------------------- 381 | | 2 | | 5 | 382 | |-----| |-----| 383 | | 3 | 1 | 6 | 384 | |-----| |-----| 385 | | 4 | | 7 | 386 | ------------------- 387 | 388 | -------------------------------------------------------------------------------- 389 | 390 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is execfile()d with the current directory set to its containing dir. 4 | # 5 | # Note that not all possible configuration values are present in this 6 | # autogenerated file. 7 | # 8 | # All configuration values have a default; values that are commented out 9 | # serve to show the default. 10 | 11 | import os 12 | import sys 13 | import inspect 14 | import shutil 15 | 16 | __location__ = os.path.join(os.getcwd(), os.path.dirname( 17 | inspect.getfile(inspect.currentframe()))) 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.join(__location__, '../src')) 23 | 24 | # -- Run sphinx-apidoc ------------------------------------------------------ 25 | # This hack is necessary since RTD does not issue `sphinx-apidoc` before running 26 | # `sphinx-build -b html . _build/html`. See Issue: 27 | # https://github.com/rtfd/readthedocs.org/issues/1139 28 | # DON'T FORGET: Check the box "Install your project inside a virtualenv using 29 | # setup.py install" in the RTD Advanced Settings. 30 | # Additionally it helps us to avoid running apidoc manually 31 | 32 | try: # for Sphinx >= 1.7 33 | from sphinx.ext import apidoc 34 | except ImportError: 35 | from sphinx import apidoc 36 | 37 | output_dir = os.path.join(__location__, "api") 38 | module_dir = os.path.join(__location__, "../src/i3_instant_layout") 39 | try: 40 | shutil.rmtree(output_dir) 41 | except FileNotFoundError: 42 | pass 43 | 44 | try: 45 | import sphinx 46 | from pkg_resources import parse_version 47 | 48 | cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" 49 | cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) 50 | 51 | args = cmd_line.split(" ") 52 | if parse_version(sphinx.__version__) >= parse_version('1.7'): 53 | args = args[1:] 54 | 55 | apidoc.main(args) 56 | except Exception as e: 57 | print("Running `sphinx-apidoc` failed!\n{}".format(e)) 58 | 59 | # -- General configuration ----------------------------------------------------- 60 | 61 | # If your documentation needs a minimal Sphinx version, state it here. 62 | # needs_sphinx = '1.0' 63 | 64 | # Add any Sphinx extension module names here, as strings. They can be extensions 65 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 66 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 67 | 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'sphinx.ext.coverage', 68 | 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.mathjax', 69 | 'sphinx.ext.napoleon'] 70 | 71 | # Add any paths that contain templates here, relative to this directory. 72 | templates_path = ['_templates'] 73 | 74 | # The suffix of source filenames. 75 | source_suffix = '.rst' 76 | 77 | # The encoding of source files. 78 | # source_encoding = 'utf-8-sig' 79 | 80 | # The master toctree document. 81 | master_doc = 'index' 82 | 83 | # General information about the project. 84 | project = u'i3-instant-layout' 85 | copyright = u'2020, Florian Finkernagel' 86 | 87 | # The version info for the project you're documenting, acts as replacement for 88 | # |version| and |release|, also used in various other places throughout the 89 | # built documents. 90 | # 91 | # The short X.Y version. 92 | version = '' # Is set by calling `setup.py docs` 93 | # The full version, including alpha/beta/rc tags. 94 | release = '' # Is set by calling `setup.py docs` 95 | 96 | # The language for content autogenerated by Sphinx. Refer to documentation 97 | # for a list of supported languages. 98 | # language = None 99 | 100 | # There are two options for replacing |today|: either, you set today to some 101 | # non-false value, then it is used: 102 | # today = '' 103 | # Else, today_fmt is used as the format for a strftime call. 104 | # today_fmt = '%B %d, %Y' 105 | 106 | # List of patterns, relative to source directory, that match files and 107 | # directories to ignore when looking for source files. 108 | exclude_patterns = ['_build'] 109 | 110 | # The reST default role (used for this markup: `text`) to use for all documents. 111 | # default_role = None 112 | 113 | # If true, '()' will be appended to :func: etc. cross-reference text. 114 | # add_function_parentheses = True 115 | 116 | # If true, the current module name will be prepended to all description 117 | # unit titles (such as .. function::). 118 | # add_module_names = True 119 | 120 | # If true, sectionauthor and moduleauthor directives will be shown in the 121 | # output. They are ignored by default. 122 | # show_authors = False 123 | 124 | # The name of the Pygments (syntax highlighting) style to use. 125 | pygments_style = 'sphinx' 126 | 127 | # A list of ignored prefixes for module index sorting. 128 | # modindex_common_prefix = [] 129 | 130 | # If true, keep warnings as "system message" paragraphs in the built documents. 131 | # keep_warnings = False 132 | 133 | 134 | # -- Options for HTML output --------------------------------------------------- 135 | 136 | # The theme to use for HTML and HTML Help pages. See the documentation for 137 | # a list of builtin themes. 138 | html_theme = 'alabaster' 139 | 140 | # Theme options are theme-specific and customize the look and feel of a theme 141 | # further. For a list of options available for each theme, see the 142 | # documentation. 143 | html_theme_options = { 144 | 'sidebar_width': '300px', 145 | 'page_width': '1200px' 146 | } 147 | 148 | # Add any paths that contain custom themes here, relative to this directory. 149 | # html_theme_path = [] 150 | 151 | # The name for this set of Sphinx documents. If None, it defaults to 152 | # " v documentation". 153 | try: 154 | from i3_instant_layout import __version__ as version 155 | except ImportError: 156 | pass 157 | else: 158 | release = version 159 | 160 | # A shorter title for the navigation bar. Default is the same as html_title. 161 | # html_short_title = None 162 | 163 | # The name of an image file (relative to this directory) to place at the top 164 | # of the sidebar. 165 | # html_logo = "" 166 | 167 | # The name of an image file (within the static path) to use as favicon of the 168 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 169 | # pixels large. 170 | # html_favicon = None 171 | 172 | # Add any paths that contain custom static files (such as style sheets) here, 173 | # relative to this directory. They are copied after the builtin static files, 174 | # so a file named "default.css" will overwrite the builtin "default.css". 175 | html_static_path = ['_static'] 176 | 177 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 178 | # using the given strftime format. 179 | # html_last_updated_fmt = '%b %d, %Y' 180 | 181 | # If true, SmartyPants will be used to convert quotes and dashes to 182 | # typographically correct entities. 183 | # html_use_smartypants = True 184 | 185 | # Custom sidebar templates, maps document names to template names. 186 | # html_sidebars = {} 187 | 188 | # Additional templates that should be rendered to pages, maps page names to 189 | # template names. 190 | # html_additional_pages = {} 191 | 192 | # If false, no module index is generated. 193 | # html_domain_indices = True 194 | 195 | # If false, no index is generated. 196 | # html_use_index = True 197 | 198 | # If true, the index is split into individual pages for each letter. 199 | # html_split_index = False 200 | 201 | # If true, links to the reST sources are added to the pages. 202 | # html_show_sourcelink = True 203 | 204 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 205 | # html_show_sphinx = True 206 | 207 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 208 | # html_show_copyright = True 209 | 210 | # If true, an OpenSearch description file will be output, and all pages will 211 | # contain a tag referring to it. The value of this option must be the 212 | # base URL from which the finished HTML is served. 213 | # html_use_opensearch = '' 214 | 215 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 216 | # html_file_suffix = None 217 | 218 | # Output file base name for HTML help builder. 219 | htmlhelp_basename = 'i3-instant-layout-doc' 220 | 221 | 222 | # -- Options for LaTeX output -------------------------------------------------- 223 | 224 | latex_elements = { 225 | # The paper size ('letterpaper' or 'a4paper'). 226 | # 'papersize': 'letterpaper', 227 | 228 | # The font size ('10pt', '11pt' or '12pt'). 229 | # 'pointsize': '10pt', 230 | 231 | # Additional stuff for the LaTeX preamble. 232 | # 'preamble': '', 233 | } 234 | 235 | # Grouping the document tree into LaTeX files. List of tuples 236 | # (source start file, target name, title, author, documentclass [howto/manual]). 237 | latex_documents = [ 238 | ('index', 'user_guide.tex', u'i3-instant-layout Documentation', 239 | u'Florian Finkernagel', 'manual'), 240 | ] 241 | 242 | # The name of an image file (relative to this directory) to place at the top of 243 | # the title page. 244 | # latex_logo = "" 245 | 246 | # For "manual" documents, if this is true, then toplevel headings are parts, 247 | # not chapters. 248 | # latex_use_parts = False 249 | 250 | # If true, show page references after internal links. 251 | # latex_show_pagerefs = False 252 | 253 | # If true, show URL addresses after external links. 254 | # latex_show_urls = False 255 | 256 | # Documents to append as an appendix to all manuals. 257 | # latex_appendices = [] 258 | 259 | # If false, no module index is generated. 260 | # latex_domain_indices = True 261 | 262 | # -- External mapping ------------------------------------------------------------ 263 | python_version = '.'.join(map(str, sys.version_info[0:2])) 264 | intersphinx_mapping = { 265 | 'sphinx': ('http://www.sphinx-doc.org/en/stable', None), 266 | 'python': ('https://docs.python.org/' + python_version, None), 267 | 'matplotlib': ('https://matplotlib.org', None), 268 | 'numpy': ('https://docs.scipy.org/doc/numpy', None), 269 | 'sklearn': ('http://scikit-learn.org/stable', None), 270 | 'pandas': ('http://pandas.pydata.org/pandas-docs/stable', None), 271 | 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), 272 | } 273 | -------------------------------------------------------------------------------- /src/i3_instant_layout/layouts.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | layouts = [] 4 | 5 | 6 | def register_layout(cls): 7 | layouts.append(cls) 8 | return cls 9 | 10 | 11 | def node(percent, layout, swallows, children): 12 | result = { 13 | "border": "normal", 14 | # "current_border_width": 2, 15 | "floating": "auto_off", 16 | # "name": "fish <>", 17 | "percent": percent, 18 | "type": "con", 19 | "layout": layout, 20 | } 21 | if swallows: 22 | result["swallows"] = ([{"class": "."}],) 23 | if children: 24 | result["nodes"] = children 25 | return result 26 | 27 | 28 | def get_stack(window_count, split): 29 | return get_stack_unequal([1.0 / window_count] * window_count, split) 30 | 31 | 32 | def get_stack_unequal(percentages, split): 33 | elements = [] 34 | for p in percentages: 35 | elements.append(node(p, split, True, False)) 36 | return [{"layout": split, "type": "con", "nodes": elements}] 37 | 38 | 39 | @register_layout 40 | class Layout_vStack: 41 | name = "vStack" 42 | aliases = ["1col", "1c"] 43 | description = """\ 44 | One column / a vertical stack. 45 | 46 | --------- 47 | | 1 | 48 | --------- 49 | | 2 | 50 | --------- 51 | | 3 | 52 | --------- 53 | """ 54 | 55 | def get_json(self, window_count): 56 | return get_stack(window_count, "splitv") 57 | 58 | 59 | @register_layout 60 | class Layout_hStack: 61 | name = "hStack" 62 | aliases = ["1row", "1r"] 63 | description = """\ 64 | One row / a horizontal stack 65 | 66 | ------------- 67 | | | | | 68 | | 1 | 2 | 3 | 69 | | | | | 70 | ------------- 71 | """ 72 | 73 | def get_json(self, window_count): 74 | return get_stack(window_count, "splith") 75 | 76 | 77 | @register_layout 78 | class Layout_tabbed: 79 | name = "tabbed" 80 | aliases = [] 81 | description = """\ 82 | Tabbed 83 | 84 | --------- 85 | | | 86 | | 1/2/3 | 87 | | | 88 | --------- 89 | """ 90 | 91 | def get_json(self, window_count): 92 | return get_stack(window_count, "tabbed") 93 | 94 | 95 | @register_layout 96 | class Layout_v2Stack: 97 | name = "v2Stack" 98 | aliases = ["2col", "2c", "2v"] 99 | description = """\ 100 | Two columns of stacks 101 | ------------- 102 | | 1 | 4 | 103 | ------------- 104 | | 2 | 5 | 105 | ------------- 106 | | 3 | 6 | 107 | ------------- 108 | """ 109 | 110 | def get_json(self, window_count): 111 | s = int(math.ceil(window_count / 2)) 112 | left = get_stack(s, "splitv") 113 | right = get_stack(s if window_count % 2 == 0 else s - 1, "splitv") 114 | return [{"layout": "splith", "type": "con", "nodes": [left, right]}] 115 | 116 | 117 | @register_layout 118 | class Layout_h2Stack: 119 | name = "h2Stack" 120 | aliases = ["2row", "2r", "2h"] 121 | description = """\ 122 | Two rows of stacks 123 | ------------------- 124 | | 1 | 2 | 3 | 125 | ------------------- 126 | | 4 | 5 | 6 | 127 | ------------------- 128 | """ 129 | 130 | def get_json(self, window_count): 131 | s = int(math.ceil(window_count / 2)) 132 | left = get_stack(s, "splith") 133 | right = get_stack(s if window_count % 2 == 0 else s - 1, "splith") 134 | return [{"layout": "splitv", "type": "con", "nodes": [left, right]}] 135 | 136 | 137 | @register_layout 138 | class Layout_v3Stack: 139 | name = "v3Stack" 140 | aliases = ["3col", "3c", "3v"] 141 | description = """\ 142 | Three columns of stacks 143 | ------------------- 144 | | 1 | 3 | 5 | 145 | ------------------- 146 | | 2 | 4 | 6 | 147 | ------------------- 148 | """ 149 | 150 | def get_json(self, window_count): 151 | s = window_count // 3 152 | a = get_stack(s + window_count % 3, "splitv") 153 | b = get_stack(s, "splitv") 154 | c = get_stack(s, "splitv") 155 | return [{"layout": "splith", "type": "con", "nodes": [a, b, c]}] 156 | 157 | 158 | @register_layout 159 | class Layout_h3Stack: 160 | name = "h3Stack" 161 | aliases = ["3row", "3r", "3h"] 162 | description = """\ 163 | Three rows of stacks 164 | ------------------- 165 | | 1 | 2 | 3 | 166 | ------------------- 167 | | 4 | 5 | 6 | 168 | ------------------- 169 | | 7 | 8 | 9 | 170 | ------------------- 171 | """ 172 | 173 | def get_json(self, window_count): 174 | s = window_count // 3 175 | a = get_stack(s + window_count % 3, "splith") 176 | b = get_stack(s, "splith") 177 | c = get_stack(s, "splith") 178 | return [{"layout": "splitv", "type": "con", "nodes": [a, b, c]}] 179 | 180 | 181 | @register_layout 182 | class Layout_Max: 183 | name = "max" 184 | aliases = ["maxTabbed"] 185 | description = """\ 186 | One large container, 187 | in tabbed mode. 188 | 189 | --------------- 190 | | | 191 | | 1,2,3,4, | 192 | | | 193 | --------------- 194 | """ 195 | 196 | def get_json(self, window_count): 197 | return get_stack(window_count, "tabbed") 198 | 199 | 200 | @register_layout 201 | class Layout_MainLeft: 202 | name = "mainLeft" 203 | aliases = ["ml", "mv", "MonadTall"] 204 | description = """\ 205 | One large window to the left at 50%, 206 | all others stacked to the right vertically. 207 | 208 | ------------- 209 | | | 2 | 210 | | |-----| 211 | | 1 | 3 | 212 | | |-----| 213 | | | 4 | 214 | ------------- 215 | """ 216 | 217 | def get_json(self, window_count): 218 | return node( 219 | 1, 220 | "splith", 221 | False, 222 | [node(0.5, "splitv", True, []), get_stack(window_count - 1, "splitv")], 223 | ) 224 | 225 | 226 | @register_layout 227 | class Layout_MainRight: 228 | name = "mainRight" 229 | aliases = ["mr", "vm", "MonadTallFlip"] 230 | description = """\ 231 | One large window to the right at 50%, 232 | all others stacked to the right vertically. 233 | 234 | ------------- 235 | | 2 | | 236 | |-----| | 237 | | 3 | 1 | 238 | |-----| | 239 | | 4 | | 240 | ------------- 241 | """ 242 | 243 | def get_json(self, window_count): 244 | return ( 245 | node( 246 | 1, 247 | "splith", 248 | False, 249 | [get_stack(window_count - 1, "splitv"), node(0.75, "splitv", True, [])], 250 | ), 251 | list(range(1, window_count)) + [0], 252 | ) 253 | 254 | 255 | @register_layout 256 | class Layout_MainMainVStack: 257 | name = "MainMainVStack" 258 | aliases = ["mmv"] 259 | description = """\ 260 | Two large windows to the left at 30%, 261 | all others stacked to the right vertically. 262 | 263 | ------------------- 264 | | | | 3 | 265 | | | |-----| 266 | | 1 | 2 | 4 | 267 | | | |-----| 268 | | | | 5 | 269 | ------------------- 270 | """ 271 | 272 | def get_json(self, window_count): 273 | return node( 274 | 1, 275 | "splith", 276 | False, 277 | [ 278 | node(1 / 3, "splitv", True, []), 279 | node(1 / 3, "splitv", True, []), 280 | get_stack(window_count - 2, "splitv"), 281 | ], 282 | ) 283 | 284 | 285 | @register_layout 286 | class Layout_MainVStackMain: 287 | name = "MainVStackMain" 288 | aliases = ["mvm"] 289 | description = """\ 290 | Two large windows at 30% to the left and right, 291 | a vstack in the center 292 | 293 | ------------------- 294 | | | 3 | | 295 | | |-----| | 296 | | 1 | 4 | 2 | 297 | | |-----| | 298 | | | 5 | | 299 | ------------------- 300 | """ 301 | 302 | def get_json(self, window_count): 303 | return ( 304 | node( 305 | 1, 306 | "splith", 307 | False, 308 | [ 309 | node(1 / 3, "splitv", True, []), 310 | get_stack(window_count - 2, "splitv"), 311 | node(1 / 3, "splitv", True, []), 312 | ], 313 | ), 314 | [0] + list(range(2, window_count)) + [1], 315 | ) 316 | 317 | 318 | @register_layout 319 | class Layout_Matrix: 320 | name = "matrix" 321 | aliases = [] 322 | description = """\ 323 | Place windows in a n * n matrix. 324 | 325 | The matrix will place swallow-markers 326 | if you have less than n*n windows. 327 | 328 | N is math.ceil(math.sqrt(window_count)) 329 | """ 330 | 331 | def get_json(self, window_count): 332 | n = int(math.ceil(math.sqrt(window_count))) 333 | stacks = [get_stack(n, "splith") for stack in range(n)] 334 | return node(1, "splitv", False, stacks) 335 | 336 | 337 | @register_layout 338 | class Layout_VerticalTileTop: 339 | name = "VerticalTileTop" 340 | aliases = ["vtt"] 341 | description = """\ 342 | Large master area (66%) on top, 343 | horizontal stacking below 344 | """ 345 | 346 | def get_json(self, window_count): 347 | return node( 348 | 1, 349 | "splitv", 350 | False, 351 | [ 352 | node(0.66, "splitv", True, []), 353 | node( 354 | 0.33, 355 | "splitv", 356 | False, 357 | get_stack_unequal( 358 | [0.33 / (window_count - 1)] * (window_count - 1), 359 | "splitv", 360 | ), 361 | ), 362 | ], 363 | ) 364 | 365 | 366 | @register_layout 367 | class Layout_VerticalTileBottom: 368 | name = "VerticalTileBottom" 369 | aliases = ["vtb"] 370 | description = """\ 371 | Large master area (66%) on bottom, 372 | horizontal stacking above 373 | """ 374 | 375 | def get_json(self, window_count): 376 | return ( 377 | node( 378 | 1, 379 | "splitv", 380 | False, 381 | [ 382 | node( 383 | 0.33, 384 | "splitv", 385 | False, 386 | get_stack_unequal( 387 | [0.33 / (window_count - 1)] * (window_count - 1), 388 | "splitv", 389 | ), 390 | ), 391 | node(0.66, "splitv", True, []), 392 | ], 393 | ), 394 | list(range(1, window_count)) + [0], 395 | ) 396 | 397 | 398 | @register_layout 399 | class Nested: 400 | name = "NestedRight" 401 | aliases = ["nr"] 402 | 403 | description = """\ 404 | Nested layout, starting with a full left half. 405 | 406 | 407 | ------------------------- 408 | | | | 409 | | | 2 | 410 | | | | 411 | | 1 |-----------| 412 | | | | 4 | 413 | | | 3 |-----| 414 | | | |5 | 6| 415 | ------------------------- 416 | """ 417 | 418 | def get_json(self, window_count): 419 | dir = "h" 420 | parent = node(1, "splith", False, []) 421 | root = parent 422 | parent["nodes"] = [] 423 | for ii in range(window_count): 424 | parent["nodes"].append(get_stack_unequal([0.5], "split" + dir)) 425 | n = node(1, "splith", False, []) 426 | if dir == "h": 427 | dir = "v" 428 | else: 429 | dir = "h" 430 | 431 | n["layout"] = "split" + dir 432 | n["nodes"] = [] 433 | if ii < window_count - 1: 434 | parent["nodes"].append(n) 435 | parent = n 436 | return root 437 | 438 | 439 | @register_layout 440 | class Smart: 441 | name = "SmartNestedRight" 442 | aliases = ["snr"] 443 | 444 | description = """\ 445 | Nested layout, starting with a full left half, 446 | but never going below 1/16th of the size. 447 | 448 | 2 windows 449 | ------------------------- 450 | | | | 451 | | | | 452 | | | | 453 | | 1 | 2 | 454 | | | | 455 | | | | 456 | | | | 457 | ------------------------- 458 | 459 | 5 windows 460 | ------------------------- 461 | | | | 462 | | | 2 | 463 | | | | 464 | | 1 |-----------| 465 | | | | 4 | 466 | | | 3 |-----| 467 | | | | 5 | 468 | ------------------------- 469 | 470 | 6 windows 471 | ------------------------- 472 | | | | 473 | | | 2 | 474 | | | | 475 | | 1 |-----------| 476 | | | 3 | 4 | 477 | | |-----|-----| 478 | | | 5 | 6 | 479 | ------------------------- 480 | 481 | 7 windows 482 | ------------------------- 483 | | | | | 484 | | | 2 | 3 | 485 | | | | | 486 | | 1 |-----------| 487 | | | 4 | 5 | 488 | | |-----|-----| 489 | | | 6 | 7 | 490 | ------------------------- 491 | 492 | 493 | 15 windows 494 | ------------------------- 495 | | | 2 | 4 | 6 | 496 | | 1 |-----|-----|-----| 497 | | | 3 | 5 | 7 | 498 | |-----------|-----------| 499 | | 8 | A | C | E | 500 | |-----|-----|-----|-----| 501 | | 9 | B | D | F | 502 | ------------------------- 503 | 504 | Falls back to matrix layout above 16 windows. 505 | """ 506 | 507 | def get_json(self, window_count): 508 | def nest_1(): 509 | return node(1, "splith", True, []) 510 | 511 | def nest_2(): 512 | return get_stack(2, "splith") 513 | 514 | def nest_3(): 515 | return node( 516 | 1, 517 | "splith", 518 | False, 519 | [node(0.5, "splitv", True, []), get_stack(2, "splitv")], 520 | ) 521 | 522 | def nest_4(): 523 | return node( 524 | 1, 525 | "splith", 526 | False, 527 | [get_stack(2, "splitv"), get_stack(2, "splitv")], 528 | ) 529 | 530 | if window_count == 1: 531 | return nest_1() 532 | elif window_count == 2: 533 | return nest_2() 534 | elif window_count == 3: 535 | return nest_3() 536 | elif window_count == 4: 537 | return nest_4() 538 | elif window_count == 5: 539 | return node( 540 | 1, 541 | "splith", 542 | False, 543 | [ 544 | node( 545 | 0.5, 546 | "splitv", 547 | True, 548 | [], 549 | ), 550 | node( 551 | 0.5, "splitv", False, [node(0.5, "split", True, []), nest_3()] 552 | ), 553 | ], 554 | ) 555 | elif window_count == 6: 556 | return node( 557 | 1, 558 | "splith", 559 | False, 560 | [ 561 | node( 562 | 0.5, 563 | "splitv", 564 | True, 565 | [], 566 | ), 567 | node( 568 | 0.5, "splitv", False, [node(0.5, "split", True, []), nest_4()] 569 | ), 570 | ], 571 | ) 572 | elif window_count == 7: 573 | return node( 574 | 1, 575 | "splith", 576 | False, 577 | [ 578 | node( 579 | 0.5, 580 | "splitv", 581 | True, 582 | [], 583 | ), 584 | node(0.5, "splitv", False, [nest_2(), nest_4()]), 585 | ], 586 | ) 587 | elif window_count == 8: 588 | return node( 589 | 1, 590 | "splith", 591 | False, 592 | [ 593 | node( 594 | 0.5, 595 | "splitv", 596 | True, 597 | [], 598 | ), 599 | node(0.5, "splitv", False, [nest_3(), nest_4()]), 600 | ], 601 | ) 602 | elif window_count == 9: 603 | return node( 604 | 1, 605 | "splith", 606 | False, 607 | [ 608 | node( 609 | 0.5, 610 | "splitv", 611 | True, 612 | [], 613 | ), 614 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 615 | ], 616 | ) 617 | elif window_count == 10: 618 | return node( 619 | 1, 620 | "splith", 621 | False, 622 | [ 623 | node( 624 | 0.5, 625 | "splitv", 626 | False, 627 | [node(0.5, "splitv", True, []), node(0.5, "splitv", True, [])], 628 | ), 629 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 630 | ], 631 | ) 632 | elif window_count == 11: 633 | return node( 634 | 1, 635 | "splith", 636 | False, 637 | [ 638 | node( 639 | 0.5, 640 | "splitv", 641 | False, 642 | [node(0.5, "splitv", True, []), nest_2()], 643 | ), 644 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 645 | ], 646 | ) 647 | elif window_count == 12: 648 | return node( 649 | 1, 650 | "splith", 651 | False, 652 | [ 653 | node( 654 | 0.5, 655 | "splitv", 656 | False, 657 | [node(0.5, "splitv", True, []), nest_3()], 658 | ), 659 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 660 | ], 661 | ) 662 | elif window_count == 13: 663 | return node( 664 | 1, 665 | "splith", 666 | False, 667 | [ 668 | node( 669 | 0.5, 670 | "splitv", 671 | False, 672 | [node(0.5, "splitv", True, []), nest_4()], 673 | ), 674 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 675 | ], 676 | ) 677 | elif window_count == 14: 678 | return node( 679 | 1, 680 | "splith", 681 | False, 682 | [ 683 | node( 684 | 0.5, 685 | "splitv", 686 | False, 687 | [nest_2(), nest_4()], 688 | ), 689 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 690 | ], 691 | ) 692 | elif window_count == 15: 693 | return node( 694 | 1, 695 | "splith", 696 | False, 697 | [ 698 | node( 699 | 0.5, 700 | "splitv", 701 | False, 702 | [nest_3(), nest_4()], 703 | ), 704 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 705 | ], 706 | ) 707 | elif window_count == 16: 708 | return node( 709 | 1, 710 | "splith", 711 | False, 712 | [ 713 | node( 714 | 0.5, 715 | "splitv", 716 | False, 717 | [nest_4(), nest_4()], 718 | ), 719 | node(0.5, "splitv", False, [nest_4(), nest_4()]), 720 | ], 721 | ) 722 | else: 723 | return Layout_Matrix().get_json(window_count) 724 | 725 | 726 | @register_layout 727 | class Layout_MainCenter: 728 | name = "mainCenter" 729 | aliases = ["mc", "vmv"] 730 | description = """\ 731 | One large window in the midle at 50%, 732 | all others stacked to the left/right vertically. 733 | 734 | ------------------- 735 | | 2 | | 5 | 736 | |-----| |-----| 737 | | 3 | 1 | 6 | 738 | |-----| |-----| 739 | | 4 | | 7 | 740 | ------------------- 741 | """ 742 | 743 | def get_json(self, window_count): 744 | lr = window_count - 1 745 | left = math.ceil(lr / 2) 746 | right = math.floor(lr / 2) 747 | nodes = [] 748 | if left: 749 | nodes.append(node(0.25, "splith", False, get_stack(left, "splitv"))) 750 | nodes.append(node(0.5, "splitv", True, [])) 751 | if right: 752 | nodes.append(node(0.25, "splith", False, get_stack(right, "splitv"))) 753 | order = list(range(1, left + 1)) + [0] + list(range(left + 1, left + 1 + right)) 754 | print(order) 755 | return node(1, "splith", False, nodes), order 756 | 757 | 758 | @register_layout 759 | class Layout_MainTop: 760 | name = "mainTop" 761 | aliases = ["mt"] 762 | description = """\ 763 | One large window to the top at 50%, 764 | all others stacked to the right horizontally. 765 | 766 | ------------- 767 | | | 768 | | 1 | 769 | | | 770 | |-----|-----| 771 | | 2 | 3 | 772 | ------------- 773 | """ 774 | 775 | def get_json(self, window_count): 776 | return node( 777 | 1, 778 | "splitv", 779 | False, 780 | [node(0.5, "splith", True, []), get_stack(window_count - 1, "splith")], 781 | ) 782 | --------------------------------------------------------------------------------