Source code for libqtile.layout.wmii

# Copyright (c) 2013 Mattias Svala
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 ramnes
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 dmpayton
# Copyright (c) 2014 dequis
# Copyright (c) 2014 Tycho Andersen
# Copyright (c) 2015 Serge Hallyn
#
# 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 __future__ import division

from .base import Layout

# We have an array of columns.  Each columns is a dict containing
# width (in percent), rows (an array of rows), and mode, which is
# either 'stack' or 'split'
#
# Each row is an array of clients

[docs]class Wmii(Layout): """This layout emulates wmii layouts The screen it split into columns, always starting with one. A new window is created in the active window's column. Windows can be shifted left and right. If there is no column when shifting, a new one is created. Each column can be stacked or divided (equally split). This layout implements something akin to wmii's semantics. Each group starts with one column. The first window takes up the whole screen. Next window splits the column in half. Windows can be moved to the column to the left or right. If there is no column in the direction being moved into, a new column is created. Each column can be either stacked (each window takes up the whole vertical real estate) or split (the windows are split equally vertically in the column) Columns can be grown horizontally (cmd_grow_left/right). My config.py has the following added:: Key( [mod, "shift", "control"], "l", lazy.layout.grow_right() ), Key( [mod, "shift"], "l", lazy.layout.shuffle_right() ), Key( [mod, "shift", "control"], "h", lazy.layout.grow_left() ), Key( [mod, "shift"], "h", lazy.layout.shuffle_left() ), Key( [mod], "s", lazy.layout.toggle_split() ), """ defaults = [ ("border_focus", "#881111", "Border colour for the focused window."), ("border_normal", "#220000", "Border colour for un-focused windows."), ("border_focus_stack", "#0000ff", "Border colour for un-focused windows."), ("border_normal_stack", "#000022", "Border colour for un-focused windows."), ("grow_amount", 5, "Amount by which to grow/shrink a window."), ("border_width", 2, "Border width."), ("name", "wmii", "Name of this layout."), ("margin", 0, "Margin of the layout"), ] def __init__(self, **config): Layout.__init__(self, **config) self.add_defaults(Wmii.defaults) self.current_window = None self.clients = [] self.columns = [{'active': 0, 'width': 100, 'mode': 'split', 'rows': []}] def info(self): d = Layout.info(self) d["current_window"] = self.current_window d["clients"] = [x.name for x in self.clients] return d def add_column(self, prepend, win): newwidth = int(100 / (len(self.columns) + 1)) # we are only called if there already is a column, simplifies things for c in self.columns: c['width'] = newwidth c = {'width': newwidth, 'mode': 'split', 'rows': [win]} if prepend: self.columns.insert(0, c) else: self.columns.append(c) def clone(self, group): c = Layout.clone(self, group) c.current_window = None c.clients = [] c.columns = [{'active': 0, 'width': 100, 'mode': 'split', 'rows': []}] return c def current_column(self): if self.current_window is None: return None for c in self.columns: if self.current_window in c['rows']: return c return None def add(self, client): self.clients.append(client) c = self.current_column() if c is None: if len(self.columns) == 0: self.columns = [{'active': 0, 'width': 100, 'mode': 'split', 'rows': []}] c = self.columns[0] c['rows'].append(client) self.focus(client) def remove(self, client): if client not in self.clients: return self.clients.remove(client) for c in self.columns: if client in c['rows']: ridx = c['rows'].index(client) cidx = self.columns.index(c) c['rows'].remove(client) if len(c['rows']) != 0: if client == self.current_window: if ridx > 0: ridx -= 1 newclient = c['rows'][ridx] self.focus(newclient) self.group.focus(self.current_window) return self.current_window # column is now empty, remove it and select the previous one self.columns.remove(c) if len(self.columns) == 0: return None newwidth = int(100 / len(self.columns)) for c in self.columns: c['width'] = newwidth if len(self.columns) == 1: # there is no window at all return None if cidx > 0: cidx -= 1 c = self.columns[cidx] rows = c['rows'] newclient = rows[0] self.focus(newclient) self.group.focus(newclient) return newclient def is_last_column(self, cidx): return cidx == len(self.columns) - 1 def focus(self, client): self.current_window = client for c in self.columns: if client in c['rows']: c['active'] = c['rows'].index(client) def configure(self, client, screen): show = True if client not in self.clients: return ridx = -1 xoffset = int(screen.x) for c in self.columns: if client in c['rows']: ridx = c['rows'].index(client) break xoffset += int(float(c['width']) * screen.width / 100.0) if ridx == -1: return if client == self.current_window: if c['mode'] == 'split': px = self.group.qtile.colorPixel(self.border_focus) else: px = self.group.qtile.colorPixel(self.border_focus_stack) else: if c['mode'] == 'split': px = self.group.qtile.colorPixel(self.border_normal) else: px = self.group.qtile.colorPixel(self.border_normal_stack) if c['mode'] == 'split': oneheight = screen.height / len(c['rows']) yoffset = int(screen.y + oneheight * ridx) win_height = int(oneheight - 2 * self.border_width) else: # stacked if c['active'] != c['rows'].index(client): show = False yoffset = int(screen.y) win_height = int(screen.height - 2 * self.border_width) win_width = int(float(c['width'] * screen.width / 100.0)) win_width -= 2 * self.border_width if show: client.place( xoffset, yoffset, win_width, win_height, self.border_width, px, margin=self.margin, ) client.unhide() else: client.hide() def cmd_toggle_split(self): c = self.current_column() if c['mode'] == "split": c['mode'] = "stack" else: c['mode'] = "split" self.group.layoutAll() def focus_next(self, win): self.cmd_down() return self.curent_window def focus_previous(self, win): self.cmd_up() return self.current_window def focus_first(self): if len(self.columns) == 0: self.columns = [{'active': 0, 'width': 100, 'mode': 'split', 'rows': []}] c = self.columns[0] if len(c['rows']) != 0: return c['rows'][0] def focus_last(self): c = self.columns[len(self.columns) - 1] if len(c['rows']) != 0: return c['rows'][len(c['rows']) - 1] def cmd_left(self): """Switch to the first window on prev column""" c = self.current_column() cidx = self.columns.index(c) if cidx == 0: return cidx -= 1 c = self.columns[cidx] if c['mode'] == "split": self.group.focus(c['rows'][0]) else: self.group.focus(c['rows'][c['active']]) def cmd_right(self): """Switch to the first window on next column""" c = self.current_column() cidx = self.columns.index(c) if self.is_last_column(cidx): return cidx += 1 c = self.columns[cidx] if c['mode'] == "split": self.group.focus(c['rows'][0]) else: self.group.focus(c['rows'][c['active']]) def cmd_up(self): """Switch to the previous window in current column""" c = self.current_column() if c is None: return ridx = c['rows'].index(self.current_window) if ridx == 0: if c['mode'] != "split": ridx = len(c['rows']) - 1 else: ridx -= 1 client = c['rows'][ridx] self.group.focus(client) def cmd_down(self): """Switch to the next window in current column""" c = self.current_column() if c is None: return ridx = c['rows'].index(self.current_window) if ridx == len(c['rows']) - 1: if c['mode'] != "split": ridx = 0 else: ridx += 1 client = c['rows'][ridx] self.group.focus(client) cmd_next = cmd_down cmd_previous = cmd_up def cmd_shuffle_left(self): cur = self.current_window if cur is None: return for c in self.columns: if cur in c['rows']: cidx = self.columns.index(c) if cidx == 0: if len(c['rows']) == 1: return c['rows'].remove(cur) self.add_column(True, cur) if len(c['rows']) == 0: self.columns.remove(c) else: c['rows'].remove(cur) self.columns[cidx - 1]['rows'].append(cur) if len(c['rows']) == 0: self.columns.remove(c) newwidth = int(100 / len(self.columns)) for c in self.columns: c['width'] = newwidth else: if c['active'] >= len(c['rows']): c['active'] = len(c['rows']) - 1 self.group.focus(cur) return def swap_column_width(self, grow, shrink): grower = self.columns[grow] shrinker = self.columns[shrink] amount = self.grow_amount if shrinker['width'] - amount < 5: return grower['width'] += amount shrinker['width'] -= amount def cmd_grow_left(self): cur = self.current_window if cur is None: return for c in self.columns: if cur in c['rows']: cidx = self.columns.index(c) if cidx == 0: # grow left for leftmost-column, shrink left if self.is_last_column(cidx): return self.swap_column_width(cidx + 1, cidx) self.group.focus(cur) return self.swap_column_width(cidx, cidx - 1) self.group.focus(cur) return def cmd_grow_right(self): cur = self.current_window if cur is None: return for c in self.columns: if cur in c['rows']: cidx = self.columns.index(c) if self.is_last_column(cidx): # grow right from right most, shrink right if cidx == 0: return self.swap_column_width(cidx - 1, cidx) self.group.focus(cur) return # grow my width by 20, reduce neighbor to the right by 20 self.swap_column_width(cidx, cidx + 1) self.group.focus(cur) return def cmd_shuffle_right(self): cur = self.current_window if cur is None: return for c in self.columns: if cur in c['rows']: cidx = self.columns.index(c) if self.is_last_column(cidx): if len(c['rows']) == 1: return c['rows'].remove(cur) self.add_column(False, cur) if len(c['rows']) == 0: self.columns.remove(c) else: c['rows'].remove(cur) self.columns[cidx + 1]['rows'].append(cur) if len(c['rows']) == 0: self.columns.remove(c) newwidth = int(100 / len(self.columns)) for c in self.columns: c['width'] = newwidth else: if c['active'] >= len(c['rows']): c['active'] = len(c['rows']) - 1 self.group.focus(cur) return def cmd_shuffle_down(self): for c in self.columns: if self.current_window in c['rows']: r = c['rows'] ridx = r.index(self.current_window) if ridx + 1 < len(r): r[ridx], r[ridx + 1] = r[ridx + 1], r[ridx] client = r[ridx + 1] self.focus(client) self.group.focus(client) return def cmd_shuffle_up(self): for c in self.columns: if self.current_window in c['rows']: r = c['rows'] ridx = r.index(self.current_window) if ridx > 0: r[ridx - 1], r[ridx] = r[ridx], r[ridx - 1] client = r[ridx - 1] self.focus(client) self.group.focus(client) return