Merge pull request #366 from ncoop/iss364

Notification with update summary on_rightclick
This commit is contained in:
enkore 2016-06-23 15:16:26 +02:00 committed by GitHub
commit 806ebe9060
6 changed files with 104 additions and 32 deletions

View File

@ -2,6 +2,7 @@ import threading
from i3pystatus import SettingsBase, Module, formatp
from i3pystatus.core.util import internet, require
from i3pystatus.core.desktop import DesktopNotification
class Backend(SettingsBase):
@ -18,13 +19,17 @@ class Updates(Module):
Left clicking on the module will refresh the count of upgradeable packages.
This may be used to dismiss the notification after updating your system.
Right clicking shows a desktop notification with a summary count and a list
of available updates.
.. rubric:: Available formatters
* `{count}` Sum of all available updates from all backends.
* For each backend registered there is one formatter named after the backend,
multiple identical backends do not accumulate, but overwrite each other.
* For example, `{Cower}` (note capitcal C) is the number of updates reported by
the cower backend, assuming it has been registered.
* For each backend registered there is one formatter named after the
backend, multiple identical backends do not accumulate, but overwrite
each other.
* For example, `{Cower}` (note capital C) is the number of updates
reported by the cower backend, assuming it has been registered.
.. rubric:: Usage example
@ -50,9 +55,16 @@ class Updates(Module):
("backends", "Required list of backends used to check for updates."),
("format", "Format used when updates are available. "
"May contain formatters."),
("format_no_updates", "String that is shown if no updates are available."
" If not set the module will be hidden if no updates are available."),
("format_working", "Format used while update queries are run. By default the same as ``format``."),
("format_no_updates", "String that is shown if no updates are "
"available. If not set the module will be hidden if no updates "
"are available."),
("format_working", "Format used while update queries are run. By "
"default the same as ``format``."),
("format_summary", "Format for the summary line of notifications. By "
"default the same as ``format``."),
("notification_icon", "Icon shown when reporting the list of updates. "
"Default is ``software-update-available``, and can be "
"None for no icon."),
"color",
"color_no_updates",
"color_working",
@ -64,21 +76,27 @@ class Updates(Module):
format = "Updates: {count}"
format_no_updates = None
format_working = None
format_summary = None
notification_icon = "software-update-available"
color = "#00DD00"
color_no_updates = None
color_working = None
on_leftclick = "run"
on_rightclick = "report"
def init(self):
if not isinstance(self.backends, list):
self.backends = [self.backends]
if self.format_working is None: # we want to allow an empty format
self.format_working = self.format
if self.format_summary is None: # we want to allow an empty format
self.format_summary = self.format
self.color_working = self.color_working or self.color
self.data = {
"count": 0
}
self.notif_body = {}
self.condition = threading.Condition()
self.thread = threading.Thread(target=self.update_thread, daemon=True)
self.thread.start()
@ -95,7 +113,9 @@ class Updates(Module):
for backend in self.backends:
key = backend.__class__.__name__
if key not in self.data:
self.data[key] = '?'
self.data[key] = "?"
if key not in self.notif_body:
self.notif_body[key] = ""
self.output = {
"full_text": formatp(self.format_working, **self.data).strip(),
@ -104,9 +124,11 @@ class Updates(Module):
updates_count = 0
for backend in self.backends:
updates = backend.updates
name = backend.__class__.__name__
updates, notif_body = backend.updates
updates_count += updates
self.data[backend.__class__.__name__] = updates
self.data[name] = updates
self.notif_body[name] = notif_body or ""
if updates_count == 0:
self.output = {} if not self.format_no_updates else {
@ -124,3 +146,12 @@ class Updates(Module):
def run(self):
with self.condition:
self.condition.notify()
def report(self):
DesktopNotification(
title=formatp(self.format_summary, **self.data).strip(),
body="\n".join(self.notif_body.values()),
icon=self.notification_icon,
urgency=1,
timeout=0,
).display()

View File

@ -23,10 +23,14 @@ class AptGet(Backend):
command = "apt-get upgrade -s -o Dir::State::Lists=" + cache_dir
apt = run_through_shell(command.split())
update_count = 0
for line in apt.out.split("\n"):
if line.startswith("Inst"):
update_count += 1
return update_count
out = apt.out.splitlines()
out = [line[5:] for line in apt.out if line.startswith("Inst ")]
return out.count("\n"), out
Backend = AptGet
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))

View File

@ -13,6 +13,12 @@ class Cower(Backend):
def updates(self):
command = ["cower", "-u"]
cower = run_through_shell(command)
return cower.out.count('\n')
return cower.out.count('\n'), cower.out
Backend = Cower
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))

View File

@ -1,11 +1,14 @@
from i3pystatus.core.command import run_through_shell
from i3pystatus.updates import Backend
from re import split
from re import split, sub
class Dnf(Backend):
"""
Gets update count for RPM-based distributions with dnf.
Gets updates for RPM-based distributions with `dnf check-update`.
The notification body consists of the status line followed by the package
name and version for each update.
https://dnf.readthedocs.org/en/latest/command_ref.html#check-update-command
"""
@ -14,12 +17,22 @@ class Dnf(Backend):
def updates(self):
command = ["dnf", "check-update"]
dnf = run_through_shell(command)
if dnf.err:
return "?", dnf.err
raw = dnf.out
update_count = 0
if dnf.rc == 100:
lines = dnf.out.splitlines()[2:]
lines = [l for l in lines if len(split("\s{2,}", l.rstrip())) == 3]
lines = raw.splitlines()[2:]
lines = [l for l in lines if len(split("\s+", l.rstrip())) == 3]
update_count = len(lines)
return update_count
notif_body = sub(r"(\S+)\s+(\S+)\s+\S+\s*\n", r"\1: \2\n", raw)
return update_count, notif_body
Backend = Dnf
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))

View File

@ -12,6 +12,12 @@ class Pacman(Backend):
def updates(self):
command = ["checkupdates"]
checkupdates = run_through_shell(command)
return checkupdates.out.count('\n')
return checkupdates.out.count("\n"), checkupdates.out
Backend = Pacman
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))

View File

@ -1,4 +1,3 @@
import re
from i3pystatus.core.command import run_through_shell
from i3pystatus.updates import Backend
@ -6,16 +5,22 @@ from i3pystatus.updates import Backend
class Yaourt(Backend):
"""
This module counts the available updates using yaourt.
By default it will only count aur packages. Thus it can be used with the pacman backend like this:
By default it will only count aur packages. Thus it can be used with the
pacman backend like this:
from i3pystatus.updates import pacman, yaourt
status.register("updates", backends = [pacman.Pacman(), yaourt.Yaourt()])
.. code-block:: python
If you want to count both pacman and aur packages with this module you can set the variable
count_only_aur = False like this:
from i3pystatus.updates import pacman, yaourt
status.register("updates", backends = \
[pacman.Pacman(), yaourt.Yaourt()])
from i3pystatus.updates import yaourt
status.register("updates", backends = [yaourt.Yaourt(False)])
If you want to count both pacman and aur packages with this module you can
set the variable count_only_aur = False like this:
.. code-block:: python
from i3pystatus.updates import yaourt
status.register("updates", backends = [yaourt.Yaourt(False)])
"""
def __init__(self, aur_only=True):
@ -25,8 +30,15 @@ class Yaourt(Backend):
def updates(self):
command = ["yaourt", "-Qua"]
checkupdates = run_through_shell(command)
out = checkupdates.out
if(self.aur_only):
return len(re.findall("^aur/", checkupdates.out, flags=re.M))
return checkupdates.out.count("\n")
out = [line for line in out if line.startswith("aur")]
return out.count("\n"), out
Backend = Yaourt
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))