Source code for libqtile.config

# Copyright (c) 2012-2015 Tycho Andersen
# Copyright (c) 2013 xarvh
# Copyright (c) 2013 horsik
# Copyright (c) 2013-2014 roger
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 ramnes
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 Adi Sieker
# 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.

from . import command
from . import configurable
from . import hook
from . import utils
import sys

import warnings

[docs]class Key: """Defines a keybinding. Parameters ========== modifiers: A list of modifier specifications. Modifier specifications are one of: "shift", "lock", "control", "mod1", "mod2", "mod3", "mod4", "mod5". key: A key specification, e.g. "a", "Tab", "Return", "space". commands: A list of lazy command objects generated with the command.lazy helper. If multiple Call objects are specified, they are run in sequence. kwds: A dictionary containing "desc", allowing a description to be added """ def __init__(self, modifiers, key, *commands, **kwds): self.modifiers = modifiers self.key = key self.commands = commands self.desc = kwds.get("desc", "") def __repr__(self): return "<Key (%s, %s)>" % (self.modifiers, self.key)
class Mouse: def __init__(self, modifiers, button, *commands, **kwargs): self.focus = kwargs.pop("focus", "before") self.modifiers = modifiers self.button = button self.commands = commands self.button_code = int(self.button.replace('Button', '')) for k, v in kwargs.items(): setattr(self, k, v)
[docs]class Drag(Mouse): """Defines binding of a mouse to some dragging action On each motion event command is executed with two extra parameters added x and y offset from previous move It focuses clicked window by default. If you want to prevent it pass, `focus=None` as an argument """ def __repr__(self): return "<Drag (%s, %s)>" % (self.modifiers, self.button)
[docs]class Click(Mouse): """Defines binding of a mouse click It focuses clicked window by default. If you want to prevent it, pass `focus=None` as an argument """ def __init__(self, modifiers, button, *commands, **kwargs): super().__init__(modifiers, button, *commands, **kwargs) def __repr__(self): return "<Click (%s, %s)>" % (self.modifiers, self.button)
[docs]class EzConfig: """ Helper class for defining key and button bindings in an emacs-like format. Inspired by Xmonad's XMonad.Util.EZConfig. """ modifier_keys = { 'M': 'mod4', 'A': 'mod1', 'S': 'shift', 'C': 'control', } def parse(self, spec): """ Splits an emacs keydef into modifiers and keys. For example: "M-S-a" -> ['mod4', 'shift'], 'a' "A-<minus>" -> ['mod1'], 'minus' "C-<Tab>" -> ['control'], 'Tab' """ mods = [] keys = [] for key in spec.split('-'): if not key: break if key in self.modifier_keys: if keys: msg = 'Modifiers must always come before key/btn: %s' raise utils.QtileError(msg % spec) mods.append(self.modifier_keys[key]) continue if len(key) == 1: keys.append(key) continue if len(key) > 3 and key[0] == '<' and key[-1] == '>': keys.append(key[1:-1]) continue if not keys: msg = 'Invalid key/btn specifier: %s' raise utils.QtileError(msg % spec) if len(keys) > 1: msg = 'Key chains are not supported: %s' % spec raise utils.QtileError(msg) return mods, keys[0]
class EzKey(EzConfig, Key): def __init__(self, keydef, *commands): modkeys, key = self.parse(keydef) super().__init__(modkeys, key, *commands) class EzClick(EzConfig, Click): def __init__(self, btndef, *commands, **kwargs): modkeys, button = self.parse(btndef) button = 'Button%s' % button super().__init__(modkeys, button, *commands, **kwargs) class EzDrag(EzConfig, Drag): def __init__(self, btndef, *commands, **kwargs): modkeys, button = self.parse(btndef) button = 'Button%s' % button super().__init__(modkeys, button, *commands, **kwargs) class ScreenRect: def __init__(self, x, y, width, height): self.x = x self.y = y self.width = width self.height = height def __repr__(self): return '<%s %d,%d %d,%d>' % ( self.__class__.__name__, self.x, self.y, self.width, self.height ) def hsplit(self, columnwidth): assert columnwidth > 0 assert columnwidth < self.width return ( self.__class__(self.x, self.y, columnwidth, self.height), self.__class__( self.x + columnwidth, self.y, self.width - columnwidth, self.height ) ) def vsplit(self, rowheight): assert rowheight > 0 assert rowheight < self.height return ( self.__class__(self.x, self.y, self.width, rowheight), self.__class__( self.x, self.y + rowheight, self.width, self.height - rowheight ) )
[docs]class Screen(command.CommandObject): """A physical screen, and its associated paraphernalia. Define a screen with a given set of Bars of a specific geometry. Note that bar.Bar objects can only be placed at the top or the bottom of the screen (bar.Gap objects can be placed anywhere). Also, ``x``, ``y``, ``width``, and ``height`` aren't specified usually unless you are using 'fake screens'. Parameters ========== top: List of Gap/Bar objects, or None. bottom: List of Gap/Bar objects, or None. left: List of Gap/Bar objects, or None. right: List of Gap/Bar objects, or None. x : int or None y : int or None width : int or None height : int or None """ def __init__(self, top=None, bottom=None, left=None, right=None, x=None, y=None, width=None, height=None): = None self.previous_group = None = top self.bottom = bottom self.left = left self.right = right self.qtile = None self.index = None # x position of upper left corner can be > 0 # if one screen is "right" of the other self.x = x self.y = y self.width = width self.height = height def _configure(self, qtile, index, x, y, width, height, group): self.qtile = qtile self.index = index self.x = x self.y = y self.width = width self.height = height self.set_group(group) for i in self.gaps: i._configure(qtile, self) @property def gaps(self): return (i for i in [, self.bottom, self.left, self.right] if i) @property def dx(self): return self.x + self.left.size if self.left else self.x @property def dy(self): return self.y + if else self.y @property def dwidth(self): val = self.width if self.left: val -= self.left.size if self.right: val -= self.right.size return val @property def dheight(self): val = self.height if val -= if self.bottom: val -= self.bottom.size return val def get_rect(self): return ScreenRect(self.dx, self.dy, self.dwidth, self.dheight) def set_group(self, new_group, save_prev=True): """Put group on this screen""" if new_group.screen == self: return if save_prev: self.previous_group = if new_group is None: return if new_group.screen: # g1 <-> s1 (self) # g2 (new_group) <-> s2 to # g1 <-> s2 # g2 <-> s1 g1 = s1 = self g2 = new_group s2 = new_group.screen = g1 g1._set_screen(s2) = g2 g2._set_screen(s1) else: old_group = = new_group # display clients of the new group and then hide from old group # to remove the screen flickering new_group._set_screen(self) if old_group is not None: old_group._set_screen(None)"setgroup")"focus_change") "layout_change",[], ) def _items(self, name): if name == "layout": return (True, list(range(len( elif name == "window": return (True, [i.window.wid for i in]) elif name == "bar": return (False, [x.position for x in self.gaps]) def _select(self, name, sel): if name == "layout": if sel is None: return else: return utils.lget(, sel) elif name == "window": if sel is None: return else: for i in if i.window.wid == sel: return i elif name == "bar": return getattr(self, sel) def resize(self, x=None, y=None, w=None, h=None): if x is None: x = self.x if y is None: y = self.y if w is None: w = self.width if h is None: h = self.height self._configure(self.qtile, self.index, x, y, w, h, for bar in [, self.bottom, self.left, self.right]: if bar: bar.draw() self.qtile.call_soon(
[docs] def cmd_info(self): """ Returns a dictionary of info for this screen. """ return dict( index=self.index, width=self.width, height=self.height, x=self.x, y=self.y )
[docs] def cmd_resize(self, x=None, y=None, w=None, h=None): """Resize the screen""" self.resize(x, y, w, h)
[docs] def cmd_next_group(self, skip_empty=False, skip_managed=False): """Switch to the next group""" n =, skip_managed) self.set_group(n) return
[docs] def cmd_prev_group(self, skip_empty=False, skip_managed=False): """Switch to the previous group""" n =, skip_managed) self.set_group(n) return
[docs] def cmd_toggle_group(self, group_name=None): """Switch to the selected group or to the previously active one""" group = self.qtile.groups_map.get(group_name) if group in (, None): group = self.previous_group self.set_group(group)
[docs] def cmd_togglegroup(self, groupName=None): # noqa """Switch to the selected group or to the previously active one Deprecated: use toggle_group()""" warnings.warn("togglegroup is deprecated, use toggle_group", DeprecationWarning) self.cmd_toggle_group(groupName)
[docs]class Group: """Represents a "dynamic" group These groups can spawn apps, only allow certain Matched windows to be on them, hide when they're not in use, etc. Groups are identified by their name. Parameters ========== name : string the name of this group matches : default ``None`` list of ``Match`` objects whose windows will be assigned to this group exclusive : boolean when other apps are started in this group, should we allow them here or not? spawn : string or list of strings this will be ``exec()`` d when the group is created, you can pass either a program name or a list of programs to ``exec()`` layout : string the name of default layout for this group (e.g. 'max' or 'stack'). This is the name specified for a particular layout in or if not defined it defaults in general the class name in all lower case. layouts : list the group layouts list overriding global layouts. Use this to define a separate list of layouts for this particular group. persist : boolean should this group stay alive with no member windows? init : boolean is this group alive when qtile starts? position : int group position label : string the display name of the group. Use this to define a display name other than name of the group. If set to None, the display name is set to the name. """ def __init__(self, name, matches=None, exclusive=False, spawn=None, layout=None, layouts=None, persist=True, init=True, layout_opts=None, screen_affinity=None, position=sys.maxsize, label=None): = name self.label = label self.exclusive = exclusive self.spawn = spawn self.layout = layout self.layouts = layouts or [] self.persist = persist self.init = init self.matches = matches or [] self.layout_opts = layout_opts or {} self.screen_affinity = screen_affinity self.position = position def __repr__(self): attrs = utils.describe_attributes( self, ['exclusive', 'spawn', 'layout', 'layouts', 'persist', 'init', 'matches', 'layout_opts', 'screen_affinity']) return '<config.Group %r (%s)>' % (, attrs)
[docs]class ScratchPad(Group): """Represents a "ScratchPad" group ScratchPad adds a (by default) invisible group to qtile. That group is used as a place for currently not visible windows spawned by a ``DropDown`` configuration. Parameters ========== name : string the name of this group dropdowns : default ``None`` list of DropDown objects position : int group position label : string The display name of the ScratchPad group. Defaults to the empty string such that the group is hidden in ``GroupList`` widget. """ def __init__(self, name, dropdowns=None, position=sys.maxsize, label=''): Group.__init__(self, name, layout='floating', layouts=['floating'], init=False, position=position, label=label) self.dropdowns = dropdowns if dropdowns is not None else [] def __repr__(self): return '<config.ScratchPad %r (%s)>' % (, ', '.join( for dd in self.dropdowns))
[docs]class Match: """Match for dynamic groups It can match by title, class or role. ``Match`` supports both regular expression objects (i.e. the result of ``re.compile()``) or strings (match as a "include" match). If a window matches any of the things in any of the lists, it is considered a match. Parameters ========== title: things to match against the title (WM_NAME) wm_class: things to match against the second string in WM_CLASS atom role: things to match against the WM_ROLE atom wm_type: things to match against the WM_TYPE atom wm_instance_class: things to match against the first string in WM_CLASS atom net_wm_pid: things to match against the _NET_WM_PID atom (only int allowed in this rule) """ def __init__(self, title=None, wm_class=None, role=None, wm_type=None, wm_instance_class=None, net_wm_pid=None): if not title: title = [] if not wm_class: wm_class = [] if not role: role = [] if not wm_type: wm_type = [] if not wm_instance_class: wm_instance_class = [] if not net_wm_pid: net_wm_pid = [] try: net_wm_pid = list(map(int, net_wm_pid)) except ValueError: error = 'Invalid rule for net_wm_pid: "%s" '\ 'only ints allowed' % str(net_wm_pid) raise utils.QtileError(error) self._rules = [('title', t) for t in title] self._rules += [('wm_class', w) for w in wm_class] self._rules += [('role', r) for r in role] self._rules += [('wm_type', r) for r in wm_type] self._rules += [('wm_instance_class', w) for w in wm_instance_class] self._rules += [('net_wm_pid', w) for w in net_wm_pid] def compare(self, client): for _type, rule in self._rules: if _type == "net_wm_pid": def match_func(value): return rule == value else: match_func = getattr(rule, 'match', None) or \ getattr(rule, 'count') if _type == 'title': value = elif _type == 'wm_class': value = None _value = client.window.get_wm_class() if _value and len(_value) > 1: value = _value[1] elif _type == 'wm_instance_class': value = client.window.get_wm_class() if value: value = value[0] elif _type == 'wm_type': value = client.window.get_wm_type() elif _type == 'net_wm_pid': value = client.window.get_net_wm_pid() else: value = client.window.get_wm_window_role() if value and match_func(value): return True return False def map(self, callback, clients): """Apply callback to each client that matches this Match""" for c in clients: if callback(c) def __repr__(self): return '<Match %s>' % self._rules
[docs]class Rule: """How to act on a Match A Rule contains a Match object, and a specification about what to do when that object is matched. Parameters ========== match : ``Match`` object associated with this ``Rule`` float : auto float this window? intrusive : override the group's exclusive setting? break_on_match : Should we stop applying rules if this rule is matched? """ def __init__(self, match, group=None, float=False, intrusive=False, break_on_match=True): self.match = match = group self.float = float self.intrusive = intrusive self.break_on_match = break_on_match def matches(self, w): return def __repr__(self): actions = utils.describe_attributes(self, ['group', 'float', 'intrusive', 'break_on_match']) return '<Rule match=%r actions=(%s)>' % (self.match, actions)