From 69e799a9ae144d05d0b4656e866db71ae52ce9c3 Mon Sep 17 00:00:00 2001 From: Frantisek Bohacek Date: Sun, 24 Sep 2023 11:40:50 +0200 Subject: [PATCH] feat: update qtile config --- modules/desktop/qtile/config/config.py | 18 +- modules/desktop/qtile/config/xmonad.py | 695 ----------- modules/desktop/qtile/config/xmonadcustom.py | 1078 ++++++++++++++++++ modules/desktop/qtile/default.nix | 1 + modules/desktop/qtile/home.nix | 2 +- modules/services/picom.nix | 8 +- 6 files changed, 1096 insertions(+), 706 deletions(-) delete mode 100644 modules/desktop/qtile/config/xmonad.py create mode 100644 modules/desktop/qtile/config/xmonadcustom.py diff --git a/modules/desktop/qtile/config/config.py b/modules/desktop/qtile/config/config.py index 4956b85..0d54e25 100644 --- a/modules/desktop/qtile/config/config.py +++ b/modules/desktop/qtile/config/config.py @@ -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)) + diff --git a/modules/desktop/qtile/config/xmonad.py b/modules/desktop/qtile/config/xmonad.py deleted file mode 100644 index 41e3227..0000000 --- a/modules/desktop/qtile/config/xmonad.py +++ /dev/null @@ -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, - ], - ) diff --git a/modules/desktop/qtile/config/xmonadcustom.py b/modules/desktop/qtile/config/xmonadcustom.py new file mode 100644 index 0000000..923ecfd --- /dev/null +++ b/modules/desktop/qtile/config/xmonadcustom.py @@ -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() diff --git a/modules/desktop/qtile/default.nix b/modules/desktop/qtile/default.nix index f4b46d0..494189c 100644 --- a/modules/desktop/qtile/default.nix +++ b/modules/desktop/qtile/default.nix @@ -5,6 +5,7 @@ environment.systemPackages = with pkgs; [ playerctl + xkblayout-state ]; services = { diff --git a/modules/desktop/qtile/home.nix b/modules/desktop/qtile/home.nix index 194919c..981afc3 100644 --- a/modules/desktop/qtile/home.nix +++ b/modules/desktop/qtile/home.nix @@ -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 diff --git a/modules/services/picom.nix b/modules/services/picom.nix index 08ac00e..a33e076 100644 --- a/modules/services/picom.nix +++ b/modules/services/picom.nix @@ -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" -- 2.48.1