Updated install-protocol.sh to use xdg-mime, added Chrome extension (courtesy of IINA's extension)
This commit is contained in:
5
Chrome/README.md
Normal file
5
Chrome/README.md
Normal file
@@ -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)
|
14
Chrome/background.html
Normal file
14
Chrome/background.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Document</title>
|
||||||
|
<script src="common.js" type="module"></script>
|
||||||
|
<script src="background.js" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
14
Chrome/background.js
Normal file
14
Chrome/background.js
Normal file
@@ -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]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
102
Chrome/common.js
Normal file
102
Chrome/common.js
Normal file
@@ -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" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
BIN
Chrome/icon.png
Normal file
BIN
Chrome/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
86
Chrome/icon.svg
Normal file
86
Chrome/icon.svg
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 63.999999 63.999999"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="mpv.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.3710484"
|
||||||
|
inkscape:cx="10.112865"
|
||||||
|
inkscape:cy="18.643164"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-988.3622)">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#e5e5e5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10161044;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4380"
|
||||||
|
cx="32"
|
||||||
|
cy="1020.3622"
|
||||||
|
r="27.949194" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#672168;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0988237;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4390"
|
||||||
|
cx="32.727058"
|
||||||
|
cy="1019.5079"
|
||||||
|
r="25.950588" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#420143;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||||
|
id="path4400"
|
||||||
|
cx="34.224396"
|
||||||
|
cy="1017.7957"
|
||||||
|
r="20" />
|
||||||
|
<path
|
||||||
|
style="fill:#dddbdd;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 44.481446,1020.4807 a 12.848894,12.848894 0 0 1 -12.84889,12.8489 12.848894,12.848894 0 0 1 -12.8489,-12.8489 12.848894,12.848894 0 0 1 12.8489,-12.8489 12.848894,12.848894 0 0 1 12.84889,12.8489 z"
|
||||||
|
id="path4412"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#691f69;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 28.374316,1014.709 0,11.4502 9.21608,-5.8647 z"
|
||||||
|
id="path4426"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Chrome/icon128.png
Normal file
BIN
Chrome/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
BIN
Chrome/icon16.png
Normal file
BIN
Chrome/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
BIN
Chrome/icon48.png
Normal file
BIN
Chrome/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
26
Chrome/manifest.json
Normal file
26
Chrome/manifest.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
65
Chrome/options.html
Normal file
65
Chrome/options.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Options</title>
|
||||||
|
<script src="common.js" type="module"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 880px;
|
||||||
|
margin: 1rem auto;
|
||||||
|
background: #f4f4f4;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 0 16px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.option {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.option:not(:last-child) {
|
||||||
|
border-top: 1px solid #e4e4e4;
|
||||||
|
border-bottom: 1px solid #e4e4e4;
|
||||||
|
}
|
||||||
|
.option .option-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.option .option-details {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="title">When clicking on the extension icon:</h2>
|
||||||
|
<div class="option">
|
||||||
|
<label class="option-title"><input type="radio" name="iconAction" value="clickOnly"> Open current page in mpv</label>
|
||||||
|
<div class="option-details">
|
||||||
|
<div class="item">
|
||||||
|
<label><input type="radio" name="iconActionOption" value="direct"> Open directly</label>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<label><input type="radio" name="iconActionOption" value="fullScreen"> Enter full screen</label>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<label><input type="radio" name="iconActionOption" value="pip"> Enter Picture-in-Picture</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label class="option-title"><input type="radio" name="iconAction" value="menu"> Show a menu to select action</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="options.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
Chrome/options.js
Normal file
10
Chrome/options.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
34
Chrome/popup.html
Normal file
34
Chrome/popup.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Document</title>
|
||||||
|
<style>
|
||||||
|
.menu {
|
||||||
|
font-family: sans-serif;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.menu .menu-item {
|
||||||
|
padding: 0.5rem;
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.menu .menu-item:hover {
|
||||||
|
background: #e4e4e4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="menu">
|
||||||
|
<div class="menu-item" id="open-normal">Open in mpv</div>
|
||||||
|
<div class="menu-item" id="open-fullScreen">Open in mpv and enter full screen</div>
|
||||||
|
<div class="menu-item" id="open-pip">Open in mpv and enter Picture-in-Picture</div>
|
||||||
|
<div class="menu-item" id="open-newWindow">Open in a new mpv window</div>
|
||||||
|
<div class="menu-item" id="open-enqueue">Add to mpv playlist</div>
|
||||||
|
</div>
|
||||||
|
<script src="./popup.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
17
Chrome/popup.js
Normal file
17
Chrome/popup.js
Normal file
@@ -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",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
21
install-protocol.sh
Executable file
21
install-protocol.sh
Executable file
@@ -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
|
@@ -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};
|
use url::{percent_encoding::percent_decode, Url};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -102,7 +102,8 @@ fn main() {
|
|||||||
|
|
||||||
Command::new("mpv")
|
Command::new("mpv")
|
||||||
.args(build_args(mo))
|
.args(build_args(mo))
|
||||||
.output()
|
.stdout(Stdio::null())
|
||||||
|
.spawn()
|
||||||
.expect("failed to open mpv");
|
.expect("failed to open mpv");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user