summaryrefslogtreecommitdiffstats
path: root/py3status
diff options
context:
space:
mode:
Diffstat (limited to 'py3status')
-rwxr-xr-xpy3status/__init__.py548
-rw-r--r--py3status/modules/__init__.py0
-rw-r--r--py3status/modules/battery-level.py68
-rw-r--r--py3status/modules/clementine.py65
-rw-r--r--py3status/modules/dpms.py44
-rw-r--r--py3status/modules/empty_class.py41
-rw-r--r--py3status/modules/glpi.py52
-rw-r--r--py3status/modules/i3bar_click_events.py127
-rw-r--r--py3status/modules/imap.py58
-rw-r--r--py3status/modules/keyboard-layout.py73
-rw-r--r--py3status/modules/mpd_status.py117
-rw-r--r--py3status/modules/net_rate.py163
-rw-r--r--py3status/modules/netdata.py132
-rw-r--r--py3status/modules/ns_checker.py57
-rw-r--r--py3status/modules/pingdom.py59
-rw-r--r--py3status/modules/pomodoro.py123
-rw-r--r--py3status/modules/scratchpad-counter.py50
-rw-r--r--py3status/modules/sysdata.py148
-rw-r--r--py3status/modules/vnstat.py138
-rw-r--r--py3status/modules/weather_yahoo.py109
-rw-r--r--py3status/modules/whoami.py29
-rw-r--r--py3status/modules/window-title.py60
22 files changed, 2131 insertions, 130 deletions
diff --git a/py3status/__init__.py b/py3status/__init__.py
index 4182b7c..49bd97e 100755
--- a/py3status/__init__.py
+++ b/py3status/__init__.py
@@ -14,6 +14,7 @@ from signal import SIGUSR1
from subprocess import Popen
from subprocess import PIPE
from subprocess import call
+from tempfile import NamedTemporaryFile
from threading import Event, Thread
from time import sleep, time
from syslog import syslog, LOG_ERR, LOG_INFO, LOG_WARNING
@@ -81,69 +82,147 @@ class I3status(Thread):
Our output will be read asynchronously from 'last_output'.
"""
Thread.__init__(self)
- self.i3status_config_path = i3status_config_path
- self.standalone = standalone
- self.config = self.i3status_config_reader()
self.error = None
+ self.i3status_module_names = [
+ 'battery',
+ 'cpu_temperature',
+ 'cpu_usage',
+ 'ddate',
+ 'disk',
+ 'ethernet',
+ 'ipv6',
+ 'load',
+ 'path_exists',
+ 'run_watch',
+ 'time',
+ 'tztime',
+ 'volume',
+ 'wireless'
+ ]
+ self.json_list = None
+ self.json_list_ts = None
self.last_output = None
self.last_output_ts = None
self.last_prefix = None
self.lock = lock
- self.json_list = None
- self.json_list_ts = None
+ self.standalone = standalone
+ self.time_format = '%Y-%m-%d %H:%M:%S'
+ #
+ self.config = self.i3status_config_reader(i3status_config_path)
- def i3status_config_reader(self):
+ def valid_config_param(self, param_name):
+ """
+ Check if a given section name is a valid parameter for i3status.
+ """
+ valid_config_params = self.i3status_module_names + ['general', 'order']
+ return param_name.split(' ')[0] in valid_config_params
+
+ def i3status_config_reader(self, i3status_config_path):
"""
Parse i3status.conf so we can adapt our code to the i3status config.
"""
- in_time = False
- in_general = False
config = {
- 'colors': False,
- 'color_good': '#00FF00',
- 'color_bad': '#FF0000',
- 'color_degraded': '#FFFF00',
- 'color_separator': '#333333',
- 'interval': 5,
- 'output_format': None,
- 'time_format': '%Y-%m-%d %H:%M:%S'
+ 'general': {
+ 'color_bad': '#FF0000',
+ 'color_degraded': '#FFFF00',
+ 'color_good': '#00FF00',
+ 'color_separator': '#333333',
+ 'colors': False,
+ 'interval': 5,
+ 'output_format': 'i3bar'
+ },
+ 'i3s_modules': [],
+ 'on_click': {},
+ 'order': [],
+ 'py3_modules': []
}
# some ugly parsing
- if os.path.isfile(self.i3status_config_path):
- for line in open(self.i3status_config_path, 'r'):
- line = line.strip(' \t\n\r')
- if line.startswith('general'):
- in_general = True
- elif line.startswith('time') or line.startswith('tztime'):
- in_time = True
- elif line.startswith('}'):
- in_general = False
- in_time = False
- if in_general and '=' in line:
- key = line.split('=')[0].strip()
- value = line.split('=')[1].strip()
- if key in config:
- if value in ['true', 'false']:
- value = 'True' if value == 'true' else 'False'
+ in_section = False
+ section_name = ''
+
+ for line in open(i3status_config_path, 'r'):
+ line = line.strip(' \t\n\r')
+
+ if not line or line.startswith('#'):
+ continue
+
+ if line.startswith('order'):
+ in_section = True
+ section_name = 'order'
+
+ if not in_section:
+ section_name = line.split('{')[0].strip()
+ if section_name not in config:
+ config[section_name] = {}
+
+ if '{' in line:
+ in_section = True
+
+ if section_name and '=' in line:
+ line = line.split('}')[0].strip()
+
+ key = line.split('=')[0].strip()
+ value = line.split('=')[1].strip()
+ try:
+ e_value = eval(value)
+ if isinstance(e_value, str) or isinstance(e_value, int):
+ value = e_value
+ else:
+ raise ValueError()
+ except NameError:
+ pass
+ except ValueError:
+ pass
+
+ if section_name == 'order':
+ config[section_name].append(value)
+ line = '}'
+
+ # detect internal modules to be loaded dynamically
+ if not self.valid_config_param(value):
+ config['py3_modules'].append(value)
+ else:
+ config['i3s_modules'].append(value)
+ else:
+ if not key.startswith('on_click'):
+ config[section_name][key] = value
+ else:
+ # on_click special parameters
try:
- config[key] = eval(value)
- except NameError:
- config[key] = value
- if in_time and '=' in line:
- key = line.split('=')[0].strip()
- value = line.split('=')[1].strip()
- if 'time_' + key in config:
- config['time_' + key] = eval(value)
+ button = int(key.split()[1])
+ if button not in range(1, 6):
+ raise ValueError(
+ 'should be 1, 2, 3, 4 or 5'
+ )
+ except IndexError as e:
+ raise IndexError(
+ 'missing "button id" for "on_click" '
+ 'parameter in section {}'.format(section_name)
+ )
+ except ValueError as e:
+ raise ValueError(
+ 'invalid "button id" '
+ 'for "on_click" parameter '
+ 'in section {} ({})'.format(section_name, e)
+ )
+ on_c = config['on_click']
+ on_c[section_name] = on_c.get(section_name, {})
+ on_c[section_name][button] = value
- # force output format on standalone mode
- if self.standalone:
- config['output_format'] = 'i3bar'
+ # override time format
+ if section_name in ['time', 'tztime'] and key == 'format':
+ self.time_format = value
- # py3status uses only the i3bar protocol
- assert config['output_format'] == 'i3bar', \
- 'i3status output_format should be set to "i3bar" on {}'.format(
- self.i3status_config_path
+ if '}' in line:
+ in_section = False
+ section_name = ''
+
+ # py3status only uses the i3bar protocol because it needs JSON output
+ if config['general']['output_format'] != 'i3bar':
+ raise RuntimeError(
+ 'i3status output_format should '
+ 'be set to "i3bar" on {}'.format(i3status_config_path)
)
return config
@@ -154,7 +233,7 @@ class I3status(Thread):
"""
json_list = deepcopy(self.json_list)
try:
- time_format = self.config['time_format']
+ time_format = self.time_format
for item in json_list:
if 'name' in item and item['name'] in ['time', 'tztime']:
i3status_time = item['full_text'].encode('UTF-8', 'replace')
@@ -172,9 +251,7 @@ class I3status(Thread):
)
date = datetime.strptime(i3status_time, time_format)
date += delta
- item['full_text'] = date.strftime(
- self.config['time_format']
- )
+ item['full_text'] = date.strftime(self.time_format)
item['transformed'] = True
except Exception:
err = sys.exc_info()[1]
@@ -191,60 +268,106 @@ class I3status(Thread):
self.json_list = deepcopy(self.last_output)
self.json_list_ts = deepcopy(self.last_output_ts)
+ def get_modules_output(self, json_list, py3_modules):
+ """
+ Return the final json list to be displayed on the i3bar by taking
+ into account every py3status configured module and i3status'.
+ Simply put, this method honors the initial 'order' configured by
+ the user in his i3status.conf.
+ """
+ ordered = []
+ for module in self.config['order']:
+ if module in py3_modules:
+ for method in py3_modules[module].methods.values():
+ ordered.append(method['last_output'])
+ else:
+ for m, j in zip(self.config['i3s_modules'], json_list):
+ if m == module:
+ ordered.append(j)
+ return ordered
+
+ def write_tmp_i3status_config(self, tmpfile):
+ """
+ Given a temporary file descriptor, write a valid i3status config file
+ based on the parsed one from 'i3status_config_path'.
+ """
+ for section_name, conf in self.config.items():
+ if section_name in ['i3s_modules', 'py3_modules']:
+ continue
+ elif section_name == 'order':
+ for module_name in conf:
+ if self.valid_config_param(module_name):
+ tmpfile.write('order += "%s"\n' % module_name)
+ tmpfile.write('\n')
+ elif self.valid_config_param(section_name):
+ tmpfile.write('%s {\n' % section_name)
+ for key, value in conf.items():
+ tmpfile.write(' %s = "%s"\n' % (key, value))
+ tmpfile.write('}\n\n')
+ tmpfile.flush()
+
def run(self):
"""
- Spawn i3status and poll its output.
+ Spawn i3status using a self generated config file and poll its output.
"""
- i3status_pipe = Popen(
- ['i3status', '-c', self.i3status_config_path],
- stdout=PIPE,
- stderr=PIPE,
- )
- self.poller_inp = IOPoller(i3status_pipe.stdout)
- self.poller_err = IOPoller(i3status_pipe.stderr)
+ with NamedTemporaryFile(prefix='py3status_') as tmpfile:
+ self.write_tmp_i3status_config(tmpfile)
+ syslog(
+ LOG_INFO,
+ 'i3status spawned using config file {}'.format(tmpfile.name)
+ )
- try:
- # at first, poll very quickly to avoid delay in first i3bar display
- timeout = 0.001
+ i3status_pipe = Popen(
+ ['i3status', '-c', tmpfile.name],
+ stdout=PIPE,
+ stderr=PIPE,
+ )
+ self.poller_inp = IOPoller(i3status_pipe.stdout)
+ self.poller_err = IOPoller(i3status_pipe.stderr)
- # loop on i3status output
- while self.lock.is_set():
- line = self.poller_inp.readline(timeout)
- if line:
- if line.startswith('[{'):
- with jsonify(line) as (prefix, json_list):
- self.last_output = json_list
- self.last_output_ts = datetime.utcnow()
- self.last_prefix = ','
- self.update_json_list()
- print_line(line)
- elif not line.startswith(','):
- if 'version' in line:
- header = loads(line)
- header.update({'click_events': True})
- line = dumps(header)
- print_line(line)
- else:
- timeout = 0.5
- with jsonify(line) as (prefix, json_list):
- self.last_output = json_list
- self.last_output_ts = datetime.utcnow()
- self.last_prefix = prefix
- else:
- err = self.poller_err.readline(timeout)
- code = i3status_pipe.poll()
- if code is not None:
- if err:
- msg = 'i3status died and said: {}'.format(err)
+ try:
+ # at first, poll very quickly
+ # to avoid delay in first i3bar display
+ timeout = 0.001
+
+ # loop on i3status output
+ while self.lock.is_set():
+ line = self.poller_inp.readline(timeout)
+ if line:
+ if line.startswith('[{'):
+ with jsonify(line) as (prefix, json_list):
+ self.last_output = json_list
+ self.last_output_ts = datetime.utcnow()
+ self.last_prefix = ','
+ self.update_json_list()
+ print_line(line)
+ elif not line.startswith(','):
+ if 'version' in line:
+ header = loads(line)
+ header.update({'click_events': True})
+ line = dumps(header)
+ print_line(line)
else:
- msg = 'i3status died with code {}'.format(code)
- raise IOError(msg)
+ timeout = 0.5
+ with jsonify(line) as (prefix, json_list):
+ self.last_output = json_list
+ self.last_output_ts = datetime.utcnow()
+ self.last_prefix = prefix
else:
- # poll is CPU intensive, breath a bit
- sleep(timeout)
- except IOError:
- err = sys.exc_info()[1]
- self.error = err
+ err = self.poller_err.readline(timeout)
+ code = i3status_pipe.poll()
+ if code is not None:
+ if err:
+ msg = 'i3status died and said: {}'.format(err)
+ else:
+ msg = 'i3status died with code {}'.format(code)
+ raise IOError(msg)
+ else:
+ # poll is CPU intensive, breath a bit
+ sleep(timeout)
+ except IOError:
+ err = sys.exc_info()[1]
+ self.error = err
def mock(self):
"""
@@ -273,7 +396,7 @@ class Events(Thread):
"""
This class is responsible for dispatching event JSONs sent by the i3bar.
"""
- def __init__(self, lock, config, modules):
+ def __init__(self, lock, config, modules, on_click):
"""
We need to poll stdin to receive i3bar messages.
"""
@@ -282,6 +405,7 @@ class Events(Thread):
self.lock = lock
self.modules = modules
self.poller_inp = IOPoller(sys.stdin)
+ self.on_click = on_click
def dispatch(self, module, obj, event):
"""
@@ -306,7 +430,7 @@ class Events(Thread):
it will dispatch i3status click events to this module so you can catch
them and trigger any function call based on the event.
"""
- for module in self.modules:
+ for module in self.modules.values():
if not module.click_events:
continue
if module.module_name == 'i3bar_click_events.py':
@@ -314,6 +438,57 @@ class Events(Thread):
else:
return False
+ def refresh(self, module_name):
+ """
+ Force a cache expiration for all the methods of the given module.
+ """
+ module = self.modules.get(module_name)
+ if module is not None:
+ if self.config['debug']:
+ syslog(LOG_INFO, 'refresh module {}'.format(module_name))
+ for obj in module.methods.values():
+ obj['cached_until'] = time()
+
+ @staticmethod
+ def refresh_all(module_name):
+ """
+ Force a full refresh of py3status and i3status modules by sending
+ a SIGUSR1 signal to py3status.
+ """
+ Popen(['killall', '-USR1', 'py3status'])
+
+ def on_click_dispatcher(self, module_name, command):
+ """
+ Dispatch on_click config parameters to either:
+ - Our own methods for special py3status commands (listed below)
+ - The i3-msg program which is part of i3wm
+ """
+ py3_commands = ['refresh', 'refresh_all']
+ if command is None:
+ return
+ elif command in py3_commands:
+ # this is a py3status command handled by this class
+ method = getattr(self, command)
+ method(module_name)
+ else:
+ # this is a i3 message
+ self.i3_msg(module_name, command)
+
+ @staticmethod
+ def i3_msg(module_name, command):
+ """
+ Execute the given i3 message and log its output.
+ """
+ i3_msg_pipe = Popen(['i3-msg', command], stdout=PIPE)
+ syslog(
+ LOG_INFO,
+ 'i3-msg module={} command="{}" stdout={}'.format(
+ module_name,
+ command,
+ i3_msg_pipe.stdout.read()
+ )
+ )
+
def run(self):
"""
Wait for an i3bar JSON event, then find the right module to dispatch
@@ -336,13 +511,28 @@ class Events(Thread):
if self.config['debug']:
syslog(LOG_INFO, 'received event {}'.format(event))
- # setup default action on button 2 press
+ # usage variables
+ button = event.get('button', 0)
default_event = False
dispatched = False
- if 'button' in event and event['button'] == 2:
+ instance = event.get('instance', '')
+ name = event.get('name', '')
+
+ # guess the module config name
+ module_name = '{} {}'.format(name, instance).strip()
+
+ # execute any configured i3-msg command
+ if self.on_click.get(module_name, {}).get(button):
+ self.on_click_dispatcher(
+ module_name,
+ self.on_click[module_name].get(button)
+ )
+ dispatched = True
+ # otherwise setup default action on button 2 press
+ elif button == 2:
default_event = True
- for module in self.modules:
+ for module in self.modules.values():
# skip modules not supporting click_events
# unless we have a default_event set
if not module.click_events and not default_event:
@@ -350,9 +540,9 @@ class Events(Thread):
# check for the method name/instance
for obj in module.methods.values():
- if event['name'] == obj['name']:
- if 'instance' in event:
- if event['instance'] == obj['instance']:
+ if name == obj['name']:
+ if instance:
+ if instance == obj['instance']:
self.dispatch(module, obj, event)
dispatched = True
break
@@ -413,6 +603,20 @@ class Module(Thread):
inst = py_mod.Py3status()
return (mod_name, inst)
+ @staticmethod
+ def load_from_namespace(module_name):
+ """
+ Load a py3status bundled module.
+ """
+ inst = None
+ name = 'py3status.modules.{}'.format(module_name.split(' ')[0])
+ py_mod = __import__(name)
+ components = name.split('.')
+ for comp in components[1:]:
+ py_mod = getattr(py_mod, comp)
+ inst = py_mod.Py3status()
+ return (module_name, inst)
+
def clear_cache(self):
"""
Reset the cache for all methods of this module.
@@ -431,9 +635,20 @@ class Module(Thread):
- 'on_click' methods as they'll be called upon a click_event
- 'kill' methods as they'll be called upon this thread's exit
"""
- module, class_inst = self.load_from_file(include_path + f_name)
+ if include_path is not None:
+ module, class_inst = self.load_from_file(include_path + f_name)
+ else:
+ module, class_inst = self.load_from_namespace(f_name)
+
if module and class_inst:
self.module_class = class_inst
+
+ # apply module configuration from i3status config
+ mod_config = self.i3status_thread.config.get(self.module_name, {})
+ for config, value in mod_config.items():
+ setattr(self.module_class, config, value)
+
+ # get the available methods for execution
for method in sorted(dir(class_inst)):
if method.startswith('_'):
continue
@@ -480,7 +695,7 @@ class Module(Thread):
click_method = getattr(self.module_class, 'on_click')
click_method(
self.i3status_thread.json_list,
- self.i3status_thread.config,
+ self.i3status_thread.config['general'],
event
)
except Exception:
@@ -511,18 +726,31 @@ class Module(Thread):
try:
# execute method and get its output
method = getattr(self.module_class, meth)
- position, result = method(
+ response = method(
self.i3status_thread.json_list,
- self.i3status_thread.config
+ self.i3status_thread.config['general']
)
- # validate the result
- assert isinstance(result, dict), "should return a dict"
- assert 'full_text' in result, "missing 'full_text' key"
- assert 'name' in result, "missing 'name' key"
+ if isinstance(response, dict):
+ # this is a shiny new module giving a dict response
+ position, result = None, response
+ result['name'] = self.module_name.split(' ')[0]
+ result['instance'] = ''.join(
+ self.module_name.split(' ')[1:]
+ )
+ else:
+ # this is an old school module reporting its position
+ position, result = response
+ if not isinstance(position, int):
+ raise TypeError('position is not an int')
+ if not isinstance(result, dict):
+ raise TypeError('response should be a dict')
+ if 'name' not in result:
+ raise KeyError('missing "name" key in response')
- # validate the position
- assert isinstance(position, int), "position is not an int"
+ # validate the response
+ if not 'full_text' in result:
+ raise KeyError('missing "full_text" key in response')
# initialize method object
if my_method['name'] is None:
@@ -568,7 +796,7 @@ class Module(Thread):
kill_method = getattr(self.module_class, 'kill')
kill_method(
self.i3status_thread.json_list,
- self.i3status_thread.config
+ self.i3status_thread.config['general']
)
except Exception:
# this would be stupid to die on exit
@@ -583,8 +811,9 @@ class Py3statusWrapper():
"""
Useful variables we'll need.
"""
- self.modules = []
+ self.modules = {}
self.lock = Event()
+ self.py3_modules = []
def get_config(self):
"""
@@ -666,14 +895,27 @@ class Py3statusWrapper():
def list_modules(self):
"""
- Search import directories and files through given include paths.
+ Search import directories and files through given include paths with
+ respect to i3status.conf configured py3status modules.
+
+ User provided modules take precedence over py3status generic modules
+ but if none has been configured then we'll load every file present
+ as this is the legacy behavior.
+
This method is a generator and loves to yield.
"""
for include_path in sorted(self.config['include_paths']):
include_path = os.path.abspath(include_path) + '/'
- if os.path.isdir(include_path):
- for f_name in sorted(os.listdir(include_path)):
- if f_name.endswith('.py'):
+ if not os.path.isdir(include_path):
+ continue
+
+ for f_name in sorted(os.listdir(include_path)):
+ if f_name.endswith('.py'):
+ if self.py3_modules:
+ mod_name = f_name.rstrip('.py')
+ if mod_name in self.py3_modules:
+ yield (include_path, f_name)
+ else:
yield (include_path, f_name)
def setup(self):
@@ -718,7 +960,12 @@ class Py3statusWrapper():
)
# setup input events thread
- self.events_thread = Events(self.lock, self.config, self.modules)
+ self.events_thread = Events(
+ self.lock,
+ self.config,
+ self.modules,
+ self.i3status_thread.config['on_click']
+ )
self.events_thread.start()
if self.config['debug']:
syslog(LOG_INFO, 'events thread started')
@@ -728,8 +975,13 @@ class Py3statusWrapper():
sys.stdout = open('/dev/null', 'w')
sys.stderr = open('/dev/null', 'w')
- # load and spawn modules threads
+ # get the list of py3status configured modules
+ self.py3_modules = self.i3status_thread.config['py3_modules']
+
+ # load and spawn external modules threads
+ # based on inclusion folder
for include_path, f_name in self.list_modules():
+ module_name = f_name.rstrip('.py')
try:
my_m = Module(
self.lock,
@@ -741,7 +993,7 @@ class Py3statusWrapper():
# only start and handle modules with available methods
if my_m.methods:
my_m.start()
- self.modules.append(my_m)
+ self.modules[module_name] = my_m
elif self.config['debug']:
syslog(
LOG_INFO,
@@ -752,6 +1004,33 @@ class Py3statusWrapper():
msg = 'loading {} failed ({})'.format(f_name, err)
self.i3_nagbar(msg, level='warning')
+ # load and spawn i3status.conf configured modules threads
+ for module_name in self.py3_modules:
+ # ignore if the user already provided this module
+ if module_name in self.modules:
+ continue
+ try:
+ my_m = Module(
+ self.lock,
+ self.config,
+ None,
+ module_name,
+ self.i3status_thread
+ )
+ # only start and handle modules with available methods
+ if my_m.methods:
+ my_m.start()
+ self.modules[module_name] = my_m
+ elif self.config['debug']:
+ syslog(
+ LOG_INFO,
+ 'ignoring {} (no methods found)'.format(module_name)
+ )
+ except Exception:
+ err = sys.exc_info()[1]
+ msg = 'loading {} failed ({})'.format(module_name, err)
+ self.i3_nagbar(msg, level='warning')
+
def i3_nagbar(self, msg, level='error'):
"""
Make use of i3-nagbar to display errors and warnings to the user.
@@ -791,7 +1070,7 @@ class Py3statusWrapper():
"""
For every module, reset the 'cached_until' of all its methods.
"""
- for module in self.modules:
+ for module in self.modules.values():
module.clear_cache()
def get_modules_output(self, json_list):
@@ -804,7 +1083,8 @@ class Py3statusWrapper():
# prepopulate the list so that every usable index exists, thx @Lujeni
m_list = [
'' for value in range(
- sum([len(x.methods) for x in self.modules]) + len(json_list)
+ sum([len(x.methods) for x in self.modules.values()])
+ + len(json_list)
)
]
@@ -817,7 +1097,7 @@ class Py3statusWrapper():
# run through modules/methods output and insert them in reverse order
debug_msg = ''
- for m in reversed(self.modules):
+ for m in reversed(self.modules.values()):
for meth in m.methods:
position = m.methods[meth]['position']
last_output = m.methods[meth]['last_output']
@@ -925,7 +1205,7 @@ class Py3statusWrapper():
json_list = self.i3status_thread.adjust_time(delta)
# check that every module thread is alive
- for module in self.modules:
+ for module in self.modules.values():
if not module.is_alive():
# don't spam the user with i3-nagbar warnings
if not hasattr(module, 'i3_nagbar'):
@@ -937,7 +1217,15 @@ class Py3statusWrapper():
# construct the global output, modules first
if self.modules:
- json_list = self.get_modules_output(json_list)
+ if self.py3_modules:
+ # new style i3status configured ordering
+ json_list = self.i3status_thread.get_modules_output(
+ json_list,
+ self.modules
+ )
+ else:
+ # old style ordering
+ json_list = self.get_modules_output(json_list)
# dump the line to stdout
print_line('{}{}'.format(prefix, dumps(json_list)))
diff --git a/py3status/modules/__init__.py b/py3status/modules/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/py3status/modules/__init__.py
diff --git a/py3status/modules/battery-level.py b/py3status/modules/battery-level.py
new file mode 100644
index 0000000..6c30556
--- /dev/null
+++ b/py3status/modules/battery-level.py
@@ -0,0 +1,68 @@
+# -*- coding: utf8 -*-
+
+from __future__ import division # python2 compatibility
+from time import time
+
+import math
+import re
+import subprocess
+
+"""
+Module for displaying information about battery.
+
+Requires:
+ - the 'acpi' command line
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+CACHE_TIMEOUT = 30 # time to update battery
+HIDE_WHEN_FULL = False # hide any information when battery is fully charged
+
+MODE = "bar" # for primitive-one-char bar, or "text" for text percentage ouput
+
+BLOCKS = ["_", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] # block for bar
+TEXT_FORMAT = "Battery: {}" # text with "text" mode. percentage with % replaces {}
+
+CHARGING_CHARACTER = "⚡"
+
+# None means - get it from i3 config
+COLOR_BAD = None
+COLOR_CHARGING = "#FCE94F"
+COLOR_DEGRADED = None
+COLOR_GOOD = None
+
+
+class Py3status:
+ def battery_level(self, i3status_output_json, i3status_config):
+ response = {'name': 'battery-level'}
+
+ acpi = subprocess.check_output(["acpi"]).decode('utf-8')
+ proc = int(re.search(r"(\d+)%", acpi).group(1))
+
+ charging = bool(re.match(r".*Charging.*", acpi))
+ full = bool(re.match(r".*Unknown.*", acpi)) or bool(re.match(r".*Full.*", acpi))
+
+ if MODE == "bar":
+ character = BLOCKS[int(math.ceil(proc/100*(len(BLOCKS) - 1)))]
+ else:
+ character = TEXT_FORMAT.format(str(proc) + "%")
+
+ if proc < 30:
+ response['color'] = COLOR_DEGRADED if COLOR_DEGRADED else i3status_config['color_degraded']
+ if proc < 10:
+ response['color'] = COLOR_BAD if COLOR_BAD else i3status_config['color_bad']
+
+ if full:
+ response['color'] = COLOR_GOOD if COLOR_GOOD else i3status_config['color_good']
+ response['full_text'] = "" if HIDE_WHEN_FULL else BLOCKS[-1]
+ elif charging:
+ response['color'] = COLOR_CHARGING
+ response['full_text'] = CHARGING_CHARACTER
+ else:
+ response['full_text'] = character
+
+ response['cached_until'] = time() + CACHE_TIMEOUT
+
+ return (0, response)
diff --git a/py3status/modules/clementine.py b/py3status/modules/clementine.py
new file mode 100644
index 0000000..61917b7
--- /dev/null
+++ b/py3status/modules/clementine.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+from subprocess import check_output
+
+
+class Py3status:
+ """
+ clementine.py
+
+ This module display the current "artist - title" playing in Clementine.
+
+ Last modified: 2014-03-23
+ Author: Francois LASSERRE <choiz@me.com>
+ License: GNU GPL http://www.gnu.org/licenses/gpl.html
+ """
+ def _getMetadatas(self):
+ """
+ Get the current song metadatas (artist - title)
+ """
+ track_id = check_output('qdbus org.mpris.clementine /TrackList org.freedesktop.MediaPlayer.GetCurrentTrack', shell=True)
+ metadatas = check_output('qdbus org.mpris.clementine /TrackList org.freedesktop.MediaPlayer.GetMetadata {}'.format(track_id.decode()), shell=True)
+ lines = metadatas.decode('utf-8').split('\n')
+ lines = filter(None, lines)
+
+ now_playing = ''
+
+ if lines:
+ artist = ''
+ title = ''
+ internet_radio = False
+
+ for item in lines:
+ if item.find('artist:') != -1:
+ artist = item[8:]
+ if item.find('title:') != -1:
+ title = item[7:]
+
+ if title.find('.wav') != -1 or title.find('.mp3') != -1:
+ title = title[:-4]
+ if title.find('http') != -1:
+ title = ''
+ internet_radio = True
+
+ if artist and title:
+ now_playing = '♫ {} - {}'.format(artist, title)
+ elif artist:
+ now_playing = '♫ {}'.format(artist)
+ elif title:
+ now_playing = '♫ {}'.format(title)
+ elif internet_radio:
+ now_playing = '♫ Internet Radio'
+
+ return now_playing
+
+ def clementine(self, i3status_output_json, i3status_config):
+ """
+ Get the current "artist - title" and return it.
+ """
+ response = {'full_text': '', 'name': 'clementine'}
+
+ response['cached_until'] = time()
+ response['full_text'] = self._getMetadatas()
+
+ return (0, response)
diff --git a/py3status/modules/dpms.py b/py3status/modules/dpms.py
new file mode 100644
index 0000000..783c53c
--- /dev/null
+++ b/py3status/modules/dpms.py
@@ -0,0 +1,44 @@
+from os import system
+
+
+class Py3status:
+ """
+ This module allows activation and deactivation
+ of DPMS (Display Power Management Signaling)
+ by clicking on 'DPMS' in the status bar.
+
+ Written and contributed by @tasse:
+ Andre Doser <dosera AT tf.uni-freiburg.de>
+ """
+ def __init__(self):
+ """
+ Detect current state on start.
+ """
+ self.run = system('xset -q | grep -iq "DPMS is enabled"') == 0
+
+ def dpms(self, i3status_output_json, i3status_config):
+ """
+ Display a colorful state of DPMS.
+ """
+ result = {
+ 'full_text': 'DPMS',
+ 'name': 'dpms'
+ }
+ if self.run:
+ result['color'] = i3status_config['color_good']
+ else:
+ result['color'] = i3status_config['color_bad']
+ return (0, result)
+
+ def on_click(self, json, i3status_config, event):
+ """
+ Enable/Disable DPMS on left click.
+ """
+ if event['button'] == 1:
+ if self.run:
+ self.run = False
+ system("xset -dpms")
+ else:
+ self.run = True
+ system("xset +dpms")
+ system("killall -USR1 py3status")
diff --git a/py3status/modules/empty_class.py b/py3status/modules/empty_class.py
new file mode 100644
index 0000000..077269c
--- /dev/null
+++ b/py3status/modules/empty_class.py
@@ -0,0 +1,41 @@
+class Py3status:
+ """
+ Empty and basic py3status class.
+
+ NOTE: py3status will NOT execute:
+ - methods starting with '_'
+ - methods decorated by @property and @staticmethod
+
+ NOTE: reserved method names:
+ - 'kill' method for py3status exit notification
+ - 'on_click' method for click events from i3bar
+ """
+ def kill(self, i3status_output_json, i3status_config):
+ """
+ This method will be called upon py3status exit.
+ """
+ pass
+
+ def on_click(self, i3status_output_json, i3status_config, event):
+ """
+ This method will be called when a click event occurs on this module's
+ output on the i3bar.
+
+ Example 'event' json object:
+ {'y': 13, 'x': 1737, 'button': 1, 'name': 'empty', 'instance': 'first'}
+ """
+ pass
+
+ def empty(self, i3status_output_json, i3status_config):
+ """
+ This method will return an empty text message
+ so it will NOT be displayed on your i3bar.
+
+ If you want something displayed you should write something
+ in the 'full_text' key of your response.
+
+ See the i3bar protocol spec for more information:
+ http://i3wm.org/docs/i3bar-protocol.html
+ """
+ response = {'full_text': '', 'name': 'empty', 'instance': 'first'}
+ return (0, response)
diff --git a/py3status/modules/glpi.py b/py3status/modules/glpi.py
new file mode 100644
index 0000000..929749d
--- /dev/null
+++ b/py3status/modules/glpi.py
@@ -0,0 +1,52 @@
+# You need MySQL-python from http://pypi.python.org/pypi/MySQL-python
+import MySQLdb
+
+
+class Py3status:
+ """
+ This example class demonstrates how to display the current total number of
+ open tickets from GLPI in your i3bar.
+
+ It features thresholds to colorize the output and forces a low timeout to
+ limit the impact of a server connectivity problem on your i3bar freshness.
+
+ Note that we don't have to implement a cache layer as it is handled by
+ py3status automagically.
+ """
+ def count_glpi_open_tickets(self, json, i3status_config):
+ response = {'full_text': '', 'name': 'glpi_tickets'}
+
+ # user-defined variables
+ CRIT_THRESHOLD = 20
+ WARN_THRESHOLD = 15
+ MYSQL_DB = ''
+ MYSQL_HOST = ''
+ MYSQL_PASSWD = ''
+ MYSQL_USER = ''
+ POSITION = 0
+
+ mydb = MySQLdb.connect(
+ host=MYSQL_HOST,
+ user=MYSQL_USER,
+ passwd=MYSQL_PASSWD,
+ db=MYSQL_DB,
+ connect_timeout=5,
+ )
+ mycr = mydb.cursor()
+ mycr.execute('''select count(*)
+ from glpi_tickets
+ where closedate is NULL and solvedate is NULL;''')
+ row = mycr.fetchone()
+ if row:
+ open_tickets = int(row[0])
+ if i3status_config['colors']:
+ if open_tickets > CRIT_THRESHOLD:
+ response.update({'color': i3status_config['color_bad']})
+ elif open_tickets > WARN_THRESHOLD:
+ response.update(
+ {'color': i3status_config['color_degraded']}
+ )
+ response['full_text'] = '%s tickets' % open_tickets
+ mydb.close()
+
+ return (POSITION, response)
diff --git a/py3status/modules/i3bar_click_events.py b/py3status/modules/i3bar_click_events.py
new file mode 100644
index 0000000..792965c
--- /dev/null
+++ b/py3status/modules/i3bar_click_events.py
@@ -0,0 +1,127 @@
+from subprocess import Popen
+from time import time
+
+
+class Py3status:
+ """
+ This module allows you to take actions based on click events made on
+ the i3status modules. For example, thanks to this module you could
+ launch the wicd GUI when clicking on the ethernet or wireless module
+ of your i3status output !
+
+ IMPORTANT:
+ This module file name is reserved and should NOT be changed if you
+ want py3status to handle your i3status modules click events !
+
+ The behavior described above will only work if this file is named
+ 'i3bar_click_events.py' !
+ """
+ def __init__(self):
+ """
+ This is where you setup your actions based on your i3status config.
+
+ Configuration:
+ --------------
+ self.actions = {
+ "<module name and instance>": {
+ <button number>: [<function to run>, <arg1>, <arg2>],
+ }
+ }
+
+ Variables:
+ ----------
+ <button number> is an integer from 1 to 3:
+ 1 : left click
+ 2 : middle click
+ 3 : right click
+
+ <function to run> is a python function written in this module.
+ The 'external_command' function is provided for convenience if
+ you want to call an external program from this module.
+ You can of course write your own functions and have them executed
+ on a click event at will with possible arguments <arg1>, <arg2>...
+
+ <module name and instance> is a string made from the module
+ attribute 'name' and 'instance' using a space as separator:
+ For i3status modules, it's simply the name of the module as it
+ appears in the 'order' instruction of your i3status.conf.
+ Example:
+ in i3status.conf -> order += "wireless wlan0"
+ self.actions key -> "wireless wlan0"
+
+ Usage example:
+ --------------
+ - Open the wicd-gtk GUI when we LEFT click on the ethernet
+ module of i3status.
+ - Open emelfm2 to /home when we LEFT click on
+ the /home instance of disk_info
+ - Open emelfm2 to / when we LEFT click on
+ the / instance of disk_info
+
+ The related i3status.conf looks like:
+ order += "disk /home"
+ order += "disk /"
+ order += "ethernet eth0"
+
+ The resulting self.actions should be:
+ self.actions = {
+ "ethernet eth0": {
+ 1: [external_command, 'wicd-gtk', '-n'],
+ },
+ "disk_info /home": {
+ 1: [external_command, 'emelfm2', '-1', '~'],
+ },
+ "disk_info /": {
+ 1: [external_command, 'emelfm2', '-1', '/'],
+ },
+ }
+ """
+ # CONFIGURE ME PLEASE, LOVE YOU BIG TIME !
+ self.actions = {
+ }
+
+ def on_click(self, i3status_output_json, i3status_config, event):
+ """
+ If an action is configured for the given i3status module 'name' and
+ 'instance' (if present), then we'll execute the given function with
+ its arguments (if present).
+
+ Usually you SHOULD NOT modify this part of the code.
+ """
+ button = event['button']
+ key_name = '{} {}'.format(
+ event['name'],
+ event.get('instance', '')
+ ).strip()
+ if key_name in self.actions and button in self.actions[key_name]:
+ # get the function to run
+ func = self.actions[key_name][button][0]
+ assert hasattr(func, '__call__'), \
+ 'first element of the action list must be a function'
+
+ # run the function with the possibly given arguments
+ func(*self.actions[key_name][button][1:])
+
+ def i3bar_click_events(self, i3status_output_json, i3status_config):
+ """
+ Cached empty output, this module doesn't show anything.
+ """
+ response = {'full_text': '', 'name': 'i3bar_click_events'}
+ response['cached_until'] = time() + 3600
+ return (-1, response)
+
+
+def external_command(*cmd):
+ """
+ This convenience function lets you call an external program at will.
+
+ NOTE:
+ The stdout and stderr MUST be suppressed as shown here to avoid any
+ output from being caught by the i3bar (this would freeze it).
+ See issue #20 for more info.
+ """
+ Popen(
+ cmd,
+ stdout=open('/dev/null', 'w'),
+ stderr=open('/dev/null', 'w')
+ )
diff --git a/py3status/modules/imap.py b/py3status/modules/imap.py
new file mode 100644
index 0000000..5953ee3
--- /dev/null
+++ b/py3status/modules/imap.py
@@ -0,0 +1,58 @@
+# -*- coding: utf8 -*-
+
+from time import time
+import imaplib
+
+"""
+Module displaying the number of unread messages
+on an IMAP inbox (configurable).
+
+@author obb
+"""
+
+
+class Py3status:
+
+ def __init__(self):
+ self.service_name = 'Mail'
+ self.imap_server = '<IMAP_SERVER>'
+ self.port = '993'
+
+ self.user = '<USERNAME>'
+ self.password = '<PASSWORD>'
+
+ self.mailbox = 'INBOX'
+ self.criterion = 'UNSEEN'
+
+ self.check_frequency = 60
+
+ def check_mail(self, json, i3status_config):
+ mail_count = self._get_mail_count()
+
+ response = {
+ 'name': self.service_name + '_checker',
+ 'full_text': '{}: {}'.format(self.service_name, mail_count),
+ 'cached_until': time() + self.check_frequency
+ }
+
+ new_mail_color = i3status_config['color_good']
+ check_failed_color = i3status_config['color_bad']
+
+ if mail_count == 'N/A':
+ response['color'] = check_failed_color
+ elif mail_count != '0':
+ response['color'] = new_mail_color
+
+ return (0, response)
+
+ def _get_mail_count(self):
+ try:
+ connection = imaplib.IMAP4_SSL(self.imap_server, self.port)
+ connection.login(self.user, self.password)
+ connection.select(self.mailbox)
+ unseen_response = connection.search(None, self.criterion)
+ mails = unseen_response[1][0].split()
+ mail_count = len(mails)
+ return mail_count
+ except:
+ return 'N/A'
diff --git a/py3status/modules/keyboard-layout.py b/py3status/modules/keyboard-layout.py
new file mode 100644
index 0000000..1ed3cee
--- /dev/null
+++ b/py3status/modules/keyboard-layout.py
@@ -0,0 +1,73 @@
+from subprocess import check_output
+from time import time
+
+"""
+Module for showing current keyboard layout.
+
+Requires:
+ - xkblayout-state
+ or
+ - setxkbmap
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+CACHE_TIMEOUT = 10 # refresh time to update indicator
+
+# colors of layouts, check your command's output to match keys
+LANG_COLORS = {
+ 'fr': '#268BD2', # solarized blue
+ 'ru': '#F75252', # red
+ 'ua': '#FCE94F', # yellow
+ 'us': '#729FCF', # light blue
+}
+
+
+def xbklayout():
+ """
+ check using xkblayout-state (preferred method)
+ """
+ return check_output(
+ ["xkblayout-state", "print", "%s"]
+ ).decode('utf-8')
+
+
+def setxkbmap():
+ """
+ check using setxkbmap >= 1.3.0
+
+ Please read issue 33 for more information :
+ https://github.com/ultrabug/py3status/pull/33
+ """
+ q = check_output(['setxkbmap', '-query']).decode('utf-8')
+ return q.replace(' ', '').split(':')[-1]
+
+
+class Py3status:
+ def __init__(self):
+ """
+ find the best implementation to get the keyboard's layout
+ """
+ try:
+ xbklayout()
+ except:
+ self.command = setxkbmap
+ else:
+ self.command = xbklayout
+
+ def keyboard_layout(self, i3status_output_json, i3status_config):
+ response = {
+ 'full_text': '',
+ 'name': 'keyboard-layout',
+ 'cached_until': time() + CACHE_TIMEOUT
+ }
+
+ lang = self.command().strip()
+ lang_color = LANG_COLORS.get(lang)
+
+ response['full_text'] = lang or '??'
+ if lang_color:
+ response['color'] = lang_color
+
+ return (0, response)
diff --git a/py3status/modules/mpd_status.py b/py3status/modules/mpd_status.py
new file mode 100644
index 0000000..28e99dc
--- /dev/null
+++ b/py3status/modules/mpd_status.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from mpd import (MPDClient, CommandError)
+from socket import error as SocketError
+from time import time
+
+"""
+Mpd - module to show information from mpd to your's bar! Settings listed above.
+Reequires
+ - python-mpd2 (NOT python2-mpd2)
+ # pip install python-mpd2
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+# mpd settings
+HOST = "localhost"
+PORT = "6600"
+PASSWORD = None
+
+# time to update
+CACHE_TIMEOUT = 1
+
+# position
+POSITION = 0
+
+# if text length will be greater - it'll shrink it
+MAX_WIDTH = 120
+
+# hide any indicator, if
+HIDE_WHEN_PAUSED = False
+HIDE_WHEN_STOPPED = True
+
+# state characters (or strings). Actual of them replaces {state} placeholder in STRFORMAT
+STATE_CHARACTERS = {
+ "pause": "[pause]",
+ "play": "[play]",
+ "stop": "[stop]",
+}
+
+""" format of result string
+can contain:
+ {state} - current state from STATE_CHARACTERS
+ Track information:
+ {track}, {artist}, {title}, {time}, {album}, {pos}
+ In additional, information about next track also comes in, in analogue with current, but with next_ prefix,
+ like {next_title}
+"""
+STRFORMAT = "{state} №{pos}. {artist} - {title} [{time}] | {next_title}"
+
+
+class Py3status:
+ def __init__(self):
+ self.text = ''
+
+ def currentTrack(self, i3status_output_json, i3status_config):
+ try:
+ c = MPDClient()
+ c.connect(host=HOST, port=PORT)
+ if PASSWORD:
+ c.password(PASSWORD)
+
+ status = c.status()
+ song = int(status.get("song", 0))
+ next_song = int(status.get("nextsong", 0))
+
+ if (status["state"] == "pause" and HIDE_WHEN_PAUSED) or (status["state"] == "stop" and HIDE_WHEN_STOPPED):
+ text = ""
+ else:
+ try:
+ song = c.playlistinfo()[song]
+ song["time"] = "{0:.2f}".format(int(song.get("time", 1)) / 60)
+ except IndexError:
+ song = {}
+
+ try:
+ next_song = c.playlistinfo()[next_song]
+ except IndexError:
+ next_song = {}
+
+ format_args = song
+ format_args["state"] = STATE_CHARACTERS.get(status.get("state", None))
+ for k, v in next_song.items():
+ format_args["next_{}".format(k)] = v
+
+ text = STRFORMAT
+ for k, v in format_args.items():
+ text = text.replace("{" + k + "}", v)
+
+ for sub in re.findall(r"{\S+?}", text):
+ text = text.replace(sub, "")
+ except SocketError:
+ text = "Failed to connect to mpd!"
+ except CommandError:
+ text = "Failed to authenticate to mpd!"
+ c.disconnect()
+
+ if len(text) > MAX_WIDTH:
+ text = text[-MAX_WIDTH-3:] + "..."
+
+ if self.text != text:
+ transformed = True
+ self.text = text
+ else:
+ transformed = False
+
+ response = {
+ 'cached_until': time() + CACHE_TIMEOUT,
+ 'full_text': self.text,
+ 'name': 'scratchpad-count',
+ 'transformed': transformed
+ }
+
+ return (POSITION, response)
diff --git a/py3status/modules/net_rate.py b/py3status/modules/net_rate.py
new file mode 100644
index 0000000..dff075c
--- /dev/null
+++ b/py3status/modules/net_rate.py
@@ -0,0 +1,163 @@
+# -*- coding: utf8 -*-
+
+from __future__ import division # python2 compatibility
+from time import time, sleep
+
+
+"""
+Module for displaying current network transfer rate.
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+CACHED_TIME = 2 # update time (in seconds)
+POSITION = 0 # bar position
+
+DEVFILE = "/proc/net/dev" # location of dev file under /proc
+
+INTERFACES = [] # list of interfaces to track
+ALL_INTERFACES = True # ignore INTERFACES, but not INTERFACES_BLACKLIST
+INTERFACES_BLACKLIST = ["lo"] # list of interfaces to ignore
+
+"""
+Format of status string.
+
+Placeholders:
+ interface - name of interface
+ total - total rate
+ up - upload rate
+ down - download rate
+"""
+FORMAT = "{interface}: {total}"
+
+PRECISION = 1 # amount of numbers after dot
+MULTIPLIER_TOP = 999 # if value is greater, divide it with UNIT_MULTI and get next unit from UNITS
+LEFT_ALIGN = len(str(MULTIPLIER_TOP)) + 1 + PRECISION # == 6 characters (from MULTIPLIER_TOP + dot + PRECISION)
+
+"""
+Format of total, up and down placeholders under FORMAT.
+As default, substitutes LEFT_ALIGN and PRECISION as %s and %s
+Placeholders:
+ value - value (float)
+ unit - unit (string)
+
+"""
+VALUE_FORMAT = "{value:%s.%sf} {unit}" % (LEFT_ALIGN, PRECISION)
+
+INITIAL_MULTI = 1024 # initial multiplier, if you want to get rid of first bytes, set to 1 to disable
+UNIT_MULTI = 1024 # value to divide if rate is greater than MULTIPLIER_TOP
+UNITS = ["kb/s", "mb/s", "gb/s", "tb/s", ] # list of units, first one - value/INITIAL_MULTI, second - value/1024, third - value/1024^2, etc...
+
+NO_CONNECTION = "! no data" # when there is no data transmitted from the start of the plugin
+HIDE_IF_NO = False # hide indicator if rate == 0
+
+
+def get_stat():
+ """
+ Get statistics from devfile in list of lists of words
+ """
+ def dev_filter(x):
+ # get first word and remove trailing interface number
+ x = x.strip().split(" ")[0][:-1]
+
+ if x in INTERFACES_BLACKLIST:
+ return False
+
+ if ALL_INTERFACES:
+ return True
+
+ if x in INTERFACES:
+ return True
+
+ return False
+
+ # read devfile, skip two header files
+ x = filter(dev_filter, open(DEVFILE).readlines()[2:])
+
+ try:
+ # split info into words, filter empty ones
+ return [list(filter(lambda x: x, _x.split(" "))) for _x in x]
+
+ except StopIteration:
+ return None
+
+
+def divide_and_format(value):
+ """
+ Divide a value and return formatted string
+ """
+ for i, unit in enumerate(UNITS):
+ if value > MULTIPLIER_TOP:
+ value /= UNIT_MULTI
+ else:
+ break
+
+ return VALUE_FORMAT.format(value=value, unit=unit)
+
+
+class Py3status:
+ def __init__(self, *args, **kwargs):
+ self.last_stat = get_stat()
+ self.last_time = time()
+ self.last_interface = None
+
+ def currentSpeed(self, json, i3status_config):
+ ns = get_stat()
+ deltas = {}
+ try:
+ # time from previous check
+ timedelta = time() - self.last_time
+
+ # calculate deltas for all interfaces
+ for old, new in zip(self.last_stat, ns):
+ down = int(new[1]) - int(old[1])
+ up = int(new[9]) - int(old[9])
+
+ down /= timedelta * INITIAL_MULTI
+ up /= timedelta * INITIAL_MULTI
+
+ deltas[new[0]] = {'total': up+down, 'up': up, 'down': down, }
+
+ # update last_ info
+ self.last_stat = get_stat()
+ self.last_time = time()
+
+ # get the interface with max rate
+ interface = max(deltas, key=lambda x: deltas[x]['total'])
+
+ # if there is no rate - show last active interface, or hide
+ if deltas[interface]['total'] == 0:
+ interface = self.last_interface
+ hide = HIDE_IF_NO
+ # if there is - update last_interface
+ else:
+ self.last_interface = interface
+ hide = False
+
+ # get the deltas into variable
+ delta = deltas[interface] if interface else None
+
+ except TypeError:
+ delta = None
+ interface = None
+ hide = HIDE_IF_NO
+
+ return (POSITION, {
+ 'transformed': True,
+ 'full_text': "" if hide else
+ FORMAT.format(
+ total=divide_and_format(delta['total']),
+ up=divide_and_format(delta['up']),
+ down=divide_and_format(delta['down']),
+ interface=interface[:-1],
+ ) if interface else NO_CONNECTION,
+ 'name': 'speed',
+ 'cached_until': time() + CACHED_TIME,
+ })
+
+if __name__ == "__main__":
+ x = Py3status()
+ while True:
+ print(x.currentSpeed(1, 1))
+ sleep(1)
diff --git a/py3status/modules/netdata.py b/py3status/modules/netdata.py
new file mode 100644
index 0000000..729983b
--- /dev/null
+++ b/py3status/modules/netdata.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+# netdata
+
+# Netdata is a module uses great Py3status (i3status wrapper) to
+# display network information (Linux systems) in i3bar.
+# For more information read:
+# i3wm homepage: http://i3wm.org
+# py3status homepage: https://github.com/ultrabug/py3status
+
+# Copyright (C) <2013> <Shahin Azad [ishahinism at Gmail]>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# ----------------------------------------------------------------- #
+# Notes:
+# 1. netdata will check 'eth0' interface by default. You can
+# change it by changing 'self.net_interface' variable in 'GetData'
+# class.
+# 2. Colors are depended on strict specification in traffic/netspeed methods.
+# You can change them by manipulating conditions.
+
+import subprocess
+from time import time
+
+# Method 'netSpeed' will use this variables to calculate downloaded
+# bytes in last second. Initializing this variables globally is
+# necessary since we can't use __init__ method in Py3Status class.
+old_transmitted, old_received = 0, 0
+
+
+class GetData:
+ """Get system status
+
+ """
+ def __init__(self):
+ # You can change it to another interface.
+ # It'll be used for grabbing net interface data.
+ self.net_interface = 'eth0'
+
+ def execCMD(self, cmd, arg):
+ """Take a system command and its argument, then return the result.
+
+ Arguments:
+ - `cmd`: system command.
+ - `arg`: argument.
+ """
+ result = subprocess.check_output([cmd, arg])
+ return result
+
+ def netBytes(self):
+ """Execute 'cat /proc/net/dev', find the interface line (Default
+ 'eth0') and grab received/transmitted bytes.
+
+ """
+ net_data = self.execCMD('cat', '/proc/net/dev').decode('utf-8').split()
+ interface_index = net_data.index(self.net_interface + ':')
+ received_bytes = int(net_data[interface_index + 1])
+ transmitted_bytes = int(net_data[interface_index + 9])
+
+ return received_bytes, transmitted_bytes
+
+
+class Py3status:
+ """
+ System status in i3bar
+ """
+ def netSpeed(self, json, i3status_config):
+ """Calculate network speed ('eth0' interface) and return it. You can
+ change the interface using 'self.net_interface' variable in
+ 'GetData' class.
+
+ """
+ data = GetData()
+ response = {'full_text': '', 'name': 'net_speed'}
+
+ global old_received
+ global old_transmitted
+
+ received_bytes, transmitted_bytes = data.netBytes()
+ dl_speed = (received_bytes - old_received) / 1024.
+ up_speed = (transmitted_bytes - old_transmitted) / 1024.
+
+ if dl_speed < 30:
+ response['color'] = i3status_config['color_bad']
+ elif dl_speed < 60:
+ response['color'] = i3status_config['color_degraded']
+ else:
+ response['color'] = i3status_config['color_good']
+
+ response['full_text'] = "LAN(Kb): {:5.1f}↓ {:5.1f}↑"\
+ .format(dl_speed, up_speed)
+ response['cached_until'] = time()
+
+ old_received, old_transmitted = received_bytes, transmitted_bytes
+ return (0, response)
+
+ def traffic(self, json, i3status_config):
+ """Calculate networks used traffic. Same as 'netSpeed' method you can
+ change the interface.
+
+ """
+ data = GetData()
+ response = {'full_text': '', 'name': 'traffic'}
+
+ received_bytes, transmitted_bytes = data.netBytes()
+ download = received_bytes / 1024 / 1024.
+ upload = transmitted_bytes / 1024 / 1024.
+ total = download + upload
+
+ if total < 400:
+ response['color'] = i3status_config['color_good']
+ elif total < 700:
+ response['color'] = i3status_config['color_degraded']
+ else:
+ response['color'] = i3status_config['color_bad']
+
+ response['full_text'] = "T(Mb): {:3.0f}↓ {:3.0f}↑ {:3.0f}↕"\
+ .format(download, upload, total)
+
+ return (1, response)
diff --git a/py3status/modules/ns_checker.py b/py3status/modules/ns_checker.py
new file mode 100644
index 0000000..4a332b1
--- /dev/null
+++ b/py3status/modules/ns_checker.py
@@ -0,0 +1,57 @@
+import dns.resolver
+import socket
+
+
+class Py3status:
+ """
+ This module launch a simple query on each nameservers for the specified domain.
+ Nameservers are dynamically retrieved. The FQDN is the only one mandatory parameter.
+ It's also possible to add additional nameservers by appending them in nameservers list.
+
+ The default resolver can be overwritten with my_resolver.nameservers parameter.
+
+ Written and contributed by @nawadanp
+ """
+ def __init__(self):
+ self.domain = 'google.com'
+ self.lifetime = 0.3
+ self.resolver = []
+ self.nameservers = []
+
+ def ns_checker(self, i3status_output_json, i3status_config):
+ response = {'full_text': '', 'name': 'ns_checker'}
+ position = 0
+ counter = 0
+ error = False
+ nameservers = []
+
+ my_resolver = dns.resolver.Resolver()
+ my_resolver.lifetime = self.lifetime
+ if self.resolver:
+ my_resolver.nameservers = self.resolver
+
+ my_ns = my_resolver.query(self.domain, 'NS')
+
+ # Insert each NS ip address in nameservers
+ for ns in my_ns:
+ nameservers.append(str(socket.gethostbyname(str(ns))))
+ for ns in self.nameservers:
+ nameservers.append(str(ns))
+
+ # Perform a simple DNS query, for each NS servers
+ for ns in nameservers:
+ my_resolver.nameservers = [ns]
+ counter += 1
+ try:
+ my_resolver.query(self.domain, 'A')
+ except:
+ error = True
+
+ if error:
+ response['full_text'] = str(counter) + ' NS NOK'
+ response['color'] = i3status_config['color_bad']
+ else:
+ response['full_text'] = str(counter) + ' NS OK'
+ response['color'] = i3status_config['color_good']
+
+ return (position, response)
diff --git a/py3status/modules/pingdom.py b/py3status/modules/pingdom.py
new file mode 100644
index 0000000..cddf1a9
--- /dev/null
+++ b/py3status/modules/pingdom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import requests
+from time import time
+
+
+class Py3status:
+ """
+ Dynamically display the latest response time of the configured checks using
+ the Pingdom API.
+ We also verify the status of the checks and colorize if needed.
+ Pingdom API doc : https://www.pingdom.com/services/api-documentation-rest/
+
+ #NOTE: This module needs the 'requests' python module from pypi
+ https://pypi.python.org/pypi/requests
+ """
+ def pingdom_checks(self, json, i3status_config):
+ response = {'full_text': '', 'name': 'pingdom_checks'}
+
+ #NOTE: configure me !
+ APP_KEY = '' # create an APP KEY on pingdom first
+ CACHE_TIMEOUT = 600 # recheck every 10 mins
+ CHECKS = [] # checks' names you want added to your bar
+ LATENCY_THRESHOLD = 500 # when to colorize the output
+ LOGIN = '' # pingdom login
+ PASSWORD = '' # pingdom password
+ TIMEOUT = 15
+ POSITION = 0
+
+ r = requests.get(
+ 'https://api.pingdom.com/api/2.0/checks',
+ auth=(LOGIN, PASSWORD),
+ headers={'App-Key': APP_KEY},
+ timeout=TIMEOUT,
+ )
+ result = r.json()
+ if 'checks' in result:
+ for check in [
+ ck for ck in result['checks'] if ck['name'] in CHECKS
+ ]:
+ if check['status'] == 'up':
+ response['full_text'] += '{}: {}ms, '.format(
+ check['name'],
+ check['lastresponsetime']
+ )
+ if check['lastresponsetime'] > LATENCY_THRESHOLD:
+ response.update(
+ {'color': i3status_config['color_degraded']}
+ )
+ else:
+ response['full_text'] += '{}: DOWN'.format(
+ check['name'],
+ check['lastresponsetime']
+ )
+ response.update({'color': i3status_config['color_bad']})
+ response['full_text'] = response['full_text'].strip(', ')
+ response['cached_until'] = time() + CACHE_TIMEOUT
+
+ return (POSITION, response)
diff --git a/py3status/modules/pomodoro.py b/py3status/modules/pomodoro.py
new file mode 100644
index 0000000..28b5ea1
--- /dev/null
+++ b/py3status/modules/pomodoro.py
@@ -0,0 +1,123 @@
+"""
+Pomodoro countdown on i3bar originally written by @Fandekasp (Adrien Lemaire)
+"""
+from subprocess import call
+from time import time
+
+MAX_BREAKS = 4
+POSITION = 0
+TIMER_POMODORO = 25 * 60
+TIMER_BREAK = 5 * 60
+TIMER_LONG_BREAK = 15 * 60
+
+
+class Py3status:
+ """
+ """
+ def __init__(self):
+ self.__setup('stop')
+ self.alert = False
+ self.run = False
+
+ def on_click(self, json, i3status_config, event):
+ """
+ Handles click events
+ """
+ if event['button'] == 1:
+ if self.status == 'stop':
+ self.status = 'start'
+ self.run = True
+
+ elif event['button'] == 2:
+ self.__setup('stop')
+ self.run = False
+
+ elif event['button'] == 3:
+ self.__setup('pause')
+ self.run = False
+
+ @property
+ def response(self):
+ """
+ Return the response full_text string
+ """
+ return {
+ 'full_text': '{} ({})'.format(self.prefix, self.timer),
+ 'name': 'pomodoro'
+ }
+
+ def __setup(self, status):
+ """
+ Setup a step
+ """
+ self.status = status
+ if status == 'stop':
+ self.prefix = 'Pomodoro'
+ self.status = 'stop'
+ self.timer = TIMER_POMODORO
+ self.breaks = 1
+
+ elif status == 'start':
+ self.prefix = 'Pomodoro'
+ self.timer = TIMER_POMODORO
+
+ elif status == 'pause':
+ self.prefix = 'Break #%d' % self.breaks
+ if self.breaks > MAX_BREAKS:
+ self.timer = TIMER_LONG_BREAK
+ self.breaks = 1
+ else:
+ self.breaks += 1
+ self.timer = TIMER_BREAK
+
+ def __decrement(self):
+ """
+ Countdown handler
+ """
+ self.timer -= 1
+ if self.timer < 0:
+ self.alert = True
+ self.run = False
+ self.__i3_nagbar()
+ if self.status == 'start':
+ self.__setup('pause')
+ self.status = 'pause'
+ elif self.status == 'pause':
+ self.__setup('start')
+ self.status = 'start'
+
+ def __i3_nagbar(self, level='warning'):
+ """
+ Make use of i3-nagbar to display warnings to the user.
+ """
+ msg = '{} time is up !'.format(self.prefix)
+ try:
+ call(
+ ['i3-nagbar', '-m', msg, '-t', level],
+ stdout=open('/dev/null', 'w'),
+ stderr=open('/dev/null', 'w')
+ )
+ except:
+ pass
+
+ def pomodoro(self, json, i3status_config):
+ """
+ Pomodoro response handling and countdown
+ """
+ if self.run:
+ self.__decrement()
+
+ response = self.response
+ if self.alert:
+ response['urgent'] = True
+ self.alert = False
+
+ if self.status == 'start':
+ response['color'] = i3status_config['color_good']
+ elif self.status == 'pause':
+ response['color'] = i3status_config['color_degraded']
+ else:
+ response['color'] = i3status_config['color_bad']
+
+ response['cached_until'] = time()
+ return (POSITION, response)
diff --git a/py3status/modules/scratchpad-counter.py b/py3status/modules/scratchpad-counter.py
new file mode 100644
index 0000000..390745f
--- /dev/null
+++ b/py3status/modules/scratchpad-counter.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import i3
+from time import time
+
+"""
+Module showing amount of windows at the scratchpad.
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+CACHE_TIMEOUT = 5
+HIDE_WHEN_NONE = False # hide indicator when there is no windows
+POSITION = 0
+STRFORMAT = "{} ⌫" # format of indicator. {} replaces with count of windows
+
+
+def find_scratch(tree):
+ if tree["name"] == "__i3_scratch":
+ return tree
+ else:
+ for x in tree["nodes"]:
+ result = find_scratch(x)
+ if result:
+ return result
+ return None
+
+
+class Py3status:
+ def __init__(self):
+ self.count = -1
+
+ def scratchpad_counter(self, i3status_output_json, i3status_config):
+ count = len(find_scratch(i3.get_tree()).get("floating_nodes", []))
+
+ if self.count != count:
+ transformed = True
+ self.count = count
+ else:
+ transformed = False
+
+ response = {
+ 'cached_until': time() + CACHE_TIMEOUT,
+ 'full_text': '' if HIDE_WHEN_NONE and count == 0 else STRFORMAT.format(count),
+ 'name': 'scratchpad-counter',
+ 'transformed': transformed
+ }
+
+ return (POSITION, response)
diff --git a/py3status/modules/sysdata.py b/py3status/modules/sysdata.py
new file mode 100644
index 0000000..fbbd157
--- /dev/null
+++ b/py3status/modules/sysdata.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+# sysdata
+
+# Sysdata is a module uses great Py3status (i3status wrapper) to
+# display system information (RAM usage) in i3bar (Linux systems).
+# For more information read:
+# i3wm homepage: http://i3wm.org
+# py3status homepage: https://github.com/ultrabug/py3status
+
+# NOTE: If you want py3status to show you your CPU temperature, change value of CPUTEMP into True
+# in Py3status class - CPUInfo function
+# and REMEMBER that you must install lm_sensors if you want CPU temp!
+
+# Copyright (C) <2013> <Shahin Azad [ishahinism at Gmail]>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import subprocess
+from time import time
+
+
+class GetData:
+ """Get system status
+
+ """
+ def execCMD(self, cmd, arg):
+ """Take a system command and its argument, then return the result.
+
+ Arguments:
+ - `cmd`: system command.
+ - `arg`: argument.
+ """
+ result = subprocess.check_output([cmd, arg])
+ return result
+
+ def cpu(self):
+ """Get the cpu usage data from /proc/stat :
+ cpu 2255 34 2290 22625563 6290 127 456 0 0
+ - user: normal processes executing in user mode
+ - nice: niced processes executing in user mode
+ - system: processes executing in kernel mode
+ - idle: twiddling thumbs
+ - iowait: waiting for I/O to complete
+ - irq: servicing interrupts
+ - softirq: servicing softirqs
+ - steal: involuntary wait
+ - guest: running a normal guest
+ - guest_nice: running a niced guest
+ These numbers identify the amount of time the CPU has spent performing
+ different kinds of work. Time units are in USER_HZ (typically hundredths of a
+ second)
+ """
+ with open('/proc/stat', 'r') as fd:
+ line = fd.readline()
+ cpu_data = line.split()
+ total_cpu_time = sum(map(int, cpu_data[1:]))
+ cpu_idle_time = int(cpu_data[4])
+
+ #return the cpu total&idle time
+ return total_cpu_time, cpu_idle_time
+
+ def memory(self):
+ """Execute 'free -m' command, grab the memory capacity and used size
+ then return; Memory size 'total_mem', Used_mem, and percentage
+ of used memory.
+
+ """
+ # Run 'free -m' command and make a list from output.
+ mem_data = self.execCMD('free', '-m').split()
+ total_mem = int(mem_data[7]) / 1024.
+ used_mem = int(mem_data[15]) / 1024.
+ # Caculate percentage
+ used_mem_percent = int(used_mem / (total_mem / 100))
+
+ # Results are in kilobyte.
+ return total_mem, used_mem, used_mem_percent
+
+
+class Py3status:
+ """
+ System status in i3bar
+ """
+ def __init__(self):
+ self.data = GetData()
+ self.cpu_total = 0
+ self.cpu_idle = 0
+
+ def cpuInfo(self, json, i3status_config):
+ """calculate the CPU status and return it.
+
+ """
+ response = {'full_text': '', 'name': 'cpu_usage'}
+ cpu_total, cpu_idle = self.data.cpu()
+ used_cpu_percent = 1 - float(cpu_idle-self.cpu_idle)/float(cpu_total-self.cpu_total)
+ self.cpu_total = cpu_total
+ self.cpu_idle = cpu_idle
+
+ if used_cpu_percent <= 40/100.0:
+ response['color'] = i3status_config['color_good']
+ elif used_cpu_percent <= 75/100.0:
+ response['color'] = i3status_config['color_degraded']
+ else:
+ response['color'] = i3status_config['color_bad']
+ #cpu temp
+ CPUTEMP=False
+ if CPUTEMP:
+ cputemp=subprocess.check_output('sensors | grep "CPU Temp" | cut -f 2 -d "+" | cut -f 1 -d " "',shell=True)
+ cputemp=cputemp[:-1].decode('utf-8')
+ response['full_text'] = "CPU: %.2f%%" % (used_cpu_percent*100) +" "+cputemp
+ else:
+ response['full_text'] = "CPU: %.2f%%" % (used_cpu_percent*100)
+
+ #cache the status for 10 seconds
+ response['cached_until'] = time() + 10
+
+ return (0, response)
+
+ def ramInfo(self, json, i3status_config):
+ """calculate the memory (RAM) status and return it.
+
+ """
+ response = {'full_text': '', 'name': 'ram_info'}
+ total_mem, used_mem, used_mem_percent = self.data.memory()
+
+ if used_mem_percent <= 40:
+ response['color'] = i3status_config['color_good']
+ elif used_mem_percent <= 75:
+ response['color'] = i3status_config['color_degraded']
+ else:
+ response['color'] = i3status_config['color_bad']
+
+ response['full_text'] = "RAM: %.2f/%.2f GB (%d%%)" % \
+ (used_mem, total_mem, used_mem_percent)
+ response['cached_until'] = time()
+
+ return (0, response)
diff --git a/py3status/modules/vnstat.py b/py3status/modules/vnstat.py
new file mode 100644
index 0000000..100138c
--- /dev/null
+++ b/py3status/modules/vnstat.py
@@ -0,0 +1,138 @@
+# -*- coding: utf8 -*-
+
+from __future__ import division # python2 compatibility
+from time import time, sleep
+from subprocess import check_output
+
+
+"""
+Module for displaying vnstat's statistics.
+REQUIRE external program called "vnstat" installed and configured to work.
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+CACHED_TIME = 3*60 # update time (in seconds)
+POSITION = 0 # bar position
+STATISTICS_TYPE = "d" # d for daily, m for monthly
+
+"""
+Coloring rules.
+
+If value is bigger that dict key, status string will turn to color, specified in the value.
+Example:
+COLORING = {
+ 800: "#dddd00",
+ 900: "#dd0000",
+}
+(0 - 800: white, 800-900: yellow, >900 - red)
+"""
+COLORING = {}
+
+"""
+Format of status string.
+
+Placeholders:
+ total - total
+ up - upload
+ down - download
+"""
+FORMAT = "{total}"
+
+PRECISION = 1 # amount of numbers after dot
+MULTIPLIER_TOP = 1024 # if value is greater, divide it with UNIT_MULTI and get next unit from UNITS
+LEFT_ALIGN = 0
+
+"""
+Format of total, up and down placeholders under FORMAT.
+As default, substitutes LEFT_ALIGN and PRECISION as %s and %s
+Placeholders:
+ value - value (float)
+ unit - unit (string)
+
+"""
+VALUE_FORMAT = "{value:%s.%sf} {unit}" % (LEFT_ALIGN, PRECISION)
+
+INITIAL_MULTI = 1024 # initial multiplier, if you want to get rid of first bytes, set to 1 to disable
+UNIT_MULTI = 1024 # value to divide if rate is greater than MULTIPLIER_TOP
+UNITS = ["kb", "mb", "gb", "tb", ] # list of units, first one - value/INITIAL_MULTI, second - value/1024, third - value/1024^2, etc...
+
+
+def get_stat():
+ """
+ Get statistics from devfile in list of lists of words
+ """
+ def filter_stat():
+ for x in check_output(["vnstat", "--dumpdb"]).decode("utf-8").splitlines():
+ if x.startswith("{};0;".format(STATISTICS_TYPE)):
+ return x
+
+ try:
+ type, number, ts, rxm, txm, rxk, txk, fill = filter_stat().split(";")
+ except OSError as e:
+ print("Looks like you have'nt installed or configured vnstat!")
+ raise e
+ except ValueError:
+ raise RuntimeError("vnstat returned wrong output, maybe it's configured wrong or module is outdated")
+
+ up = (int(txm) * 1024 + int(txk)) * 1024
+ down = (int(rxm) * 1024 + int(rxk)) * 1024
+
+ return {"up": up,
+ "down": down,
+ "total": up+down, }
+
+
+def divide_and_format(value):
+ """
+ Divide a value and return formatted string
+ """
+ value /= INITIAL_MULTI
+ for i, unit in enumerate(UNITS):
+ if value > MULTIPLIER_TOP:
+ value /= UNIT_MULTI
+ else:
+ break
+
+ return VALUE_FORMAT.format(value=value, unit=unit)
+
+
+class Py3status:
+ def __init__(self, *args, **kwargs):
+ self.last_stat = get_stat()
+ self.last_time = time()
+ self.last_interface = None
+
+ def currentSpeed(self, json, i3status_config):
+ stat = get_stat()
+
+ color = None
+ keys = list(COLORING.keys())
+ keys.sort()
+ for k in keys:
+ if stat["total"] < k * 1024 * 1024:
+ break
+ else:
+ color = COLORING[k]
+
+ response = {
+ 'transformed': True,
+ 'full_text': FORMAT.format(
+ total=divide_and_format(stat['total']),
+ up=divide_and_format(stat['up']),
+ down=divide_and_format(stat['down']),),
+ 'name': 'vnstat',
+ 'cached_until': time() + CACHED_TIME,
+ }
+
+ if color:
+ response["color"] = color
+
+ return POSITION, response
+
+if __name__ == "__main__":
+ x = Py3status()
+ while True:
+ print(x.currentSpeed(1, 1))
+ sleep(1)
diff --git a/py3status/modules/weather_yahoo.py b/py3status/modules/weather_yahoo.py
new file mode 100644
index 0000000..09e1c72
--- /dev/null
+++ b/py3status/modules/weather_yahoo.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+"""
+Display current day + 3 days weather forecast as icons on your i3bar
+Based on Yahoo! Weather. forecast, thanks guys !
+ http://developer.yahoo.com/weather/
+
+Find your city code using:
+ http://answers.yahoo.com/question/index?qid=20091216132708AAf7o0g
+
+The city_code in this example is for Paris, France => FRXX0076
+"""
+
+from time import time
+import requests
+
+
+class Py3status:
+
+ # available configuration parameters
+ cache_timeout = 1800
+ city_code = 'FRXX0076'
+ forecast_days = 3
+ request_timeout = 10
+
+ def _get_forecast(self):
+ """
+ Ask Yahoo! Weather. for a forecast
+ """
+ q = requests.get(
+ 'http://query.yahooapis.com/v1/public/yql?q=' +
+ 'select item from weather.forecast ' +
+ 'where location="%s"&format=json' % self.city_code,
+ timeout=self.request_timeout
+ )
+
+ r = q.json()
+ status = q.status_code
+ forecasts = []
+
+ if status == 200:
+ forecasts = r['query']['results']['channel']['item']['forecast']
+ # reset today
+ forecasts[0] = r['query']['results']['channel']['item']['condition']
+ else:
+ raise Exception('got status {}'.format(status))
+
+ # return current today + forecast_days days forecast
+ return forecasts[:self.forecast_days + 1]
+
+ def _get_icon(self, forecast):
+ """
+ Return an unicode icon based on the forecast code and text
+ See: http://developer.yahoo.com/weather/#codes
+ """
+ icons = ['☀', '☁', '☂', '☃', '?']
+ code = int(forecast['code'])
+ text = forecast['text'].lower()
+
+ # sun
+ if 'sun' in text or code in [31, 32, 33, 34, 36]:
+ code = 0
+
+ # cloud / early rain
+ elif 'cloud' in text or code in [
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 44
+ ]:
+ code = 1
+
+ # rain
+ elif 'rain' in text or code in [
+ 0, 1, 2, 3, 4, 5, 6, 9,
+ 11, 12,
+ 37, 38, 39,
+ 40, 45, 47
+ ]:
+ code = 2
+
+ # snow
+ elif 'snow' in text or code in [
+ 7, 8,
+ 10, 13, 14, 15, 16, 17, 18,
+ 35,
+ 41, 42, 43, 46
+ ]:
+ code = 3
+
+ # dunno
+ else:
+ code = -1
+
+ return icons[code]
+
+ def weather_yahoo(self, json, i3status_config):
+ """
+ This method gets executed by py3status
+ """
+ response = {
+ 'cached_until': time() + self.cache_timeout,
+ 'full_text': ''
+ }
+
+ forecasts = self._get_forecast()
+ for forecast in forecasts:
+ icon = self._get_icon(forecast)
+ response['full_text'] += '{} '.format(icon)
+ response['full_text'] = response['full_text'].strip()
+
+ return response
diff --git a/py3status/modules/whoami.py b/py3status/modules/whoami.py
new file mode 100644
index 0000000..11ca9bf
--- /dev/null
+++ b/py3status/modules/whoami.py
@@ -0,0 +1,29 @@
+"""
+Simply output the currently logged in user in i3bar.
+
+Inspired by i3 FAQ:
+ https://faq.i3wm.org/question/1618/add-user-name-to-status-bar/
+"""
+
+from getpass import getuser
+from time import time
+
+
+class Py3status:
+
+ # available configuration parameters
+ cache_timeout = 1800
+
+ def whoami(self, i3status_output_json, i3status_config):
+ """
+ We use the getpass module to get the current user.
+ """
+ # here you can change the format of the output
+ # default is just to show the username
+ username = '{}'.format(getuser())
+
+ response = {
+ 'cached_until': time() + self.cache_timeout,
+ 'full_text': username
+ }
+ return response
diff --git a/py3status/modules/window-title.py b/py3status/modules/window-title.py
new file mode 100644
index 0000000..5096bd3
--- /dev/null
+++ b/py3status/modules/window-title.py
@@ -0,0 +1,60 @@
+"""
+Py3status plugin - shows current window title.
+
+Requires:
+ - i3-py (https://github.com/ziberna/i3-py)
+ # pip install i3-py
+
+If payload from server contains wierd utf-8
+(for example one window have something bad in title) - the plugin will
+give empty output UNTIL this window is closed.
+I can't fix or workaround that in PLUGIN, problem is in i3-py library.
+
+@author shadowprince
+@license Eclipse Public License
+"""
+
+import i3
+from time import time
+
+
+def find_focused(tree):
+ if type(tree) == list:
+ for el in tree:
+ res = find_focused(el)
+ if res:
+ return res
+ elif type(tree) == dict:
+ if tree['focused']:
+ return tree
+ else:
+ return find_focused(tree['nodes'] + tree['floating_nodes'])
+
+
+class Py3status:
+
+ # available configuration parameters
+ cache_timeout = 0.5
+ max_width = 120 # if width of title is greater, shrink it and add '...'
+
+ def __init__(self):
+ self.text = ''
+
+ def window_title(self, i3_status_output_json, i3status_config):
+ window = find_focused(i3.get_tree())
+
+ transformed = False
+ if window and 'name' in window and window['name'] != self.text:
+ self.text = (
+ len(window['name']) > self.max_width
+ and "..." + window['name'][-(self.max_width-3):]
+ or window['name']
+ )
+ transformed = True
+
+ response = {
+ 'cached_until': time() + self.cache_timeout,
+ 'full_text': self.text,
+ 'transformed': transformed
+ }
+ return response