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:
+
+
Open current page in mpv
+
+
+
+ Show a menu to select action
+
+
+
+
+
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");
}