mirror of
https://github.com/NotAShelf/mpvrc.git
synced 2026-04-18 08:39:53 +00:00
cli: add --socket, --yes flags and shell completions
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I7cfd2149ff2c7599f9c6b192559ee4956a6a6964
This commit is contained in:
parent
a1e132051d
commit
ec229485d4
3 changed files with 669 additions and 194 deletions
746
Cargo.lock
generated
746
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
|
@ -4,10 +4,10 @@ description = "MPV Remote Control - CLI and server for controlling MPV via IPC"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
default-run = "cli"
|
default-run = "cli"
|
||||||
authors = ["NotAShelf <raf@notashelf.dev>"]
|
|
||||||
repository = "https://github.com/notashelf/mrc"
|
repository = "https://github.com/notashelf/mrc"
|
||||||
license = "MPL-2.*"
|
license = "MPL-2.0"
|
||||||
readme = "README.md"
|
rust-version = "1.92.0"
|
||||||
|
readme = true
|
||||||
keywords = ["mpv", "media", "player", "control", "ipc"]
|
keywords = ["mpv", "media", "player", "control", "ipc"]
|
||||||
categories = ["command-line-utilities", "multimedia"]
|
categories = ["command-line-utilities", "multimedia"]
|
||||||
|
|
||||||
|
|
@ -16,14 +16,14 @@ categories = ["command-line-utilities", "multimedia"]
|
||||||
name = "cli"
|
name = "cli"
|
||||||
path = "src/cli.rs"
|
path = "src/cli.rs"
|
||||||
|
|
||||||
# server implementation for remote usage
|
# Server implementation for remote usage
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "server"
|
name = "server"
|
||||||
path = "src/server.rs"
|
path = "src/server.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.102"
|
anyhow = "1.0.102"
|
||||||
clap = { version = "4.6.0", features = ["derive"] }
|
clap = { version = "4.6.0", features = ["derive", "cargo"] }
|
||||||
clap_derive = "4.6.0"
|
clap_derive = "4.6.0"
|
||||||
ipc-channel = "0.21.0"
|
ipc-channel = "0.21.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
|
@ -34,6 +34,10 @@ native-tls = "0.2.18"
|
||||||
tokio-native-tls = "0.3.1"
|
tokio-native-tls = "0.3.1"
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.23"
|
tracing-subscriber = "0.3.23"
|
||||||
|
rustyline = "18.0.0"
|
||||||
|
anstyle = "1.0.14"
|
||||||
|
dirs = "6.0.0"
|
||||||
|
clap_complete = "4.6.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
|
||||||
141
src/cli.rs
141
src/cli.rs
|
|
@ -1,15 +1,21 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
|
||||||
use mrc::SOCKET_PATH;
|
|
||||||
use mrc::commands::Commands;
|
use mrc::commands::Commands;
|
||||||
use mrc::interactive::InteractiveMode;
|
use mrc::interactive::InteractiveMode;
|
||||||
use mrc::{MrcError, Result};
|
use mrc::{MrcError, Result, SOCKET_PATH};
|
||||||
|
use std::io::{self, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[arg(short, long, global = true)]
|
#[arg(short, long, global = true, help = "Path to MPV socket")]
|
||||||
|
socket: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short, long, global = true, help = "Skip confirmation prompts")]
|
||||||
|
yes: bool,
|
||||||
|
|
||||||
|
#[arg(short, long, global = true, help = "Enable debug output")]
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
@ -88,6 +94,68 @@ enum CommandOptions {
|
||||||
|
|
||||||
/// Enter interactive mode to send commands to MPV IPC
|
/// Enter interactive mode to send commands to MPV IPC
|
||||||
Interactive,
|
Interactive,
|
||||||
|
|
||||||
|
/// Generate shell completions
|
||||||
|
Completion {
|
||||||
|
#[arg(value_enum, default_value_t = Shell::Bash)]
|
||||||
|
shell: Shell,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::enum_variant_names)]
|
||||||
|
#[derive(Clone, ValueEnum)]
|
||||||
|
pub enum Shell {
|
||||||
|
Bash,
|
||||||
|
Elvish,
|
||||||
|
Fish,
|
||||||
|
PowerShell,
|
||||||
|
Zsh,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
pub fn generate(&self, app: &mut clap::Command) {
|
||||||
|
match self {
|
||||||
|
Shell::Bash => {
|
||||||
|
clap_complete::generate(clap_complete::Shell::Bash, app, "mrc", &mut io::stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::Elvish => {
|
||||||
|
clap_complete::generate(clap_complete::Shell::Elvish, app, "mrc", &mut io::stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::Fish => {
|
||||||
|
clap_complete::generate(clap_complete::Shell::Fish, app, "mrc", &mut io::stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::PowerShell => clap_complete::generate(
|
||||||
|
clap_complete::Shell::PowerShell,
|
||||||
|
app,
|
||||||
|
"mrc",
|
||||||
|
&mut io::stdout(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Shell::Zsh => {
|
||||||
|
clap_complete::generate(clap_complete::Shell::Zsh, app, "mrc", &mut io::stdout())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_socket_path(cli: &Cli) -> String {
|
||||||
|
cli.socket
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| SOCKET_PATH.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(prompt: &str, yes: bool) -> bool {
|
||||||
|
if yes {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
print!("{} [y/N] ", prompt);
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut input = String::new();
|
||||||
|
io::stdin().read_line(&mut input).unwrap();
|
||||||
|
input.trim().eq_ignore_ascii_case("y")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -95,9 +163,22 @@ async fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
if !PathBuf::from(SOCKET_PATH).exists() {
|
if matches!(cli.command, CommandOptions::Completion { .. }) {
|
||||||
debug!(SOCKET_PATH);
|
let mut app = Cli::command();
|
||||||
error!("Error: MPV socket not found. Is MPV running?");
|
if let CommandOptions::Completion { shell } = &cli.command {
|
||||||
|
shell.generate(&mut app);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let socket_path = get_socket_path(&cli);
|
||||||
|
|
||||||
|
if !PathBuf::from(&socket_path).exists() {
|
||||||
|
debug!(socket_path);
|
||||||
|
error!(
|
||||||
|
"Error: MPV socket not found at {}. Is MPV running?",
|
||||||
|
socket_path
|
||||||
|
);
|
||||||
return Err(MrcError::ConnectionError(std::io::Error::new(
|
return Err(MrcError::ConnectionError(std::io::Error::new(
|
||||||
std::io::ErrorKind::NotFound,
|
std::io::ErrorKind::NotFound,
|
||||||
"MPV socket not found",
|
"MPV socket not found",
|
||||||
|
|
@ -106,61 +187,79 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
CommandOptions::Play { index } => {
|
CommandOptions::Play { index } => {
|
||||||
Commands::play(index).await?;
|
Commands::play(index, Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Pause => {
|
CommandOptions::Pause => {
|
||||||
Commands::pause().await?;
|
Commands::pause(Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Stop => {
|
CommandOptions::Stop => {
|
||||||
Commands::stop().await?;
|
if confirm("This will stop playback and quit MPV. Continue?", cli.yes) {
|
||||||
|
Commands::stop(Some(&socket_path)).await?;
|
||||||
|
} else {
|
||||||
|
println!("Cancelled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Next => {
|
CommandOptions::Next => {
|
||||||
Commands::next().await?;
|
Commands::next(Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Prev => {
|
CommandOptions::Prev => {
|
||||||
Commands::prev().await?;
|
Commands::prev(Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Seek { seconds } => {
|
CommandOptions::Seek { seconds } => {
|
||||||
Commands::seek_to(seconds.into()).await?;
|
Commands::seek_to(seconds.into(), Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Move { index1, index2 } => {
|
CommandOptions::Move { index1, index2 } => {
|
||||||
Commands::move_item(index1, index2).await?;
|
Commands::move_item(index1, index2, Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Remove { index } => {
|
CommandOptions::Remove { index } => {
|
||||||
Commands::remove_item(index).await?;
|
if confirm(
|
||||||
|
&format!("This will remove item at index {:?}. Continue?", index),
|
||||||
|
cli.yes,
|
||||||
|
) {
|
||||||
|
Commands::remove_item(index, Some(&socket_path)).await?;
|
||||||
|
} else {
|
||||||
|
println!("Cancelled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Clear => {
|
CommandOptions::Clear => {
|
||||||
Commands::clear_playlist().await?;
|
if confirm("This will clear the entire playlist. Continue?", cli.yes) {
|
||||||
|
Commands::clear_playlist(Some(&socket_path)).await?;
|
||||||
|
} else {
|
||||||
|
println!("Cancelled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::List => {
|
CommandOptions::List => {
|
||||||
Commands::list_playlist().await?;
|
Commands::list_playlist(Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Add { filenames } => {
|
CommandOptions::Add { filenames } => {
|
||||||
Commands::add_files(&filenames).await?;
|
Commands::add_files(&filenames, Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Replace { filenames } => {
|
CommandOptions::Replace { filenames } => {
|
||||||
Commands::replace_playlist(&filenames).await?;
|
Commands::replace_playlist(&filenames, Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Prop { properties } => {
|
CommandOptions::Prop { properties } => {
|
||||||
Commands::get_properties(&properties).await?;
|
Commands::get_properties(&properties, Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Interactive => {
|
CommandOptions::Interactive => {
|
||||||
InteractiveMode::run().await?;
|
InteractiveMode::run(Some(&socket_path)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandOptions::Completion { .. } => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue