Source code for libqtile.dgroups

# Copyright (c) 2011-2012 Florian Mounier
# Copyright (c) 2012-2014 roger
# Copyright (c) 2012 Craig Barnes
# Copyright (c) 2012-2014 Tycho Andersen
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 ramnes
# Copyright (c) 2014 Sebastian Kricner
# Copyright (c) 2014 Sean Vig
#
# 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.

import collections

import libqtile.hook
from libqtile.backend.base import Static
from libqtile.command import lazy
from libqtile.config import Group, Key, Match, Rule
from libqtile.log_utils import logger


[docs]def simple_key_binder(mod, keynames=None): """Bind keys to mod+group position or to the keys specified as second argument""" def func(dgroup): # unbind all for key in dgroup.keys[:]: dgroup.qtile.ungrab_key(key) dgroup.keys.remove(key) if keynames: keys = keynames else: # keys 1 to 9 and 0 keys = list(map(str, list(range(1, 10)) + [0])) # bind all keys for keyname, group in zip(keys, dgroup.qtile.groups): name = group.name key = Key([mod], keyname, lazy.group[name].toscreen()) key_s = Key([mod, "shift"], keyname, lazy.window.togroup(name)) key_c = Key( [mod, "control"], keyname, lazy.group.switch_groups(name) ) dgroup.keys.append(key) dgroup.keys.append(key_s) dgroup.keys.append(key_c) dgroup.qtile.grab_key(key) dgroup.qtile.grab_key(key_s) dgroup.qtile.grab_key(key_c) return func
class DGroups: """Dynamic Groups""" def __init__(self, qtile, dgroups, key_binder=None, delay=1): self.qtile = qtile self.groups = dgroups self.groups_map = {} self.rules = [] self.rules_map = {} self.last_rule_id = 0 for rule in getattr(qtile.config, 'dgroups_app_rules', []): self.add_rule(rule) self.keys = [] self.key_binder = key_binder self._setup_hooks() self._setup_groups() self.delay = delay self.timeout = {} def add_rule(self, rule, last=True): rule_id = self.last_rule_id self.rules_map[rule_id] = rule if last: self.rules.append(rule) else: self.rules.insert(0, rule) self.last_rule_id += 1 return rule_id def remove_rule(self, rule_id): rule = self.rules_map.get(rule_id) if rule: self.rules.remove(rule) del self.rules_map[rule_id] else: logger.warning('Rule "%s" not found', rule_id) def add_dgroup(self, group, start=False): self.groups_map[group.name] = group rule = Rule(group.matches, group=group.name) self.rules.append(rule) if start: self.qtile.add_group(group.name, group.layout, group.layouts, group.label) def _setup_groups(self): for group in self.groups: self.add_dgroup(group, group.init) if group.spawn and not self.qtile.no_spawn: if isinstance(group.spawn, str): spawns = [group.spawn] else: spawns = group.spawn for spawn in spawns: pid = self.qtile.cmd_spawn(spawn) self.add_rule(Rule(Match(net_wm_pid=pid), group.name)) def _setup_hooks(self): libqtile.hook.subscribe.addgroup(self._addgroup) libqtile.hook.subscribe.client_new(self._add) libqtile.hook.subscribe.client_killed(self._del) if self.key_binder: libqtile.hook.subscribe.setgroup( lambda: self.key_binder(self) ) libqtile.hook.subscribe.changegroup( lambda: self.key_binder(self) ) def _addgroup(self, group_name): if group_name not in self.groups_map: self.add_dgroup(Group(group_name, persist=False)) def _add(self, client): if client in self.timeout: logger.debug('Remove dgroup source') self.timeout.pop(client).cancel() # ignore static windows if isinstance(client, Static): return # ignore windows whose groups is already set (e.g. from another hook or # when it was set on state restore) if client.group is not None: return group_set = False intrusive = False for rule in self.rules: # Matching Rules if rule.matches(client): if rule.group: if rule.group in self.groups_map: layout = self.groups_map[rule.group].layout layouts = self.groups_map[rule.group].layouts label = self.groups_map[rule.group].label else: layout = None layouts = None label = None group_added = self.qtile.add_group(rule.group, layout, layouts, label) client.togroup(rule.group) group_set = True group_obj = self.qtile.groups_map[rule.group] group = self.groups_map.get(rule.group) if group and group_added: for k, v in list(group.layout_opts.items()): if isinstance(v, collections.Callable): v(group_obj.layout) else: setattr(group_obj.layout, k, v) affinity = group.screen_affinity if affinity and len(self.qtile.screens) > affinity: self.qtile.screens[affinity].set_group(group_obj) if rule.float: client.enablefloating() if rule.intrusive: intrusive = rule.intrusive if rule.break_on_match: break # If app doesn't have a group if not group_set: current_group = self.qtile.current_group.name if current_group in self.groups_map and \ self.groups_map[current_group].exclusive and \ not intrusive: wm_class = client.get_wm_class() if wm_class: if len(wm_class) > 1: wm_class = wm_class[1] else: wm_class = wm_class[0] group_name = wm_class else: group_name = client.name or 'Unnamed' self.add_dgroup(Group(group_name, persist=False), start=True) client.togroup(group_name) self.sort_groups() def sort_groups(self): grps = self.qtile.groups sorted_grps = sorted(grps, key=lambda g: self.groups_map[g.name].position) if grps != sorted_grps: self.qtile.groups = sorted_grps libqtile.hook.fire("changegroup") def _del(self, client): # ignore static windows if isinstance(client, Static): return group = client.group def delete_client(): # Delete group if empty and don't persist if group and group.name in self.groups_map and \ not self.groups_map[group.name].persist and \ len(group.windows) <= 0: self.qtile.delete_group(group.name) self.sort_groups() del self.timeout[client] logger.debug(f'Deleting {group} in {self.delay}s') self.timeout[client] = self.qtile.call_later( self.delay, delete_client )