#![warn(
missing_docs,
clippy::missing_docs_in_private_items,
clippy::cargo,
clippy::unwrap_used,
clippy::pedantic,
clippy::nursery,
future_incompatible
)]
#[cfg(test)]
mod tests;
use std::{process::Command, str::FromStr};
fn command(command: &str) -> String {
let mut parts = command.split_whitespace().collect::<Vec<&str>>();
let stdout = Command::new(parts.remove(0))
.args(parts)
.output()
.unwrap_or_else(|_| panic!("Failed to execute command '{}'", command))
.stdout;
String::from_utf8(stdout).expect("Stdout was not valid UTF-8")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlayerStatus {
Playing,
Paused,
Stopped,
}
impl FromStr for PlayerStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Playing" => Ok(Self::Playing),
"Paused" => Ok(Self::Paused),
"Stopped" => Ok(Self::Stopped),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopStatus {
None,
Track,
Playlist,
}
impl FromStr for LoopStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"None" => Ok(Self::None),
"Track" => Ok(Self::Track),
"Playlist" => Ok(Self::Playlist),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShuffleStatus {
On,
Off,
Toggle,
}
impl FromStr for ShuffleStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"On" => Ok(Self::On),
"Off" => Ok(Self::Off),
"Toggle" => Ok(Self::Toggle),
_ => Err(()),
}
}
}
pub struct TrackMetadata {
pub artist: String,
pub title: String,
pub album: String,
}
pub struct PlayerCtl;
impl PlayerCtl {
pub fn play() {
command("playerctl play");
}
pub fn pause() {
command("playerctl pause");
}
pub fn play_pause() {
command("playerctl play-pause");
}
pub fn stop() {
command("playerctl stop");
}
pub fn next() {
command("playerctl next");
}
pub fn previous() {
command("playerctl previous");
}
pub fn position(secs: f32) {
if secs < 0. {
command(&format!("playerctl position {}-", -secs));
} else {
command(&format!("playerctl position {}+", secs));
}
}
pub fn volume(percent: f32) {
if percent < 0. {
command(&format!("playerctl volume {}-", -percent));
} else {
command(&format!("playerctl volume {}+", percent));
}
}
#[must_use]
pub fn status() -> PlayerStatus {
command("playerctl status")
.trim()
.parse()
.expect("Failed to parse player status")
}
#[must_use]
pub fn metadata() -> TrackMetadata {
let title = command("playerctl metadata title").trim().to_string();
let artist = command("playerctl metadata artist").trim().to_string();
let album = command("playerctl metadata album").trim().to_string();
TrackMetadata {
artist,
title,
album,
}
}
pub fn open(uri: &str) {
command(&format!("playerctl open {uri}"));
}
#[must_use]
pub fn loop_get() -> LoopStatus {
command("playerctl loop")
.trim()
.parse()
.expect("Failed to parse loop status")
}
pub fn loop_set(status: LoopStatus) {
command(&format!("playerctl loop {:?}", status));
}
#[must_use]
pub fn shuffle_get() -> ShuffleStatus {
command("playerctl shuffle")
.trim()
.parse()
.expect("Failed to parse shuffle status")
}
pub fn shuffle_set(status: ShuffleStatus) {
command(&format!("playerctl shuffle {:?}", status));
}
}