Skip to content

Commit

Permalink
send_notification: create popup when dbus is not available
Browse files Browse the repository at this point in the history
Currently send_notification will try to send a notification but fail
quietly if there is no notification server running. To get the message
to the user's eyes, it can instead fall back to a transient Popup
window.

This also changes the config-reading code to use this mechanism to
inform the user of a config error, rather than creating a new Config
object just so we can stick a TextBox in it to notify them.
  • Loading branch information
m-col committed Apr 4, 2021
1 parent 1e2f0bb commit dd73f8f
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 17 deletions.
9 changes: 3 additions & 6 deletions libqtile/core/manager.py
Expand Up @@ -36,7 +36,7 @@
import xcffib.xproto

import libqtile
from libqtile import confreader, hook, ipc, utils, window
from libqtile import hook, ipc, utils, window
from libqtile.backend.x11 import xcbq
from libqtile.command import interface
from libqtile.command.base import CommandError, CommandException, CommandObject
Expand Down Expand Up @@ -104,11 +104,8 @@ def load_config(self):
self.config.load()
self.config.validate()
except Exception as e:
logger.exception('Error while reading config file (%s)', e)
self.config = confreader.Config()
from libqtile.widget import TextBox
widgets = self.config.screens[0].bottom.widgets
widgets.insert(0, TextBox('Config Err!'))
msg = "Woops! Error in config: " + e.__class__.__name__
send_notification("Qtile Config", msg, timeout=20000)

self.core.wmname = getattr(self.config, "wmname", "qtile")

Expand Down
40 changes: 32 additions & 8 deletions libqtile/popup.py
Expand Up @@ -19,8 +19,11 @@
# SOFTWARE.


import asyncio

from xcffib.xproto import StackMode

import libqtile
from libqtile import configurable, drawer, pangocffi, window


Expand All @@ -29,26 +32,30 @@ class Popup(configurable.Configurable):
This class can be used to create popup windows that display images and/or text.
"""
defaults = [
('opacity', 1.0, 'Opacity of notifications.'),
('opacity', 1.0, 'Opacity of window.'),
('foreground', '#ffffff', 'Colour of text.'),
('background', '#111111', 'Background colour.'),
('border', '#111111', 'Border colour.'),
('border_width', 0, 'Line width of drawn borders.'),
('border', '#ffffff', 'Border colour.'),
('border_width', 1, 'Line width of drawn borders.'),
('corner_radius', None, 'Corner radius for round corners, or None.'),
('font', 'sans', 'Font used in notifications.'),
('font_size', 14, 'Size of font.'),
('fontshadow', None, 'Colour for text shadows, or None for no shadows.'),
('horizontal_padding', 0, 'Padding at sides of text.'),
('vertical_padding', 0, 'Padding at top and bottom of text.'),
('horizontal_padding', 4, 'Padding at sides of text.'),
('vertical_padding', 4, 'Padding at top and bottom of text.'),
('text_alignment', 'left', 'Text alignment: left, center or right.'),
('wrap', True, 'Whether to wrap text.'),
]

def __init__(self, qtile, x=50, y=50, width=256, height=64, **config):
configurable.Configurable.__init__(self, **config)
configurable.Configurable.__init__(self, x=x, y=y, width=width, height=height, **config)
self.add_defaults(Popup.defaults)
self.qtile = qtile

if qtile.current_screen:
self.x += qtile.current_screen.x
self.y += qtile.current_screen.y

win = qtile.conn.create_window(x, y, width, height)
win.set_property("QTILE_INTERNAL", 1)
self.win = window.Internal(win, qtile)
Expand Down Expand Up @@ -77,8 +84,6 @@ def __init__(self, qtile, x=50, y=50, width=256, height=64, **config):
self.win.handle_KeyPress = self._handle_KeyPress
self.win.handle_ButtonPress = self._handle_ButtonPress

self.x = self.win.x
self.y = self.win.y
if not self.border_width:
self.border = None

Expand Down Expand Up @@ -166,3 +171,22 @@ def hide(self):

def kill(self):
self.win.kill()


def send_popup(message, timeout=10000, **config):
"""Create a transient popup window to display some text."""
popup = Popup(libqtile.qtile, **config)
popup.win.handle_ButtonPress = lambda ev: popup.kill()
popup.clear()
popup.text = message
popup.draw_text()
popup.place()
popup.unhide()
popup.draw()

if timeout:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
return
loop.call_later(timeout / 1000, popup.kill)
5 changes: 2 additions & 3 deletions libqtile/utils.py
Expand Up @@ -194,9 +194,8 @@ def send_notification(title, message, urgent=False, timeout=10000, id=None):
https://developer.gnome.org/notification-spec/
"""
if not has_dbus:
logger.warning(
"dbus-next is not installed. Unable to send notifications."
)
from libqtile.popup import send_popup
send_popup(f"<b>{title}</b>\n{message}", timeout=timeout)
return -1

id = randint(10, 1000) if id is None else id
Expand Down
1 change: 1 addition & 0 deletions test/test_popup.py
Expand Up @@ -33,6 +33,7 @@ def test_popup_focus(manager):

# we have to add .conn so that Popup thinks this is libqtile.qtile
manager.conn = xcbq.Connection(manager.display)
manager.current_screen = None

try:
popup = Popup(manager)
Expand Down

0 comments on commit dd73f8f

Please sign in to comment.