diff --git a/hm/desktop/default.nix b/hm/desktop/default.nix index 9481309..6fd3cc7 100644 --- a/hm/desktop/default.nix +++ b/hm/desktop/default.nix @@ -52,7 +52,7 @@ hwdec = "auto-safe"; profile = "gpu-hq"; }; - scripts = with pkgs.mpvScripts; [ thumbnail ]; + scripts = with pkgs.mpvScripts; [ thumbnail mpris ]; scriptOpts = { mpv_thumbnail_script = { autogenerate = false; # TODO It creates too many processes at once, crashing the system diff --git a/hm/desktop/frobar/default.nix b/hm/desktop/frobar/default.nix index e28bb45..ca2c174 100644 --- a/hm/desktop/frobar/default.nix +++ b/hm/desktop/frobar/default.nix @@ -25,7 +25,7 @@ pkgs.python3Packages.buildPythonApplication { pulsectl pyinotify ]; - makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath ([ lemonbar ] ++ (with pkgs; [ wirelesstools ]))}" ]; + makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath ([ lemonbar ] ++ (with pkgs; [ wirelesstools playerctl ]))}" ]; src = ./.; } diff --git a/hm/desktop/frobar/frobar/__init__.py b/hm/desktop/frobar/frobar/__init__.py index 4a3e58c..de8d07a 100644 --- a/hm/desktop/frobar/frobar/__init__.py +++ b/hm/desktop/frobar/frobar/__init__.py @@ -37,7 +37,8 @@ def run() -> None: ) # TODO Middle - Bar.addSectionAll(fp.MpdProvider(theme=9), BarGroupType.LEFT) + Bar.addSectionAll(fp.MprisProvider(theme=9), BarGroupType.LEFT) + # Bar.addSectionAll(fp.MpdProvider(theme=9), BarGroupType.LEFT) # Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT) # TODO Computer modes diff --git a/hm/desktop/frobar/frobar/providers.py b/hm/desktop/frobar/frobar/providers.py index 4c72cfb..21c22e6 100644 --- a/hm/desktop/frobar/frobar/providers.py +++ b/hm/desktop/frobar/frobar/providers.py @@ -849,3 +849,96 @@ class MpdProvider(Section, ThreadedUpdater): self.connect() except BaseException as e: log.error(e, exc_info=True) + + +class MprisProviderSection(Section, Updater): + def __init__(self, parent: "MprisProvider"): + Updater.__init__(self) + Section.__init__(self, theme=parent.theme) + self.parent = parent + + +class MprisProvider(Section, ThreadedUpdater): + # TODO Controls (select player at least) + # TODO Use the Python native thing for it: + # https://github.com/altdesktop/playerctl?tab=readme-ov-file#using-the-library + # TODO Make it less sucky + + SECTIONS = [ + "{{ playerName }} {{ status }}", + "{{ album }}", + "{{ artist }}", + "{{ duration(position) }}|{{ duration(mpris:length) }}" + " {{ title }}", + ] + + # nf-fd icons don't work (UTF-16?) + SUBSTITUTIONS = { + "Playing": "", + "Paused": "", + "Stopped": "", + "mpd": "", + "firefox": "", + "chromium": "", + "mpv": "", + } + + ICONS = { + 1: "", + 2: "", + 3: "", + } + + def __init__(self, theme: int | None = None): + ThreadedUpdater.__init__(self) + Section.__init__(self, theme) + + self.line = "" + self.start() + + self.sections: list[Section] = [] + + def fetcher(self) -> Element: + create = not len(self.sections) + populate = self.line + split = self.line.split("\t") + + lastSection: Section = self + for i in range(len(self.SECTIONS)): + if create: + section = Section(theme=self.theme) + lastSection.appendAfter(section) + lastSection = section + self.sections.append(section) + else: + section = self.sections[i] + + if populate: + text = split[i] + if i == 0: + for key, val in self.SUBSTITUTIONS.items(): + text = text.replace(key, val) + if text: + if i in self.ICONS: + text = f"{self.ICONS[i]} {text}" + section.updateText(text) + else: + section.updateText(None) + else: + section.updateText(None) + + return None + + def loop(self) -> None: + cmd = [ + "playerctl", + "metadata", + "--format", + "\t".join(self.SECTIONS), + "--follow", + ] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + assert p.stdout + while p.poll() is None: + self.line = p.stdout.readline().decode().strip() + self.refreshData() diff --git a/hm/desktop/frobar/frobar/updaters.py b/hm/desktop/frobar/frobar/updaters.py index 9f55fe6..b5e07d5 100644 --- a/hm/desktop/frobar/frobar/updaters.py +++ b/hm/desktop/frobar/frobar/updaters.py @@ -4,6 +4,7 @@ import functools import logging import math import os +import subprocess import threading import time @@ -11,8 +12,8 @@ import coloredlogs import i3ipc import pyinotify -from frobar.display import Element from frobar.common import notBusy +from frobar.display import Element coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") log = logging.getLogger() diff --git a/hm/desktop/mpd/default.nix b/hm/desktop/mpd/default.nix index 9f2c220..5483fb7 100644 --- a/hm/desktop/mpd/default.nix +++ b/hm/desktop/mpd/default.nix @@ -6,26 +6,36 @@ ashuffle mpc-cli vimpc + playerctl ]; sessionVariables = { MPD_PORT = "${toString config.services.mpd.network.port}"; }; }; - services.mpd = { - enable = true; - network = { - listenAddress = "0.0.0.0"; # Can be controlled remotely, determined with firewall - startWhenNeeded = true; + services = { + mpd = { + enable = true; + network = { + listenAddress = "0.0.0.0"; # Can be controlled remotely, determined with firewall + startWhenNeeded = true; + }; + extraConfig = '' + restore_paused "yes" + audio_output { + type "pipewire" + name "PipeWire Sound Server" + } + ''; + # UPST auto audio_output ? + musicDirectory = "${config.home.homeDirectory}/Musiques"; }; - extraConfig = '' - restore_paused "yes" - audio_output { - type "pipewire" - name "PipeWire Sound Server" - } - ''; - # UPST auto audio_output ? - musicDirectory = "${config.home.homeDirectory}/Musiques"; + # Expose mpd to mpris + # mpd-mpris also exists but is MIT and make playerctld not pick up on play/pause events + mpdris2.enable = true; + # Allow control from headset + mpris-proxy.enable = true; + # Remember the last player + playerctld.enable = true; }; xdg = { configFile = { @@ -45,9 +55,9 @@ }; xsession.windowManager.i3.config.keybindings = { - "XF86AudioPrev" = "exec ${pkgs.mpc-cli}/bin/mpc prev"; - "XF86AudioPlay" = "exec ${pkgs.mpc-cli}/bin/mpc toggle"; - "XF86AudioNext" = "exec ${pkgs.mpc-cli}/bin/mpc next"; + "XF86AudioPrev" = "exec ${lib.getExe pkgs.playerctl} previous"; + "XF86AudioPlay" = "exec ${lib.getExe pkgs.playerctl} play-pause"; + "XF86AudioNext" = "exec ${lib.getExe pkgs.playerctl} next"; }; }; }