release\
6
README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Download With Extension
|
||||||
|
================
|
||||||
|
|
||||||
|
Download With is an extension that overrides the default download operation and instead sends it to a defined external program. It's based off code from [OpenWith](https://github.com/darktrojan/openwith) and used it as the foundation.
|
||||||
|
|
||||||
|
The purpose of this addon is to bring back support for your own external download manager, without having to use a specific program with included integrations.
|
43
action.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
body {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
#update,
|
||||||
|
#warning,
|
||||||
|
#error,
|
||||||
|
#nobrowsers {
|
||||||
|
display: none;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#update {
|
||||||
|
background-color: #0e9352;
|
||||||
|
}
|
||||||
|
#update_message {
|
||||||
|
max-width: 280px;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
}
|
||||||
|
#warning {
|
||||||
|
background-color: #fec82f;
|
||||||
|
}
|
||||||
|
#error {
|
||||||
|
background-color: #e82727;
|
||||||
|
}
|
||||||
|
#nobrowsers {
|
||||||
|
color: #222426;
|
||||||
|
}
|
||||||
|
#browsers {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
-webkit-margin-start: 8px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.panel-section-footer-button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
37
action.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="action_browser.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="action.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="panel">
|
||||||
|
<div id="update" class="panel-section">
|
||||||
|
<div id="update_message"></div>
|
||||||
|
<div>
|
||||||
|
<button data-message="update_changelog_button"></button>
|
||||||
|
<button data-message="donate_button"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="warning" class="panel-section" data-message="update_warning"></div>
|
||||||
|
<div id="error" class="panel-section" data-message="update_error"></div>
|
||||||
|
<div class="panel-section panel-section-list">
|
||||||
|
<div id="nobrowsers" class="panel-section" data-message="popup_nobrowsers"></div>
|
||||||
|
<ul id="browsers">
|
||||||
|
<template>
|
||||||
|
<li class="panel-list-item">
|
||||||
|
<img class="icon" width="16" height="16" />
|
||||||
|
<div class="name text"></div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="panel-section panel-section-footer">
|
||||||
|
<div class="panel-section-footer-button" data-message="popup_options_button"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="common.js"></script>
|
||||||
|
<script type="text/javascript" src="action.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
117
action.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/* globals chrome, get_string, get_strings, is_same_colour, ERROR_COLOUR, WARNING_COLOUR */
|
||||||
|
let errorMessage = document.getElementById('error');
|
||||||
|
let warningMessage = document.getElementById('warning');
|
||||||
|
let updateMessage = document.getElementById('update');
|
||||||
|
let browsersList = document.getElementById('browsers');
|
||||||
|
let browsersTemplate = browsersList.querySelector('template');
|
||||||
|
|
||||||
|
get_strings();
|
||||||
|
|
||||||
|
chrome.browserAction.getBadgeBackgroundColor({}, function(colour) {
|
||||||
|
chrome.browserAction.setBadgeText({text: ''});
|
||||||
|
chrome.browserAction.setBadgeBackgroundColor({color: [0, 0, 0, 0]});
|
||||||
|
|
||||||
|
if (is_same_colour(colour, ERROR_COLOUR)) {
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
} else if (is_same_colour(colour, WARNING_COLOUR)) {
|
||||||
|
warningMessage.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
chrome.management.getSelf(function({version: currentVersion}) {
|
||||||
|
let now = new Date();
|
||||||
|
|
||||||
|
chrome.storage.local.get({
|
||||||
|
versionLastUpdate: '1970-01-01T00:00:00.000Z',
|
||||||
|
versionLastAck: '1970-01-01T00:00:00.000Z'
|
||||||
|
}, function({versionLastUpdate, versionLastAck}) {
|
||||||
|
if (typeof versionLastUpdate == 'string') {
|
||||||
|
versionLastUpdate = new Date(versionLastUpdate);
|
||||||
|
}
|
||||||
|
if (typeof versionLastAck == 'string') {
|
||||||
|
versionLastAck = new Date(versionLastAck);
|
||||||
|
}
|
||||||
|
if (now - versionLastUpdate < 43200000 && now - versionLastAck > 604800000) {
|
||||||
|
document.getElementById('update_message').textContent = get_string('update_message', currentVersion);
|
||||||
|
updateMessage.style.display = 'block';
|
||||||
|
}
|
||||||
|
chrome.storage.local.set({
|
||||||
|
versionLastUpdate: versionLastUpdate.toJSON(),
|
||||||
|
versionLastAck: versionLastAck.toJSON()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let userIcons = new Map();
|
||||||
|
chrome.runtime.sendMessage({action: 'get_icons'}, function(result) {
|
||||||
|
for (let l of result) {
|
||||||
|
userIcons.set(l.id.toString(), l);
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage({action: 'get_browsers'}, function(browsers) {
|
||||||
|
if (browsers.length === 0) {
|
||||||
|
document.getElementById('nobrowsers').style.display = 'block';
|
||||||
|
}
|
||||||
|
for (let b of browsers) {
|
||||||
|
if (b.hidden) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
add_browser(b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
browsersList.onclick = function(event) {
|
||||||
|
let target = event.target;
|
||||||
|
while (target && target.localName != 'li') {
|
||||||
|
target = target.parentNode;
|
||||||
|
}
|
||||||
|
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
action: 'open_browser',
|
||||||
|
id: target.dataset.id,
|
||||||
|
url: event.ctrlKey ? null : tabs[0].url
|
||||||
|
});
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMessage.onclick = function({target}) {
|
||||||
|
chrome.storage.local.set({versionLastAck: new Date().toJSON()});
|
||||||
|
switch (target.dataset.message) {
|
||||||
|
case 'update_changelog_button':
|
||||||
|
chrome.management.getSelf(function({version}) {
|
||||||
|
chrome.tabs.create({url: 'https://addons.mozilla.org/addon/open-with/versions/' + version});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case 'donate_button':
|
||||||
|
chrome.tabs.create({url: 'https://darktrojan.github.io/donate.html?openwith'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open_options_tab();
|
||||||
|
};
|
||||||
|
errorMessage.onclick = warningMessage.onclick = function() {
|
||||||
|
chrome.storage.local.set({versionLastAck: new Date().toJSON()});
|
||||||
|
open_options_tab();
|
||||||
|
};
|
||||||
|
document.querySelector('.panel-section-footer-button').onclick = open_options_tab;
|
||||||
|
|
||||||
|
function open_options_tab() {
|
||||||
|
chrome.runtime.openOptionsPage(function() {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_browser(b) {
|
||||||
|
let li = browsersTemplate.content.firstElementChild.cloneNode(true);
|
||||||
|
li.dataset.id = b.id;
|
||||||
|
if ('icon' in b && b.icon) { // b.icon could be undefined if we stuffed up (#170)
|
||||||
|
if (b.icon.startsWith('user_icon_')) {
|
||||||
|
li.querySelector('img').src = userIcons.get(b.icon.substring(10))['16'];
|
||||||
|
} else {
|
||||||
|
li.querySelector('img').src = 'icons/' + b.icon + '_16x16.png';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
li.querySelector('div.name').textContent = b.name;
|
||||||
|
browsersList.appendChild(li);
|
||||||
|
}
|
75
action_browser.css
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #222426;
|
||||||
|
cursor: default;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
font: message-box;
|
||||||
|
font-size: 13.333px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
body * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-size: 13.333px;
|
||||||
|
}
|
||||||
|
.panel-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.panel-section-list {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.panel-list-item {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
.panel-list-item:not(.disabled):hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.panel-list-item:not(.disabled):hover:active {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.panel-list-item > .icon {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.panel-list-item > .text {
|
||||||
|
flex-grow: 10;
|
||||||
|
}
|
||||||
|
.panel-section-footer {
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
color: #1a1a1a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 41px;
|
||||||
|
margin-top: -1px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.panel-section-footer-button {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 -1px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.panel-section-footer-button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.panel-section-footer-button:hover:active {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
33
background.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/* globals chrome, compare_versions, get_version_warn, ERROR_COLOUR, WARNING_COLOUR */
|
||||||
|
var exe;
|
||||||
|
|
||||||
|
function open_external(item) {
|
||||||
|
function error_listener(error) {
|
||||||
|
console.error(error, chrome.runtime.lastError);
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.downloads.cancel(item.id).then(
|
||||||
|
function () {
|
||||||
|
let command = exe.replace('%s', item.url);
|
||||||
|
|
||||||
|
let port = chrome.runtime.connectNative('download_with');
|
||||||
|
port.onDisconnect.addListener(error_listener);
|
||||||
|
port.onMessage.addListener((m) => {
|
||||||
|
console.log(m);
|
||||||
|
port.onDisconnect.removeListener(error_listener);
|
||||||
|
port.disconnect();
|
||||||
|
});
|
||||||
|
console.log('executing: '+command);
|
||||||
|
port.postMessage(command.split(' '));
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
console.log(`download_with: Could not cancel ${item.filename} (${item.id}): Error: ${err}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage.local.get({execute: null}, function({execute}) {
|
||||||
|
exe = execute;
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.downloads.onCreated.addListener(open_external);
|
89
common.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/* globals chrome */
|
||||||
|
/* exported get_version_warn, compare_versions, compare_object_versions, get_string, get_strings,
|
||||||
|
ERROR_COLOUR, WARNING_COLOUR, is_same_colour */
|
||||||
|
var _version_warn = null;
|
||||||
|
async function get_version_warn() {
|
||||||
|
if (!!_version_warn) {
|
||||||
|
return _version_warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('browser' in this && 'runtime' in this.browser && 'getBrowserInfo' in this.browser.runtime) {
|
||||||
|
browserInfo = await browser.runtime.getBrowserInfo();
|
||||||
|
if (browserInfo.name == 'Thunderbird') {
|
||||||
|
return '7.2.3';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0.0.1';
|
||||||
|
|
||||||
|
// return new Promise(function(resolve) {
|
||||||
|
// chrome.runtime.getPlatformInfo(function(platformInfo) {
|
||||||
|
// _version_warn = platformInfo.os == 'win' ? '7.0.1' : '7.0b10';
|
||||||
|
// resolve(_version_warn);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare_versions(a, b) {
|
||||||
|
function split_apart(name) {
|
||||||
|
var parts = [];
|
||||||
|
var lastIsDigit = false;
|
||||||
|
var part = '';
|
||||||
|
for (let c of name.toString()) {
|
||||||
|
let currentIsDigit = c >= '0' && c <= '9';
|
||||||
|
if (c == '.' || lastIsDigit != currentIsDigit) {
|
||||||
|
if (part) {
|
||||||
|
parts.push(lastIsDigit ? parseInt(part, 10) : part);
|
||||||
|
}
|
||||||
|
part = c == '.' ? '' : c;
|
||||||
|
} else {
|
||||||
|
part += c;
|
||||||
|
}
|
||||||
|
lastIsDigit = currentIsDigit;
|
||||||
|
}
|
||||||
|
if (part) {
|
||||||
|
parts.push(lastIsDigit ? parseInt(part, 10) : part);
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
function compare_parts(x, y) {
|
||||||
|
let xType = typeof x;
|
||||||
|
let yType = typeof y;
|
||||||
|
|
||||||
|
switch (xType) {
|
||||||
|
case yType:
|
||||||
|
return x == y ? 0 : (x < y ? -1 : 1);
|
||||||
|
case 'string':
|
||||||
|
return -1;
|
||||||
|
case 'undefined':
|
||||||
|
return yType == 'number' ? (y === 0 ? 0 : -1) : 1;
|
||||||
|
case 'number':
|
||||||
|
return x === 0 && yType == 'undefined' ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let aParts = split_apart(a);
|
||||||
|
let bParts = split_apart(b);
|
||||||
|
for (let i = 0; i <= aParts.length || i <= bParts.length; i++) {
|
||||||
|
let comparison = compare_parts(aParts[i], bParts[i]);
|
||||||
|
if (comparison !== 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare_object_versions(a, b) {
|
||||||
|
return compare_versions(a.name, b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ERROR_COLOUR = [232, 39, 39, 255];
|
||||||
|
var WARNING_COLOUR = [254, 200, 47, 255];
|
||||||
|
|
||||||
|
function is_same_colour(a, b) {
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
if (a[i] != b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
BIN
images/128.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
images/16.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/24.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
images/32.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
images/48.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/64.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
images/96.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
41
installed.css
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
body {
|
||||||
|
min-width: 25em;
|
||||||
|
}
|
||||||
|
div.panel {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
div.panel > div {
|
||||||
|
padding: 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
div.panel > div:last-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
div#header {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #0454d4;
|
||||||
|
}
|
||||||
|
div#button {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
border: 1px solid #b1b1b1;
|
||||||
|
box-shadow: 0 0 0 0 transparent;
|
||||||
|
height: 24px;
|
||||||
|
outline: 0 !important;
|
||||||
|
padding: 0 8px 0;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
transition-property: box-shadow, border;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #ebebeb;
|
||||||
|
border: 1px solid #b1b1b1;
|
||||||
|
}
|
||||||
|
button:hover:active {
|
||||||
|
background-color: #d4d4d4;
|
||||||
|
border: 1px solid #858585;
|
||||||
|
}
|
25
installed.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="action_browser.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="installed.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="panel">
|
||||||
|
<div>
|
||||||
|
<img src="images/openwith.svg" width="64" height="64" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div id="header" data-message="popup_installed_header2"></div>
|
||||||
|
<div data-message="popup_installed_text"></div>
|
||||||
|
<div><a data-href="webextension_info_url" data-message="webextension_info_label"></a></div>
|
||||||
|
<div id="button">
|
||||||
|
<button data-message="popup_options_button"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="common.js"></script>
|
||||||
|
<script type="text/javascript" src="installed.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
installed.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/* globals chrome, get_strings */
|
||||||
|
get_strings();
|
||||||
|
|
||||||
|
document.querySelector('button').onclick = function() {
|
||||||
|
chrome.runtime.openOptionsPage(function() {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
};
|
38
manifest.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Download With",
|
||||||
|
"description": "Set an external manager for your downloads.",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "downloadwith@wirlaburla.github.io",
|
||||||
|
"strict_min_version": "63.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"16": "images/16.png",
|
||||||
|
"24": "images/24.png",
|
||||||
|
"32": "images/32.png",
|
||||||
|
"48": "images/48.png",
|
||||||
|
"64": "images/64.png",
|
||||||
|
"96": "images/96.png",
|
||||||
|
"128": "images/128.png"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"common.js",
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"downloads",
|
||||||
|
"nativeMessaging",
|
||||||
|
"storage",
|
||||||
|
"tabs"
|
||||||
|
],
|
||||||
|
"options_ui": {
|
||||||
|
"browser_style": false,
|
||||||
|
"open_in_tab": true,
|
||||||
|
"page": "options.html"
|
||||||
|
}
|
||||||
|
}
|
110
native/download_with_linux.py
Executable file
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
VERSION = '0.0.1'
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdin.buffer
|
||||||
|
|
||||||
|
# Python 3.x version
|
||||||
|
# Read a message from stdin and decode it.
|
||||||
|
def getMessage():
|
||||||
|
rawLength = sys.stdin.buffer.read(4)
|
||||||
|
if len(rawLength) == 0:
|
||||||
|
sys.exit(0)
|
||||||
|
messageLength = struct.unpack('@I', rawLength)[0]
|
||||||
|
message = sys.stdin.buffer.read(messageLength).decode('utf-8')
|
||||||
|
return json.loads(message)
|
||||||
|
|
||||||
|
# Send an encoded message to stdout
|
||||||
|
def sendMessage(messageContent):
|
||||||
|
encodedContent = json.dumps(messageContent).encode('utf-8')
|
||||||
|
encodedLength = struct.pack('@I', len(encodedContent))
|
||||||
|
|
||||||
|
sys.stdout.buffer.write(encodedLength)
|
||||||
|
sys.stdout.buffer.write(encodedContent)
|
||||||
|
sys.stdout.buffer.flush()
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
# Python 2.x version (if sys.stdin.buffer is not defined)
|
||||||
|
print('Python 3.2 or newer is required.')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
def install():
|
||||||
|
home_path = os.getenv('HOME')
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
'name': 'download_with',
|
||||||
|
'description': 'Download With native host',
|
||||||
|
'path': os.path.realpath(__file__),
|
||||||
|
'type': 'stdio',
|
||||||
|
}
|
||||||
|
locations = {
|
||||||
|
'chrome': os.path.join(home_path, '.config', 'google-chrome', 'NativeMessagingHosts'),
|
||||||
|
'chrome-beta': os.path.join(home_path, '.config', 'google-chrome-beta', 'NativeMessagingHosts'),
|
||||||
|
'chrome-unstable': os.path.join(home_path, '.config', 'google-chrome-unstable', 'NativeMessagingHosts'),
|
||||||
|
'chromium': os.path.join(home_path, '.config', 'chromium', 'NativeMessagingHosts'),
|
||||||
|
'firefox': os.path.join(home_path, '.mozilla', 'native-messaging-hosts'),
|
||||||
|
'librewolf': os.path.join(home_path, '.librewolf', 'native-messaging-hosts'),
|
||||||
|
'waterfox': os.path.join(home_path, '.waterfox', 'native-messaging-hosts'),
|
||||||
|
'waterfox-g4': os.path.join(home_path, '.waterfox', 'native-messaging-hosts'),
|
||||||
|
'thunderbird': os.path.join(home_path, '.thunderbird', 'native-messaging-hosts'),
|
||||||
|
}
|
||||||
|
filename = 'download_with.json'
|
||||||
|
|
||||||
|
for browser, location in locations.items():
|
||||||
|
if os.path.exists(os.path.dirname(location)):
|
||||||
|
if not os.path.exists(location):
|
||||||
|
os.mkdir(location)
|
||||||
|
|
||||||
|
browser_manifest = manifest.copy()
|
||||||
|
if browser in ['firefox', 'thunderbird', 'librewolf', 'waterfox', 'waterfox-g4']:
|
||||||
|
browser_manifest['allowed_extensions'] = ['downloadwith@wirlaburla.github.io']
|
||||||
|
else:
|
||||||
|
browser_manifest['allowed_origins'] = [
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/', # Chrome
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/', # Opera
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(os.path.join(location, filename), 'w') as file:
|
||||||
|
file.write(
|
||||||
|
json.dumps(browser_manifest, indent=2, separators=(',', ': '), sort_keys=True).replace(' ', '\t') + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
def listen():
|
||||||
|
receivedMessage = getMessage()
|
||||||
|
if receivedMessage == 'ping':
|
||||||
|
sendMessage({
|
||||||
|
'version': VERSION,
|
||||||
|
'file': os.path.realpath(__file__)
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
devnull = open(os.devnull, 'w')
|
||||||
|
subprocess.Popen(receivedMessage, stdout=devnull, stderr=devnull)
|
||||||
|
sendMessage(None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if sys.argv[1] == 'install':
|
||||||
|
install()
|
||||||
|
sys.exit(0)
|
||||||
|
allowed_extensions = [
|
||||||
|
'downloadwith@wirlaburla.github.io',
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/',
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/',
|
||||||
|
]
|
||||||
|
for ae in allowed_extensions:
|
||||||
|
if ae in sys.argv:
|
||||||
|
listen()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print('This is the Download With native helper, version %s.' % VERSION)
|
||||||
|
print('Run this script again with the word "install" after the file name to install.')
|
152
native/download_with_mac.py
Executable file
|
@ -0,0 +1,152 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
VERSION = '7.2.6'
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdin.buffer
|
||||||
|
|
||||||
|
# Python 3.x version
|
||||||
|
# Read a message from stdin and decode it.
|
||||||
|
def getMessage():
|
||||||
|
rawLength = sys.stdin.buffer.read(4)
|
||||||
|
if len(rawLength) == 0:
|
||||||
|
sys.exit(0)
|
||||||
|
messageLength = struct.unpack('@I', rawLength)[0]
|
||||||
|
message = sys.stdin.buffer.read(messageLength).decode('utf-8')
|
||||||
|
return json.loads(message)
|
||||||
|
|
||||||
|
# Send an encoded message to stdout
|
||||||
|
def sendMessage(messageContent):
|
||||||
|
encodedContent = json.dumps(messageContent).encode('utf-8')
|
||||||
|
encodedLength = struct.pack('@I', len(encodedContent))
|
||||||
|
|
||||||
|
sys.stdout.buffer.write(encodedLength)
|
||||||
|
sys.stdout.buffer.write(encodedContent)
|
||||||
|
sys.stdout.buffer.flush()
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
print('Python 3.2 or newer is required.')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
def install():
|
||||||
|
home_path = os.getenv('HOME')
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
'name': 'open_with',
|
||||||
|
'description': 'Open With native host',
|
||||||
|
'path': os.path.realpath(__file__),
|
||||||
|
'type': 'stdio',
|
||||||
|
}
|
||||||
|
locations = {
|
||||||
|
'chrome': os.path.join(home_path, 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts'),
|
||||||
|
'chromium': os.path.join(home_path, 'Library', 'Application Support', 'Chromium', 'NativeMessagingHosts'),
|
||||||
|
'edge': os.path.join(home_path, 'Library', 'Application Support', 'Microsoft Edge', 'NativeMessagingHosts'),
|
||||||
|
'firefox': os.path.join(home_path, 'Library', 'Application Support', 'Mozilla', 'NativeMessagingHosts'),
|
||||||
|
'thunderbird1': os.path.join(home_path, 'Library', 'Application Support', 'Thunderbird', 'NativeMessagingHosts'),
|
||||||
|
'thunderbird2': os.path.join(home_path, 'Library', 'Mozilla', 'NativeMessagingHosts'),
|
||||||
|
}
|
||||||
|
filename = 'open_with.json'
|
||||||
|
|
||||||
|
for browser, location in locations.items():
|
||||||
|
if os.path.exists(os.path.dirname(location)):
|
||||||
|
if not os.path.exists(location):
|
||||||
|
os.makedirs(location, exist_ok=True)
|
||||||
|
|
||||||
|
browser_manifest = manifest.copy()
|
||||||
|
if browser in ['firefox', 'thunderbird1', 'thunderbird2']:
|
||||||
|
browser_manifest['allowed_extensions'] = ['openwith@darktrojan.net']
|
||||||
|
else:
|
||||||
|
browser_manifest['allowed_origins'] = [
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/', # Chrome
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/', # Opera
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(os.path.join(location, filename), 'w') as file:
|
||||||
|
file.write(
|
||||||
|
json.dumps(browser_manifest, indent=2, separators=(',', ': '), sort_keys=True).replace(' ', '\t') + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_browsers():
|
||||||
|
apps = [
|
||||||
|
'Chrome',
|
||||||
|
'Chromium',
|
||||||
|
'Firefox',
|
||||||
|
'Google Chrome',
|
||||||
|
'Microsoft Edge',
|
||||||
|
'Opera',
|
||||||
|
'Safari',
|
||||||
|
'SeaMonkey',
|
||||||
|
]
|
||||||
|
paths = [
|
||||||
|
os.path.join(os.getenv('HOME'), 'Applications'),
|
||||||
|
'/Applications',
|
||||||
|
]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for p in paths:
|
||||||
|
for a in apps:
|
||||||
|
fp = os.path.join(p, a) + '.app'
|
||||||
|
if os.path.exists(fp):
|
||||||
|
results.append({
|
||||||
|
'name': a,
|
||||||
|
'command': '"%s.app"' % os.path.join(p, a)
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def listen():
|
||||||
|
receivedMessage = getMessage()
|
||||||
|
if receivedMessage == 'ping':
|
||||||
|
sendMessage({
|
||||||
|
'version': VERSION,
|
||||||
|
'file': os.path.realpath(__file__)
|
||||||
|
})
|
||||||
|
elif receivedMessage == 'find':
|
||||||
|
sendMessage(find_browsers())
|
||||||
|
else:
|
||||||
|
for k, v in os.environ.items():
|
||||||
|
if k.startswith('MOZ_'):
|
||||||
|
try:
|
||||||
|
os.unsetenv(k)
|
||||||
|
except:
|
||||||
|
os.environ[k] = ''
|
||||||
|
|
||||||
|
devnull = open(os.devnull, 'w')
|
||||||
|
if receivedMessage[0].endswith('.app'):
|
||||||
|
command = ['/usr/bin/open', '-a'] + receivedMessage
|
||||||
|
else:
|
||||||
|
command = receivedMessage
|
||||||
|
subprocess.Popen(command, stdout=devnull, stderr=devnull)
|
||||||
|
sendMessage(None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if sys.argv[1] == 'install':
|
||||||
|
install()
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.argv[1] == 'find_browsers':
|
||||||
|
print(find_browsers())
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
allowed_extensions = [
|
||||||
|
'openwith@darktrojan.net',
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/',
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/',
|
||||||
|
]
|
||||||
|
for ae in allowed_extensions:
|
||||||
|
if ae in sys.argv:
|
||||||
|
listen()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print('This is the Open With native helper, version %s.' % VERSION)
|
||||||
|
print('Run this script again with the word "install" after the file name to install.')
|
161
native/download_with_windows.ps1
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
function GetMessage {
|
||||||
|
$reader = New-Object System.IO.BinaryReader([System.Console]::OpenStandardInput())
|
||||||
|
$messageLength = $reader.ReadInt32()
|
||||||
|
$messageBytes = $reader.ReadBytes($messageLength)
|
||||||
|
return [System.Text.Encoding]::UTF8.GetString($messageBytes) | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
|
||||||
|
function SendReply {
|
||||||
|
param ($reply)
|
||||||
|
$replyBytes = [System.Text.Encoding]::UTF8.GetBytes(($reply | ConvertTo-Json))
|
||||||
|
$writer = New-Object System.IO.BinaryWriter([System.Console]::OpenStandardOutput())
|
||||||
|
$writer.Write($replyBytes.Count)
|
||||||
|
$writer.Write($replyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install {
|
||||||
|
$registry_locations = @{
|
||||||
|
chrome='HKCU:\Software\Google\Chrome\NativeMessagingHosts';
|
||||||
|
firefox='HKCU:\Software\Mozilla\NativeMessagingHosts'
|
||||||
|
}
|
||||||
|
|
||||||
|
$install_path = Split-Path $PSCommandPath -Parent
|
||||||
|
$bat_path = (Join-Path $install_path -ChildPath 'open_with.bat')
|
||||||
|
New-Item -Force -Path $bat_path -Value (@'
|
||||||
|
@echo off
|
||||||
|
call "powershell" -file "
|
||||||
|
'@ + $PSCommandPath + '"') > $null
|
||||||
|
|
||||||
|
$manifest = @{name='open_with';type='stdio';path=$bat_path;description='Open With native host'}
|
||||||
|
|
||||||
|
foreach ($browser in $registry_locations.Keys) {
|
||||||
|
$registry_location = $registry_locations[$browser]
|
||||||
|
if (Get-Item (Split-Path $registry_location -Parent)) {
|
||||||
|
if (!(Get-Item $registry_location -ErrorAction Ignore)) {
|
||||||
|
New-Item $registry_location > $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$registry_location = Join-Path $registry_location -ChildPath 'open_with'
|
||||||
|
$manifest_location = Join-Path $install_path -ChildPath ('open_with_' + $browser + '.json')
|
||||||
|
if (!(Get-Item $registry_location -ErrorAction Ignore)) {
|
||||||
|
New-Item $registry_location > $null
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Item -Path $registry_location -Value $manifest_location -Force
|
||||||
|
$browser_manifest = $manifest.Clone()
|
||||||
|
if ($browser -eq 'firefox') {
|
||||||
|
$browser_manifest['allowed_extensions'] = @('openwith@darktrojan.net')
|
||||||
|
} else {
|
||||||
|
$browser_manifest['allowed_origins'] = @('chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/')
|
||||||
|
}
|
||||||
|
New-Item -Force -Path $manifest_location -Value ($browser_manifest | ConvertTo-Json) > $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FindBrowsers {
|
||||||
|
return (Get-ChildItem -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\' |
|
||||||
|
Select-Object -Property @{Name='name';Expression={$_.GetValue($null)}}, @{Name='command';Expression={$_.OpenSubKey('shell\open\command').GetValue($null)}})
|
||||||
|
}
|
||||||
|
|
||||||
|
# From https://github.com/FuzzySecurity/PowerShell-Suite
|
||||||
|
function Invoke-CreateProcess {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$Binary,
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
[string]$Args=$null,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$CreationFlags,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$ShowWindow,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$StartF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define all the structures for CreateProcess
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PROCESS_INFORMATION
|
||||||
|
{
|
||||||
|
public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public struct STARTUPINFO
|
||||||
|
{
|
||||||
|
public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle;
|
||||||
|
public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars;
|
||||||
|
public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow;
|
||||||
|
public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput;
|
||||||
|
public IntPtr hStdError;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct SECURITY_ATTRIBUTES
|
||||||
|
{
|
||||||
|
public int length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Kernel32
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError=true)]
|
||||||
|
public static extern bool CreateProcess(
|
||||||
|
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
|
||||||
|
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
|
||||||
|
IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
|
||||||
|
out PROCESS_INFORMATION lpProcessInformation);
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
# StartupInfo Struct
|
||||||
|
$StartupInfo = New-Object STARTUPINFO
|
||||||
|
$StartupInfo.dwFlags = $StartF # StartupInfo.dwFlag
|
||||||
|
$StartupInfo.wShowWindow = $ShowWindow # StartupInfo.ShowWindow
|
||||||
|
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size
|
||||||
|
|
||||||
|
# ProcessInfo Struct
|
||||||
|
$ProcessInfo = New-Object PROCESS_INFORMATION
|
||||||
|
|
||||||
|
# SECURITY_ATTRIBUTES Struct (Process & Thread)
|
||||||
|
$SecAttr = New-Object SECURITY_ATTRIBUTES
|
||||||
|
$SecAttr.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SecAttr)
|
||||||
|
|
||||||
|
# CreateProcess --> lpCurrentDirectory
|
||||||
|
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName
|
||||||
|
|
||||||
|
# Call CreateProcess
|
||||||
|
[Kernel32]::CreateProcess($Binary, $Args, [ref] $SecAttr, [ref] $SecAttr, $false, $CreationFlags, [IntPtr]::Zero, $GetCurrentPath, [ref] $StartupInfo, [ref] $ProcessInfo) |out-null
|
||||||
|
|
||||||
|
echo "`nProcess Information:"
|
||||||
|
Get-Process -Id $ProcessInfo.dwProcessId |ft
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($args.Length -eq 1) {
|
||||||
|
if ($args[0] -eq 'install') {
|
||||||
|
Install
|
||||||
|
Exit(0)
|
||||||
|
} elseif ($args[0] -eq 'find_browsers') {
|
||||||
|
FindBrowsers | Format-List
|
||||||
|
Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = GetMessage
|
||||||
|
if ($message -eq 'ping') {
|
||||||
|
SendReply @{'version'='7.2.2';'file'=$PSCommandPath}
|
||||||
|
} elseif ($message -eq 'find') {
|
||||||
|
SendReply (FindBrowsers)
|
||||||
|
} else {
|
||||||
|
if ($message.Length -gt 1) {
|
||||||
|
$c = $message.Length - 1
|
||||||
|
Invoke-CreateProcess -Binary $message[0] -Args ('"' + $message[0] + '" ' + [String]::Join(' ', $message[1..$c])) -CreationFlags 0x01000010 -ShowWindow 1 -StartF 1
|
||||||
|
} else {
|
||||||
|
Invoke-CreateProcess -Binary $message[0] -CreationFlags 0x01000000 -ShowWindow 1 -StartF 1
|
||||||
|
}
|
||||||
|
SendReply $null
|
||||||
|
}
|
164
native/download_with_windows.py
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
VERSION = '7.2.6'
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdin.buffer
|
||||||
|
|
||||||
|
# Python 3.x version
|
||||||
|
# Read a message from stdin and decode it.
|
||||||
|
def getMessage():
|
||||||
|
rawLength = sys.stdin.buffer.read(4)
|
||||||
|
if len(rawLength) == 0:
|
||||||
|
sys.exit(0)
|
||||||
|
messageLength = struct.unpack('@I', rawLength)[0]
|
||||||
|
message = sys.stdin.buffer.read(messageLength).decode('utf-8')
|
||||||
|
return json.loads(message)
|
||||||
|
|
||||||
|
# Send an encoded message to stdout
|
||||||
|
def sendMessage(messageContent):
|
||||||
|
encodedContent = json.dumps(messageContent).encode('utf-8')
|
||||||
|
encodedLength = struct.pack('@I', len(encodedContent))
|
||||||
|
|
||||||
|
sys.stdout.buffer.write(encodedLength)
|
||||||
|
sys.stdout.buffer.write(encodedContent)
|
||||||
|
sys.stdout.buffer.flush()
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
# Python 2.x version (if sys.stdin.buffer is not defined)
|
||||||
|
print('Python 3.2 or newer is required.')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
def install():
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
import winreg as _winreg
|
||||||
|
except:
|
||||||
|
import _winreg
|
||||||
|
|
||||||
|
this_file = os.path.realpath(__file__)
|
||||||
|
install_path = os.path.dirname(this_file)
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
'name': 'open_with',
|
||||||
|
'description': 'Open With native host',
|
||||||
|
'path': this_file,
|
||||||
|
'type': 'stdio',
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest['path'] = filename = os.path.join(install_path, 'open_with.bat')
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
file.write('@echo off\r\ncall "%s" "%s" %%1 %%2\r\n' % (sys.executable, this_file))
|
||||||
|
|
||||||
|
registry_locations = {
|
||||||
|
'chrome': os.path.join('Software', 'Google', 'Chrome', 'NativeMessagingHosts'),
|
||||||
|
'chromium': os.path.join('Software', 'Chromium', 'NativeMessagingHosts'),
|
||||||
|
'firefox': os.path.join('Software', 'Mozilla', 'NativeMessagingHosts'),
|
||||||
|
'thunderbird': os.path.join('Software', 'Thunderbird', 'NativeMessagingHosts'),
|
||||||
|
}
|
||||||
|
|
||||||
|
for browser, registry_location in registry_locations.items():
|
||||||
|
browser_manifest = manifest.copy()
|
||||||
|
if browser in ['firefox', 'thunderbird']:
|
||||||
|
browser_manifest['allowed_extensions'] = ['openwith@darktrojan.net']
|
||||||
|
else:
|
||||||
|
browser_manifest['allowed_origins'] = [
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/', # Chrome
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/', # Opera
|
||||||
|
]
|
||||||
|
|
||||||
|
filename = os.path.join(install_path, 'open_with_%s.json' % browser)
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
file.write(
|
||||||
|
json.dumps(browser_manifest, indent=2, separators=(',', ': '), sort_keys=True).replace(' ', '\t') + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
key = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, registry_location)
|
||||||
|
_winreg.SetValue(key, 'open_with', _winreg.REG_SZ, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def find_browsers():
|
||||||
|
try:
|
||||||
|
import winreg as _winreg
|
||||||
|
except:
|
||||||
|
import _winreg
|
||||||
|
|
||||||
|
windir = os.getenv('windir')
|
||||||
|
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, os.path.join('Software', 'Clients', 'StartMenuInternet'))
|
||||||
|
count = _winreg.QueryInfoKey(key)[0]
|
||||||
|
|
||||||
|
browsers = []
|
||||||
|
found_msedge = False
|
||||||
|
while count > 0:
|
||||||
|
subkey = _winreg.EnumKey(key, count - 1)
|
||||||
|
try:
|
||||||
|
browsers.append({
|
||||||
|
'name': _winreg.QueryValue(key, subkey),
|
||||||
|
'command': _winreg.QueryValue(key, os.path.join(subkey, 'shell', 'open', 'command'))
|
||||||
|
})
|
||||||
|
if subkey == 'Microsoft Edge':
|
||||||
|
found_msedge = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
count -= 1
|
||||||
|
|
||||||
|
if not found_msedge and \
|
||||||
|
os.path.exists(os.path.join(windir, 'SystemApps', 'Microsoft.MicrosoftEdge_8wekyb3d8bbwe', 'MicrosoftEdge.exe')):
|
||||||
|
browsers.append({
|
||||||
|
'name': 'Microsoft Edge',
|
||||||
|
'command': os.path.join(windir, 'explorer.exe') + ' "microsoft-edge:%s "'
|
||||||
|
})
|
||||||
|
|
||||||
|
return browsers
|
||||||
|
|
||||||
|
|
||||||
|
def listen():
|
||||||
|
receivedMessage = getMessage()
|
||||||
|
if receivedMessage == 'ping':
|
||||||
|
sendMessage({
|
||||||
|
'version': VERSION,
|
||||||
|
'file': os.path.realpath(__file__)
|
||||||
|
})
|
||||||
|
elif receivedMessage == 'find':
|
||||||
|
sendMessage(find_browsers())
|
||||||
|
else:
|
||||||
|
for k, v in os.environ.items():
|
||||||
|
if k.startswith('MOZ_'):
|
||||||
|
try:
|
||||||
|
os.unsetenv(k)
|
||||||
|
except:
|
||||||
|
os.environ[k] = ''
|
||||||
|
|
||||||
|
CREATE_BREAKAWAY_FROM_JOB = 0x01000000
|
||||||
|
CREATE_NEW_CONSOLE = 0x00000010
|
||||||
|
subprocess.Popen(receivedMessage, creationflags=CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_CONSOLE)
|
||||||
|
sendMessage(None)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if sys.argv[1] == 'install':
|
||||||
|
install()
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.argv[1] == 'find_browsers':
|
||||||
|
print(find_browsers())
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
allowed_extensions = [
|
||||||
|
'openwith@darktrojan.net',
|
||||||
|
'chrome-extension://cogjlncmljjnjpbgppagklanlcbchlno/',
|
||||||
|
'chrome-extension://fbmcaggceafhobjkhnaakhgfmdaadhhg/',
|
||||||
|
]
|
||||||
|
for ae in allowed_extensions:
|
||||||
|
if ae in sys.argv:
|
||||||
|
listen()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print('This is the Open With native helper, version %s.' % VERSION)
|
||||||
|
print('Run this script again with the word "install" after the file name to install.')
|
12
options.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#group > div {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
#test_results, #exeinput {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
27
options.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title data-message="options_title">Download With Options</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="options.css" />
|
||||||
|
<link rel="icon" type="image/png" href="images/16.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<center id=group>
|
||||||
|
<div><img width=24 height=24 src='images/24.png'> <b>Download With</b><div style='flex-grow: 1;'></div>Options</div>
|
||||||
|
<hr>
|
||||||
|
<b>Native Test</b>
|
||||||
|
<div id=test>
|
||||||
|
<div id=test_results>Click button to test.</div>
|
||||||
|
<button id=test_button data-message="test_button">Test</button>
|
||||||
|
</div>
|
||||||
|
<b>Executable</b>
|
||||||
|
<div id=execute>
|
||||||
|
<input id=exeinput type=text></div>
|
||||||
|
</div>
|
||||||
|
<div>Use '%s' in place of the URL.</div>
|
||||||
|
</center>
|
||||||
|
<script type="text/javascript" src="common.js"></script>
|
||||||
|
<script type="text/javascript" src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
options.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/* globals chrome, compare_versions, compare_object_versions, get_version_warn, get_string, get_strings */
|
||||||
|
let testResult = document.getElementById('test_results');
|
||||||
|
let exeInput = document.getElementById('exeinput');
|
||||||
|
|
||||||
|
document.getElementById('test_button').onclick = function() {
|
||||||
|
function error_listener() {
|
||||||
|
testResult.style.color = 'red';
|
||||||
|
testResult.innerText = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
let port = chrome.runtime.connectNative('download_with');
|
||||||
|
port.onDisconnect.addListener(error_listener);
|
||||||
|
port.onMessage.addListener(function(message) {
|
||||||
|
if (message) {
|
||||||
|
console.log(message);
|
||||||
|
testResult.style.color = 'darkgreen';
|
||||||
|
testResult.innerText = "Success!";
|
||||||
|
} else {
|
||||||
|
error_listener();
|
||||||
|
}
|
||||||
|
port.onDisconnect.removeListener(error_listener);
|
||||||
|
port.disconnect();
|
||||||
|
});
|
||||||
|
port.postMessage('ping');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exeInput.onchange = function(event) {
|
||||||
|
chrome.storage.local.set({execute: exeInput.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage.local.get({execute: null}, function({execute}) {
|
||||||
|
exeInput.value = execute;
|
||||||
|
});
|