This commit is contained in:
enkore 2013-10-01 15:22:09 +02:00
parent 21cd318c29
commit 78c01dd3e5
35 changed files with 416 additions and 162 deletions

View File

@ -16,10 +16,12 @@ __all__ = [
"formatp", "formatp",
] ]
def main(): def main():
parser = argparse.ArgumentParser(description="A replacement for i3status") parser = argparse.ArgumentParser(description="A replacement for i3status")
parser.add_argument("-c", "--config", action="store", help="Config file") parser.add_argument("-c", "--config", action="store", help="Config file")
parser.add_argument("-t", "--test", action="store_true", help="Test modules") parser.add_argument(
"-t", "--test", action="store_true", help="Test modules")
args = parser.parse_args() args = parser.parse_args()
config = Config(config_file=args.config) config = Config(config_file=args.config)

View File

@ -2,7 +2,9 @@ from alsaaudio import Mixer, ALSAAudioError
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class ALSA(IntervalModule): class ALSA(IntervalModule):
""" """
Shows volume of ALSA mixer. You can also use this for inputs, btw. Shows volume of ALSA mixer. You can also use this for inputs, btw.
@ -54,7 +56,8 @@ class ALSA(IntervalModule):
} }
def create_mixer(self): def create_mixer(self):
self.alsamixer = Mixer(control=self.mixer, id=self.mixer_id, cardindex=self.card) self.alsamixer = Mixer(
control=self.mixer, id=self.mixer_id, cardindex=self.card)
def run(self): def run(self):
self.create_mixer() self.create_mixer()
@ -62,10 +65,11 @@ class ALSA(IntervalModule):
muted = False muted = False
if self.has_mute: if self.has_mute:
muted = self.alsamixer.getmute()[self.channel] == 1 muted = self.alsamixer.getmute()[self.channel] == 1
self.fdict["volume"] = self.alsamixer.getvolume()[self.channel] self.fdict["volume"] = self.alsamixer.getvolume()[self.channel]
self.fdict["muted"] = self.muted if muted else self.muted self.fdict["muted"] = self.muted if muted else self.muted
self.output = { self.output = {
"full_text" : self.format.format(**self.fdict), "full_text": self.format.format(**self.fdict),
"color": self.color_muted if muted else self.color, "color": self.color_muted if muted else self.color,
} }

View File

@ -1,6 +1,8 @@
from i3pystatus.file import File from i3pystatus.file import File
class Backlight(File): class Backlight(File):
""" """
Screen backlight info Screen backlight info
@ -17,15 +19,15 @@ class Backlight(File):
) )
required = () required = ()
backlight="acpi_video0" backlight = "acpi_video0"
format="{brightness}/{max_brightness}" format = "{brightness}/{max_brightness}"
base_path = "/sys/class/backlight/{backlight}/" base_path = "/sys/class/backlight/{backlight}/"
components={ components = {
"brightness": (int, "brightness"), "brightness": (int, "brightness"),
"max_brightness": (int, "max_brightness"), "max_brightness": (int, "max_brightness"),
} }
transforms={ transforms = {
"percentage": lambda cdict: (cdict["brightness"] / cdict["max_brightness"]) * 100, "percentage": lambda cdict: (cdict["brightness"] / cdict["max_brightness"]) * 100,
} }

View File

@ -4,11 +4,13 @@
import re import re
import configparser import configparser
from i3pystatus import IntervalModule from i3pystatus import IntervalModule, formatp
from i3pystatus.core.util import PrefixedKeyDict, lchop, TimeWrapper from i3pystatus.core.util import PrefixedKeyDict, lchop, TimeWrapper
from i3pystatus.core.desktop import display_notification from i3pystatus.core.desktop import display_notification
class UEventParser(configparser.ConfigParser): class UEventParser(configparser.ConfigParser):
@staticmethod @staticmethod
def parse_file(file): def parse_file(file):
parser = UEventParser() parser = UEventParser()
@ -25,7 +27,9 @@ class UEventParser(configparser.ConfigParser):
def read_string(self, string): def read_string(self, string):
super().read_string("[id10t]\n" + string) super().read_string("[id10t]\n" + string)
class Battery: class Battery:
@staticmethod @staticmethod
def create(from_file): def create(from_file):
batinfo = UEventParser.parse_file(from_file) batinfo = UEventParser.parse_file(from_file)
@ -52,33 +56,41 @@ class Battery:
else: else:
return "Full" return "Full"
class BatteryCharge(Battery): class BatteryCharge(Battery):
def consumption(self): def consumption(self):
return self.bat["VOLTAGE_NOW"] * self.bat["CURRENT_NOW"] # V * A = W return self.bat["VOLTAGE_NOW"] * self.bat["CURRENT_NOW"] # V * A = W
def _percentage(self, design): def _percentage(self, design):
return self.bat["CHARGE_NOW"] / self.bat["CHARGE_FULL"+design] return self.bat["CHARGE_NOW"] / self.bat["CHARGE_FULL" + design]
def remaining(self): def remaining(self):
if self.status() == "Discharging": if self.status() == "Discharging":
return self.bat["CHARGE_NOW"] / self.bat["CURRENT_NOW"] * 60 # Ah / A = h * 60 min = min # Ah / A = h * 60 min = min
return self.bat["CHARGE_NOW"] / self.bat["CURRENT_NOW"] * 60
else: else:
return (self.bat["CHARGE_FULL"] - self.bat["CHARGE_NOW"]) / self.bat["CURRENT_NOW"] * 60 return (self.bat["CHARGE_FULL"] - self.bat["CHARGE_NOW"]) / self.bat["CURRENT_NOW"] * 60
class BatteryEnergy(Battery): class BatteryEnergy(Battery):
def consumption(self): def consumption(self):
return self.bat["POWER_NOW"] return self.bat["POWER_NOW"]
def _percentage(self, design): def _percentage(self, design):
return self.bat["ENERGY_NOW"] / self.bat["ENERGY_FULL"+design] return self.bat["ENERGY_NOW"] / self.bat["ENERGY_FULL" + design]
def remaining(self): def remaining(self):
if self.status() == "Discharging": if self.status() == "Discharging":
return self.bat["ENERGY_NOW"] / self.bat["POWER_NOW"] * 60 # Wh / W = h * 60 min = min # Wh / W = h * 60 min = min
return self.bat["ENERGY_NOW"] / self.bat["POWER_NOW"] * 60
else: else:
return (self.bat["ENERGY_FULL"] - self.bat["ENERGY_NOW"]) / self.bat["POWER_NOW"] * 60 return (self.bat["ENERGY_FULL"] - self.bat["ENERGY_NOW"]) / self.bat["POWER_NOW"] * 60
class BatteryChecker(IntervalModule): class BatteryChecker(IntervalModule):
""" """
This class uses the /sys/class/power_supply//uevent interface to check for the This class uses the /sys/class/power_supply//uevent interface to check for the
battery status battery status
@ -98,8 +110,10 @@ class BatteryChecker(IntervalModule):
"format", "format",
("alert", "Display a libnotify-notification on low battery"), ("alert", "Display a libnotify-notification on low battery"),
"alert_percentage", "alert_percentage",
("alert_format_title", "The title of the notification, all formatters can be used"), ("alert_format_title",
("alert_format_body", "The body text of the notification, all formatters can be used"), "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"), ("path", "Override the default-generated path"),
("status", "A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names"), ("status", "A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names"),
) )
@ -110,7 +124,7 @@ class BatteryChecker(IntervalModule):
"DIS": "DIS", "DIS": "DIS",
"FULL": "FULL", "FULL": "FULL",
} }
alert = False alert = False
alert_percentage = 10 alert_percentage = 10
alert_format_title = "Low battery" alert_format_title = "Low battery"
@ -120,7 +134,8 @@ class BatteryChecker(IntervalModule):
def init(self): def init(self):
if not self.path: if not self.path:
self.path = "/sys/class/power_supply/{0}/uevent".format(self.battery_ident) self.path = "/sys/class/power_supply/{0}/uevent".format(
self.battery_ident)
def run(self): def run(self):
urgent = False urgent = False
@ -152,8 +167,8 @@ class BatteryChecker(IntervalModule):
if self.alert and fdict["status"] == "DIS" and fdict["percentage"] <= self.alert_percentage: if self.alert and fdict["status"] == "DIS" and fdict["percentage"] <= self.alert_percentage:
display_notification( display_notification(
title=self.alert_format_title.format(**fdict), title=formatp(self.alert_format_title, **fdict),
body=self.alert_format_body.format(**fdict), body=formatp(self.alert_format_body, **fdict),
icon="battery-caution", icon="battery-caution",
urgency=2, urgency=2,
) )
@ -161,7 +176,7 @@ class BatteryChecker(IntervalModule):
fdict["status"] = self.status[fdict["status"]] fdict["status"] = self.status[fdict["status"]]
self.output = { self.output = {
"full_text": self.format.format(**fdict).strip(), "full_text": formatp(self.format, **fdict).strip(),
"instance": self.battery_ident, "instance": self.battery_ident,
"urgent": urgent, "urgent": urgent,
"color": color "color": color

View File

@ -6,7 +6,9 @@ from threading import Thread
from i3pystatus.core import io, util from i3pystatus.core import io, util
from i3pystatus.core.modules import Module, START_HOOKS from i3pystatus.core.modules import Module, START_HOOKS
class Status: class Status:
def __init__(self, standalone=False, interval=1, input_stream=sys.stdin): def __init__(self, standalone=False, interval=1, input_stream=sys.stdin):
self.standalone = standalone self.standalone = standalone
if standalone: if standalone:
@ -28,7 +30,7 @@ class Status:
return None return None
def run_command_endpoint(self): def run_command_endpoint(self):
for command in io.JSONIO(io=io.IOHandler(sys.stdin, open(os.devnull,"w")), skiplines=1).read(): for command in io.JSONIO(io=io.IOHandler(sys.stdin, open(os.devnull, "w")), skiplines=1).read():
module = self.modules.get_by_id(command["instance"]) module = self.modules.get_by_id(command["instance"])
if module: if module:
module.on_click(command["button"]) module.on_click(command["button"])

View File

@ -18,6 +18,7 @@ SEARCHPATH = (
class ConfigFinder: class ConfigFinder:
def __init__(self, searchpath=SEARCHPATH): def __init__(self, searchpath=SEARCHPATH):
self.searchpath = searchpath self.searchpath = searchpath
@ -37,10 +38,12 @@ class ConfigFinder:
else: else:
failed.append(path) failed.append(path)
raise RuntimeError("Didn't find a config file, tried\n * {mods}".format(mods="\n * ".join(failed))) raise RuntimeError(
"Didn't find a config file, tried\n * {mods}".format(mods="\n * ".join(failed)))
class Config: class Config:
def __init__(self, config_file=None): def __init__(self, config_file=None):
self.finder = ConfigFinder() self.finder = ConfigFinder()
self.config_file = config_file or self.finder.find_config_file() self.config_file = config_file or self.finder.find_config_file()
@ -52,12 +55,15 @@ class Config:
@contextlib.contextmanager @contextlib.contextmanager
def setup(): def setup():
import i3pystatus import i3pystatus
class TestStatus(i3pystatus.Status): class TestStatus(i3pystatus.Status):
def run(self): def run(self):
self.modules.reverse() self.modules.reverse()
self.call_start_hooks() self.call_start_hooks()
for module in self.modules: for module in self.modules:
sys.stdout.write("{module}: ".format(module=module.__name__)) sys.stdout.write(
"{module}: ".format(module=module.__name__))
sys.stdout.flush() sys.stdout.flush()
test = module.test() test = module.test()
if test is not True: if test is not True:
@ -72,5 +78,6 @@ class Config:
i3pystatus.Status = i3pystatus.Status.__bases__[0] i3pystatus.Status = i3pystatus.Status.__bases__[0]
with setup(): with setup():
print("Using configuration file {file}\n".format(file=self.config_file)) print(
"Using configuration file {file}\n".format(file=self.config_file))
self.run() self.run()

View File

@ -1,27 +1,34 @@
class ConfigError(Exception): class ConfigError(Exception):
"""ABC for configuration exceptions""" """ABC for configuration exceptions"""
def __init__(self, module, *args, **kwargs): def __init__(self, module, *args, **kwargs):
message = "Module '{0}': {1}".format(module, self.format(*args, **kwargs)) message = "Module '{0}': {1}".format(
module, self.format(*args, **kwargs))
super().__init__(message) super().__init__(message)
class ConfigKeyError(ConfigError, KeyError): class ConfigKeyError(ConfigError, KeyError):
def format(self, key): def format(self, key):
return "invalid option '{0}'".format(key) return "invalid option '{0}'".format(key)
class ConfigMissingError(ConfigError): class ConfigMissingError(ConfigError):
def format(self, missing): def format(self, missing):
return "missing required options: {0}".format(missing) return "missing required options: {0}".format(missing)
class ConfigAmbigiousClassesError(ConfigError): class ConfigAmbigiousClassesError(ConfigError):
def format(self, ambigious_classes): def format(self, ambigious_classes):
return "ambigious module specification, found multiple classes: {0}".format(ambigious_classes) return "ambigious module specification, found multiple classes: {0}".format(ambigious_classes)
class ConfigInvalidModuleError(ConfigError): class ConfigInvalidModuleError(ConfigError):
def format(self): def format(self):
return "no class found" return "no class found"

View File

@ -4,6 +4,7 @@ from importlib import import_module
class ClassFinder: class ClassFinder:
"""Support class to find classes of specific bases in a module""" """Support class to find classes of specific bases in a module"""
def __init__(self, baseclass): def __init__(self, baseclass):
@ -44,5 +45,6 @@ class ClassFinder:
elif inspect.isclass(module) and issubclass(module, self.baseclass): elif inspect.isclass(module) and issubclass(module, self.baseclass):
return module(*args, **kwargs) return module(*args, **kwargs)
elif args or kwargs: elif args or kwargs:
raise ValueError("Additional arguments are invalid if 'module' is already an object") raise ValueError(
"Additional arguments are invalid if 'module' is already an object")
return module return module

View File

@ -5,7 +5,9 @@ import sys
import threading import threading
from contextlib import contextmanager from contextlib import contextmanager
class IOHandler: class IOHandler:
def __init__(self, inp=sys.stdin, out=sys.stdout): def __init__(self, inp=sys.stdin, out=sys.stdout):
self.inp = inp self.inp = inp
self.out = out self.out = out
@ -42,7 +44,9 @@ class IOHandler:
raise EOFError() raise EOFError()
return line return line
class StandaloneIO(IOHandler): class StandaloneIO(IOHandler):
""" """
I/O handler for standalone usage of i3pystatus (w/o i3status) I/O handler for standalone usage of i3pystatus (w/o i3status)
@ -69,9 +73,11 @@ class StandaloneIO(IOHandler):
def read_line(self): def read_line(self):
self.n += 1 self.n += 1
return self.proto[min(self.n, len(self.proto)-1)] return self.proto[min(self.n, len(self.proto) - 1)]
class JSONIO: class JSONIO:
def __init__(self, io, skiplines=2): def __init__(self, io, skiplines=2):
self.io = io self.io = io
for i in range(skiplines): for i in range(skiplines):

View File

@ -9,6 +9,7 @@ __all__ = [
"Module", "AsyncModule", "IntervalModule", "Module", "AsyncModule", "IntervalModule",
] ]
class Module(SettingsBase): class Module(SettingsBase):
output = None output = None
position = 0 position = 0
@ -34,9 +35,9 @@ class Module(SettingsBase):
pass pass
def on_click(self, button): def on_click(self, button):
if button == 1: # Left mouse button if button == 1: # Left mouse button
self.on_leftclick() self.on_leftclick()
elif button == 3: # Right mouse button elif button == 3: # Right mouse button
self.on_rightclick() self.on_rightclick()
@chain @chain
@ -52,8 +53,9 @@ class Module(SettingsBase):
def __repr__(self): def __repr__(self):
return self.__class__.__name__ return self.__class__.__name__
class IntervalModule(Module): class IntervalModule(Module):
interval = 5 # seconds interval = 5 # seconds
managers = {} managers = {}
def registered(self, status_handler): def registered(self, status_handler):

View File

@ -1,7 +1,9 @@
from i3pystatus.core.util import KeyConstraintDict from i3pystatus.core.util import KeyConstraintDict
from i3pystatus.core.exceptions import ConfigKeyError, ConfigMissingError from i3pystatus.core.exceptions import ConfigKeyError, ConfigMissingError
class SettingsBase: class SettingsBase:
""" """
Support class for providing a nice and flexible settings interface Support class for providing a nice and flexible settings interface
@ -26,6 +28,7 @@ class SettingsBase:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
def flatten_setting(setting): def flatten_setting(setting):
return setting[0] if isinstance(setting, tuple) else setting return setting[0] if isinstance(setting, tuple) else setting
def flatten_settings(settings): def flatten_settings(settings):
return tuple(flatten_setting(setting) for setting in settings) return tuple(flatten_setting(setting) for setting in settings)
@ -44,14 +47,16 @@ class SettingsBase:
try: try:
sm.update(settings_source) sm.update(settings_source)
except KeyError as exc: except KeyError as exc:
raise ConfigKeyError(type(self).__name__, key=exc.args[0]) from exc raise ConfigKeyError(type(self).__name__, key=exc.args[0]) from exc
try: try:
self.__dict__.update(sm) self.__dict__.update(sm)
except KeyConstraintDict.MissingKeys as exc: except KeyConstraintDict.MissingKeys as exc:
raise ConfigMissingError(type(self).__name__, missing=exc.keys) from exc raise ConfigMissingError(
type(self).__name__, missing=exc.keys) from exc
self.__name__ = "{}.{}".format(self.__module__, self.__class__.__name__) self.__name__ = "{}.{}".format(
self.__module__, self.__class__.__name__)
self.init() self.init()

View File

@ -8,6 +8,7 @@ timer = time.perf_counter if hasattr(time, "perf_counter") else time.clock
class Thread(threading.Thread): class Thread(threading.Thread):
def __init__(self, target_interval, workloads=None, start_barrier=1): def __init__(self, target_interval, workloads=None, start_barrier=1):
super().__init__() super().__init__()
self.workloads = workloads or [] self.workloads = workloads or []
@ -36,7 +37,8 @@ class Thread(threading.Thread):
time.sleep(0.4) time.sleep(0.4)
def execute_workloads(self): def execute_workloads(self):
for workload in self: workload() for workload in self:
workload()
self.workloads.sort(key=lambda workload: workload.time) self.workloads.sort(key=lambda workload: workload.time)
def run(self): def run(self):
@ -54,6 +56,7 @@ class Thread(threading.Thread):
class Wrapper: class Wrapper:
def __init__(self, workload): def __init__(self, workload):
self.workload = workload self.workload = workload
@ -62,6 +65,7 @@ class Wrapper:
class ExceptionWrapper(Wrapper): class ExceptionWrapper(Wrapper):
def __call__(self): def __call__(self):
try: try:
self.workload() self.workload()
@ -84,6 +88,7 @@ class WorkloadWrapper(Wrapper):
class Manager: class Manager:
def __init__(self, target_interval): def __init__(self, target_interval):
self.target_interval = target_interval self.target_interval = target_interval
self.upper_bound = target_interval * 1.1 self.upper_bound = target_interval * 1.1
@ -108,7 +113,8 @@ class Manager:
return partition(workloads, self.lower_bound, lambda workload: workload.time) return partition(workloads, self.lower_bound, lambda workload: workload.time)
def create_threads(self, threads): def create_threads(self, threads):
for workloads in threads: self.create_thread(workloads) for workloads in threads:
self.create_thread(workloads)
def create_thread(self, workloads): def create_thread(self, workloads):
thread = Thread(self.target_interval, workloads, start_barrier=0) thread = Thread(self.target_interval, workloads, start_barrier=0)
@ -119,4 +125,5 @@ class Manager:
self.threads[0].append(self.wrap(workload)) self.threads[0].append(self.wrap(workload))
def start(self): def start(self):
for thread in self.threads: thread.start() for thread in self.threads:
thread.start()

View File

@ -7,17 +7,20 @@ import string
from i3pystatus.core.exceptions import * from i3pystatus.core.exceptions import *
from i3pystatus.core.imputil import ClassFinder from i3pystatus.core.imputil import ClassFinder
def chain(fun): def chain(fun):
def chained(self, *args, **kwargs): def chained(self, *args, **kwargs):
fun(self, *args, **kwargs) fun(self, *args, **kwargs)
return self return self
return chained return chained
def lchop(string, prefix): def lchop(string, prefix):
if string.startswith(prefix): if string.startswith(prefix):
return string[len(prefix):] return string[len(prefix):]
return string return string
def popwhile(predicate, iterable): def popwhile(predicate, iterable):
while iterable: while iterable:
item = iterable.pop() item = iterable.pop()
@ -26,6 +29,7 @@ def popwhile(predicate, iterable):
else: else:
break break
def partition(iterable, limit, key=lambda x: x): def partition(iterable, limit, key=lambda x: x):
def pop_partition(): def pop_partition():
sum = 0.0 sum = 0.0
@ -40,18 +44,22 @@ def partition(iterable, limit, key=lambda x: x):
return partitions return partitions
def round_dict(dic, places): def round_dict(dic, places):
for key, value in dic.items(): for key, value in dic.items():
dic[key] = round(value, places) dic[key] = round(value, places)
class ModuleList(collections.UserList): class ModuleList(collections.UserList):
def __init__(self, status_handler, module_base): def __init__(self, status_handler, module_base):
self.status_handler = status_handler self.status_handler = status_handler
self.finder = ClassFinder(module_base) self.finder = ClassFinder(module_base)
super().__init__() super().__init__()
def append(self, module, *args, **kwargs): def append(self, module, *args, **kwargs):
module = self.finder.instanciate_class_from_module(module, *args, **kwargs) module = self.finder.instanciate_class_from_module(
module, *args, **kwargs)
module.registered(self.status_handler) module.registered(self.status_handler)
super().append(module) super().append(module)
return module return module
@ -63,7 +71,9 @@ class ModuleList(collections.UserList):
return module return module
return None return None
class PrefixedKeyDict(collections.UserDict): class PrefixedKeyDict(collections.UserDict):
def __init__(self, prefix): def __init__(self, prefix):
super().__init__() super().__init__()
@ -72,8 +82,11 @@ class PrefixedKeyDict(collections.UserDict):
def __setitem__(self, key, value): def __setitem__(self, key, value):
super().__setitem__(self.prefix + key, value) super().__setitem__(self.prefix + key, value)
class KeyConstraintDict(collections.UserDict): class KeyConstraintDict(collections.UserDict):
class MissingKeys(Exception): class MissingKeys(Exception):
def __init__(self, keys): def __init__(self, keys):
self.keys = keys self.keys = keys
@ -104,11 +117,13 @@ class KeyConstraintDict(collections.UserDict):
def missing(self): def missing(self):
return self.required_keys - (self.seen_keys & self.required_keys) return self.required_keys - (self.seen_keys & self.required_keys)
def convert_position(pos, json): def convert_position(pos, json):
if pos < 0: if pos < 0:
pos = len(json) + (pos+1) pos = len(json) + (pos + 1)
return pos return pos
def flatten(l): def flatten(l):
l = list(l) l = list(l)
i = 0 i = 0
@ -123,6 +138,7 @@ def flatten(l):
i += 1 i += 1
return l return l
def formatp(string, **kwargs): def formatp(string, **kwargs):
""" """
Function for advanced format strings with partial formatting Function for advanced format strings with partial formatting
@ -149,17 +165,25 @@ def formatp(string, **kwargs):
""" """
class Token: class Token:
string = "" string = ""
def __repr__(self): def __repr__(self):
return "<%s> " % self.__class__.__name__ return "<%s> " % self.__class__.__name__
class OpeningBracket(Token): class OpeningBracket(Token):
def __repr__(self): def __repr__(self):
return "<Group>" return "<Group>"
class ClosingBracket(Token): class ClosingBracket(Token):
def __repr__(self): def __repr__(self):
return "</Group>" return "</Group>"
class String(Token): class String(Token):
def __init__(self, str): def __init__(self, str):
self.string = str self.string = str
def __repr__(self): def __repr__(self):
return super().__repr__() + repr(self.string) return super().__repr__() + repr(self.string)
@ -187,9 +211,11 @@ def formatp(string, **kwargs):
if prev != "\\" and char in TOKENS: if prev != "\\" and char in TOKENS:
token = TOKENS[char]() token = TOKENS[char]()
token.index = next token.index = next
if char == "]": level -= 1 if char == "]":
level -= 1
token.level = level token.level = level
if char == "[": level += 1 if char == "[":
level += 1
stack.append(token) stack.append(token)
else: else:
if stack and isinstance(stack[-1], String): if stack and isinstance(stack[-1], String):
@ -214,7 +240,7 @@ def formatp(string, **kwargs):
while items[0].level > level: while items[0].level > level:
nested.append(items.pop(0)) nested.append(items.pop(0))
if nested: if nested:
subtree.append(build_tree(nested, level+1)) subtree.append(build_tree(nested, level + 1))
item = items.pop(0) item = items.pop(0)
if item.string: if item.string:
@ -242,7 +268,9 @@ def formatp(string, **kwargs):
formatp.field_re = re.compile(r"({(\w+)[^}]*})") formatp.field_re = re.compile(r"({(\w+)[^}]*})")
class TimeWrapper: class TimeWrapper:
class TimeTemplate(string.Template): class TimeTemplate(string.Template):
delimiter = "%" delimiter = "%"
idpattern = r"[a-zA-Z]" idpattern = r"[a-zA-Z]"

View File

@ -3,7 +3,9 @@ import os
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
from .core.util import round_dict from .core.util import round_dict
class Disk(IntervalModule): class Disk(IntervalModule):
""" """
Gets `{used}`, `{free}`, `{available}` and `{total}` amount of bytes on the given mounted filesystem. Gets `{used}`, `{free}`, `{available}` and `{total}` amount of bytes on the given mounted filesystem.
@ -18,7 +20,7 @@ class Disk(IntervalModule):
required = ("path",) required = ("path",)
color = "#FFFFFF" color = "#FFFFFF"
format = "{free}/{avail}" format = "{free}/{avail}"
divisor = 1024**3 divisor = 1024 ** 3
def run(self): def run(self):
cdict = {} cdict = {}

View File

@ -2,7 +2,9 @@ from os.path import join
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class File(IntervalModule): class File(IntervalModule):
""" """
Rip information from text files Rip information from text files

View File

@ -1,13 +1,16 @@
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class Load(IntervalModule): class Load(IntervalModule):
""" """
Shows system load Shows system load
""" """
format = "{avg1} {avg5}" format = "{avg1} {avg5}"
settings = ( settings = (
("format", "format string used for output. {avg1}, {avg5} and {avg15} are the load average of the last one, five and fifteen minutes, respectively. {tasks} is the number of tasks (i.e. 1/285, which indiciates that one out of 285 total tasks is runnable)."), ("format",
"format string used for output. {avg1}, {avg5} and {avg15} are the load average of the last one, five and fifteen minutes, respectively. {tasks} is the number of tasks (i.e. 1/285, which indiciates that one out of 285 total tasks is runnable)."),
) )
file = "/proc/loadavg" file = "/proc/loadavg"
@ -17,5 +20,5 @@ class Load(IntervalModule):
avg1, avg5, avg15, tasks, lastpid = f.read().split(" ", 5) avg1, avg5, avg15, tasks, lastpid = f.read().split(" ", 5)
self.output = { self.output = {
"full_text" : self.format.format(avg1=avg1, avg5=avg5, avg15=avg15, tasks=tasks), "full_text": self.format.format(avg1=avg1, avg5=avg5, avg15=avg15, tasks=tasks),
} }

View File

@ -1,7 +1,9 @@
from i3pystatus import SettingsBase, IntervalModule from i3pystatus import SettingsBase, IntervalModule
class Backend(SettingsBase): class Backend(SettingsBase):
"""Handles the details of checking for mail""" """Handles the details of checking for mail"""
unread = 0 unread = 0
@ -9,7 +11,9 @@ class Backend(SettingsBase):
You'll probably implement that as a property""" You'll probably implement that as a property"""
class Mail(IntervalModule): class Mail(IntervalModule):
""" """
Generic mail checker Generic mail checker
@ -29,7 +33,7 @@ class Mail(IntervalModule):
required = ("backends",) required = ("backends",)
color = "#ffffff" color = "#ffffff"
color_unread ="#ff0000" color_unread = "#ff0000"
format = "{unread} new email" format = "{unread} new email"
format_plural = "{unread} new emails" format_plural = "{unread} new emails"
hide_if_null = True hide_if_null = True
@ -56,7 +60,7 @@ class Mail(IntervalModule):
format = self.format_plural format = self.format_plural
self.output = { self.output = {
"full_text" : format.format(unread=unread), "full_text": format.format(unread=unread),
"urgent" : urgent, "urgent": urgent,
"color" : color, "color": color,
} }

View File

@ -3,16 +3,18 @@
import sys import sys
import json import json
from datetime import datetime,timedelta from datetime import datetime, timedelta
import imaplib import imaplib
from i3pystatus.mail import Backend from i3pystatus.mail import Backend
class IMAP(Backend): class IMAP(Backend):
""" """
Checks for mail on a IMAP server Checks for mail on a IMAP server
""" """
settings = ( settings = (
"host", "port", "host", "port",
"username", "password", "username", "password",
@ -52,7 +54,7 @@ class IMAP(Backend):
def unread(self): def unread(self):
conn = self.get_connection() conn = self.get_connection()
if conn: if conn:
return len(conn.search(None,"UnSeen")[1][0].split()) return len(conn.search(None, "UnSeen")[1][0].split())
else: else:
sys.stderr.write("no connection") sys.stderr.write("no connection")

View File

@ -8,7 +8,9 @@ import json
from i3pystatus.mail import Backend from i3pystatus.mail import Backend
class Notmuch(Backend): class Notmuch(Backend):
""" """
This class uses the notmuch python bindings to check for the This class uses the notmuch python bindings to check for the
number of messages in the notmuch database with the tags "inbox" number of messages in the notmuch database with the tags "inbox"

View File

@ -18,7 +18,9 @@ from gi.repository import GObject
from i3pystatus.mail import Backend from i3pystatus.mail import Backend
class Thunderbird(Backend): class Thunderbird(Backend):
""" """
This class listens for dbus signals emitted by This class listens for dbus signals emitted by
the dbus-sender extension for thunderbird. the dbus-sender extension for thunderbird.

View File

@ -23,6 +23,7 @@ __Settings:__
{endstring}\n""" {endstring}\n"""
class Module: class Module:
name = "" name = ""
doc = "" doc = ""
@ -36,7 +37,8 @@ class Module:
if neighbours == 1: if neighbours == 1:
self.name = module_name self.name = module_name
else: else:
self.name = "{module}.{cls}".format(module=module_name, cls=self.cls.__name__) self.name = "{module}.{cls}".format(
module=module_name, cls=self.cls.__name__)
self.doc = self.cls.__doc__ or module.__doc__ or "" self.doc = self.cls.__doc__ or module.__doc__ or ""
@ -61,6 +63,7 @@ class Module:
endstring=self.endstring endstring=self.endstring
) )
class Setting: class Setting:
doc = "" doc = ""
required = False required = False
@ -95,6 +98,7 @@ class Setting:
return formatted return formatted
def get_modules(path): def get_modules(path):
modules = [] modules = []
for finder, modname, ispkg in pkgutil.iter_modules(path): for finder, modname, ispkg in pkgutil.iter_modules(path):
@ -102,11 +106,13 @@ def get_modules(path):
modules.append(get_module(finder, modname)) modules.append(get_module(finder, modname))
return modules return modules
def get_module(finder, modname): def get_module(finder, modname):
fullname = "i3pystatus.{modname}".format(modname=modname) fullname = "i3pystatus.{modname}".format(modname=modname)
return (modname, finder.find_loader(fullname)[0].load_module(fullname)) return (modname, finder.find_loader(fullname)[0].load_module(fullname))
def get_all(module_path, heading, finder=None):
def get_all(module_path, heading, finder=None, ignore=None):
mods = [] mods = []
if not finder: if not finder:
finder = ClassFinder(i3pystatus.Module) finder = ClassFinder(i3pystatus.Module)
@ -117,17 +123,21 @@ def get_all(module_path, heading, finder=None):
for cls in classes: for cls in classes:
if cls.__name__ not in found: if cls.__name__ not in found:
found.append(cls.__name__) found.append(cls.__name__)
mods.append(Module(cls, neighbours=len(classes), module_name=name, module=module, heading=heading)) mods.append(
Module(cls, neighbours=len(classes), module_name=name, module=module, heading=heading))
return sorted(mods, key=lambda module: module.name) return sorted(mods, key=lambda module: module.name)
def generate_doc_for_module(module_path, heading="###", finder=None):
return "".join(map(str, get_all(module_path, heading, finder))) def generate_doc_for_module(module_path, heading="###", finder=None, ignore=None):
return "".join(map(str, get_all(module_path, heading, finder, ignore or [])))
with open("README.tpl.md", "r") as template: with open("README.tpl.md", "r") as template:
tpl = template.read() tpl = template.read()
tpl = tpl.replace("!!module_doc!!", generate_doc_for_module(i3pystatus.__path__)) tpl = tpl.replace(
"!!module_doc!!", generate_doc_for_module(i3pystatus.__path__))
finder = ClassFinder(baseclass=i3pystatus.mail.Backend) finder = ClassFinder(baseclass=i3pystatus.mail.Backend)
tpl = tpl.replace("!!i3pystatus.mail!!", generate_doc_for_module(i3pystatus.mail.__path__, "###", finder).replace("\n", "\n> ")) tpl = tpl.replace("!!i3pystatus.mail!!", generate_doc_for_module(
i3pystatus.mail.__path__, "###", finder, ["Backend"]).replace("\n", "\n> "))
with open("README.md", "w") as output: with open("README.md", "w") as output:
output.write(tpl + "\n") output.write(tpl + "\n")

View File

@ -4,7 +4,9 @@ import sys
import json import json
import time import time
import threading import threading
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import re import re
import http.cookiejar import http.cookiejar
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@ -12,14 +14,17 @@ import webbrowser
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class ModsDeChecker(IntervalModule): class ModsDeChecker(IntervalModule):
""" """
This class returns i3status parsable output of the number of This class returns i3status parsable output of the number of
unread posts in any bookmark in the mods.de forums. unread posts in any bookmark in the mods.de forums.
""" """
settings = ( settings = (
("format", """Use {unread} as the formatter for number of unread posts"""), ("format",
"""Use {unread} as the formatter for number of unread posts"""),
("offset", """subtract number of posts before output"""), ("offset", """subtract number of posts before output"""),
"color", "username", "password" "color", "username", "password"
) )
@ -37,7 +42,8 @@ class ModsDeChecker(IntervalModule):
def init(self): def init(self):
self.cj = http.cookiejar.CookieJar() self.cj = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) self.opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cj))
def run(self): def run(self):
unread = self.get_unread_count() unread = self.get_unread_count()
@ -46,9 +52,9 @@ class ModsDeChecker(IntervalModule):
self.output = None self.output = None
else: else:
self.output = { self.output = {
"full_text" : self.format.format(unread=unread), "full_text": self.format.format(unread=unread),
"urgent" : "true", "urgent": "true",
"color" : self.color "color": self.color
} }
def get_unread_count(self): def get_unread_count(self):
@ -61,7 +67,8 @@ class ModsDeChecker(IntervalModule):
return int(root.attrib["newposts"]) - self.offset return int(root.attrib["newposts"]) - self.offset
except Exception: except Exception:
self.cj.clear() self.cj.clear()
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) self.opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cj))
self.logged_in = False self.logged_in = False
def test(self): def test(self):
@ -92,7 +99,8 @@ class ModsDeChecker(IntervalModule):
for cookie in self.cj: for cookie in self.cj:
self.cj.clear self.cj.clear
self.logged_in = True self.logged_in = True
self.opener.addheaders.append(("Cookie", "{}={}".format(cookie.name, cookie.value))) self.opener.addheaders.append(
("Cookie", "{}={}".format(cookie.name, cookie.value)))
return True return True
return False return False

View File

@ -4,10 +4,13 @@ import socket
from i3pystatus import IntervalModule, formatp from i3pystatus import IntervalModule, formatp
from i3pystatus.core.util import TimeWrapper from i3pystatus.core.util import TimeWrapper
def format_time(seconds): def format_time(seconds):
return "{}:{:02}".format(*divmod(int(seconds), 60)) if seconds else "" return "{}:{:02}".format(*divmod(int(seconds), 60)) if seconds else ""
class MPD(IntervalModule): class MPD(IntervalModule):
""" """
Displays various information from MPD (the music player daemon) Displays various information from MPD (the music player daemon)
@ -71,7 +74,7 @@ class MPD(IntervalModule):
currentsong = self._mpd_command(s, "currentsong") currentsong = self._mpd_command(s, "currentsong")
fdict = { fdict = {
"pos": int(status.get("song", 0))+1, "pos": int(status.get("song", 0)) + 1,
"len": int(status["playlistlength"]), "len": int(status["playlistlength"]),
"status": self.status[status["state"]], "status": self.status[status["state"]],
"volume": int(status["volume"]), "volume": int(status["volume"]),
@ -92,13 +95,14 @@ class MPD(IntervalModule):
def on_leftclick(self): def on_leftclick(self):
with socket.create_connection(("localhost", self.port)) as s: with socket.create_connection(("localhost", self.port)) as s:
s.recv(8192) s.recv(8192)
self._mpd_command(s, "pause %i" % (0 if self._mpd_command(s, "status")["state"] == "pause" else 1)) self._mpd_command(s, "pause %i" %
(0 if self._mpd_command(s, "status")["state"] == "pause" else 1))
def on_rightclick(self): def on_rightclick(self):
with socket.create_connection(("localhost", self.port)) as s: with socket.create_connection(("localhost", self.port)) as s:
s.recv(8192) s.recv(8192)
vol = int(self._mpd_command(s, "status")["volume"]) vol = int(self._mpd_command(s, "status")["volume"])
if vol == 0: if vol == 0:
self._mpd_command(s, "setvol %i" % self.vol) self._mpd_command(s, "setvol %i" % self.vol)

View File

@ -7,6 +7,8 @@ import netifaces
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
# Remainder: if we raise minimum Python version to 3.3, use ipaddress module # Remainder: if we raise minimum Python version to 3.3, use ipaddress module
def count_bits(integer): def count_bits(integer):
bits = 0 bits = 0
while(integer): while(integer):
@ -14,30 +16,38 @@ def count_bits(integer):
bits += 1 bits += 1
return bits return bits
def v6_to_int(v6): def v6_to_int(v6):
return int(v6.replace(":", ""), 16) return int(v6.replace(":", ""), 16)
def prefix6(mask): def prefix6(mask):
return count_bits(v6_to_int(mask)) return count_bits(v6_to_int(mask))
def cidr6(addr, mask): def cidr6(addr, mask):
return "{addr}/{bits}".format(addr=addr, bits=prefix6(mask)) return "{addr}/{bits}".format(addr=addr, bits=prefix6(mask))
def v4_to_int(v4): def v4_to_int(v4):
sum = 0 sum = 0
mul = 1 mul = 1
for part in reversed(v4.split(".")): for part in reversed(v4.split(".")):
sum += int(part) * mul sum += int(part) * mul
mul *= 2**8 mul *= 2 ** 8
return sum return sum
def prefix4(mask): def prefix4(mask):
return count_bits(v4_to_int(mask)) return count_bits(v4_to_int(mask))
def cidr4(addr, mask): def cidr4(addr, mask):
return "{addr}/{bits}".format(addr=addr, bits=prefix4(mask)) return "{addr}/{bits}".format(addr=addr, bits=prefix4(mask))
class Network(IntervalModule): class Network(IntervalModule):
""" """
Display network information about a interface. Display network information about a interface.
@ -72,7 +82,8 @@ class Network(IntervalModule):
def init(self): def init(self):
if self.interface not in netifaces.interfaces(): if self.interface not in netifaces.interfaces():
raise RuntimeError("Unknown interface {iface}!".format(iface=self.interface)) raise RuntimeError(
"Unknown interface {iface}!".format(iface=self.interface))
self.baseinfo = { self.baseinfo = {
"interface": self.interface, "interface": self.interface,
@ -83,7 +94,8 @@ class Network(IntervalModule):
def collect(self): def collect(self):
info = netifaces.ifaddresses(self.interface) info = netifaces.ifaddresses(self.interface)
up = netifaces.AF_INET in info or netifaces.AF_INET6 in info up = netifaces.AF_INET in info or netifaces.AF_INET6 in info
fdict = dict(zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue="")) fdict = dict(
zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue=""))
fdict.update(self.baseinfo) fdict.update(self.baseinfo)
if up: if up:

View File

@ -7,15 +7,18 @@ from lxml.cssselect import CSSSelector
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class TrackerAPI: class TrackerAPI:
def __init__(self, idcode): def __init__(self, idcode):
pass pass
def status(self): def status(self):
return {} return {}
class DHL(TrackerAPI): class DHL(TrackerAPI):
URL="http://nolp.dhl.de/nextt-online-public/set_identcodes.do?lang=en&idc={idcode}" URL = "http://nolp.dhl.de/nextt-online-public/set_identcodes.do?lang=en&idc={idcode}"
def __init__(self, idcode): def __init__(self, idcode):
self.idcode = idcode self.idcode = idcode
@ -23,7 +26,8 @@ class DHL(TrackerAPI):
error_selector = CSSSelector("#set_identcodes .error") error_selector = CSSSelector("#set_identcodes .error")
self.error = lambda page: len(error_selector(page)) >= 1 self.error = lambda page: len(error_selector(page)) >= 1
self.progress_selector = CSSSelector(".greyprogressbar > span, .greenprogressbar > span") self.progress_selector = CSSSelector(
".greyprogressbar > span, .greenprogressbar > span")
self.last_status_selector = CSSSelector(".events .eventList tr") self.last_status_selector = CSSSelector(".events .eventList tr")
self.intrarow_status_selector = CSSSelector("td.status div") self.intrarow_status_selector = CSSSelector("td.status div")
@ -36,14 +40,16 @@ class DHL(TrackerAPI):
else: else:
ret["progress"] = self.progress_selector(page)[0].text.strip() ret["progress"] = self.progress_selector(page)[0].text.strip()
last_row = self.last_status_selector(page)[-1] last_row = self.last_status_selector(page)[-1]
ret["status"] = self.intrarow_status_selector(last_row)[0].text.strip() ret["status"] = self.intrarow_status_selector(
last_row)[0].text.strip()
return ret return ret
def get_url(self): def get_url(self):
return self.url return self.url
class UPS(TrackerAPI): class UPS(TrackerAPI):
URL="http://wwwapps.ups.com/WebTracking/processRequest?HTMLVersion=5.0&Requester=NES&AgreeToTermsAndConditions=yes&loc=en_US&tracknum={idcode}" URL = "http://wwwapps.ups.com/WebTracking/processRequest?HTMLVersion=5.0&Requester=NES&AgreeToTermsAndConditions=yes&loc=en_US&tracknum={idcode}"
def __init__(self, idcode): def __init__(self, idcode):
self.idcode = idcode self.idcode = idcode
@ -62,13 +68,15 @@ class UPS(TrackerAPI):
ret["progress"] = ret["status"] = "n/a" ret["progress"] = ret["status"] = "n/a"
else: else:
ret["status"] = self.status_selector(page)[0].text.strip() ret["status"] = self.status_selector(page)[0].text.strip()
progress_cls = int(int(self.progress_selector(page)[0].get("class").strip("staus")) / 5 * 100) progress_cls = int(
ret["progress"] = progress_cls int(self.progress_selector(page)[0].get("class").strip("staus")) / 5 * 100)
ret["progress"] = progress_cls
return ret return ret
def get_url(self): def get_url(self):
return self.url return self.url
class ParcelTracker(IntervalModule): class ParcelTracker(IntervalModule):
interval = 20 interval = 20

View File

@ -2,7 +2,9 @@ from .pulse import *
from i3pystatus import Module from i3pystatus import Module
class PulseAudio(Module): class PulseAudio(Module):
""" """
Shows volume of default PulseAudio sink (output). Shows volume of default PulseAudio sink (output).
@ -22,10 +24,11 @@ class PulseAudio(Module):
"""Creates context, when context is ready context_notify_cb is called""" """Creates context, when context is ready context_notify_cb is called"""
# Wrap callback methods in appropriate ctypefunc instances so # Wrap callback methods in appropriate ctypefunc instances so
# that the Pulseaudio C API can call them # that the Pulseaudio C API can call them
self._context_notify_cb = pa_context_notify_cb_t(self.context_notify_cb) self._context_notify_cb = pa_context_notify_cb_t(
self.context_notify_cb)
self._sink_info_cb = pa_sink_info_cb_t(self.sink_info_cb) self._sink_info_cb = pa_sink_info_cb_t(self.sink_info_cb)
self._update_cb = pa_context_subscribe_cb_t(self.update_cb) self._update_cb = pa_context_subscribe_cb_t(self.update_cb)
self._success_cb = pa_context_success_cb_t (self.success_cb) self._success_cb = pa_context_success_cb_t(self.success_cb)
self._server_info_cb = pa_server_info_cb_t(self.server_info_cb) self._server_info_cb = pa_server_info_cb_t(self.server_info_cb)
# Create the mainloop thread and set our context_notify_cb # Create the mainloop thread and set our context_notify_cb
@ -41,7 +44,8 @@ class PulseAudio(Module):
def request_update(self, context): def request_update(self, context):
"""Requests a sink info update (sink_info_cb is called)""" """Requests a sink info update (sink_info_cb is called)"""
pa_operation_unref(pa_context_get_sink_info_by_name(context, self.sink, self._sink_info_cb, None)) pa_operation_unref(pa_context_get_sink_info_by_name(
context, self.sink, self._sink_info_cb, None))
def success_cb(self, context, success, userdata): def success_cb(self, context, success, userdata):
pass pass
@ -63,11 +67,13 @@ class PulseAudio(Module):
state = pa_context_get_state(context) state = pa_context_get_state(context)
if state == PA_CONTEXT_READY: if state == PA_CONTEXT_READY:
pa_operation_unref(pa_context_get_server_info(context, self._server_info_cb, None)) pa_operation_unref(
pa_context_get_server_info(context, self._server_info_cb, None))
pa_context_set_subscribe_callback(context, self._update_cb, None) pa_context_set_subscribe_callback(context, self._update_cb, None)
pa_operation_unref(pa_context_subscribe(context, PA_SUBSCRIPTION_EVENT_CHANGE|PA_SUBSCRIPTION_MASK_SINK, self._success_cb, None)) pa_operation_unref(pa_context_subscribe(
context, PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_MASK_SINK, self._success_cb, None))
def update_cb(self, context, t, idx, userdata): def update_cb(self, context, t, idx, userdata):
"""A sink property changed, calls request_update""" """A sink property changed, calls request_update"""
@ -77,7 +83,7 @@ class PulseAudio(Module):
"""Updates self.output""" """Updates self.output"""
if sink_info_p: if sink_info_p:
sink_info = sink_info_p.contents sink_info = sink_info_p.contents
volume_percent = int(100 * sink_info.volume.values[0]/0x10000) volume_percent = int(100 * sink_info.volume.values[0] / 0x10000)
volume_db = pa_sw_volume_to_dB(sink_info.volume.values[0]) volume_db = pa_sw_volume_to_dB(sink_info.volume.values[0])
if volume_db == float('-Infinity'): if volume_db == float('-Infinity'):
volume_db = "-∞" volume_db = "-∞"

View File

@ -20,48 +20,64 @@ PA_OPERATION_RUNNING = 0
PA_SUBSCRIPTION_EVENT_CHANGE = 16 PA_SUBSCRIPTION_EVENT_CHANGE = 16
PA_SUBSCRIPTION_MASK_SINK = 1 PA_SUBSCRIPTION_MASK_SINK = 1
class pa_sink_port_info(Structure): class pa_sink_port_info(Structure):
pass pass
class pa_format_info(Structure): class pa_format_info(Structure):
pass pass
class pa_context(Structure): class pa_context(Structure):
pass pass
pa_context._fields_ = [ pa_context._fields_ = [
] ]
pa_context_notify_cb_t = CFUNCTYPE(None, POINTER(pa_context), c_void_p) pa_context_notify_cb_t = CFUNCTYPE(None, POINTER(pa_context), c_void_p)
pa_context_success_cb_t = CFUNCTYPE(None, POINTER(pa_context), c_int, c_void_p) pa_context_success_cb_t = CFUNCTYPE(None, POINTER(pa_context), c_int, c_void_p)
class pa_proplist(Structure): class pa_proplist(Structure):
pass pass
pa_context_event_cb_t = CFUNCTYPE(None, POINTER(pa_context), STRING, POINTER(pa_proplist), c_void_p) pa_context_event_cb_t = CFUNCTYPE(
None, POINTER(pa_context), STRING, POINTER(pa_proplist), c_void_p)
class pa_mainloop_api(Structure): class pa_mainloop_api(Structure):
pass pass
pa_context_new = _libraries['libpulse.so.0'].pa_context_new pa_context_new = _libraries['libpulse.so.0'].pa_context_new
pa_context_new.restype = POINTER(pa_context) pa_context_new.restype = POINTER(pa_context)
pa_context_new.argtypes = [POINTER(pa_mainloop_api), STRING] pa_context_new.argtypes = [POINTER(pa_mainloop_api), STRING]
pa_context_new_with_proplist = _libraries['libpulse.so.0'].pa_context_new_with_proplist pa_context_new_with_proplist = _libraries[
'libpulse.so.0'].pa_context_new_with_proplist
pa_context_new_with_proplist.restype = POINTER(pa_context) pa_context_new_with_proplist.restype = POINTER(pa_context)
pa_context_new_with_proplist.argtypes = [POINTER(pa_mainloop_api), STRING, POINTER(pa_proplist)] pa_context_new_with_proplist.argtypes = [
POINTER(pa_mainloop_api), STRING, POINTER(pa_proplist)]
pa_context_unref = _libraries['libpulse.so.0'].pa_context_unref pa_context_unref = _libraries['libpulse.so.0'].pa_context_unref
pa_context_unref.restype = None pa_context_unref.restype = None
pa_context_unref.argtypes = [POINTER(pa_context)] pa_context_unref.argtypes = [POINTER(pa_context)]
pa_context_ref = _libraries['libpulse.so.0'].pa_context_ref pa_context_ref = _libraries['libpulse.so.0'].pa_context_ref
pa_context_ref.restype = POINTER(pa_context) pa_context_ref.restype = POINTER(pa_context)
pa_context_ref.argtypes = [POINTER(pa_context)] pa_context_ref.argtypes = [POINTER(pa_context)]
pa_context_set_state_callback = _libraries['libpulse.so.0'].pa_context_set_state_callback pa_context_set_state_callback = _libraries[
'libpulse.so.0'].pa_context_set_state_callback
pa_context_set_state_callback.restype = None pa_context_set_state_callback.restype = None
pa_context_set_state_callback.argtypes = [POINTER(pa_context), pa_context_notify_cb_t, c_void_p] pa_context_set_state_callback.argtypes = [
POINTER(pa_context), pa_context_notify_cb_t, c_void_p]
# values for enumeration 'pa_context_state' # values for enumeration 'pa_context_state'
pa_context_state = c_int # enum pa_context_state = c_int # enum
pa_context_state_t = pa_context_state pa_context_state_t = pa_context_state
pa_context_get_state = _libraries['libpulse.so.0'].pa_context_get_state pa_context_get_state = _libraries['libpulse.so.0'].pa_context_get_state
pa_context_get_state.restype = pa_context_state_t pa_context_get_state.restype = pa_context_state_t
pa_context_get_state.argtypes = [POINTER(pa_context)] pa_context_get_state.argtypes = [POINTER(pa_context)]
# values for enumeration 'pa_context_flags' # values for enumeration 'pa_context_flags'
pa_context_flags = c_int # enum pa_context_flags = c_int # enum
pa_context_flags_t = pa_context_flags pa_context_flags_t = pa_context_flags
class pa_spawn_api(Structure): class pa_spawn_api(Structure):
_fields_ = [ _fields_ = [
('prefork', CFUNCTYPE(None)), ('prefork', CFUNCTYPE(None)),
@ -71,13 +87,17 @@ class pa_spawn_api(Structure):
pa_context_connect = _libraries['libpulse.so.0'].pa_context_connect pa_context_connect = _libraries['libpulse.so.0'].pa_context_connect
pa_context_connect.restype = c_int pa_context_connect.restype = c_int
pa_context_connect.argtypes = [POINTER(pa_context), STRING, pa_context_flags_t, POINTER(pa_spawn_api)] pa_context_connect.argtypes = [
POINTER(pa_context), STRING, pa_context_flags_t, POINTER(pa_spawn_api)]
pa_context_disconnect = _libraries['libpulse.so.0'].pa_context_disconnect pa_context_disconnect = _libraries['libpulse.so.0'].pa_context_disconnect
pa_context_disconnect.restype = None pa_context_disconnect.restype = None
pa_context_disconnect.argtypes = [POINTER(pa_context)] pa_context_disconnect.argtypes = [POINTER(pa_context)]
class pa_operation(Structure): class pa_operation(Structure):
pass pass
class pa_sample_spec(Structure): class pa_sample_spec(Structure):
_fields_ = [ _fields_ = [
('format', c_int), ('format', c_int),
@ -86,27 +106,31 @@ class pa_sample_spec(Structure):
] ]
# values for enumeration 'pa_subscription_mask' # values for enumeration 'pa_subscription_mask'
pa_subscription_mask = c_int # enum pa_subscription_mask = c_int # enum
pa_subscription_mask_t = pa_subscription_mask pa_subscription_mask_t = pa_subscription_mask
# values for enumeration 'pa_subscription_event_type' # values for enumeration 'pa_subscription_event_type'
pa_subscription_event_type = c_int # enum pa_subscription_event_type = c_int # enum
pa_subscription_event_type_t = pa_subscription_event_type pa_subscription_event_type_t = pa_subscription_event_type
pa_context_subscribe_cb_t = CFUNCTYPE(None, POINTER(pa_context), pa_subscription_event_type_t, c_uint32, c_void_p) pa_context_subscribe_cb_t = CFUNCTYPE(
None, POINTER(pa_context), pa_subscription_event_type_t, c_uint32, c_void_p)
pa_context_subscribe = _libraries['libpulse.so.0'].pa_context_subscribe pa_context_subscribe = _libraries['libpulse.so.0'].pa_context_subscribe
pa_context_subscribe.restype = POINTER(pa_operation) pa_context_subscribe.restype = POINTER(pa_operation)
pa_context_subscribe.argtypes = [POINTER(pa_context), pa_subscription_mask_t, pa_context_success_cb_t, c_void_p] pa_context_subscribe.argtypes = [
pa_context_set_subscribe_callback = _libraries['libpulse.so.0'].pa_context_set_subscribe_callback POINTER(pa_context), pa_subscription_mask_t, pa_context_success_cb_t, c_void_p]
pa_context_set_subscribe_callback = _libraries[
'libpulse.so.0'].pa_context_set_subscribe_callback
pa_context_set_subscribe_callback.restype = None pa_context_set_subscribe_callback.restype = None
pa_context_set_subscribe_callback.argtypes = [POINTER(pa_context), pa_context_subscribe_cb_t, c_void_p] pa_context_set_subscribe_callback.argtypes = [
POINTER(pa_context), pa_context_subscribe_cb_t, c_void_p]
# values for enumeration 'pa_sink_flags' # values for enumeration 'pa_sink_flags'
pa_sink_flags = c_int # enum pa_sink_flags = c_int # enum
pa_sink_flags_t = pa_sink_flags pa_sink_flags_t = pa_sink_flags
# values for enumeration 'pa_sink_state' # values for enumeration 'pa_sink_state'
pa_sink_state = c_int # enum pa_sink_state = c_int # enum
pa_sink_state_t = pa_sink_state pa_sink_state_t = pa_sink_state
pa_free_cb_t = CFUNCTYPE(None, c_void_p) pa_free_cb_t = CFUNCTYPE(None, c_void_p)
@ -114,14 +138,18 @@ pa_strerror = _libraries['libpulse.so.0'].pa_strerror
pa_strerror.restype = STRING pa_strerror.restype = STRING
pa_strerror.argtypes = [c_int] pa_strerror.argtypes = [c_int]
class pa_sink_info(Structure): class pa_sink_info(Structure):
pass pass
class pa_cvolume(Structure): class pa_cvolume(Structure):
_fields_ = [ _fields_ = [
('channels', c_uint8), ('channels', c_uint8),
('values', pa_volume_t * 32), ('values', pa_volume_t * 32),
] ]
class pa_channel_map(Structure): class pa_channel_map(Structure):
_fields_ = [ _fields_ = [
('channels', c_uint8), ('channels', c_uint8),
@ -153,16 +181,24 @@ pa_sink_info._fields_ = [
('n_formats', c_uint8), ('n_formats', c_uint8),
('formats', POINTER(POINTER(pa_format_info))), ('formats', POINTER(POINTER(pa_format_info))),
] ]
pa_sink_info_cb_t = CFUNCTYPE(None, POINTER(pa_context), POINTER(pa_sink_info), c_int, c_void_p) pa_sink_info_cb_t = CFUNCTYPE(
pa_context_get_sink_info_by_name = _libraries['libpulse.so.0'].pa_context_get_sink_info_by_name None, POINTER(pa_context), POINTER(pa_sink_info), c_int, c_void_p)
pa_context_get_sink_info_by_name = _libraries[
'libpulse.so.0'].pa_context_get_sink_info_by_name
pa_context_get_sink_info_by_name.restype = POINTER(pa_operation) pa_context_get_sink_info_by_name.restype = POINTER(pa_operation)
pa_context_get_sink_info_by_name.argtypes = [POINTER(pa_context), STRING, pa_sink_info_cb_t, c_void_p] pa_context_get_sink_info_by_name.argtypes = [
pa_context_get_sink_info_by_index = _libraries['libpulse.so.0'].pa_context_get_sink_info_by_index POINTER(pa_context), STRING, pa_sink_info_cb_t, c_void_p]
pa_context_get_sink_info_by_index = _libraries[
'libpulse.so.0'].pa_context_get_sink_info_by_index
pa_context_get_sink_info_by_index.restype = POINTER(pa_operation) pa_context_get_sink_info_by_index.restype = POINTER(pa_operation)
pa_context_get_sink_info_by_index.argtypes = [POINTER(pa_context), c_uint32, pa_sink_info_cb_t, c_void_p] pa_context_get_sink_info_by_index.argtypes = [
pa_context_get_sink_info_list = _libraries['libpulse.so.0'].pa_context_get_sink_info_list POINTER(pa_context), c_uint32, pa_sink_info_cb_t, c_void_p]
pa_context_get_sink_info_list = _libraries[
'libpulse.so.0'].pa_context_get_sink_info_list
pa_context_get_sink_info_list.restype = POINTER(pa_operation) pa_context_get_sink_info_list.restype = POINTER(pa_operation)
pa_context_get_sink_info_list.argtypes = [POINTER(pa_context), pa_sink_info_cb_t, c_void_p] pa_context_get_sink_info_list.argtypes = [
POINTER(pa_context), pa_sink_info_cb_t, c_void_p]
class pa_server_info(Structure): class pa_server_info(Structure):
pass pass
@ -177,10 +213,14 @@ pa_server_info._fields_ = [
('cookie', c_uint32), ('cookie', c_uint32),
('channel_map', pa_channel_map), ('channel_map', pa_channel_map),
] ]
pa_server_info_cb_t = CFUNCTYPE(None, POINTER(pa_context), POINTER(pa_server_info), c_void_p) pa_server_info_cb_t = CFUNCTYPE(
pa_context_get_server_info = _libraries['libpulse.so.0'].pa_context_get_server_info None, POINTER(pa_context), POINTER(pa_server_info), c_void_p)
pa_context_get_server_info = _libraries[
'libpulse.so.0'].pa_context_get_server_info
pa_context_get_server_info.restype = POINTER(pa_operation) pa_context_get_server_info.restype = POINTER(pa_operation)
pa_context_get_server_info.argtypes = [POINTER(pa_context), pa_server_info_cb_t, c_void_p] pa_context_get_server_info.argtypes = [
POINTER(pa_context), pa_server_info_cb_t, c_void_p]
class pa_threaded_mainloop(Structure): class pa_threaded_mainloop(Structure):
pass pass
@ -189,37 +229,48 @@ pa_threaded_mainloop._fields_ = [
pa_threaded_mainloop_new = _libraries['libpulse.so.0'].pa_threaded_mainloop_new pa_threaded_mainloop_new = _libraries['libpulse.so.0'].pa_threaded_mainloop_new
pa_threaded_mainloop_new.restype = POINTER(pa_threaded_mainloop) pa_threaded_mainloop_new.restype = POINTER(pa_threaded_mainloop)
pa_threaded_mainloop_new.argtypes = [] pa_threaded_mainloop_new.argtypes = []
pa_threaded_mainloop_free = _libraries['libpulse.so.0'].pa_threaded_mainloop_free pa_threaded_mainloop_free = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_free
pa_threaded_mainloop_free.restype = None pa_threaded_mainloop_free.restype = None
pa_threaded_mainloop_free.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_free.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_start = _libraries['libpulse.so.0'].pa_threaded_mainloop_start pa_threaded_mainloop_start = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_start
pa_threaded_mainloop_start.restype = c_int pa_threaded_mainloop_start.restype = c_int
pa_threaded_mainloop_start.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_start.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_stop = _libraries['libpulse.so.0'].pa_threaded_mainloop_stop pa_threaded_mainloop_stop = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_stop
pa_threaded_mainloop_stop.restype = None pa_threaded_mainloop_stop.restype = None
pa_threaded_mainloop_stop.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_stop.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_lock = _libraries['libpulse.so.0'].pa_threaded_mainloop_lock pa_threaded_mainloop_lock = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_lock
pa_threaded_mainloop_lock.restype = None pa_threaded_mainloop_lock.restype = None
pa_threaded_mainloop_lock.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_lock.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_unlock = _libraries['libpulse.so.0'].pa_threaded_mainloop_unlock pa_threaded_mainloop_unlock = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_unlock
pa_threaded_mainloop_unlock.restype = None pa_threaded_mainloop_unlock.restype = None
pa_threaded_mainloop_unlock.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_unlock.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_wait = _libraries['libpulse.so.0'].pa_threaded_mainloop_wait pa_threaded_mainloop_wait = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_wait
pa_threaded_mainloop_wait.restype = None pa_threaded_mainloop_wait.restype = None
pa_threaded_mainloop_wait.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_wait.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_signal = _libraries['libpulse.so.0'].pa_threaded_mainloop_signal pa_threaded_mainloop_signal = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_signal
pa_threaded_mainloop_signal.restype = None pa_threaded_mainloop_signal.restype = None
pa_threaded_mainloop_signal.argtypes = [POINTER(pa_threaded_mainloop), c_int] pa_threaded_mainloop_signal.argtypes = [POINTER(pa_threaded_mainloop), c_int]
pa_threaded_mainloop_accept = _libraries['libpulse.so.0'].pa_threaded_mainloop_accept pa_threaded_mainloop_accept = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_accept
pa_threaded_mainloop_accept.restype = None pa_threaded_mainloop_accept.restype = None
pa_threaded_mainloop_accept.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_accept.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_get_retval = _libraries['libpulse.so.0'].pa_threaded_mainloop_get_retval pa_threaded_mainloop_get_retval = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_get_retval
pa_threaded_mainloop_get_retval.restype = c_int pa_threaded_mainloop_get_retval.restype = c_int
pa_threaded_mainloop_get_retval.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_get_retval.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_get_api = _libraries['libpulse.so.0'].pa_threaded_mainloop_get_api pa_threaded_mainloop_get_api = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_get_api
pa_threaded_mainloop_get_api.restype = POINTER(pa_mainloop_api) pa_threaded_mainloop_get_api.restype = POINTER(pa_mainloop_api)
pa_threaded_mainloop_get_api.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_get_api.argtypes = [POINTER(pa_threaded_mainloop)]
pa_threaded_mainloop_in_thread = _libraries['libpulse.so.0'].pa_threaded_mainloop_in_thread pa_threaded_mainloop_in_thread = _libraries[
'libpulse.so.0'].pa_threaded_mainloop_in_thread
pa_threaded_mainloop_in_thread.restype = c_int pa_threaded_mainloop_in_thread.restype = c_int
pa_threaded_mainloop_in_thread.argtypes = [POINTER(pa_threaded_mainloop)] pa_threaded_mainloop_in_thread.argtypes = [POINTER(pa_threaded_mainloop)]

View File

@ -1,12 +1,16 @@
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import http.cookiejar import http.cookiejar
import webbrowser import webbrowser
import json import json
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class pyLoad(IntervalModule): class pyLoad(IntervalModule):
""" """
Shows pyLoad status Shows pyLoad status
@ -40,12 +44,13 @@ class pyLoad(IntervalModule):
def _rpc_call(self, method, data=None): def _rpc_call(self, method, data=None):
if not data: if not data:
data = {} data = {}
urlencoded = urllib.parse.urlencode(data).encode("ascii") urlencoded = urllib.parse.urlencode(data).encode("ascii")
return json.loads(self.opener.open("{address}/api/{method}/".format(address=self.address, method=method), urlencoded).read().decode("utf-8")) return json.loads(self.opener.open("{address}/api/{method}/".format(address=self.address, method=method), urlencoded).read().decode("utf-8"))
def init(self): def init(self):
self.cj = http.cookiejar.CookieJar() self.cj = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) self.opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cj))
def login(self): def login(self):
return self._rpc_call("login", { return self._rpc_call("login", {
@ -59,7 +64,8 @@ class pyLoad(IntervalModule):
downloads_status = self._rpc_call("statusDownloads") downloads_status = self._rpc_call("statusDownloads")
if downloads_status: if downloads_status:
progress = sum(dl["percent"] for dl in downloads_status) / len(downloads_status) * 100 progress = sum(dl["percent"]
for dl in downloads_status) / len(downloads_status) * 100
else: else:
progress = 100.0 progress = 100.0

View File

@ -2,7 +2,9 @@ import re
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class Regex(IntervalModule): class Regex(IntervalModule):
""" """
Simple regex file watcher Simple regex file watcher
@ -26,5 +28,5 @@ class Regex(IntervalModule):
with open(self.file, "r") as f: with open(self.file, "r") as f:
match = self.re.search(f.read()) match = self.re.search(f.read())
self.output = { self.output = {
"full_text" : self.format.format(*match.groups()), "full_text": self.format.format(*match.groups()),
} }

View File

@ -3,7 +3,9 @@ import os.path
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class RunWatch(IntervalModule): class RunWatch(IntervalModule):
""" """
Expands the given path using glob to a pidfile and checks Expands the given path using glob to a pidfile and checks
if the process ID found inside is valid if the process ID found inside is valid

View File

@ -3,7 +3,9 @@ import glob
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
class Temperature(IntervalModule): class Temperature(IntervalModule):
""" """
Shows CPU temperature of Intel processors Shows CPU temperature of Intel processors
@ -11,7 +13,8 @@ class Temperature(IntervalModule):
""" """
settings = ( settings = (
("format", "format string used for output. {temp} is the temperature in degrees celsius, {critical} and {high} are the trip point temps."), ("format",
"format string used for output. {temp} is the temperature in degrees celsius, {critical} and {high} are the trip point temps."),
"color", "color_critical", "high_factor" "color", "color_critical", "high_factor"
) )
format = "{temp} °C" format = "{temp} °C"
@ -22,9 +25,11 @@ class Temperature(IntervalModule):
def init(self): def init(self):
self.base_path = "/sys/devices/platform/coretemp.0" self.base_path = "/sys/devices/platform/coretemp.0"
input = glob.glob("{base_path}/temp*_input".format(base_path=self.base_path))[0] input = glob.glob(
"{base_path}/temp*_input".format(base_path=self.base_path))[0]
self.input = re.search("temp([0-9]+)_input", input).group(1) self.input = re.search("temp([0-9]+)_input", input).group(1)
self.base_path = "{base_path}/temp{input}_".format(base_path=self.base_path, input=self.input) self.base_path = "{base_path}/temp{input}_".format(
base_path=self.base_path, input=self.input)
with open("{base_path}crit".format(base_path=self.base_path), "r") as f: with open("{base_path}crit".format(base_path=self.base_path), "r") as f:
self.critical = float(f.read().strip()) / 1000 self.critical = float(f.read().strip()) / 1000
@ -44,7 +49,7 @@ class Temperature(IntervalModule):
color = self.color_high color = self.color_high
self.output = { self.output = {
"full_text" : self.format.format(temp=temp, critical=self.critical, high=self.high), "full_text": self.format.format(temp=temp, critical=self.critical, high=self.high),
"urgent": urgent, "urgent": urgent,
"color": color, "color": color,
} }

View File

@ -3,7 +3,9 @@ import basiciw
from i3pystatus.network import Network from i3pystatus.network import Network
class Wireless(Network): class Wireless(Network):
""" """
Display network information about a interface. Display network information about a interface.

View File

@ -8,20 +8,20 @@ setup(name="i3pystatus",
url="http://github.com/enkore/i3pystatus", url="http://github.com/enkore/i3pystatus",
license="MIT", license="MIT",
classifiers=[ classifiers=[
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Environment :: X11 Applications", "Environment :: X11 Applications",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux", "Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Desktop Environment :: Window Managers", "Topic :: Desktop Environment :: Window Managers",
], ],
packages=[ packages=[
"i3pystatus", "i3pystatus",
"i3pystatus.core", "i3pystatus.core",
"i3pystatus.mail", "i3pystatus.mail",
"i3pystatus.pulseaudio", "i3pystatus.pulseaudio",
], ],
entry_points={ entry_points={
"console_scripts": ["i3pystatus = i3pystatus:main"], "console_scripts": ["i3pystatus = i3pystatus:main"],
}, },
) )

View File

@ -4,6 +4,7 @@ import unittest
from i3pystatus import battery from i3pystatus import battery
def factory(path, format, expected): def factory(path, format, expected):
def test(): def test():
bc = battery.BatteryChecker(path=path, format=format) bc = battery.BatteryChecker(path=path, format=format)
@ -13,6 +14,7 @@ def factory(path, format, expected):
test.description = path + ":" + format test.description = path + ":" + format
return test return test
def basic_test_generator(): def basic_test_generator():
cases = [ cases = [
("test_battery_basic1", "FULL", "0.000", ""), ("test_battery_basic1", "FULL", "0.000", ""),

View File

@ -9,15 +9,18 @@ import types
from i3pystatus.core import util from i3pystatus.core import util
def get_random_string(length=6, chars=string.printable): def get_random_string(length=6, chars=string.printable):
return ''.join(random.choice(chars) for x in range(length)) return ''.join(random.choice(chars) for x in range(length))
def lchop(prefix, string): def lchop(prefix, string):
chopped = util.lchop(string, prefix) chopped = util.lchop(string, prefix)
if string.startswith(prefix): if string.startswith(prefix):
assert len(chopped) == len(string) - len(prefix) assert len(chopped) == len(string) - len(prefix)
assert not (prefix and chopped.startswith(prefix)) assert not (prefix and chopped.startswith(prefix))
def lchop_test_generator(): def lchop_test_generator():
cases = [ cases = [
('\x0b,S0I', "=t5Bk+\x0b'_;duq=9"), ('\x0b,S0I', "=t5Bk+\x0b'_;duq=9"),
@ -49,17 +52,19 @@ def lchop_test_generator():
("<|(h|P9Wz9d9'u,M", '7d-A\nY{}5"\' !*gHHh`x0!B2Ox?yeKb\x0b'), ("<|(h|P9Wz9d9'u,M", '7d-A\nY{}5"\' !*gHHh`x0!B2Ox?yeKb\x0b'),
('bV?:f\x0b#HDhuwSvys3', ";\r,L![\x0cU7@ne@'?[*&V<dap]+Tq[n1!|PE"), ('bV?:f\x0b#HDhuwSvys3', ";\r,L![\x0cU7@ne@'?[*&V<dap]+Tq[n1!|PE"),
('T\r~bGV^@JC?P@Pa66.', "9,q>VI,[}pHM\nB65@LfE16VJPw=r'zU\x0bzWj@"), ('T\r~bGV^@JC?P@Pa66.', "9,q>VI,[}pHM\nB65@LfE16VJPw=r'zU\x0bzWj@"),
('^|j7N!mV0o(?*1>p?dy', '\\ZdA&:\t\x0b:8\t|7.Kl,oHw-\x0cS\nwZlND~uC@le`Sm'), ('^|j7N!mV0o(?*1>p?dy',
'\\ZdA&:\t\x0b:8\t|7.Kl,oHw-\x0cS\nwZlND~uC@le`Sm'),
] ]
for prefix, string in cases: for prefix, string in cases:
yield lchop, prefix, prefix+string yield lchop, prefix, prefix + string
yield lchop, prefix, string yield lchop, prefix, string
yield lchop, string, string yield lchop, string, string
yield lchop, string, prefix yield lchop, string, prefix
yield lchop, "", string yield lchop, "", string
yield lchop, prefix, "" yield lchop, prefix, ""
yield lchop, prefix+prefix, prefix+prefix+prefix+string yield lchop, prefix + prefix, prefix + prefix + prefix + string
def partition(iterable, limit, assrt): def partition(iterable, limit, assrt):
partitions = util.partition(iterable, limit) partitions = util.partition(iterable, limit)
@ -70,8 +75,8 @@ def partition(iterable, limit, assrt):
def partition_test_generator(): def partition_test_generator():
cases = [ cases = [
([1, 2, 3, 4], 3, [[1,2], [3], [4]]), ([1, 2, 3, 4], 3, [[1, 2], [3], [4]]),
([2, 1, 3, 4], 3, [[1,2], [3], [4]]), ([2, 1, 3, 4], 3, [[1, 2], [3], [4]]),
([0.33, 0.45, 0.89], 1, [[0.33, 0.45, 0.89]]), ([0.33, 0.45, 0.89], 1, [[0.33, 0.45, 0.89]]),
([], 10, []), ([], 10, []),
] ]
@ -79,9 +84,11 @@ def partition_test_generator():
for iterable, limit, assrt in cases: for iterable, limit, assrt in cases:
yield partition, iterable, limit, assrt yield partition, iterable, limit, assrt
def popwhile(iterable, predicate, assrt): def popwhile(iterable, predicate, assrt):
assert list(util.popwhile(predicate, iterable)) == assrt assert list(util.popwhile(predicate, iterable)) == assrt
def popwhile_test_generator(): def popwhile_test_generator():
cases = [ cases = [
([1, 2, 3, 4], lambda x: x < 2, []), ([1, 2, 3, 4], lambda x: x < 2, []),
@ -94,25 +101,30 @@ def popwhile_test_generator():
for iterable, predicate, assrt in cases: for iterable, predicate, assrt in cases:
yield popwhile, iterable, predicate, assrt yield popwhile, iterable, predicate, assrt
def keyconstraintdict_missing(valid, required, feedkeys, assrt_missing): def keyconstraintdict_missing(valid, required, feedkeys, assrt_missing):
kcd = util.KeyConstraintDict(valid_keys=valid, required_keys=required) kcd = util.KeyConstraintDict(valid_keys=valid, required_keys=required)
kcd.update(dict.fromkeys(feedkeys)) kcd.update(dict.fromkeys(feedkeys))
assert kcd.missing() == set(assrt_missing) assert kcd.missing() == set(assrt_missing)
def keyconstraintdict_missing_test_generator(): def keyconstraintdict_missing_test_generator():
cases = [ cases = [
# ( valid, required, feed, missing ) # ( valid, required, feed, missing )
(("foo", "bar", "baz"), ("foo",), ("bar",), ("foo",)), (("foo", "bar", "baz"), ("foo",), ("bar",), ("foo",)),
(("foo", "bar", "baz"), ("foo",), tuple(), ("foo",)), (("foo", "bar", "baz"), ("foo",), tuple(), ("foo",)),
(("foo", "bar", "baz"), ("bar", "baz"), ("bar", "baz"), tuple()), (("foo", "bar", "baz"), ("bar", "baz"), ("bar", "baz"), tuple()),
(("foo", "bar", "baz"), ("bar", "baz"), ("bar", "foo", "baz"), tuple()), (("foo", "bar", "baz"), ("bar", "baz"),
("bar", "foo", "baz"), tuple()),
] ]
for valid, required, feed, missing in cases: for valid, required, feed, missing in cases:
yield keyconstraintdict_missing, valid, required, feed, missing yield keyconstraintdict_missing, valid, required, feed, missing
class ModuleListTests(unittest.TestCase): class ModuleListTests(unittest.TestCase):
class ModuleBase: class ModuleBase:
pass pass
@ -128,7 +140,8 @@ class ModuleListTests(unittest.TestCase):
module.registered.assert_called_with(self.status_handler) module.registered.assert_called_with(self.status_handler)
def _create_module_class(self, name, bases=None): def _create_module_class(self, name, bases=None):
if not bases: bases = (self.ModuleBase,) if not bases:
bases = (self.ModuleBase,)
return type(name, bases, { return type(name, bases, {
"registered": MagicMock(), "registered": MagicMock(),
"__init__": MagicMock(return_value=None), "__init__": MagicMock(return_value=None),
@ -173,7 +186,9 @@ class ModuleListTests(unittest.TestCase):
cls.__init__.assert_called_with() cls.__init__.assert_called_with()
cls.registered.assert_called_with(self.status_handler) cls.registered.assert_called_with(self.status_handler)
class PrefixedKeyDictTests(unittest.TestCase): class PrefixedKeyDictTests(unittest.TestCase):
def test_no_prefix(self): def test_no_prefix(self):
dict = util.PrefixedKeyDict("") dict = util.PrefixedKeyDict("")
dict["foo"] = None dict["foo"] = None
@ -200,46 +215,55 @@ class PrefixedKeyDictTests(unittest.TestCase):
assert realdict["pfx_foo"] == None assert realdict["pfx_foo"] == None
assert realdict["pfx_bar"] == 42 assert realdict["pfx_bar"] == 42
class KeyConstraintDictAdvancedTests(unittest.TestCase): class KeyConstraintDictAdvancedTests(unittest.TestCase):
def test_invalid_1(self): def test_invalid_1(self):
kcd = util.KeyConstraintDict(valid_keys=tuple(), required_keys=tuple()) kcd = util.KeyConstraintDict(valid_keys=tuple(), required_keys=tuple())
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
kcd["invalid"] = True kcd["invalid"] = True
def test_invalid_2(self): def test_invalid_2(self):
kcd = util.KeyConstraintDict(valid_keys=("foo", "bar"), required_keys=tuple()) kcd = util.KeyConstraintDict(
valid_keys=("foo", "bar"), required_keys=tuple())
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
kcd["invalid"] = True kcd["invalid"] = True
def test_incomplete_iteration(self): def test_incomplete_iteration(self):
kcd = util.KeyConstraintDict(valid_keys=("foo", "bar"), required_keys=("foo",)) kcd = util.KeyConstraintDict(
valid_keys=("foo", "bar"), required_keys=("foo",))
with self.assertRaises(util.KeyConstraintDict.MissingKeys): with self.assertRaises(util.KeyConstraintDict.MissingKeys):
for x in kcd: for x in kcd:
pass pass
def test_completeness(self): def test_completeness(self):
kcd = util.KeyConstraintDict(valid_keys=("foo", "bar"), required_keys=("foo",)) kcd = util.KeyConstraintDict(
valid_keys=("foo", "bar"), required_keys=("foo",))
kcd["foo"] = False kcd["foo"] = False
for x in kcd: for x in kcd:
pass pass
assert kcd.missing() == set() assert kcd.missing() == set()
def test_remove_required(self): def test_remove_required(self):
kcd = util.KeyConstraintDict(valid_keys=("foo", "bar"), required_keys=("foo",)) kcd = util.KeyConstraintDict(
valid_keys=("foo", "bar"), required_keys=("foo",))
kcd["foo"] = None kcd["foo"] = None
assert kcd.missing() == set() assert kcd.missing() == set()
del kcd["foo"] del kcd["foo"]
assert kcd.missing() == set(["foo"]) assert kcd.missing() == set(["foo"])
def test_set_twice(self): def test_set_twice(self):
kcd = util.KeyConstraintDict(valid_keys=("foo", "bar"), required_keys=("foo",)) kcd = util.KeyConstraintDict(
valid_keys=("foo", "bar"), required_keys=("foo",))
kcd["foo"] = 1 kcd["foo"] = 1
kcd["foo"] = 2 kcd["foo"] = 2
assert kcd.missing() == set() assert kcd.missing() == set()
del kcd["foo"] del kcd["foo"]
assert kcd.missing() == set(["foo"]) assert kcd.missing() == set(["foo"])
class FormatPTests(unittest.TestCase): class FormatPTests(unittest.TestCase):
def test_escaping(self): def test_escaping(self):
assert util.formatp("[razamba \[ mabe \]]") == "razamba [ mabe ]" assert util.formatp("[razamba \[ mabe \]]") == "razamba [ mabe ]"
@ -251,22 +275,28 @@ class FormatPTests(unittest.TestCase):
def test_nesting(self): def test_nesting(self):
s = "[[{artist} - ]{album} - ]{title}" s = "[[{artist} - ]{album} - ]{title}"
assert util.formatp(s, title="Black rose") == "Black rose" assert util.formatp(s, title="Black rose") == "Black rose"
assert util.formatp(s, artist="In Flames", title="Gyroscope") == "Gyroscope" assert util.formatp(
assert util.formatp(s, artist="SOAD", album="Toxicity", title="Science") == "SOAD - Toxicity - Science" s, artist="In Flames", title="Gyroscope") == "Gyroscope"
assert util.formatp(s, album="Toxicity", title="Science") == "Toxicity - Science" assert util.formatp(
s, artist="SOAD", album="Toxicity", title="Science") == "SOAD - Toxicity - Science"
assert util.formatp(
s, album="Toxicity", title="Science") == "Toxicity - Science"
def test_bare(self): def test_bare(self):
assert util.formatp("{foo} blar", foo="bar") == "bar blar" assert util.formatp("{foo} blar", foo="bar") == "bar blar"
def test_presuffix(self): def test_presuffix(self):
assert util.formatp("ALINA[{title} schnacke]KOMMAHER", title="") == "ALINAKOMMAHER" assert util.formatp(
"ALINA[{title} schnacke]KOMMAHER", title="") == "ALINAKOMMAHER"
assert util.formatp("grml[{title}]") == "grml" assert util.formatp("grml[{title}]") == "grml"
assert util.formatp("[{t}]grml") == "grml" assert util.formatp("[{t}]grml") == "grml"
def test_side_by_side(self): def test_side_by_side(self):
s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]" s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]"
assert util.formatp(s, status="", title="Only For The Weak", song_elapsed="1:41", song_length="4:55") == "▷ Only For The Weak 1:41/4:55" assert util.formatp(s, status="", title="Only For The Weak",
assert util.formatp(s, status="", album="Foo", title="Die, Die, Crucified", song_elapsed="2:52") == " Die, Die, Crucified" song_elapsed="1:41", song_length="4:55") == "▷ Only For The Weak 1:41/4:55"
assert util.formatp(
s, status="", album="Foo", title="Die, Die, Crucified", song_elapsed="2:52") == " Die, Die, Crucified"
assert util.formatp("[[{a}][{b}]]", b=1) == "1" assert util.formatp("[[{a}][{b}]]", b=1) == "1"
def test_complex_field(self): def test_complex_field(self):