diff --git a/Chrome/README.md b/Chrome/README.md new file mode 100644 index 0000000..f95060a --- /dev/null +++ b/Chrome/README.md @@ -0,0 +1,5 @@ +# Open In mpv (Chrome) + +> WIP + +This code is a copy of [Chrome_Open_In_IINA](https://github.com/iina/iina/tree/develop/browser/Chrome_Open_In_IINA) \ No newline at end of file diff --git a/Chrome/background.html b/Chrome/background.html new file mode 100644 index 0000000..adccbe1 --- /dev/null +++ b/Chrome/background.html @@ -0,0 +1,14 @@ + + + + + + + Document + + + + + + + diff --git a/Chrome/background.js b/Chrome/background.js new file mode 100644 index 0000000..ebe3de4 --- /dev/null +++ b/Chrome/background.js @@ -0,0 +1,14 @@ +import { updateBrowserAction, openInMPV } from "./common.js"; + +updateBrowserAction(); + +[["page", "url"], ["link", "linkUrl"], ["video", "srcUrl"], ["audio", "srcUrl"]].forEach(([item, linkType]) => { + chrome.contextMenus.create({ + title: `Open this ${item} in mpv`, + id: `open${item}inmpv`, + contexts: [item], + onclick: (info, tab) => { + openInMPV(tab.id, info[linkType]); + }, + }); +}); diff --git a/Chrome/common.js b/Chrome/common.js new file mode 100644 index 0000000..59e591a --- /dev/null +++ b/Chrome/common.js @@ -0,0 +1,102 @@ +class Option { + constructor(name, type, defaultValue) { + this.name = name; + this.type = type; + this.defaultValue = defaultValue; + } + + setValue(value) { + switch (this.type) { + case "radio": + Array.prototype.forEach.call(document.getElementsByName(this.name), (el) => { + el.checked = el.value === value; + }); + break; + case "checkbox": + break; + } + } + + getValue() { + switch (this.type) { + case "radio": + return document.querySelector(`input[name="${this.name}"]:checked`).value; + case "checkbox": + break; + } + } +} + +const options = [ + new Option("iconAction", "radio", "clickOnly"), + new Option("iconActionOption", "radio", "direct"), +]; + +export function getOptions(callback) { + const getDict = {}; + options.forEach((item) => { + getDict[item.name] = item.defaultValue; + }) + chrome.storage.sync.get(getDict, callback); +} + +export function saveOptions() { + const saveDict = {}; + options.forEach((item) => { + saveDict[item.name] = item.getValue(); + }) + chrome.storage.sync.set(saveDict); +} + +export function restoreOptions() { + getOptions((items) => { + options.forEach((option) => { + option.setValue(items[option.name]); + }); + }); +} + +export function openInMPV(tabId, url, options = {}) { + const baseURL = `mpv:///open?`; + const params = [`url=${encodeURIComponent(url)}`]; + switch (options.mode) { + case "fullScreen": + params.push("full_screen=1"); break; + case "pip": + params.push("pip=1"); break; + case "enqueue": + params.push("enqueue=1"); break; + } + if (options.newWindow) { + params.push("new_window=1"); + } + const code = ` + var link = document.createElement('a'); + link.href='${baseURL}${params.join("&")}'; + document.body.appendChild(link); + link.click(); + `; + chrome.tabs.executeScript(tabId, { code }); +} + +export function updateBrowserAction() { + getOptions((options) => { + if (options.iconAction === "clickOnly") { + chrome.browserAction.setPopup({ popup: "" }); + chrome.browserAction.onClicked.addListener(() => { + // get active window + chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => { + if (tabs.length === 0) { return; } + // TODO: filter url + const tab = tabs[0]; + if (tab.id === chrome.tabs.TAB_ID_NONE) { return; } + openInMPV(tab.id, tab.url, { + mode: options.iconActionOption, + }); + }); + }); + } else { + chrome.browserAction.setPopup({ popup: "popup.html" }); + } + }); +} diff --git a/Chrome/icon.png b/Chrome/icon.png new file mode 100644 index 0000000..4aadd76 Binary files /dev/null and b/Chrome/icon.png differ diff --git a/Chrome/icon.svg b/Chrome/icon.svg new file mode 100644 index 0000000..97f02e4 --- /dev/null +++ b/Chrome/icon.svg @@ -0,0 +1,86 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + \ No newline at end of file diff --git a/Chrome/icon128.png b/Chrome/icon128.png new file mode 100644 index 0000000..1ac2466 Binary files /dev/null and b/Chrome/icon128.png differ diff --git a/Chrome/icon16.png b/Chrome/icon16.png new file mode 100644 index 0000000..d663bcb Binary files /dev/null and b/Chrome/icon16.png differ diff --git a/Chrome/icon48.png b/Chrome/icon48.png new file mode 100644 index 0000000..34d97b8 Binary files /dev/null and b/Chrome/icon48.png differ diff --git a/Chrome/manifest.json b/Chrome/manifest.json new file mode 100644 index 0000000..3ed597a --- /dev/null +++ b/Chrome/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 2, + + "name": "Open In MPV", + "description": "Open videos and audios in MPV.", + "version": "2.0.0", + "options_page": "options.html", + "background": { + "page": "background.html" + }, + "browser_action": { + "default_icon": "icon.png", + "default_title": "Open In MPV" + }, + "permissions": [ + "tabs", + "activeTab", + "contextMenus", + "storage" + ], + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + } +} diff --git a/Chrome/options.html b/Chrome/options.html new file mode 100644 index 0000000..6beacfc --- /dev/null +++ b/Chrome/options.html @@ -0,0 +1,65 @@ + + + + + + + Options + + + + +
+

When clicking on the extension icon:

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ + + diff --git a/Chrome/options.js b/Chrome/options.js new file mode 100644 index 0000000..a57bdea --- /dev/null +++ b/Chrome/options.js @@ -0,0 +1,10 @@ +import { restoreOptions, saveOptions, updateBrowserAction } from "./common.js"; + +document.addEventListener("DOMContentLoaded", restoreOptions); + +Array.prototype.forEach.call(document.getElementsByTagName("input"), (el) => { + el.addEventListener("change", () => { + saveOptions(); + updateBrowserAction(); + }); +}); diff --git a/Chrome/popup.html b/Chrome/popup.html new file mode 100644 index 0000000..d743c34 --- /dev/null +++ b/Chrome/popup.html @@ -0,0 +1,34 @@ + + + + + + + Document + + + + + + + diff --git a/Chrome/popup.js b/Chrome/popup.js new file mode 100644 index 0000000..d1240ba --- /dev/null +++ b/Chrome/popup.js @@ -0,0 +1,17 @@ +import { openInMPV } from "./common.js"; + +Array.prototype.forEach.call(document.getElementsByClassName("menu-item"), (item) => { + const mode = item.id.split("-")[1]; + item.addEventListener("click", () => { + chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => { + if (tabs.length === 0) { return; } + const tab = tabs[0]; + if (tab.id === chrome.tabs.TAB_ID_NONE) { return; } + console.log(mode) + openInMPV(tab.id, tab.url, { + mode, + newWindow: mode === "newWindow", + }); + }); + }); +}); diff --git a/install-protocol.sh b/install-protocol.sh new file mode 100755 index 0000000..44b81b9 --- /dev/null +++ b/install-protocol.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +desktop_dir=~/.local/share/applications + +if [[ ! -d "$desktop_dir/open-in-mpv.desktop" ]]; then + pushd $desktop_dir + cat << 'EOF' >> open-in-mpv.desktop +[Desktop Entry] +Name=open-in-mpv +Exec=open-in-mpv %u +Type=Application +Terminal=false +MimeType=x-scheme-handler/mpv +EOF + update-desktop-database . + popd +fi + +xdg-mime default open-in-mpv.desktop x-scheme-handler/mpv \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c2397ec..8796817 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, env, process::{Command, exit}, vec::Vec}; +use std::{collections::HashMap, env, process::{Command, exit, Stdio}, vec::Vec}; use url::{percent_encoding::percent_decode, Url}; #[derive(Debug)] @@ -102,7 +102,8 @@ fn main() { Command::new("mpv") .args(build_args(mo)) - .output() + .stdout(Stdio::null()) + .spawn() .expect("failed to open mpv"); }