~ruther/nixos-config

6272fa313eae9eed1d43fe4e7197edf338b1f8c1 — Frantisek Bohacek 1 year, 6 months ago 3bd6fc0
feat: add qtile xmonad layout
M modules/desktop/qtile/config/config.py => modules/desktop/qtile/config/config.py +10 -5
@@ 16,6 16,7 @@ from qtile_extras.widget.decorations import BorderDecoration, PowerLineDecoratio
from tasklist import TaskList
from mpris2widget import Mpris2
from bluetooth import Bluetooth
import xmonad
from nixenvironment import setupLocation, configLocation

colors = {


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

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

widget_defaults = dict(


@@ 332,8 333,8 @@ def init_screen(top_bar, wallpaper):
    return Screen(top=top_bar, bottom=create_bottom_bar(), wallpaper=wallpaper, width=1920, height=1080)

screens = [
    init_screen(create_top_bar(systray = True), f'{setupLocation}/wall.png'),
    init_screen(create_top_bar(), f'{setupLocation}/wall.png'),
    init_screen(create_top_bar(systray = True), f'{setupLocation}/wall.png'),
    init_screen(create_top_bar(), f'{setupLocation}/wall.png'),
]



@@ 359,6 360,8 @@ keys.extend([
    EzKey('M-<Return>', lazy.layout.swap_main()),
    EzKey('M-<Space>', lazy.next_layout()),
    EzKey('M-S-<Space>', lazy.to_layout_index(0), desc = 'Default layout'),
    EzKey('M-<comma>', lazy.layout.increase_nmaster()),
    EzKey('M-<period>', lazy.layout.decrease_nmaster()),
])

# Spwning programs


@@ 426,12 429,14 @@ 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']
monitor_index_map = [ 1, 2, 0 ]
for i, key in enumerate(monitor_navigation_keys):
    monitor_index_map = [ 2, 1, 0 ]
    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}'),


@@ 576,7 581,7 @@ def startup_applications(client: Window):
    if client.match(Match(wm_class = 'firefox')) and firefoxInstance <= 1:
        client.togroup(groups[firefoxInstance].name)
        firefoxInstance += 1
    elif client.match(Match(wm_class = 'discord')) or client.match(Match(wm_class = 'cinny')):
    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)

# Turn off fullscreen on unfocus

A modules/desktop/qtile/config/xmonad.py => modules/desktop/qtile/config/xmonad.py +695 -0
@@ 0,0 1,695 @@
# -*- 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,
                ],
            )

M modules/desktop/qtile/home.nix => modules/desktop/qtile/home.nix +1 -0
@@ 74,6 74,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/nixenvironment.py".text = ''
from string import Template

Do not follow this link