From b7a73f5b46f6e38b9b8bec51317008a066e0e160 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Fri, 5 Aug 2016 22:11:09 +0800 Subject: [PATCH 1/7] add four modules --- i3pystatus/deadbeef.py | 168 ++++++++++++ i3pystatus/network_colour.py | 482 +++++++++++++++++++++++++++++++++++ i3pystatus/scratchpad.py | 74 ++++++ i3pystatus/window_title.py | 114 +++++++++ 4 files changed, 838 insertions(+) create mode 100644 i3pystatus/deadbeef.py create mode 100644 i3pystatus/network_colour.py create mode 100644 i3pystatus/scratchpad.py create mode 100644 i3pystatus/window_title.py diff --git a/i3pystatus/deadbeef.py b/i3pystatus/deadbeef.py new file mode 100644 index 0000000..dd6f3d8 --- /dev/null +++ b/i3pystatus/deadbeef.py @@ -0,0 +1,168 @@ +from i3pystatus import IntervalModule, formatp +from subprocess import check_output, CalledProcessError + + +class NotRunningException(Exception): + pass + + +class StopException(Exception): + pass + + +class ErrorException(Exception): + pass + + +class DeaDBeeF(IntervalModule): + """ + Display track currently playing in deadbeef for i3pystatus. + fork from deadbeef of py3status by mrt-prodz + + Requires the `DeaDBeeF` player. + + .. rubric:: Available formatters + * `{status}` — player status + * `{album}` — album + * `{artist}` — artist + * `{title}` — title + * `{elapsed}` — elapsed time + * `{length}` — total length + * `{bitrate}` — bit rate + * `{codec}` — encode type + + @author jok + """ + + settings = ( + ("status", "Dictionary mapping pause, play and stop to output text"), + ("format", "Format string"), + ("format_not_running", "Text to show if deadbeef is not running"), + ("format_stopped", "Text to show if deadbeef is stopped"), + ("color", "Text color"), + ("color_not_running", "Text color when deadbeef is not running"), + ("color_stopped", "Text color when deadbeef is stopped"), + ) + + delimiter = '¥' + color = "#FFFFFF" + color_not_running = "#FFFFFF" + color_stopped = "#FF0000" + format = "{status} {artist} - {title}" + format_not_running = "Not Running" + format_stopped = "STOPPED" + interval = 1 + status = { + 'paused': "⠿", + 'playing': '▶', + 'stopped': '◾', + } + + on_leftclick = "play_pause" + on_rightclick = "next_song" + + def get_info(self): + try: + # check if we have deadbeef running + check_output(['pidof', 'deadbeef']) + except CalledProcessError: + raise NotRunningException + + # get all properties using ¥ as delimiter + info = check_output(['deadbeef', + '--nowplaying-tf', + self.delimiter.join(['%artist%', + '%title%', + '%album%', + '%length%', + '%playback_time%', + '%bitrate%', + '%codec%', + '%isplaying%', + '%ispaused%'])]).decode() + + if info == self.delimiter * 3 + "0:00" + self.delimiter * 5: + raise StopException + + # split properties using special delimiter + parts = info.split(self.delimiter) + if len(parts) == 9: + return parts + else: + raise ErrorException + + def run(self): + try: + parts = self.get_info() + artist, title, album, length, elapsed,\ + bitrate, codec, isplaying, ispaused = parts + + db_status = 'stopped' + if isplaying == '1' and ispaused == '': + db_status = 'playing' + elif isplaying == '' and ispaused == '1': + db_status = 'paused' + + p_dict = { + "status": self.status[db_status], + "artist": artist, + "title": title, + "album": album, + "length": length, + "elapsed": elapsed, + "bitrate": bitrate, + "codec": codec, + } + + self.data = p_dict + self.output = { + "full_text": formatp(self.format, **p_dict).strip(), + "color": self.color, + } + + except NotRunningException: + self.output = { + "full_text": self.format_not_running, + "color": self.color_not_running, + } + if hasattr(self, "data"): + del self.data + return + + except StopException: + self.output = { + "full_text": self.status['stopped'] + " " + self.format_stopped, + "color": self.color_stopped, + } + if hasattr(self, "data"): + del self.data + return + + except ErrorException: + self.output = { + "full_text": "ERROR WITH DEADBEEF", + "color": "#FF00000", + } + if hasattr(self, "data"): + del self.data + return + + @staticmethod + def play_pause(): + + try: + check_output(['pidof', 'deadbeef']) + except CalledProcessError: + return + + check_output(['deadbeef', '--toggle-pause']) + + @staticmethod + def next_song(): + + try: + check_output(['pidof', 'deadbeef']) + except CalledProcessError: + return + + check_output(['deadbeef', '--next']) diff --git a/i3pystatus/network_colour.py b/i3pystatus/network_colour.py new file mode 100644 index 0000000..5d6be0a --- /dev/null +++ b/i3pystatus/network_colour.py @@ -0,0 +1,482 @@ +import netifaces +from i3pystatus import IntervalModule, formatp +from i3pystatus.core.color import ColorRangeModule +from i3pystatus.core.util import round_dict + + +def count_bits(integer): + bits = 0 + while integer: + integer &= integer - 1 + bits += 1 + return bits + + +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. + """ + def __init__(self, interface, ignore_interfaces, detached_down, unknown_up, get_wifi_info=False): + 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 + self.get_wifi_info = get_wifi_info + + def get_info(self, interface): + 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 = get_bonded_slaves() + try: + master = slaves[interface] + except KeyError: + pass + else: + if 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"] = 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"] = cidr6(v6["addr"], v6["netmask"]) + if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses + break + return info + + def extract_wireless_info(self, interface): + info = dict(essid="", freq="", quality=0.0, quality_bar="") + + # Just return empty values if we're not using any Wifi functionality + if not self.get_wifi_info: + return info + + import basiciw + + try: + iwi = basiciw.iwinfo(interface) + except Exception: + # Not a wireless interface + return info + + 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"] = round(info["quality"]) + + return info + + +class NetworkTraffic: + """ + 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): + import psutil + + 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_rx_tot_Mbytes(self, interface): + try: + with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0]) / (1024 * 1024) + except FileNotFoundError: + return False + + def get_tx_tot_Mbytes(self, interface): + try: + with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0]) / (1024 * 1024) + except FileNotFoundError: + return False + + def get_usage(self, interface): + self.update_counters(interface) + usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) + + if not 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() + usage["rx_tot_Mbytes"] = self.get_rx_tot_Mbytes(interface) + usage["tx_tot_Mbytes"] = self.get_tx_tot_Mbytes(interface) + round_dict(usage, self.round_size) + return usage + + +class NetworkColour(IntervalModule, ColorRangeModule): + """ + Displays network information for an interface. + no graph support + formatp enabled + + mode1: single and static color text, dynamic_color and pango disabled. + mode2: mutilple and static color text, dynamic_color disabled and pango enabled. + 'color_info', 'color_recv' and 'color_sent' are vaild. + mode3: single and dynamic color text, dynamic_color enabled and pango disabled. + 'recv_limit' and 'sent_limit' are vaild. default limit is 20Mbps download bandwidth. + mode4: mutilple and static color text, dynamic_color and pango enabled. + works with '{bytes_sent}' and '{bytes_recv}'. 'color_info', 'recv_limit' and 'sent_limit' + are vaild. '{bytes_sent}' and '{bytes_recv}' will show their color separate. + + Requires the PyPI packages `colour`, `netifaces`, `psutil` (optional, see below) + and `basiciw` (optional, see below). + + if u wanna display mutilple colors please enable pango markup and set color. + + .. rubric:: Available formatters + + Network Information Formatters: + + * `{interface}` — same as setting + * `{v4}` — IPv4 address + * `{v4mask}` — subnet mask + * `{v4cidr}` — IPv4 address in cidr notation (i.e. 192.168.2.204/24) + * `{v6}` — IPv6 address + * `{v6mask}` — subnet mask + * `{v6cidr}` — IPv6 address in cidr notation + * `{mac}` — MAC of interface + + Wireless Information Formatters (requires PyPI package `basiciw`): + + * `{essid}` — ESSID of currently connected wifi + * `{freq}` — Current frequency + * `{quality}` — Link quality in percent + + Network Traffic Formatters (requires PyPI pacakge `psutil`): + + * `{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) + * `{rx_tot_Mbytes}` — total Mbytes received + * `{tx_tot_Mbytes}` — total Mbytes sent + """ + + settings = ( + ("format_up", "format string"), + ("format_down", "format string when interface is down"), + ("interface", "Interface to watch, eg 'eth0'"), + ("color_down", "text color when interface is down"), + ("color_info", "color of network information"), + ("color_normal", "text color"), + ("color_recv", "color of receive speed, effect when pango is enabled"), + ("color_sent", "color of sent speed, effect when pango is enabled"), + ("dynamic_color", "Set color dynamically based on network traffic."), + ("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'"), + ("recv_limit", "Expected max KiB/s. This value controls the drawing color of receive speed"), + ("sent_limit", "Expected max KiB/s. similiar with receive_limit"), + ("divisor", "divide all byte values by this value"), + ("ignore_interfaces", "Array of interfaces to ignore when cycling through " + "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"), + ) + + interval = 1 + interface = 'eth0' + + format_up = "{interface} {bytes_recv}KB/s" + format_down = "{interface}: DOWN" + color_normal = "#00FF00" + color_down = "#FF0000" + color_info = None + color_recv = None + color_sent = None + + # Dynamic color settings + dynamic_color = True + recv_limit = None + sent_limit = None + + # 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" + on_upscroll = ['cycle_interface', 1] + on_downscroll = ['cycle_interface', -1] + + # categories + net_info_list = ["interface", "v4", "v4mask", "v4cidr", "v6", \ + "v6mask", "v6cidr", "mac", "essid", "freq", "quality"] + net_recv_list = ["bytes_recv", "packets_recv", "rx_tot_Mbytes"] + net_sent_list = ["bytes_sent", "packets_sent", "tx_tot_Mbytes"] + + def init(self): + # Don't require importing basiciw unless using the functionality it offers. + if any(s in self.format_up or s in self.format_down for s in + ['essid', 'freq', 'quality']): + get_wifi_info = True + else: + get_wifi_info = False + + self.network_info = NetworkInfo(self.interface, self.ignore_interfaces, \ + self.detached_down, self.unknown_up, get_wifi_info) + + # Don't require importing psutil unless using the functionality it offers. + if any(s in self.format_up or s in self.format_down for s in + ['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', + 'rx_tot_Mbytes', 'tx_tot_Mbytes',]): + self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size) + else: + self.network_traffic = None + + self.colors = self.get_hex_color_range(self.start_color, self.end_color, 100) + + def cycle_interface(self, increment=1): + """ + Cycle through available interfaces in `increment` steps. Sign indicates direction. + """ + interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces] + if self.interface in interfaces: + next_index = (interfaces.index(self.interface) + increment) % len(interfaces) + self.interface = interfaces[next_index] + elif len(interfaces) > 0: + self.interface = interfaces[0] + + if self.network_traffic: + self.network_traffic.clear_counters() + + def get_status_code(self): + status_code = "" + if self.dynamic_color: + status_code += "1" + else: + status_code += "0" + + if self.hints.get("markup", False) and self.hints["markup"] == "pango": + status_code += "1" + else: + status_code += "0" + + return status_code + + def markup_text(self, values): + if self.color_info: + for va in self.net_info_list: + if values[va]: + values[va] = "" \ + + values[va] + "" + + if self.color_recv: + for va in self.net_recv_list: + if values[va] or values[va] == 0: + values[va] = "" \ + + str(values[va]) + "" + + if self.color_sent: + for va in self.net_sent_list: + if values[va] or values[va] == 0: + values[va] = "" \ + + str(values[va]) + "" + + def dynamic_mode(self, values): + """ + works with `{bytes_sent}`, `{bytes_recv}` + """ + if self.recv_limit and self.sent_limit: + per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) + per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) + return int(max(per_recv, per_sent) * 100) + elif self.recv_limit: + per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) + return int(per_recv * 100) + elif self.sent_limit: + per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) + return int(per_sent * 100) + else: + # default 20Mbps bandwidth + per_recv = values["bytes_recv"] * self.divisor / (2560 * 1024) + return int(per_recv * 100) + + def mix_dynamic_mode(self, values): + if self.color_info: + for va in self.net_info_list: + if values[va]: + values[va] = "" \ + + values[va] + "" + + if self.recv_limit and self.sent_limit: + per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) + per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) + c_recv = self.get_gradient(int(per_recv * 100), self.colors, 100) + c_sent = self.get_gradient(int(per_sent * 100), self.colors, 100) + values["bytes_recv"] = "" + str(values["bytes_recv"]) + "" + values["bytes_sent"] = "" + str(values["bytes_sent"]) + "" + else: + raise Exception("LIMIT NOT SET CORRECTLY") + + def run(self): + format_values = dict(bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", + rx_tot_Mbytes="", tx_tot_Mbytes="", + interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="", + essid="", freq="", quality="") + + color = None + if self.network_traffic: + network_usage = self.network_traffic.get_usage(self.interface) + format_values.update(network_usage) + + if sysfs_interface_up(self.interface, self.unknown_up): + format_str = self.format_up + else: + color = self.color_down + format_str = self.format_down + + network_info = self.network_info.get_info(self.interface) + format_values.update(network_info) + format_values['interface'] = self.interface + + # color + if not color: + code = self.get_status_code() + if code == "01": + # markup text + color = self.color_normal + self.markup_text(format_values) + elif code == "10" and self.network_traffic: + # normal dynamic mode + percent = self.dynamic_mode(format_values) + color = self.get_gradient(percent, self.colors, 100) + elif code == "11" and self.network_traffic: + # mix dynamic mode + color = self.color_normal + self.mix_dynamic_mode(format_values) + else: + color = self.color_normal + + self.data = format_values + self.output = { + "full_text": formatp(format_str, **format_values).strip(), + 'color': color, + } diff --git a/i3pystatus/scratchpad.py b/i3pystatus/scratchpad.py new file mode 100644 index 0000000..d3ab849 --- /dev/null +++ b/i3pystatus/scratchpad.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from threading import Thread +from i3pystatus import Module +import i3ipc + + +class Scratchpad(Module): + """ + Display the amount of windows and indicate urgency hints on scratchpad (async). + + fork from scratchpad_async of py3status by cornerman + + Requires the PyPI package `i3ipc`. + + .. rubric:: Available formaters + + * `{number}` — amount of windows on scratchpad + + @author jok + @license BSD + """ + + settings = ( + ("format", "format string."), + ("always_show", "whether the indicator should be shown if there are" + " no scratchpad windows"), + ("color_urgent", "color of urgent"), + ("color", "text color"), + ) + + format = u"{number} ⌫" + always_show = True + color_urgent = "#900000" + color = "#FFFFFF" + + def init(self): + self.count = 0 + self.urgent = False + + t = Thread(target=self._listen) + t.daemon = True + t.start() + + def update_scratchpad_counter(self, conn, e=None): + cons = conn.get_tree().scratchpad().leaves() + self.urgent = any(con for con in cons if con.urgent) + self.count = len(cons) + + # output + if self.urgent: + color = self.color_urgent + else: + color = self.color + + if self.always_show or self.count > 0: + full_text = self.format.format(number=self.count) + else: + full_text = '' + + self.output = { + "full_text": full_text, + "color": color, + } + + def _listen(self): + conn = i3ipc.Connection() + self.update_scratchpad_counter(conn) + + conn.on('window::move', self.update_scratchpad_counter) + conn.on('window::urgent', self.update_scratchpad_counter) + conn.on('window::new', self.update_scratchpad_counter) + conn.on('window::close', self.update_scratchpad_counter) + + conn.main() diff --git a/i3pystatus/window_title.py b/i3pystatus/window_title.py new file mode 100644 index 0000000..9b28719 --- /dev/null +++ b/i3pystatus/window_title.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +from i3pystatus import Module +from threading import Thread +import i3ipc + + +class WindowTitle(Module): + """ + Display the current window title with async update. + Uses asynchronous update via i3 IPC events. + Provides instant title update only when it required. + + fork from window_tile_async of py3status by Anon1234 https://github.com/Anon1234 + + Requires the PyPI package `i3ipc`. + + .. rubric:: Available formaters + + * `{title}` — title of current focused window + * `{class_name}` - name of application class + + @author jok + @license BSD + """ + + settings = ( + ("format", "format string."), + ("always_show", "do not hide the title when it can be already visible"), + ("empty_title", "string that will be shown instead of the title when the title is hidden"), + ("max_width", "maximum width of title"), + ("color", "text color"), + ) + + format = "{title}" + always_show = False + empty_title = "" + max_width = 79 + color = "#FFFFFF" + + def init(self): + self.title = self.empty_title + self.output = { + "full_text": self.title, + "color": self.color, + } + + # we are listening to i3 events in a separate thread + t = Thread(target=self._loop) + t.daemon = True + t.start() + + def get_title(self, conn): + tree = conn.get_tree() + w = tree.find_focused() + p = w.parent + + # don't show window title when the window already has means + # to display it + if not self.always_show and ( + w.border == "normal" or + w.type == "workspace" or + (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1)): + return self.empty_title + else: + title = w.name + class_name = w.window_class + if len(title) > self.max_width: + title = title[:self.max_width - 1] + "…" + return self.format.format(title=title, class_name=class_name) + + def update_title(self, conn, e): + # catch only focused window title updates + title_changed = hasattr(e, "container") and e.container.focused + + # check if we need to update title due to changes + # in the workspace layout + layout_changed = ( + hasattr(e, "binding") and + (e.binding.command.startswith("layout") or + e.binding.command.startswith("move container") or + e.binding.command.startswith("border")) + ) + + if title_changed or layout_changed: + self.title = self.get_title(conn) + self.update_display() + + def clear_title(self, *args): + self.title = self.empty_title + self.update_display() + + def update_display(self): + self.output = { + "full_text": self.title, + "color": self.color, + } + + def _loop(self): + conn = i3ipc.Connection() + self.title = self.get_title(conn) # set title on startup + self.update_display() + + # The order of following callbacks is important! + # clears the title on empty ws + conn.on('workspace::focus', self.clear_title) + + # clears the title when the last window on ws was closed + conn.on("window::close", self.clear_title) + + # listens for events which can trigger the title update + conn.on("window::title", self.update_title) + conn.on("window::focus", self.update_title) + + conn.main() # run the event loop From 4882714382be3d7712071b7b9f087c4e78a1f912 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sat, 6 Aug 2016 07:01:52 +0800 Subject: [PATCH 2/7] merge network colour and network --- i3pystatus/deadbeef.py | 5 +- i3pystatus/network.py | 77 ++++-- i3pystatus/network_colour.py | 482 ----------------------------------- i3pystatus/window_title.py | 10 +- 4 files changed, 64 insertions(+), 510 deletions(-) delete mode 100644 i3pystatus/network_colour.py diff --git a/i3pystatus/deadbeef.py b/i3pystatus/deadbeef.py index dd6f3d8..013bc36 100644 --- a/i3pystatus/deadbeef.py +++ b/i3pystatus/deadbeef.py @@ -53,7 +53,7 @@ class DeaDBeeF(IntervalModule): format_stopped = "STOPPED" interval = 1 status = { - 'paused': "⠿", + 'paused': '▷', 'playing': '▶', 'stopped': '◾', } @@ -94,8 +94,7 @@ class DeaDBeeF(IntervalModule): def run(self): try: parts = self.get_info() - artist, title, album, length, elapsed,\ - bitrate, codec, isplaying, ispaused = parts + artist, title, album, length, elapsed, bitrate, codec, isplaying, ispaused = parts db_status = 'stopped' if isplaying == '1' and ispaused == '': diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 879711f..4b24106 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -1,13 +1,13 @@ import netifaces -from i3pystatus import IntervalModule +from i3pystatus import IntervalModule, formatp 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): + while integer: integer &= integer - 1 bits += 1 return bits @@ -67,7 +67,7 @@ def sysfs_interface_up(interface, unknown_up=False): return status == "up" or unknown_up and status == "unknown" -class NetworkInfo(): +class NetworkInfo: """ Retrieve network information. """ @@ -161,7 +161,7 @@ class NetworkInfo(): return info -class NetworkTraffic(): +class NetworkTraffic: """ Retrieve network traffic information """ @@ -232,6 +232,15 @@ class Network(IntervalModule, ColorRangeModule): """ Displays network information for an interface. + formatp support + + if u wanna display recv/send speed separate in dynamic color mode, please enable pango hint. + + status.register( + "network", + hints={"markup": "pango"}, + ) + Requires the PyPI packages `colour`, `netifaces`, `psutil` (optional, see below) and `basiciw` (optional, see below). @@ -255,10 +264,10 @@ class Network(IntervalModule, ColorRangeModule): * `{quality}` — Link quality in percent * `{quality_bar}` —Bar graphically representing link quality - Network Traffic Formatters (requires PyPI pacakge `psutil`): + Network Traffic Formatters (requires PyPI package `psutil`): * `{interface}` — the configured network interface - * `{kbs}` – Float representing kb\s + * `{kbs}` – Float representing KiB\s corresponds to graph type * `{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) @@ -278,8 +287,10 @@ class Network(IntervalModule, ColorRangeModule): ("end_color", "Hex or English name for end of color range, eg '#FF0000' or 'red'"), ("graph_width", "Width of the network traffic graph"), ("graph_style", "Graph style ('blocks', 'braille-fill', 'braille-peak', or 'braille-snake')"), - ("upper_limit", - "Expected max kb/s. This value controls how the network traffic graph is drawn and in what color"), + ("recv_limit", "Expected max KiB/s. This value controls the drawing color of receive speed"), + ("sent_limit", "Expected max KiB/s. similar with receive_limit"), + ("separate_color", "display recv/send color separate in dynamic color mode." + "Note: only network speed formatters will display with range 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"), @@ -301,7 +312,9 @@ class Network(IntervalModule, ColorRangeModule): graph_type = 'input' graph_width = 15 graph_style = 'blocks' - upper_limit = 150.0 + recv_limit = 2048 + sent_limit = 1024 + separate_color = False # Network traffic settings divisor = 1024 @@ -319,7 +332,7 @@ class Network(IntervalModule, ColorRangeModule): def init(self): # Don't require importing basiciw unless using the functionality it offers. - if any(s in self.format_up or s in self.format_up for s in + if any(s in self.format_down or s in self.format_up for s in ['essid', 'freq', 'quality', 'quality_bar']): get_wifi_info = True else: @@ -337,9 +350,10 @@ class Network(IntervalModule, ColorRangeModule): self.network_traffic = None if not self.dynamic_color: - self.end_color = self.start_color - self.colors = self.get_hex_color_range(self.start_color, self.end_color, int(self.upper_limit)) + self.end_color = self.start_color = self.color_up + self.colors = self.get_hex_color_range(self.start_color, self.end_color, 100) self.kbs_arr = [0.0] * self.graph_width + self.pango_enabled = self.hints.get("markup", False) and self.hints["markup"] == "pango" def cycle_interface(self, increment=1): """Cycle through available interfaces in `increment` steps. Sign indicates direction.""" @@ -354,11 +368,11 @@ class Network(IntervalModule, ColorRangeModule): self.network_traffic.clear_counters() self.kbs_arr = [0.0] * self.graph_width - def get_network_graph(self, kbs): + def get_network_graph(self, kbs, limit): # 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, 0.0, self.upper_limit, self.graph_style) + return make_graph(self.kbs_arr, 0.0, limit, self.graph_style) def run(self): format_values = dict(kbs="", network_graph="", bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", @@ -369,15 +383,38 @@ class Network(IntervalModule, ColorRangeModule): network_usage = self.network_traffic.get_usage(self.interface) format_values.update(network_usage) if self.graph_type == 'input': - kbs = network_usage['bytes_recv'] + limit = self.recv_limit + kbs = network_usage['bytes_recv'] * self.divisor / 1024 elif self.graph_type == 'output': - kbs = network_usage['bytes_sent'] + limit = self.sent_limit + kbs = network_usage['bytes_sent'] * self.divisor / 1024 else: raise Exception("graph_type must be either 'input' or 'output'!") - format_values['network_graph'] = self.get_network_graph(kbs) - format_values['kbs'] = "{0:.1f}".format(round(kbs, 2)).rjust(6) - color = self.get_gradient(kbs, self.colors, self.upper_limit) + format_values['network_graph'] = self.get_network_graph(kbs, limit) + format_values['kbs'] = "{0:.1f}".format(round(kbs, 2)) + + if self.separate_color and self.pango_enabled: + color = self.color_up + per_recv = network_usage["bytes_recv"] * self.divisor / (self.recv_limit * 1024) + per_sent = network_usage["bytes_sent"] * self.divisor / (self.sent_limit * 1024) + c_recv = self.get_gradient(int(per_recv * 100), self.colors, 100) + c_sent = self.get_gradient(int(per_sent * 100), self.colors, 100) + format_values["bytes_recv"] = "" + \ + str(network_usage["bytes_recv"]) + "" + format_values["bytes_sent"] = "" + \ + str(network_usage["bytes_sent"]) + "" + if self.graph_type == 'output': + c_kbs = c_sent + else: + c_kbs = c_recv + format_values['network_graph'] = "" + \ + str(format_values["network_graph"]) + "" + format_values['kbs'] = "" + \ + str(format_values["kbs"]) + "" + else: + percent = int(kbs * 100 / limit) + color = self.get_gradient(percent, self.colors, 100) else: color = None @@ -395,6 +432,6 @@ class Network(IntervalModule, ColorRangeModule): self.data = format_values self.output = { - "full_text": format_str.format(**format_values), + "full_text": formatp(format_str, **format_values).strip(), 'color': color, } diff --git a/i3pystatus/network_colour.py b/i3pystatus/network_colour.py deleted file mode 100644 index 5d6be0a..0000000 --- a/i3pystatus/network_colour.py +++ /dev/null @@ -1,482 +0,0 @@ -import netifaces -from i3pystatus import IntervalModule, formatp -from i3pystatus.core.color import ColorRangeModule -from i3pystatus.core.util import round_dict - - -def count_bits(integer): - bits = 0 - while integer: - integer &= integer - 1 - bits += 1 - return bits - - -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. - """ - def __init__(self, interface, ignore_interfaces, detached_down, unknown_up, get_wifi_info=False): - 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 - self.get_wifi_info = get_wifi_info - - def get_info(self, interface): - 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 = get_bonded_slaves() - try: - master = slaves[interface] - except KeyError: - pass - else: - if 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"] = 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"] = cidr6(v6["addr"], v6["netmask"]) - if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses - break - return info - - def extract_wireless_info(self, interface): - info = dict(essid="", freq="", quality=0.0, quality_bar="") - - # Just return empty values if we're not using any Wifi functionality - if not self.get_wifi_info: - return info - - import basiciw - - try: - iwi = basiciw.iwinfo(interface) - except Exception: - # Not a wireless interface - return info - - 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"] = round(info["quality"]) - - return info - - -class NetworkTraffic: - """ - 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): - import psutil - - 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_rx_tot_Mbytes(self, interface): - try: - with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0]) / (1024 * 1024) - except FileNotFoundError: - return False - - def get_tx_tot_Mbytes(self, interface): - try: - with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0]) / (1024 * 1024) - except FileNotFoundError: - return False - - def get_usage(self, interface): - self.update_counters(interface) - usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) - - if not 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() - usage["rx_tot_Mbytes"] = self.get_rx_tot_Mbytes(interface) - usage["tx_tot_Mbytes"] = self.get_tx_tot_Mbytes(interface) - round_dict(usage, self.round_size) - return usage - - -class NetworkColour(IntervalModule, ColorRangeModule): - """ - Displays network information for an interface. - no graph support - formatp enabled - - mode1: single and static color text, dynamic_color and pango disabled. - mode2: mutilple and static color text, dynamic_color disabled and pango enabled. - 'color_info', 'color_recv' and 'color_sent' are vaild. - mode3: single and dynamic color text, dynamic_color enabled and pango disabled. - 'recv_limit' and 'sent_limit' are vaild. default limit is 20Mbps download bandwidth. - mode4: mutilple and static color text, dynamic_color and pango enabled. - works with '{bytes_sent}' and '{bytes_recv}'. 'color_info', 'recv_limit' and 'sent_limit' - are vaild. '{bytes_sent}' and '{bytes_recv}' will show their color separate. - - Requires the PyPI packages `colour`, `netifaces`, `psutil` (optional, see below) - and `basiciw` (optional, see below). - - if u wanna display mutilple colors please enable pango markup and set color. - - .. rubric:: Available formatters - - Network Information Formatters: - - * `{interface}` — same as setting - * `{v4}` — IPv4 address - * `{v4mask}` — subnet mask - * `{v4cidr}` — IPv4 address in cidr notation (i.e. 192.168.2.204/24) - * `{v6}` — IPv6 address - * `{v6mask}` — subnet mask - * `{v6cidr}` — IPv6 address in cidr notation - * `{mac}` — MAC of interface - - Wireless Information Formatters (requires PyPI package `basiciw`): - - * `{essid}` — ESSID of currently connected wifi - * `{freq}` — Current frequency - * `{quality}` — Link quality in percent - - Network Traffic Formatters (requires PyPI pacakge `psutil`): - - * `{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) - * `{rx_tot_Mbytes}` — total Mbytes received - * `{tx_tot_Mbytes}` — total Mbytes sent - """ - - settings = ( - ("format_up", "format string"), - ("format_down", "format string when interface is down"), - ("interface", "Interface to watch, eg 'eth0'"), - ("color_down", "text color when interface is down"), - ("color_info", "color of network information"), - ("color_normal", "text color"), - ("color_recv", "color of receive speed, effect when pango is enabled"), - ("color_sent", "color of sent speed, effect when pango is enabled"), - ("dynamic_color", "Set color dynamically based on network traffic."), - ("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'"), - ("recv_limit", "Expected max KiB/s. This value controls the drawing color of receive speed"), - ("sent_limit", "Expected max KiB/s. similiar with receive_limit"), - ("divisor", "divide all byte values by this value"), - ("ignore_interfaces", "Array of interfaces to ignore when cycling through " - "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"), - ) - - interval = 1 - interface = 'eth0' - - format_up = "{interface} {bytes_recv}KB/s" - format_down = "{interface}: DOWN" - color_normal = "#00FF00" - color_down = "#FF0000" - color_info = None - color_recv = None - color_sent = None - - # Dynamic color settings - dynamic_color = True - recv_limit = None - sent_limit = None - - # 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" - on_upscroll = ['cycle_interface', 1] - on_downscroll = ['cycle_interface', -1] - - # categories - net_info_list = ["interface", "v4", "v4mask", "v4cidr", "v6", \ - "v6mask", "v6cidr", "mac", "essid", "freq", "quality"] - net_recv_list = ["bytes_recv", "packets_recv", "rx_tot_Mbytes"] - net_sent_list = ["bytes_sent", "packets_sent", "tx_tot_Mbytes"] - - def init(self): - # Don't require importing basiciw unless using the functionality it offers. - if any(s in self.format_up or s in self.format_down for s in - ['essid', 'freq', 'quality']): - get_wifi_info = True - else: - get_wifi_info = False - - self.network_info = NetworkInfo(self.interface, self.ignore_interfaces, \ - self.detached_down, self.unknown_up, get_wifi_info) - - # Don't require importing psutil unless using the functionality it offers. - if any(s in self.format_up or s in self.format_down for s in - ['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', - 'rx_tot_Mbytes', 'tx_tot_Mbytes',]): - self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size) - else: - self.network_traffic = None - - self.colors = self.get_hex_color_range(self.start_color, self.end_color, 100) - - def cycle_interface(self, increment=1): - """ - Cycle through available interfaces in `increment` steps. Sign indicates direction. - """ - interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces] - if self.interface in interfaces: - next_index = (interfaces.index(self.interface) + increment) % len(interfaces) - self.interface = interfaces[next_index] - elif len(interfaces) > 0: - self.interface = interfaces[0] - - if self.network_traffic: - self.network_traffic.clear_counters() - - def get_status_code(self): - status_code = "" - if self.dynamic_color: - status_code += "1" - else: - status_code += "0" - - if self.hints.get("markup", False) and self.hints["markup"] == "pango": - status_code += "1" - else: - status_code += "0" - - return status_code - - def markup_text(self, values): - if self.color_info: - for va in self.net_info_list: - if values[va]: - values[va] = "" \ - + values[va] + "" - - if self.color_recv: - for va in self.net_recv_list: - if values[va] or values[va] == 0: - values[va] = "" \ - + str(values[va]) + "" - - if self.color_sent: - for va in self.net_sent_list: - if values[va] or values[va] == 0: - values[va] = "" \ - + str(values[va]) + "" - - def dynamic_mode(self, values): - """ - works with `{bytes_sent}`, `{bytes_recv}` - """ - if self.recv_limit and self.sent_limit: - per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) - per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) - return int(max(per_recv, per_sent) * 100) - elif self.recv_limit: - per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) - return int(per_recv * 100) - elif self.sent_limit: - per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) - return int(per_sent * 100) - else: - # default 20Mbps bandwidth - per_recv = values["bytes_recv"] * self.divisor / (2560 * 1024) - return int(per_recv * 100) - - def mix_dynamic_mode(self, values): - if self.color_info: - for va in self.net_info_list: - if values[va]: - values[va] = "" \ - + values[va] + "" - - if self.recv_limit and self.sent_limit: - per_recv = values["bytes_recv"] * self.divisor / (self.recv_limit * 1024) - per_sent = values["bytes_sent"] * self.divisor / (self.sent_limit * 1024) - c_recv = self.get_gradient(int(per_recv * 100), self.colors, 100) - c_sent = self.get_gradient(int(per_sent * 100), self.colors, 100) - values["bytes_recv"] = "" + str(values["bytes_recv"]) + "" - values["bytes_sent"] = "" + str(values["bytes_sent"]) + "" - else: - raise Exception("LIMIT NOT SET CORRECTLY") - - def run(self): - format_values = dict(bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", - rx_tot_Mbytes="", tx_tot_Mbytes="", - interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="", - essid="", freq="", quality="") - - color = None - if self.network_traffic: - network_usage = self.network_traffic.get_usage(self.interface) - format_values.update(network_usage) - - if sysfs_interface_up(self.interface, self.unknown_up): - format_str = self.format_up - else: - color = self.color_down - format_str = self.format_down - - network_info = self.network_info.get_info(self.interface) - format_values.update(network_info) - format_values['interface'] = self.interface - - # color - if not color: - code = self.get_status_code() - if code == "01": - # markup text - color = self.color_normal - self.markup_text(format_values) - elif code == "10" and self.network_traffic: - # normal dynamic mode - percent = self.dynamic_mode(format_values) - color = self.get_gradient(percent, self.colors, 100) - elif code == "11" and self.network_traffic: - # mix dynamic mode - color = self.color_normal - self.mix_dynamic_mode(format_values) - else: - color = self.color_normal - - self.data = format_values - self.output = { - "full_text": formatp(format_str, **format_values).strip(), - 'color': color, - } diff --git a/i3pystatus/window_title.py b/i3pystatus/window_title.py index 9b28719..b8e5e27 100644 --- a/i3pystatus/window_title.py +++ b/i3pystatus/window_title.py @@ -40,8 +40,8 @@ class WindowTitle(Module): def init(self): self.title = self.empty_title self.output = { - "full_text": self.title, - "color": self.color, + "full_text": self.title, + "color": self.color, } # we are listening to i3 events in a separate thread @@ -57,9 +57,9 @@ class WindowTitle(Module): # don't show window title when the window already has means # to display it if not self.always_show and ( - w.border == "normal" or - w.type == "workspace" or - (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1)): + w.border == "normal" or + w.type == "workspace" + or (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1)): return self.empty_title else: title = w.name From 88b8813bd7e00c98345ff1fb5d85e01b3dbec0be Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sat, 6 Aug 2016 07:08:01 +0800 Subject: [PATCH 3/7] bug fix --- i3pystatus/window_title.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/i3pystatus/window_title.py b/i3pystatus/window_title.py index b8e5e27..87f02b8 100644 --- a/i3pystatus/window_title.py +++ b/i3pystatus/window_title.py @@ -42,7 +42,7 @@ class WindowTitle(Module): self.output = { "full_text": self.title, "color": self.color, - } + } # we are listening to i3 events in a separate thread t = Thread(target=self._loop) @@ -57,8 +57,7 @@ class WindowTitle(Module): # don't show window title when the window already has means # to display it if not self.always_show and ( - w.border == "normal" or - w.type == "workspace" + w.border == "normal" or w.type == "workspace" or (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1)): return self.empty_title else: From eb93387290f5c466fa62ecd91a952bbe7cf4d092 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sat, 6 Aug 2016 08:29:25 +0800 Subject: [PATCH 4/7] ci fix --- dev-requirements.txt | 1 + i3pystatus/deadbeef.py | 1 + i3pystatus/network.py | 8 +------- i3pystatus/window_title.py | 6 +++--- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index ed38b0e..d4ff798 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -3,3 +3,4 @@ sphinx>=1.1 colour>=0.0.5 mock>=1.0 pep8>=1.5.7 +i3ipc>=1.2.0 diff --git a/i3pystatus/deadbeef.py b/i3pystatus/deadbeef.py index 013bc36..f6bb077 100644 --- a/i3pystatus/deadbeef.py +++ b/i3pystatus/deadbeef.py @@ -22,6 +22,7 @@ class DeaDBeeF(IntervalModule): Requires the `DeaDBeeF` player. .. rubric:: Available formatters + * `{status}` — player status * `{album}` — album * `{artist}` — artist diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 4b24106..3d066b6 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -231,16 +231,9 @@ class NetworkTraffic: class Network(IntervalModule, ColorRangeModule): """ Displays network information for an interface. - formatp support - if u wanna display recv/send speed separate in dynamic color mode, please enable pango hint. - status.register( - "network", - hints={"markup": "pango"}, - ) - Requires the PyPI packages `colour`, `netifaces`, `psutil` (optional, see below) and `basiciw` (optional, see below). @@ -276,6 +269,7 @@ class Network(IntervalModule, ColorRangeModule): * `{rx_tot_Mbytes}` — total Mbytes received * `{tx_tot_Mbytes}` — total Mbytes sent """ + settings = ( ("format_up", "format string"), ("format_down", "format string"), diff --git a/i3pystatus/window_title.py b/i3pystatus/window_title.py index 87f02b8..359e1d2 100644 --- a/i3pystatus/window_title.py +++ b/i3pystatus/window_title.py @@ -56,9 +56,9 @@ class WindowTitle(Module): # don't show window title when the window already has means # to display it - if not self.always_show and ( - w.border == "normal" or w.type == "workspace" - or (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1)): + if (not self.always_show and + (w.border == "normal" or w.type == "workspace" or + (p.layout in ("stacked", "tabbed") and len(p.nodes) > 1))): return self.empty_title else: title = w.name From 5eb1f9115140eb44f7eb3246f78aba8863277610 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sat, 6 Aug 2016 16:03:28 +0800 Subject: [PATCH 5/7] remove deadbeef --- i3pystatus/deadbeef.py | 168 ----------------------------------------- 1 file changed, 168 deletions(-) delete mode 100644 i3pystatus/deadbeef.py diff --git a/i3pystatus/deadbeef.py b/i3pystatus/deadbeef.py deleted file mode 100644 index f6bb077..0000000 --- a/i3pystatus/deadbeef.py +++ /dev/null @@ -1,168 +0,0 @@ -from i3pystatus import IntervalModule, formatp -from subprocess import check_output, CalledProcessError - - -class NotRunningException(Exception): - pass - - -class StopException(Exception): - pass - - -class ErrorException(Exception): - pass - - -class DeaDBeeF(IntervalModule): - """ - Display track currently playing in deadbeef for i3pystatus. - fork from deadbeef of py3status by mrt-prodz - - Requires the `DeaDBeeF` player. - - .. rubric:: Available formatters - - * `{status}` — player status - * `{album}` — album - * `{artist}` — artist - * `{title}` — title - * `{elapsed}` — elapsed time - * `{length}` — total length - * `{bitrate}` — bit rate - * `{codec}` — encode type - - @author jok - """ - - settings = ( - ("status", "Dictionary mapping pause, play and stop to output text"), - ("format", "Format string"), - ("format_not_running", "Text to show if deadbeef is not running"), - ("format_stopped", "Text to show if deadbeef is stopped"), - ("color", "Text color"), - ("color_not_running", "Text color when deadbeef is not running"), - ("color_stopped", "Text color when deadbeef is stopped"), - ) - - delimiter = '¥' - color = "#FFFFFF" - color_not_running = "#FFFFFF" - color_stopped = "#FF0000" - format = "{status} {artist} - {title}" - format_not_running = "Not Running" - format_stopped = "STOPPED" - interval = 1 - status = { - 'paused': '▷', - 'playing': '▶', - 'stopped': '◾', - } - - on_leftclick = "play_pause" - on_rightclick = "next_song" - - def get_info(self): - try: - # check if we have deadbeef running - check_output(['pidof', 'deadbeef']) - except CalledProcessError: - raise NotRunningException - - # get all properties using ¥ as delimiter - info = check_output(['deadbeef', - '--nowplaying-tf', - self.delimiter.join(['%artist%', - '%title%', - '%album%', - '%length%', - '%playback_time%', - '%bitrate%', - '%codec%', - '%isplaying%', - '%ispaused%'])]).decode() - - if info == self.delimiter * 3 + "0:00" + self.delimiter * 5: - raise StopException - - # split properties using special delimiter - parts = info.split(self.delimiter) - if len(parts) == 9: - return parts - else: - raise ErrorException - - def run(self): - try: - parts = self.get_info() - artist, title, album, length, elapsed, bitrate, codec, isplaying, ispaused = parts - - db_status = 'stopped' - if isplaying == '1' and ispaused == '': - db_status = 'playing' - elif isplaying == '' and ispaused == '1': - db_status = 'paused' - - p_dict = { - "status": self.status[db_status], - "artist": artist, - "title": title, - "album": album, - "length": length, - "elapsed": elapsed, - "bitrate": bitrate, - "codec": codec, - } - - self.data = p_dict - self.output = { - "full_text": formatp(self.format, **p_dict).strip(), - "color": self.color, - } - - except NotRunningException: - self.output = { - "full_text": self.format_not_running, - "color": self.color_not_running, - } - if hasattr(self, "data"): - del self.data - return - - except StopException: - self.output = { - "full_text": self.status['stopped'] + " " + self.format_stopped, - "color": self.color_stopped, - } - if hasattr(self, "data"): - del self.data - return - - except ErrorException: - self.output = { - "full_text": "ERROR WITH DEADBEEF", - "color": "#FF00000", - } - if hasattr(self, "data"): - del self.data - return - - @staticmethod - def play_pause(): - - try: - check_output(['pidof', 'deadbeef']) - except CalledProcessError: - return - - check_output(['deadbeef', '--toggle-pause']) - - @staticmethod - def next_song(): - - try: - check_output(['pidof', 'deadbeef']) - except CalledProcessError: - return - - check_output(['deadbeef', '--next']) From 25a21a3fe06c2c9f4232f21df429096f37ea3eb9 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sun, 7 Aug 2016 18:29:58 +0800 Subject: [PATCH 6/7] minor fix --- i3pystatus/network.py | 13 +++++-------- i3pystatus/scratchpad.py | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 3d066b6..5d7a8f4 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -390,22 +390,19 @@ class Network(IntervalModule, ColorRangeModule): if self.separate_color and self.pango_enabled: color = self.color_up + color_template = "{}" per_recv = network_usage["bytes_recv"] * self.divisor / (self.recv_limit * 1024) per_sent = network_usage["bytes_sent"] * self.divisor / (self.sent_limit * 1024) c_recv = self.get_gradient(int(per_recv * 100), self.colors, 100) c_sent = self.get_gradient(int(per_sent * 100), self.colors, 100) - format_values["bytes_recv"] = "" + \ - str(network_usage["bytes_recv"]) + "" - format_values["bytes_sent"] = "" + \ - str(network_usage["bytes_sent"]) + "" + format_values["bytes_recv"] = color_template.format(c_recv, network_usage["bytes_recv"]) + format_values["bytes_recv"] = color_template.format(c_sent, network_usage["bytes_sent"]) if self.graph_type == 'output': c_kbs = c_sent else: c_kbs = c_recv - format_values['network_graph'] = "" + \ - str(format_values["network_graph"]) + "" - format_values['kbs'] = "" + \ - str(format_values["kbs"]) + "" + format_values['network_graph'] = color_template.format(c_kbs, format_values["network_graph"]) + format_values['kbs'] = color_template.format(c_kbs, format_values["kbs"]) else: percent = int(kbs * 100 / limit) color = self.get_gradient(percent, self.colors, 100) diff --git a/i3pystatus/scratchpad.py b/i3pystatus/scratchpad.py index d3ab849..f6e4d9b 100644 --- a/i3pystatus/scratchpad.py +++ b/i3pystatus/scratchpad.py @@ -41,7 +41,7 @@ class Scratchpad(Module): t.daemon = True t.start() - def update_scratchpad_counter(self, conn, e=None): + def update_scratchpad_counter(self, conn, *args): cons = conn.get_tree().scratchpad().leaves() self.urgent = any(con for con in cons if con.urgent) self.count = len(cons) From 1f3581bceac485beb6d3fdfc94e768ad5691b5c3 Mon Sep 17 00:00:00 2001 From: limbe-osc Date: Sun, 7 Aug 2016 19:16:44 +0800 Subject: [PATCH 7/7] bug fix --- i3pystatus/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 5d7a8f4..92a5baa 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -396,7 +396,7 @@ class Network(IntervalModule, ColorRangeModule): c_recv = self.get_gradient(int(per_recv * 100), self.colors, 100) c_sent = self.get_gradient(int(per_sent * 100), self.colors, 100) format_values["bytes_recv"] = color_template.format(c_recv, network_usage["bytes_recv"]) - format_values["bytes_recv"] = color_template.format(c_sent, network_usage["bytes_sent"]) + format_values["bytes_sent"] = color_template.format(c_sent, network_usage["bytes_sent"]) if self.graph_type == 'output': c_kbs = c_sent else: