~ruther/nixos-config

69e799a9ae144d05d0b4656e866db71ae52ce9c3 — Frantisek Bohacek 1 year, 6 months ago 32a67f8
feat: update qtile config
M modules/desktop/qtile/config/config.py => modules/desktop/qtile/config/config.py +12 -6
@@ 16,7 16,7 @@ from qtile_extras.widget.decorations import BorderDecoration, PowerLineDecoratio
from tasklist import TaskList
from mpris2widget import Mpris2
from bluetooth import Bluetooth
import xmonad
import xmonadcustom
from nixenvironment import setupLocation, configLocation

colors = {


@@ 110,9 110,9 @@ layout_theme = {
}

layouts = [
    xmonad.MonadTall(**layout_theme),
    xmonadcustom.MonadTall(**layout_theme),
    layout.Max(**layout_theme),
    xmonad.MonadWide(**layout_theme),
    xmonadcustom.MonadWide(**layout_theme),
]

widget_defaults = dict(


@@ 429,14 429,12 @@ keys.extend([
    EzKey('M-S-c', lazy.window.kill()),
    EzKey('M-C-r', lazy.reload_config()),
    EzKey('M-C-q', lazy.shutdown()),

    #EzKey(f'M-r', lazy.to_screen(0), desc = f'Move focus to screen {i}'),
])

# Monitor navigation
monitor_navigation_keys = ['w', 'e', 'r']
for i, key in enumerate(monitor_navigation_keys):
    monitor_index_map = [ 2, 1, 0 ]
    monitor_index_map = [ 2, 0, 1 ]
    keys.extend([
        EzKey(f'M-{key}', lazy.to_screen(monitor_index_map[i]), desc = f'Move focus to screen {i}'),
        EzKey(f'M-S-{key}', lazy.window.toscreen(monitor_index_map[i]), desc = f'Move focus to screen {i}'),


@@ 584,6 582,13 @@ def startup_applications(client: Window):
    elif client.match(Match(wm_class = 'discord')) or client.match(Match(wm_class = 'telegram-desktop')) or client.match(Match(wm_class = 'cinny')):
        client.togroup(groups[8].name)

@hook.subscribe.screen_change
@lazy.function
def set_screens(qtile, event):
    if not os.path.exists(os.path.expanduser('~/NO-AUTORANDR')):
        subprocess.run(["autorandr", "--change"])
        qtile.cmd_restart()

# Turn off fullscreen on unfocus
@hook.subscribe.client_focus
def exit_fullscreen_on_focus_changed(client: Window):


@@ 608,3 613,4 @@ def scratchpad_startup():
            return hide_dropdown

        hook.subscribe.client_managed(wrapper(dropdown_name))


D modules/desktop/qtile/config/xmonad.py => modules/desktop/qtile/config/xmonad.py +0 -695
@@ 1,695 0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2011-2012 Dustin Lacewell
# Copyright (c) 2011 Mounier Florian
# Copyright (c) 2012 Craig Barnes
# Copyright (c) 2012 Maximilian Köhl
# Copyright (c) 2012, 2014-2015 Tycho Andersen
# Copyright (c) 2013 jpic
# Copyright (c) 2013 babadoo
# Copyright (c) 2013 Jure Ham
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 ramnes
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 dmpayton
# Copyright (c) 2014 dequis
# Copyright (c) 2014 Florian Scherf
# Copyright (c) 2017 Dirk Hartmann
# Copyright (c) 2021 Jakob Helmecke
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from libqtile.layout.base import _SimpleLayoutBase


class MonadTall(_SimpleLayoutBase):
    """Emulate the behavior of XMonad's default tiling scheme.

    Master-Pane:

    A master pane that contains a set number of windows takes up a vertical portion of
    the screen_rect based on the ratio setting. This ratio can be adjusted with
    the ``cmd_grow_master`` and ``cmd_shrink_master``.

    ::

        ---------------------
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        ---------------------

    Using the ``cmd_flip`` method will switch which horizontal side the master
    pane will occupy. The master pane is considered the "top" of the stack.

    ::

        ---------------------
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        ---------------------

    Slave-panes:

    Occupying the rest of the screen_rect are one or more slave panes. The
    slave panes will share the vertical space of the screen_rect.

    ::

        ---------------------
        |            |      |
        |            |______|
        |            |      |
        |            |______|
        |            |      |
        |            |      |
        ---------------------

    Panes can be moved with the ``cmd_shuffle_up`` and ``cmd_shuffle_down``
    methods. As mentioned the master pane is considered the top of the stack;
    moving up is counter-clockwise and moving down is clockwise.

    The opposite is true if the layout is "flipped".

    ::

        ---------------------          ---------------------
        |            |  2   |          |   2   |           |
        |            |______|          |_______|           |
        |            |  3   |          |   3   |           |
        |     1      |______|          |_______|     1     |
        |            |  4   |          |   4   |           |
        |            |      |          |       |           |
        ---------------------          ---------------------


    Normalizing/Resetting:

    To restore master slave ratio use the ``cmd_normalize`` method.

    To reset the layout to its default state, including the master
    windows, master slave ratio and flip, use the ``cmd_reset`` method.

    Maximizing:

    To toggle a maximized layout, showing a single window, simply use the
    ``cmd_maximize`` on a focused client.

    Suggested Bindings::
        # these mirror the xmonad default config

        Key([modkey], "j", lazy.layout.down()),
        Key([modkey], "k", lazy.layout.up()),
        Key([modkey, "shift"], "j", lazy.layout.shuffle_down()),
        Key([modkey, "shift"], "k", lazy.layout.shuffle_up()),
        Key([modkey], "h", lazy.layout.shink_master()),
        Key([modkey], "l", lazy.layout.grow_master()),
        Key([modkey], "n", lazy.layout.normalize()),
        Key([modkey], "m", lazy.layout.master()),
        Key([modkey], "comma", lazy.layout.decrease_nmaster()),
        Key([modkey], "period", lazy.layout.increase_nmaster()),
        Key([modkey], "Return", lazy.layout.swap_master())

        # keybindings not in xmonad default config

        Key([modkey, "shift"], "n", lazy.layout.reset()),
        Key([modkey], "space", lazy.layout.flip()),
        Key([modkey, "shift"], "space", lazy.layout.flip_master()),
        Key([modkey, "shift"], "m", lazy.layout.maximize()),
    """

    _left = 0
    _right = 1
    _vert = 0
    _hori = 1
    _med_ratio = 0.5

    defaults = [
        ("border_focus", "#ff0000", "Border colour(s) for the focused window."),
        ("border_normal", "#000000", "Border colour(s) for un-focused windows."),
        ("border_width", 2, "Border width."),
        ("single_border_width", None, "Border width for single window"),
        ("single_margin", None, "Margin size for single window"),
        ("margin", 0, "Margin of the layout"),
        (
            "ratio",
            0.5,
            "The percent of the screen-space the master pane should occupy by default.",
        ),
        (
            "min_ratio",
            0.25,
            "The percent of the screen-space the master pane should occupy at minimum.",
        ),
        (
            "max_ratio",
            0.75,
            "The percent of the screen-space the master pane should occupy at maximum.",
        ),
        (
            "align",
            _left,
            "Which side master plane will be placed (one of ``MonadTall._left`` or "
            "``MonadTall._right``)",
        ),
        ("change_ratio", 0.05, "Resize ratio"),
        (
            "new_client_position",
            "before_current",
            "Place new windows: "
            " after_current - after the active window."
            " before_current - before the active window,"
            " top - at the top of the stack,"
            " bottom - at the bottom of the stack,",
        ),
        (
            "master_length",
            1,
            "Amount of windows displayed in the master stack. Surplus windows will be moved to "
            "the slave stack.",
        ),
        (
            "orientation",
            _vert,
            "Orientation in which master windows will be "
            "placed (one of ``MonadTall._vert`` or ``MonadTall._hori``)",
        ),
        ("maximized", False, "Start maximized"),
    ]

    def __init__(self, **config):
        _SimpleLayoutBase.__init__(self, **config)
        self.add_defaults(MonadTall.defaults)
        if self.single_border_width is None:
            self.single_border_width = self.border_width
        if self.single_margin is None:
            self.single_margin = self.margin
        self.screen_rect = None

    @property
    def focused(self):
        return self.clients.current_index

    @property
    def master_windows(self):
        return self.clients[: self.master_length]

    @property
    def slave_windows(self):
        return self.clients[self.master_length :]

    def clone(self, group):
        "Clone layout for other groups"
        c = _SimpleLayoutBase.clone(self, group)
        c.screen_rect = group.screen.get_rect() if group.screen else None
        c.ratio = self.ratio
        c.align = self.align
        c.orientation = self.orientation
        return c

    def add(self, client):
        "Add client to layout"
        self.clients.add(client, client_position=self.new_client_position)
        self.do_normalize = True

    def remove(self, client):
        "Remove client from layout"
        self.do_normalize = True
        return self.clients.remove(client)

    def cmd_normalize(self, redraw=True):
        "Evenly distribute screen-space between master and slave pane"
        if redraw:
            self.ratio = self._med_ratio
            self.group.layout_all()
        self.do_normalize = False

    def cmd_reset(self, redraw=True):
        "Reset Layout."
        self.ratio = self._med_ratio
        if self.align == self._right:
            self.align = self._left
        if self.orientation == self._hori:
            self.orientation = self._vert
        self.master_length = 1
        self.cmd_normalize(redraw)

    def cmd_maximize(self):
        "Grow the currently focused client to the max size"
        if self.maximized:
            self.maximized = False
        else:
            self.maximized = True
        self.group.layout_all()

    def configure(self, client, screen_rect):
        "Position client based on order and sizes"
        self.screen_rect = screen_rect

        if self.do_normalize:
            self.cmd_normalize(False)

        # if client not in this layout
        if not self.clients or client not in self.clients:
            client.hide()
            return

        # determine focus border-color
        if client.has_focus:
            px = self.border_focus
        else:
            px = self.border_normal

        # single client - fullscreen
        if len(self.clients) == 1 or self.maximized:
            if self.clients and client is self.clients.current_client:
                client.place(
                    self.screen_rect.x,
                    self.screen_rect.y,
                    self.screen_rect.width - 2 * self.single_border_width,
                    self.screen_rect.height - 2 * self.single_border_width,
                    self.single_border_width,
                    px,
                    margin=self.single_margin,
                )
                client.unhide()
            else:
                client.hide()
            return

        cidx = self.clients.index(client)
        self._configure_specific(client, screen_rect, px, cidx)
        client.unhide()

    def _configure_specific(self, client, screen_rect, px, cidx):
        """Specific configuration for xmonad tall."""
        self.screen_rect = screen_rect

        # calculate master/slave pane size
        width_master = int(self.screen_rect.width * self.ratio)
        width_slave = self.screen_rect.width - width_master

        if len(self.master_windows) == 0:
            width_master = 0
            width_slave = self.screen_rect.width
        if len(self.slave_windows) == 0:
            width_master = self.screen_rect.width
            width_slave = 0

        # calculate client's x offset
        if self.align == self._left:  # left or up orientation
            if client in self.master_windows:
                # master client
                xpos = self.screen_rect.x
            else:
                # slave client
                xpos = self.screen_rect.x + width_master
        else:  # right or down orientation
            if client in self.master_windows:
                # master client
                xpos = self.screen_rect.x + width_slave - self.margin
            else:
                # slave client
                xpos = self.screen_rect.x

        # calculate client height and place
        if client in self.slave_windows:
            pos = self.clients.index(client)
            # slave client
            width = width_slave - 2 * self.border_width
            # ypos is the sum of all clients above it
            height = self.screen_rect.height // len(self.slave_windows)
            ypos = self.screen_rect.y + self.clients[self.master_length :].index(client) * height
            # fix double margin
            if cidx > 1:
                ypos -= self.margin
                height += self.margin
            # place client based on calculated dimensions
            client.place(
                xpos,
                ypos,
                width,
                height - 2 * self.border_width,
                self.border_width,
                px,
                margin=self.margin,
            )
        else:
            pos = self.clients.index(client)
            if self.orientation == self._vert:
                height = self.screen_rect.height // self.master_length
                width = width_master
                ypos = self.screen_rect.y + pos * height
            else:
                height = self.screen_rect.height
                width = width_master // self.master_length
                overflow = width_master % self.master_length
                ypos = self.screen_rect.y
                if self.align == self._left:
                    xpos = self.screen_rect.x + pos * width + overflow
                else:
                    xpos = self.screen_rect.x + width_slave + pos * width

            # master client
            client.place(
                xpos,
                ypos,
                width,
                height,
                self.border_width,
                px,
                margin=[
                    self.margin,
                    2 * self.border_width,
                    self.margin + 2 * self.border_width,
                    self.margin,
                ],
            )

    def info(self):
        d = _SimpleLayoutBase.info(self)
        d.update(
            dict(
                master=[c.name for c in self.master_windows],
                slave=[c.name for c in self.slave_windows],
            )
        )
        return d

    def _grow_master(self, amt):
        """Will grow the client that is currently in the master pane"""
        self.ratio += amt
        self.ratio = min(self.max_ratio, self.ratio)

    def cmd_grow_master(self):
        """Grow master pane

        Will grow the master pane, reducing the size of clients in the slave
        pane.
        """
        self._grow_master(self.change_ratio)
        self.group.layout_all()

    def cmd_shrink_master(self):
        """Shrink master pane

        Will shrink the master pane, increasing the size of clients in the
        slave pane.
        """
        self._shrink_master(self.change_ratio)
        self.group.layout_all()

    def _shrink_master(self, amt):
        """Will shrink the client that currently in the master pane"""
        self.ratio -= amt
        self.ratio = max(self.min_ratio, self.ratio)

    cmd_next = _SimpleLayoutBase.next
    cmd_previous = _SimpleLayoutBase.previous

    cmd_up = cmd_previous
    cmd_down = cmd_next

    def cmd_shuffle_up(self):
        """Shuffle the client up the stack"""
        self.clients.shuffle_up()
        self.group.layout_all()
        self.group.focus(self.clients.current_client)

    def cmd_shuffle_down(self):
        """Shuffle the client down the stack"""
        self.clients.shuffle_down()
        self.group.layout_all()
        self.group.focus(self.clients[self.focused])

    def cmd_flip(self):
        """Flip the layout horizontally"""
        self.align = self._left if self.align == self._right else self._right
        self.group.layout_all()

    def cmd_swap(self, window1, window2):
        """Swap two windows"""
        self.clients.swap(window1, window2, 1)
        self.group.layout_all()
        self.group.focus(window1)

    def cmd_swap_master(self):
        """Swap current window to master pane"""
        win = self.clients.current_client
        cidx = self.clients.index(win)

        if cidx < self.master_length - 1:
            target = self.clients[cidx + 1]
        else:
            target = self.clients[0]

        self.cmd_swap(win, target)

    def cmd_decrease_nmaster(self):
        """Decrease number of windows in master pane"""
        self.master_length -= 1
        if self.master_length <= 0:
            self.master_length = 0
        self.group.layout_all()

    def cmd_increase_nmaster(self):
        """Increase number of windows in master pane"""
        self.master_length += 1
        if self.master_length >= len(self.clients):
            self.master_length = len(self.clients)
        self.group.layout_all()

    def cmd_master(self):
        """Focus windows in master pane"""
        win = self.clients.current_client
        cidx = self.clients.index(win)
        if cidx < self.master_length - 1:
            self.group.focus(self.clients[cidx + 1])
        else:
            self.group.focus(self.clients[0])

    def cmd_flip_master(self):
        """Flip the layout horizontally"""
        self.orientation = self._vert if self.orientation == self._hori else self._hori
        self.group.layout_all()


class MonadWide(MonadTall):
    """Emulate the behavior of XMonad's horizontal tiling scheme.

    This layout attempts to emulate the behavior of XMonad wide
    tiling scheme.

    Master-Pane:

    A master pane that contains a single window takes up a horizontal
    portion of the screen_rect based on the ratio setting. This ratio can be
    adjusted with the ``cmd_grow_master`` and ``cmd_shrink_master`` or.

    ::

        ---------------------
        |                   |
        |                   |
        |                   |
        |___________________|
        |                   |
        |                   |
        ---------------------

    Using the ``cmd_flip`` method will switch which vertical side the
    master pane will occupy. The master pane is considered the "top" of
    the stack.

    ::

        ---------------------
        |                   |
        |___________________|
        |                   |
        |                   |
        |                   |
        |                   |
        ---------------------

    Slave-panes:

    Occupying the rest of the screen_rect are one or more slave panes.
    The slave panes will share the horizontal space of the screen_rect.

    ::

        ---------------------
        |                   |
        |                   |
        |                   |
        |___________________|
        |     |       |     |
        |     |       |     |
        ---------------------

    Panes can be moved with the ``cmd_shuffle_up`` and ``cmd_shuffle_down``
    methods. As mentioned the master pane is considered the top of the
    stack; moving up is counter-clockwise and moving down is clockwise.

    The opposite is true if the layout is "flipped".

    ::

        ---------------------          ---------------------
        |                   |          |  2  |   3   |  4  |
        |         1         |          |_____|_______|_____|
        |                   |          |                   |
        |___________________|          |                   |
        |     |       |     |          |        1          |
        |  2  |   3   |  4  |          |                   |
        ---------------------          ---------------------

    Normalizing/Resetting:

    To restore master slave ratio use the ``cmd_normalize`` method.

    To reset the layout to its default state, including the master
    windows, master slave ratio and flip, use the ``cmd_reset`` method.

    Maximizing:

    To toggle a maximized layout, showing a single window, simply use the
    ``cmd_maximize`` on a focused client.

    Suggested Bindings::
        # these mirror the xmonad default config

        Key([modkey], "j", lazy.layout.down()),
        Key([modkey], "k", lazy.layout.up()),
        Key([modkey, "shift"], "j", lazy.layout.shuffle_down()),
        Key([modkey, "shift"], "k", lazy.layout.shuffle_up()),
        Key([modkey], "h", lazy.layout.shink_master()),
        Key([modkey], "l", lazy.layout.grow_master()),
        Key([modkey], "n", lazy.layout.normalize()),
        Key([modkey], "m", lazy.layout.master()),
        Key([modkey], "comma", lazy.layout.decrease_nmaster()),
        Key([modkey], "period", lazy.layout.increase_nmaster()),
        Key([modkey], "Return", lazy.layout.swap_master())

        # keybindings not in xmonad default config

        Key([modkey, "shift"], "n", lazy.layout.reset()),
        Key([modkey], "space", lazy.layout.flip()),
        Key([modkey, "shift"], "space", lazy.layout.flip_master()),
        Key([modkey, "shift"], "m", lazy.layout.maximize()),
    """

    _up = 0
    _down = 1
    _hori = 0
    _vert = 1

    def _configure_specific(self, client, screen_rect, px, cidx):
        """Specific configuration for xmonad wide."""
        self.screen_rect = screen_rect

        # calculate master/slave column widths
        height_master = int(self.screen_rect.height * self.ratio)
        height_slave = self.screen_rect.height - height_master

        if len(self.master_windows) == 0:
            height_master = 0
            height_slave = self.screen_rect.height
        if len(self.slave_windows) == 0:
            height_master = self.screen_rect.height
            height_slave = 0

        # calculate client's x offset
        if self.align == self._up:  # up orientation
            if client in self.master_windows:
                # master client
                ypos = self.screen_rect.y
            else:
                # slave client
                ypos = self.screen_rect.y + height_master
        else:  # right or down orientation
            if client in self.master_windows:
                # master client
                ypos = self.screen_rect.y + height_slave - self.margin
            else:
                # slave client
                ypos = self.screen_rect.y

        # calculate client height and place
        if client in self.slave_windows:
            # slave client
            height = height_slave - 2 * self.border_width
            # xpos is the sum of all clients left of it
            width = self.screen_rect.width // len(self.slave_windows)
            xpos = self.screen_rect.x + self.clients[self.master_length :].index(client) * width
            # get width from precalculated width list
            width = self.screen_rect.width // len(self.slave_windows)

            # fix double margin
            if cidx > 1:
                xpos -= self.margin
                width += self.margin
            # place client based on calculated dimensions
            client.place(
                xpos,
                ypos,
                width - 2 * self.border_width,
                height,
                self.border_width,
                px,
                margin=self.margin,
            )
        else:
            pos = self.clients.index(client)
            if self.orientation == self._hori:
                width = self.screen_rect.width // self.master_length
                height = height_master
                xpos = self.screen_rect.x + pos * width
            else:
                width = self.screen_rect.width
                height = height_master // self.master_length
                overflow = height_master % self.master_length
                xpos = self.screen_rect.x
                if self.align == self._up:
                    ypos = self.screen_rect.y + pos * height + overflow
                else:
                    ypos = self.screen_rect.y + height_slave + pos * height

            # master client
            client.place(
                xpos,
                ypos,
                width,
                height,
                self.border_width,
                px,
                margin=[
                    self.margin,
                    self.margin + 2 * self.border_width,
                    2 * self.border_width,
                    self.margin,
                ],
            )

A modules/desktop/qtile/config/xmonadcustom.py => modules/desktop/qtile/config/xmonadcustom.py +1078 -0
@@ 0,0 1,1078 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2011-2012 Dustin Lacewell
# Copyright (cis_master) 2011 Mounier Florian
# Copyright (c) 2012 Craig Barnes
# Copyright (c) 2012 Maximilian Köhl
# Copyright (c) 2012, 2014-2015 Tycho Andersen
# Copyright (c) 2013 jpic
# Copyright (c) 2013 babadoo
# Copyright (c) 2013 Jure Ham
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 ramnes
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 dmpayton
# Copyright (c) 2014 dequis
# Copyright (c) 2014 Florian Scherf
# Copyright (c) 2017 Dirk Hartmann
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations

import math
from collections import namedtuple
from typing import TYPE_CHECKING

from libqtile.command.base import expose_command
from libqtile.layout.base import _SimpleLayoutBase

if TYPE_CHECKING:
    from typing import Any, Self

    from libqtile.backend.base import Window
    from libqtile.config import ScreenRect
    from libqtile.group import _Group


class MonadTall(_SimpleLayoutBase):
    """Emulate the behavior of XMonad's default tiling scheme.

    Main-Pane:

    A main pane that contains a single window takes up a vertical portion of
    the screen_rect based on the ratio setting. This ratio can be adjusted with
    the ``grow_main`` and ``shrink_main`` or, while the main pane is in
    focus, ``grow`` and ``shrink``. You may also set the ratio directly
    with ``set_ratio``.

    ::

        ---------------------
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        |            |      |
        ---------------------

    Using the ``flip`` method will switch which horizontal side the main
    pane will occupy. The main pane is considered the "top" of the stack.

    ::

        ---------------------
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        |      |            |
        ---------------------

    Secondary-panes:

    Occupying the rest of the screen_rect are one or more secondary panes.  The
    secondary panes will share the vertical space of the screen_rect however
    they can be resized at will with the ``grow`` and ``shrink``
    methods.  The other secondary panes will adjust their sizes to smoothly fill
    all of the space.

    ::

        ---------------------          ---------------------
        |            |      |          |            |______|
        |            |______|          |            |      |
        |            |      |          |            |      |
        |            |______|          |            |      |
        |            |      |          |            |______|
        |            |      |          |            |      |
        ---------------------          ---------------------

    Panes can be moved with the ``shuffle_up`` and ``shuffle_down``
    methods. As mentioned the main pane is considered the top of the stack;
    moving up is counter-clockwise and moving down is clockwise.

    The opposite is true if the layout is "flipped".

    ::

        ---------------------          ---------------------
        |            |  2   |          |   2   |           |
        |            |______|          |_______|           |
        |            |  3   |          |   3   |           |
        |     1      |______|          |_______|     1     |
        |            |  4   |          |   4   |           |
        |            |      |          |       |           |
        ---------------------          ---------------------


    Normalizing/Resetting:

    To restore all secondary client windows to their default size ratios
    use the ``normalize`` method.

    To reset all client windows to their default sizes, including the primary
    window, use the ``reset`` method.

    Maximizing:

    To toggle a client window between its minimum and maximum sizes
    simply use the ``maximize`` on a focused client.

    Suggested Bindings::

        Key([modkey], "h", lazy.layout.left()),
        Key([modkey], "l", lazy.layout.right()),
        Key([modkey], "j", lazy.layout.down()),
        Key([modkey], "k", lazy.layout.up()),
        Key([modkey, "shift"], "h", lazy.layout.swap_left()),
        Key([modkey, "shift"], "l", lazy.layout.swap_right()),
        Key([modkey, "shift"], "j", lazy.layout.shuffle_down()),
        Key([modkey, "shift"], "k", lazy.layout.shuffle_up()),
        Key([modkey], "i", lazy.layout.grow()),
        Key([modkey], "m", lazy.layout.shrink()),
        Key([modkey], "n", lazy.layout.normalize()),
        Key([modkey], "o", lazy.layout.maximize()),
        Key([modkey, "shift"], "space", lazy.layout.flip()),
    """

    _left = 0
    _right = 1

    defaults = [
        ("border_focus", "#ff0000", "Border colour(s) for the focused window."),
        ("border_normal", "#000000", "Border colour(s) for un-focused windows."),
        ("border_width", 2, "Border width."),
        ("single_border_width", None, "Border width for single window"),
        ("single_margin", None, "Margin size for single window"),
        ("margin", 0, "Margin of the layout"),
        (
            "ratio",
            0.5,
            "The percent of the screen-space the master pane should occupy " "by default.",
        ),
        (
            "min_ratio",
            0.25,
            "The percent of the screen-space the master pane should occupy " "at minimum.",
        ),
        (
            "max_ratio",
            0.75,
            "The percent of the screen-space the master pane should occupy " "at maximum.",
        ),
        (
            "master_length",
            1,
            "Amount of windows displayed in the master stack. Surplus windows "
            "will be moved to the slave stack.",
        ),
        ("min_secondary_size", 85, "minimum size in pixel for a secondary pane window "),
        (
            "align",
            _left,
            "Which side master plane will be placed "
            "(one of ``MonadTall._left`` or ``MonadTall._right``)",
        ),
        ("change_ratio", 0.05, "Resize ratio"),
        ("change_size", 20, "Resize change in pixels"),
        (
            "new_client_position",
            "after_current",
            "Place new windows: "
            " after_current - after the active window."
            " before_current - before the active window,"
            " top - at the top of the stack,"
            " bottom - at the bottom of the stack,",
        ),
    ]

    def __init__(self, **config):
        _SimpleLayoutBase.__init__(self, **config)
        self.add_defaults(MonadTall.defaults)
        if self.single_border_width is None:
            self.single_border_width = self.border_width
        if self.single_margin is None:
            self.single_margin = self.margin
        self.relative_sizes = []
        self._screen_rect = None
        self.default_ratio = self.ratio

    # screen_rect is a property as the MonadThreeCol layout needs to perform
    # additional actions when the attribute is modified
    @property
    def screen_rect(self):
        return self._screen_rect

    @screen_rect.setter
    def screen_rect(self, value):
        self._screen_rect = value

    @property
    def focused(self):
        return self.clients.current_index

    def _get_relative_size_from_absolute(self, absolute_size):
        return absolute_size / self.screen_rect.height

    def _get_absolute_size_from_relative(self, relative_size):
        return int(relative_size * self.screen_rect.height)

    def clone(self, group: _Group) -> Self:
        "Clone layout for other groups"
        c = _SimpleLayoutBase.clone(self, group)
        c.relative_sizes = []
        c.screen_rect = group.screen.get_rect() if group.screen else None
        c.ratio = self.ratio
        c.align = self.align
        return c

    def add_client(self, client: Window) -> None:  # type: ignore[override]
        "Add client to layout"
        self.clients.add_client(client, client_position=self.new_client_position)
        self.do_normalize = True

    def remove(self, client: Window) -> Window | None:
        "Remove client from layout"
        self.do_normalize = True
        return self.clients.remove(client)

    @expose_command()
    def set_ratio(self, ratio):
        "Directly set the main pane ratio"
        ratio = min(self.max_ratio, ratio)
        self.ratio = max(self.min_ratio, ratio)
        self.group.layout_all()

    @expose_command()
    def normalize(self, redraw=True):
        "Evenly distribute screen-space among secondary clients"
        n = len(self.clients) - self.master_length  # exclude main client, 0
        # if secondary clients exist
        if n > 0 and self.screen_rect is not None:
            self.relative_sizes = [1.0 / n] * n
        # reset main pane ratio
        if redraw:
            self.group.layout_all()
        self.do_normalize = False

    @expose_command()
    def reset(self, ratio=None, redraw=True):
        "Reset Layout."
        self.ratio = ratio or self.default_ratio
        if self.align == self._right:
            self.align = self._left
        self.normalize(redraw)

    def _maximize_main(self):
        "Toggle the main pane between min and max size"
        if self.ratio <= 0.5 * (self.max_ratio + self.min_ratio):
            self.ratio = self.max_ratio
        else:
            self.ratio = self.min_ratio
        self.group.layout_all()

    def _maximize_secondary(self):
        "Toggle the focused secondary pane between min and max size"
        n = len(self.clients) - 2  # total shrinking clients
        # total size of collapsed secondaries
        collapsed_size = self.min_secondary_size * n
        nidx = self.focused - self.master_length  # focused size index
        # total height of maximized secondary
        maxed_size = self.group.screen.dheight - collapsed_size
        # if maximized or nearly maximized
        if (
            abs(self._get_absolute_size_from_relative(self.relative_sizes[nidx]) - maxed_size)
            < self.change_size
        ):
            # minimize
            self._shrink_secondary(
                self._get_absolute_size_from_relative(self.relative_sizes[nidx])
                - self.min_secondary_size
            )
        # otherwise maximize
        else:
            self._grow_secondary(maxed_size)

    @expose_command()
    def maximize(self):
        "Grow the currently focused client to the max size"
        # if we have 1 or 2 panes or main pane is focused
        if len(self.clients) < 3 or self.focused == 0:
            self._maximize_main()
        # secondary is focused
        else:
            self._maximize_secondary()
        self.group.layout_all()

    def configure(self, client: Window, screen_rect: ScreenRect) -> None:
        "Position client based on order and sizes"
        self.screen_rect = screen_rect

        # if no sizes or normalize flag is set, normalize
        if not self.relative_sizes or self.do_normalize:
            self.normalize(False)

        # if client not in this layout
        if not self.clients or client not in self.clients:
            client.hide()
            return

        # determine focus border-color
        if client.has_focus:
            px = self.border_focus
        else:
            px = self.border_normal

        # single client - fullscreen
        if len(self.clients) == 1:
            client.place(
                self.screen_rect.x,
                self.screen_rect.y,
                self.screen_rect.width - 2 * self.single_border_width,
                self.screen_rect.height - 2 * self.single_border_width,
                self.single_border_width,
                px,
                margin=self.single_margin,
            )
            client.unhide()
            return
        cidx = self.clients.index(client)
        self._configure_specific(client, screen_rect, px, cidx)
        client.unhide()

    def _is_master(self, cidx):
        return cidx < self.master_length

    def _configure_specific(self, client, screen_rect, px, cidx):
        """Specific configuration for xmonad tall."""
        self.screen_rect = screen_rect

        is_master = self._is_master(cidx);

        # calculate main/secondary pane size
        width_main = int(self.screen_rect.width * self.ratio)
        width_shared = self.screen_rect.width - width_main

        # calculate client's x offset
        if self.align == self._left:  # left or up orientation
            if is_master:
                # main client
                xpos = self.screen_rect.x
            else:
                # secondary client
                xpos = self.screen_rect.x + width_main
        else:  # right or down orientation
            if is_master:
                # main client
                xpos = self.screen_rect.x + width_shared - self.margin
            else:
                # secondary client
                xpos = self.screen_rect.x

        # calculate client height and place
        if not is_master:
            # secondary client
            width = width_shared - 2 * self.border_width
            # ypos is the sum of all clients above it
            ypos = self.screen_rect.y + self._get_absolute_size_from_relative(
                sum(self.relative_sizes[: cidx - self.master_length])
            )
            # get height from precalculated height list
            height = self._get_absolute_size_from_relative(self.relative_sizes[cidx - self.master_length])
            # fix double margin
            if cidx > 1:
                ypos -= self.margin
                height += self.margin
            # place client based on calculated dimensions
            client.place(
                xpos,
                ypos,
                width,
                height - 2 * self.border_width,
                self.border_width,
                px,
                margin=self.margin,
            )
        else:
            # main client
            height = self.screen_rect.height // self.master_length
            client.place(
                xpos,
                self.screen_rect.y + height * cidx,
                width_main,
                height,
                self.border_width,
                px,
                margin=[
                    self.margin,
                    2 * self.border_width,
                    self.margin + 2 * self.border_width,
                    self.margin,
                ],
            )

    @expose_command()
    def info(self) -> dict[str, Any]:
        d = _SimpleLayoutBase.info(self)
        d.update(
            dict(
                main=d["clients"][0] if self.clients else None,
                secondary=d["clients"][1::] if self.clients else [],
            )
        )
        return d

    def get_shrink_margin(self, cidx):
        "Return how many remaining pixels a client can shrink"
        return max(
            0,
            self._get_absolute_size_from_relative(self.relative_sizes[cidx])
            - self.min_secondary_size,
        )

    def _shrink(self, cidx, amt):
        """Reduce the size of a client

        Will only shrink the client until it reaches the configured minimum
        size. Any amount that was prevented in the resize is returned.
        """
        # get max resizable amount
        margin = self.get_shrink_margin(cidx)
        if amt > margin:  # too much
            self.relative_sizes[cidx] -= self._get_relative_size_from_absolute(margin)
            return amt - margin
        else:
            self.relative_sizes[cidx] -= self._get_relative_size_from_absolute(amt)
            return 0

    def shrink_up(self, cidx, amt):
        """Shrink the window up

        Will shrink all secondary clients above the specified index in order.
        Each client will attempt to shrink as much as it is able before the
        next client is resized.

        Any amount that was unable to be applied to the clients is returned.
        """
        left = amt  # track unused shrink amount
        # for each client before specified index
        for idx in range(0, cidx):
            # shrink by whatever is left-over of original amount
            left -= left - self._shrink(idx, left)
        # return unused shrink amount
        return left

    def shrink_up_shared(self, cidx, amt):
        """Shrink the shared space

        Will shrink all secondary clients above the specified index by an equal
        share of the provided amount. After applying the shared amount to all
        affected clients, any amount left over will be applied in a non-equal
        manner with ``shrink_up``.

        Any amount that was unable to be applied to the clients is returned.
        """
        # split shrink amount among number of clients
        per_amt = amt / cidx
        left = amt  # track unused shrink amount
        # for each client before specified index
        for idx in range(0, cidx):
            # shrink by equal amount and track left-over
            left -= per_amt - self._shrink(idx, per_amt)
        # apply non-equal shrinkage secondary pass
        # in order to use up any left over shrink amounts
        left = self.shrink_up(cidx, left)
        # return whatever could not be applied
        return left

    def shrink_down(self, cidx, amt):
        """Shrink current window down

        Will shrink all secondary clients below the specified index in order.
        Each client will attempt to shrink as much as it is able before the
        next client is resized.

        Any amount that was unable to be applied to the clients is returned.
        """
        left = amt  # track unused shrink amount
        # for each client after specified index
        for idx in range(cidx + 1, len(self.relative_sizes)):
            # shrink by current total left-over amount
            left -= left - self._shrink(idx, left)
        # return unused shrink amount
        return left

    def shrink_down_shared(self, cidx, amt):
        """Shrink secondary clients

        Will shrink all secondary clients below the specified index by an equal
        share of the provided amount. After applying the shared amount to all
        affected clients, any amount left over will be applied in a non-equal
        manner with ``shrink_down``.

        Any amount that was unable to be applied to the clients is returned.
        """
        # split shrink amount among number of clients
        per_amt = amt / (len(self.relative_sizes) - self.master_length - cidx)
        left = amt  # track unused shrink amount
        # for each client after specified index
        for idx in range(cidx + 1, len(self.relative_sizes)):
            # shrink by equal amount and track left-over
            left -= per_amt - self._shrink(idx, per_amt)
        # apply non-equal shrinkage secondary pass
        # in order to use up any left over shrink amounts
        left = self.shrink_down(cidx, left)
        # return whatever could not be applied
        return left

    def _grow_main(self, amt):
        """Will grow the client that is currently in the main pane"""
        self.ratio += amt
        self.ratio = min(self.max_ratio, self.ratio)

    def _grow_solo_secondary(self, amt):
        """Will grow the solitary client in the secondary pane"""
        self.ratio -= amt
        self.ratio = max(self.min_ratio, self.ratio)

    def _grow_secondary(self, amt):
        """Will grow the focused client in the secondary pane"""
        half_change_size = amt / 2
        # track unshrinkable amounts
        left = amt
        # first secondary (top)
        if self.focused == 1:
            # only shrink downwards
            left -= amt - self.shrink_down_shared(0, amt)
        # last secondary (bottom)
        elif self.focused == len(self.clients) - self.master_length:
            # only shrink upwards
            left -= amt - self.shrink_up(len(self.relative_sizes) - self.master_length, amt)
        # middle secondary
        else:
            # get size index
            idx = self.focused - self.master_length
            # shrink up and down
            left -= half_change_size - self.shrink_up_shared(idx, half_change_size)
            left -= half_change_size - self.shrink_down_shared(idx, half_change_size)
            left -= half_change_size - self.shrink_up_shared(idx, half_change_size)
            left -= half_change_size - self.shrink_down_shared(idx, half_change_size)
        # calculate how much shrinkage took place
        diff = amt - left
        # grow client by diff amount
        self.relative_sizes[self.focused - self.master_length] += self._get_relative_size_from_absolute(diff)

    @expose_command()
    def grow(self):
        """Grow current window

        Will grow the currently focused client reducing the size of those
        around it. Growing will stop when no other secondary clients can reduce
        their size any further.
        """
        if self.focused == 0:
            self._grow_main(self.change_ratio)
        elif len(self.clients) == 2:
            self._grow_solo_secondary(self.change_ratio)
        else:
            self._grow_secondary(self.change_size)
        self.group.layout_all()

    @expose_command()
    def grow_main(self):
        """Grow main pane

        Will grow the main pane, reducing the size of clients in the secondary
        pane.
        """
        self._grow_main(self.change_ratio)
        self.group.layout_all()

    @expose_command()
    def shrink_main(self):
        """Shrink main pane

        Will shrink the main pane, increasing the size of clients in the
        secondary pane.
        """
        self._shrink_main(self.change_ratio)
        self.group.layout_all()

    def _grow(self, cidx, amt):
        "Grow secondary client by specified amount"
        self.relative_sizes[cidx] += self._get_relative_size_from_absolute(amt)

    def grow_up_shared(self, cidx, amt):
        """Grow higher secondary clients

        Will grow all secondary clients above the specified index by an equal
        share of the provided amount.
        """
        # split grow amount among number of clients
        per_amt = amt / cidx
        for idx in range(0, cidx):
            self._grow(idx, per_amt)

    def grow_down_shared(self, cidx, amt):
        """Grow lower secondary clients

        Will grow all secondary clients below the specified index by an equal
        share of the provided amount.
        """
        # split grow amount among number of clients
        per_amt = amt / (len(self.relative_sizes) - self.master_length - cidx)
        for idx in range(cidx + 1, len(self.relative_sizes)):
            self._grow(idx, per_amt)

    def _shrink_main(self, amt):
        """Will shrink the client that currently in the main pane"""
        self.ratio -= amt
        self.ratio = max(self.min_ratio, self.ratio)

    def _shrink_solo_secondary(self, amt):
        """Will shrink the solitary client in the secondary pane"""
        self.ratio += amt
        self.ratio = min(self.max_ratio, self.ratio)

    def _shrink_secondary(self, amt):
        """Will shrink the focused client in the secondary pane"""
        # get focused client
        client = self.clients[self.focused]

        # get default change size
        change = amt

        # get left-over height after change
        left = client.height - amt
        # if change would violate min_secondary_size
        if left < self.min_secondary_size:
            # just reduce to min_secondary_size
            change = client.height - self.min_secondary_size

        # calculate half of that change
        half_change = change / 2

        # first secondary (top)
        if self.focused == 1:
            # only grow downwards
            self.grow_down_shared(0, change)
        # last secondary (bottom)
        elif self.focused == len(self.clients) - self.master_length:
            # only grow upwards
            self.grow_up_shared(len(self.relative_sizes) - self.master_length, change)
        # middle secondary
        else:
            idx = self.focused - self.master_length
            # grow up and down
            self.grow_up_shared(idx, half_change)
            self.grow_down_shared(idx, half_change)
        # shrink client by total change
        self.relative_sizes[self.focused - self.master_length] -= self._get_relative_size_from_absolute(change)

    @expose_command("down")
    def next(self) -> None:
        _SimpleLayoutBase.next(self)

    @expose_command("up")
    def previous(self) -> None:
        _SimpleLayoutBase.previous(self)

    @expose_command()
    def shrink(self):
        """Shrink current window

        Will shrink the currently focused client reducing the size of those
        around it. Shrinking will stop when the client has reached the minimum
        size.
        """
        if self.focused == 0:
            self._shrink_main(self.change_ratio)
        elif len(self.clients) == 2:
            self._shrink_solo_secondary(self.change_ratio)
        else:
            self._shrink_secondary(self.change_size)
        self.group.layout_all()

    @expose_command()
    def shuffle_up(self):
        """Shuffle the client up the stack"""
        self.clients.shuffle_up()
        self.group.layout_all()
        self.group.focus(self.clients.current_client)

    @expose_command()
    def shuffle_down(self):
        """Shuffle the client down the stack"""
        self.clients.shuffle_down()
        self.group.layout_all()
        self.group.focus(self.clients[self.focused])

    @expose_command()
    def flip(self):
        """Flip the layout horizontally"""
        self.align = self._left if self.align == self._right else self._right
        self.group.layout_all()

    def _get_closest(self, x, y, clients):
        """Get closest window to a point x,y"""
        target = min(
            clients,
            key=lambda c: math.hypot(c.x - x, c.y - y),
            default=self.clients.current_client,
        )
        return target

    @expose_command()
    def swap(self, window1: Window, window2: Window) -> None:
        """Swap two windows"""
        _SimpleLayoutBase.swap(self, window1, window2)

    @expose_command("shuffle_left")
    def swap_left(self):
        """Swap current window with closest window to the left"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients if c.info()["x"] < x]
        target = self._get_closest(x, y, candidates)
        self.swap(win, target)

    @expose_command("shuffle_right")
    def swap_right(self):
        """Swap current window with closest window to the right"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients if c.info()["x"] > x]
        target = self._get_closest(x, y, candidates)
        self.swap(win, target)

    @expose_command()
    def swap_main(self):
        """Swap current window to main pane"""
        if self.align == self._left:
            self.swap_left()
        elif self.align == self._right:
            self.swap_right()

    @expose_command()
    def left(self):
        """Focus on the closest window to the left of the current window"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients if c.info()["x"] < x]
        self.clients.current_client = self._get_closest(x, y, candidates)
        self.group.focus(self.clients.current_client)

    @expose_command()
    def right(self):
        """Focus on the closest window to the right of the current window"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients if c.info()["x"] > x]
        self.clients.current_client = self._get_closest(x, y, candidates)
        self.group.focus(self.clients.current_client)

    @expose_command()
    def decrease_nmaster(self):
        self.master_length -= 1
        if self.master_length <= 0:
            self.master_length = 1
        self.group.layout_all()
        self.normalize()

    @expose_command()
    def increase_nmaster(self):
        self.master_length += 1
        if self.master_length >= len(self.clients):
            self.master_length = len(self.clients) - 1
        self.group.layout_all()
        self.normalize()

    @expose_command
    def focus_first(self):
        if self.clients != None and len(self.clients) > 0:
            self.clients.current_client = self.clients[0]
            self.group.focus(self.clients[0])



class MonadWide(MonadTall):
    """Emulate the behavior of XMonad's horizontal tiling scheme.

    This layout attempts to emulate the behavior of XMonad wide
    tiling scheme.

    Main-Pane:

    A main pane that contains a single window takes up a horizontal
    portion of the screen_rect based on the ratio setting. This ratio can be
    adjusted with the ``grow_main`` and ``shrink_main`` or,
    while the main pane is in focus, ``grow`` and ``shrink``.

    ::

        ---------------------
        |                   |
        |                   |
        |                   |
        |___________________|
        |                   |
        |                   |
        ---------------------

    Using the ``flip`` method will switch which vertical side the
    main pane will occupy. The main pane is considered the "top" of
    the stack.

    ::

        ---------------------
        |                   |
        |___________________|
        |                   |
        |                   |
        |                   |
        |                   |
        ---------------------

    Secondary-panes:

    Occupying the rest of the screen_rect are one or more secondary panes.
    The secondary panes will share the horizontal space of the screen_rect
    however they can be resized at will with the ``grow`` and
    ``shrink`` methods. The other secondary panes will adjust their
    sizes to smoothly fill all of the space.

    ::

        ---------------------          ---------------------
        |                   |          |                   |
        |                   |          |                   |
        |                   |          |                   |
        |___________________|          |___________________|
        |     |       |     |          |   |           |   |
        |     |       |     |          |   |           |   |
        ---------------------          ---------------------

    Panes can be moved with the ``shuffle_up`` and ``shuffle_down``
    methods. As mentioned the main pane is considered the top of the
    stack; moving up is counter-clockwise and moving down is clockwise.

    The opposite is true if the layout is "flipped".

    ::

        ---------------------          ---------------------
        |                   |          |  2  |   3   |  4  |
        |         1         |          |_____|_______|_____|
        |                   |          |                   |
        |___________________|          |                   |
        |     |       |     |          |        1          |
        |  2  |   3   |  4  |          |                   |
        ---------------------          ---------------------

    Normalizing/Resetting:

    To restore all secondary client windows to their default size ratios
    use the ``normalize`` method.

    To reset all client windows to their default sizes, including the primary
    window, use the ``reset`` method.


    Maximizing:

    To toggle a client window between its minimum and maximum sizes
    simply use the ``maximize`` on a focused client.

    Suggested Bindings::

        Key([modkey], "h", lazy.layout.left()),
        Key([modkey], "l", lazy.layout.right()),
        Key([modkey], "j", lazy.layout.down()),
        Key([modkey], "k", lazy.layout.up()),
        Key([modkey, "shift"], "h", lazy.layout.swap_left()),
        Key([modkey, "shift"], "l", lazy.layout.swap_right()),
        Key([modkey, "shift"], "j", lazy.layout.shuffle_down()),
        Key([modkey, "shift"], "k", lazy.layout.shuffle_up()),
        Key([modkey], "i", lazy.layout.grow()),
        Key([modkey], "m", lazy.layout.shrink()),
        Key([modkey], "n", lazy.layout.normalize()),
        Key([modkey], "o", lazy.layout.maximize()),
        Key([modkey, "shift"], "space", lazy.layout.flip()),
    """

    _up = 0
    _down = 1

    def _get_relative_size_from_absolute(self, absolute_size):
        return absolute_size / self.screen_rect.width

    def _get_absolute_size_from_relative(self, relative_size):
        return int(relative_size * self.screen_rect.width)

    def _maximize_secondary(self):
        """Toggle the focused secondary pane between min and max size."""
        n = len(self.clients) - 2  # total shrinking clients
        # total size of collapsed secondaries
        collapsed_size = self.min_secondary_size * n
        nidx = self.focused - self.master_length  # focused size index
        # total width of maximized secondary
        maxed_size = self.screen_rect.width - collapsed_size
        # if maximized or nearly maximized
        if (
            abs(self._get_absolute_size_from_relative(self.relative_sizes[nidx]) - maxed_size)
            < self.change_size
        ):
            # minimize
            self._shrink_secondary(
                self._get_absolute_size_from_relative(self.relative_sizes[nidx])
                - self.min_secondary_size
            )
        # otherwise maximize
        else:
            self._grow_secondary(maxed_size)

    def _configure_specific(self, client, screen_rect, px, cidx):
        """Specific configuration for xmonad wide."""
        self.screen_rect = screen_rect
        is_master = self._is_master(cidx);

        # calculate main/secondary column widths
        height_main = int(self.screen_rect.height * self.ratio)
        height_shared = self.screen_rect.height - height_main

        # calculate client's x offset
        if self.align == self._up:  # up orientation
            if is_master:
                # main client
                ypos = self.screen_rect.y
            else:
                # secondary client
                ypos = self.screen_rect.y + height_main
        else:  # right or down orientation
            if is_master:
                # main client
                ypos = self.screen_rect.y + height_shared - self.margin
            else:
                # secondary client
                ypos = self.screen_rect.y

        # calculate client height and place
        if not is_master:
            # secondary client
            height = height_shared - 2 * self.border_width
            # xpos is the sum of all clients left of it
            xpos = self.screen_rect.x + self._get_absolute_size_from_relative(
                sum(self.relative_sizes[: cidx - self.master_length])
            )
            # get width from precalculated width list
            width = self._get_absolute_size_from_relative(self.relative_sizes[cidx - self.master_length])
            # fix double margin
            if cidx > 1:
                xpos -= self.margin
                width += self.margin
            # place client based on calculated dimensions
            client.place(
                xpos,
                ypos,
                width - 2 * self.border_width,
                height,
                self.border_width,
                px,
                margin=self.margin,
            )
        else:
            # main client
            width = self.screen_rect.width // self.master_length
            client.place(
                self.screen_rect.x + width * cidx,
                ypos,
                width,
                height_main,
                self.border_width,
                px,
                margin=[
                    self.margin,
                    self.margin + 2 * self.border_width,
                    2 * self.border_width,
                    self.margin,
                ],
            )

    def _shrink_secondary(self, amt):
        """Will shrink the focused client in the secondary pane"""
        # get focused client
        client = self.clients[self.focused]

        # get default change size
        change = amt

        # get left-over height after change
        left = client.width - amt
        # if change would violate min_secondary_size
        if left < self.min_secondary_size:
            # just reduce to min_secondary_size
            change = client.width - self.min_secondary_size

        # calculate half of that change
        half_change = change / 2

        # first secondary (top)
        if self.focused == 1:
            # only grow downwards
            self.grow_down_shared(0, change)
        # last secondary (bottom)
        elif self.focused == len(self.clients) - self.master_length:
            # only grow upwards
            self.grow_up_shared(len(self.relative_sizes) - self.master_length, change)
        # middle secondary
        else:
            idx = self.focused - self.master_length
            # grow up and down
            self.grow_up_shared(idx, half_change)
            self.grow_down_shared(idx, half_change)
        # shrink client by total change
        self.relative_sizes[self.focused - self.master_length] -= self._get_relative_size_from_absolute(change)

    @expose_command()
    def swap_left(self):
        """Swap current window with closest window to the down"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients.clients if c.info()["y"] > y]
        target = self._get_closest(x, y, candidates)
        self.swap(win, target)

    @expose_command()
    def swap_right(self):
        """Swap current window with closest window to the up"""
        win = self.clients.current_client
        x, y = win.x, win.y
        candidates = [c for c in self.clients if c.info()["y"] < y]
        target = self._get_closest(x, y, candidates)
        self.swap(win, target)

    @expose_command()
    def swap_main(self):
        """Swap current window to main pane"""
        if self.align == self._up:
            self.swap_right()
        elif self.align == self._down:
            self.swap_left()

M modules/desktop/qtile/default.nix => modules/desktop/qtile/default.nix +1 -0
@@ 5,6 5,7 @@

  environment.systemPackages = with pkgs; [
    playerctl
    xkblayout-state
  ];

  services = {

M modules/desktop/qtile/home.nix => modules/desktop/qtile/home.nix +1 -1
@@ 78,7 78,7 @@
  xdg.configFile."qtile/bluetooth.py".source = ./config/bluetooth.py;
  xdg.configFile."qtile/mpris2widget.py".source = ./config/mpris2widget.py;
  xdg.configFile."qtile/tasklist.py".source = ./config/tasklist.py;
  xdg.configFile."qtile/xmonad.py".source = ./config/xmonad.py;
  xdg.configFile."qtile/xmonadcustom.py".source = ./config/xmonadcustom.py;

  xdg.configFile."qtile/nixenvironment.py".text = ''
from string import Template

M modules/services/picom.nix => modules/services/picom.nix +4 -4
@@ 24,10 24,10 @@
    #  "100:name = 'Picture in picture'"
    #  "100:name = 'Picture-in-Picture'"
    #  "85:class_i ?= 'rofi'"
      "80:class_i *= 'discord'"
      "80:class_i *= 'telegram-desktop'"
      "80:class_i *= 'emacs'"
      "80:class_i *= 'Alacritty'"
      "90:class_i *= 'discord'"
      "90:class_i *= 'telegram-desktop'"
      "90:class_i *= 'emacs'"
      "90:class_i *= 'Alacritty'"
    #  "100:fullscreen"
    ];                                            # Find with $ xprop | grep "WM_CLASS"


Do not follow this link