Merge pull request #423 from jk0dick/add-modules

* Network multi color
* Add scratchpad module
* Add window_title module
This commit is contained in:
facetoe 2016-08-07 19:41:29 +08:00 committed by GitHub
commit 45c10d0fe9
4 changed files with 236 additions and 20 deletions

View File

@ -3,3 +3,4 @@ sphinx>=1.1
colour>=0.0.5
mock>=1.0
pep8>=1.5.7
i3ipc>=1.2.0

View File

@ -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
"""
@ -231,6 +231,8 @@ 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.
Requires the PyPI packages `colour`, `netifaces`, `psutil` (optional, see below)
and `basiciw` (optional, see below).
@ -255,10 +257,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)
@ -267,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"),
@ -278,8 +281,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 +306,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 +326,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 +344,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 +362,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 +377,35 @@ 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
color_template = "<span color=\"{}\">{}</span>"
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"] = color_template.format(c_recv, network_usage["bytes_recv"])
format_values["bytes_sent"] = 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'] = 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)
else:
color = None
@ -395,6 +423,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,
}

74
i3pystatus/scratchpad.py Normal file
View File

@ -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, *args):
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()

113
i3pystatus/window_title.py Normal file
View File

@ -0,0 +1,113 @@
# -*- 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