Merge branch 'Master' into hints

# Conflicts:
#	i3pystatus/core/modules.py
This commit is contained in:
Lukáš Mandák 2015-06-18 21:05:54 +02:00
commit d802c7d3de
34 changed files with 490 additions and 240 deletions

View File

@ -1,5 +1,3 @@
.. Always edit README.tpl.rst. Do not change the module reference manually.
i3pystatus i3pystatus
========== ==========
@ -9,7 +7,7 @@ i3pystatus
.. image:: https://travis-ci.org/enkore/i3pystatus.svg?branch=master .. image:: https://travis-ci.org/enkore/i3pystatus.svg?branch=master
:target: https://travis-ci.org/enkore/i3pystatus :target: https://travis-ci.org/enkore/i3pystatus
i3pystatus is a (hopefully growing) collection of python scripts for i3pystatus is a growing collection of python scripts for
status output compatible to i3status / i3bar of the i3 window manager. status output compatible to i3status / i3bar of the i3 window manager.
Installation Installation
@ -110,16 +108,13 @@ Contributors
Contribute Contribute
---------- ----------
To contribute a module, make sure it uses one of the Module classes. Most modules To contribute a module, make sure it uses one of the ``Module`` classes. Most modules
use IntervalModule, which just calls a function repeatedly in a specified interval. use ``IntervalModule``, which just calls a function repeatedly in a specified interval.
The output attribute should be set to a dictionary which represents your modules output, The ``output`` attribute should be set to a dictionary which represents your modules output,
the protocol is documented `here <http://i3wm.org/docs/i3bar-protocol.html>`_. the protocol is documented `here <http://i3wm.org/docs/i3bar-protocol.html>`_.
To update this readme run ``python -m i3pystatus.mkdocs`` in the
repository root and you're done :)
Developer documentation is available in the source code and `here Developer documentation is available in the source code and `here
<http://i3pystatus.readthedocs.org/en/latest/>`_. <http://docs.enkore.de/i3pystatus>`_.
**Patches and pull requests are very welcome :-)** **Patches and pull requests are very welcome :-)**

View File

@ -17,6 +17,7 @@ mkdir ${BUILD}/test-install ${BUILD}/test-install-bin
PYTHONPATH=${BUILD}/test-install python3 setup.py --quiet install --install-lib ${BUILD}/test-install --install-scripts ${BUILD}/test-install-bin PYTHONPATH=${BUILD}/test-install python3 setup.py --quiet install --install-lib ${BUILD}/test-install --install-scripts ${BUILD}/test-install-bin
test -f ${BUILD}/test-install-bin/i3pystatus test -f ${BUILD}/test-install-bin/i3pystatus
test -f ${BUILD}/test-install-bin/i3pystatus-setting-util
PYTHONPATH=${BUILD}/test-install py.test --junitxml ${BUILD}/testlog.xml tests PYTHONPATH=${BUILD}/test-install py.test --junitxml ${BUILD}/testlog.xml tests

View File

@ -6,9 +6,11 @@ master branch
+++++++++++++ +++++++++++++
* Errors can now be logged to ``~/.i3pystatus-<pid>`` * Errors can now be logged to ``~/.i3pystatus-<pid>``
- ``log_level`` setting - See :ref:`logging`
* Added new callback system * Added new callback system
- See :ref:`callbacks`
* Added credentials storage * Added credentials storage
- See :ref:`credentials`
* Added deadbeef module * Added deadbeef module
* Added github module * Added github module
* Added whosonlocation module * Added whosonlocation module

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# #
# i3pystatus documentation build configuration file, created by # i3pystatus documentation build configuration file, created by
# sphinx-quickstart on Mon Oct 14 17:41:37 2013. # sphinx-quickstart on Mon Oct 14 17:41:37 2013.
@ -32,7 +31,8 @@ MOCK_MODULES = [
"requests", "requests",
"bs4", "bs4",
"dota2py", "dota2py",
"novaclient.v2" "novaclient.v2",
"speedtest_cli"
] ]
for mod_name in MOCK_MODULES: for mod_name in MOCK_MODULES:

View File

@ -120,12 +120,20 @@ Also change your i3wm config to the following:
workspace_buttons yes workspace_buttons yes
} }
.. note:: .. note:: Don't name your config file ``i3pystatus.py``, as it would
Don't name your config file ``i3pystatus.py`` make ``i3pystatus`` un-importable and lead to errors.
Settings that require credentials can utilize the keyring module to keep sensitive information out of config files. .. _credentials:
To take advantage of this feature, simply use the setting_util.py script to set the credentials for a module. Once this
is done you can add the module to your config without specifying the credentials, eg: Credentials
-----------
Settings that require credentials can utilize the keyring module to
keep sensitive information out of config files. To take advantage of
this feature, simply use the ``i3pystatus-setting-util`` script
installed along i3pystatus to set the credentials for a module. Once
this is done you can add the module to your config without specifying
the credentials, e.g.:
.. code:: python .. code:: python
@ -141,4 +149,152 @@ If you don't want to use the default you can set a specific keyring like so:
from keyring.backends.file import PlaintextKeyring from keyring.backends.file import PlaintextKeyring
status.register('github', keyring_backend=PlaintextKeyring()) status.register('github', keyring_backend=PlaintextKeyring())
i3pystatus will locate and set the credentials during the module loading process. Currently supported credentals are "password", "email" and "username". i3pystatus will locate and set the credentials during the module
loading process. Currently supported credentials are "password",
"email" and "username".
.. note:: Credential handling requires the PyPI package
``keyring``. Many distributions have it pre-packaged available as
``python-keyring``.
Formatting
----------
All modules let you specifiy the exact output formatting using a
`format string <http://docs.python.org/3/library/string.html#formatstrings>`_, which
gives you a great deal of flexibility.
If a module gives you a float, it probably has a ton of
uninteresting decimal places. Use ``{somefloat:.0f}`` to get the integer
value, ``{somefloat:0.2f}`` gives you two decimal places after the
decimal dot
.. _formatp:
formatp
~~~~~~~
Some modules use an extended format string syntax (the :py:mod:`.mpd`
module, for example). Given the format string below the output adapts
itself to the available data.
::
[{artist}/{album}/]{title}{status}
Only if both the artist and album is known they're displayed. If only one or none
of them is known the entire group between the brackets is excluded.
"is known" is here defined as "value evaluating to True in Python", i.e. an empty
string or 0 (or 0.0) counts as "not known".
Inside a group always all format specifiers must evaluate to true (logical and).
You can nest groups. The inner group will only become part of the output if both
the outer group and the inner group are eligible for output.
.. _TimeWrapper:
TimeWrapper
~~~~~~~~~~~
Some modules that output times use :py:class:`.TimeWrapper` to format
these. TimeWrapper is a mere extension of the standard formatting
method.
The time format that should be used is specified using the format specifier, i.e.
with some_time being 3951 seconds a format string like ``{some_time:%h:%m:%s}``
would produce ``1:5:51``.
* ``%h``, ``%m`` and ``%s`` are the hours, minutes and seconds without
leading zeros (i.e. 0 to 59 for minutes and seconds)
* ``%H``, ``%M`` and ``%S`` are padded with a leading zero to two digits,
i.e. 00 to 59
* ``%l`` and ``%L`` produce hours non-padded and padded but only if hours
is not zero. If the hours are zero it produces an empty string.
* ``%%`` produces a literal %
* ``%E`` (only valid on beginning of the string) if the time is null,
don't format anything but rather produce an empty string. If the
time is non-null it is removed from the string.
* When the module in question also uses formatp, 0 seconds counts as
"not known".
* The formatted time is stripped, i.e. spaces on both ends of the
result are removed.
.. _logging:
Logging
-------
Errors do happen and to ease debugging i3pystatus includes a logging
facility. By default i3pystatus will log exceptions raised by modules
to files in your home directory named
``.i3pystatus-<pid-of-thread>``. Some modules might log additional
information.
.. rubric:: Log level
Every module has a ``log_level`` option which sets the *minimum*
severity required for an event to be logged.
The numeric values of logging levels are given in the following
table.
+--------------+---------------+
| Level | Numeric value |
+==============+===============+
| ``CRITICAL`` | 50 |
+--------------+---------------+
| ``ERROR`` | 40 |
+--------------+---------------+
| ``WARNING`` | 30 |
+--------------+---------------+
| ``INFO`` | 20 |
+--------------+---------------+
| ``DEBUG`` | 10 |
+--------------+---------------+
| ``NOTSET`` | 0 |
+--------------+---------------+
Exceptions raised by modules are of severity ``ERROR`` by default. The
default ``log_level`` in i3pystatus (some modules might redefine the
default, see the reference of the module in question) is 30
(``WARNING``).
.. _callbacks:
Callbacks
---------
Callbacks are used for click-events (merged into i3bar since i3 4.6,
mouse wheel events are merged since 4.8), that is, you click (or
scroll) on the output of a module in your i3bar and something
happens. What happens is defined by these settings for each module
individually:
- ``on_leftclick``
- ``on_rightclick``
- ``on_upscroll``
- ``on_downscroll``
The global default action for all settings is ``None`` (do nothing),
but many modules define other defaults, which are documented in the
module reference.
The settings can be of different types, namely
- a list referring to a method of the module, e.g.
.. code:: python
status.register("clock",
on_leftclick=["scroll_format", 1])
``scroll_format`` is a method of the ``clock`` module, the ``1`` is
passed as a parameter and indicates the direction in this case.
- as a special case of the above: a string referring to a method, no
parameters are passed.
- a list where the first element is a callable and the following
elements are passed as arguments to the callable
- again a special case of the above: just a callable, no parameters
- a string which is run in a shell

View File

@ -1,63 +0,0 @@
Formatting
==========
All modules let you specifiy the exact output formatting using a
`format string <http://docs.python.org/3/library/string.html#formatstrings>`_, which
gives you a great deal of flexibility.
If a module gives you a float, it probably has a ton of
uninteresting decimal places. Use ``{somefloat:.0f}`` to get the integer
value, ``{somefloat:0.2f}`` gives you two decimal places after the
decimal dot
.. _formatp:
formatp
-------
Some modules use an extended format string syntax (the :py:mod:`.mpd`
module, for example). Given the format string below the output adapts
itself to the available data.
::
[{artist}/{album}/]{title}{status}
Only if both the artist and album is known they're displayed. If only one or none
of them is known the entire group between the brackets is excluded.
"is known" is here defined as "value evaluating to True in Python", i.e. an empty
string or 0 (or 0.0) counts as "not known".
Inside a group always all format specifiers must evaluate to true (logical and).
You can nest groups. The inner group will only become part of the output if both
the outer group and the inner group are eligible for output.
.. _TimeWrapper:
TimeWrapper
-----------
Some modules that output times use :py:class:`.TimeWrapper` to format
these. TimeWrapper is a mere extension of the standard formatting
method.
The time format that should be used is specified using the format specifier, i.e.
with some_time being 3951 seconds a format string like ``{some_time:%h:%m:%s}``
would produce ``1:5:51``.
* ``%h``, ``%m`` and ``%s`` are the hours, minutes and seconds without
leading zeros (i.e. 0 to 59 for minutes and seconds)
* ``%H``, ``%M`` and ``%S`` are padded with a leading zero to two digits,
i.e. 00 to 59
* ``%l`` and ``%L`` produce hours non-padded and padded but only if hours
is not zero. If the hours are zero it produces an empty string.
* ``%%`` produces a literal %
* ``%E`` (only valid on beginning of the string) if the time is null,
don't format anything but rather produce an empty string. If the
time is non-null it is removed from the string.
* When the module in question also uses formatp, 0 seconds counts as
"not known".
* The formatted time is stripped, i.e. spaces on both ends of the
result are removed.

View File

@ -4,18 +4,20 @@ Module reference
.. Don't list *every* module here, e.g. cpu-usage suffices, because the other .. Don't list *every* module here, e.g. cpu-usage suffices, because the other
variants are listed below that one. variants are listed below that one.
.. rubric:: Module overview:
:System: `clock`_ - `disk`_ - `load`_ - `mem`_ - `cpu_usage`_ :System: `clock`_ - `disk`_ - `load`_ - `mem`_ - `cpu_usage`_
:Audio: `alsa`_ - `pulseaudio`_ :Audio: `alsa`_ - `pulseaudio`_
:Hardware: `battery`_ - `backlight`_ - `temp`_ :Hardware: `battery`_ - `backlight`_ - `temp`_
:Network: `network`_ :Network: `network`_
:Music: `now_playing`_ - `mpd`_ :Music: `now_playing`_ - `mpd`_
:Websites & stuff: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_ :Websites: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_
:Other: `mail`_ - `pyload`_ - `text`_ - `updates`_ :Other: `mail`_ - `pyload`_ - `text`_ - `updates`_
:Advanced: `file`_ - `regex`_ - `runwatch`_ - `shell`_ :Advanced: `file`_ - `regex`_ - `runwatch`_ - `shell`_
.. autogen:: i3pystatus Module .. autogen:: i3pystatus Module
.. note:: List of all modules: .. rubric:: Module list:
.. _mailbackends: .. _mailbackends:

View File

@ -8,7 +8,6 @@ Contents:
:maxdepth: 4 :maxdepth: 4
configuration configuration
formatting
i3pystatus i3pystatus
changelog changelog
module module

View File

@ -12,7 +12,7 @@ import i3pystatus.core.modules
from i3pystatus.core.imputil import ClassFinder from i3pystatus.core.imputil import ClassFinder
from i3pystatus.core.color import ColorRangeModule from i3pystatus.core.color import ColorRangeModule
IGNORE_MODULES = ("__main__", "core") IGNORE_MODULES = ("__main__", "core", "tools")
def is_module(obj): def is_module(obj):
@ -132,7 +132,7 @@ def generate_automodules(path, name, basecls):
contents = [] contents = []
for mod in modules: for mod in modules:
contents.append(" * :py:mod:`~{}`".format(mod[0])) contents.append("* :py:mod:`~{}`".format(mod[0]))
contents.append("") contents.append("")
for mod in modules: for mod in modules:

View File

@ -8,13 +8,6 @@ from i3pystatus.core.util import formatp
import logging import logging
import os import os
h = logging.FileHandler(".i3pystatus-" + str(os.getpid()), delay=True)
logger = logging.getLogger("i3pystatus")
logger.addHandler(h)
logger.setLevel(logging.CRITICAL)
__path__ = extend_path(__path__, __name__) __path__ = extend_path(__path__, __name__)
__all__ = [ __all__ = [
@ -24,6 +17,12 @@ __all__ = [
"formatp", "formatp",
] ]
logpath = os.path.join(os.path.expanduser("~"), ".i3pystatus-%s" % os.getpid())
handler = logging.FileHandler(logpath, delay=True)
logger = logging.getLogger("i3pystatus")
logger.addHandler(handler)
logger.setLevel(logging.CRITICAL)
def main(): def main():
from i3pystatus.clock import Clock from i3pystatus.clock import Clock

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os import os
import re import re
import configparser import configparser

View File

@ -40,8 +40,6 @@ class Bitcoin(IntervalModule):
("colorize", "Enable color change on price increase/decrease"), ("colorize", "Enable color change on price increase/decrease"),
("color_up", "Color for price increases"), ("color_up", "Color for price increases"),
("color_down", "Color for price decreases"), ("color_down", "Color for price decreases"),
("leftclick", "URL to visit or command to run on left click"),
("rightclick", "URL to visit or command to run on right click"),
("interval", "Update interval."), ("interval", "Update interval."),
("symbol", "Symbol for bitcoin sign"), ("symbol", "Symbol for bitcoin sign"),
"status" "status"
@ -54,16 +52,14 @@ class Bitcoin(IntervalModule):
colorize = False colorize = False
color_up = "#00FF00" color_up = "#00FF00"
color_down = "#FF0000" color_down = "#FF0000"
leftclick = "electrum"
rightclick = "https://bitcoinaverage.com/"
interval = 600 interval = 600
status = { status = {
"price_up": "", "price_up": "",
"price_down": "", "price_down": "",
} }
on_leftclick = "handle_leftclick" on_leftclick = "electrum"
on_rightclick = "handle_rightclick" on_rightclick = [user_open, "https://bitcoinaverage.com/"]
_price_prev = 0 _price_prev = 0
@ -128,9 +124,3 @@ class Bitcoin(IntervalModule):
"full_text": self.format.format(**fdict), "full_text": self.format.format(**fdict),
"color": color, "color": color,
} }
def handle_leftclick(self):
user_open(self.leftclick)
def handle_rightclick(self):
user_open(self.rightclick)

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os import os
import locale import locale
import time import time

View File

@ -30,6 +30,8 @@ class DesktopNotification(BaseDesktopNotification):
try: try:
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify from gi.repository import Notify
except ImportError: except ImportError:
pass pass

View File

@ -9,10 +9,10 @@ class Module(SettingsBase):
position = 0 position = 0
settings = ( settings = (
('on_leftclick', "Callback called on left click (string)"), ('on_leftclick', "Callback called on left click (see :ref:`callbacks`)"),
('on_rightclick', "Callback called on right click (string)"), ('on_rightclick', "Callback called on right click (see :ref:`callbacks`)"),
('on_upscroll', "Callback called on scrolling up (string)"), ('on_upscroll', "Callback called on scrolling up (see :ref:`callbacks`)"),
('on_downscroll', "Callback called on scrolling down (string)"), ('on_downscroll', "Callback called on scrolling down (see :ref:`callbacks`)"),
('hints', "Additional output blocks for module output (dict)"), ('hints', "Additional output blocks for module output (dict)"),
) )

View File

@ -23,12 +23,18 @@ class SettingsBaseMeta(type):
name(setting) in seen or seen.add(name(setting)))] name(setting) in seen or seen.add(name(setting)))]
settings = tuple() settings = tuple()
required = tuple() required = set()
# getmro returns base classes according to Method Resolution Order, # getmro returns base classes according to Method Resolution Order,
# which always includes the class itself as the first element. # which always includes the class itself as the first element.
for base in inspect.getmro(cls): for base in inspect.getmro(cls):
settings += tuple(getattr(base, "settings", [])) settings += tuple(getattr(base, "settings", []))
required += tuple(getattr(base, "required", [])) required |= set(getattr(base, "required", []))
# if a derived class defines a default for a setting it is not
# required anymore.
for base in inspect.getmro(cls):
for r in list(required):
if hasattr(base, r):
required.remove(r)
return unique(settings), required return unique(settings), required
@ -48,7 +54,7 @@ class SettingsBase(metaclass=SettingsBaseMeta):
__PROTECTED_SETTINGS = ["password", "email", "username"] __PROTECTED_SETTINGS = ["password", "email", "username"]
settings = ( settings = (
("log_level", "Set to true to log error to .i3pystatus-<pid> file"), ("log_level", "Set to true to log error to .i3pystatus-<pid> file."),
) )
"""settings should be tuple containing two types of elements: """settings should be tuple containing two types of elements:
@ -61,7 +67,7 @@ class SettingsBase(metaclass=SettingsBaseMeta):
required = tuple() required = tuple()
"""required can list settings which are required""" """required can list settings which are required"""
log_level = logging.NOTSET log_level = logging.WARNING
logger = None logger = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -67,23 +67,26 @@ class ExceptionWrapper(Wrapper):
try: try:
self.workload() self.workload()
except: except:
sys.stderr.write("Exception in {thread} at {time}\n".format( message = "\n> Exception in {thread} at {time}, module {name}".format(
thread=threading.current_thread().name, thread=threading.current_thread().name,
time=time.strftime("%c") time=time.strftime("%c"),
)) name=self.workload.__class__.__name__
traceback.print_exc(file=sys.stderr) )
sys.stderr.flush() if hasattr(self.workload, "logger"):
if hasattr(self.workload, "output"): self.workload.logger.error(message, exc_info=True)
self.workload.output = { self.workload.output = {
"full_text": "{}: {}".format(self.workload.__class__.__name__, "full_text": "{}: {}".format(self.workload.__class__.__name__,
self.format_error(str(sys.exc_info()[1]))), self.format_error(str(sys.exc_info()[1]))),
"color": "#FF0000", "color": "#FF0000",
} }
def format_error(self, exception_message): def format_error(self, exception_message):
if hasattr(self.workload, 'max_error_len'): if hasattr(self.workload, 'max_error_len'):
error_len = self.workload.max_error_len error_len = self.workload.max_error_len
return exception_message[:error_len] + '...' if len(exception_message) > error_len else exception_message if len(exception_message) > error_len:
return exception_message[:error_len] + ''
else:
return exception_message
else: else:
return exception_message return exception_message

View File

@ -1,4 +1,3 @@
# coding=utf-8
from i3pystatus import IntervalModule from i3pystatus import IntervalModule

View File

@ -1,5 +1,3 @@
# -*- coding:utf-8 -*-
from collections import defaultdict from collections import defaultdict
from string import Formatter from string import Formatter

View File

@ -1,4 +1,3 @@
# -*- coding:utf-8 -*-
from i3pystatus.core.color import ColorRangeModule from i3pystatus.core.color import ColorRangeModule
from i3pystatus.cpu_usage import CpuUsage from i3pystatus.cpu_usage import CpuUsage
from i3pystatus.core.util import make_bar, make_vertical_bar from i3pystatus.core.util import make_bar, make_vertical_bar

View File

@ -96,7 +96,7 @@ class Dota2wins(IntervalModule):
"screenname": screenname, "screenname": screenname,
"wins": wins, "wins": wins,
"losses": losses, "losses": losses,
"win_percent": win_percent, "win_percent": "%.2f" % win_percent,
} }
self.output = { self.output = {

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys import sys
import imaplib import imaplib

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os import os
from i3pystatus.mail import Backend from i3pystatus.mail import Backend

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys import sys
from i3pystatus.mail import Backend from i3pystatus.mail import Backend
import subprocess import subprocess

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# note that this needs the notmuch python bindings. For more info see: # note that this needs the notmuch python bindings. For more info see:
# http://notmuchmail.org/howto/#index4h2 # http://notmuchmail.org/howto/#index4h2
import notmuch import notmuch

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# This plugin listens for dbus signals emitted by the # This plugin listens for dbus signals emitted by the
# thunderbird-dbus-sender extension for TB: # thunderbird-dbus-sender extension for TB:
# https://github.com/janoliver/thunderbird-dbus-sender # https://github.com/janoliver/thunderbird-dbus-sender

111
i3pystatus/net_speed.py Normal file
View File

@ -0,0 +1,111 @@
from i3pystatus import IntervalModule
import speedtest_cli
import requests
import time
import os
from urllib.parse import urlparse
import contextlib
import sys
from io import StringIO
class NetSpeed(IntervalModule):
"""
Attempts to provide an estimation of internet speeds.
Requires: speedtest_cli
"""
settings = (
("url", "Target URL to download a file from. Uses speedtest_cli to "
"find the 'best' server if none is supplied."),
("units", "Valid values are B, b, bytes, or bits"),
"format"
)
color = "#FFFFFF"
interval = 300
url = None
units = 'bits'
format = "{speed} ({hosting_provider})"
def run(self):
# since speedtest_cli likes to print crap, we need to squelch it
@contextlib.contextmanager
def nostdout():
save_stdout = sys.stdout
sys.stdout = StringIO()
yield
sys.stdout = save_stdout
if not self.url:
with nostdout():
try:
config = speedtest_cli.getConfig()
servers = speedtest_cli.closestServers(config['client'])
best = speedtest_cli.getBestServer(servers)
# 1500x1500 is about 4.3MB, which seems like a reasonable place to
# start, i guess...
url = '%s/random1500x1500.jpg' % os.path.dirname(best['url'])
except KeyError:
url = None
if not url:
cdict = {
"speed": 0,
"hosting_provider": 'null',
}
else:
with open('/dev/null', 'wb') as devnull:
start = time.time()
req = requests.get(url, stream=True)
devnull.write(req.content)
end = time.time()
total_length = int(req.headers.get('content-length'))
devnull.close()
# chop off the float after the 4th decimal point
# note: not rounding, simply cutting
# note: dl_time is in seconds
dl_time = float(end - start)
if self.units == 'bits' or self.units == 'b':
unit = 'bps'
kilo = 1000
mega = 1000000
giga = 1000000000
factor = 8
elif self.units == 'bytes' or self.units == 'B':
unit = 'Bps'
kilo = 8000
mega = 8000000
giga = 8000000000
factor = 1
if total_length < kilo:
bps = float(total_length / dl_time)
if total_length >= kilo and total_length < mega:
unit = "K" + unit
bps = float((total_length / 1024.0) / dl_time)
if total_length >= mega and total_length < giga:
unit = "M" + unit
bps = float((total_length / (1024.0 * 1024.0)) / dl_time)
if total_length >= giga:
unit = "G" + unit
bps = float((total_length / (1024.0 * 1024.0 * 1024.0)) / dl_time)
bps = "%.2f" % (bps * factor)
speed = "%s %s" % (bps, unit)
hosting_provider = '.'.join(urlparse(url).hostname.split('.')[-2:])
cdict = {
"speed": speed,
"hosting_provider": hosting_provider,
}
self.output = {
"full_text": self.format.format(**cdict),
"color": self.color
}

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import netifaces import netifaces
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
@ -198,6 +197,20 @@ class NetworkTraffic():
def get_packets_received(self): def get_packets_received(self):
return self.pnic.packets_recv - self.pnic_before.packets_recv return self.pnic.packets_recv - self.pnic_before.packets_recv
def get_rx_tot_Mbytes(self, interface):
try:
with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f:
return int(f.readline().split('\n')[0]) / (1024 * 1024)
except FileNotFoundError:
return False
def get_tx_tot_Mbytes(self, interface):
try:
with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f:
return int(f.readline().split('\n')[0]) / (1024 * 1024)
except FileNotFoundError:
return False
def get_usage(self, interface): def get_usage(self, interface):
self.update_counters(interface) self.update_counters(interface)
usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0)
@ -209,6 +222,8 @@ class NetworkTraffic():
usage["bytes_recv"] = self.get_bytes_received() usage["bytes_recv"] = self.get_bytes_received()
usage["packets_sent"] = self.get_packets_sent() usage["packets_sent"] = self.get_packets_sent()
usage["packets_recv"] = self.get_packets_received() usage["packets_recv"] = self.get_packets_received()
usage["rx_tot_Mbytes"] = self.get_rx_tot_Mbytes(interface)
usage["tx_tot_Mbytes"] = self.get_tx_tot_Mbytes(interface)
round_dict(usage, self.round_size) round_dict(usage, self.round_size)
return usage return usage
@ -249,6 +264,8 @@ class Network(IntervalModule, ColorRangeModule):
* `{bytes_recv}` bytes received per second (divided by divisor) * `{bytes_recv}` bytes received per second (divided by divisor)
* `{packets_sent}` bytes sent per second (divided by divisor) * `{packets_sent}` bytes sent per second (divided by divisor)
* `{packets_recv}` bytes received per second (divided by divisor) * `{packets_recv}` bytes received per second (divided by divisor)
* `{rx_tot_Mbytes}` total Mbytes received
* `{tx_tot_Mbytes}` total Mbytes sent
""" """
settings = ( settings = (
("format_up", "format string"), ("format_up", "format string"),
@ -313,7 +330,8 @@ class Network(IntervalModule, ColorRangeModule):
# Don't require importing psutil unless using the functionality it offers. # Don't require importing psutil unless using the functionality it offers.
if any(s in self.format_up or s in self.format_down for s in if any(s in self.format_up or s in self.format_down for s in
['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', 'network_graph', 'kbs']): ['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', 'network_graph',
'rx_tot_Mbytes', 'tx_tot_Mbytes', 'kbs']):
self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size) self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size)
else: else:
self.network_traffic = None self.network_traffic = None
@ -324,6 +342,7 @@ class Network(IntervalModule, ColorRangeModule):
self.kbs_arr = [0.0] * self.graph_width self.kbs_arr = [0.0] * self.graph_width
def cycle_interface(self, increment=1): def cycle_interface(self, increment=1):
"""Cycle through available interfaces in `increment` steps. Sign indicates direction."""
interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces] interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces]
if self.interface in interfaces: if self.interface in interfaces:
next_index = (interfaces.index(self.interface) + increment) % len(interfaces) next_index = (interfaces.index(self.interface) + increment) % len(interfaces)
@ -343,6 +362,7 @@ class Network(IntervalModule, ColorRangeModule):
def run(self): def run(self):
format_values = dict(kbs="", network_graph="", bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", format_values = dict(kbs="", network_graph="", bytes_sent="", bytes_recv="", packets_sent="", packets_recv="",
rx_tot_Mbytes="", tx_tot_Mbytes="",
interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="", interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="",
essid="", freq="", quality="", quality_bar="") essid="", freq="", quality="", quality_bar="")
if self.network_traffic: if self.network_traffic:

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os import os
import subprocess import subprocess
import locale import locale

View File

134
i3pystatus/tools/setting_util.py Executable file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env python
import glob
import inspect
import os
import getpass
import sys
import signal
import pkgutil
from collections import defaultdict, OrderedDict
import keyring
import i3pystatus
from i3pystatus import Module, SettingsBase
from i3pystatus.core import ClassFinder
def signal_handler(signal, frame):
sys.exit(0)
def get_int_in_range(prompt, _range):
while True:
try:
answer = input(prompt)
except EOFError:
print()
sys.exit(0)
try:
n = int(answer.strip())
if n in _range:
return n
else:
print("Value out of range!")
except ValueError:
print("Invalid input!")
def enumerate_choices(choices):
lines = []
for index, choice in enumerate(choices, start=1):
lines.append(" %d - %s\n" % (index, choice))
return "".join(lines)
def get_modules():
for importer, modname, ispkg in pkgutil.iter_modules(i3pystatus.__path__):
if modname not in ["core", "tools"]:
yield modname
def get_credential_modules():
verbose = "-v" in sys.argv
protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS
class_finder = ClassFinder(Module)
credential_modules = defaultdict(dict)
for module_name in get_modules():
try:
module = class_finder.get_module(module_name)
except ImportError:
if verbose:
print("ImportError while importing", module_name)
continue
clazz = class_finder.get_class(module)
members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')]
if any([hasattr(clazz, setting) for setting in protected_settings]):
credential_modules[clazz.__name__]['credentials'] = list(set(protected_settings) & set(members))
credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__)
elif hasattr(clazz, 'required'):
protected = []
required = getattr(clazz, 'required')
for setting in protected_settings:
if setting in required:
protected.append(setting)
if protected:
credential_modules[clazz.__name__]['credentials'] = protected
credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__)
return credential_modules
def main():
signal.signal(signal.SIGINT, signal_handler)
print("""%s - part of i3pystatus
This allows you to edit keyring-protected settings of
i3pystatus modules, which are stored globally (independent
of your i3pystatus configuration) in your keyring.
Options:
-l: list all stored settings (no values are printed)
-v: print informational messages
""" % os.path.basename(sys.argv[0]))
credential_modules = get_credential_modules()
if "-l" in sys.argv:
for name, module in credential_modules.items():
print(name)
for credential in enumerate_choices(module["credentials"]):
if keyring.get_password("%s.%s" % (module["key"], credential), getpass.getuser()):
print(" - %s: set" % credential)
else:
print(" (none stored)")
return
choices = list(credential_modules.keys())
prompt = "Choose a module to edit:\n"
prompt += enumerate_choices(choices)
prompt += "> "
index = get_int_in_range(prompt, range(1, len(choices) + 1))
module_name = choices[index - 1]
module = credential_modules[module_name]
prompt = "Choose setting of %s to edit:\n" % module_name
prompt += enumerate_choices(module["credentials"])
prompt += "> "
choices = module['credentials']
index = get_int_in_range(prompt, range(1, len(choices) + 1))
setting = choices[index - 1]
answer = getpass.getpass("Enter value for %s:\n> " % setting)
answer2 = getpass.getpass("Re-enter value\n> ")
if answer == answer2:
key = "%s.%s" % (module['key'], setting)
keyring.set_password(key, getpass.getuser(), answer)
print("%s set!" % setting)
else:
print("Values don't match - nothing set.")
if __name__ == "__main__":
main()

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python
import glob
import inspect
import os
import keyring
import getpass
import sys
import signal
from i3pystatus import Module, SettingsBase
from i3pystatus.core import ClassFinder
from collections import defaultdict, OrderedDict
def signal_handler(signal, frame):
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def get_int_in_range(prompt, _range):
while True:
answer = input(prompt)
try:
n = int(answer.strip())
if n in _range:
return n
else:
print("Value out of range!")
except ValueError:
print("Invalid input!")
modules = [os.path.basename(m.replace('.py', ''))
for m in glob.glob(os.path.join(os.path.dirname(__file__), "i3pystatus", "*.py"))
if not os.path.basename(m).startswith('_')]
protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS
class_finder = ClassFinder(Module)
credential_modules = defaultdict(dict)
for module_name in modules:
try:
module = class_finder.get_module(module_name)
clazz = class_finder.get_class(module)
members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')]
if any([hasattr(clazz, setting) for setting in protected_settings]):
credential_modules[clazz.__name__]['credentials'] = list(set(protected_settings) & set(members))
credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__)
elif hasattr(clazz, 'required'):
protected = []
required = getattr(clazz, 'required')
for setting in protected_settings:
if setting in required:
protected.append(setting)
if protected:
credential_modules[clazz.__name__]['credentials'] = protected
credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__)
except ImportError:
continue
choices = [k for k in credential_modules.keys()]
for idx, module in enumerate(choices, start=1):
print("%s - %s" % (idx, module))
index = get_int_in_range("Choose module:\n> ", range(1, len(choices) + 1))
module_name = choices[index - 1]
module = credential_modules[module_name]
for idx, setting in enumerate(module['credentials'], start=1):
print("%s - %s" % (idx, setting))
choices = module['credentials']
index = get_int_in_range("Choose setting for %s:\n> " % module_name, range(1, len(choices) + 1))
setting = choices[index - 1]
answer = getpass.getpass("Enter value for %s:\n> " % setting)
answer2 = getpass.getpass("Re-enter value\n> ")
if answer == answer2:
key = "%s.%s" % (module['key'], setting)
keyring.set_password(key, getpass.getuser(), answer)
print("%s set!" % setting)
else:
print("Values don't match - nothing set.")

1
setting_util.py Symbolic link
View File

@ -0,0 +1 @@
i3pystatus/tools/setting_util.py

View File

@ -18,13 +18,15 @@ setup(name="i3pystatus",
packages=[ packages=[
"i3pystatus", "i3pystatus",
"i3pystatus.core", "i3pystatus.core",
"i3pystatus.tools",
"i3pystatus.mail", "i3pystatus.mail",
"i3pystatus.pulseaudio", "i3pystatus.pulseaudio",
"i3pystatus.updates", "i3pystatus.updates",
], ],
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"i3pystatus = i3pystatus:main" "i3pystatus = i3pystatus:main",
"i3pystatus-setting-util = i3pystatus.tools.setting_util:main"
] ]
}, },
zip_safe=True, zip_safe=True,

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python3
# coding=utf-8
""" """
Basic tests for the cpu_freq module Basic tests for the cpu_freq module
""" """