From 9a3fdf1b65b4b23988f3febdb5f350c888615e6b Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Sat, 2 Dec 2017 09:29:26 +0100 Subject: [PATCH] Add KuroWMII layout that manages new windows in a way I like, add some more shortcuts, improve battery icon, automatically open programs on workspaces and remove the GroupRules for them after they spawn so that new instances of that app don't open on that workspace any more. --- kuro/config.py | 3 ++ kuro/theme.py | 80 +++++++++++++++++++++++++++++++++----- kuro/utils/general.py | 54 +++++++++++++++++++++++-- kuro/utils/kb_backlight.py | 2 +- kuro/utils/layouts.py | 51 ++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 kuro/utils/layouts.py diff --git a/kuro/config.py b/kuro/config.py index a7c8486..ba86da4 100644 --- a/kuro/config.py +++ b/kuro/config.py @@ -10,8 +10,11 @@ class Config(BaseConfig): # Default Applications app_terminal = "terminator" app_launcher = "dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'" + web_browser = "firefox" + file_manager = "thunar" cmd_brightness_up = "sudo /usr/bin/xbacklight -inc 10" cmd_brightness_down = "sudo /usr/bin/xbacklight -dec 10" + lock_command = "/home/kevin/bin/lock.sh" # Images desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png" diff --git a/kuro/theme.py b/kuro/theme.py index c2d3b1f..7c00083 100644 --- a/kuro/theme.py +++ b/kuro/theme.py @@ -7,8 +7,9 @@ from kuro.utils import general as utils # Import variables from kuro.base import BaseTheme -from kuro.utils.general import display_wm_class +from kuro.utils.general import display_wm_class, test_popups from kuro.utils.kb_backlight import handle_focus_change as kb_handle_focus_change +from kuro.utils import layouts as kuro_layouts try: from kuro.config import Config @@ -103,9 +104,17 @@ class Kuro(BaseTheme): # Super-R to start dmenu_run Key([self.mod], "r", lazy.spawn(Config.get('app_launcher', "dmenu_run"))), + # Super-B to start webbrowser + Key([self.mod], "b", lazy.spawn(Config.get('web_browser', "xterm links"))), + + # Super-F to start file manager +# Key([self.mod], "f", lazy.spawn(Config.get('file_manager', "thunar"))), + # Super-Shift-R to start spawncmd Key([self.mod, "shift"], "r", lazy.spawncmd()), + # Lock shortcut + Key([self.mod], "l", lazy.spawn(Config.get('lock_command', "i3lock"))), # Backlight keys Key([], "XF86MonBrightnessUp", lazy.spawn(Config.get('cmd_brightness_up', 'xbacklight -inc 10'))), @@ -120,9 +129,6 @@ class Kuro(BaseTheme): # Restart QTile Key([self.mod, "control"], "r", lazy.restart()), - # Redraw the top bar - Key([self.mod, "shift", "control"], "r", lazy.function(self.redraw_bar)), - # Shutdown QTile Key([self.mod, "control"], "q", lazy.shutdown()), @@ -132,20 +138,39 @@ class Kuro(BaseTheme): ## # Debug keyboard shortcuts ## - Key([self.mod, "control"], "w", lazy.function(display_wm_class)) + Key([self.mod, "control"], "w", lazy.function(display_wm_class)), + + # Redraw the top bar + Key([self.mod, "shift", "control"], "r", lazy.function(self.redraw_bar)), + + # Spawn a popup, and despawn it after 3 seconds + Key([self.mod, "control"], "p", lazy.function(test_popups)), ] def init_groups(self): self.log_debug("Initializing groups") - + + groups = [] + # http://fontawesome.io/cheatsheet - return [Group(i) for i in ""] + groups.append(Group("", spawn=Config.get('web_browser', "xterm links"))) + groups.append(Group("", spawn=Config.get('app_terminal', "xterm"))) + groups.append(Group("")) + groups.append(Group("", spawn="franz4-bin")) + groups.append(Group("", spawn="quasselclient")) + groups.append(Group("", spawn=Config.get('file_manager', "thunar"))) + groups.append(Group("", spawn="thunderbird")) + groups.append(Group("")) + groups.append(Group("", spawn="qupzilla https://music.kurocon.nl/")) + groups.append(Group("")) + + return groups def init_layouts(self): self.log_debug("Initializing layouts") return [ - layout.Wmii( + kuro_layouts.KuroWmii( border_focus=Config.get('colour_border_focus', "#ffffff"), border_focus_stack=Config.get('colour_border_normal', "#777777"), border_normal=Config.get('colour_border_normal', "#777777"), @@ -261,7 +286,7 @@ class Kuro(BaseTheme): frequency=2, ), - widget.BatteryIcon( + utils.KuroBatteryIcon( battery_name=Config.get('battery_name', 'BAT0'), energy_full_file=Config.get('battery_energy_full_file', 'charge_full'), energy_now_file=Config.get('battery_energy_now_file', 'charge_now'), @@ -378,6 +403,8 @@ class Kuro(BaseTheme): self.log_debug("Updating keys") for i, g in enumerate(self.groups): + if i == 9: + i = -1 # mod1 + number = switch to group self.keys.append( Key([self.mod], str(i + 1), lazy.group[g.name].toscreen()) @@ -441,3 +468,38 @@ class Kuro(BaseTheme): def callback_focus_change(self, *args, **kwargs): kb_handle_focus_change(self) + + initial_windows = [] + + def callback_startup_complete(self, *args, **kwargs): + # Only run on first startup + if not self.qtile.no_spawn: + dg = self.qtile.dgroups + for r in dg.rules: + pid = -1 + # noinspection PyProtectedMember + for r2 in r.match._rules: + if r2[0] == "net_wm_pid": + pid = r2[1] + break + if pid != -1: + self.initial_windows.append((pid, r.group)) + + self.callback_client_new() + + def callback_client_new(self, *args, **kwargs): + if len(self.initial_windows) > 0: + init = self.initial_windows.copy() + for pid, gname in init: + for group in self.qtile.groups: + if len(group.windows) > 0: + for window in group.windows: + w_pid = window.window.get_net_wm_pid() + if pid == w_pid: + c = self.qtile.dgroups.rules_map.copy() + for rid, r in c.items(): + if r.matches(window): + self.qtile.dgroups.remove_rule(rid) + self.initial_windows.remove((pid, gname)) + self.log_info("Removed group rule for PID {}, window {}".format(pid, window.name)) + self.log_info(str(self.qtile.dgroups.rules_map)) diff --git a/kuro/utils/general.py b/kuro/utils/general.py index ab2ff17..255dc6a 100644 --- a/kuro/utils/general.py +++ b/kuro/utils/general.py @@ -1,10 +1,12 @@ import os import re import subprocess +from time import sleep import cairocffi import notify2 from libqtile import widget, bar +from libqtile.window import Internal from libqtile.bar import Bar from libqtile.utils import catch_exception_and_warn, UnixCommandNotFound from libqtile.widget import base @@ -13,6 +15,7 @@ from libqtile.widget.check_updates import CheckUpdates from libqtile.widget.image import Image from libqtile.widget.sensors import ThermalSensor from libqtile.widget.volume import Volume +from libqtile.widget.battery import BatteryIcon from libqtile.widget.wlan import get_status from libqtile.log_utils import logger from notify2 import Notification, URGENCY_NORMAL @@ -80,13 +83,45 @@ def notify(title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None): def spawn_popup(qtile, x, y, text): + """ + :param qtile: The main qtile instance + :type qtile: Qtile + :param x: x-coordinate + :type x: int + :param y: y-coordinate + :type y: int + :param text: String to display + :type text: str + :return: The popup instance + :rtype: Internal + """ + popup = Internal.create( + qtile, x, y, 100, 100, opacity=1 + ) + # Create textwidget for in window - pass + popup.bordercolor = "#000000" + popup.borderwidth = 1 + + popup.focus(False) + + #popup. + + return popup - # window.Internal.create( - # qtile, x, y, width, height, opacity=1 - # ) +def despawn_popup(popup): + """ + :type popup: Internal + :param popup: The popup to despawn + """ + popup.kill() + + +def test_popups(qtile): + popup = spawn_popup(qtile, 10, 10, "Hello World!") + sleep(3) + despawn_popup(popup) def display_wm_class(qtile): @@ -218,6 +253,17 @@ class CheckUpdatesYaourt(CheckUpdates): elif button == BUTTON_MIDDLE and self.execute is not None: subprocess.Popen(self.execute, shell=True) +class KuroBatteryIcon(BatteryIcon): + status_cmd = "acpi" + + def button_press(self, x, y, button): + if button == BUTTON_LEFT: + output = subprocess.check_output(self.status_cmd).decode('utf-8') + + notify( + "Battery Status", + output + ) class PulseVolumeWidget(Volume): diff --git a/kuro/utils/kb_backlight.py b/kuro/utils/kb_backlight.py index d028aa9..35f46a9 100644 --- a/kuro/utils/kb_backlight.py +++ b/kuro/utils/kb_backlight.py @@ -59,7 +59,7 @@ def handle_focus_change(theme): name = window.name if wm_class: - theme.log_info(str(wm_class)) + theme.log_info("{}, {}".format(wm_class, name)) # Check which window we entered and do some special effects if it is a special window. diff --git a/kuro/utils/layouts.py b/kuro/utils/layouts.py new file mode 100644 index 0000000..7b34071 --- /dev/null +++ b/kuro/utils/layouts.py @@ -0,0 +1,51 @@ +from libqtile.layout.wmii import Wmii + + +class KuroWmii(Wmii): + def cmd_previous(self): + super(KuroWmii, self).cmd_previous() + + def cmd_next(self): + super(KuroWmii, self).cmd_next() + + def add(self, client): + """ + Add a new client window to the layout and focus it. It will be added to either the current column if there + are less rows in the current column than columns on the screen, or to a new row to the right of the current + column if there are less columns than rows in the current column. + :param client: The client window to add. + """ + 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) + else: + num_cols = len(self.columns) + num_rows_curr_col = len(c['rows']) + if num_rows_curr_col < num_cols: + c['rows'].append(client) + else: + self.add_column_to_right(c, client) + self.focus(client) + + def add_column_to_right(self, column, win): + """ + Adds a new column to the right of the given column with the given window in it + :param column: The column that's going to be to the left of the new column + :param win: The window to add to the new column + """ + 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]} + + try: + index = self.columns.index(column) + 1 + except ValueError: + index = 0 + + self.columns.insert(index, c)