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.
#
# 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 . import command
from . import hook
from . import utils
from . import xcbq

from six import MAXSIZE
import warnings


[docs]class Key(object): """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", "") if key not in xcbq.keysyms: raise utils.QtileError("Unknown key: %s" % key) self.keysym = xcbq.keysyms[key] try: self.modmask = utils.translate_masks(self.modifiers) except KeyError as v: raise utils.QtileError(v) def __repr__(self): return "<Key (%s, %s)>" % (self.modifiers, self.key)
[docs]class Drag(object): """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 __init__(self, modifiers, button, *commands, **kwargs): self.start = kwargs.get("start") self.focus = kwargs.get("focus", "before") self.modifiers = modifiers self.button = button self.commands = commands try: self.button_code = int(self.button.replace('Button', '')) self.modmask = utils.translate_masks(self.modifiers) except KeyError as v: raise utils.QtileError(v) def __repr__(self): return "<Drag (%s, %s)>" % (self.modifiers, self.button)
[docs]class Click(object): """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): self.focus = kwargs.get("focus", "before") self.modifiers = modifiers self.button = button self.commands = commands try: self.button_code = int(self.button.replace('Button', '')) self.modmask = utils.translate_masks(self.modifiers) except KeyError as v: raise utils.QtileError(v) def __repr__(self): return "<Click (%s, %s)>" % (self.modifiers, self.button)
[docs]class EzConfig(object): """ 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(EzKey, self).__init__(modkeys, key, *commands) class EzClick(EzConfig, Click): def __init__(self, btndef, *commands, **kwargs): modkeys, button = self.parse(btndef) button = 'Button%s' % button super(EzClick, self).__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(EzDrag, self).__init__(modkeys, button, *commands, **kwargs) class ScreenRect(object): 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): self.group = None self.previous_group = None self.top = 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.setGroup(group) for i in self.gaps: i._configure(qtile, self) @property def gaps(self): return (i for i in [self.top, 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 + self.top.size if self.top 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 self.top: val -= self.top.size if self.bottom: val -= self.bottom.size return val def get_rect(self): return ScreenRect(self.dx, self.dy, self.dwidth, self.dheight) def setGroup(self, new_group, save_prev=True): """Put group on this screen""" if new_group.screen == self: return if save_prev: self.previous_group = self.group if new_group is None: return if new_group.screen: # g1 <-> s1 (self) # g2 (new_group) <-> s2 to # g1 <-> s2 # g2 <-> s1 g1 = self.group s1 = self g2 = new_group s2 = new_group.screen s2.group = g1 g1._setScreen(s2) s1.group = g2 g2._setScreen(s1) else: old_group = self.group self.group = new_group # display clients of the new group and then hide from old group # to remove the screen flickering new_group._setScreen(self) if old_group is not None: old_group._setScreen(None) hook.fire("setgroup") hook.fire("focus_change") hook.fire( "layout_change", self.group.layouts[self.group.currentLayout], self.group ) def _items(self, name): if name == "layout": return (True, list(range(len(self.group.layouts)))) elif name == "window": return (True, [i.window.wid for i in self.group.windows]) 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 self.group.layout else: return utils.lget(self.group.layouts, sel) elif name == "window": if sel is None: return self.group.currentWindow else: for i in self.group.windows: 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): x = x or self.x y = y or self.y w = w or self.width h = h or self.height self._configure(self.qtile, self.index, x, y, w, h, self.group) for bar in [self.top, self.bottom, self.left, self.right]: if bar: bar.draw() self.qtile.call_soon(self.group.layoutAll())
[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 = self.group.nextGroup(skip_empty, skip_managed) self.setGroup(n) return n.name
[docs] def cmd_prev_group(self, skip_empty=False, skip_managed=False): """Switch to the previous group""" n = self.group.prevGroup(skip_empty, skip_managed) self.setGroup(n) return n.name
[docs] def cmd_toggle_group(self, group_name=None): """Switch to the selected group or to the previously active one""" group = self.qtile.groupMap.get(group_name) if group in (self.group, None): group = self.previous_group self.setGroup(group)
[docs] def cmd_togglegroup(self, groupName=None): """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(object): """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. 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 default layout for this group (e.g. 'max' or 'stack') layouts : list the group layouts list overriding global layouts persist : boolean should this group stay alive with no member windows? init : boolean is this group alive when qtile starts? position : int group position """ 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=MAXSIZE): self.name = name 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)>' % (self.name, attrs)
[docs]class Match(object): """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 = client.name 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 self.compare(c): callback(c) def __repr__(self): return '<Match %s>' % self._rules
[docs]class Rule(object): """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 self.group = group self.float = float self.intrusive = intrusive self.break_on_match = break_on_match def matches(self, w): return self.match.compare(w) def __repr__(self): actions = utils.describe_attributes(self, ['group', 'float', 'intrusive', 'break_on_match']) return '<Rule match=%r actions=(%s)>' % (self.match, actions)