From a53d8970610225609d603f8fe116ca624770297f Mon Sep 17 00:00:00 2001 From: elParaguayo Date: Wed, 5 Jun 2024 22:11:52 +0100 Subject: [PATCH] Add conditional window border formatting Adds a new `ConditionalBorder` which allows users to provide multiple border styles which are selected based on provided `Match` criteria. --- CHANGELOG | 1 + qtile_extras/layout/decorations/__init__.py | 1 + qtile_extras/layout/decorations/borders.py | 66 +++++++++++++++++++ qtile_extras/layout/decorations/injections.py | 10 ++- .../decorations/test_border_decorations.py | 16 ++++- 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 45becc8f..71911002 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +2024-06-06: [FEATURE] Add new `ConditionalBorder` to set window border depending on window conditions (name, class etc.) 2024-06-01: [BUGFIX] Fix `GlobalMenu` crash after reloading config 2024-05-29: [BUGFIX] Fix bug with border decorations when using both standard and decorated borders 2024-05-22: [RELEASE] v0.26.0 release - compatible with qtile 0.26.0 diff --git a/qtile_extras/layout/decorations/__init__.py b/qtile_extras/layout/decorations/__init__.py index f0fad602..5944cb6a 100644 --- a/qtile_extras/layout/decorations/__init__.py +++ b/qtile_extras/layout/decorations/__init__.py @@ -20,6 +20,7 @@ from libqtile import hook from qtile_extras.layout.decorations.borders import ( # noqa: F401 + ConditionalBorder, GradientBorder, GradientFrame, ScreenGradientBorder, diff --git a/qtile_extras/layout/decorations/borders.py b/qtile_extras/layout/decorations/borders.py index 6c4a60fe..474c6e92 100644 --- a/qtile_extras/layout/decorations/borders.py +++ b/qtile_extras/layout/decorations/borders.py @@ -428,3 +428,69 @@ def wayland_draw(self, borderwidth, x, y, width, height, surface): scene_rects.append(rect) return scene_rects + + +class ConditionalBorder(_BorderStyle): + """ + A decoration that allows finer control as to which border is applied to which window. + + To configure the decoration, you need to provide two parameters: + + * ``matches``: a list of tuples of (Match rules, border style) + * ``fallback``: border style to apply if no matches + + Example: + + .. code:: python + + from qtile_extras.layout.decorations import ConditionalBorder, GradientBorder + + layouts = [ + layout.MonadTall( + border_focus=ConditionalBorder( + matches=[ + (Match(wm_class="vlc"), GradientBorder(colours=["e85e00", "e80000", "e85e00"])), + (Match(wm_class="firefox"), "f0f") + ], + fallback="00f"), + border_width=4 + ), + ] + + The above code will draw an orange/red gradient border when VLC is focused, a solid purple border when + firefox is focused and a solid blue border when any other window is focused. + + Matches can be provided as single rule or a list of rules. The advanced combination of rules (using + ``&``, ``|``, ``~``) is also supported here. + + """ + + needs_surface = False + + defaults = [ + ("matches", [], "List of tuples of match rules and applicable"), + ( + "fallback", + "fff", + "Border to be applied if no matches", + ), + ] + + def __init__(self, **config): + _BorderStyle.__init__(self, **config) + self.add_defaults(ConditionalBorder.defaults) + + def compare(self, win): + if not win: + return self.fallback + + for match, colour in self.matches: + if isinstance(match, (list, str)): + matched = any(m.compare(win) for m in match) + else: + matched = match.compare(win) + + if matched: + return colour + + return self.fallback diff --git a/qtile_extras/layout/decorations/injections.py b/qtile_extras/layout/decorations/injections.py index 1d115e11..33187e43 100644 --- a/qtile_extras/layout/decorations/injections.py +++ b/qtile_extras/layout/decorations/injections.py @@ -23,10 +23,11 @@ from typing import TYPE_CHECKING import xcffib +from libqtile import qtile from libqtile.backend.wayland.window import SceneRect, Window, _rgb from xcffib.wrappers import GContextID, PixmapID -from qtile_extras.layout.decorations.borders import _BorderStyle +from qtile_extras.layout.decorations.borders import ConditionalBorder, _BorderStyle if TYPE_CHECKING: from libqtile.backend.wayland.window import Core, Qtile, S @@ -49,6 +50,8 @@ def wayland_paint_borders(self, colors: ColorsType | None, width: int) -> None: if not isinstance(colors, list): colors = [colors] + colors = [c.compare(self) if isinstance(c, ConditionalBorder) else c for c in colors] + if self.tree: self.tree.node.set_position(width, width) self.bordercolor = colors @@ -162,6 +165,11 @@ def x11_paint_borders(self, depth, colors, borderwidth, width, height): if len(colors) > borderwidth: colors = colors[:borderwidth] + + win = qtile.windows_map.get(self.wid) + + colors = [c.compare(win) if isinstance(c, ConditionalBorder) else c for c in colors] + core = self.conn.conn.core outer_w = width + borderwidth * 2 outer_h = height + borderwidth * 2 diff --git a/test/layout/decorations/test_border_decorations.py b/test/layout/decorations/test_border_decorations.py index f7f312df..4ffacc15 100644 --- a/test/layout/decorations/test_border_decorations.py +++ b/test/layout/decorations/test_border_decorations.py @@ -18,11 +18,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest -from libqtile.config import Screen +from libqtile.config import Match, Screen from libqtile.confreader import Config, ConfigError from libqtile.layout import Matrix from qtile_extras.layout.decorations import ( + ConditionalBorder, GradientBorder, GradientFrame, ScreenGradientBorder, @@ -59,8 +60,17 @@ class BorderDecorationConfig(Config): ScreenGradientBorder(colours=["f00", "0f0", "00f"], points=[(0, 0), (1, 0)]), ScreenGradientBorder(colours=["f00", "0f0", "00f"], offsets=[0, 0.1, 1]), ScreenGradientBorder(colours=["f00", "0f0", "00f"], radial=True), - # SolidEdge(), - # SolidEdge(colours=["f00", "00f", "f00", "00f"]) + SolidEdge(), + SolidEdge(colours=["f00", "00f", "f00", "00f"]), + ConditionalBorder(), + ConditionalBorder(matches=[(Match(title="one"), "f00")]), + ConditionalBorder(matches=[(Match(title="one"), GradientBorder())]), + ConditionalBorder(matches=[(Match(title="three"), "f00")]), + ConditionalBorder( + matches=[(Match(title="three"), "f00"), (Match(wm_class="vlc"), "f60")] + ), + ConditionalBorder(matches=[(Match(title="three"), "f00")], fallback="00f"), + ConditionalBorder(matches=[(Match(title="three"), "f00")], fallback=GradientBorder()), ], indirect=True, )