Skip to content

Instantly share code, notes, and snippets.

@Agyar
Last active February 21, 2018 18:29
Show Gist options
  • Save Agyar/5679d5d9e4d76ec11ee991b18f0b6abe to your computer and use it in GitHub Desktop.
Save Agyar/5679d5d9e4d76ec11ee991b18f0b6abe to your computer and use it in GitHub Desktop.
py3status Toggle <3
# -*- coding: utf-8 -*-
"""
Display battery information.
Configuration parameters:
battery_id: id of the battery to be displayed
set to 'all' for combined display of all batteries
(default 0)
blocks: a string, where each character represents battery level
especially useful when using icon fonts (e.g. FontAwesome)
(default "_▁▂▃▄▅▆▇█")
cache_timeout: a timeout to refresh the battery state
(default 30)
charging_character: a character to represent charging battery
especially useful when using icon fonts (e.g. FontAwesome)
(default "⚡")
format: string that formats the output. See placeholders below.
(default "{icon}")
format_notify_charging: format of the notification received when you click
on the module while your computer is plugged in
(default 'Charging ({percent}%)')
format_notify_discharging: format of the notification received when you
click on the module while your computer is not plugged in
(default "{time_remaining}")
hide_seconds: hide seconds in remaining time
(default False)
hide_when_full: hide any information when battery is fully charged (when
the battery level is greater than or equal to 'threshold_full')
(default False)
measurement_mode: either 'acpi' or 'sys', or None to autodetect. 'sys'
should be more robust and does not have any extra requirements, however
the time measurement may not work in some cases
(default None)
notification: show current battery state as notification on click
(default False)
notify_low_level: display notification when battery is running low (when
the battery level is less than 'threshold_degraded')
(default False)
sys_battery_path: set the path to your battery(ies), without including its
number
(default "/sys/class/power_supply/")
threshold_bad: a percentage below which the battery level should be
considered bad
(default 10)
threshold_degraded: a percentage below which the battery level should be
considered degraded
(default 30)
threshold_full: a percentage at or above which the battery level should
should be considered full
(default 100)
Format placeholders:
{ascii_bar} - a string of ascii characters representing the battery level,
an alternative visualization to '{icon}' option
{icon} - a character representing the battery level,
as defined by the 'blocks' and 'charging_character' parameters
{percent} - the remaining battery percentage (previously '{}')
{time_remaining} - the remaining time until the battery is empty
Color options:
color_bad: Battery level is below threshold_bad
color_charging: Battery is charging (default "#FCE94F")
color_degraded: Battery level is below threshold_degraded
color_good: Battery level is above thresholds
Requires:
- the `acpi` the acpi command line utility (only if
`measurement_mode='acpi'`)
@author shadowprince, AdamBSteele, maximbaz, 4iar, m45t3r, Agyar
@license Eclipse Public License
SAMPLE OUTPUT
{'color': '#FCE94F', 'full_text': u'\u26a1'}
discharging
{'color': '#FF0000', 'full_text': u'\u2340'}
"""
from __future__ import division # python2 compatibility
from re import findall
from glob import iglob
import math
import os
BLOCKS = u"_▁▂▃▄▅▆▇█"
CHARGING_CHARACTER = u"⚡"
EMPTY_BLOCK_CHARGING = u'|'
EMPTY_BLOCK_DISCHARGING = u'⍀'
FULL_BLOCK = u'█'
FORMAT = u"{icon}"
FORMAT_NOTIFY_CHARGING = u"Charging ({percent}%)"
FORMAT_NOTIFY_DISCHARGING = u"{time_remaining}"
SYS_BATTERY_PATH = u"/sys/class/power_supply/"
MEASUREMENT_MODE = None
FULLY_CHARGED = u'?'
class Py3status:
"""
"""
# available configuration parameters
battery_id = 0
blocks = BLOCKS
cache_timeout = 30
charging_character = CHARGING_CHARACTER
format = FORMAT
format_notify_charging = FORMAT_NOTIFY_CHARGING
format_notify_discharging = FORMAT_NOTIFY_DISCHARGING
hide_seconds = False
hide_when_full = False
measurement_mode = MEASUREMENT_MODE
notification = False
notify_low_level = False
sys_battery_path = SYS_BATTERY_PATH
threshold_bad = 10
threshold_degraded = 30
threshold_full = 100
button_toggle = 1
toggle = False
class Meta:
deprecated = {
'format_fix_unnamed_param': [
{
'param': 'format',
'placeholder': 'percent',
'msg': '{} should not be used in format use `{percent}`',
},
],
'substitute_by_value': [
{
'param': 'mode',
'value': 'ascii_bar',
'substitute': {
'param': 'format',
'value': '{ascii_bar}',
},
'msg': 'obsolete parameter use `format = "{ascii_bar}"`',
},
{
'param': 'mode',
'value': 'text',
'substitute': {
'param': 'format',
'value': 'Battery: {percent}',
},
'msg': 'obsolete parameter use `format = "{percent}"`',
},
{
'param': 'show_percent_with_blocks',
'value': True,
'substitute': {
'param': 'format',
'value': '{icon} {percent}%',
},
'msg': 'obsolete parameter use `format = "{icon} {percent}%"`',
},
],
}
def post_config_hook(self):
self.last_known_status = ''
# Guess mode if not set
if self.measurement_mode is None:
if self.py3.check_commands(["acpi"]):
self.measurement_mode = "acpi"
elif os.path.isdir(self.sys_battery_path):
self.measurement_mode = "sys"
self.py3.log("Measurement mode: " + self.measurement_mode)
if self.measurement_mode != "acpi" and self.measurement_mode != "sys":
raise NameError("Invalid measurement mode")
def battery_level(self):
if not os.listdir(self.sys_battery_path):
return {
"full_text": "",
"cached_until": self.py3.time_in(self.cache_timeout)
}
self._refresh_battery_info()
self._update_icon()
self._update_ascii_bar()
self._update_full_text()
return self._build_response()
def on_click(self, event):
"""
Display a notification following the specified format
"""
button = event['button']
if button == self.button_toggle:
self.toggle = not self.toggle
if not self.notification:
return
if self.charging:
format = self.format_notify_charging
else:
format = self.format_notify_discharging
message = self.py3.safe_format(format,
dict(ascii_bar=self.ascii_bar,
icon=self.icon,
is_toggled=self.toggle,
percent=self.percent_charged,
time_remaining=self.time_remaining))
if message:
self.py3.notify_user(message, 'info')
def _extract_battery_information_from_acpi(self):
"""
Get the battery info from acpi
# Example acpi -bi raw output (Discharging):
Battery 0: Discharging, 94%, 09:23:28 remaining
Battery 0: design capacity 5703 mAh, last full capacity 5283 mAh = 92%
Battery 1: Unknown, 98%
Battery 1: design capacity 1880 mAh, last full capacity 1370 mAh = 72%
# Example Charging
Battery 0: Charging, 96%, 00:20:40 until charged
Battery 0: design capacity 5566 mAh, last full capacity 5156 mAh = 92%
Battery 1: Unknown, 98%
Battery 1: design capacity 1879 mAh, last full capacity 1370 mAh = 72%
"""
def _parse_battery_info(acpi_battery_lines):
battery = {}
battery["percent_charged"] = int(findall("(?<= )(\d+)(?=%)",
acpi_battery_lines[0])[0])
battery["charging"] = "Charging" in acpi_battery_lines[0]
battery["capacity"] = int(findall("(?<= )(\d+)(?= mAh)",
acpi_battery_lines[1])[1])
# ACPI only shows time remaining if battery is discharging or
# charging
try:
battery["time_remaining"] = ''.join(findall(
"(?<=, )(\d+:\d+:\d+)(?= remaining)|"
"(?<=, )(\d+:\d+:\d+)(?= until)", acpi_battery_lines[0])[0])
except IndexError:
battery["time_remaining"] = FULLY_CHARGED
return battery
acpi_list = self.py3.command_output(["acpi", "-b", "-i"]).splitlines()
# Separate the output because each pair of lines corresponds to a
# single battery. Now the list index will correspond to the index of
# the battery we want to look at
acpi_list = [acpi_list[i:i + 2]
for i in range(0, len(acpi_list) - 1, 2)]
return [_parse_battery_info(battery) for battery in acpi_list]
def _extract_battery_information_from_sys(self):
"""
Extract the percent charged, charging state, time remaining,
and capacity for a battery, using Linux's kernel /sys interface
Only available in kernel 2.6.24(?) and newer. Before kernel provided
a similar, yet incompatible interface in /proc
"""
def _parse_battery_info(sys_path):
"""
Extract battery information from uevent file, already convert to
int if necessary
"""
raw_values = {}
with open(os.path.join(sys_path, u"uevent")) as f:
for var in f.read().splitlines():
k, v = var.split("=")
try:
raw_values[k] = int(v)
except ValueError:
raw_values[k] = v
return raw_values
battery_list = []
for path in iglob(os.path.join(self.sys_battery_path, "BAT*")):
r = _parse_battery_info(path)
capacity = r.get("POWER_SUPPLY_ENERGY_FULL", r.get("POWER_SUPPLY_CHARGE_FULL"))
present_rate = r.get("POWER_SUPPLY_POWER_NOW", r.get("POWER_SUPPLY_CURRENT_NOW"))
remaining_energy = r.get("POWER_SUPPLY_ENERGY_NOW", r.get("POWER_SUPPLY_CHARGE_NOW"))
battery = {}
battery["capacity"] = capacity
battery["charging"] = "Charging" in r["POWER_SUPPLY_STATUS"]
battery["percent_charged"] = int(math.floor(
remaining_energy / capacity * 100))
try:
if battery["charging"]:
time_in_secs = ((capacity - remaining_energy) /
present_rate * 3600)
else:
time_in_secs = (remaining_energy / present_rate * 3600)
battery["time_remaining"] = self._seconds_to_hms(time_in_secs)
except ZeroDivisionError:
# Battery is either full charged or is not discharging
battery["time_remaining"] = FULLY_CHARGED
battery_list.append(battery)
return battery_list
def _hms_to_seconds(self, t):
h, m, s = [int(i) for i in t.split(':')]
return 3600 * h + 60 * m + s
def _seconds_to_hms(self, secs):
m, s = divmod(secs, 60)
h, m = divmod(m, 60)
return "%d:%02d:%02d" % (h, m, s)
def _refresh_battery_info(self):
if self.measurement_mode == "acpi":
battery_list = self._extract_battery_information_from_acpi()
else:
battery_list = self._extract_battery_information_from_sys()
if type(self.battery_id) == int:
battery = battery_list[self.battery_id]
self.percent_charged = battery['percent_charged']
self.charging = battery['charging']
self.time_remaining = battery['time_remaining']
elif self.battery_id == "all":
total_capacity = sum([battery['capacity'] for battery in
battery_list])
# Average and weigh % charged by the capacities of the batteries so
# that self.percent_charged properly represents batteries that have
# different capacities.
self.percent_charged = int(sum([battery[
"capacity"] / total_capacity * battery["percent_charged"]
for battery in battery_list]))
self.charging = any([battery["charging"] for battery in
battery_list])
# Assumes a system has at max two batteries
active_battery = None
inactive_battery = battery_list[:]
for battery_id in range(0, len(battery_list)):
if (battery_list[battery_id]["time_remaining"] and
battery_list[battery_id]["time_remaining"] !=
FULLY_CHARGED):
active_battery = battery_list[battery_id]
del inactive_battery[battery_id]
# Only one battery will be discharging or charging at a time.
# Therefore, ACPI does not provide a time remaining value for the
# other battery. So the time remaining for the other battery is
# calculated using the time remaining of the first battery and the
# capacity values for both batteries.
if active_battery and inactive_battery:
inactive_battery = inactive_battery[0]
time_remaining_seconds = self._hms_to_seconds(active_battery[
"time_remaining"])
try:
rate_second_per_mah = time_remaining_seconds / (
active_battery["capacity"] *
(active_battery["percent_charged"] / 100))
time_remaining_seconds += inactive_battery["capacity"] * \
inactive_battery["percent_charged"] / 100 * \
rate_second_per_mah
except ZeroDivisionError:
# Either active or inactive battery has 0% charge
time_remaining_seconds = 0
rate_second_per_mah = 0
self.time_remaining = self._seconds_to_hms(
time_remaining_seconds)
elif active_battery:
self.time_remaining = active_battery["time_remaining"]
else:
self.time_remaining = None
if self.time_remaining and self.hide_seconds:
self.time_remaining = self.time_remaining[:-3]
def _update_ascii_bar(self):
self.ascii_bar = FULL_BLOCK * int(self.percent_charged / 10)
if self.charging:
self.ascii_bar += EMPTY_BLOCK_CHARGING * (
10 - int(self.percent_charged / 10))
else:
self.ascii_bar += EMPTY_BLOCK_DISCHARGING * (
10 - int(self.percent_charged / 10))
def _update_icon(self):
if self.charging:
self.icon = self.charging_character
else:
self.icon = self.blocks[min(len(self.blocks) - 1,
int(math.ceil(self.percent_charged / 100 *
(len(self.blocks) - 1))))]
def _update_full_text(self):
self.full_text = self.py3.safe_format(
self.format,
dict(ascii_bar=self.ascii_bar,
is_toggled = self.toggle,
icon=self.icon,
percent=self.percent_charged,
time_remaining=self.time_remaining)
)
def _build_response(self):
self.response = {}
self._set_bar_text()
self._set_bar_color()
self._set_cache_timeout()
return self.response
def _set_bar_text(self):
self.response['full_text'] = '' if self.hide_when_full and \
self.percent_charged >= self.threshold_full else self.full_text
def _set_bar_color(self):
notify_msg = None
if self.charging:
self.response['color'] = self.py3.COLOR_CHARGING or "#FCE94F"
battery_status = 'charging'
elif self.percent_charged < self.threshold_bad:
self.response['color'] = self.py3.COLOR_BAD
battery_status = 'bad'
notify_msg = {'msg': 'Battery level is critically low ({}%)',
'level': 'error'}
elif self.percent_charged < self.threshold_degraded:
self.response['color'] = self.py3.COLOR_DEGRADED
battery_status = 'degraded'
notify_msg = {'msg': 'Battery level is running low ({}%)',
'level': 'warning'}
elif self.percent_charged >= self.threshold_full:
self.response['color'] = self.py3.COLOR_GOOD
battery_status = 'full'
else:
battery_status = 'good'
if (notify_msg and self.notify_low_level and
self.last_known_status != battery_status):
self.py3.notify_user(notify_msg['msg'].format(self.percent_charged),
notify_msg['level'])
self.last_known_status = battery_status
def _set_cache_timeout(self):
self.response['cached_until'] = self.py3.time_in(self.cache_timeout)
if __name__ == "__main__":
"""
Run module in test mode.
"""
from py3status.module_test import module_test
module_test(Py3status)
# -*- coding: utf-8 -*-
"""
Display song currently playing in Spotify.
Configuration parameters:
cache_timeout: how often to update the bar (default 5)
format: see placeholders below (default '{artist} : {title}')
format_down: define output if spotify is not running
(default 'Spotify not running')
format_stopped: define output if spotify is not playing
(default 'Spotify stopped')
sanitize_titles: whether to remove meta data from album/track title
(default True)
sanitize_words: which meta data to remove
(default ['bonus', 'demo', 'edit', 'explicit',
'extended', 'feat', 'mono', 'remaster',
'stereo', 'version'])
Format placeholders:
{album} album name
{artist} artiste name (first one)
{time} time duration of the song
{title} name of the song
Color options:
color_offline: Spotify is not running, defaults to color_bad
color_paused: Song is stopped or paused, defaults to color_degraded
color_playing: Song is playing, defaults to color_good
i3status.conf example:
```
spotify {
format = "{title} by {artist} -> {time}"
format_down = "no Spotify"
}
```
Requires:
spotify (>=1.0.27.71.g0a26e3b2)
@author Pierre Guilbert, Jimmy Garpehäll, sondrele, Andrwe, Agyar
SAMPLE OUTPUT
{'color': '#00FF00', 'full_text': 'Rick Astley : Never Gonna Give You Up'}
paused
{'color': '#FFFF00', 'full_text': 'Rick Astley : Never Gonna Give You Up'}
stopped
{'color': '#FF0000', 'full_text': 'Spotify stopped'}
"""
from datetime import timedelta
import dbus
import re
class Py3status:
"""
"""
# available configuration parameters
cache_timeout = 5
format = '{artist} : {title}'
format_down = 'Spotify not running'
format_stopped = 'Spotify stopped'
sanitize_titles = True
sanitize_words = [
'bonus',
'demo',
'edit',
'explicit',
'extended',
'feat',
'mono',
'remaster',
'stereo',
'version'
]
toggle = False
button_toggle = 1
def post_config_hook(self):
"""
"""
# Match string after hyphen, comma, semicolon or slash containing any metadata word
# examples:
# - Remastered 2012
# / Radio Edit
# ; Remastered
self.after_delimiter = self._compile_re(r"([\-,;/])([^\-,;/])*(META_WORDS_HERE).*")
# Match brackets with their content containing any metadata word
# examples:
# (Remastered 2017)
# [Single]
# (Bonus Track)
self.inside_brackets = self._compile_re(r"([\(\[][^)\]]*?(META_WORDS_HERE)[^)\]]*?[\)\]])")
def _compile_re(self, expression):
"""
Compile given regular expression for current sanitize words
"""
meta_words = '|'.join(self.sanitize_words)
expression = expression.replace('META_WORDS_HERE', meta_words)
return re.compile(expression, re.IGNORECASE)
def _get_text(self):
"""
Get the current song metadatas (artist - title)
"""
bus = dbus.SessionBus()
try:
self.__bus = bus.get_object('org.mpris.MediaPlayer2.spotify',
'/org/mpris/MediaPlayer2')
self.player = dbus.Interface(
self.__bus, 'org.freedesktop.DBus.Properties')
try:
metadata = self.player.Get('org.mpris.MediaPlayer2.Player',
'Metadata')
album = metadata.get('xesam:album')
artist = metadata.get('xesam:artist')[0]
microtime = metadata.get('mpris:length')
rtime = str(timedelta(microseconds=microtime))[:-7]
title = metadata.get('xesam:title')
if self.sanitize_titles:
album = self._sanitize_title(album)
title = self._sanitize_title(title)
playback_status = self.player.Get(
'org.mpris.MediaPlayer2.Player', 'PlaybackStatus'
)
if playback_status.strip() == 'Playing':
color = self.py3.COLOR_PLAYING or self.py3.COLOR_GOOD
else:
color = self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED
except Exception:
return (
self.format_stopped,
self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED)
return (
self.py3.safe_format(
self.format,
dict(title=title,
is_toggled=self.toggle,
artist=artist,
album=album,
time=rtime)
), color)
except Exception:
return (
self.format_down,
self.py3.COLOR_OFFLINE or self.py3.COLOR_BAD)
def _sanitize_title(self, title):
"""
Remove redunant meta data from title and return it
"""
title = re.sub(self.inside_brackets, "", title)
title = re.sub(self.after_delimiter, "", title)
return title.strip()
def spotify(self):
"""
Get the current "artist - title" and return it.
"""
(text, color) = self._get_text()
response = {
'cached_until': self.py3.time_in(self.cache_timeout),
'color': color,
'full_text': text
}
return response
def on_click(self, event):
button = event['button']
if button == self.button_toggle:
self.toggle = not self.toggle
if __name__ == "__main__":
"""
Run module in test mode.
"""
from py3status.module_test import module_test
module_test(Py3status)
# -*- coding: utf-8 -*-
#!/usr/bin/python3
"""
A small requests getter to check given stream status
Inputs:
<stream> channel name such as twitch.tv/<stream>
<client> client-id app key you have to create from twitch
@author Agyar
@licence Beerware
"""
import requests
DEFAULT_FORMAT = '[\?if=live ][\?if=rediff ]'
class Py3status:
format = DEFAULT_FORMAT
live = False
rediff = False
stream = ''
client = ''
cache_timeout = 600
color_live=''
color_rediff=''
button_open = 1
watcher = 'xdg-open'
def twitch(self):
r = requests.get('https://api.twitch.tv/helix/streams?user_login={0}'.format(self.stream),
headers={'Client-ID':self.client}).json()
if r['data']:
if "Rediffusion" in r['data'][0]['title']:
self.rediff = True
else:
self.live = True
full_text = self.py3.safe_format(self.format, dict( live=self.live, rediff=self.rediff))
if self.live:
color = self.color_live
elif self.rediff:
color = self.color_rediff
return { 'cache_until': self.py3.time_in(self.cache_timeout),
'full_text': full_text,
'color': color}
def on_click(self, event):
button = event['button']
if button == self.button_open:
self.py3.command_run(self.watcher +' ' + 'https://twitch.tv/{0}'.format(self.stream))
if __name__ == "__main__":
"""
Run module in test mode.
"""
from py3status.module_test import module_test
module_test(Py3status)
# -*- coding: utf-8 -*-
"""
Display WiFi bit rate, quality, signal and SSID using iw.
Configuration parameters:
bitrate_bad: Bad bit rate in Mbit/s (default 26)
bitrate_degraded: Degraded bit rate in Mbit/s (default 53)
blocks: a string, where each character represents quality level
(default "_▁▂▃▄▅▆▇█")
cache_timeout: Update interval in seconds (default 10)
device: Wireless device name (default "wlan0")
down_color: Output color when disconnected, possible values:
"good", "degraded", "bad" (default "bad")
format: Display format for this module
(default 'W: {bitrate} {signal_percent} {ssid}|W: down')
round_bitrate: If true, bit rate is rounded to the nearest whole number
(default True)
signal_bad: Bad signal strength in percent (default 29)
signal_degraded: Degraded signal strength in percent (default 49)
use_sudo: Use sudo to run iw, make sure iw requires some root rights
without a password by adding a sudoers entry, eg...
'<user> ALL=(ALL) NOPASSWD:/usr/bin/iw dev,/usr/bin/iw dev [a-z]* link'
(default False)
Format placeholders:
{bitrate} Display bit rate
{device} Display device name
{icon} Character representing the quality based on bitrate,
as defined by the 'blocks'
{ip} Display IP address
{signal_dbm} Display signal in dBm
{signal_percent} Display signal in percent
{ssid} Display SSID
Color options:
color_bad: Signal strength signal_bad or lower
color_degraded: Signal strength signal_degraded or lower
color_good: Signal strength above signal_degraded
Requires:
iw: cli configuration utility for wireless devices
ip: only for {ip}. may be part of iproute2: ip routing utilities
__Note: Some distributions eg Debian require `iw` to be run with privileges.
In this case you will need to use the `use_sudo` configuration parameter.__
@author Markus Weimar, Agyar
@license BSD
SAMPLE OUTPUT
{'color': '#00FF00', 'full_text': u'W: 54.0 MBit/s 100% Chicken Remixed'}
"""
import re
import math
STRING_ERROR = "iw: command failed"
DEFAULT_FORMAT = 'W: up [\?if=is_toggled {signal_percent} {ssid}]|W: down'
class Py3status:
"""
"""
# available configuration parameters
bitrate_bad = 26
bitrate_degraded = 53
blocks = u"_▁▂▃▄▅▆▇█"
cache_timeout = 10
device = 'wlan0'
down_color = 'bad'
format = DEFAULT_FORMAT
round_bitrate = True
signal_bad = 29
signal_degraded = 49
use_sudo = False
toggle = False
button_toggle = 1
def post_config_hook(self):
self._max_bitrate = 0
self._ssid = ''
self.iw_cmd = self.py3.check_commands(['iw', '/sbin/iw'])
# Try and guess the wifi interface
cmd = [self.iw_cmd, 'dev']
if self.use_sudo:
cmd.insert(0, 'sudo')
try:
iw = self.py3.command_output(cmd)
devices = re.findall('Interface\s*([^\s]+)', iw)
if not devices or 'wlan0' in devices:
self.device = 'wlan0'
else:
self.device = devices[0]
except:
pass
# DEPRECATION WARNING
format_down = getattr(self, 'format_down', None)
format_up = getattr(self, 'format_up', None)
if self.format != DEFAULT_FORMAT:
return
if format_up or format_down:
self.format = u'{}|{}'.format(
format_up or 'W: {bitrate} {signal_percent} {ssid}',
format_down or 'W: down',
)
msg = 'DEPRECATION WARNING: you are using old style configuration '
msg += 'parameters you should update to use the new format.'
self.py3.log(msg)
def wifi(self):
"""
Get WiFi status using iw.
"""
self.signal_dbm_bad = self._percent_to_dbm(self.signal_bad)
self.signal_dbm_degraded = self._percent_to_dbm(self.signal_degraded)
cmd = [self.iw_cmd, 'dev', self.device, 'link']
if self.use_sudo:
cmd.insert(0, 'sudo')
try:
iw = self.py3.command_output(cmd)
except:
return {'cache_until': self.py3.CACHE_FOREVER,
'color': self.py3.COLOR_ERROR or self.py3.COLOR_BAD,
'full_text': STRING_ERROR}
# bitrate
bitrate_out = re.search('tx bitrate: ([^\s]+) ([^\s]+)', iw)
if bitrate_out:
bitrate = float(bitrate_out.group(1))
if self.round_bitrate:
bitrate = round(bitrate)
bitrate_unit = bitrate_out.group(2)
if bitrate_unit == 'Gbit/s':
bitrate *= 1000
else:
bitrate = None
bitrate_unit = None
# signal
signal_out = re.search('signal: ([\-0-9]+)', iw)
if signal_out:
signal_dbm = int(signal_out.group(1))
signal_percent = min(self._dbm_to_percent(signal_dbm), 100)
else:
signal_dbm = None
signal_percent = None
ssid_out = re.search('SSID: (.+)', iw)
if ssid_out:
ssid = ssid_out.group(1)
# `iw` command would prints unicode SSID like `\xe8\x8b\x9f`
# the the `ssid` here would be '\\xe8\\x8b\\x9f' (note the escape)
# it needs to be decoded using 'unicode_escape', to '苟'
ssid = ssid.encode('latin-1').decode('unicode_escape')
ssid = ssid.encode('latin-1').decode('utf-8')
else:
ssid = None
# check command
if self.py3.format_contains(self.format, 'ip'):
cmd = ['ip', 'addr', 'list', self.device]
if self.use_sudo:
cmd.insert(0, 'sudo')
ip_info = self.py3.command_output(cmd)
ip_match = re.search('inet\s+([0-9.]+)', ip_info)
if ip_match:
ip = ip_match.group(1)
else:
ip = None
else:
ip = ''
# reset _max_bitrate if we have changed network
if self._ssid != ssid:
self._ssid = ssid
self._max_bitrate = self.bitrate_degraded
if bitrate:
if bitrate > self._max_bitrate:
self._max_bitrate = bitrate
quality = int((bitrate / self._max_bitrate) * 100)
else:
quality = 0
icon = self.blocks[int(math.ceil(quality / 100.0 * (len(self.blocks) - 1)))]
# wifi down
if ssid is None:
color = getattr(self.py3, 'COLOR_{}'.format(self.down_color.upper()))
full_text = self.py3.safe_format(self.format)
# wifi up
else:
color = self.py3.COLOR_GOOD
if bitrate:
if bitrate <= self.bitrate_bad:
color = self.py3.COLOR_BAD
elif bitrate <= self.bitrate_degraded:
color = self.py3.COLOR_DEGRADED
bitrate = '{} {}'.format(bitrate, bitrate_unit)
else:
bitrate = '? MBit/s'
if signal_dbm:
if signal_dbm <= self.signal_dbm_bad:
color = self.py3.COLOR_BAD
elif signal_dbm <= self.signal_dbm_degraded:
color = self.py3.COLOR_DEGRADED
signal_dbm = '{} dBm'.format(signal_dbm)
signal_percent = '{}%'.format(signal_percent)
else:
signal_dbm = '? dBm'
signal_percent = '?%'
full_text = self.py3.safe_format(
self.format,
dict(
is_toggled=self.toggle,
bitrate=bitrate,
device=self.device,
icon=icon,
ip=ip,
signal_dbm=signal_dbm,
signal_percent=signal_percent,
ssid=ssid,
))
return {
'cache_until': self.py3.time_in(self.cache_timeout),
'full_text': full_text,
'color': color,
}
def _dbm_to_percent(self, dbm):
return 2 * (dbm + 100)
def _percent_to_dbm(self, percent):
return (percent / 2) - 100
def on_click(self, event):
button = event['button']
if button == self.button_toggle:
self.toggle = not self.toggle
if __name__ == "__main__":
"""
Run module in test mode.
"""
from py3status.module_test import module_test
module_test(Py3status)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment