2020-07-30 06:13:23 -03:00
|
|
|
/* jslint esversion: 6 */
|
2020-09-10 10:19:17 -03:00
|
|
|
/* exported Icons, Image, Images, Json, getJsons, getDateString */
|
2020-07-30 06:13:23 -03:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Copyright 2019 Abakkk
|
|
|
|
|
*
|
|
|
|
|
* This file is part of DrawOnYourScreen, a drawing extension for GNOME Shell.
|
|
|
|
|
* https://framagit.org/abakkk/DrawOnYourScreen
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-08-04 21:00:20 -03:00
|
|
|
const ByteArray = imports.byteArray;
|
2020-07-30 06:13:23 -03:00
|
|
|
const Gdk = imports.gi.Gdk;
|
|
|
|
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
|
|
|
|
const Gio = imports.gi.Gio;
|
|
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
|
const Lang = imports.lang;
|
2020-09-09 17:25:56 -03:00
|
|
|
const St = imports.gi.St;
|
2020-07-30 06:13:23 -03:00
|
|
|
|
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
|
|
|
const Me = ExtensionUtils.getCurrentExtension();
|
2020-09-10 23:06:21 -03:00
|
|
|
const EXAMPLE_IMAGE_DIRECTORY = Me.dir.get_child('data').get_child('images');
|
|
|
|
|
const DEFAULT_USER_IMAGE_LOCATION = GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']);
|
2020-09-09 17:25:56 -03:00
|
|
|
const Clipboard = St.Clipboard.get_default();
|
|
|
|
|
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
|
2020-09-10 10:19:17 -03:00
|
|
|
const ICON_DIR = Me.dir.get_child('data').get_child('icons');
|
|
|
|
|
const ICON_NAMES = ['color', 'dashed-line', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke'];
|
|
|
|
|
|
|
|
|
|
var Icons = {
|
|
|
|
|
get ENTER() { return this._enter || void (this._enter = new Gio.ThemedIcon({ name: 'applications-graphics-symbolic' })) || this._enter; },
|
|
|
|
|
get GRAB() { return this._grab || void (this._grab = new Gio.ThemedIcon({ name: 'input-touchpad-symbolic' })) || this._grab; },
|
|
|
|
|
get LEAVE() { return this._leave || void (this._leave = new Gio.ThemedIcon({ name: 'application-exit-symbolic' })) || this._leave; },
|
|
|
|
|
get OPEN() { return this._open || void (this._open = new Gio.ThemedIcon({ name: 'document-open-symbolic' })) || this._open; },
|
|
|
|
|
get SAVE() { return this._save || void (this._save = new Gio.ThemedIcon({ name: 'document-save-symbolic' })) || this._save; },
|
|
|
|
|
get UNGRAB() { return this._ungrab || void (this._ungrab = new Gio.ThemedIcon({ name: 'touchpad-disabled-symbolic' })) || this._ungrab; }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ICON_NAMES.forEach(name => {
|
|
|
|
|
Object.defineProperty(Icons, name.toUpperCase().replace(/-/gi, '_'), {
|
|
|
|
|
get: function() {
|
|
|
|
|
if (!this[`_${name}`]) {
|
|
|
|
|
let file = Gio.File.new_for_path(ICON_DIR.get_child(`${name}-symbolic.svg`).get_path());
|
|
|
|
|
this[`_${name}`] = file.query_exists(null) ? new Gio.FileIcon({ file }) : new Gio.ThemedIcon({ name: 'error-symbolic' });
|
|
|
|
|
}
|
|
|
|
|
return this[`_${name}`];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2020-07-30 06:13:23 -03:00
|
|
|
|
2020-08-04 21:00:20 -03:00
|
|
|
// wrapper around an image file
|
2020-07-30 06:13:23 -03:00
|
|
|
var Image = new Lang.Class({
|
|
|
|
|
Name: 'DrawOnYourScreenImage',
|
|
|
|
|
|
|
|
|
|
_init: function(params) {
|
|
|
|
|
for (let key in params)
|
|
|
|
|
this[key] = params[key];
|
2020-09-10 23:06:21 -03:00
|
|
|
|
|
|
|
|
if (this.info) {
|
|
|
|
|
this.displayName = this.info.get_display_name();
|
|
|
|
|
this.contentType = this.info.get_content_type();
|
|
|
|
|
}
|
2020-07-30 06:13:23 -03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toString: function() {
|
|
|
|
|
return this.displayName;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toJson: function() {
|
|
|
|
|
return {
|
|
|
|
|
displayName: this.displayName,
|
|
|
|
|
contentType: this.contentType,
|
2020-08-04 21:13:56 -03:00
|
|
|
base64: this.base64,
|
|
|
|
|
hash: this.hash
|
2020-07-30 06:13:23 -03:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
get thumbnailFile() {
|
|
|
|
|
if (!this._thumbnailFile) {
|
|
|
|
|
if (this.info.has_attribute('thumbnail::path') && this.info.get_attribute_boolean('thumbnail::is-valid')) {
|
|
|
|
|
let thumbnailPath = this.info.get_attribute_as_string('thumbnail::path');
|
|
|
|
|
this._thumbnailFile = Gio.File.new_for_path(thumbnailPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this._thumbnailFile || null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// only called from menu or area so this.file exists
|
2020-07-30 06:13:23 -03:00
|
|
|
get gicon() {
|
|
|
|
|
if (!this._gicon)
|
2020-09-10 23:06:21 -03:00
|
|
|
this._gicon = new Gio.FileIcon({ file: this.thumbnailFile || this.file });
|
2020-07-30 06:13:23 -03:00
|
|
|
return this._gicon;
|
|
|
|
|
},
|
|
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
// use only thumbnails in menu (memory)
|
|
|
|
|
get thumbnailGicon() {
|
|
|
|
|
if (this.contentType != 'image/svg+xml' && !this.thumbnailFile)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return this.gicon;
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-30 06:13:23 -03:00
|
|
|
get bytes() {
|
|
|
|
|
if (!this._bytes) {
|
|
|
|
|
if (this.file)
|
2020-08-07 19:27:41 -03:00
|
|
|
try {
|
|
|
|
|
// load_bytes available in GLib 2.56+
|
|
|
|
|
this._bytes = this.file.load_bytes(null)[0];
|
|
|
|
|
} catch(e) {
|
2020-09-10 23:06:21 -03:00
|
|
|
let [, contents] = this.file.load_contents(null);
|
2020-08-07 19:27:41 -03:00
|
|
|
if (contents instanceof Uint8Array)
|
|
|
|
|
this._bytes = ByteArray.toGBytes(contents);
|
|
|
|
|
else
|
|
|
|
|
this._bytes = contents.toGBytes();
|
|
|
|
|
}
|
2020-07-30 06:13:23 -03:00
|
|
|
else
|
2020-08-04 21:13:56 -03:00
|
|
|
this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64));
|
2020-07-30 06:13:23 -03:00
|
|
|
}
|
|
|
|
|
return this._bytes;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get base64() {
|
|
|
|
|
if (!this._base64)
|
|
|
|
|
this._base64 = GLib.base64_encode(this.bytes.get_data());
|
|
|
|
|
return this._base64;
|
|
|
|
|
},
|
|
|
|
|
|
2020-08-04 21:13:56 -03:00
|
|
|
set base64(base64) {
|
|
|
|
|
this._base64 = base64;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// hash is not used
|
2020-07-30 06:13:23 -03:00
|
|
|
get hash() {
|
|
|
|
|
if (!this._hash)
|
|
|
|
|
this._hash = this.bytes.hash();
|
|
|
|
|
return this._hash;
|
|
|
|
|
},
|
|
|
|
|
|
2020-08-04 21:13:56 -03:00
|
|
|
set hash(hash) {
|
|
|
|
|
this._hash = hash;
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-30 06:13:23 -03:00
|
|
|
get pixbuf() {
|
|
|
|
|
if (!this._pixbuf) {
|
|
|
|
|
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
|
|
|
|
|
this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
|
|
|
|
|
stream.close(null);
|
|
|
|
|
}
|
|
|
|
|
return this._pixbuf;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getPixbufAtScale: function(width, height) {
|
|
|
|
|
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
|
|
|
|
|
let pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, width, height, true, null);
|
|
|
|
|
stream.close(null);
|
|
|
|
|
return pixbuf;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setCairoSource: function(cr, x, y, width, height, preserveAspectRatio) {
|
|
|
|
|
let pixbuf = preserveAspectRatio ? this.getPixbufAtScale(width, height)
|
|
|
|
|
: this.pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR);
|
|
|
|
|
Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
// Get images with getPrevious, getNext, or by iterating over it.
|
2020-09-09 17:25:56 -03:00
|
|
|
var Images = {
|
2020-09-10 23:06:21 -03:00
|
|
|
_images: [],
|
|
|
|
|
_clipboardImages: [],
|
|
|
|
|
_upToDate: false,
|
|
|
|
|
|
|
|
|
|
_clipboardImagesContains: function(file) {
|
|
|
|
|
return this._clipboardImages.some(image => image.file.equal(file));
|
|
|
|
|
},
|
2020-07-30 06:13:23 -03:00
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
_getImages: function() {
|
|
|
|
|
if (this._upToDate)
|
|
|
|
|
return this._images;
|
|
|
|
|
|
2020-09-09 17:25:56 -03:00
|
|
|
let images = [];
|
2020-09-10 23:06:21 -03:00
|
|
|
let userLocation = Me.drawingSettings.get_string('image-location') || DEFAULT_USER_IMAGE_LOCATION;
|
|
|
|
|
let userDirectory = Gio.File.new_for_commandline_arg(userLocation);
|
2020-09-09 17:25:56 -03:00
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
[EXAMPLE_IMAGE_DIRECTORY, userDirectory].forEach(directory => {
|
2020-09-09 17:25:56 -03:00
|
|
|
let enumerator;
|
|
|
|
|
try {
|
2020-09-10 23:06:21 -03:00
|
|
|
enumerator = directory.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null);
|
2020-09-09 17:25:56 -03:00
|
|
|
} catch(e) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
let info = enumerator.next_file(null);
|
|
|
|
|
while (info) {
|
|
|
|
|
let file = enumerator.get_child(info);
|
|
|
|
|
|
|
|
|
|
if (info.get_content_type().indexOf('image') == 0 && !this._clipboardImagesContains(file)) {
|
|
|
|
|
let index = this._images.findIndex(image => image.file.equal(file));
|
|
|
|
|
if (index != -1)
|
|
|
|
|
images.push(this._images[index]);
|
|
|
|
|
else
|
|
|
|
|
images.push(new Image({ file, info }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info = enumerator.next_file(null);
|
2020-09-09 17:25:56 -03:00
|
|
|
}
|
|
|
|
|
enumerator.close(null);
|
|
|
|
|
});
|
|
|
|
|
|
2020-09-10 23:06:21 -03:00
|
|
|
this._images = images.concat(this._clipboardImages)
|
|
|
|
|
.sort((a, b) => a.toString().localeCompare(b.toString()));
|
|
|
|
|
this._upToDate = true;
|
|
|
|
|
return this._images;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
[Symbol.iterator]: function() {
|
|
|
|
|
return this._getImages()[Symbol.iterator]();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getNext: function(currentImage) {
|
|
|
|
|
let images = this._getImages();
|
|
|
|
|
let index = currentImage ? images.findIndex(image => image.file.equal(currentImage.file)) : 0;
|
|
|
|
|
return images[index == images.length - 1 ? 0 : index + 1] || null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getPrevious: function(currentImage) {
|
|
|
|
|
let images = this._getImages();
|
|
|
|
|
let index = currentImage ? images.findIndex(image => image.file.equal(currentImage.file)) : 0;
|
|
|
|
|
return images[index <= 0 ? images.length - 1 : index - 1] || null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
|
this._upToDate = false;
|
2020-09-09 17:25:56 -03:00
|
|
|
},
|
2020-07-30 06:13:23 -03:00
|
|
|
|
2020-09-09 17:25:56 -03:00
|
|
|
addImagesFromClipboard: function(callback) {
|
|
|
|
|
Clipboard.get_text(CLIPBOARD_TYPE, (clipBoard, text) => {
|
|
|
|
|
if (!text)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
let lines = text.split('\n');
|
|
|
|
|
if (lines[0] == 'x-special/nautilus-clipboard')
|
|
|
|
|
lines = lines.slice(2);
|
|
|
|
|
|
|
|
|
|
let images = lines.filter(line => !!line)
|
|
|
|
|
.map(line => Gio.File.new_for_commandline_arg(line))
|
|
|
|
|
.filter(file => file.query_exists(null))
|
2020-09-10 23:06:21 -03:00
|
|
|
.map(file => [file, file.query_info('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null)])
|
2020-09-09 17:25:56 -03:00
|
|
|
.filter(pair => pair[1].get_content_type().indexOf('image') == 0)
|
2020-09-10 23:06:21 -03:00
|
|
|
.map(pair => new Image({ file: pair[0], info: pair[1] }));
|
2020-09-09 17:25:56 -03:00
|
|
|
|
|
|
|
|
// Prevent duplicated
|
2020-09-10 23:06:21 -03:00
|
|
|
images.filter(image => !this._clipboardImagesContains(image.file))
|
|
|
|
|
.forEach(image => this._clipboardImages.push(image));
|
2020-09-09 17:25:56 -03:00
|
|
|
|
|
|
|
|
if (images.length) {
|
2020-09-10 23:06:21 -03:00
|
|
|
this.reset();
|
|
|
|
|
let allImages = this._getImages();
|
2020-09-09 17:25:56 -03:00
|
|
|
let lastFile = images[images.length - 1].file;
|
|
|
|
|
let index = allImages.findIndex(image => image.file.equal(lastFile));
|
2020-09-10 23:06:21 -03:00
|
|
|
callback(allImages[index]);
|
|
|
|
|
}
|
2020-09-09 17:25:56 -03:00
|
|
|
});
|
|
|
|
|
}
|
2020-07-30 06:13:23 -03:00
|
|
|
};
|
2020-08-04 21:00:20 -03:00
|
|
|
|
|
|
|
|
// wrapper around a json file
|
|
|
|
|
var Json = new Lang.Class({
|
|
|
|
|
Name: 'DrawOnYourScreenJson',
|
|
|
|
|
|
|
|
|
|
_init: function(params) {
|
|
|
|
|
for (let key in params)
|
|
|
|
|
this[key] = params[key];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toString: function() {
|
|
|
|
|
return this.displayName || this.name;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
delete: function() {
|
|
|
|
|
this.file.delete(null);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get file() {
|
|
|
|
|
if (!this._file && this.name)
|
|
|
|
|
this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], `${this.name}.json`]));
|
|
|
|
|
|
|
|
|
|
return this._file || null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
set file(file) {
|
|
|
|
|
this._file = file;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get contents() {
|
|
|
|
|
let success_, contents;
|
|
|
|
|
try {
|
|
|
|
|
[success_, contents] = this.file.load_contents(null);
|
|
|
|
|
if (contents instanceof Uint8Array)
|
|
|
|
|
contents = ByteArray.toString(contents);
|
|
|
|
|
} catch(e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return contents;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
set contents(contents) {
|
|
|
|
|
try {
|
|
|
|
|
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
|
|
|
|
|
} catch(e) {
|
|
|
|
|
this.file.get_parent().make_directory_with_parents(null);
|
|
|
|
|
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var getJsons = function() {
|
|
|
|
|
let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]));
|
|
|
|
|
|
|
|
|
|
let enumerator;
|
|
|
|
|
try {
|
|
|
|
|
enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
|
|
|
|
|
} catch(e) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let jsons = [];
|
|
|
|
|
let fileInfo = enumerator.next_file(null);
|
|
|
|
|
while (fileInfo) {
|
|
|
|
|
if (fileInfo.get_content_type().indexOf('json') != -1 && fileInfo.get_name() != `${Me.metadata['persistent-file-name']}.json`) {
|
|
|
|
|
let file = enumerator.get_child(fileInfo);
|
|
|
|
|
jsons.push(new Json({
|
|
|
|
|
file,
|
|
|
|
|
name: fileInfo.get_name().slice(0, -5),
|
|
|
|
|
displayName: fileInfo.get_display_name().slice(0, -5),
|
|
|
|
|
// fileInfo.get_modification_date_time: Gio 2.62+
|
|
|
|
|
modificationUnixTime: fileInfo.get_attribute_uint64('time::modified')
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
fileInfo = enumerator.next_file(null);
|
|
|
|
|
}
|
|
|
|
|
enumerator.close(null);
|
|
|
|
|
|
|
|
|
|
jsons.sort((a, b) => {
|
|
|
|
|
return b.modificationUnixTime - a.modificationUnixTime;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return jsons;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getDateString = function() {
|
|
|
|
|
let date = GLib.DateTime.new_now_local();
|
|
|
|
|
return `${date.format("%F")} ${date.format("%X")}`;
|
|
|
|
|
};
|