Updated install-protocol.sh to use xdg-mime, added Chrome extension (courtesy of IINA's extension)

This commit is contained in:
Baldomo
2019-07-04 09:13:03 +02:00
parent a37de57a39
commit 6dbd77ffc1
16 changed files with 397 additions and 2 deletions

5
Chrome/README.md Normal file
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

86
Chrome/icon.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
Chrome/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
Chrome/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

26
Chrome/manifest.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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");
}