From 451181b9fae4311ae2cb0e7e784d234f5168131a Mon Sep 17 00:00:00 2001 From: fwastring Date: Sun, 12 Oct 2025 14:14:42 +0200 Subject: [PATCH] initial --- flake.lock | 27 ++++++++++++ flake.nix | 79 +++++++++++++++++++++++++++++++++ playerctl_gui.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++ result | 1 + 4 files changed, 219 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 playerctl_gui.py create mode 120000 result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a963538 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1760038930, + "narHash": "sha256-Oncbh0UmHjSlxO7ErQDM3KM0A5/Znfofj2BSzlHLeVw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0b4defa2584313f3b781240b29d61f6f9f7e0df3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..3d5e704 --- /dev/null +++ b/flake.nix @@ -0,0 +1,79 @@ +{ + description = "GTK controls wrapper for playerctl"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; + forAllSystems = nixpkgs.lib.genAttrs systems; + in { + packages = forAllSystems (system: + let + pkgs = import nixpkgs { inherit system; }; + pythonPath = pkgs.python3Packages.makePythonPath ([ pkgs.python3Packages.pygobject3 ]); + in { + default = pkgs.stdenv.mkDerivation { + pname = "playerctl-gtk"; + version = "0.1.0"; + src = self; + + dontConfigure = true; + dontBuild = true; + + nativeBuildInputs = [ + pkgs.wrapGAppsHook + pkgs.python3 + ]; + + buildInputs = [ + pkgs.python3Packages.pygobject3 + pkgs.playerctl + pkgs.gtk3 + pkgs.gsettings-desktop-schemas + pkgs.adwaita-icon-theme + pkgs.gobject-introspection + ]; + + installPhase = '' + runHook preInstall + install -Dm755 playerctl_gui.py $out/bin/playerctl-gtk + patchShebangs $out/bin/playerctl-gtk + runHook postInstall + ''; + + preFixup = '' + gappsWrapperArgs+=(--prefix PYTHONPATH : "${pythonPath}") + gappsWrapperArgs+=(--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.playerctl ]}) + ''; + }; + }); + + apps = forAllSystems (system: + let + pkg = self.packages.${system}.default; + in { + default = { + type = "app"; + program = "${pkg}/bin/playerctl-gtk"; + }; + }); + + devShells = forAllSystems (system: + let + pkgs = import nixpkgs { inherit system; }; + in { + default = pkgs.mkShell { + buildInputs = [ + pkgs.python3 + pkgs.python3Packages.pygobject3 + pkgs.playerctl + pkgs.gtk3 + pkgs.gobject-introspection + ]; + }; + }); + }; +} diff --git a/playerctl_gui.py b/playerctl_gui.py new file mode 100644 index 0000000..ece6e29 --- /dev/null +++ b/playerctl_gui.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +import subprocess +from typing import Optional + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import GLib, Gtk + + +METADATA_FORMAT = "{{{{status}}}} — {{{{artist}}}} — {{{{title}}}}" + + +class PlayerctlApp(Gtk.Application): + def __init__(self) -> None: + super().__init__(application_id="dev.codex.PlayerctlGUI") + self._label: Optional[Gtk.Label] = None + + def do_activate(self, *args) -> None: # type: ignore[override] + if self.props.active_window: + self.props.active_window.present() + return + + window = Gtk.ApplicationWindow(application=self) + window.set_title("Playerctl Controls") + window.set_border_width(12) + window.set_default_size(320, 120) + + self._label = Gtk.Label(label="Connecting to player…") + self._label.set_hexpand(True) + self._label.set_line_wrap(True) + self._label.set_justify(Gtk.Justification.CENTER) + + controls = Gtk.Box(spacing=6) + controls.set_orientation(Gtk.Orientation.HORIZONTAL) + + prev_button = Gtk.Button.new_with_label("Prev") + prev_button.connect("clicked", self._on_prev_clicked) + controls.pack_start(prev_button, True, True, 0) + + toggle_button = Gtk.Button.new_with_label("Play/Pause") + toggle_button.connect("clicked", self._on_toggle_clicked) + controls.pack_start(toggle_button, True, True, 0) + + next_button = Gtk.Button.new_with_label("Next") + next_button.connect("clicked", self._on_next_clicked) + controls.pack_start(next_button, True, True, 0) + + layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + layout.pack_start(self._label, True, True, 0) + layout.pack_start(controls, False, False, 0) + + window.add(layout) + window.show_all() + + GLib.timeout_add_seconds(2, self._refresh_metadata) + self._refresh_metadata() + + def _on_toggle_clicked(self, _button: Gtk.Button) -> None: + self._run_playerctl(["play-pause"]) + GLib.idle_add(self._refresh_metadata) + + def _on_next_clicked(self, _button: Gtk.Button) -> None: + self._run_playerctl(["next"]) + GLib.idle_add(self._refresh_metadata) + + def _on_prev_clicked(self, _button: Gtk.Button) -> None: + self._run_playerctl(["previous"]) + GLib.idle_add(self._refresh_metadata) + + def _refresh_metadata(self) -> bool: + if not self._label: + return False + + metadata = self._run_playerctl(["metadata", "--format", METADATA_FORMAT]) + if metadata is None or not metadata.strip(): + status = self._run_playerctl(["status"]) + if status is None: + self._label.set_text("No active media player detected") + else: + self._label.set_text(status.strip()) + else: + self._label.set_text(metadata.strip()) + return True + + def _run_playerctl(self, args: list[str]) -> Optional[str]: + cmd = ["playerctl", *args] + try: + completed = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=5, + ) + except (FileNotFoundError, subprocess.SubprocessError): + if self._label: + self._label.set_text("playerctl command not available") + return None + + if completed.returncode != 0: + return None + return completed.stdout + + +def main() -> None: + app = PlayerctlApp() + app.run() + + +if __name__ == "__main__": + main() diff --git a/result b/result new file mode 120000 index 0000000..ffd94ab --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/89vapw3nx4jhbpl8fnjhx3akm2vf710w-playerctl-gtk-0.1.0 \ No newline at end of file