Merge branch 'Master' into hints
# Conflicts: # i3pystatus/core/modules.py
This commit is contained in:
commit
d802c7d3de
15
README.rst
15
README.rst
@ -1,5 +1,3 @@
|
||||
.. Always edit README.tpl.rst. Do not change the module reference manually.
|
||||
|
||||
i3pystatus
|
||||
==========
|
||||
|
||||
@ -9,7 +7,7 @@ i3pystatus
|
||||
.. image:: https://travis-ci.org/enkore/i3pystatus.svg?branch=master
|
||||
: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.
|
||||
|
||||
Installation
|
||||
@ -110,16 +108,13 @@ Contributors
|
||||
Contribute
|
||||
----------
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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>`_.
|
||||
|
||||
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
|
||||
<http://i3pystatus.readthedocs.org/en/latest/>`_.
|
||||
<http://docs.enkore.de/i3pystatus>`_.
|
||||
|
||||
**Patches and pull requests are very welcome :-)**
|
||||
|
@ -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
|
||||
|
||||
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
|
||||
|
||||
|
@ -6,9 +6,11 @@ master branch
|
||||
+++++++++++++
|
||||
|
||||
* Errors can now be logged to ``~/.i3pystatus-<pid>``
|
||||
- ``log_level`` setting
|
||||
- See :ref:`logging`
|
||||
* Added new callback system
|
||||
- See :ref:`callbacks`
|
||||
* Added credentials storage
|
||||
- See :ref:`credentials`
|
||||
* Added deadbeef module
|
||||
* Added github module
|
||||
* Added whosonlocation module
|
||||
|
@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# i3pystatus documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Oct 14 17:41:37 2013.
|
||||
@ -32,7 +31,8 @@ MOCK_MODULES = [
|
||||
"requests",
|
||||
"bs4",
|
||||
"dota2py",
|
||||
"novaclient.v2"
|
||||
"novaclient.v2",
|
||||
"speedtest_cli"
|
||||
]
|
||||
|
||||
for mod_name in MOCK_MODULES:
|
||||
|
@ -120,12 +120,20 @@ Also change your i3wm config to the following:
|
||||
workspace_buttons yes
|
||||
}
|
||||
|
||||
.. note::
|
||||
Don't name your config file ``i3pystatus.py``
|
||||
.. note:: Don't name your config file ``i3pystatus.py``, as it would
|
||||
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.
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
|
@ -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.
|
@ -4,18 +4,20 @@ Module reference
|
||||
.. Don't list *every* module here, e.g. cpu-usage suffices, because the other
|
||||
variants are listed below that one.
|
||||
|
||||
.. rubric:: Module overview:
|
||||
|
||||
:System: `clock`_ - `disk`_ - `load`_ - `mem`_ - `cpu_usage`_
|
||||
:Audio: `alsa`_ - `pulseaudio`_
|
||||
:Hardware: `battery`_ - `backlight`_ - `temp`_
|
||||
:Network: `network`_
|
||||
:Music: `now_playing`_ - `mpd`_
|
||||
:Websites & stuff: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_
|
||||
:Websites: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_
|
||||
:Other: `mail`_ - `pyload`_ - `text`_ - `updates`_
|
||||
:Advanced: `file`_ - `regex`_ - `runwatch`_ - `shell`_
|
||||
|
||||
.. autogen:: i3pystatus Module
|
||||
|
||||
.. note:: List of all modules:
|
||||
.. rubric:: Module list:
|
||||
|
||||
.. _mailbackends:
|
||||
|
||||
|
@ -8,7 +8,6 @@ Contents:
|
||||
:maxdepth: 4
|
||||
|
||||
configuration
|
||||
formatting
|
||||
i3pystatus
|
||||
changelog
|
||||
module
|
||||
|
@ -12,7 +12,7 @@ import i3pystatus.core.modules
|
||||
from i3pystatus.core.imputil import ClassFinder
|
||||
from i3pystatus.core.color import ColorRangeModule
|
||||
|
||||
IGNORE_MODULES = ("__main__", "core")
|
||||
IGNORE_MODULES = ("__main__", "core", "tools")
|
||||
|
||||
|
||||
def is_module(obj):
|
||||
|
@ -8,13 +8,6 @@ from i3pystatus.core.util import formatp
|
||||
import logging
|
||||
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__)
|
||||
|
||||
__all__ = [
|
||||
@ -24,6 +17,12 @@ __all__ = [
|
||||
"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():
|
||||
from i3pystatus.clock import Clock
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
import configparser
|
||||
|
@ -40,8 +40,6 @@ class Bitcoin(IntervalModule):
|
||||
("colorize", "Enable color change on price increase/decrease"),
|
||||
("color_up", "Color for price increases"),
|
||||
("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."),
|
||||
("symbol", "Symbol for bitcoin sign"),
|
||||
"status"
|
||||
@ -54,16 +52,14 @@ class Bitcoin(IntervalModule):
|
||||
colorize = False
|
||||
color_up = "#00FF00"
|
||||
color_down = "#FF0000"
|
||||
leftclick = "electrum"
|
||||
rightclick = "https://bitcoinaverage.com/"
|
||||
interval = 600
|
||||
status = {
|
||||
"price_up": "▲",
|
||||
"price_down": "▼",
|
||||
}
|
||||
|
||||
on_leftclick = "handle_leftclick"
|
||||
on_rightclick = "handle_rightclick"
|
||||
on_leftclick = "electrum"
|
||||
on_rightclick = [user_open, "https://bitcoinaverage.com/"]
|
||||
|
||||
_price_prev = 0
|
||||
|
||||
@ -128,9 +124,3 @@ class Bitcoin(IntervalModule):
|
||||
"full_text": self.format.format(**fdict),
|
||||
"color": color,
|
||||
}
|
||||
|
||||
def handle_leftclick(self):
|
||||
user_open(self.leftclick)
|
||||
|
||||
def handle_rightclick(self):
|
||||
user_open(self.rightclick)
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import locale
|
||||
import time
|
||||
|
@ -30,6 +30,8 @@ class DesktopNotification(BaseDesktopNotification):
|
||||
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.require_version('Notify', '0.7')
|
||||
from gi.repository import Notify
|
||||
except ImportError:
|
||||
pass
|
||||
|
@ -9,10 +9,10 @@ class Module(SettingsBase):
|
||||
position = 0
|
||||
|
||||
settings = (
|
||||
('on_leftclick', "Callback called on left click (string)"),
|
||||
('on_rightclick', "Callback called on right click (string)"),
|
||||
('on_upscroll', "Callback called on scrolling up (string)"),
|
||||
('on_downscroll', "Callback called on scrolling down (string)"),
|
||||
('on_leftclick', "Callback called on left click (see :ref:`callbacks`)"),
|
||||
('on_rightclick', "Callback called on right click (see :ref:`callbacks`)"),
|
||||
('on_upscroll', "Callback called on scrolling up (see :ref:`callbacks`)"),
|
||||
('on_downscroll', "Callback called on scrolling down (see :ref:`callbacks`)"),
|
||||
('hints', "Additional output blocks for module output (dict)"),
|
||||
)
|
||||
|
||||
|
@ -23,12 +23,18 @@ class SettingsBaseMeta(type):
|
||||
name(setting) in seen or seen.add(name(setting)))]
|
||||
|
||||
settings = tuple()
|
||||
required = tuple()
|
||||
required = set()
|
||||
# getmro returns base classes according to Method Resolution Order,
|
||||
# which always includes the class itself as the first element.
|
||||
for base in inspect.getmro(cls):
|
||||
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
|
||||
|
||||
|
||||
@ -48,7 +54,7 @@ class SettingsBase(metaclass=SettingsBaseMeta):
|
||||
__PROTECTED_SETTINGS = ["password", "email", "username"]
|
||||
|
||||
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:
|
||||
@ -61,7 +67,7 @@ class SettingsBase(metaclass=SettingsBaseMeta):
|
||||
required = tuple()
|
||||
"""required can list settings which are required"""
|
||||
|
||||
log_level = logging.NOTSET
|
||||
log_level = logging.WARNING
|
||||
logger = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -67,13 +67,13 @@ class ExceptionWrapper(Wrapper):
|
||||
try:
|
||||
self.workload()
|
||||
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,
|
||||
time=time.strftime("%c")
|
||||
))
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
if hasattr(self.workload, "output"):
|
||||
time=time.strftime("%c"),
|
||||
name=self.workload.__class__.__name__
|
||||
)
|
||||
if hasattr(self.workload, "logger"):
|
||||
self.workload.logger.error(message, exc_info=True)
|
||||
self.workload.output = {
|
||||
"full_text": "{}: {}".format(self.workload.__class__.__name__,
|
||||
self.format_error(str(sys.exc_info()[1]))),
|
||||
@ -83,7 +83,10 @@ class ExceptionWrapper(Wrapper):
|
||||
def format_error(self, exception_message):
|
||||
if hasattr(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:
|
||||
return exception_message
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
# coding=utf-8
|
||||
from i3pystatus import IntervalModule
|
||||
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from collections import defaultdict
|
||||
from string import Formatter
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from i3pystatus.core.color import ColorRangeModule
|
||||
from i3pystatus.cpu_usage import CpuUsage
|
||||
from i3pystatus.core.util import make_bar, make_vertical_bar
|
||||
|
@ -96,7 +96,7 @@ class Dota2wins(IntervalModule):
|
||||
"screenname": screenname,
|
||||
"wins": wins,
|
||||
"losses": losses,
|
||||
"win_percent": win_percent,
|
||||
"win_percent": "%.2f" % win_percent,
|
||||
}
|
||||
|
||||
self.output = {
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import imaplib
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from i3pystatus.mail import Backend
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from i3pystatus.mail import Backend
|
||||
import subprocess
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# note that this needs the notmuch python bindings. For more info see:
|
||||
# http://notmuchmail.org/howto/#index4h2
|
||||
import notmuch
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This plugin listens for dbus signals emitted by the
|
||||
# thunderbird-dbus-sender extension for TB:
|
||||
# https://github.com/janoliver/thunderbird-dbus-sender
|
||||
|
111
i3pystatus/net_speed.py
Normal file
111
i3pystatus/net_speed.py
Normal 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
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import netifaces
|
||||
|
||||
from i3pystatus import IntervalModule
|
||||
@ -198,6 +197,20 @@ class NetworkTraffic():
|
||||
def get_packets_received(self):
|
||||
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):
|
||||
self.update_counters(interface)
|
||||
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["packets_sent"] = self.get_packets_sent()
|
||||
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)
|
||||
return usage
|
||||
|
||||
@ -249,6 +264,8 @@ class Network(IntervalModule, ColorRangeModule):
|
||||
* `{bytes_recv}` — bytes received per second (divided by divisor)
|
||||
* `{packets_sent}` — bytes sent 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 = (
|
||||
("format_up", "format string"),
|
||||
@ -313,7 +330,8 @@ class Network(IntervalModule, ColorRangeModule):
|
||||
|
||||
# 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
|
||||
['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)
|
||||
else:
|
||||
self.network_traffic = None
|
||||
@ -324,6 +342,7 @@ class Network(IntervalModule, ColorRangeModule):
|
||||
self.kbs_arr = [0.0] * self.graph_width
|
||||
|
||||
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]
|
||||
if self.interface in interfaces:
|
||||
next_index = (interfaces.index(self.interface) + increment) % len(interfaces)
|
||||
@ -343,6 +362,7 @@ class Network(IntervalModule, ColorRangeModule):
|
||||
|
||||
def run(self):
|
||||
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="",
|
||||
essid="", freq="", quality="", quality_bar="")
|
||||
if self.network_traffic:
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import locale
|
||||
|
0
i3pystatus/tools/__init__.py
Normal file
0
i3pystatus/tools/__init__.py
Normal file
134
i3pystatus/tools/setting_util.py
Executable file
134
i3pystatus/tools/setting_util.py
Executable 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()
|
@ -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
1
setting_util.py
Symbolic link
@ -0,0 +1 @@
|
||||
i3pystatus/tools/setting_util.py
|
4
setup.py
4
setup.py
@ -18,13 +18,15 @@ setup(name="i3pystatus",
|
||||
packages=[
|
||||
"i3pystatus",
|
||||
"i3pystatus.core",
|
||||
"i3pystatus.tools",
|
||||
"i3pystatus.mail",
|
||||
"i3pystatus.pulseaudio",
|
||||
"i3pystatus.updates",
|
||||
],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"i3pystatus = i3pystatus:main"
|
||||
"i3pystatus = i3pystatus:main",
|
||||
"i3pystatus-setting-util = i3pystatus.tools.setting_util:main"
|
||||
]
|
||||
},
|
||||
zip_safe=True,
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding=utf-8
|
||||
"""
|
||||
Basic tests for the cpu_freq module
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user