initial
This commit is contained in:
commit
451181b9fa
4 changed files with 219 additions and 0 deletions
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
79
flake.nix
Normal file
79
flake.nix
Normal file
|
|
@ -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
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
112
playerctl_gui.py
Normal file
112
playerctl_gui.py
Normal file
|
|
@ -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()
|
||||||
1
result
Symbolic link
1
result
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/89vapw3nx4jhbpl8fnjhx3akm2vf710w-playerctl-gtk-0.1.0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue