import collections
import libqtile.hook
from libqtile.backend.base import Static
from libqtile.config import Group, Key, Rule
from libqtile.lazy import lazy
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.qtile.config.keys.remove(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.extend([key, key_s, key_c])
dgroup.qtile.config.keys.extend([key, key_s, 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,
screen_affinity=group.screen_affinity,
)
def _setup_groups(self):
def launch_app(app, group):
def launcher():
self.qtile.spawn(app, group=group)
return launcher
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:
libqtile.hook.subscribe.startup_once(launch_app(spawn, group=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
delete_rules = []
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.abc.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.enable_floating()
if rule.intrusive:
intrusive = rule.intrusive
if rule.one_time:
delete_rules.append(rule)
if rule.break_on_match:
break
if delete_rules:
ids_to_delete = [
rule_id for rule_id, rule in self.rules_map.items() if rule in delete_rules
]
for rule_id in ids_to_delete:
self.remove_rule(rule_id)
# 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]
if group is not None and group.persist:
return
logger.debug("Deleting %s in %ss", group, self.delay)
if client not in self.timeout:
self.timeout[client] = self.qtile.call_later(self.delay, delete_client)