Repository: qtile/qtile

Issue:
widget.CurrentLayoutIcon cannot work with .svg files
### The issue:

qtile-git
Arch linux.

 widget.CurrentLayoutIcon is working with .png files stored in `custom_icon_paths`, but .svg files are not loaded.


### Required:

- [X] I have searched past issues to see if this bug has already been reported.
widget.CurrentLayoutIcon cannot work with .svg files
### The issue:

qtile-git
Arch linux.

 widget.CurrentLayoutIcon is working with .png files stored in `custom_icon_paths`, but .svg files are not loaded.


### Required:

- [X] I have searched past issues to see if this bug has already been reported.


Discussion:
Thanks. The widget is hardcoded to look for a file called `layout-<name>.png`. We should fix that. No reason the icon couldn't be an svg file.
Thanks. The widget is hardcoded to look for a file called `layout-<name>.png`. We should fix that. No reason the icon couldn't be an svg file.

Resolving patch:
diff --git a/libqtile/widget/currentlayout.py b/libqtile/widget/currentlayout.py
--- a/libqtile/widget/currentlayout.py
+++ b/libqtile/widget/currentlayout.py
@@ -27,9 +27,8 @@
 # SOFTWARE.
 import os
 
-import cairocffi
-
 from libqtile import bar, hook
+from libqtile.images import Img
 from libqtile.log_utils import logger
 from libqtile.widget import base
 
@@ -84,6 +83,8 @@ class CurrentLayoutIcon(base._TextBox):
     use other directories, but then you need to specify those directories
     in `custom_icon_paths` argument for this plugin.
 
+    The widget will look for icons with a `png` or `svg` extension.
+
     The order of icon search is:
 
     - dirs in `custom_icon_paths` config argument
@@ -94,7 +95,7 @@ class CurrentLayoutIcon(base._TextBox):
     orientations = base.ORIENTATION_HORIZONTAL
 
     defaults = [
-        ("scale", 1, "Scale factor relative to the bar height.  " "Defaults to 1"),
+        ("scale", 1, "Scale factor relative to the bar height. Defaults to 1"),
         (
             "custom_icon_paths",
             [],
@@ -109,7 +110,6 @@ class CurrentLayoutIcon(base._TextBox):
     def __init__(self, **config):
         base._TextBox.__init__(self, "", **config)
         self.add_defaults(CurrentLayoutIcon.defaults)
-        self.scale = 1.0 / self.scale
 
         self.length_type = bar.STATIC
         self.length = 0
@@ -158,8 +158,13 @@ def draw(self):
                 logger.error("No icon for layout %s", self.current_layout)
             else:
                 self.drawer.clear(self.background or self.bar.background)
-                self.drawer.ctx.set_source(surface)
+                self.drawer.ctx.save()
+                self.drawer.ctx.translate(
+                    (self.width - surface.width) / 2, (self.height - surface.height) / 2
+                )
+                self.drawer.ctx.set_source(surface.pattern)
                 self.drawer.ctx.paint()
+                self.drawer.ctx.restore()
                 self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)
         else:
             # Fallback to text
@@ -179,7 +184,7 @@ def _update_icon_paths(self):
         self.icon_paths = []
 
         # We allow user to override icon search path
-        self.icon_paths.extend(self.custom_icon_paths)
+        self.icon_paths.extend(os.path.expanduser(path) for path in self.custom_icon_paths)
 
         # We also look in ~/.icons/ and ~/.local/share/icons
         self.icon_paths.append(os.path.expanduser("~/.icons"))
@@ -192,11 +197,12 @@ def _update_icon_paths(self):
         self.icon_paths.append(os.path.join(root, "resources", "layout-icons"))
 
     def find_icon_file_path(self, layout_name):
-        icon_filename = "layout-{}.png".format(layout_name)
         for icon_path in self.icon_paths:
-            icon_file_path = os.path.join(icon_path, icon_filename)
-            if os.path.isfile(icon_file_path):
-                return icon_file_path
+            for extension in ["png", "svg"]:
+                icon_filename = "layout-{}.{}".format(layout_name, extension)
+                icon_file_path = os.path.join(icon_path, icon_filename)
+                if os.path.isfile(icon_file_path):
+                    return icon_file_path
 
     def _setup_images(self):
         """
@@ -217,40 +223,14 @@ def _setup_images(self):
                 logger.warning('No icon found for layout "%s"', layout_name)
                 icon_file_path = self.find_icon_file_path("unknown")
 
-            try:
-                img = cairocffi.ImageSurface.create_from_png(icon_file_path)
-            except (cairocffi.Error, IOError) as e:
-                # Icon file is guaranteed to exist at this point.
-                # If this exception happens, it means the icon file contains
-                # an invalid image or is not readable.
-                self.icons_loaded = False
-                logger.exception(
-                    'Failed to load icon from file "%s", error was: %s', icon_file_path, e.message
-                )
-                return
-
-            input_width = img.get_width()
-            input_height = img.get_height()
-
-            sp = input_height / (self.bar.height - 1)
-
-            width = input_width / sp
-            if width > self.length:
-                self.length = int(width) + self.actual_padding * 2
-
-            imgpat = cairocffi.SurfacePattern(img)
-
-            scaler = cairocffi.Matrix()
+            img = Img.from_path(icon_file_path)
 
-            scaler.scale(sp, sp)
-            scaler.scale(self.scale, self.scale)
-            factor = (1 - 1 / self.scale) / 2
-            scaler.translate(-width * factor, -width * factor)
-            scaler.translate(self.actual_padding * -1, 0)
-            imgpat.set_matrix(scaler)
+            new_height = (self.height - 2) * self.scale
+            img.resize(height=new_height)
+            if img.width > self.length:
+                self.length = img.width + self.actual_padding * 2
 
-            imgpat.set_filter(cairocffi.FILTER_BEST)
-            self.surfaces[layout_name] = imgpat
+            self.surfaces[layout_name] = img
 
         self.icons_loaded = True
 


Test patch: