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

242 lines
6.8 KiB
JavaScript

'use strict';
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Config = imports.config;
const PluginBase = imports.service.plugin;
var Metadata = {
label: _('Photo'),
description: _('Request the paired device to take a photo and transfer it to this PC'),
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 PluginBase.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),
});
}
handlePacket(packet) {
switch (packet.type) {
case 'kdeconnect.photo':
this._receivePhoto(packet);
break;
case 'kdeconnect.photo.request':
this._sendPhoto(packet);
break;
}
}
/**
* Ensure we have a directory set for storing files that exists.
*
* @return {string} An absolute directory path
*/
_ensureReceiveDirectory() {
if (this._receiveDir !== undefined)
return this._receiveDir;
// Ensure a directory is set
this._receiveDir = this.settings.get_string('receive-directory');
if (this._receiveDir === '') {
this._receiveDir = GLib.get_user_special_dir(
GLib.UserDirectory.DIRECTORY_PICTURES
);
// Fallback to ~/Pictures
const homeDir = GLib.get_home_dir();
if (!this._receiveDir || this._receiveDir === homeDir) {
this._receiveDir = GLib.build_filenamev([homeDir, 'Pictures']);
this.settings.set_string('receive-directory', this._receiveDir);
}
}
// Ensure the directory exists
if (!GLib.file_test(this._receiveDir, GLib.FileTest.IS_DIR))
GLib.mkdir_with_parents(this._receiveDir, 448);
return this._receiveDir;
}
/**
* Get a GFile for @filename, while ensuring the directory exists and the
* file is unique.
*
* @param {string} filename - A filename (eg. `image.jpg`)
* @return {Gio.File} a file object
*/
_getFile(filename) {
const dirpath = this._ensureReceiveDirectory();
const basepath = GLib.build_filenamev([dirpath, filename]);
let filepath = basepath;
let copyNum = 0;
while (GLib.file_test(filepath, GLib.FileTest.EXISTS))
filepath = `${basepath} (${++copyNum})`;
return Gio.File.new_for_path(filepath);
}
/**
* Receive a photo taken by the remote device.
*
* @param {Core.Packet} packet - a `kdeconnect.photo`
*/
async _receivePhoto(packet) {
let file, transfer;
try {
// Remote device cancelled the photo operation
if (packet.body.hasOwnProperty('cancel'))
return;
// Open the target path and create a transfer
file = this._getFile(packet.body.filename);
transfer = this.device.createTransfer();
transfer.addFile(packet, file);
// Open the photo if successful, delete on failure
await transfer.start();
const uri = file.get_uri();
Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null);
} catch (e) {
debug(e, this.device.name);
if (file)
file.delete_async(GLib.PRIORITY_DEFAULT, null, null);
}
}
/**
* Take a photo using the Webcam and return the path.
*
* @param {Core.Packet} packet - A `kdeconnect.photo.request`
* @return {Promise<string>} A file path
*/
_takePhoto(packet) {
return new Promise((resolve, reject) => {
const time = GLib.DateTime.new_now_local().format('%T');
const path = GLib.build_filenamev([GLib.get_tmp_dir(), `${time}.jpg`]);
const proc = this._launcher.spawnv([
Config.FFMPEG_PATH,
'-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);
}
});
});
}
/**
* Send a photo to the remote device.
*
* @param {Core.Packet} packet - A `kdeconnect.photo.request`
*/
async _sendPhoto(packet) {
if (this.settings.get_boolean('share-camera'))
return;
let file, transfer;
try {
// Take a photo
const path = await this._takePhoto();
if (path.startsWith('file://'))
file = Gio.File.new_for_uri(path);
else
file = Gio.File.new_for_path(path);
// Create the transfer
transfer = this.device.createTransfer();
transfer.addFile({
type: 'kdeconnect.photo',
body: {
filename: file.get_basename(),
},
}, file);
await transfer.start();
} catch (e) {
debug(e, this.device.name);
if (transfer) {
this.device.showNotification({
id: transfer.uuid,
title: _('Transfer Failed'),
// TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel
body: _('Failed to send “%s” to %s').format(
file.get_basename(),
this.device.name
),
icon: new Gio.ThemedIcon({name: 'dialog-warning-symbolic'}),
});
}
}
}
/**
* Request the remote device begin a photo operation.
*/
photo() {
this.device.sendPacket({
type: 'kdeconnect.photo.request',
body: {},
});
}
});