From d84b1675a96fbc0cff8c2d11a6c8922da14860f2 Mon Sep 17 00:00:00 2001 From: facetoe Date: Sat, 3 Jan 2015 13:50:54 +0800 Subject: [PATCH 1/5] Centralize network related functionality under one module. --- i3pystatus/network.py | 446 +++++++++++++++++++++++----------- i3pystatus/network_graph.py | 69 ------ i3pystatus/network_traffic.py | 94 ------- i3pystatus/wireless.py | 42 ---- 4 files changed, 306 insertions(+), 345 deletions(-) delete mode 100644 i3pystatus/network_graph.py delete mode 100644 i3pystatus/network_traffic.py delete mode 100644 i3pystatus/wireless.py diff --git a/i3pystatus/network.py b/i3pystatus/network.py index ca35ae0..9d412cd 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -1,79 +1,232 @@ -from itertools import zip_longest +# -*- coding: utf-8 -*- import netifaces - +import basiciw +import psutil +from itertools import zip_longest from i3pystatus import IntervalModule - -# Reminder: if we raise minimum Python version to 3.3, use ipaddress module +from i3pystatus.core.color import ColorRangeModule +from i3pystatus.core.util import make_graph, round_dict, make_bar -def count_bits(integer): - bits = 0 - while (integer): - integer &= integer - 1 - bits += 1 - return bits +class NetUtil(object): + @staticmethod + def count_bits(integer): + bits = 0 + while (integer): + integer &= integer - 1 + bits += 1 + return bits + + @staticmethod + def v6_to_int(v6): + return int(v6.replace(":", ""), 16) + + @staticmethod + def prefix6(mask): + return NetUtil.count_bits(NetUtil.v6_to_int(mask)) + + @staticmethod + def cidr6(addr, mask): + return "{addr}/{bits}".format(addr=addr, bits=NetUtil.prefix6(mask)) + + @staticmethod + def v4_to_int(v4): + sum = 0 + mul = 1 + for part in reversed(v4.split(".")): + sum += int(part) * mul + mul *= 2 ** 8 + return sum + + @staticmethod + def prefix4(mask): + return NetUtil.count_bits(NetUtil.v4_to_int(mask)) + + @staticmethod + def cidr4(addr, mask): + return "{addr}/{bits}".format(addr=addr, bits=NetUtil.prefix4(mask)) + + @staticmethod + def get_bonded_slaves(): + try: + with open("/sys/class/net/bonding_masters") as f: + masters = f.read().split() + except FileNotFoundError: + return {} + slaves = {} + for master in masters: + with open("/sys/class/net/{}/bonding/slaves".format(master)) as f: + for slave in f.read().split(): + slaves[slave] = master + return slaves + + @staticmethod + def sysfs_interface_up(interface, unknown_up=False): + try: + with open("/sys/class/net/{}/operstate".format(interface)) as f: + status = f.read().strip() + except FileNotFoundError: + # Interface doesn't exist + return False + + return status == "up" or unknown_up and status == "unknown" -def v6_to_int(v6): - return int(v6.replace(":", ""), 16) - - -def prefix6(mask): - return count_bits(v6_to_int(mask)) - - -def cidr6(addr, mask): - return "{addr}/{bits}".format(addr=addr, bits=prefix6(mask)) - - -def v4_to_int(v4): - sum = 0 - mul = 1 - for part in reversed(v4.split(".")): - sum += int(part) * mul - mul *= 2 ** 8 - return sum - - -def prefix4(mask): - return count_bits(v4_to_int(mask)) - - -def cidr4(addr, mask): - return "{addr}/{bits}".format(addr=addr, bits=prefix4(mask)) - - -def get_bonded_slaves(): - try: - with open("/sys/class/net/bonding_masters") as f: - masters = f.read().split() - except FileNotFoundError: - return {} - slaves = {} - for master in masters: - with open("/sys/class/net/{}/bonding/slaves".format(master)) as f: - for slave in f.read().split(): - slaves[slave] = master - return slaves - - -def sysfs_interface_up(interface, unknown_up=False): - try: - with open("/sys/class/net/{}/operstate".format(interface)) as f: - status = f.read().strip() - except FileNotFoundError: - raise RuntimeError("Unknown interface {iface}!".format(iface=interface)) - return status == "up" or unknown_up and status == "unknown" - - -class Network(IntervalModule): +class NetworkInfo(object): + """ + Retrieve network information. """ - Display network information about a interface. - Requires the PyPI package `netifaces`. + def __init__(self, interface, ignore_interfaces, detached_down, unknown_up): + if interface not in netifaces.interfaces() and not detached_down: + raise RuntimeError( + "Unknown interface {iface}!".format(iface=interface)) + + self.ignore_interfaces = ignore_interfaces + self.detached_down = detached_down + self.unknown_up = unknown_up + + def get_info(self, interface): + format_dict = dict( + zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue="")) + + iface_up = NetUtil.sysfs_interface_up(interface, self.unknown_up) + if not iface_up: + return format_dict + + network_info = netifaces.ifaddresses(interface) + slaves = NetUtil.get_bonded_slaves() + try: + master = slaves[interface] + except KeyError: + pass + else: + if NetUtil.sysfs_interface_up(interface, self.unknown_up): + master_info = netifaces.ifaddresses(master) + for af in (netifaces.AF_INET, netifaces.AF_INET6): + try: + network_info[af] = master_info[af] + except KeyError: + pass + + try: + mac = network_info[netifaces.AF_PACKET][0]["addr"] + except KeyError: + mac = "NONE" + format_dict['mac'] = mac + + if iface_up: + format_dict.update(self.extract_network_info(network_info)) + format_dict.update(self.extract_wireless_info(interface)) + + return format_dict + + @staticmethod + def extract_network_info(network_info): + info = dict() + if netifaces.AF_INET in network_info: + v4 = network_info[netifaces.AF_INET][0] + info["v4"] = v4["addr"] + info["v4mask"] = v4["netmask"] + info["v4cidr"] = NetUtil.cidr4(v4["addr"], v4["netmask"]) + if netifaces.AF_INET6 in network_info: + for v6 in network_info[netifaces.AF_INET6]: + info["v6"] = v6["addr"] + info["v6mask"] = v6["netmask"] + info["v6cidr"] = NetUtil.cidr6(v6["addr"], v6["netmask"]) + if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses + break + return info + + @staticmethod + def extract_wireless_info(interface): + try: + iwi = basiciw.iwinfo(interface) + except Exception: + # Not a wireless interface + return dict(essid="", freq="", quality=0.0, quality_bar="") + + info = dict() + info["essid"] = iwi["essid"] + info["freq"] = iwi["freq"] + quality = iwi["quality"] + if quality["quality_max"] > 0: + info["quality"] = quality["quality"] / quality["quality_max"] + else: + info["quality"] = quality["quality"] + info["quality"] *= 100 + info["quality_bar"] = make_bar(info["quality"]) + + return info + + +class NetworkTraffic(object): + """ + Retrieve network traffic information + """ + + pnic = None + pnic_before = None + + def __init__(self, unknown_up, divisor, round_size): + self.unknown_up = unknown_up + self.divisor = divisor + self.round_size = round_size + + def update_counters(self, interface): + self.pnic_before = self.pnic + counters = psutil.net_io_counters(pernic=True) + self.pnic = counters[interface] if interface in counters else None + + def clear_counters(self): + self.pnic_before = None + self.pnic = None + + def get_bytes_sent(self): + return (self.pnic.bytes_sent - self.pnic_before.bytes_sent) / self.divisor + + def get_bytes_received(self): + return (self.pnic.bytes_recv - self.pnic_before.bytes_recv) / self.divisor + + def get_packets_sent(self): + return self.pnic.packets_sent - self.pnic_before.packets_sent + + def get_packets_received(self): + return self.pnic.packets_recv - self.pnic_before.packets_recv + + def get_usage(self, interface): + self.update_counters(interface) + usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) + + if not NetUtil.sysfs_interface_up(interface, self.unknown_up) or not self.pnic_before: + return usage + else: + usage["bytes_sent"] = self.get_bytes_sent() + usage["bytes_recv"] = self.get_bytes_received() + usage["packets_sent"] = self.get_packets_sent() + usage["packets_recv"] = self.get_packets_received() + round_dict(usage, self.round_size) + return usage + + +class Network(IntervalModule, ColorRangeModule): + """ + Displays network information for an interface. + + Requires the PyPI packages `psutil`, `colour`, `netifaces` and `basiciw` .. rubric:: Available formatters + Network Traffic Formatters: + * `{interface}` — the configured network interface + * `{kbs}` – Float representing kb\s + * `{network_graph}` – Unicode graph representing network usage + * `{bytes_sent}` — bytes sent per second (divided by divisor) + * `{bytes_recv}` — bytes received per second (divided by divisor) + * `{packets_sent}` — bytes sent per second (divided by divisor) + * `{packets_recv}` — bytes received per second (divided by divisor) + + Network Information Formatters: * `{interface}` — same as setting * `{name}` — same as setting * `{v4}` — IPv4 address @@ -84,91 +237,65 @@ class Network(IntervalModule): * `{v6cidr}` — IPv6 address in cidr notation * `{mac}` — MAC of interface - Not available addresses (i.e. no IPv6 connectivity) are replaced with empty strings. + Wireless Information Formatters: + * `{essid}` — ESSID of currently connected wifi + * `{freq}` — Current frequency + * `{quality}` — Link quality in percent + * `{quality_bar}` —Bar graphically representing link quality """ - settings = ( - ("interface", "Interface to obtain information for"), + ("format_up", "format string"), + ("format_down", "format string"), + "color_up", + "color_down", + ("interface", "Interface to watch, eg 'eth0'"), + ("dynamic_color", "Set color dynamically based on network traffic. Note: this overrides color_up"), + ("start_color", "Hex or English name for start of color range, eg '#00FF00' or 'green'"), + ("end_color", "Hex or English name for end of color range, eg '#FF0000' or 'red'"), + ("graph_width", "Width of the network traffic graph"), + ("upper_limit", + "Expected max kb/s. This value controls how the network traffic graph is drawn and in what color"), + ("graph_type", "Whether to draw the network traffic graph for input or output. " + "Allowed values 'input' or 'output'"), + ("divisor", "divide all byte values by this value"), ("ignore_interfaces", "Array of interfaces to ignore when cycling through " - "with right click. Eg, 'lo'"), - "format_up", "color_up", - "format_down", "color_down", + "on click, eg, ['lo']"), + ("round_size", "defines number of digits in round"), ("detached_down", "If the interface doesn't exist, display it as if it were down"), ("unknown_up", "If the interface is in unknown state, display it as if it were up"), "name", ) - name = interface = "eth0" - ignore_interfaces = ["lo"] - format_up = "{interface}: {v4}" - format_down = "{interface}" + interval = 1 + interface = 'eth0' + + format_up = "{interface} {network_graph}{kbs}KB/s" + format_down = "{interface}: DOWN" color_up = "#00FF00" color_down = "#FF0000" + dynamic_color = True + graph_type = 'input' + graph_width = 15 + upper_limit = 150.0 + + # Network traffic settings + divisor = 1024 + round_size = None + + # Network info settings detached_down = True unknown_up = False + ignore_interfaces = ["lo"] + on_leftclick = "nm-connection-editor" on_rightclick = "cycle_interface" - interval = 1 def init(self): - if self.interface not in netifaces.interfaces() and not self.detached_down: - raise RuntimeError( - "Unknown interface {iface}!".format(iface=self.interface)) + self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size) + self.network_info = NetworkInfo(self.interface, self.ignore_interfaces, self.detached_down, self.unknown_up) - def collect(self): - if self.interface not in netifaces.interfaces() and self.detached_down: - self.format = self.format_down - color = self.color_down - return self.color_down, self.format_down, {"interface": self.interface, "name": self.name}, False - - info = netifaces.ifaddresses(self.interface) - slaves = get_bonded_slaves() - try: - master = slaves[self.interface] - except KeyError: - pass - else: - if sysfs_interface_up(self.interface, self.unknown_up): - master_info = netifaces.ifaddresses(master) - for af in (netifaces.AF_INET, netifaces.AF_INET6): - try: - info[af] = master_info[af] - except KeyError: - pass - up = sysfs_interface_up(self.interface, self.unknown_up) - fdict = dict( - zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue="")) - - try: - mac = info[netifaces.AF_PACKET][0]["addr"] - except KeyError: - mac = "NONE" - fdict.update({ - "interface": self.interface, - "name": self.name, - "mac": mac, - }) - - if up: - format = self.format_up - color = self.color_up - if netifaces.AF_INET in info: - v4 = info[netifaces.AF_INET][0] - fdict["v4"] = v4["addr"] - fdict["v4mask"] = v4["netmask"] - fdict["v4cidr"] = cidr4(v4["addr"], v4["netmask"]) - if netifaces.AF_INET6 in info: - for v6 in info[netifaces.AF_INET6]: - fdict["v6"] = v6["addr"] - fdict["v6mask"] = v6["netmask"] - fdict["v6cidr"] = cidr6(v6["addr"], v6["netmask"]) - if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses - break - else: - format = self.format_down - color = self.color_down - - return color, format, fdict, up + self.colors = self.get_hex_color_range(self.start_color, self.end_color, int(self.upper_limit)) + self.kbs_arr = [0.0] * self.graph_width def cycle_interface(self): interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces] @@ -178,11 +305,50 @@ class Network(IntervalModule): elif len(interfaces) > 0: self.interface = interfaces[0] - def run(self): - color, format, fdict, up = self.collect() + self.network_traffic.clear_counters() + self.kbs_arr = [0.0] * self.graph_width - self.output = { - "full_text": format.format(**fdict), - "color": color, - "instance": self.interface - } + def get_network_graph(self, kbs): + # Cycle array by inserting at the start and chopping off the last element + self.kbs_arr.insert(0, kbs) + self.kbs_arr = self.kbs_arr[:self.graph_width] + return make_graph(self.kbs_arr, self.upper_limit) + + def run(self): + format_values = dict(kbs="", network_graph="", bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", + interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="", + essid="", freq="", quality="", quality_bar="") + + network_usage = self.network_traffic.get_usage(self.interface) + format_values.update(network_usage) + + network_info = self.network_info.get_info(self.interface) + format_values.update(network_info) + + if self.graph_type == 'input': + kbs = network_usage['bytes_recv'] + elif self.graph_type == 'output': + kbs = network_usage['bytes_sent'] + else: + raise Exception("graph_type must be either 'input' or 'output'!") + + format_values['interface'] = self.interface + format_values['network_graph'] = self.get_network_graph(kbs) + format_values['kbs'] = "{0:.1f}".format(round(kbs, 2)).rjust(6) + + if NetUtil.sysfs_interface_up(self.interface, self.unknown_up): + if self.dynamic_color: + color = self.get_gradient(kbs, self.colors, self.upper_limit) + else: + color = self.color_up + + self.output = { + "full_text": self.format_up.format(**format_values), + 'color': color, + } + else: + color = self.color_down + self.output = { + "full_text": self.format_down.format(**format_values), + 'color': color, + } diff --git a/i3pystatus/network_graph.py b/i3pystatus/network_graph.py deleted file mode 100644 index 54925d6..0000000 --- a/i3pystatus/network_graph.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -from i3pystatus.core.color import ColorRangeModule -from i3pystatus.network_traffic import NetworkTraffic -from i3pystatus.core.util import make_graph - - -class NetworkGraph(NetworkTraffic, ColorRangeModule): - """ - Shows Network activity as a Unicode graph - - Linux only - - Requires the PyPI packages `psutil` and `colour`. - - .. rubric:: Available formatters - - * {kbs} – Float representing kb\s - * {network_graph} – Unicode network graph - - """ - settings = ( - ("format", "format string"), - ("graph_width", "Width of the graph"), - ("upper_limit", "Expected max kb/s. This value controls how the graph is drawn and in what color"), - ("graph_type", "Whether to draw the graph for input or output. " - "Allowed values 'input' or 'output'"), - ("divisor", "divide all byte values by this value"), - ("interface", "Interface to watch, eg 'eth0'"), - ("start_color", "Hex or English name for start of color range, eg '#00FF00' or 'green'"), - ("end_color", "Hex or English name for end of color range, eg '#FF0000' or 'red'") - ) - - format = "{network_graph}{kbs}KB/s" - graph_type = 'input' - - interval = 1 - graph_width = 15 - upper_limit = 150.0 - - def init(self): - self.colors = self.get_hex_color_range(self.start_color, self.end_color, int(self.upper_limit)) - self.kbs_arr = [0.0] * self.graph_width - - def run(self): - self.update_counters() - if not self.pnic_before: - return - - if self.graph_type == 'input': - kbs = self.get_bytes_received() - elif self.graph_type == 'output': - kbs = self.get_bytes_sent() - else: - raise Exception("graph_type must be either 'input' or 'output'!") - - # Cycle array by inserting at the start and chopping off the last element - self.kbs_arr.insert(0, kbs) - self.kbs_arr = self.kbs_arr[:self.graph_width] - - color = self.get_gradient(kbs, self.colors, self.upper_limit) - network_graph = make_graph(self.kbs_arr, self.upper_limit) - - self.output = { - "full_text": self.format.format( - network_graph=network_graph, - kbs="{0:.1f}".format(round(kbs, 2)).rjust(6) - ), - 'color': color, - } diff --git a/i3pystatus/network_traffic.py b/i3pystatus/network_traffic.py deleted file mode 100644 index f8da5e1..0000000 --- a/i3pystatus/network_traffic.py +++ /dev/null @@ -1,94 +0,0 @@ -from . import IntervalModule -from .core.util import round_dict -import psutil - - -class NetworkTraffic(IntervalModule): - """ - Network traffic per interface, i.e., packets/bytes sent/received per second. - - Requires the PyPI packages `psutil`. - - .. rubric:: Available formatters - - * `{interface}` — the configured network interface - * `{bytes_sent}` — bytes sent per second (divided by divisor) - * `{bytes_recv}` — bytes received per second (divided by divisor) - * `{packets_sent}` — bytes sent per second (divided by divisor) - * `{packets_recv}` — bytes received per second (divided by divisor) - """ - - interval = 1 - settings = ( - ("format", "format string"), - ("format_down", "format string if the interface is down (unless hide_down is set)"), - ("hide_down", "whether to not display a interface which is down"), - ("interface", "network interface"), - ("divisor", "divide all byte values by this value"), - ("round_size", "defines number of digits in round"), - ) - - format = "{interface} \u2197{bytes_sent}kB/s \u2198{bytes_recv}kB/s" - format_down = "{interface} \u2013" - hide_down = False - interface = "eth0" - divisor = 1024 - round_size = None - - pnic = None - pnic_before = None - - def update_counters(self): - self.pnic_before = self.pnic - counters = psutil.net_io_counters(pernic=True) - self.pnic = counters[self.interface] if self.interface in counters else None - - def get_bytes_sent(self): - return (self.pnic.bytes_sent - self.pnic_before.bytes_sent) / self.divisor - - def get_bytes_received(self): - return (self.pnic.bytes_recv - self.pnic_before.bytes_recv) / self.divisor - - def get_packets_sent(self): - return self.pnic.packets_sent - self.pnic_before.packets_sent - - def get_packets_received(self): - return self.pnic.packets_recv - self.pnic_before.packets_recv - - def sysfs_interface_up(self): - try: - sysfs = "/sys/class/net/{}/operstate".format(self.interface) - with open(sysfs) as operstate: - status = operstate.read().strip() - return status == "up" or status == "unknown" - except FileNotFoundError: - return False - - def run(self): - self.update_counters() - if self.sysfs_interface_up(): - if not self.pnic_before: - return - cdict = { - "bytes_sent": self.get_bytes_sent(), - "bytes_recv": self.get_bytes_received(), - "packets_sent": self.get_packets_sent(), - "packets_recv": self.get_packets_received(), - } - round_dict(cdict, self.round_size) - cdict["interface"] = self.interface - self.output = { - "full_text": self.format.format(**cdict), - "instance": self.interface, - } - elif self.hide_down: - self.output = None - return - else: - cdict = { - "interface": self.interface, - } - self.output = { - "full_text": self.format_down.format(**cdict), - "instance": self.interface, - } diff --git a/i3pystatus/wireless.py b/i3pystatus/wireless.py deleted file mode 100644 index 76a580b..0000000 --- a/i3pystatus/wireless.py +++ /dev/null @@ -1,42 +0,0 @@ -import basiciw - -from i3pystatus.core.util import make_bar -from i3pystatus.network import Network - - -class Wireless(Network): - """ - Display network information about a interface. - - Requires the PyPI packages `netifaces` and `basiciw`. - - This is based on the network module, so all options and formatters are - the same, except for these additional formatters and that detached_down doesn't work. - - * `{essid}` — ESSID of currently connected wifi - * `{freq}` — Current frequency - * `{quality}` — Link quality in percent - * `{quality_bar}` —Bar graphically representing link quality - """ - - interface = "wlan0" - - def collect(self): - color, format, fdict, up = super().collect() - - if up: - iwi = basiciw.iwinfo(self.interface) - fdict["essid"] = iwi["essid"] - fdict["freq"] = iwi["freq"] - quality = iwi["quality"] - if quality["quality_max"] > 0: - fdict["quality"] = quality["quality"] / quality["quality_max"] - else: - fdict["quality"] = quality["quality"] - fdict["quality"] *= 100 - fdict["quality_bar"] = make_bar(fdict["quality"]) - else: - fdict["essid"] = "" - fdict["freq"] = fdict["quality"] = 0.0 - - return color, format, fdict, up From a9116fa592a4d8164fec5b47625d8fcd909ba54f Mon Sep 17 00:00:00 2001 From: facetoe Date: Sat, 3 Jan 2015 17:34:11 +0800 Subject: [PATCH 2/5] Update docs --- docs/i3pystatus.rst | 2 +- i3pystatus/network.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/i3pystatus.rst b/docs/i3pystatus.rst index ec24356..80637a5 100644 --- a/docs/i3pystatus.rst +++ b/docs/i3pystatus.rst @@ -7,7 +7,7 @@ Module reference :System: `clock`_ - `disk`_ - `load`_ - `mem`_ - `cpu_usage`_ :Audio: `alsa`_ - `pulseaudio`_ :Hardware: `battery`_ - `backlight`_ - `temp`_ -:Network: `network`_ - `wireless`_ +:Network: `network`_ :Music: `now_playing`_ - `mpd`_ :Websites & stuff: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_ :Other: `mail`_ - `pyload`_ - `text`_ diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 9d412cd..5c22bd2 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -228,7 +228,6 @@ class Network(IntervalModule, ColorRangeModule): Network Information Formatters: * `{interface}` — same as setting - * `{name}` — same as setting * `{v4}` — IPv4 address * `{v4mask}` — subnet mask * `{v4cidr}` — IPv4 address in cidr notation (i.e. 192.168.2.204/24) @@ -263,7 +262,6 @@ class Network(IntervalModule, ColorRangeModule): ("round_size", "defines number of digits in round"), ("detached_down", "If the interface doesn't exist, display it as if it were down"), ("unknown_up", "If the interface is in unknown state, display it as if it were up"), - "name", ) interval = 1 From 8425db8d8dbaa9971ed00859e8af195b037d19b7 Mon Sep 17 00:00:00 2001 From: facetoe Date: Sun, 4 Jan 2015 10:46:15 +0800 Subject: [PATCH 3/5] Remove NetUtil class, don't inherit from object, slight refactoring. --- i3pystatus/network.py | 151 ++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 5c22bd2..cca478a 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -2,77 +2,74 @@ import netifaces import basiciw import psutil -from itertools import zip_longest from i3pystatus import IntervalModule from i3pystatus.core.color import ColorRangeModule from i3pystatus.core.util import make_graph, round_dict, make_bar -class NetUtil(object): - @staticmethod - def count_bits(integer): - bits = 0 - while (integer): - integer &= integer - 1 - bits += 1 - return bits - - @staticmethod - def v6_to_int(v6): - return int(v6.replace(":", ""), 16) - - @staticmethod - def prefix6(mask): - return NetUtil.count_bits(NetUtil.v6_to_int(mask)) - - @staticmethod - def cidr6(addr, mask): - return "{addr}/{bits}".format(addr=addr, bits=NetUtil.prefix6(mask)) - - @staticmethod - def v4_to_int(v4): - sum = 0 - mul = 1 - for part in reversed(v4.split(".")): - sum += int(part) * mul - mul *= 2 ** 8 - return sum - - @staticmethod - def prefix4(mask): - return NetUtil.count_bits(NetUtil.v4_to_int(mask)) - - @staticmethod - def cidr4(addr, mask): - return "{addr}/{bits}".format(addr=addr, bits=NetUtil.prefix4(mask)) - - @staticmethod - def get_bonded_slaves(): - try: - with open("/sys/class/net/bonding_masters") as f: - masters = f.read().split() - except FileNotFoundError: - return {} - slaves = {} - for master in masters: - with open("/sys/class/net/{}/bonding/slaves".format(master)) as f: - for slave in f.read().split(): - slaves[slave] = master - return slaves - - @staticmethod - def sysfs_interface_up(interface, unknown_up=False): - try: - with open("/sys/class/net/{}/operstate".format(interface)) as f: - status = f.read().strip() - except FileNotFoundError: - # Interface doesn't exist - return False - - return status == "up" or unknown_up and status == "unknown" +def count_bits(integer): + bits = 0 + while (integer): + integer &= integer - 1 + bits += 1 + return bits -class NetworkInfo(object): +def v6_to_int(v6): + return int(v6.replace(":", ""), 16) + + +def prefix6(mask): + return count_bits(v6_to_int(mask)) + + +def cidr6(addr, mask): + return "{addr}/{bits}".format(addr=addr, bits=prefix6(mask)) + + +def v4_to_int(v4): + sum = 0 + mul = 1 + for part in reversed(v4.split(".")): + sum += int(part) * mul + mul *= 2 ** 8 + return sum + + +def prefix4(mask): + return count_bits(v4_to_int(mask)) + + +def cidr4(addr, mask): + return "{addr}/{bits}".format(addr=addr, bits=prefix4(mask)) + + +def get_bonded_slaves(): + try: + with open("/sys/class/net/bonding_masters") as f: + masters = f.read().split() + except FileNotFoundError: + return {} + slaves = {} + for master in masters: + with open("/sys/class/net/{}/bonding/slaves".format(master)) as f: + for slave in f.read().split(): + slaves[slave] = master + return slaves + + +def sysfs_interface_up(interface, unknown_up=False): + try: + with open("/sys/class/net/{}/operstate".format(interface)) as f: + status = f.read().strip() + except FileNotFoundError: + # Interface doesn't exist + return False + + return status == "up" or unknown_up and status == "unknown" + + +class NetworkInfo(): """ Retrieve network information. """ @@ -87,21 +84,19 @@ class NetworkInfo(object): self.unknown_up = unknown_up def get_info(self, interface): - format_dict = dict( - zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue="")) - - iface_up = NetUtil.sysfs_interface_up(interface, self.unknown_up) + format_dict = dict(v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="") + iface_up = sysfs_interface_up(interface, self.unknown_up) if not iface_up: return format_dict network_info = netifaces.ifaddresses(interface) - slaves = NetUtil.get_bonded_slaves() + slaves = get_bonded_slaves() try: master = slaves[interface] except KeyError: pass else: - if NetUtil.sysfs_interface_up(interface, self.unknown_up): + if sysfs_interface_up(interface, self.unknown_up): master_info = netifaces.ifaddresses(master) for af in (netifaces.AF_INET, netifaces.AF_INET6): try: @@ -128,25 +123,25 @@ class NetworkInfo(object): v4 = network_info[netifaces.AF_INET][0] info["v4"] = v4["addr"] info["v4mask"] = v4["netmask"] - info["v4cidr"] = NetUtil.cidr4(v4["addr"], v4["netmask"]) + info["v4cidr"] = cidr4(v4["addr"], v4["netmask"]) if netifaces.AF_INET6 in network_info: for v6 in network_info[netifaces.AF_INET6]: info["v6"] = v6["addr"] info["v6mask"] = v6["netmask"] - info["v6cidr"] = NetUtil.cidr6(v6["addr"], v6["netmask"]) + info["v6cidr"] = cidr6(v6["addr"], v6["netmask"]) if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses break return info @staticmethod def extract_wireless_info(interface): + info = dict(essid="", freq="", quality=0.0, quality_bar="") try: iwi = basiciw.iwinfo(interface) except Exception: # Not a wireless interface - return dict(essid="", freq="", quality=0.0, quality_bar="") + return info - info = dict() info["essid"] = iwi["essid"] info["freq"] = iwi["freq"] quality = iwi["quality"] @@ -160,7 +155,7 @@ class NetworkInfo(object): return info -class NetworkTraffic(object): +class NetworkTraffic(): """ Retrieve network traffic information """ @@ -198,7 +193,7 @@ class NetworkTraffic(object): self.update_counters(interface) usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) - if not NetUtil.sysfs_interface_up(interface, self.unknown_up) or not self.pnic_before: + if not sysfs_interface_up(interface, self.unknown_up) or not self.pnic_before: return usage else: usage["bytes_sent"] = self.get_bytes_sent() @@ -330,11 +325,11 @@ class Network(IntervalModule, ColorRangeModule): else: raise Exception("graph_type must be either 'input' or 'output'!") - format_values['interface'] = self.interface format_values['network_graph'] = self.get_network_graph(kbs) format_values['kbs'] = "{0:.1f}".format(round(kbs, 2)).rjust(6) + format_values['interface'] = self.interface - if NetUtil.sysfs_interface_up(self.interface, self.unknown_up): + if sysfs_interface_up(self.interface, self.unknown_up): if self.dynamic_color: color = self.get_gradient(kbs, self.colors, self.upper_limit) else: From 404c9ea3488c00b4cec4ffa6312c769ac12de737 Mon Sep 17 00:00:00 2001 From: Christopher Ganas Date: Sun, 4 Jan 2015 16:07:25 -0500 Subject: [PATCH 4/5] Updated spotify module to check for metadata on start. --- i3pystatus/spotify.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/i3pystatus/spotify.py b/i3pystatus/spotify.py index 8e26435..a4a97bb 100644 --- a/i3pystatus/spotify.py +++ b/i3pystatus/spotify.py @@ -29,7 +29,11 @@ class Spotify(Module): def main_loop(self): """ Mainloop blocks so we thread it.""" self.player = Playerctl.Player() - self.player.on('metadata', self.on_track_change) + self.player.on('metadata', self.set_status) + + if self.player.props.status != "": + self.set_status(self.player) + main = GLib.MainLoop() main.run() @@ -44,18 +48,20 @@ class Spotify(Module): "color": "#FF0000" } - def on_track_change(self, player, e): + def set_status(self, player, e=None): artist = player.get_artist() title = player.get_title() album = player.get_album() volume = player.props.volume - time = e["mpris:length"] / 60.0e6 - minutes = math.floor(time) - seconds = round(time % 1 * 60) - if seconds < 10: - seconds = "0" + str(seconds) - length = "{}:{}".format(minutes, seconds) + length = "" + if e is not None: + time = e["mpris:length"] / 60.0e6 + minutes = math.floor(time) + seconds = round(time % 1 * 60) + if seconds < 10: + seconds = "0" + str(seconds) + length = "{}:{}".format(minutes, seconds) self.output = { "full_text": self.format.format( From c6c7e162b3eb6889b093c9f9d7a2c426abd67df2 Mon Sep 17 00:00:00 2001 From: Gordon Schulz Date: Tue, 6 Jan 2015 12:35:45 +0100 Subject: [PATCH 5/5] Add support for depleted batteries This commit adds support for detecting depleted (empty) batteries. Introduces new 'Depleted' battery status and a 'DPL' status mapping. --- i3pystatus/battery.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/i3pystatus/battery.py b/i3pystatus/battery.py index c510473..1daf15a 100644 --- a/i3pystatus/battery.py +++ b/i3pystatus/battery.py @@ -51,6 +51,8 @@ class Battery: def status(self): if self.consumption() > 0.1 and self.percentage() < 99.9: return "Discharging" if self.battery_info["STATUS"] == "Discharging" else "Charging" + elif self.consumption() == 0 and self.percentage() == 0.00: + return "Depleted" else: return "Full" @@ -125,7 +127,7 @@ class BatteryChecker(IntervalModule): ("alert_format_title", "The title of the notification, all formatters can be used"), ("alert_format_body", "The body text of the notification, all formatters can be used"), ("path", "Override the default-generated path"), - ("status", "A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names"), + ("status", "A dictionary mapping ('DPL', 'DIS', 'CHR', 'FULL') to alternative names"), ("color", "The text color"), ("full_color", "The full color"), ("charging_color", "The charging color"), @@ -137,6 +139,7 @@ class BatteryChecker(IntervalModule): battery_ident = "BAT0" format = "{status} {remaining}" status = { + "DPL": "DPL", "CHR": "CHR", "DIS": "DIS", "FULL": "FULL", @@ -201,6 +204,9 @@ class BatteryChecker(IntervalModule): else: fdict["status"] = "CHR" color = self.charging_color + elif status == 'Depleted': + fdict["status"] = "DPL" + color = self.critical_color else: fdict["status"] = "FULL" color = self.full_color