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
|
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 :-)**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
.. 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:
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ Contents:
|
|||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
configuration
|
configuration
|
||||||
formatting
|
|
||||||
i3pystatus
|
i3pystatus
|
||||||
changelog
|
changelog
|
||||||
module
|
module
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import configparser
|
import configparser
|
||||||
|
@ -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)
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import locale
|
import locale
|
||||||
import time
|
import time
|
||||||
|
@ -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
|
||||||
|
@ -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)"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -67,13 +67,13 @@ 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]))),
|
||||||
@ -83,7 +83,10 @@ class ExceptionWrapper(Wrapper):
|
|||||||
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
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# coding=utf-8
|
|
||||||
from i3pystatus import IntervalModule
|
from i3pystatus import IntervalModule
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 = {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import imaplib
|
import imaplib
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
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
|
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:
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import locale
|
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=[
|
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,
|
||||||
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user