From 86d8ea4f751e5849a120c488bda5c716c6356e86 Mon Sep 17 00:00:00 2001 From: Baldomo Date: Thu, 16 Jul 2020 19:27:02 +0200 Subject: [PATCH] Total c++ rewrite --- Cargo.lock | 167 ------------------------------------------------ Cargo.toml | 15 ----- Makefile | 19 ++++++ README.md | 8 ++- build.rs | 85 ------------------------ src/lib.rs | 130 ------------------------------------- src/main.cpp | 64 +++++++++++++++++++ src/main.rs | 97 ---------------------------- src/mpvipc.hpp | 43 +++++++++++++ src/mpvopts.hpp | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 296 insertions(+), 495 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml create mode 100644 Makefile delete mode 100644 build.rs delete mode 100644 src/lib.rs create mode 100644 src/main.cpp delete mode 100644 src/main.rs create mode 100644 src/mpvipc.hpp create mode 100644 src/mpvopts.hpp diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 2b5f00f..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,167 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cc" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.66" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "open-in-mpv" -version = "0.1.0" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl" -version = "0.10.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl-sys" -version = "0.9.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pkg-config" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "url" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" -"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index a567e6f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "open-in-mpv" -version = "0.1.0" -authors = ["Leonardo Baldin "] -edition = "2018" -license = "GPL-3.0-or-later" -build = false - -[dependencies] -percent-encoding = "2.1.0" -url = "2.1.1" - -[build-dependencies] -byteorder = "1.3.2" -openssl = "0.10.26" \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..226e1c5 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +INCLUDES = -Isrc/ -I/opt/curl/include +LDFLAGS = -L/opt/curl/lib +LDLIBS = -lcurl +CXXFLAGS = -Wall $(INCLUDES) $(LDFLAGS) $(LDLIBS) +SRCS = src/curl.hpp \ + src/mpvopts.hpp \ + src/main.cpp + +all: + $(CXX) $(CXXFLAGS) -o open-in-mpv src/main.cpp + +install: all + cp open-in-mpv /usr/bin + +uninstall: + rm /usr/bin/open-in-mpv + +clean: + rm open-in-mpv \ No newline at end of file diff --git a/README.md b/README.md index ea3dddf..28442ff 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # open-in-mpv This is a simple web extension (for Chrome and Firefox) which helps open any video in the currently open tab in the [mpv player](https://mpv.io). -The extension itself is a copy of the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in Rust (any stable version will suffice). +The extension itself is a copy of the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in C++11 (this is a rewrite from Rust). ## Installation > Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases). +To install `open-in-mpv`, just run +```sh +sudo make install +``` + +### The `mpv://` protocol `open-in-mpv install-protocol` will create a custom `xdg-open` desktop file with a scheme handler for the `mpv://` protocol. This lets `xdg-open` call `open-in-mpv` with an encoded URI, so that it can be parsed and the information can be relayed to `mpv` - this logic follows how `iina` parses and opens custom URIs with the `iina://` protocol on Mac. `install-protocol.sh` has the same functionality. \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index 17b4ded..0000000 --- a/build.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fs::{read, rename, File}; -use std::io::Write; -use std::process::Command; - -use byteorder::{LittleEndian, WriteBytesExt}; - -use openssl::error::ErrorStack; -use openssl::hash::MessageDigest; -use openssl::pkey::{PKey, Private}; -use openssl::sign::Signer; - -type Buffer = Vec; - -// TODO: signing for the Chrome extension - -fn main() { - zip_dir("Chrome", "Chrome/ext.zip"); - package_crx("./Chrome/ext.zip", "cert.pem"); - - zip_dir("Firefox", "Firefox/ext.zip"); - package_xpi("Firefox/ext.zip"); -} - -fn zip_dir(path: &str, out: &str) { - Command::new("zip") - .arg("-FS") - .arg(out) - .arg(path) - .output() - .unwrap(); -} - -fn package_xpi(source: &str) { - let trimmed: &str = source.trim_end_matches(".zip"); - rename(source, format!("{}.xpi", trimmed)).unwrap(); -} - -fn package_crx(source: &str, pem: &str) { - // read input file and store them into buffers - let mut source_buff = read(source).unwrap(); - - let user_key = load_key_from_file(pem).unwrap(); - - let mut public_key_content: Vec = user_key.public_key_to_der().unwrap(); - - let mut signer = Signer::new(MessageDigest::sha1(), &user_key).unwrap(); - - signer.update(&source_buff).unwrap(); - - let mut signature: Vec = signer.sign_to_vec().unwrap(); - - let mut pub_key_len: Vec = vec![]; - pub_key_len - .write_u32::(public_key_content.len() as u32) - .unwrap(); - - let mut sig_len: Vec = vec![]; - sig_len - .write_u32::(signature.len() as u32) - .unwrap(); - - let trimmed_filename: &str = source.trim_end_matches(".zip"); - let mut package_file: File = File::create(format!("{}.crx", trimmed_filename)).unwrap(); - let mut package_buffer: Vec = Vec::new(); - - let mut magic_bytes: Buffer = vec![0x43, 0x72, 0x32, 0x34]; - let mut version: Buffer = vec![0x02, 0x00, 0x00, 0x00]; - - package_buffer.append(&mut magic_bytes); - package_buffer.append(&mut version); - package_buffer.append(&mut pub_key_len); - package_buffer.append(&mut sig_len); - package_buffer.append(&mut public_key_content); - package_buffer.append(&mut signature); - package_buffer.append(&mut source_buff); - - package_file.write_all(&package_buffer).unwrap(); -} - -fn load_key_from_file(path: &str) -> Result, ErrorStack> { - // Read the private key from the file - let pem_buff = read(path).unwrap(); - - PKey::private_key_from_pem(&pem_buff) -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index ff26aea..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::vec::Vec; -use percent_encoding::percent_decode; - -#[derive(Debug)] -pub struct MpvOption { - url: String, - fullscreen: bool, - pip: bool, - enqueue: bool, -} - -impl MpvOption { - /// Builds a MpvOption from the supplied arguments. - /// - /// # Example - /// ``` - /// use open_in_mpv::MpvOption; - /// let mo: MpvOption = MpvOption::new( - /// "url", - /// "1", - /// "0", - /// "0", - /// ); - /// - /// print!("{:?}", mo); - /// ``` - pub fn new(url: &str, fullscreen: &str, pip: &str, enqueue: &str) -> Self { - Self { - url: percent_decode(url.as_bytes()) - .decode_utf8() - .unwrap() - .as_ref() - .to_owned(), - - fullscreen: match fullscreen { - "1" => true, - "0" => false, - _ => false, - }, - - pip: match pip { - "1" => true, - "0" => false, - _ => false, - }, - - enqueue: match enqueue { - "1" => true, - "0" => false, - _ => false, - }, - } - } - - - /// Returns a Vec containing the argument list for a new mpv process - /// - /// Each parameter of MpvOption is bound to a set of defaults, e.g. `--ontop --no-border` etc. - /// - /// # Example - /// ``` - /// use open_in_mpv::MpvOption; - /// let mo: MpvOption = MpvOption::new( - /// "url", - /// "1", - /// "0", - /// "0", - /// ); - /// - /// print!("{:?}", mo.build_args()); - /// ``` - pub fn build_args(self) -> Vec { - let mut ret: Vec = Vec::new(); - if self.fullscreen { - ret.push("--fs".to_owned()); - } - - if self.pip { - ret.push("--ontop".to_owned()); - ret.push("--no-border".to_owned()); - ret.push("--autofit=384x216".to_owned()); - ret.push("--geometry=98%:98%".to_owned()); - } - - if self.enqueue { - // TODO: figure this out - } - - ret.push(self.url.to_owned()); - - return ret; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_option() { - let encoded_url: &str = "https%3A%2F%2Fst3x_plus.cdnfile.info%2Fuser592%2F5543369133e06eedecc4907bfcd0fd45%2FEP.1.360p.mp4%3Ftoken%3DZDpvjlchVpTP0yb_5AsaEw%26expires%3D1562085204%26title%3D(360P%2520-%2520mp4)%2520Terminator%2B4%253A%2BSalvation%2BHD-720p"; - let expected_url: String = String::from("https://st3x_plus.cdnfile.info/user592/5543369133e06eedecc4907bfcd0fd45/EP.1.360p.mp4?token=ZDpvjlchVpTP0yb_5AsaEw&expires=1562085204&title=(360P%20-%20mp4)%20Terminator+4%3A+Salvation+HD-720p"); - - let mo: MpvOption = MpvOption::new( - encoded_url, - "1", - "0", - "1", - ); - - assert_eq!(mo.url, expected_url); - assert_eq!(mo.fullscreen, true); - assert_eq!(mo.pip, false); - assert_eq!(mo.enqueue, true); - } - - #[test] - fn test_build_args() { - let mo: MpvOption = MpvOption { - url: String::from("invalid_url_but_who_cares"), - fullscreen: true, - pip: false, - enqueue: true, - }; - - let args: Vec = mo.build_args(); - assert_eq!(args[0], "--fs".to_owned()); - assert_eq!(args[1], "invalid_url_but_who_cares".to_owned()); - } -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7540e94 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,64 @@ +#include "mpvopts.hpp" +#include "mpvipc.hpp" +#include +#include +#include +#include + +using std::string; + +const char *help[2] = { + "This program is not supposed to be called from the command line!", + "Call with 'install-protocol' to instal the xdg-compatible protocol file in ~/.local/share/applications/" +}; + +bool install_protocol() { + const char *protocol_file = R"([Desktop Entry] +Name=open-in-mpv +Exec=open-in-mpv %u +Type=Application +Terminal=false +NoDisplay=true +MimeType=x-scheme-handler/mpv +)"; + + const char *homedir = std::getenv("HOME"); + if (!homedir) return false; + + std::ofstream protfile(string(homedir) + "/.local/share/applications/open-in-mpv.desktop"); + protfile << protocol_file; + protfile.flush(); + protfile.close(); + + return true; +} + +int main(int argc, char const *argv[]) { + if (argc == 1) { + std::cout << help[0] << std::endl << help[1] << std::endl; + return 0; + }; + + if (string(argv[1]) == "install-protocol") { + return install_protocol(); + } + + mpvoptions *mo = new mpvoptions(); + try { + mo->parse(argv[1]); + } catch (string err) { + std::cout << err << std::endl; + return 1; + } + + if (mo->needs_ipc()) { + mpvipc *mipc = new mpvipc(); + bool success = mipc->send(mo->build_ipc()); + if (!success) { + std::cout << "Error writing to mpv socket" << std::endl; + return 1; + } + } else std::system(mo->build_cmd().c_str()); + + return 0; +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 8afa26c..0000000 --- a/src/main.rs +++ /dev/null @@ -1,97 +0,0 @@ -use open_in_mpv::MpvOption; -use std::{env, fs}; -use std::collections::HashMap; -use std::process::{Command, exit, Stdio}; -use url::Url; - -fn main() { - // mpv:///open?url=XXXXXXX&full_screen=1&pip=1&enqueue=0 - let raw_arg: String = env::args_os().nth(1).unwrap().into_string().unwrap(); - - // If first argument is "install-protocol", install the protocol file - if raw_arg == "install-protocol" { - install_protocol(); - exit(0); - } - - // Parse the String in raw_arg - let parsed_url: Url = match Url::parse(&raw_arg) { - Ok(parsed) => parsed, - Err(e) => { - println!("Error parsing url: {}", raw_arg); - println!("{}", e); - exit(1); - }, - }; - - // Check if url has the correct protocol format - if parsed_url.scheme() != "mpv" { - println!("Unsupported protocol: {}", parsed_url.scheme()); - exit(1); - } - - // Check for supported methods of operation (just `open` for now) - if parsed_url.path() != "/open" { - println!("Unsupported method: {}", parsed_url.path()); - exit(1); - } - - // Convert query into a HashMap - let query: HashMap = parsed_url.query_pairs().into_owned().collect(); - - if !query.contains_key("url") { - println!("Url cannot be empty!"); - exit(1); - } - - // Build a new MpvOption object - // Note: it's now safe to unwrap all the Option's with fallbacks - let mo = MpvOption::new( - query.get("url").unwrap(), - query.get("full_screen") - .as_deref() - .unwrap_or(&"0".to_owned()), - query.get("pip") - .as_deref() - .unwrap_or(&"0".to_owned()), - query.get("enqueue") - .as_deref() - .unwrap_or(&"0".to_owned()), - ); - - // Spawn a new mpv process - Command::new("mpv") - .args(mo.build_args()) - .stdout(Stdio::null()) - .spawn() - .expect("failed to open mpv"); - - exit(0); -} - -// Installs the protocol file in "$HOME/.local/share/applications/open-in-mpv.desktop" -fn install_protocol() { - const PROTOCOL_FILE: &str = "[Desktop Entry] -Name=open-in-mpv -Exec=open-in-mpv %u -Type=Application -Terminal=false -NoDisplay=true -MimeType=x-scheme-handler/mpv -"; - - // Fetch $HOME - let mut file_path: String = String::new(); - match env::var("HOME") { - Ok(val) => file_path.push_str(&val), - Err(e) => { - println!("couldn't interpret HOME: {}", e); - return; - }, - } - - // Refine file path - file_path.push_str("/.local/share/applications/open-in-mpv.desktop"); - - fs::write(file_path, PROTOCOL_FILE).expect("Error writing file!"); -} diff --git a/src/mpvipc.hpp b/src/mpvipc.hpp new file mode 100644 index 0000000..26c0684 --- /dev/null +++ b/src/mpvipc.hpp @@ -0,0 +1,43 @@ +#ifndef MPVIPC_H_ +#define MPVIPC_H_ + +#include +#include +#include +#include +#include +using std::string; + +const char *DEFAULT_SOCK = "/tmp/mpvsocket"; + +class mpvipc { +private: + sockaddr_un sockaddress; + int sockfd; + int socklen; +public: + mpvipc() : mpvipc(DEFAULT_SOCK) {}; + mpvipc(const char *sockpath); + ~mpvipc(); + + bool send(string cmd); +}; + +mpvipc::mpvipc(const char *sockpath) { + this->sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + this->sockaddress.sun_family = AF_UNIX; + std::strcpy(this->sockaddress.sun_path, sockpath); + this->socklen = sizeof(this->sockaddress); + + connect(this->sockfd, (const sockaddr*)&this->sockaddress, this->socklen); +} + +mpvipc::~mpvipc() { + close(this->sockfd); +} + +bool mpvipc::send(string cmd) { + return write(this->sockfd, cmd.c_str(), cmd.length()) != -1; +} + +#endif \ No newline at end of file diff --git a/src/mpvopts.hpp b/src/mpvopts.hpp new file mode 100644 index 0000000..b5bdab7 --- /dev/null +++ b/src/mpvopts.hpp @@ -0,0 +1,163 @@ +#ifndef MPVOPTS_H_ +#define MPVOPTS_H_ + +#include +#include +#include +#include +#include +using std::string; + +string url_decode(const string encoded); +string query_value(string query, string key); + +class mpvoptions { +private: + string url; + bool fullscreen; + bool pip; + bool enqueue; + bool new_window; + + CURLU *curlu; +public: + mpvoptions(); + ~mpvoptions(); + + string build_cmd(); + string build_ipc(); + void parse(const char *url); + + bool needs_ipc(); +}; + +mpvoptions::mpvoptions() { + this->curlu = curl_url(); + this->fullscreen = false; + this->pip = false; + this->enqueue = false; +} + +mpvoptions::~mpvoptions() { + curl_url_cleanup(this->curlu); +} + +/* + * Builds a command used to invoke mpv with the appropriate arguments + */ +string mpvoptions::build_cmd() { + std::ostringstream ret; + + ret << "mpv "; + if (this->fullscreen) ret << "--fs "; + if (this->pip) ret << "--ontop --no-border --autofit=384x216 --geometry=98\%:98\% "; + // NOTE: this is not needed for mpv (it always opens a new window), maybe for other players? + // if (this->new_window) ret << "--new-window"; + ret << this->url; + + return ret.str(); +} + +string mpvoptions::build_ipc() { + std::ostringstream ret; + + if (!this->needs_ipc()) return ""; + + // TODO: in the future this will need a serious json serializer for more complicated commands + // Syntax: {"command": ["loadfile", "%s", "append-play"]}\n + ret << R"({"command": ["loadfile", ")" << this->url << R"(", "append-play"]})" << std::endl; + + return ret.str(); +} + +/* + * Parse a URL and populate the current MpvOptions (uses libcurl for parsing) + */ +void mpvoptions::parse(const char *url) { + curl_url_set(this->curlu, CURLUPART_URL, url, CURLU_NO_DEFAULT_PORT | CURLU_NON_SUPPORT_SCHEME | CURLU_NO_AUTHORITY); + + // Check wether the url contains the right scheme or not + char *scheme; + curl_url_get(this->curlu, CURLUPART_SCHEME, &scheme, 0); + if (std::strcmp(scheme, "mpv")) { + curl_free(scheme); + throw string("Unsupported protocol supplied"); + } + curl_free(scheme); + + // NOTE: libcurl really doesn't like "malformed" url's such as `mpv:///open?xxxxx` + // and it messes up parsing path and host: HOST becomes `open` and PATH becomes `/` + // so we just check HOST instead of PATH for the correct method + char *method; + curl_url_get(this->curlu, CURLUPART_HOST, &method, 0); + if (std::strcmp(method, "open")) { + curl_free(method); + throw string("Unsupported method supplied"); + } + curl_free(method); + + // Check wether the url query is empty or not + char *query; + curl_url_get(this->curlu, CURLUPART_QUERY, &query, 0); + if (!query) { + curl_free(query); + throw string("Empty query"); + } + + // If the query is not empty, parse it and populate the current object + string querystr(query); + curl_free(query); + this->url = url_decode(query_value(querystr, "url")); + this->fullscreen = query_value(querystr, "fullscreen") == "1"; + this->pip = query_value(querystr, "pip") == "1"; + this->enqueue = query_value(querystr, "enqueue") == "1"; + this->new_window = query_value(querystr, "new_window") == "1"; +} + +/* + * Checks wether or not MpvOptions needs to communicate with mpv via IPC + * instead of the command line interface + */ +bool mpvoptions::needs_ipc() { + // For now this is needed only when queuing videos + return this->enqueue; +} + +/* + * Percent-decodes a URL using curl_easy_unescape + */ +string url_decode(const string encoded) { + CURL *curl = curl_easy_init(); + std::unique_ptr url_decoded( + curl_easy_unescape( + curl, + encoded.c_str(), + (int) encoded.length(), + nullptr + ), + [](char *ptr) { curl_free(ptr); } + ); + + return string(url_decoded.get()); +} + +/* + * Gets a value from a query string given a key + */ +string query_value(string query, string key) { + // Find the beginning of the last occurrence of `key` in `query` + auto pos = query.rfind(key); + if (pos == string::npos) return ""; + + // Offset calculation (beginning of the value string associated with `key`): + // pos: positione of the first character of `key` + // key.length(): self explanatory + // 1: length of character '=' + int offset = pos + key.length() + 1; + // Return a string starting from the offset and with appropriate length + // (difference between the position of the first '&' char after the value and `offset`) + return query.substr(offset, query.find('&', pos) - offset); +} + + +#endif \ No newline at end of file