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 import SettingsBase, Module, formatp
from i3pystatus.core.util import internet, require from i3pystatus.core.util import internet, require
from i3pystatus.core.desktop import DesktopNotification
class Backend(SettingsBase): class Backend(SettingsBase):
@ -18,13 +19,17 @@ class Updates(Module):
Left clicking on the module will refresh the count of upgradeable packages. Left clicking on the module will refresh the count of upgradeable packages.
This may be used to dismiss the notification after updating your system. 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 .. rubric:: Available formatters
* `{count}` Sum of all available updates from all backends. * `{count}` Sum of all available updates from all backends.
* For each backend registered there is one formatter named after the backend, * For each backend registered there is one formatter named after the
multiple identical backends do not accumulate, but overwrite each other. backend, multiple identical backends do not accumulate, but overwrite
* For example, `{Cower}` (note capitcal C) is the number of updates reported by each other.
the cower backend, assuming it has been registered. * For example, `{Cower}` (note capital C) is the number of updates
reported by the cower backend, assuming it has been registered.
.. rubric:: Usage example .. rubric:: Usage example
@ -50,9 +55,16 @@ class Updates(Module):
("backends", "Required list of backends used to check for updates."), ("backends", "Required list of backends used to check for updates."),
("format", "Format used when updates are available. " ("format", "Format used when updates are available. "
"May contain formatters."), "May contain formatters."),
("format_no_updates", "String that is shown if no updates are available." ("format_no_updates", "String that is shown if no updates are "
" If not set the module will be hidden if no updates are available."), "available. If not set the module will be hidden if no updates "
("format_working", "Format used while update queries are run. By default the same as ``format``."), "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",
"color_no_updates", "color_no_updates",
"color_working", "color_working",
@ -64,21 +76,27 @@ class Updates(Module):
format = "Updates: {count}" format = "Updates: {count}"
format_no_updates = None format_no_updates = None
format_working = None format_working = None
format_summary = None
notification_icon = "software-update-available"
color = "#00DD00" color = "#00DD00"
color_no_updates = None color_no_updates = None
color_working = None color_working = None
on_leftclick = "run" on_leftclick = "run"
on_rightclick = "report"
def init(self): def init(self):
if not isinstance(self.backends, list): if not isinstance(self.backends, list):
self.backends = [self.backends] self.backends = [self.backends]
if self.format_working is None: # we want to allow an empty format if self.format_working is None: # we want to allow an empty format
self.format_working = self.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.color_working = self.color_working or self.color
self.data = { self.data = {
"count": 0 "count": 0
} }
self.notif_body = {}
self.condition = threading.Condition() self.condition = threading.Condition()
self.thread = threading.Thread(target=self.update_thread, daemon=True) self.thread = threading.Thread(target=self.update_thread, daemon=True)
self.thread.start() self.thread.start()
@ -95,7 +113,9 @@ class Updates(Module):
for backend in self.backends: for backend in self.backends:
key = backend.__class__.__name__ key = backend.__class__.__name__
if key not in self.data: 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 = { self.output = {
"full_text": formatp(self.format_working, **self.data).strip(), "full_text": formatp(self.format_working, **self.data).strip(),
@ -104,9 +124,11 @@ class Updates(Module):
updates_count = 0 updates_count = 0
for backend in self.backends: for backend in self.backends:
updates = backend.updates name = backend.__class__.__name__
updates, notif_body = backend.updates
updates_count += 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: if updates_count == 0:
self.output = {} if not self.format_no_updates else { self.output = {} if not self.format_no_updates else {
@ -124,3 +146,12 @@ class Updates(Module):
def run(self): def run(self):
with self.condition: with self.condition:
self.condition.notify() 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 command = "apt-get upgrade -s -o Dir::State::Lists=" + cache_dir
apt = run_through_shell(command.split()) apt = run_through_shell(command.split())
update_count = 0 out = apt.out.splitlines()
for line in apt.out.split("\n"): out = [line[5:] for line in apt.out if line.startswith("Inst ")]
if line.startswith("Inst"): return out.count("\n"), out
update_count += 1
return update_count
Backend = AptGet 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): def updates(self):
command = ["cower", "-u"] command = ["cower", "-u"]
cower = run_through_shell(command) cower = run_through_shell(command)
return cower.out.count('\n') return cower.out.count('\n'), cower.out
Backend = Cower 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.core.command import run_through_shell
from i3pystatus.updates import Backend from i3pystatus.updates import Backend
from re import split from re import split, sub
class Dnf(Backend): 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 https://dnf.readthedocs.org/en/latest/command_ref.html#check-update-command
""" """
@ -14,12 +17,22 @@ class Dnf(Backend):
def updates(self): def updates(self):
command = ["dnf", "check-update"] command = ["dnf", "check-update"]
dnf = run_through_shell(command) dnf = run_through_shell(command)
if dnf.err:
return "?", dnf.err
raw = dnf.out
update_count = 0 update_count = 0
if dnf.rc == 100: if dnf.rc == 100:
lines = dnf.out.splitlines()[2:] lines = raw.splitlines()[2:]
lines = [l for l in lines if len(split("\s{2,}", l.rstrip())) == 3] lines = [l for l in lines if len(split("\s+", l.rstrip())) == 3]
update_count = len(lines) 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 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): def updates(self):
command = ["checkupdates"] command = ["checkupdates"]
checkupdates = run_through_shell(command) checkupdates = run_through_shell(command)
return checkupdates.out.count('\n') return checkupdates.out.count("\n"), checkupdates.out
Backend = Pacman 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.core.command import run_through_shell
from i3pystatus.updates import Backend from i3pystatus.updates import Backend
@ -6,13 +5,19 @@ from i3pystatus.updates import Backend
class Yaourt(Backend): class Yaourt(Backend):
""" """
This module counts the available updates using yaourt. 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:
.. code-block:: python
from i3pystatus.updates import pacman, yaourt from i3pystatus.updates import pacman, yaourt
status.register("updates", backends = [pacman.Pacman(), yaourt.Yaourt()]) status.register("updates", backends = \
[pacman.Pacman(), yaourt.Yaourt()])
If you want to count both pacman and aur packages with this module you can set the variable If you want to count both pacman and aur packages with this module you can
count_only_aur = False like this: set the variable count_only_aur = False like this:
.. code-block:: python
from i3pystatus.updates import yaourt from i3pystatus.updates import yaourt
status.register("updates", backends = [yaourt.Yaourt(False)]) status.register("updates", backends = [yaourt.Yaourt(False)])
@ -25,8 +30,15 @@ class Yaourt(Backend):
def updates(self): def updates(self):
command = ["yaourt", "-Qua"] command = ["yaourt", "-Qua"]
checkupdates = run_through_shell(command) checkupdates = run_through_shell(command)
out = checkupdates.out
if(self.aur_only): if(self.aur_only):
return len(re.findall("^aur/", checkupdates.out, flags=re.M)) out = [line for line in out if line.startswith("aur")]
return checkupdates.out.count("\n") return out.count("\n"), out
Backend = Yaourt Backend = Yaourt
if __name__ == "__main__":
"""
Call this module directly; Print the update count and notification body.
"""
print("Updates: {}\n\n{}".format(*Backend().updates))