dot/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/plugins/photo.js

216 lines
6.0 KiB
JavaScript

'use strict';
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const PluginsBase = imports.service.plugins.base;
var Metadata = {
label: _('Photo'),
id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Photo',
incomingCapabilities: ['kdeconnect.photo', 'kdeconnect.photo.request'],
outgoingCapabilities: ['kdeconnect.photo', 'kdeconnect.photo.request'],
actions: {
photo: {
label: _('Photo'),
icon_name: 'camera-photo-symbolic',
parameter_type: null,
incoming: ['kdeconnect.photo'],
outgoing: ['kdeconnect.photo.request']
}
}
};
/**
* Photo Plugin
* https://github.com/KDE/kdeconnect-kde/tree/master/plugins/photo
*
* TODO: use Cheese?
* check for /dev/video*
*/
var Plugin = GObject.registerClass({
GTypeName: 'GSConnectPhotoPlugin'
}, class Plugin extends PluginsBase.Plugin {
_init(device) {
super._init(device, 'photo');
// A reusable launcher for silence procs
this._launcher = new Gio.SubprocessLauncher({
flags: (Gio.SubprocessFlags.STDOUT_SILENCE |
Gio.SubprocessFlags.STDERR_SILENCE)
});
}
get camera() {
return this.settings.get_boolean('share-camera');
}
handlePacket(packet) {
if (packet.type === 'kdeconnect.photo.request' && this.camera) {
this._sendPhoto();
} else if (packet.type === 'kdeconnect.photo') {
this._receivePhoto(packet);
}
}
_ensureReceiveDirectory() {
let receiveDir = this.settings.get_string('receive-directory');
// Ensure a directory is set
if (!receiveDir) {
receiveDir = GLib.get_user_special_dir(
GLib.UserDirectory.DIRECTORY_PICTURES
);
// Fallback to ~/Pictures
let homeDir = GLib.get_home_dir();
if (!receiveDir || receiveDir === homeDir) {
receiveDir = GLib.build_filenamev([homeDir, 'Pictures']);
}
this.settings.set_string('receive-directory', receiveDir);
}
// Ensure the directory exists
if (!GLib.file_test(receiveDir, GLib.FileTest.IS_DIR)) {
GLib.mkdir_with_parents(receiveDir, 448);
}
return receiveDir;
}
_getFile(filename) {
let dirpath = this._ensureReceiveDirectory();
let basepath = GLib.build_filenamev([dirpath, filename]);
let filepath = basepath;
let copyNum = 0;
while (GLib.file_test(filepath, GLib.FileTest.EXISTS)) {
copyNum += 1;
filepath = `${basepath} (${copyNum})`;
}
return Gio.File.new_for_path(filepath);
}
async _receivePhoto(packet) {
let file, stream, success, transfer;
try {
// Remote device cancelled the photo operation
if (packet.body.cancel) return;
file = this._getFile(packet.body.filename);
stream = await new Promise((resolve, reject) => {
file.replace_async(null, false, 0, 0, null, (file, res) => {
try {
resolve(file.replace_finish(res));
} catch (e) {
reject(e);
}
});
});
transfer = this.device.createTransfer(Object.assign({
output_stream: stream,
size: packet.payloadSize
}, packet.payloadTransferInfo));
// Start transfer
success = await transfer.download(packet.payloadTransferInfo.port);
// Open the photo on success
if (success) {
let uri = file.get_uri();
Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null);
// Clean up the downloaded file on failure
} else {
file.delete(null);
}
} catch (e) {
logError(e);
}
}
/**
* Take a photo using the Webcam, return a file path
*/
_takePhoto() {
return new Promise((resolve, reject) => {
let time = GLib.DateTime.new_now_local().format('%T');
let path = GLib.build_filenamev([GLib.get_tmp_dir(), `${time}.jpg`]);
let proc = this._launcher.spawnv([
gsconnect.metadata.bin.ffmpeg,
'-f', 'video4linux2',
'-ss', '0:0:2',
'-i', '/dev/video0',
'-frames', '1',
path
]);
proc.wait_check_async(null, (proc, res) => {
try {
proc.wait_check_finish(res);
resolve(path);
} catch (e) {
reject(e);
}
});
});
}
async _sendPhoto() {
let file, path, stream, transfer;
try {
path = await this._takePhoto();
if (path.startsWith('file://')) {
file = Gio.File.new_for_uri(path);
} else {
file = Gio.File.new_for_path(path);
}
stream = await new Promise((resolve, reject) => {
file.read_async(GLib.PRIORITY_DEFAULT, null, (file, res) => {
try {
resolve(file.read_finish(res));
} catch (e) {
reject(e);
}
});
});
transfer = this.device.createTransfer({
input_stream: stream,
size: file.query_info('standard::size', 0, null).get_size()
});
await transfer.upload({
type: 'kdeconnect.photo',
body: {
filename: file.get_basename()
}
});
} catch (e) {
debug(e, this.device.name);
}
}
photo() {
this.device.sendPacket({
type: 'kdeconnect.photo.request',
body: {}
});
}
});