Source code for libqtile.widget.currentlayout

import itertools
import os

from libqtile import bar, hook
from libqtile.images import Img
from libqtile.log_utils import logger
from libqtile.widget import base


[docs] class CurrentLayout(base._TextBox): """ Display the icon or the name of the current layout of the current group of the screen on which the bar containing the widget is. If you are using custom layouts, a default icon with question mark will be displayed for them. If you want to use custom icon for your own layout, for example, `FooGrid`, then create a file named "layout-foogrid.png" and place it in `~/.icons` directory. You can as well 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 - `~/.icons` - built-in qtile icons """ defaults = [ ("icon_first", True, "When ``mode='both'``, the icon will display before the text."), ( "mode", "text", "Display only the name of the layout with ``text`` " "or only the icon of the layout with ``icon``. " "Altenatively display the two in order with ``both``", ), ("scale", 1, "Icon's scale factor relative to the bar height."), ( "custom_icon_paths", [], "List of folders where to search icons before " "using built-in icons or icons in ~/.icons dir. " "This can also be used to provide " "missing icons for custom layouts.", ), ] def __init__(self, width=bar.CALCULATED, **config): base._TextBox.__init__(self, "", width, **config) self.add_defaults(CurrentLayout.defaults) self.icons_loaded = False self.img_length = 0 def _configure(self, qtile, bar): assert self.mode in ("text", "icon", "both") base._TextBox._configure(self, qtile, bar) layout_id = self.bar.screen.group.current_layout self.text = self.bar.screen.group.layouts[layout_id].name self.icon_paths = [] self.surfaces = {} self._update_icon_paths() self._setup_images() self.setup_hooks() self.add_callbacks( { "Button1": qtile.next_layout, "Button2": qtile.prev_layout, "Button3": self.change_draw_method, } ) @property def current_layout(self): return self.text def calculate_length(self): if self.mode == "text": return base._TextBox.calculate_length(self) elif self.mode == "icon": return self.img_length + self.padding * 2 # self.mode == "both" # add only one padding because base._TextBox.calculate_length already add two return base._TextBox.calculate_length(self) + self.img_length + self.padding def hook_response(self, layout, group): if group.screen is not None and group.screen == self.bar.screen: self.text = layout.name self.bar.draw() def setup_hooks(self): """ Listens for layout change and performs a redraw when it occurs. """ hook.subscribe.layout_change(self.hook_response) def remove_hooks(self): """ Listens for layout change and performs a redraw when it occurs. """ hook.unsubscribe.layout_change(self.hook_response) def change_draw_method(self): if self.mode == "both": return self.mode = "text" if self.mode != "text" else "icon" self.bar.draw() def draw(self): if self.mode != "text": self.draw_icon() else: base._TextBox.draw(self) def draw_icon(self): if not self.icons_loaded: return try: surface = self.surfaces[self.current_layout] except KeyError: logger.error("No icon for layout %s", self.current_layout) return self.drawer.clear(self.background or self.bar.background) self.drawer.ctx.save() self.rotate_drawer() translatex, translatey = self.width, self.height if self.mode == "both": y = (self.bar.size - self.layout.height) / 2 + 1 if self.bar.horizontal: if self.icon_first: # padding - icon - padding - text - padding x = self.padding + self.img_length + self.padding translatex -= base._TextBox.calculate_length(self) - self.padding else: # padding - text - padding - icon - padding x = self.padding translatex += base._TextBox.calculate_length(self) - self.padding elif self.rotate: if self.icon_first: # padding - icon - padding - text - padding x = self.padding + self.img_length + self.padding translatey -= base._TextBox.calculate_length(self) - self.padding else: # padding - text - padding - icon - padding x = self.padding translatey += base._TextBox.calculate_length(self) - self.padding else: x = 0 if self.icon_first: # padding - icon - padding - text - padding y = self.padding + self.img_length + self.padding translatey -= base._TextBox.calculate_length(self) - self.padding else: # padding - text - padding - icon - padding y = self.padding translatey += base._TextBox.calculate_length(self) - self.padding self.layout.draw(x, y) if not self.bar.horizontal and self.rotate: translatex, translatey = translatey, translatex self.drawer.ctx.translate( (translatex - surface.width) / 2, (translatey - surface.height) / 2, ) self.drawer.ctx.set_source(surface.pattern) self.drawer.ctx.paint() self.drawer.ctx.restore() self.draw_at_default_position() def _get_layout_names(self): """ Returns a sequence of tuples of layout name and lowercased class name strings for each available layout. """ layouts = itertools.chain( self.qtile.config.layouts, (layout for group in self.qtile.config.groups for layout in group.layouts), ) return set((layout.name, layout.__class__.__name__.lower()) for layout in layouts) def _update_icon_paths(self): self.icon_paths = [] # We allow user to override icon search path 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")) self.icon_paths.append(os.path.expanduser("~/.local/share/icons")) # Default icons are in libqtile/resources/layout-icons. # If using default config without any custom icons, # this path will be used. root = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2]) self.icon_paths.append(os.path.join(root, "resources", "layout-icons")) def find_icon_file_path(self, layout_name): for icon_path in self.icon_paths: for extension in ["png", "svg"]: icon_filename = f"layout-{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): """ Loads layout icons. """ new_height = (self.bar.size - 2) * self.scale for names in self._get_layout_names(): layout_name = names[0] # Python doesn't have an ordered set but we can use a dictionary instead # First key is the layout's name (which may have been set by the user), # the second is the class name. If these are the same (i.e. the user hasn't # set a name) then there is only one key in the dictionary. layouts = dict.fromkeys(names) for layout in layouts.keys(): icon_file_path = self.find_icon_file_path(layout) if icon_file_path: break else: logger.warning('No icon found for layout "%s"', layout_name) icon_file_path = self.find_icon_file_path("unknown") img = Img.from_path(icon_file_path) img.resize(height=new_height) img_length = img.width if self.bar.horizontal else img.height if img_length > self.img_length: self.img_length = img_length self.surfaces[layout_name] = img self.icons_loaded = True def finalize(self): self.remove_hooks() base._TextBox.finalize(self)