Merge pull request #366 from ncoop/iss364
Notification with update summary on_rightclick
This commit is contained in:
commit
806ebe9060
@ -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()
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user