[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
This commit is contained in:
Tobi-wan Kenobi 2016-12-10 07:47:24 +01:00
parent 9ce7739efb
commit 87e76b9e40
6 changed files with 102 additions and 1 deletions

56
bumblebee/modules/cmus.py Normal file
View file

@ -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

View file

@ -3,6 +3,8 @@
"""Displays CPU utilization across all CPUs.""" """Displays CPU utilization across all CPUs."""
import psutil import psutil
import bumblebee.input
import bumblebee.output
import bumblebee.engine import bumblebee.engine
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):

View file

@ -8,9 +8,10 @@ import uuid
class Widget(object): class Widget(object):
"""Represents a single visible block in the status bar""" """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._full_text = full_text
self.module = None self.module = None
self.name = name
self.id = str(uuid.uuid4()) self.id = str(uuid.uuid4())
def link_module(self, module): def link_module(self, module):

View file

@ -18,4 +18,12 @@ def execute(cmd, wait=True):
return out.decode("utf-8") return out.decode("utf-8")
return None 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 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -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

View file

@ -30,6 +30,10 @@ class MockEngine(object):
def __init__(self): def __init__(self):
self.input = MockInput() self.input = MockInput()
class MockConfig(object):
def get(self, name, default):
return default
class MockOutput(object): class MockOutput(object):
def start(self): def start(self):
pass pass