Name | qtile-bonsai JSON |
Version |
0.4.0
JSON |
| download |
home_page | None |
Summary | A flexible custom layout for the qtile window manager that supports arbitrarily nestable tabs and splits. |
upload_time | 2024-08-17 04:01:29 |
maintainer | None |
docs_url | None |
author | None |
requires_python | <3.13,>=3.10 |
license | MIT |
keywords |
qtile
bonsai
tiling
window
manager
layout
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
<!--
README.md is a generated file!
To make modifications, make sure you're editing `templates/README.template.md`.
Then generate the README with `python scripts/generate_readme.py`
-->
<h1 align="center">
Qtile Bonsai
</h1>
<p align="center">
<a href="https://github.com/aravinda0/qtile-bonsai/actions?query=branch%3Amaster"><img src="https://img.shields.io/github/actions/workflow/status/aravinda0/qtile-bonsai/ci.yml?style=for-the-badge&logo=github"></a>
<a href="https://codecov.io/gh/aravinda0/qtile-bonsai"><img src="https://img.shields.io/codecov/c/github/aravinda0/qtile-bonsai?style=for-the-badge&logo=codecov"></a>
<a href="https://github.com/aravinda0/qtile-bonsai/blob/master/LICENSE.txt"><img src="https://img.shields.io/github/license/aravinda0/qtile-bonsai?style=for-the-badge"></a>
</p>
<p align="center">
<a href="https://aravinda0.github.io/qtile-bonsai/static/visual_guide/"><img src="https://img.shields.io/badge/Visual%20Guide-d79921?style=for-the-badge"></a>
<a href="#reference"><img src="https://img.shields.io/badge/Reference-d79921?style=for-the-badge"></a>
</p>
<br/>
## Introduction
_Qtile Bonsai_ provides a flexible layout for the
[qtile](https://github.com/qtile/qtile) tiling window manager that allows you to
arrange windows as tabs, splits and even subtabs inside splits.
It also provides an API with window-management operations that allow for quick
access and rearrangements of tabs/windows.
For a quick feeler, look at the demo below, or the [visual guide](#visual-guide)
further below.
<br>
<!-- GitHub will simply render this into a video it seems. Generated by dragging
and dropping into GitHub README editor GUI -->
https://github.com/aravinda0/qtile-bonsai/assets/960763/b74e559a-30c0-4de7-86f4-ecea8e68125c
<br/>
## Getting Started
### Installation
Assuming you already have
[qtile up and running](https://docs.qtile.org/en/latest/manual/install/), you
have the following options for installation.
#### PyPI
```bash
pip install qtile-bonsai
```
> [!NOTE]
> If you have qtile installed globally (eg. via your distro's package manager),
> you likely have to do:
>
> ```bash
> pip install qtile-bonsai --break-system-packages
> ```
>
> Nowadays `pip` tries to play it safe and not potentially modify some
> dependency that your system's Python-software may depend on. For qtile-bonsai,
> using this flag should be safe.
>
> An alternative approach would be to have installed qtile via [pipx](https://github.com/pypa/pipx)
> and then 'inject' qtile-bonsai into the same virtualenv where qtile resides:
>
> ```
> pipx install qtile
> pipx inject qtile qtile-bonsai
> ```
#### AUR
For arch-based distros, you can install it from the AUR either manually or with
your favorite AUR-helper. For example:
```
yay -S qtile-bonsai
```
### Configuration
#### 1. Make Bonsai available as a layout in your [qtile config](https://docs.qtile.org/en/latest/manual/config/default.html)
```python
from qtile_bonsai import Bonsai
layouts = [
Bonsai(**{
# Specify your options here. These examples are defaults.
"window.border_size": 1,
"tab_bar.height": 20,
# You can specify subtab level specific options if desired by prefixing
# the option key with the appropriate level, eg. L1, L2, L3 etc.
# For example, the following options affect only 2nd level subtabs and
# their windows:
# "L2.window.border_color": "#ff0000",
# "L2.window.margin": 5,
}),
]
```
#### 2. Add your personal keybindings to your qtile config
```python
from libqtile.config import EzKey, KeyChord
from libqtile.lazy import lazy
from libqtile.utils import guess_terminal
terminal = guess_terminal()
rofi_run_cmd = "rofi -show drun -m -1"
keys = [
# Open your terminal emulator quickly. See further below for how to
# directly open other apps as splits/tabs using something like rofi.
EzKey("M-v", lazy.layout.spawn_split(terminal, "x")),
EzKey("M-x", lazy.layout.spawn_split(terminal, "y")),
EzKey("M-t", lazy.layout.spawn_tab(terminal)),
EzKey("M-S-t", lazy.layout.spawn_tab(terminal, new_level=True)),
# Sometimes it's handy to have a split open in the 'previous' position
EzKey("M-S-v", lazy.layout.spawn_split(terminal, "x", position="previous")),
EzKey("M-S-x", lazy.layout.spawn_split(terminal, "y", position="previous")),
# Motions to move focus. The names are compatible with built-in layouts.
EzKey("M-h", lazy.layout.left()),
EzKey("M-l", lazy.layout.right()),
EzKey("M-k", lazy.layout.up()),
EzKey("M-j", lazy.layout.down()),
EzKey("M-d", lazy.layout.prev_tab()),
EzKey("M-f", lazy.layout.next_tab()),
# Precise motions to move directly to specific tabs at the nearest tab level
EzKey("M-1", lazy.layout.focus_nth_tab(1, level=-1)),
EzKey("M-2", lazy.layout.focus_nth_tab(2, level=-1)),
EzKey("M-3", lazy.layout.focus_nth_tab(3, level=-1)),
EzKey("M-4", lazy.layout.focus_nth_tab(4, level=-1)),
EzKey("M-5", lazy.layout.focus_nth_tab(5, level=-1)),
# Precise motions to move to specific windows. The options provided here let
# us pick the nth window counting only from under currently active [sub]tabs
EzKey("C-1", lazy.layout.focus_nth_window(1, ignore_inactive_tabs_at_levels=[1,2])),
EzKey("C-2", lazy.layout.focus_nth_window(2, ignore_inactive_tabs_at_levels=[1,2])),
EzKey("C-3", lazy.layout.focus_nth_window(3, ignore_inactive_tabs_at_levels=[1,2])),
EzKey("C-4", lazy.layout.focus_nth_window(4, ignore_inactive_tabs_at_levels=[1,2])),
EzKey("C-5", lazy.layout.focus_nth_window(5, ignore_inactive_tabs_at_levels=[1,2])),
# Resize operations
EzKey("M-C-h", lazy.layout.resize("left", 100)),
EzKey("M-C-l", lazy.layout.resize("right", 100)),
EzKey("M-C-k", lazy.layout.resize("up", 100)),
EzKey("M-C-j", lazy.layout.resize("down", 100)),
# Swap windows/tabs with neighbors
EzKey("M-S-h", lazy.layout.swap("left")),
EzKey("M-S-l", lazy.layout.swap("right")),
EzKey("M-S-k", lazy.layout.swap("up")),
EzKey("M-S-j", lazy.layout.swap("down")),
EzKey("A-S-d", lazy.layout.swap_tabs("previous")),
EzKey("A-S-f", lazy.layout.swap_tabs("next")),
# Manipulate selections after entering container-select mode
EzKey("M-o", lazy.layout.select_container_outer()),
EzKey("M-i", lazy.layout.select_container_inner()),
# It's kinda nice to have more advanced window management commands under a
# qtile key chord.
KeyChord(
["mod4"],
"w",
[
# Use something like rofi to pick GUI apps to open as splits/tabs.
EzKey("v", lazy.layout.spawn_split(rofi_run_cmd, "x")),
EzKey("x", lazy.layout.spawn_split(rofi_run_cmd, "y")),
EzKey("t", lazy.layout.spawn_tab(rofi_run_cmd)),
EzKey("S-t", lazy.layout.spawn_tab(rofi_run_cmd, new_level=True)),
# Toggle container-selection mode to split/tab over containers of
# multiple windows. Manipulate using select_container_outer()/select_container_inner()
EzKey("C-v", lazy.layout.toggle_container_select_mode()),
EzKey("o", lazy.layout.pull_out()),
EzKey("u", lazy.layout.pull_out_to_tab()),
EzKey("r", lazy.layout.rename_tab()),
# Directional commands to merge windows with their neighbor into subtabs.
KeyChord(
[],
"m",
[
EzKey("h", lazy.layout.merge_to_subtab("left")),
EzKey("l", lazy.layout.merge_to_subtab("right")),
EzKey("j", lazy.layout.merge_to_subtab("down")),
EzKey("k", lazy.layout.merge_to_subtab("up")),
# Merge entire tabs with each other as splits
EzKey("S-h", lazy.layout.merge_tabs("previous")),
EzKey("S-l", lazy.layout.merge_tabs("next")),
],
),
# Directional commands for push_in() to move window inside neighbor space.
KeyChord(
[],
"i",
[
EzKey("j", lazy.layout.push_in("down")),
EzKey("k", lazy.layout.push_in("up")),
EzKey("h", lazy.layout.push_in("left")),
EzKey("l", lazy.layout.push_in("right")),
# It's nice to be able to push directly into the deepest
# neighbor node when desired. The default bindings above
# will have us push into the largest neighbor container.
EzKey(
"S-j",
lazy.layout.push_in("down", dest_selection="mru_deepest"),
),
EzKey(
"S-k",
lazy.layout.push_in("up", dest_selection="mru_deepest"),
),
EzKey(
"S-h",
lazy.layout.push_in("left", dest_selection="mru_deepest"),
),
EzKey(
"S-l",
lazy.layout.push_in("right", dest_selection="mru_deepest"),
),
],
),
]
),
# Your other bindings
# ...
]
```
#### 3. [Optional] Add the BonsaiBar widget to your qtile bar
qtile-bonsai comes with an optional `BonsaiBar` widget that lets you view all
your top-level tabs on the qtile-bar.
The default behavior is to automatically hide the top-level/outermost tab-bar if
there is a `BonsaiBar` widget on the relevant screen. If there isn't, the tab
bar is shown as usual.
```python
from libqtile import bar
from libqtile.config import Screen
from qtile_bonsai import BonsaiBar
screens = [
Screen(top=bar.Bar([
BonsaiBar(**{
# "length": 500,
# "sync_with": "bonsai_on_same_screen",
# "tab.width": 50,
# ...
}),
# ... your other widgets ...
])),
]
```
## Visual Guide
Click on the image to open a web view with the full guide.
<a href="https://aravinda0.github.io/qtile-bonsai/static/visual_guide/">![Visual Guide](static/visual_guide/visual_guide_preview.png)</a>
## Reference
### Layout Configuration
> [!TIP]
> Most options have subtab-level support! ie. you can have one setting for top
> level windows and another setting for windows under 2nd level subtabs. eg:
>
> ```python
> Bonsai({
> "window.margin": 10,
> "L2.window.margin": 5,
> })
> ```
>
> The format is `L<subtab-level>.<option-name> = <value>`
<br>
| Option Name | Default Value | Description |
| --- | --- | --- |
|`window.margin` | 0 | Size of the margin space around windows.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`window.single.margin` | (unset) | Size of the margin space around a window<br>when it is the single window remaining<br>under a top-level tab.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering.<br>If not specified, will fall back to<br>reading from `window.margin`. |
|`window.border_size` | 1 | Width of the border around windows. Must<br>be a single integer value since that's<br>what qtile allows for window borders. |
|`window.single.border_size` | (unset) | Size of the border around a window when<br>it is the single window remaining under<br>a top-level tab.<br>Must be a single integer value since<br>that's what qtile allows for window<br>borders.<br>If not specified, will fall back to<br>reading from `window.border_size`. |
|`window.border_color` | Gruvbox.dull_yellow | Color of the border around windows |
|`window.active.border_color` | Gruvbox.vivid_yellow | Color of the border around an active<br>window |
|`window.normalize_on_remove` | True | Whether or not to normalize the<br>remaining windows after a window is<br>removed.<br>If `True`, the remaining sibling windows<br>will all become of equal size.<br>If `False`, the next (right/down) window<br>will take up the free space. |
|`window.default_add_mode` | tab | (Experimental)<br><br>Determines how windows get added if they<br>are not explicitly spawned as a split or<br>a tab.<br>Can be one of "tab" or "match_previous".<br>If "match_previous", then then new<br>window will get added in the same way<br>the previous window was. eg. if the<br>previous window was added as a y-split,<br>so will the new window.<br><br>NOTE:<br>Setting this to "tab" may seem<br>convenient, since externally spawned GUI<br>apps get added as background tabs<br>instead of messing up the current split<br>layout.<br>But due to how the window creation flow<br>happens, when many splits are requested<br>in quick succession, this may cause some<br>windows requested as a split to open up<br>as a tab instead. |
|`tab_bar.height` | 20 | Height of tab bars |
|`tab_bar.hide_when` | single_tab | When to hide the tab bar. Allowed values<br>are 'never', 'always', 'single_tab'.<br><br>When 'single_tab' is configured, the bar<br>is not shown whenever there is a lone<br>tab remaining, but shows up again when<br>another tab is added.<br><br>For nested tab levels, configuring<br>'always' or 'single_tab' actually means<br>that when only a single tab remains, its<br>contents get 'merged' upwards,<br>eliminating the sub-tab level. |
|`tab_bar.hide_L1_when_bonsai_bar_on_screen` | True | For L1 (top level) tab bars only. If<br>`True`, the L1 tab bar is hidden away if<br>there is a `BonsaiBar` widget on the<br>screen this layout's group is on.<br>Otherwise the the L1 tab bar is shown<br>(depending on `tab_bar.hide_when`).<br><br>This is dynamic and essentially makes it<br>so the L1 tab bar shows up 'when<br>required'.<br>Handy in multi-screen setups if some<br>screens aren't configured to have a<br>qtile-bar, but the main screen does and<br>has a `BonsaiBar` widget as well.<br><br>Note that this takes precedence over<br>`tab_bar.hide_when` for L1 bars. |
|`tab_bar.margin` | 0 | Size of the margin space around tab<br>bars.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`tab_bar.border_size` | 0 | Size of the border around tab bars.<br>Must be a single integer value since<br>that's what qtile allows for window<br>borders. |
|`tab_bar.border_color` | Gruvbox.dark_yellow | Color of border around tab bars |
|`tab_bar.bg_color` | Gruvbox.bg0 | Background color of tab bars, beind<br>their tabs |
|`tab_bar.tab.width` | 50 | Width of a tab on a tab bar.<br><br>Can be an int or `auto`. If `auto`, the<br>tabs take up as much of the available<br>screen space as possible.<br><br>Note that this width follows the 'margin<br>box'/'principal box' model, so it<br>includes any configured margin amount. |
|`tab_bar.tab.margin` | 0 | Size of the space on either outer side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`tab_bar.tab.padding` | 0 | Size of the space on either inner side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`tab_bar.tab.bg_color` | Gruvbox.dull_yellow | Background color of individual tabs |
|`tab_bar.tab.fg_color` | Gruvbox.fg1 | Foreground text color of individual tabs |
|`tab_bar.tab.font_family` | Mono | Font family to use for tab titles |
|`tab_bar.tab.font_size` | 13 | Font size to use for tab titles |
|`tab_bar.tab.active.bg_color` | Gruvbox.vivid_yellow | Background color of active tabs |
|`tab_bar.tab.active.fg_color` | Gruvbox.bg0_hard | Foreground text color of the active tab |
|`tab_bar.tab.title_provider` | None | A callback that generates the title for<br>a tab. The callback accepts 3 parameters<br>and returns the final title string. The<br>params are:<br>1. `index`:<br> The index of the current tab in the<br> list of tabs.<br>2. `active_pane`:<br> The active `Pane` instance under<br> this tab. A `Pane` is just a<br> container for a window and can be<br> accessed via `pane.window`.<br>3. `tab`:<br> The current `Tab` instance.<br><br>For example, here's a callback that<br>returns the active window's title:<br>def my_title_provider(index,<br>active_pane, tab):<br> return active_pane.window.name |
|`container_select_mode.border_size` | 3 | Size of the border around the active<br>selection when `container_select_mode`<br>is active. |
|`container_select_mode.border_color` | Gruvbox.dark_purple | Color of the border around the active<br>selection when `container_select_mode`<br>is active. |
|`auto_cwd_for_terminals` | True | (Experimental)<br><br>If `True`, when spawning new windows by<br>specifying a `program` that happens to<br>be a well-known terminal emulator, will<br>try to open the new terminal window in<br>same working directory as the last<br>focused window. |
|`restore.threshold_seconds` | 4 | You likely don't need to tweak this.<br>Controls the time within which a<br>persisted state file is considered to be<br>from a recent qtile config-<br>reload/restart event. If the persisted<br>file is this many seconds old, we<br>restore our window tree from it. |
<br>
### Layout Commands
| Command Name | Description |
| --- | --- |
|`spawn_split` | Launch the provided `program` into a new window that splits the<br>currently focused window along the specified `axis`.<br><br>Args:<br> `program`:<br> The program to launch.<br> `axis`:<br> The axis along which to split the currently focused window. Can<br> be 'x' or 'y'.<br> An `x` split will end up with two left/right windows.<br> A `y` split will end up with two top/bottom windows.<br> `ratio`:<br> The ratio of sizes by which to split the current window.<br> If a window has a width of 100, then splitting on the x-axis<br> with a ratio = 0.3 will result in a left window of width 30 and<br> a right window of width 70.<br> Defaults to 0.5.<br> `normalize`:<br> If `True`, overrides `ratio` and leads to the new window and<br> all sibling windows becoming of equal size along the<br> corresponding split axis.<br> Defaults to `True`.<br> `position`:<br> Whether the new split content appears after or before the<br> currently focused window.<br> Can be `"next"` or `"previous"`. Defaults to `"next"`.<br><br>Examples:<br>- `layout.spawn_split(my_terminal, "x")`<br>- `layout.spawn_split( my_terminal, "y", ratio=0.2, normalize=False)`<br>- `layout.spawn_split(my_terminal, "x", position="previous")` |
|`spawn_tab` | Launch the provided `program` into a new window as a new tab.<br><br>Args:<br> `program`:<br> The program to launch.<br> `new_level`:<br> If `True`, create a new sub-tab level with 2 tabs. The first<br> sub-tab being the currently focused window, the second sub-tab<br> being the newly launched program.<br> `level`:<br> If provided, launch the new window as a tab at the provided<br> `level` of tabs in the currently focused window's tab<br> hierarchy.<br> Level 1 is the topmost level.<br><br>Examples:<br> - `layout.spawn_tab(my_terminal)`<br> - `layout.spawn_tab(my_terminal, new_level=True)`<br> - `layout.spawn_tab("qutebrowser", level=1)` |
|`move_focus` | Move focus to the window in the specified direction relative to the<br>currently focused window. If there are multiple candidates, the most<br>recently focused of them will be chosen.<br>When `container_select_mode` is active, will similarly pick neighboring<br>nodes, which may consist of multiple windows under it.<br><br>Args:<br> `direction`:<br> The direction in which a neighbor is found to move focus to.<br> Can be "up"/"down"/"left"/"right".<br> `wrap`:<br> If `True`, will wrap around the edge and select items from the<br> other end of the screen. Defaults to `True`. |
|`left` | Same as `move_focus("left")`. For compatibility with API of other<br>built-in layouts. |
|`right` | Same as `move_focus("right")`. For compatibility with API of other<br>built-in layouts. |
|`up` | Same as `move_focus("up")`. For compatibility with API of other built-<br>in layouts. |
|`down` | Same as `move_focus("down")`. For compatibility with API of other<br>built-in layouts. |
|`next_tab` | Switch focus to the next tab. The window that was previously active<br>there will be focused.<br><br>Args:<br> `wrap`:<br> If `True`, will cycle back to the fist tab if invoked on the<br> last tab.<br> Defaults to `True`. |
|`prev_tab` | Same as `next_tab()` but switches focus to the previous tab. |
|`focus_nth_tab` | Switches focus to the nth tab at the specified tab `level`.<br><br>Args:<br> `n`:<br> The 1-based index of the tab that should be focused.<br> `level`:<br> When there are subtab levels at play, specifies which<br> `TabContainer's`<br> tabs among the hierarchy of active `TabContainers` is being<br> acted upon.<br> Tab levels are 1-based.<br> `level=1` indicates outermost/top-level tabs.<br> `level=-1` (default) indicates the innermost/nearest tabs.<br><br>Examples:<br> - `layout.focus_nth_tab(2) # 2nd top-level tab`<br> - `layout.focus_nth_tab(3, level=-1) # 3rd from nearest tabs` |
|`focus_nth_window` | Switches focus to the nth window.<br><br>Counting is always done based on the geospatial position of windows -<br>ie.<br>starting from the leftmost+innermost window (ie. we traverse leaves of<br>the tree, left to right).<br><br>Args:<br> `n`:<br> The 1-based index of the window in the list of all candidate<br> windows.<br> `ignore_inactive_tabs_at_levels`:<br> For the specified list of tab levels, only consider windows<br> under the active tab at that level, ignoring windows under<br> inactive/background tabs.<br><br> eg. `[1]` means we should start counting `n` from the first<br> window in the currently active level 1 (top-level) tab,<br> ignoring windows under inactive tabs. But if there are any<br> subtabs under this active tabs, we DO consider the inactive<br> windows under background/inactive subtabs.<br><br> eg. `[1,2]` means we start counting `n` from the first window<br> of the active top-level tab, and if there are any level 2<br> subtabs under the active tab, we pick windows only from the<br> active level 2 tab as well, ignoring inactive subtabs.<br><br> eg. `[]` or `None` (default) means consider every single window<br> - even if it's inactive under a background tab.<br><br> eg. `[2]` means we start counting from the very first window at<br> the top level, even if it is inactive under a background tab.<br> But whenever there are level 2 subtabs to consider, we only<br> count its windows that are under the active level 2 subtab.<br><br> Examples:<br> - `layout.focus_nth_window(1)`<br> - layout.focus_nth_window(3,<br> ignore_inactive_tabs_at_levels=[1])<br> - layout.focus_nth_window(2, ignore_inactive_tabs_at_levels=[1,<br> 2]) |
|`resize` | Resizes by moving an appropriate border leftwards. Usually this is the<br>right/bottom border, but for the 'last' node under a SplitContainer, it<br>will be the left/top border.<br><br>Basically the way tmux does resizing.<br><br>If there are multiple nested windows under the area being resized,<br>those windows are resized proportionally.<br><br>Args:<br> `amount`:<br> The amount by which to resize.<br><br>Examples:<br> - `layout.resize("left", 100)`<br> - `layout.resize("right", 100)` |
|`swap` | Swaps the currently focused window with the nearest window in the<br>specified direction. If there are multiple candidates to pick from,<br>then the most recently focused one is chosen.<br><br>Args:<br> `wrap`:<br> If `True`, will wrap around the edge and select windows from<br> the other end of the screen to swap.<br> Defaults to `False`. |
|`swap_tabs` | Swaps the currently active tab with the previous tab.<br><br>Args:<br> `wrap`:<br> If `True`, will wrap around the edge of the tab bar and swap<br> with the last tab.<br> Defaults to `True`. |
|`rename_tab` | Rename the currently active tab.<br><br>Args:<br> `widget`:<br> The qtile widget that should be used for obtaining user input<br> for the renaming. The 'prompt' widget is used by default. |
|`merge_tabs` | Merge the currently active tab with another tab, such that both tabs'<br>contents now appear in 2 splits.<br><br>Args:<br> `direction`:<br> Which neighbor tab to merge with. Can be either "next" or<br> "previous".<br> `axis`:<br> The axis along which the merged content should appear as<br> splits.<br><br>Examples:<br> - `layout.merge_tabs("previous")`<br> - `layout.merge_tabs("next", "y")` |
|`merge_to_subtab` | Merge the currently focused window (or an ancestor node) with a<br>neighboring node in the specified `direction`, so that they both come<br>under a (possibly new) subtab.<br><br>Args:<br> `direction`:<br> The direction in which to find a neighbor to merge with.<br> `src_selection`:<br> Determines how the source window/node should be resolved. ie.<br> do we pick just the current window, or all windows under an<br> appropriate ancestor container.<br> Valid values are defined in `NodeHierarchySelectionMode`. See<br> below.<br> `dest_selection`:<br> Determines how the neighboring node should be resolved, similar<br> to how `src_selection` is resolved.<br> Valid values are defined in `NodeHierarchySelectionMode`. See<br> below.<br> `normalize`:<br> If `True`, any removals during the merge process will ensure<br> all sibling nodes are resized to be of equal dimensions.<br><br>Valid values for `NodeHierarchySelectionMode` are:<br> `"mru_deepest"`:<br> Pick a single innermost window. If there are multiple such<br> neighboring windows, pick the most recently used (MRU) one.<br> `"mru_subtab_else_deepest"` (default):<br> If the target is under a subtab, pick the subtab. If there is<br> no subtab in play, behaves like `mru_deepest`.<br> `"mru_largest"`<br> Given a window, pick the largest ancestor node that the<br> window's border is a fragment of. This resolves to a<br> SplitContainer or a TabContainer.<br> `"mru_subtab_else_largest"`<br> If the target is under a subtab, pick the subtab. If there is<br> no subtab in play, behaves like `mru_largest`.<br><br>Examples:<br> layout.merge_to_subtab( "right",<br> dest_selection="mru_subtab_else_deepest", )<br> layout.merge_to_subtab( "up", src_selection="mru_deepest",<br> dest_selection="mru_deepest", ) |
|`push_in` | Move the currently focused window (or a related node in its hierarchy)<br>into a neighboring window's container.<br><br>Args:<br> `direction`:<br> The direction in which to find a neighbor whose container we<br> push into.<br> `src_selection`:<br> (See docs in `merge_to_subtab()`)<br> `dest_selection`:<br> (See docs in `merge_to_subtab()`)<br> `normalize`:<br> If `True`, any removals during the process will ensure all<br> sibling nodes are resized to be of equal dimensions.<br> `wrap`:<br> If `True`, will wrap around the edge of the screen and push<br> into the container on the other end.<br><br>Examples:<br>- `layout.push_in("right", dest_selection="mru_deepest")`<br>- `layout.push_in("down", dest_selection="mru_largest", wrap=False)` |
|`pull_out` | Move the currently focused window out from its SplitContainer into an<br>ancestor SplitContainer at a higher level. It effectively moves a<br>window 'outwards'.<br><br>Args:<br> `position`:<br> Whether the pulled out node appears before or after its<br> original container node.<br> Can be `"next"` or `"previous"`. Defaults to `"previous"`.<br> `src_selection`:<br> Can either be `"mru_deepest"` (default) or<br> `"mru_subtab_else_deepest"`.<br> (See docs in `merge_to_subtab()`)<br> `normalize`:<br> If `True`, all sibling nodes involved in the rearrangement are<br> resized to be of equal dimensions.<br><br>Examples:<br> - `layout.pull_out()`<br> - `layout.pull_out(src_selection="mru_subtab_else_deepest")`<br> - `layout.pull_out(position="next")` |
|`pull_out_to_tab` | Extract the currently focused window into a new tab at the nearest<br>TabContainer.<br><br>Args:<br> `normalize`:<br> If `True`, any removals during the process will ensure all<br> sibling nodes are resized to be of equal dimensions. |
|`normalize` | Starting from the focused window's container, make all windows in the<br>container of equal size.<br><br>Args:<br> `recurse`:<br> If `True`, then nested nodes are also normalized similarly. |
|`normalize_tab` | Starting from the focused window's tab, make all windows in the tab of<br>equal size under their respective containers.<br><br>Args:<br> `recurse`:<br> If `True`, then nested nodes are also normalized similarly.<br> Defaults to `True`. |
|`normalize_all` | Make all windows under all tabs be of equal size under their respective<br>containers. |
|`toggle_container_select_mode` | Enable container-select mode where we can select not just a window, but<br>even their container nodes.<br><br>This will activate a special border around the active selection. You<br>can move its focus around using the same bindings as for switching<br>window focus. You can also select upper/parent or lower/child nodes<br>with the `select_container_outer()` and `select_container_inner()`<br>commands.<br><br>Handy for cases where you want to split over a collection of windows or<br>make a new subtab level over a collection of windows.<br><br>Aside from focus-switching motions, the only operations supported are<br>`spawn_split()` and `spawn_tab()`. Triggering other commands will<br>simply exit container-select mode. |
|`select_container_inner` | When in container-select mode, it will narrow the active selection by<br>selecting the first descendent node. |
|`select_container_outer` | When in container-select mode, it will expand the active selection by<br>selecting the next ancestor node. |
|`tree_repr` | Returns a YAML-like text representation of the internal tree hierarchy. |
<br>
### BonsaiBar Widget
| Option Name | Default Value | Description |
| --- | --- | --- |
|`length` | 500 | The standard `length` property of qtile<br>widgets.<br><br>As usual, it can be a fixed integer, or<br>one of the 'special' bar constants:<br>`bar.CALCULATED` or `bar.STRETCH`. |
|`sync_with` | bonsai_on_same_screen | The Bonsai layout whose state should be<br>rendered on this widget.<br><br>Can be one of the following:<br> - `bonsai_with_focus`:<br> The Bonsai layout of the window<br> that is currently focused. This<br> is relevant in a multi-screen<br> setup - the widget will keep<br> updating based on which screen's<br> Bonsai layout has focus.<br> - `bonsai_on_same_screen`:<br> The widget will stick to<br> displaying the state of the<br> Bonsai layout that is on the<br> same screen as the widget's bar. |
|`bg_color` | None | Background color of the bar.<br>If None, the qtile-bar's' background<br>color is used. |
|`font_family` | Mono | Font family to use for tab titles |
|`font_size` | 15 | Size of the font to use for tab titles |
|`tab.width` | 50 | Width of a tab on the bar.<br><br>Can be an int or `auto`. If `auto`, the<br>tabs take up as much of the available<br>space on the bar as possible.<br><br>Note that if the `length` option is set<br>to `bar.CALCULATED`, then you cannot<br>provide `auto` here, as we would need<br>fixed tab width values to perform the<br>`bar.CALCULATED` computation.<br><br>Note that this width follows the 'margin<br>box'/'principal box' model, so it<br>includes any configured margin amount. |
|`tab.margin` | 0 | Size of the space on either outer side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`tab.padding` | 0 | Size of the space on either inner side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |
|`tab.bg_color` | Gruvbox.dull_yellow | Background color of the inactive tabs |
|`tab.fg_color` | Gruvbox.fg1 | Foreground color of the inactive tabs |
|`tab.active.bg_color` | Gruvbox.vivid_yellow | Background color of active tab |
|`tab.active.fg_color` | Gruvbox.bg0_hard | Foreground color of active tab |
|`container_select_mode.indicator.bg_color` | Gruvbox.bg0_hard | Background color of active tab when in<br>container_select_mode. |
|`container_select_mode.indicator.fg_color` | Gruvbox.bg0_hard | Foreground color of active tab when in<br>container_select_mode. |
## Support
For any bug reports, please file an issue. For questions/discussions, use the
[GitHub Discussions](https://github.com/aravinda0/qtile-bonsai/discussions)
section, or you can ask on the [qtile subreddit](https://www.reddit.com/r/qtile/).
Raw data
{
"_id": null,
"home_page": null,
"name": "qtile-bonsai",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.13,>=3.10",
"maintainer_email": null,
"keywords": "qtile, bonsai, tiling, window, manager, layout",
"author": null,
"author_email": "Aravinda Rao <maniacalace@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/bc/be/b558e99c0f09d3949d45b942fec10793cb8e1dfc1e2be6e5560ff908d6b9/qtile_bonsai-0.4.0.tar.gz",
"platform": null,
"description": "<!-- \nREADME.md is a generated file! \n\nTo make modifications, make sure you're editing `templates/README.template.md`.\nThen generate the README with `python scripts/generate_readme.py`\n-->\n\n\n<h1 align=\"center\">\n Qtile Bonsai\n</h1>\n\n<p align=\"center\">\n <a href=\"https://github.com/aravinda0/qtile-bonsai/actions?query=branch%3Amaster\"><img src=\"https://img.shields.io/github/actions/workflow/status/aravinda0/qtile-bonsai/ci.yml?style=for-the-badge&logo=github\"></a>\n <a href=\"https://codecov.io/gh/aravinda0/qtile-bonsai\"><img src=\"https://img.shields.io/codecov/c/github/aravinda0/qtile-bonsai?style=for-the-badge&logo=codecov\"></a>\n <a href=\"https://github.com/aravinda0/qtile-bonsai/blob/master/LICENSE.txt\"><img src=\"https://img.shields.io/github/license/aravinda0/qtile-bonsai?style=for-the-badge\"></a>\n</p>\n<p align=\"center\">\n <a href=\"https://aravinda0.github.io/qtile-bonsai/static/visual_guide/\"><img src=\"https://img.shields.io/badge/Visual%20Guide-d79921?style=for-the-badge\"></a>\n <a href=\"#reference\"><img src=\"https://img.shields.io/badge/Reference-d79921?style=for-the-badge\"></a>\n</p>\n\n<br/>\n\n## Introduction\n\n_Qtile Bonsai_ provides a flexible layout for the\n[qtile](https://github.com/qtile/qtile) tiling window manager that allows you to\narrange windows as tabs, splits and even subtabs inside splits. \n\nIt also provides an API with window-management operations that allow for quick\naccess and rearrangements of tabs/windows.\n\nFor a quick feeler, look at the demo below, or the [visual guide](#visual-guide)\nfurther below.\n\n<br>\n\n<!-- GitHub will simply render this into a video it seems. Generated by dragging\nand dropping into GitHub README editor GUI -->\nhttps://github.com/aravinda0/qtile-bonsai/assets/960763/b74e559a-30c0-4de7-86f4-ecea8e68125c\n\n\n<br/>\n\n## Getting Started\n\n### Installation\n\nAssuming you already have\n[qtile up and running](https://docs.qtile.org/en/latest/manual/install/), you\nhave the following options for installation.\n\n#### PyPI\n\n```bash\npip install qtile-bonsai \n```\n\n> [!NOTE]\n> If you have qtile installed globally (eg. via your distro's package manager),\n> you likely have to do:\n> \n> ```bash\n> pip install qtile-bonsai --break-system-packages\n> ```\n> \n> Nowadays `pip` tries to play it safe and not potentially modify some\n> dependency that your system's Python-software may depend on. For qtile-bonsai,\n> using this flag should be safe.\n> \n> An alternative approach would be to have installed qtile via [pipx](https://github.com/pypa/pipx) \n> and then 'inject' qtile-bonsai into the same virtualenv where qtile resides:\n> \n> ```\n> pipx install qtile\n> pipx inject qtile qtile-bonsai\n> ```\n\n#### AUR\n\nFor arch-based distros, you can install it from the AUR either manually or with\nyour favorite AUR-helper. For example:\n\n```\nyay -S qtile-bonsai\n```\n\n### Configuration\n\n#### 1. Make Bonsai available as a layout in your [qtile config](https://docs.qtile.org/en/latest/manual/config/default.html)\n\n```python\nfrom qtile_bonsai import Bonsai\n\n\nlayouts = [\n Bonsai(**{\n # Specify your options here. These examples are defaults.\n \"window.border_size\": 1,\n \"tab_bar.height\": 20,\n \n # You can specify subtab level specific options if desired by prefixing\n # the option key with the appropriate level, eg. L1, L2, L3 etc.\n # For example, the following options affect only 2nd level subtabs and\n # their windows:\n # \"L2.window.border_color\": \"#ff0000\",\n # \"L2.window.margin\": 5,\n }),\n]\n```\n\n#### 2. Add your personal keybindings to your qtile config\n\n```python\nfrom libqtile.config import EzKey, KeyChord\nfrom libqtile.lazy import lazy\nfrom libqtile.utils import guess_terminal\n\n\nterminal = guess_terminal()\nrofi_run_cmd = \"rofi -show drun -m -1\"\n\nkeys = [\n # Open your terminal emulator quickly. See further below for how to\n # directly open other apps as splits/tabs using something like rofi.\n EzKey(\"M-v\", lazy.layout.spawn_split(terminal, \"x\")),\n EzKey(\"M-x\", lazy.layout.spawn_split(terminal, \"y\")),\n EzKey(\"M-t\", lazy.layout.spawn_tab(terminal)),\n EzKey(\"M-S-t\", lazy.layout.spawn_tab(terminal, new_level=True)),\n \n # Sometimes it's handy to have a split open in the 'previous' position\n EzKey(\"M-S-v\", lazy.layout.spawn_split(terminal, \"x\", position=\"previous\")),\n EzKey(\"M-S-x\", lazy.layout.spawn_split(terminal, \"y\", position=\"previous\")),\n\n # Motions to move focus. The names are compatible with built-in layouts.\n EzKey(\"M-h\", lazy.layout.left()),\n EzKey(\"M-l\", lazy.layout.right()),\n EzKey(\"M-k\", lazy.layout.up()),\n EzKey(\"M-j\", lazy.layout.down()),\n EzKey(\"M-d\", lazy.layout.prev_tab()),\n EzKey(\"M-f\", lazy.layout.next_tab()),\n \n # Precise motions to move directly to specific tabs at the nearest tab level\n EzKey(\"M-1\", lazy.layout.focus_nth_tab(1, level=-1)),\n EzKey(\"M-2\", lazy.layout.focus_nth_tab(2, level=-1)),\n EzKey(\"M-3\", lazy.layout.focus_nth_tab(3, level=-1)),\n EzKey(\"M-4\", lazy.layout.focus_nth_tab(4, level=-1)),\n EzKey(\"M-5\", lazy.layout.focus_nth_tab(5, level=-1)),\n \n # Precise motions to move to specific windows. The options provided here let\n # us pick the nth window counting only from under currently active [sub]tabs\n EzKey(\"C-1\", lazy.layout.focus_nth_window(1, ignore_inactive_tabs_at_levels=[1,2])),\n EzKey(\"C-2\", lazy.layout.focus_nth_window(2, ignore_inactive_tabs_at_levels=[1,2])),\n EzKey(\"C-3\", lazy.layout.focus_nth_window(3, ignore_inactive_tabs_at_levels=[1,2])),\n EzKey(\"C-4\", lazy.layout.focus_nth_window(4, ignore_inactive_tabs_at_levels=[1,2])),\n EzKey(\"C-5\", lazy.layout.focus_nth_window(5, ignore_inactive_tabs_at_levels=[1,2])),\n\n # Resize operations\n EzKey(\"M-C-h\", lazy.layout.resize(\"left\", 100)),\n EzKey(\"M-C-l\", lazy.layout.resize(\"right\", 100)),\n EzKey(\"M-C-k\", lazy.layout.resize(\"up\", 100)),\n EzKey(\"M-C-j\", lazy.layout.resize(\"down\", 100)),\n\n # Swap windows/tabs with neighbors\n EzKey(\"M-S-h\", lazy.layout.swap(\"left\")),\n EzKey(\"M-S-l\", lazy.layout.swap(\"right\")),\n EzKey(\"M-S-k\", lazy.layout.swap(\"up\")),\n EzKey(\"M-S-j\", lazy.layout.swap(\"down\")),\n EzKey(\"A-S-d\", lazy.layout.swap_tabs(\"previous\")),\n EzKey(\"A-S-f\", lazy.layout.swap_tabs(\"next\")),\n \n # Manipulate selections after entering container-select mode\n EzKey(\"M-o\", lazy.layout.select_container_outer()),\n EzKey(\"M-i\", lazy.layout.select_container_inner()),\n\n # It's kinda nice to have more advanced window management commands under a\n # qtile key chord.\n KeyChord(\n [\"mod4\"],\n \"w\",\n [\n # Use something like rofi to pick GUI apps to open as splits/tabs.\n EzKey(\"v\", lazy.layout.spawn_split(rofi_run_cmd, \"x\")),\n EzKey(\"x\", lazy.layout.spawn_split(rofi_run_cmd, \"y\")),\n EzKey(\"t\", lazy.layout.spawn_tab(rofi_run_cmd)),\n EzKey(\"S-t\", lazy.layout.spawn_tab(rofi_run_cmd, new_level=True)),\n \n # Toggle container-selection mode to split/tab over containers of\n # multiple windows. Manipulate using select_container_outer()/select_container_inner()\n EzKey(\"C-v\", lazy.layout.toggle_container_select_mode()),\n \n EzKey(\"o\", lazy.layout.pull_out()),\n EzKey(\"u\", lazy.layout.pull_out_to_tab()),\n \n EzKey(\"r\", lazy.layout.rename_tab()),\n \n # Directional commands to merge windows with their neighbor into subtabs.\n KeyChord(\n [],\n \"m\",\n [\n EzKey(\"h\", lazy.layout.merge_to_subtab(\"left\")),\n EzKey(\"l\", lazy.layout.merge_to_subtab(\"right\")),\n EzKey(\"j\", lazy.layout.merge_to_subtab(\"down\")),\n EzKey(\"k\", lazy.layout.merge_to_subtab(\"up\")),\n\n # Merge entire tabs with each other as splits\n EzKey(\"S-h\", lazy.layout.merge_tabs(\"previous\")),\n EzKey(\"S-l\", lazy.layout.merge_tabs(\"next\")),\n ],\n ),\n \n # Directional commands for push_in() to move window inside neighbor space.\n KeyChord(\n [],\n \"i\",\n [\n EzKey(\"j\", lazy.layout.push_in(\"down\")),\n EzKey(\"k\", lazy.layout.push_in(\"up\")),\n EzKey(\"h\", lazy.layout.push_in(\"left\")),\n EzKey(\"l\", lazy.layout.push_in(\"right\")),\n \n # It's nice to be able to push directly into the deepest\n # neighbor node when desired. The default bindings above\n # will have us push into the largest neighbor container.\n EzKey(\n \"S-j\",\n lazy.layout.push_in(\"down\", dest_selection=\"mru_deepest\"),\n ),\n EzKey(\n \"S-k\",\n lazy.layout.push_in(\"up\", dest_selection=\"mru_deepest\"),\n ),\n EzKey(\n \"S-h\",\n lazy.layout.push_in(\"left\", dest_selection=\"mru_deepest\"),\n ),\n EzKey(\n \"S-l\",\n lazy.layout.push_in(\"right\", dest_selection=\"mru_deepest\"),\n ),\n ],\n ),\n ]\n ),\n \n # Your other bindings\n # ...\n]\n```\n\n#### 3. [Optional] Add the BonsaiBar widget to your qtile bar\n\nqtile-bonsai comes with an optional `BonsaiBar` widget that lets you view all\nyour top-level tabs on the qtile-bar. \n\nThe default behavior is to automatically hide the top-level/outermost tab-bar if\nthere is a `BonsaiBar` widget on the relevant screen. If there isn't, the tab\nbar is shown as usual. \n\n\n```python\n\nfrom libqtile import bar\nfrom libqtile.config import Screen\n\nfrom qtile_bonsai import BonsaiBar\n\n\nscreens = [\n Screen(top=bar.Bar([\n BonsaiBar(**{\n # \"length\": 500,\n # \"sync_with\": \"bonsai_on_same_screen\",\n # \"tab.width\": 50,\n # ...\n }),\n # ... your other widgets ...\n ])),\n]\n```\n\n\n## Visual Guide\n\nClick on the image to open a web view with the full guide.\n\n<a href=\"https://aravinda0.github.io/qtile-bonsai/static/visual_guide/\">![Visual Guide](static/visual_guide/visual_guide_preview.png)</a>\n\n\n## Reference\n\n### Layout Configuration\n\n> [!TIP]\n> Most options have subtab-level support! ie. you can have one setting for top\n> level windows and another setting for windows under 2nd level subtabs. eg:\n> \n> ```python\n> Bonsai({\n> \"window.margin\": 10,\n> \"L2.window.margin\": 5,\n> })\n> ```\n> \n> The format is `L<subtab-level>.<option-name> = <value>`\n\n<br>\n\n| Option Name | Default Value | Description |\n| --- | --- | --- |\n|`window.margin` | 0 | Size of the margin space around windows.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`window.single.margin` | (unset) | Size of the margin space around a window<br>when it is the single window remaining<br>under a top-level tab.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering.<br>If not specified, will fall back to<br>reading from `window.margin`. |\n|`window.border_size` | 1 | Width of the border around windows. Must<br>be a single integer value since that's<br>what qtile allows for window borders. |\n|`window.single.border_size` | (unset) | Size of the border around a window when<br>it is the single window remaining under<br>a top-level tab.<br>Must be a single integer value since<br>that's what qtile allows for window<br>borders.<br>If not specified, will fall back to<br>reading from `window.border_size`. |\n|`window.border_color` | Gruvbox.dull_yellow | Color of the border around windows |\n|`window.active.border_color` | Gruvbox.vivid_yellow | Color of the border around an active<br>window |\n|`window.normalize_on_remove` | True | Whether or not to normalize the<br>remaining windows after a window is<br>removed.<br>If `True`, the remaining sibling windows<br>will all become of equal size.<br>If `False`, the next (right/down) window<br>will take up the free space. |\n|`window.default_add_mode` | tab | (Experimental)<br><br>Determines how windows get added if they<br>are not explicitly spawned as a split or<br>a tab.<br>Can be one of \"tab\" or \"match_previous\".<br>If \"match_previous\", then then new<br>window will get added in the same way<br>the previous window was. eg. if the<br>previous window was added as a y-split,<br>so will the new window.<br><br>NOTE:<br>Setting this to \"tab\" may seem<br>convenient, since externally spawned GUI<br>apps get added as background tabs<br>instead of messing up the current split<br>layout.<br>But due to how the window creation flow<br>happens, when many splits are requested<br>in quick succession, this may cause some<br>windows requested as a split to open up<br>as a tab instead. |\n|`tab_bar.height` | 20 | Height of tab bars |\n|`tab_bar.hide_when` | single_tab | When to hide the tab bar. Allowed values<br>are 'never', 'always', 'single_tab'.<br><br>When 'single_tab' is configured, the bar<br>is not shown whenever there is a lone<br>tab remaining, but shows up again when<br>another tab is added.<br><br>For nested tab levels, configuring<br>'always' or 'single_tab' actually means<br>that when only a single tab remains, its<br>contents get 'merged' upwards,<br>eliminating the sub-tab level. |\n|`tab_bar.hide_L1_when_bonsai_bar_on_screen` | True | For L1 (top level) tab bars only. If<br>`True`, the L1 tab bar is hidden away if<br>there is a `BonsaiBar` widget on the<br>screen this layout's group is on.<br>Otherwise the the L1 tab bar is shown<br>(depending on `tab_bar.hide_when`).<br><br>This is dynamic and essentially makes it<br>so the L1 tab bar shows up 'when<br>required'.<br>Handy in multi-screen setups if some<br>screens aren't configured to have a<br>qtile-bar, but the main screen does and<br>has a `BonsaiBar` widget as well.<br><br>Note that this takes precedence over<br>`tab_bar.hide_when` for L1 bars. |\n|`tab_bar.margin` | 0 | Size of the margin space around tab<br>bars.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`tab_bar.border_size` | 0 | Size of the border around tab bars.<br>Must be a single integer value since<br>that's what qtile allows for window<br>borders. |\n|`tab_bar.border_color` | Gruvbox.dark_yellow | Color of border around tab bars |\n|`tab_bar.bg_color` | Gruvbox.bg0 | Background color of tab bars, beind<br>their tabs |\n|`tab_bar.tab.width` | 50 | Width of a tab on a tab bar.<br><br>Can be an int or `auto`. If `auto`, the<br>tabs take up as much of the available<br>screen space as possible.<br><br>Note that this width follows the 'margin<br>box'/'principal box' model, so it<br>includes any configured margin amount. |\n|`tab_bar.tab.margin` | 0 | Size of the space on either outer side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`tab_bar.tab.padding` | 0 | Size of the space on either inner side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`tab_bar.tab.bg_color` | Gruvbox.dull_yellow | Background color of individual tabs |\n|`tab_bar.tab.fg_color` | Gruvbox.fg1 | Foreground text color of individual tabs |\n|`tab_bar.tab.font_family` | Mono | Font family to use for tab titles |\n|`tab_bar.tab.font_size` | 13 | Font size to use for tab titles |\n|`tab_bar.tab.active.bg_color` | Gruvbox.vivid_yellow | Background color of active tabs |\n|`tab_bar.tab.active.fg_color` | Gruvbox.bg0_hard | Foreground text color of the active tab |\n|`tab_bar.tab.title_provider` | None | A callback that generates the title for<br>a tab. The callback accepts 3 parameters<br>and returns the final title string. The<br>params are:<br>1. `index`:<br> The index of the current tab in the<br> list of tabs.<br>2. `active_pane`:<br> The active `Pane` instance under<br> this tab. A `Pane` is just a<br> container for a window and can be<br> accessed via `pane.window`.<br>3. `tab`:<br> The current `Tab` instance.<br><br>For example, here's a callback that<br>returns the active window's title:<br>def my_title_provider(index,<br>active_pane, tab):<br> return active_pane.window.name |\n|`container_select_mode.border_size` | 3 | Size of the border around the active<br>selection when `container_select_mode`<br>is active. |\n|`container_select_mode.border_color` | Gruvbox.dark_purple | Color of the border around the active<br>selection when `container_select_mode`<br>is active. |\n|`auto_cwd_for_terminals` | True | (Experimental)<br><br>If `True`, when spawning new windows by<br>specifying a `program` that happens to<br>be a well-known terminal emulator, will<br>try to open the new terminal window in<br>same working directory as the last<br>focused window. |\n|`restore.threshold_seconds` | 4 | You likely don't need to tweak this.<br>Controls the time within which a<br>persisted state file is considered to be<br>from a recent qtile config-<br>reload/restart event. If the persisted<br>file is this many seconds old, we<br>restore our window tree from it. |\n\n\n<br>\n\n### Layout Commands\n\n| Command Name | Description |\n| --- | --- |\n|`spawn_split` | Launch the provided `program` into a new window that splits the<br>currently focused window along the specified `axis`.<br><br>Args:<br> `program`:<br> The program to launch.<br> `axis`:<br> The axis along which to split the currently focused window. Can<br> be 'x' or 'y'.<br> An `x` split will end up with two left/right windows.<br> A `y` split will end up with two top/bottom windows.<br> `ratio`:<br> The ratio of sizes by which to split the current window.<br> If a window has a width of 100, then splitting on the x-axis<br> with a ratio = 0.3 will result in a left window of width 30 and<br> a right window of width 70.<br> Defaults to 0.5.<br> `normalize`:<br> If `True`, overrides `ratio` and leads to the new window and<br> all sibling windows becoming of equal size along the<br> corresponding split axis.<br> Defaults to `True`.<br> `position`:<br> Whether the new split content appears after or before the<br> currently focused window.<br> Can be `\"next\"` or `\"previous\"`. Defaults to `\"next\"`.<br><br>Examples:<br>- `layout.spawn_split(my_terminal, \"x\")`<br>- `layout.spawn_split( my_terminal, \"y\", ratio=0.2, normalize=False)`<br>- `layout.spawn_split(my_terminal, \"x\", position=\"previous\")` |\n|`spawn_tab` | Launch the provided `program` into a new window as a new tab.<br><br>Args:<br> `program`:<br> The program to launch.<br> `new_level`:<br> If `True`, create a new sub-tab level with 2 tabs. The first<br> sub-tab being the currently focused window, the second sub-tab<br> being the newly launched program.<br> `level`:<br> If provided, launch the new window as a tab at the provided<br> `level` of tabs in the currently focused window's tab<br> hierarchy.<br> Level 1 is the topmost level.<br><br>Examples:<br> - `layout.spawn_tab(my_terminal)`<br> - `layout.spawn_tab(my_terminal, new_level=True)`<br> - `layout.spawn_tab(\"qutebrowser\", level=1)` |\n|`move_focus` | Move focus to the window in the specified direction relative to the<br>currently focused window. If there are multiple candidates, the most<br>recently focused of them will be chosen.<br>When `container_select_mode` is active, will similarly pick neighboring<br>nodes, which may consist of multiple windows under it.<br><br>Args:<br> `direction`:<br> The direction in which a neighbor is found to move focus to.<br> Can be \"up\"/\"down\"/\"left\"/\"right\".<br> `wrap`:<br> If `True`, will wrap around the edge and select items from the<br> other end of the screen. Defaults to `True`. |\n|`left` | Same as `move_focus(\"left\")`. For compatibility with API of other<br>built-in layouts. |\n|`right` | Same as `move_focus(\"right\")`. For compatibility with API of other<br>built-in layouts. |\n|`up` | Same as `move_focus(\"up\")`. For compatibility with API of other built-<br>in layouts. |\n|`down` | Same as `move_focus(\"down\")`. For compatibility with API of other<br>built-in layouts. |\n|`next_tab` | Switch focus to the next tab. The window that was previously active<br>there will be focused.<br><br>Args:<br> `wrap`:<br> If `True`, will cycle back to the fist tab if invoked on the<br> last tab.<br> Defaults to `True`. |\n|`prev_tab` | Same as `next_tab()` but switches focus to the previous tab. |\n|`focus_nth_tab` | Switches focus to the nth tab at the specified tab `level`.<br><br>Args:<br> `n`:<br> The 1-based index of the tab that should be focused.<br> `level`:<br> When there are subtab levels at play, specifies which<br> `TabContainer's`<br> tabs among the hierarchy of active `TabContainers` is being<br> acted upon.<br> Tab levels are 1-based.<br> `level=1` indicates outermost/top-level tabs.<br> `level=-1` (default) indicates the innermost/nearest tabs.<br><br>Examples:<br> - `layout.focus_nth_tab(2) # 2nd top-level tab`<br> - `layout.focus_nth_tab(3, level=-1) # 3rd from nearest tabs` |\n|`focus_nth_window` | Switches focus to the nth window.<br><br>Counting is always done based on the geospatial position of windows -<br>ie.<br>starting from the leftmost+innermost window (ie. we traverse leaves of<br>the tree, left to right).<br><br>Args:<br> `n`:<br> The 1-based index of the window in the list of all candidate<br> windows.<br> `ignore_inactive_tabs_at_levels`:<br> For the specified list of tab levels, only consider windows<br> under the active tab at that level, ignoring windows under<br> inactive/background tabs.<br><br> eg. `[1]` means we should start counting `n` from the first<br> window in the currently active level 1 (top-level) tab,<br> ignoring windows under inactive tabs. But if there are any<br> subtabs under this active tabs, we DO consider the inactive<br> windows under background/inactive subtabs.<br><br> eg. `[1,2]` means we start counting `n` from the first window<br> of the active top-level tab, and if there are any level 2<br> subtabs under the active tab, we pick windows only from the<br> active level 2 tab as well, ignoring inactive subtabs.<br><br> eg. `[]` or `None` (default) means consider every single window<br> - even if it's inactive under a background tab.<br><br> eg. `[2]` means we start counting from the very first window at<br> the top level, even if it is inactive under a background tab.<br> But whenever there are level 2 subtabs to consider, we only<br> count its windows that are under the active level 2 subtab.<br><br> Examples:<br> - `layout.focus_nth_window(1)`<br> - layout.focus_nth_window(3,<br> ignore_inactive_tabs_at_levels=[1])<br> - layout.focus_nth_window(2, ignore_inactive_tabs_at_levels=[1,<br> 2]) |\n|`resize` | Resizes by moving an appropriate border leftwards. Usually this is the<br>right/bottom border, but for the 'last' node under a SplitContainer, it<br>will be the left/top border.<br><br>Basically the way tmux does resizing.<br><br>If there are multiple nested windows under the area being resized,<br>those windows are resized proportionally.<br><br>Args:<br> `amount`:<br> The amount by which to resize.<br><br>Examples:<br> - `layout.resize(\"left\", 100)`<br> - `layout.resize(\"right\", 100)` |\n|`swap` | Swaps the currently focused window with the nearest window in the<br>specified direction. If there are multiple candidates to pick from,<br>then the most recently focused one is chosen.<br><br>Args:<br> `wrap`:<br> If `True`, will wrap around the edge and select windows from<br> the other end of the screen to swap.<br> Defaults to `False`. |\n|`swap_tabs` | Swaps the currently active tab with the previous tab.<br><br>Args:<br> `wrap`:<br> If `True`, will wrap around the edge of the tab bar and swap<br> with the last tab.<br> Defaults to `True`. |\n|`rename_tab` | Rename the currently active tab.<br><br>Args:<br> `widget`:<br> The qtile widget that should be used for obtaining user input<br> for the renaming. The 'prompt' widget is used by default. |\n|`merge_tabs` | Merge the currently active tab with another tab, such that both tabs'<br>contents now appear in 2 splits.<br><br>Args:<br> `direction`:<br> Which neighbor tab to merge with. Can be either \"next\" or<br> \"previous\".<br> `axis`:<br> The axis along which the merged content should appear as<br> splits.<br><br>Examples:<br> - `layout.merge_tabs(\"previous\")`<br> - `layout.merge_tabs(\"next\", \"y\")` |\n|`merge_to_subtab` | Merge the currently focused window (or an ancestor node) with a<br>neighboring node in the specified `direction`, so that they both come<br>under a (possibly new) subtab.<br><br>Args:<br> `direction`:<br> The direction in which to find a neighbor to merge with.<br> `src_selection`:<br> Determines how the source window/node should be resolved. ie.<br> do we pick just the current window, or all windows under an<br> appropriate ancestor container.<br> Valid values are defined in `NodeHierarchySelectionMode`. See<br> below.<br> `dest_selection`:<br> Determines how the neighboring node should be resolved, similar<br> to how `src_selection` is resolved.<br> Valid values are defined in `NodeHierarchySelectionMode`. See<br> below.<br> `normalize`:<br> If `True`, any removals during the merge process will ensure<br> all sibling nodes are resized to be of equal dimensions.<br><br>Valid values for `NodeHierarchySelectionMode` are:<br> `\"mru_deepest\"`:<br> Pick a single innermost window. If there are multiple such<br> neighboring windows, pick the most recently used (MRU) one.<br> `\"mru_subtab_else_deepest\"` (default):<br> If the target is under a subtab, pick the subtab. If there is<br> no subtab in play, behaves like `mru_deepest`.<br> `\"mru_largest\"`<br> Given a window, pick the largest ancestor node that the<br> window's border is a fragment of. This resolves to a<br> SplitContainer or a TabContainer.<br> `\"mru_subtab_else_largest\"`<br> If the target is under a subtab, pick the subtab. If there is<br> no subtab in play, behaves like `mru_largest`.<br><br>Examples:<br> layout.merge_to_subtab( \"right\",<br> dest_selection=\"mru_subtab_else_deepest\", )<br> layout.merge_to_subtab( \"up\", src_selection=\"mru_deepest\",<br> dest_selection=\"mru_deepest\", ) |\n|`push_in` | Move the currently focused window (or a related node in its hierarchy)<br>into a neighboring window's container.<br><br>Args:<br> `direction`:<br> The direction in which to find a neighbor whose container we<br> push into.<br> `src_selection`:<br> (See docs in `merge_to_subtab()`)<br> `dest_selection`:<br> (See docs in `merge_to_subtab()`)<br> `normalize`:<br> If `True`, any removals during the process will ensure all<br> sibling nodes are resized to be of equal dimensions.<br> `wrap`:<br> If `True`, will wrap around the edge of the screen and push<br> into the container on the other end.<br><br>Examples:<br>- `layout.push_in(\"right\", dest_selection=\"mru_deepest\")`<br>- `layout.push_in(\"down\", dest_selection=\"mru_largest\", wrap=False)` |\n|`pull_out` | Move the currently focused window out from its SplitContainer into an<br>ancestor SplitContainer at a higher level. It effectively moves a<br>window 'outwards'.<br><br>Args:<br> `position`:<br> Whether the pulled out node appears before or after its<br> original container node.<br> Can be `\"next\"` or `\"previous\"`. Defaults to `\"previous\"`.<br> `src_selection`:<br> Can either be `\"mru_deepest\"` (default) or<br> `\"mru_subtab_else_deepest\"`.<br> (See docs in `merge_to_subtab()`)<br> `normalize`:<br> If `True`, all sibling nodes involved in the rearrangement are<br> resized to be of equal dimensions.<br><br>Examples:<br> - `layout.pull_out()`<br> - `layout.pull_out(src_selection=\"mru_subtab_else_deepest\")`<br> - `layout.pull_out(position=\"next\")` |\n|`pull_out_to_tab` | Extract the currently focused window into a new tab at the nearest<br>TabContainer.<br><br>Args:<br> `normalize`:<br> If `True`, any removals during the process will ensure all<br> sibling nodes are resized to be of equal dimensions. |\n|`normalize` | Starting from the focused window's container, make all windows in the<br>container of equal size.<br><br>Args:<br> `recurse`:<br> If `True`, then nested nodes are also normalized similarly. |\n|`normalize_tab` | Starting from the focused window's tab, make all windows in the tab of<br>equal size under their respective containers.<br><br>Args:<br> `recurse`:<br> If `True`, then nested nodes are also normalized similarly.<br> Defaults to `True`. |\n|`normalize_all` | Make all windows under all tabs be of equal size under their respective<br>containers. |\n|`toggle_container_select_mode` | Enable container-select mode where we can select not just a window, but<br>even their container nodes.<br><br>This will activate a special border around the active selection. You<br>can move its focus around using the same bindings as for switching<br>window focus. You can also select upper/parent or lower/child nodes<br>with the `select_container_outer()` and `select_container_inner()`<br>commands.<br><br>Handy for cases where you want to split over a collection of windows or<br>make a new subtab level over a collection of windows.<br><br>Aside from focus-switching motions, the only operations supported are<br>`spawn_split()` and `spawn_tab()`. Triggering other commands will<br>simply exit container-select mode. |\n|`select_container_inner` | When in container-select mode, it will narrow the active selection by<br>selecting the first descendent node. |\n|`select_container_outer` | When in container-select mode, it will expand the active selection by<br>selecting the next ancestor node. |\n|`tree_repr` | Returns a YAML-like text representation of the internal tree hierarchy. |\n\n\n<br>\n\n### BonsaiBar Widget\n\n| Option Name | Default Value | Description |\n| --- | --- | --- |\n|`length` | 500 | The standard `length` property of qtile<br>widgets.<br><br>As usual, it can be a fixed integer, or<br>one of the 'special' bar constants:<br>`bar.CALCULATED` or `bar.STRETCH`. |\n|`sync_with` | bonsai_on_same_screen | The Bonsai layout whose state should be<br>rendered on this widget.<br><br>Can be one of the following:<br> - `bonsai_with_focus`:<br> The Bonsai layout of the window<br> that is currently focused. This<br> is relevant in a multi-screen<br> setup - the widget will keep<br> updating based on which screen's<br> Bonsai layout has focus.<br> - `bonsai_on_same_screen`:<br> The widget will stick to<br> displaying the state of the<br> Bonsai layout that is on the<br> same screen as the widget's bar. |\n|`bg_color` | None | Background color of the bar.<br>If None, the qtile-bar's' background<br>color is used. |\n|`font_family` | Mono | Font family to use for tab titles |\n|`font_size` | 15 | Size of the font to use for tab titles |\n|`tab.width` | 50 | Width of a tab on the bar.<br><br>Can be an int or `auto`. If `auto`, the<br>tabs take up as much of the available<br>space on the bar as possible.<br><br>Note that if the `length` option is set<br>to `bar.CALCULATED`, then you cannot<br>provide `auto` here, as we would need<br>fixed tab width values to perform the<br>`bar.CALCULATED` computation.<br><br>Note that this width follows the 'margin<br>box'/'principal box' model, so it<br>includes any configured margin amount. |\n|`tab.margin` | 0 | Size of the space on either outer side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`tab.padding` | 0 | Size of the space on either inner side<br>of individual tabs.<br>Can be an int or a list of ints in [top,<br>right, bottom, left] ordering. |\n|`tab.bg_color` | Gruvbox.dull_yellow | Background color of the inactive tabs |\n|`tab.fg_color` | Gruvbox.fg1 | Foreground color of the inactive tabs |\n|`tab.active.bg_color` | Gruvbox.vivid_yellow | Background color of active tab |\n|`tab.active.fg_color` | Gruvbox.bg0_hard | Foreground color of active tab |\n|`container_select_mode.indicator.bg_color` | Gruvbox.bg0_hard | Background color of active tab when in<br>container_select_mode. |\n|`container_select_mode.indicator.fg_color` | Gruvbox.bg0_hard | Foreground color of active tab when in<br>container_select_mode. |\n\n\n\n## Support\n\nFor any bug reports, please file an issue. For questions/discussions, use the\n[GitHub Discussions](https://github.com/aravinda0/qtile-bonsai/discussions)\nsection, or you can ask on the [qtile subreddit](https://www.reddit.com/r/qtile/).",
"bugtrack_url": null,
"license": "MIT",
"summary": "A flexible custom layout for the qtile window manager that supports arbitrarily nestable tabs and splits.",
"version": "0.4.0",
"project_urls": {
"Documentation": "https://github.com/aravinda0/qtile-bonsai#readme",
"Issues": "https://github.com/aravinda0/qtile-bonsai/issues",
"Source": "https://github.com/aravinda0/qtile-bonsai",
"Visual guide": "https://aravinda0.github.io/qtile-bonsai/static/visual_guide/"
},
"split_keywords": [
"qtile",
" bonsai",
" tiling",
" window",
" manager",
" layout"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c01a4d2a3b8e98c55ca11b51699a4f1930200fd2dd680368586372d42f72f9a2",
"md5": "a1ac127ba93919fcbcb6b81aba528915",
"sha256": "7c3734bfee44a1acef1b91251ba6485f2a40fddd9f0067ba78ec6b2cfc16bd92"
},
"downloads": -1,
"filename": "qtile_bonsai-0.4.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a1ac127ba93919fcbcb6b81aba528915",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.13,>=3.10",
"size": 64021,
"upload_time": "2024-08-17T04:01:27",
"upload_time_iso_8601": "2024-08-17T04:01:27.181828Z",
"url": "https://files.pythonhosted.org/packages/c0/1a/4d2a3b8e98c55ca11b51699a4f1930200fd2dd680368586372d42f72f9a2/qtile_bonsai-0.4.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bcbeb558e99c0f09d3949d45b942fec10793cb8e1dfc1e2be6e5560ff908d6b9",
"md5": "b2690da4fc56e41959c80b400d2d21de",
"sha256": "f42abfc55992e38117f06c6b4f037a78839d83f68e93632e3c02a282b05eecc2"
},
"downloads": -1,
"filename": "qtile_bonsai-0.4.0.tar.gz",
"has_sig": false,
"md5_digest": "b2690da4fc56e41959c80b400d2d21de",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.13,>=3.10",
"size": 97067,
"upload_time": "2024-08-17T04:01:29",
"upload_time_iso_8601": "2024-08-17T04:01:29.830029Z",
"url": "https://files.pythonhosted.org/packages/bc/be/b558e99c0f09d3949d45b942fec10793cb8e1dfc1e2be6e5560ff908d6b9/qtile_bonsai-0.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-17 04:01:29",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "aravinda0",
"github_project": "qtile-bonsai#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "qtile-bonsai"
}