From 87e76b9e40ec434c4df1172c2e18fa98f081a311 Mon Sep 17 00:00:00 2001 From: Tobi-wan Kenobi Date: Sat, 10 Dec 2016 07:47:24 +0100 Subject: [PATCH] [modules/cmus] Re-add cmus module Re-add a first version of the cmus module originally contributed by @paxy97. Still missing: * Icon themes (status) * On-click actions see #23 --- bumblebee/modules/cmus.py | 56 ++++++++++++++++++++++++++++++++++++++ bumblebee/modules/cpu.py | 2 ++ bumblebee/output.py | 3 +- bumblebee/util.py | 8 ++++++ tests/modules/test_cmus.py | 30 ++++++++++++++++++++ tests/util.py | 4 +++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 bumblebee/modules/cmus.py create mode 100644 tests/modules/test_cmus.py diff --git a/bumblebee/modules/cmus.py b/bumblebee/modules/cmus.py new file mode 100644 index 0000000..f2dfea3 --- /dev/null +++ b/bumblebee/modules/cmus.py @@ -0,0 +1,56 @@ +# pylint: disable=C0111,R0903 + +"""Displays information about the current song in cmus.""" + +from collections import defaultdict + +import string + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [ + bumblebee.output.Widget(name="cmus.prev"), + bumblebee.output.Widget(name="cmus.main", full_text=self.description), + bumblebee.output.Widget(name="cmus.next"), + bumblebee.output.Widget(name="cmus.shuffle"), + bumblebee.output.Widget(name="cmus.repeat"), + ] + super(Module, self).__init__(engine, config, widgets) + self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}") + + def description(self): + return string.Formatter().vformat(self._fmt, (), self._tags) + + def update(self, widgets): + self._load_song() + + def _load_song(self): + info = "" + try: + info = bumblebee.util.execute("cmus-remote -Q") + except RuntimeError: + pass + self._tags = defaultdict(lambda: '') + for line in info.split("\n"): + if line.startswith("status"): + self._status = line.split(" ", 2)[1] + if line.startswith("tag"): + key, value = line.split(" ", 2)[1:] + self._tags.update({ key: value }) + if line.startswith("duration"): + self._tags.update({ + "duration": bumblebee.util.durationfmt(int(line.split(" ")[1])) + }) + if line.startswith("position"): + self._tags.update({ + "position": bumblebee.util.durationfmt(int(line.split(" ")[1])) + }) + if line.startswith("set repeat "): + self._repeat = False if "false" in line else True + if line.startswith("set shuffle "): + self._shuffle = False if "false" in line else True diff --git a/bumblebee/modules/cpu.py b/bumblebee/modules/cpu.py index b02b95b..3d20479 100644 --- a/bumblebee/modules/cpu.py +++ b/bumblebee/modules/cpu.py @@ -3,6 +3,8 @@ """Displays CPU utilization across all CPUs.""" import psutil +import bumblebee.input +import bumblebee.output import bumblebee.engine class Module(bumblebee.engine.Module): diff --git a/bumblebee/output.py b/bumblebee/output.py index e67eff6..30062f3 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -8,9 +8,10 @@ import uuid class Widget(object): """Represents a single visible block in the status bar""" - def __init__(self, full_text): + def __init__(self, full_text="", name=""): self._full_text = full_text self.module = None + self.name = name self.id = str(uuid.uuid4()) def link_module(self, module): diff --git a/bumblebee/util.py b/bumblebee/util.py index 9896c3f..24e2cb1 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -18,4 +18,12 @@ def execute(cmd, wait=True): return out.decode("utf-8") return None +def durationfmt(duration): + minutes, seconds = divmod(duration, 60) + hours, minutes = divmod(minutes, 60) + res = "{:02d}:{:02d}".format(minutes, seconds) + if hours > 0: res = "{:02d}:{}".format(hours, res) + + return res + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_cmus.py b/tests/modules/test_cmus.py new file mode 100644 index 0000000..dfb3c56 --- /dev/null +++ b/tests/modules/test_cmus.py @@ -0,0 +1,30 @@ +# pylint: disable=C0103,C0111 + +import json +import unittest +import mock + +import bumblebee.input +from bumblebee.input import I3BarInput +from bumblebee.modules.cmus import Module +from tests.util import MockEngine, MockConfig, assertPopen + +class TestCmusModule(unittest.TestCase): + def setUp(self): + self.engine = MockEngine() + self.module = Module(engine=self.engine, config={"config": MockConfig()}) + + @mock.patch("subprocess.Popen") + def test_read_song(self, mock_output): + rv = mock.Mock() + rv.configure_mock(**{ + "communicate.return_value": ("out", None) + }) + mock_output.return_value = rv + self.module.update(self.module.widgets()) + assertPopen(mock_output, "cmus-remote -Q") + + def test_widgets(self): + self.assertTrue(len(self.module.widgets()), 5) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/util.py b/tests/util.py index 7881c99..6b04725 100644 --- a/tests/util.py +++ b/tests/util.py @@ -30,6 +30,10 @@ class MockEngine(object): def __init__(self): self.input = MockInput() +class MockConfig(object): + def get(self, name, default): + return default + class MockOutput(object): def start(self): pass