Initialize files

This commit is contained in:
2019-07-09 04:57:09 +03:00
parent 8fe9a5ea9f
commit f5ed792ec6
1805 changed files with 62157 additions and 0 deletions

View File

@ -0,0 +1,12 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=ALPM package URL Handler
Comment=Install ALPM packages with pamac-installer from URL scheme x-alpm-package://packagename
Icon=system-software-install
NoDisplay=true
Categories=System;
Exec=pamac-url-handler %u
Terminal=false
MimeType=x-scheme-handler/x-alpm-package;
X-Desktop-File-Install-Version=0.1

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">
<wallpapers>
<wallpaper deleted="false">
<name>Flat 1</name>
<filename>/usr/share/backgrounds/flat1.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper><wallpaper deleted="false">
<name>Flat 2</name>
<filename>/usr/share/backgrounds/flat2.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 3</name>
<filename>/usr/share/backgrounds/flat3.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 4</name>
<filename>/usr/share/backgrounds/flat4.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 5</name>
<filename>/usr/share/backgrounds/flat5.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 6</name>
<filename>/usr/share/backgrounds/flat6.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 7</name>
<filename>/usr/share/backgrounds/flat7.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 8</name>
<filename>/usr/share/backgrounds/flat8.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 9</name>
<filename>/usr/share/backgrounds/flat9.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 10</name>
<filename>/usr/share/backgrounds/flat10.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 11</name>
<filename>/usr/share/backgrounds/flat11.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 12</name>
<filename>/usr/share/backgrounds/flat12.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 13</name>
<filename>/usr/share/backgrounds/flat13.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 14</name>
<filename>/usr/share/backgrounds/flat14.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 15</name>
<filename>/usr/share/backgrounds/flat15.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 16</name>
<filename>/usr/share/backgrounds/flat16.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 17</name>
<filename>/usr/share/backgrounds/flat17.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
<wallpaper deleted="false">
<name>Flat 18</name>
<filename>/usr/share/backgrounds/flat18.jpg</filename>
<options>zoom</options>
<pcolor>#ffffff</pcolor>
<scolor>#000000</scolor>
</wallpaper>
</wallpapers>

View File

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<application-state>
<context id="">
<application id="timeshift-gtk.desktop" score="0" last-seen="1557951964"/>
<application id="gnome-shell-extension-prefs.desktop" score="0" last-seen="1558006816"/>
<application id="org.gnome.Nautilus.desktop" score="14689" last-seen="1557954568"/>
<application id="smplayer.desktop" score="0" last-seen="1557953161"/>
<application id="manjaro-hello.desktop" score="4835" last-seen="1558006864"/>
<application id="firefox.desktop" score="9402" last-seen="1557954823"/>
<application id="org.gnome.Terminal.desktop" score="5270" last-seen="1553119601"/>
<application id="qt5ct.desktop" score="1" last-seen="1557953895"/>
<application id="kvantummanager.desktop" score="2" last-seen="1557953860"/>
<application id="gnome-control-center.desktop" score="9802" last-seen="1553906329"/>
<application id="pamac-manager.desktop" score="194" last-seen="1557953843"/>
<application id="org.gnome.tweaks.desktop" score="3" last-seen="1558006894"/>
</context>
</application-state>

View File

@ -0,0 +1,542 @@
// appfolderDialog.js
// GPLv3
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const St = imports.gi.St;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const PopupMenu = imports.ui.popupMenu;
const ShellEntry = imports.ui.shellEntry;
const Signals = imports.signals;
const Gtk = imports.gi.Gtk;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const Extension = Me.imports.extension;
const Gettext = imports.gettext.domain('appfolders-manager');
const _ = Gettext.gettext;
let FOLDER_SCHEMA;
let FOLDER_LIST;
//--------------------------------------------------------------
// This is a modal dialog for creating a new folder, or renaming or modifying
// categories of existing folders.
var AppfolderDialog = class AppfolderDialog {
// build a new dialog. If folder is null, the dialog will be for creating a new
// folder, else app is null, and the dialog will be for editing an existing folder
constructor (folder, app, id) {
this._folder = folder;
this._app = app;
this._id = id;
this.super_dialog = new ModalDialog.ModalDialog({ destroyOnClose: true });
FOLDER_SCHEMA = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
let nameSection = this._buildNameSection();
let categoriesSection = this._buildCategoriesSection();
this.super_dialog.contentLayout.style = 'spacing: 20px';
this.super_dialog.contentLayout.add(nameSection, {
x_fill: false,
x_align: St.Align.START,
y_align: St.Align.START
});
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('categories') ) {
this.super_dialog.contentLayout.add(categoriesSection, {
x_fill: false,
x_align: St.Align.START,
y_align: St.Align.START
});
}
if (this._folder == null) {
this.super_dialog.setButtons([
{ action: this.destroy.bind(this),
label: _("Cancel"),
key: Clutter.Escape },
{ action: this._apply.bind(this),
label: _("Create"),
key: Clutter.Return }
]);
} else {
this.super_dialog.setButtons([
{ action: this.destroy.bind(this),
label: _("Cancel"),
key: Clutter.Escape },
{ action: this._deleteFolder.bind(this),
label: _("Delete"),
key: Clutter.Delete },
{ action: this._apply.bind(this),
label: _("Apply"),
key: Clutter.Return }
]);
}
this._nameEntryText.connect('key-press-event', (o, e) => {
let symbol = e.get_key_symbol();
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
this.super_dialog.popModal();
this._apply();
}
});
}
// build the section of the UI handling the folder's name and returns it.
_buildNameSection () {
let nameSection = new St.BoxLayout({
style: 'spacing: 5px;',
vertical: true,
x_expand: true,
natural_width_set: true,
natural_width: 350,
});
let nameLabel = new St.Label({
text: _("Folder's name:"),
style: 'font-weight: bold;',
});
nameSection.add(nameLabel, { y_align: St.Align.START });
this._nameEntry = new St.Entry({
x_expand: true,
});
this._nameEntryText = null; ///???
this._nameEntryText = this._nameEntry.clutter_text;
nameSection.add(this._nameEntry, { y_align: St.Align.START });
ShellEntry.addContextMenu(this._nameEntry);
this.super_dialog.setInitialKeyFocus(this._nameEntryText);
if (this._folder != null) {
this._nameEntryText.set_text(this._folder.get_string('name'));
}
return nameSection;
}
// build the section of the UI handling the folder's categories and returns it.
_buildCategoriesSection () {
let categoriesSection = new St.BoxLayout({
style: 'spacing: 5px;',
vertical: true,
x_expand: true,
natural_width_set: true,
natural_width: 350,
});
let categoriesLabel = new St.Label({
text: _("Categories:"),
style: 'font-weight: bold;',
});
categoriesSection.add(categoriesLabel, {
x_fill: false,
x_align: St.Align.START,
y_align: St.Align.START,
});
let categoriesBox = new St.BoxLayout({
style: 'spacing: 5px;',
vertical: false,
x_expand: true,
});
// at the left, how to add categories
let addCategoryBox = new St.BoxLayout({
style: 'spacing: 5px;',
vertical: true,
x_expand: true,
});
this._categoryEntry = new St.Entry({
can_focus: true,
x_expand: true,
hint_text: _("Other category?"),
secondary_icon: new St.Icon({
icon_name: 'list-add-symbolic',
icon_size: 16,
style_class: 'system-status-icon',
y_align: Clutter.ActorAlign.CENTER,
}),
});
ShellEntry.addContextMenu(this._categoryEntry, null);
this._categoryEntry.connect('secondary-icon-clicked', this._addCategory.bind(this));
this._categoryEntryText = null; ///???
this._categoryEntryText = this._categoryEntry.clutter_text;
this._catSelectButton = new SelectCategoryButton(this);
addCategoryBox.add(this._catSelectButton.actor, { y_align: St.Align.CENTER });
addCategoryBox.add(this._categoryEntry, { y_align: St.Align.START });
categoriesBox.add(addCategoryBox, {
x_fill: true,
x_align: St.Align.START,
y_align: St.Align.START,
});
// at the right, a list of categories
this.listContainer = new St.BoxLayout({
vertical: true,
x_expand: true,
});
this.noCatLabel = new St.Label({ text: _("No category") });
this.listContainer.add_actor(this.noCatLabel);
categoriesBox.add(this.listContainer, {
x_fill: true,
x_align: St.Align.END,
y_align: St.Align.START,
});
categoriesSection.add(categoriesBox, {
x_fill: true,
x_align: St.Align.START,
y_align: St.Align.START,
});
// Load categories is necessary even if no this._folder,
// because it initializes the value of this._categories
this._loadCategories();
return categoriesSection;
}
open () {
this.super_dialog.open();
}
// returns if a folder id already exists
_alreadyExists (folderId) {
for(var i = 0; i < FOLDER_LIST.length; i++) {
if (FOLDER_LIST[i] == folderId) {
// this._showError( _("This appfolder already exists.") );
return true;
}
}
return false;
}
destroy () {
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
log('[AppfolderDialog v2] destroying dialog');
}
this._catSelectButton.destroy(); // TODO ?
this.super_dialog.destroy(); //XXX crée des erreurs reloues ???
}
// Generates a valid folder id, which as no space, no dot, no slash, and which
// doesn't already exist.
_folderId (newName) {
let tmp0 = newName.split(" ");
let folderId = "";
for(var i = 0; i < tmp0.length; i++) {
folderId += tmp0[i];
}
tmp0 = folderId.split(".");
folderId = "";
for(var i = 0; i < tmp0.length; i++) {
folderId += tmp0[i];
}
tmp0 = folderId.split("/");
folderId = "";
for(var i = 0; i < tmp0.length; i++) {
folderId += tmp0[i];
}
if(this._alreadyExists(folderId)) {
folderId = this._folderId(folderId+'_');
}
return folderId;
}
// creates a folder from the data filled by the user (with no properties)
_create () {
let folderId = this._folderId(this._nameEntryText.get_text());
FOLDER_LIST.push(folderId);
FOLDER_SCHEMA.set_strv('folder-children', FOLDER_LIST);
this._folder = new Gio.Settings({
schema_id: 'org.gnome.desktop.app-folders.folder',
path: '/org/gnome/desktop/app-folders/folders/' + folderId + '/'
});
// this._folder.set_string('name', this._nameEntryText.get_text()); //superflu
// est-il nécessaire d'initialiser la clé apps à [] ??
this._addToFolder();
}
// sets the name to the folder
_applyName () {
let newName = this._nameEntryText.get_text();
this._folder.set_string('name', newName); // génère un bug ?
return Clutter.EVENT_STOP;
}
// loads categories, as set in gsettings, to the UI
_loadCategories () {
if (this._folder == null) {
this._categories = [];
} else {
this._categories = this._folder.get_strv('categories');
if ((this._categories == null) || (this._categories.length == 0)) {
this._categories = [];
} else {
this.noCatLabel.visible = false;
}
}
this._categoriesButtons = [];
for (var i = 0; i < this._categories.length; i++) {
this._addCategoryBox(i);
}
}
_addCategoryBox (i) {
let aCategory = new AppCategoryBox(this, i);
this.listContainer.add_actor(aCategory.super_box);
}
// adds a category to the UI (will be added to gsettings when pressing "apply" only)
_addCategory (entry, new_cat_name) {
if (new_cat_name == null) {
new_cat_name = this._categoryEntryText.get_text();
}
if (this._categories.indexOf(new_cat_name) != -1) {
return;
}
if (new_cat_name == '') {
return;
}
this._categories.push(new_cat_name);
this._categoryEntryText.set_text('');
this.noCatLabel.visible = false;
this._addCategoryBox(this._categories.length-1);
}
// adds all categories to gsettings
_applyCategories () {
this._folder.set_strv('categories', this._categories);
return Clutter.EVENT_STOP;
}
// Apply everything by calling methods above, and reload the view
_apply () {
if (this._app != null) {
this._create();
// this._addToFolder();
}
this._applyCategories();
this._applyName();
this.destroy();
//-----------------------
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
log('[AppfolderDialog v2] reload the view');
}
}
// initializes the folder with its first app. This is not optional since empty
// folders are not displayed. TODO use the equivalent method from extension.js
_addToFolder () {
let content = this._folder.get_strv('apps');
content.push(this._app);
this._folder.set_strv('apps', content);
}
// Delete the folder, using the extension.js method
_deleteFolder () {
if (this._folder != null) {
Extension.deleteFolder(this._id);
}
this.destroy();
}
};
//------------------------------------------------
// Very complex way to have a menubutton for displaying a menu with standard
// categories. Button part.
class SelectCategoryButton {
constructor (dialog) {
this._dialog = dialog;
let catSelectBox = new St.BoxLayout({
vertical: false,
x_expand: true,
});
let catSelectLabel = new St.Label({
text: _("Select a category…"),
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
});
let catSelectIcon = new St.Icon({
icon_name: 'pan-down-symbolic',
icon_size: 16,
style_class: 'system-status-icon',
x_expand: false,
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.CENTER,
});
catSelectBox.add(catSelectLabel, { y_align: St.Align.MIDDLE });
catSelectBox.add(catSelectIcon, { y_align: St.Align.END });
this.actor = new St.Button ({
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
child: catSelectBox,
style_class: 'button',
style: 'padding: 5px 5px;',
x_expand: true,
y_expand: false,
x_fill: true,
y_fill: true,
});
this.actor.connect('button-press-event', this._onButtonPress.bind(this));
this._menu = null;
this._menuManager = new PopupMenu.PopupMenuManager(this);
}
popupMenu () {
this.actor.fake_release();
if (!this._menu) {
this._menu = new SelectCategoryMenu(this, this._dialog);
this._menu.super_menu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp) {
this.actor.sync_hover();
this.emit('menu-state-changed', false);
}
});
this._menuManager.addMenu(this._menu.super_menu);
}
this.emit('menu-state-changed', true);
this.actor.set_hover(true);
this._menu.popup();
this._menuManager.ignoreRelease();
return false;
}
_onButtonPress (actor, event) {
this.popupMenu();
return Clutter.EVENT_STOP;
}
destroy () {
if (this._menu) {
this._menu.destroy();
}
this.actor.destroy();
}
};
Signals.addSignalMethods(SelectCategoryButton.prototype);
//------------------------------------------------
// Very complex way to have a menubutton for displaying a menu with standard
// categories. Menu part.
class SelectCategoryMenu {
constructor (source, dialog) {
this.super_menu = new PopupMenu.PopupMenu(source.actor, 0.5, St.Side.RIGHT);
this._source = source;
this._dialog = dialog;
this.super_menu.actor.add_style_class_name('app-well-menu');
this._source.actor.connect('destroy', this.super_menu.destroy.bind(this));
// We want to keep the item hovered while the menu is up //XXX used ??
this.super_menu.blockSourceEvents = true;
Main.uiGroup.add_actor(this.super_menu.actor);
// This is a really terrible hack to overwrite _redisplay without
// actually inheriting from PopupMenu.PopupMenu
this.super_menu._redisplay = this._redisplay;
this.super_menu._dialog = this._dialog;
}
_redisplay () {
this.removeAll();
let mainCategories = ['AudioVideo', 'Audio', 'Video', 'Development',
'Education', 'Game', 'Graphics', 'Network', 'Office', 'Science',
'Settings', 'System', 'Utility'];
for (var i=0; i<mainCategories.length; i++) {
let labelItem = mainCategories[i] ;
let item = new PopupMenu.PopupMenuItem( labelItem );
item.connect('activate', () => {
this._dialog._addCategory(null, labelItem);
});
this.addMenuItem(item);
}
}
popup (activatingButton) {
this.super_menu._redisplay();
this.super_menu.open();
}
destroy () {
this.super_menu.close(); //FIXME error in the logs but i don't care
this.super_menu.destroy();
}
};
Signals.addSignalMethods(SelectCategoryMenu.prototype);
//----------------------------------------
// This custom widget is a deletable row, displaying a category name.
class AppCategoryBox {
constructor (dialog, i) {
this.super_box = new St.BoxLayout({
vertical: false,
style_class: 'appCategoryBox',
});
this._dialog = dialog;
this.catName = this._dialog._categories[i];
this.super_box.add_actor(new St.Label({
text: this.catName,
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
}));
this.super_box.add_actor( new St.BoxLayout({ x_expand: true }) );
this.deleteButton = new St.Button({
x_expand: false,
y_expand: true,
style_class: 'appCategoryDeleteBtn',
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
child: new St.Icon({
icon_name: 'edit-delete-symbolic',
icon_size: 16,
style_class: 'system-status-icon',
x_expand: false,
y_expand: true,
style: 'margin: 3px;',
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
}),
});
this.super_box.add_actor(this.deleteButton);
this.deleteButton.connect('clicked', this.removeFromList.bind(this));
}
removeFromList () {
this._dialog._categories.splice(this._dialog._categories.indexOf(this.catName), 1);
if (this._dialog._categories.length == 0) {
this._dialog.noCatLabel.visible = true;
}
this.destroy();
}
destroy () {
this.deleteButton.destroy();
this.super_box.destroy();
}
};

View File

@ -0,0 +1,92 @@
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GNOME nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const Gettext = imports.gettext;
const Gio = imports.gi.Gio;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
/**
* initTranslations:
* @domain: (optional): the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
*/
function initTranslations(domain) {
let extension = ExtensionUtils.getCurrentExtension();
domain = domain || extension.metadata['gettext-domain'];
// check if this extension was built with "make zip-file", and thus
// has the locale files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell
let localeDir = extension.dir.get_child('locale');
if (localeDir.query_exists(null))
Gettext.bindtextdomain(domain, localeDir.get_path());
else
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}
/**
* getSettings:
* @schema: (optional): the GSettings schema id
*
* Builds and return a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is not provided, it is taken from
* metadata['settings-schema'].
*/
function getSettings(schema) {
let extension = ExtensionUtils.getCurrentExtension();
schema = schema || extension.metadata['settings-schema'];
const GioSSS = Gio.SettingsSchemaSource;
// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null)) {
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
} else {
schemaSource = GioSSS.get_default();
}
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension '
+ extension.metadata.uuid + '. Please check your installation.');
return new Gio.Settings({ settings_schema: schemaObj });
}

View File

@ -0,0 +1,550 @@
// dragAndDrop.js
// GPLv3
const DND = imports.ui.dnd;
const AppDisplay = imports.ui.appDisplay;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const Extension = Me.imports.extension;
const CHANGE_PAGE_TIMEOUT = 400;
const Gettext = imports.gettext.domain('appfolders-manager');
const _ = Gettext.gettext;
//-------------------------------------------------
var OVERLAY_MANAGER;
/* This method is called by extension.js' enable function. It does code injections
* to AppDisplay.AppIcon, connecting it to DND-related signals.
*/
function initDND () {
OVERLAY_MANAGER = new OverlayManager();
}
//--------------------------------------------------------------
/* Amazing! A singleton! It allows easy (and safer?) access to general methods,
* managing other objects: it creates/updates/deletes all overlays (for folders,
* pages, creation, removing).
*/
class OverlayManager {
constructor () {
this.addActions = [];
this.removeAction = new FolderActionArea('remove');
this.createAction = new FolderActionArea('create');
this.upAction = new NavigationArea('up');
this.downAction = new NavigationArea('down');
this.next_drag_should_recompute = true;
this.current_width = 0;
}
on_drag_begin () {
this.ensurePopdowned();
this.ensureFolderOverlayActors();
this.updateFoldersVisibility();
this.updateState(true);
}
on_drag_end () {
// force to compute new positions if a drop occurs
this.next_drag_should_recompute = true;
this.updateState(false);
}
on_drag_cancelled () {
this.updateState(false);
}
updateArrowVisibility () {
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
if (grid.currentPage == 0) {
this.upAction.setActive(false);
} else {
this.upAction.setActive(true);
}
if (grid.currentPage == grid._nPages -1) {
this.downAction.setActive(false);
} else {
this.downAction.setActive(true);
}
this.upAction.show();
this.downAction.show();
}
updateState (isDragging) {
if (isDragging) {
this.removeAction.show();
if (this.openedFolder == null) {
this.removeAction.setActive(false);
} else {
this.removeAction.setActive(true);
}
this.createAction.show();
this.updateArrowVisibility();
} else {
this.hideAll();
}
}
hideAll () {
this.removeAction.hide();
this.createAction.hide();
this.upAction.hide();
this.downAction.hide();
this.hideAllFolders();
}
hideAllFolders () {
for (var i = 0; i < this.addActions.length; i++) {
this.addActions[i].hide();
}
}
updateActorsPositions () {
let monitor = Main.layoutManager.primaryMonitor;
this.topOfTheGrid = Main.overview.viewSelector.actor.get_parent().get_parent().get_allocation_box().y1;
let temp = Main.overview.viewSelector.appDisplay._views[1].view.actor.get_parent();
let bottomOfTheGrid = this.topOfTheGrid + temp.get_allocation_box().y2;
let _availHeight = bottomOfTheGrid - this.topOfTheGrid;
let _availWidth = Main.overview.viewSelector.appDisplay._views[1].view._grid.actor.width;
let sideMargin = (monitor.width - _availWidth) / 2;
let xMiddle = ( monitor.x + monitor.width ) / 2;
let yMiddle = ( monitor.y + monitor.height ) / 2;
// Positions of areas
this.removeAction.setPosition( xMiddle , bottomOfTheGrid );
this.createAction.setPosition( xMiddle, Main.overview._panelGhost.height );
this.upAction.setPosition( 0, Main.overview._panelGhost.height );
this.downAction.setPosition( 0, bottomOfTheGrid );
// Sizes of areas
this.removeAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
this.createAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
this.upAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
this.downAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
this.updateArrowVisibility();
}
ensureFolderOverlayActors () {
// A folder was opened, and just closed.
if (this.openedFolder != null) {
this.updateActorsPositions();
this.computeFolderOverlayActors();
this.next_drag_should_recompute = true;
return;
}
// The grid "moved" or the whole shit needs forced updating
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
let new_width = allAppsGrid.actor.allocation.get_width();
if (new_width != this.current_width || this.next_drag_should_recompute) {
this.next_drag_should_recompute = false;
this.updateActorsPositions();
this.computeFolderOverlayActors();
}
}
computeFolderOverlayActors () {
let monitor = Main.layoutManager.primaryMonitor;
let xMiddle = ( monitor.x + monitor.width ) / 2;
let yMiddle = ( monitor.y + monitor.height ) / 2;
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
let nItems = 0;
let indexes = [];
let folders = [];
let x, y;
Main.overview.viewSelector.appDisplay._views[1].view._allItems.forEach(function(icon) {
if (icon.actor.visible) {
if (icon instanceof AppDisplay.FolderIcon) {
indexes.push(nItems);
folders.push(icon);
}
nItems++;
}
});
this.current_width = allAppsGrid.actor.allocation.get_width();
let x_correction = (monitor.width - this.current_width)/2;
let availHeightPerPage = (allAppsGrid.actor.height)/(allAppsGrid._nPages);
for (var i = 0; i < this.addActions.length; i++) {
this.addActions[i].actor.destroy();
}
for (var i = 0; i < indexes.length; i++) {
let inPageIndex = indexes[i] % allAppsGrid._childrenPerPage;
let page = Math.floor(indexes[i] / allAppsGrid._childrenPerPage);
x = folders[i].actor.get_allocation_box().x1;
y = folders[i].actor.get_allocation_box().y1;
// Invalid coords (example: when dragging out of the folder) should
// not produce a visible overlay, a negative page number is an easy
// way to be sure it stays hidden.
if (x == 0) {
page = -1;
}
x = Math.floor(x + x_correction);
y = y + this.topOfTheGrid;
y = y - (page * availHeightPerPage);
this.addActions[i] = new FolderArea(folders[i].id, x, y, page);
}
}
updateFoldersVisibility () {
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
for (var i = 0; i < this.addActions.length; i++) {
if ((this.addActions[i].page == appView._grid.currentPage) && (!appView._currentPopup)) {
this.addActions[i].show();
} else {
this.addActions[i].hide();
}
}
}
ensurePopdowned () {
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
if (appView._currentPopup) {
this.openedFolder = appView._currentPopup._source.id;
appView._currentPopup.popdown();
} else {
this.openedFolder = null;
}
}
goToPage (nb) {
Main.overview.viewSelector.appDisplay._views[1].view.goToPage( nb );
this.updateArrowVisibility();
this.hideAllFolders();
this.updateFoldersVisibility(); //load folders of the new page
}
destroy () {
for (let i = 0; i > this.addActions.length; i++) {
this.addActions[i].destroy();
}
this.removeAction.destroy();
this.createAction.destroy();
this.upAction.destroy();
this.downAction.destroy();
//log('OverlayManager destroyed');
}
};
//-------------------------------------------------------
// Abstract overlay with very generic methods
class DroppableArea {
constructor (id) {
this.id = id;
this.styleClass = 'folderArea';
this.actor = new St.BoxLayout ({
width: 10,
height: 10,
visible: false,
});
this.actor._delegate = this;
this.lock = true;
this.use_frame = Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug');
}
setPosition (x, y) {
let monitor = Main.layoutManager.primaryMonitor;
this.actor.set_position(monitor.x + x, monitor.y + y);
}
setSize (w, h) {
this.actor.width = w;
this.actor.height = h;
}
hide () {
this.actor.visible = false;
this.lock = true;
}
show () {
this.actor.visible = true;
}
setActive (active) {
this._active = active;
if (this._active) {
this.actor.style_class = this.styleClass;
} else {
this.actor.style_class = 'insensitiveArea';
}
}
destroy () {
this.actor.destroy();
}
}
/* Overlay representing an "action". Actions can be creating a folder, or
* removing an app from a folder. These areas accept drop, and display a label.
*/
class FolderActionArea extends DroppableArea {
constructor (id) {
super(id);
let x, y, label;
switch (this.id) {
case 'create':
label = _("Create a new folder");
this.styleClass = 'shadowedAreaTop';
break;
case 'remove':
label = '';
this.styleClass = 'shadowedAreaBottom';
break;
default:
label = 'invalid id';
break;
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.label = new St.Label({
text: label,
style_class: 'dropAreaLabel',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.actor.add(this.label);
this.setPosition(10, 10);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
getRemoveLabel () {
let label;
if (OVERLAY_MANAGER.openedFolder == null) {
label = '…';
} else {
let folder_schema = Extension.folderSchema (OVERLAY_MANAGER.openedFolder);
label = folder_schema.get_string('name');
}
return (_("Remove from %s")).replace('%s', label);
}
setActive (active) {
super.setActive(active);
if (this.id == 'remove') {
this.label.text = this.getRemoveLabel();
}
}
handleDragOver (source, actor, x, y, time) {
if (source instanceof AppDisplay.AppIcon && this._active) {
return DND.DragMotionResult.MOVE_DROP;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
acceptDrop (source, actor, x, y, time) {
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'create')) {
Extension.createNewFolder(source);
Main.overview.endItemDrag(this);
return true;
}
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'remove')) {
this.removeApp(source);
Main.overview.endItemDrag(this);
return true;
}
Main.overview.endItemDrag(this);
return false;
}
removeApp (source) {
let id = source.app.get_id();
Extension.removeFromFolder(id, OVERLAY_MANAGER.openedFolder);
OVERLAY_MANAGER.updateState(false);
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
}
destroy () {
this.label.destroy();
super.destroy();
}
};
/* Overlay reacting to hover, but isn't droppable. The goal is to go to an other
* page of the grid while dragging an app.
*/
class NavigationArea extends DroppableArea {
constructor (id) {
super(id);
let x, y, i;
switch (this.id) {
case 'up':
i = 'pan-up-symbolic';
this.styleClass = 'shadowedAreaTop';
break;
case 'down':
i = 'pan-down-symbolic';
this.styleClass = 'shadowedAreaBottom';
break;
default:
i = 'dialog-error-symbolic';
break;
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.actor.add(new St.Icon({
icon_name: i,
icon_size: 24,
style_class: 'system-status-icon',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
this.setPosition(x, y);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
handleDragOver (source, actor, x, y, time) {
if (this.id == 'up' && this._active) {
this.pageUp();
return DND.DragMotionResult.CONTINUE;
}
if (this.id == 'down' && this._active) {
this.pageDown();
return DND.DragMotionResult.CONTINUE;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
pageUp () {
if(this.lock && !this.timeoutSet) {
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
this.timeoutSet = true;
}
if(!this.lock) {
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
this.lock = true;
OVERLAY_MANAGER.goToPage(currentPage - 1);
}
}
pageDown () {
if(this.lock && !this.timeoutSet) {
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
this.timeoutSet = true;
}
if(!this.lock) {
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
this.lock = true;
OVERLAY_MANAGER.goToPage(currentPage + 1);
}
}
acceptDrop (source, actor, x, y, time) {
Main.overview.endItemDrag(this);
return false;
}
unlock () {
this.lock = false;
this.timeoutSet = false;
Mainloop.source_remove(this._timeoutId);
}
};
/* This overlay is the area upon a folder. Position and visibility of the actor
* is handled by exterior functions.
* "this.id" is the folder's id, a string, as written in the gsettings key.
* Dropping an app on this folder will add it to the folder
*/
class FolderArea extends DroppableArea {
constructor (id, asked_x, asked_y, page) {
super(id);
this.page = page;
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
this.actor.width = grid._getHItemSize();
this.actor.height = grid._getVItemSize();
if (this.use_frame) {
this.styleClass = 'framedArea';
this.actor.add(new St.Label({
text: this.id,
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
} else {
this.styleClass = 'folderArea';
this.actor.add(new St.Icon({
icon_name: 'list-add-symbolic',
icon_size: 24,
style_class: 'system-status-icon',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.setPosition(asked_x, asked_y);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
handleDragOver (source, actor, x, y, time) {
if (source instanceof AppDisplay.AppIcon) {
return DND.DragMotionResult.MOVE_DROP;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
acceptDrop (source, actor, x, y, time) { //FIXME recharger la vue ou au minimum les miniatures des dossiers
if ((source instanceof AppDisplay.AppIcon) &&
!Extension.isInFolder(source.id, this.id)) {
Extension.addToFolder(source, this.id);
Main.overview.endItemDrag(this);
return true;
}
Main.overview.endItemDrag(this);
return false;
}
};

View File

@ -0,0 +1,445 @@
// extension.js
// GPLv3
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const St = imports.gi.St;
const Main = imports.ui.main;
const AppDisplay = imports.ui.appDisplay;
const PopupMenu = imports.ui.popupMenu;
const Meta = imports.gi.Meta;
const Mainloop = imports.mainloop;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const AppfolderDialog = Me.imports.appfolderDialog;
const DragAndDrop = Me.imports.dragAndDrop;
const Gettext = imports.gettext.domain('appfolders-manager');
const _ = Gettext.gettext;
let FOLDER_SCHEMA;
let FOLDER_LIST;
let INIT_TIME;
function init () {
Convenience.initTranslations();
INIT_TIME = getTimeStamp();
}
function getTimeStamp () {
let today = new Date();
let str = today.getDate() + '' + today.getHours() + '' + today.getMinutes()
+ '' + today.getSeconds();
return parseInt(str);
}
//------------------------------------------------------------------------------
/* do not edit this section */
function injectToFunction(parent, name, func) {
let origin = parent[name];
parent[name] = function() {
let ret;
ret = origin.apply(this, arguments);
if (ret === undefined)
ret = func.apply(this, arguments);
return ret;
}
return origin;
}
function removeInjection(object, injection, name) {
if (injection[name] === undefined)
delete object[name];
else
object[name] = injection[name];
}
var injections=[];
//------------------------------------------------------------------------------
/* this function injects items (1 or 2 submenus) in AppIconMenu's _redisplay method. */
function injectionInAppsMenus() {
injections['_redisplay'] = injectToFunction(AppDisplay.AppIconMenu.prototype, '_redisplay', function() {
if (Main.overview.viewSelector.getActivePage() == 2
|| Main.overview.viewSelector.getActivePage() == 3) {
//ok
} else {
return;
}
this._appendSeparator(); //TODO injecter ailleurs dans le menu?
let mainAppView = Main.overview.viewSelector.appDisplay._views[1].view;
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
//------------------------------------------------------------------
let addto = new PopupMenu.PopupSubMenuMenuItem(_("Add to"));
let newAppFolder = new PopupMenu.PopupMenuItem('+ ' + _("New AppFolder"));
newAppFolder.connect('activate', () => {
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
// XXX broken scrolling ??
// We can't popdown the folder immediately because the
// AppDisplay.AppFolderPopup.popdown() method tries to ungrab
// the global focus from the folder's popup actor, which isn't
// having the focus since the menu is still open. Menus' animation
// last ~0.25s so we will wait 0.30s before doing anything.
let a = Mainloop.timeout_add(300, () => {
if (mainAppView._currentPopup) {
mainAppView._currentPopup.popdown();
}
createNewFolder(this._source);
mainAppView._redisplay();
Mainloop.source_remove(a);
});
});
addto.menu.addMenuItem(newAppFolder);
for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
let _folder = FOLDER_LIST[i];
let shouldShow = !isInFolder( this._source.app.get_id(), _folder );
let iFolderSchema = folderSchema(_folder);
let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
shouldShow = true; //TODO ??? et l'exclusion ?
}
if(shouldShow) {
item.connect('activate', () => {
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
// XXX broken scrolling ??
// We can't popdown the folder immediatly because the
// AppDisplay.AppFolderPopup.popdown() method tries to
// ungrab the global focus from the folder's popup actor,
// which isn't having the focus since the menu is still
// open. Menus' animation last ~0.25s so we will wait 0.30s
// before doing anything.
let a = Mainloop.timeout_add(300, () => {
if (mainAppView._currentPopup) {
mainAppView._currentPopup.popdown();
}
addToFolder(this._source, _folder);
mainAppView._redisplay();
Mainloop.source_remove(a);
});
});
addto.menu.addMenuItem(item);
}
}
this.addMenuItem(addto);
//----------------------------------------------------------------------
let removeFrom = new PopupMenu.PopupSubMenuMenuItem(_("Remove from"));
let shouldShow2 = false;
for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
let _folder = FOLDER_LIST[i];
let appId = this._source.app.get_id();
let shouldShow = isInFolder(appId, _folder);
let iFolderSchema = folderSchema(_folder);
let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
shouldShow = true; //FIXME ??? et l'exclusion ?
}
if(shouldShow) {
item.connect('activate', () => {
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
// XXX broken scrolling ??
// We can't popdown the folder immediatly because the
// AppDisplay.AppFolderPopup.popdown() method tries to
// ungrab the global focus from the folder's popup actor,
// which isn't having the focus since the menu is still
// open. Menus' animation last ~0.25s so we will wait 0.30s
// before doing anything.
let a = Mainloop.timeout_add(300, () => {
if (mainAppView._currentPopup) {
mainAppView._currentPopup.popdown();
}
removeFromFolder(appId, _folder);
mainAppView._redisplay();
Mainloop.source_remove(a);
});
});
removeFrom.menu.addMenuItem(item);
shouldShow2 = true;
}
}
if (shouldShow2) {
this.addMenuItem(removeFrom);
}
});
}
//------------------------------------------------
function injectionInIcons() {
// Right-click on a FolderIcon launches a new AppfolderDialog
AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
constructor (id, path, parentView) {
super(id, path, parentView);
if (!this.isCustom) {
this.actor.connect('button-press-event', this._onButtonPress.bind(this));
}
this.isCustom = true;
}
_onButtonPress (actor, event) {
let button = event.get_button();
if (button == 3) {
let tmp = new Gio.Settings({
schema_id: 'org.gnome.desktop.app-folders.folder',
path: '/org/gnome/desktop/app-folders/folders/' + this.id + '/'
});
let dialog = new AppfolderDialog.AppfolderDialog(tmp, null, this.id);
dialog.open();
}
return Clutter.EVENT_PROPAGATE;
}
};
// Dragging an AppIcon triggers the DND mode
AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
constructor (app, params) {
super(app, params);
if (!this.isCustom) {
this._draggable.connect('drag-begin', this.onDragBeginExt.bind(this));
this._draggable.connect('drag-cancelled', this.onDragCancelledExt.bind(this));
this._draggable.connect('drag-end', this.onDragEndExt.bind(this));
}
this.isCustom = true;
}
onDragBeginExt () {
if (Main.overview.viewSelector.getActivePage() != 2) {
return;
}
this._removeMenuTimeout(); // why ?
Main.overview.beginItemDrag(this);
DragAndDrop.OVERLAY_MANAGER.on_drag_begin();
}
onDragEndExt () {
Main.overview.endItemDrag(this);
DragAndDrop.OVERLAY_MANAGER.on_drag_end();
}
onDragCancelledExt () {
Main.overview.cancelledItemDrag(this);
DragAndDrop.OVERLAY_MANAGER.on_drag_cancelled();
}
};
}
//------------------------------------------------------------------------------
//---------------------------------- Generic -----------------------------------
//--------------------------------- functions ----------------------------------
//------------------------------------------------------------------------------
/* These functions perform the requested actions but do not care about popdowning
* open menu/open folder, nor about hiding/showing/activating dropping areas, nor
* about redisplaying the view.
*/
function removeFromFolder (app_id, folder_id) {
let folder_schema = folderSchema(folder_id);
if ( isInFolder(app_id, folder_id) ) {
let pastContent = folder_schema.get_strv('apps');
let presentContent = [];
for(var i=0; i<pastContent.length; i++){
if(pastContent[i] != app_id) {
presentContent.push(pastContent[i]);
}
}
folder_schema.set_strv('apps', presentContent);
} else {
let content = folder_schema.get_strv('excluded-apps');
content.push(app_id);
folder_schema.set_strv('excluded-apps', content);
}
return true;
}
//------------------------------------------------------------------------------
function deleteFolder (folder_id) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
let tmp = [];
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
for(var j=0;j < FOLDER_LIST.length;j++){
if(FOLDER_LIST[j] != folder_id) {
tmp.push(FOLDER_LIST[j]);
}
}
FOLDER_LIST = tmp;
FOLDER_SCHEMA.set_strv('folder-children', FOLDER_LIST);
// ?? XXX (ne fonctionne pas mieux hors du meta.later_add)
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('total-deletion') ) {
let folder_schema = folderSchema (folder_id);
folder_schema.reset('apps'); // génère un bug volumineux ?
folder_schema.reset('categories'); // génère un bug volumineux ?
folder_schema.reset('excluded-apps'); // génère un bug volumineux ?
folder_schema.reset('name'); // génère un bug volumineux ?
}
});
return true;
}
//------------------------------------------------------------------------------
function mergeFolders (folder_staying_id, folder_dying_id) { //unused XXX
let folder_dying_schema = folderSchema (folder_dying_id);
let folder_staying_schema = folderSchema (folder_staying_id);
let newerContent = folder_dying_schema.get_strv('categories');
let presentContent = folder_staying_schema.get_strv('categories');
for(var i=0;i<newerContent.length;i++){
if(presentContent.indexOf(newerContent[i]) == -1) {
presentContent.push(newerContent[i]);
}
}
folder_staying_schema.set_strv('categories', presentContent);
newerContent = folder_dying_schema.get_strv('excluded-apps');
presentContent = folder_staying_schema.get_strv('excluded-apps');
for(var i=0;i<newerContent.length;i++){
if(presentContent.indexOf(newerContent[i]) == -1) {
presentContent.push(newerContent[i]);
}
}
folder_staying_schema.set_strv('excluded-apps', presentContent);
newerContent = folder_dying_schema.get_strv('apps');
presentContent = folder_staying_schema.get_strv('apps');
for(var i=0;i<newerContent.length;i++){
if(presentContent.indexOf(newerContent[i]) == -1) {
// if(!isInFolder(newerContent[i], folder_staying_id)) {
presentContent.push(newerContent[i]);
//TODO utiliser addToFolder malgré ses paramètres chiants
}
}
folder_staying_schema.set_strv('apps', presentContent);
deleteFolder(folder_dying_id);
return true;
}
//------------------------------------------------------------------------------
function createNewFolder (app_source) {
let id = app_source.app.get_id();
let dialog = new AppfolderDialog.AppfolderDialog(null , id);
dialog.open();
return true;
}
//------------------------------------------------------------------------------
function addToFolder (app_source, folder_id) {
let id = app_source.app.get_id();
let folder_schema = folderSchema (folder_id);
//un-exclude the application if it was excluded TODO else don't do it at all
let pastExcluded = folder_schema.get_strv('excluded-apps');
let presentExcluded = [];
for(let i=0; i<pastExcluded.length; i++){
if(pastExcluded[i] != id) {
presentExcluded.push(pastExcluded[i]);
}
}
if (presentExcluded.length > 0) {
folder_schema.set_strv('excluded-apps', presentExcluded);
}
//actually add the app
let content = folder_schema.get_strv('apps');
content.push(id);
folder_schema.set_strv('apps', content); //XXX verbose errors
//update icons in the ugliest possible way
let icons = Main.overview.viewSelector.appDisplay._views[1].view.folderIcons;
for (let i=0; i<icons.length; i++) {
let size = icons[i].icon._iconBin.width;
icons[i].icon.icon = icons[i]._createIcon(size);
icons[i].icon._iconBin.child = icons[i].icon.icon;
}
return true;
}
//------------------------------------------------------------------------------
function isInFolder (app_id, folder_id) {
let folder_schema = folderSchema(folder_id);
let isIn = false;
let content_ = folder_schema.get_strv('apps');
for(var j=0; j<content_.length; j++) {
if(content_[j] == app_id) {
isIn = true;
}
}
return isIn;
}
//------------------------------------------------------------------------------
function folderSchema (folder_id) {
let a = new Gio.Settings({
schema_id: 'org.gnome.desktop.app-folders.folder',
path: '/org/gnome/desktop/app-folders/folders/' + folder_id + '/'
});
return a;
} // TODO et AppDisplay._getFolderName ??
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function enable() {
FOLDER_SCHEMA = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
injectionInIcons();
if( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('extend-menus') ) {
injectionInAppsMenus();
}
DragAndDrop.initDND();
// Reload the view if the user load the extension at least a minute after
// opening the session. XXX works like shit
let delta = getTimeStamp() - INIT_TIME;
if (delta < 0 || delta > 105) {
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
}
}
function disable() {
AppDisplay.FolderIcon.prototype._onButtonPress = null;
AppDisplay.FolderIcon.prototype.popupMenu = null;
removeInjection(AppDisplay.AppIconMenu.prototype, injections, '_redisplay');
// Overwrite my shit for FolderIcon
AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
_onButtonPress (actor, event) {
return Clutter.EVENT_PROPAGATE;
}
};
// Overwrite my shit for AppIcon
AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
onDragBeginExt () {}
onDragEndExt () {}
onDragCancelledExt () {}
};
DragAndDrop.OVERLAY_MANAGER.destroy();
}
//------------------------------------------------------------------------------

View File

@ -0,0 +1,114 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr ""
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr ""
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, javascript-format
msgid "Remove from %s"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""

View File

@ -0,0 +1,123 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2018-12-30 14:03+0300\n"
"Last-Translator: Максім Крапіўка <metalomaniax@gmail.com>\n"
"Language-Team: \n"
"Language: be\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Дадаць у"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Новая папка праграм"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Выдаліць з"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Стварыць новую папку"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, javascript-format
msgid "Remove from %s"
msgstr "Выдаліць з %s"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Скасаваць"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Выдаліць"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Дастасаваць"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Назва папкі"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Катэгорыі:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Іншая катэгорыя?"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Без катэгорыі"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr "Выбраць катэгорыю..."
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Змены ўступяць у сілу пасля перазагрузкі пашырэння."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Галоўныя налады"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Катэгорыі"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Выдаляць усе звязаныя налады пры выдаленні папкі праграм"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
"Выкарыстоўваць націсканне правай кнопкай мышы ў дадатак да перацягвання "
"значкоў"
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Выкарыстоўваць катэгорыі"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Больш інфармацыі пра «дадатковыя» катэгорыі"
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Паведаміць пра памылкі або ідэі"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Гэта пашырэнне можна адключыць, пасля таго як вы канчаткова наладзіце "
"арганізацыю сваіх праграм."
#~ msgid "Standard specification"
#~ msgstr "Стандартная спецыфікацыя"

View File

@ -0,0 +1,124 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-10-28 01:50+0200\n"
"Last-Translator: Jonatan Hatakeyama Zeidler <jonatan_zeidler@gmx.de>\n"
"Language-Team: https://github.com/hobbypunk90/appfolders-manager-gnome-"
"extension\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Hinzufügen zu"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Neuer Ordner"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Löschen aus"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Löschen aus"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Abbrechen"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Erstelle"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Löschen"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
#, fuzzy
msgid "Other category?"
msgstr "Lösche eine Kategorie"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
#, fuzzy
msgid "No category"
msgstr "Kategorie hinzufügen"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Lösche eine Kategorie"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Änderungen werden nach Neustart der Erweiterung übernommen."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Lösche alle Einstellungen, wenn ein Ordner gelöscht wird"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
#, fuzzy
msgid "Use categories"
msgstr "Hinzugefügte Kategorien"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
#~ msgid "This appfolder already exists."
#~ msgstr "Dieser Ordner existiert bereits."

View File

@ -0,0 +1,118 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-05-29 23:26+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Προσθήκη σε"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Νέος φάκελος"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Αφαίρεση από"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Αφαίρεση από"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Αναίρεση"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Διαγραφή"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr ""
"Οι τροποποιήσεις απαιτούν την επανεκκίνηση της επέκτασης για να "
"λειτουργήσουν."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Διαγραφή όλων των σχετικών ρυθμίσεων κατά την διαγραφή ενός φακέλου"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""

View File

@ -0,0 +1,116 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-02-05 16:47+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.11\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Ajouter à"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Nouvel AppFolder"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Retirer de"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Créer un nouveau dossier"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, javascript-format
msgid "Remove from %s"
msgstr "Retirer de %s"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Annuler"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Créer"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Supprimer"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Appliquer"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Nom du dossier :"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Catégories :"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Autre catégorie ?"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Aucune catégorie"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr "Choisir une catégorie…"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Les modifications prendront effet après avoir rechargé l'extension"
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Réglages principaux"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Catégories"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr ""
"Supprimer tous les réglages relatifs à un appfolder lors de sa suppression"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr "Utiliser le menu du clic-droit en complément du glisser-déposer"
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Utiliser des catégories"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Plus d'informations à propos des \"catégories supplémentaires\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Reporter un bug ou une idée"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Cette extension peut être désactivée une fois que vos applications sont "
"organisées comme souhaité."
#~ msgid "Standard specification"
#~ msgstr "Spécification standard"

View File

@ -0,0 +1,116 @@
# Hungarian translation for appfolders-manager.
# Copyright (C) 2017 Free Software Foundation, Inc.
# This file is distributed under the same license as the PACKAGE package.
#
# Balázs Úr <urbalazs@gmail.com>, 2017.
msgid ""
msgstr ""
"Project-Id-Version: appfolders-manager master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-05-31 20:41+0100\n"
"Last-Translator: Balázs Úr <urbalazs@gmail.com>\n"
"Language-Team: Hungarian <openscope@googlegroups.com>\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 2.0\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Hozzáadás ehhez"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Új alkalmazásmappa"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Eltávolítás innen"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Eltávolítás innen"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Mégse"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Törlés"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "A módosítások a kiterjesztés újratöltése után lépnek hatályba."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr ""
"Az összes kapcsolódó beállítás törlése, ha egy alkalmazásmappa törölve lesz"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""

View File

@ -0,0 +1,127 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2018-03-08 17:21+0100\n"
"Last-Translator: Jimmy Scionti <jimmy.scionti@gmail.com>\n"
"Language-Team: \n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Aggiungi a"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Nuova AppFolder"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Rimuovi da"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Rimuovi da"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Annulla"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Crea"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Elimina"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
#, fuzzy
msgid "Other category?"
msgstr "Rimuovi una categoria"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
#, fuzzy
msgid "No category"
msgstr "Aggiungi una categoria"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Rimuovi una categoria"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Le modifiche avranno effetto dopo aver ricaricato l'estensione."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Elimina tutte le impostazioni dell'AppFolder quando viene rimossa"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
#, fuzzy
msgid "Use categories"
msgstr "Categorie aggiuntive"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Maggiori informazioni sulle \"categorie aggiuntive\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Invia segnalazioni di bug o idee"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Questa estensione può essere disattivata dopo aver organizzato le "
"applicazione come desiderato."
#~ msgid "Standard specification"
#~ msgstr "Specifiche standards"
#~ msgid "This appfolder already exists."
#~ msgstr "Questa AppFolder esiste già."

View File

@ -0,0 +1,126 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: Appfolders Management (GNOME extension)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2018-12-19 21:13+0100\n"
"Last-Translator: Piotr Komur <pkomur@gmail.com>\n"
"Language-Team: Piotr Komur\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Dodaj do folderu"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Nowy folder programów"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Usuń z folderu"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Utwórz nowy folder"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Usuń z folderu"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Anuluj"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Utwórz"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Usuń"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Zastosuj"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Nazwa folderu:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Kategorie programów:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Nowa kategoria"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Brak wybranych kategorii"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Wybierz kategorię"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Zmiany będą aktywne po ponownym zrestartowaniu środowiska Gnome."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Ustawienia"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Kategorie"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Usuń powiązane zmiany wraz z usunięciem folderu programów."
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Użyj standardowych kategorii"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Więcej informacji o \"standardowych kategoriach\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Zgłoś błędy lub nowe funkcje"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Można dezaktywować to rozszerzenie po zakończeniu porządkowania programów w "
"folderach."
#~ msgid "Standard specification"
#~ msgstr "Specyfikacja standardu"
#~ msgid "This appfolder already exists."
#~ msgstr "Folder o tej nazwie już istnieje."

View File

@ -0,0 +1,124 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2019-02-10 11:19-0200\n"
"Last-Translator: Fábio Nogueira <fnogueira@gnome.org>\n"
"Language-Team: \n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Adicionar para"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Nova AppFolder"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Remover de"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Criar uma nova pasta"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, javascript-format
msgid "Remove from %s"
msgstr "Remover de %s"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Cancelar"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Criar"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Excluir"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Aplicar"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Nome da pasta:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Categorias:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Outra categoria?"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Nenhuma categoria"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr "Selecione uma categoria…"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "As modificações entrarão em vigor depois de recarregar a extensão."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Configurações principais"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Categorias"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr ""
"Excluir todas as configurações relacionadas quando um appfolder for excluído"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr "Use os menus do botão direito, além do arrastar e soltar"
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Usar categorias"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Mais informações sobre \"categorias adicionais\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Comunicar erros ou ideias"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Esta extensão pode ser desativada assim que seus aplicativos forem "
"organizados conforme desejado."
#~ msgid "Standard specification"
#~ msgstr "Especificação padrão"
#~ msgid "This appfolder already exists."
#~ msgstr "Esta appfolder já existe."

View File

@ -0,0 +1,125 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Max Krapiŭka <metalomaniax@gmail.com>\n"
"Language-Team: RUSSIAN <metalomaniax@gmail.com>\n"
"Language: be\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Добавить в"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Новая папка"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Удалить из"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Удалить из"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Отмена"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Стварыць"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Удалить"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
#, fuzzy
msgid "Other category?"
msgstr "Удалить категорию"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
#, fuzzy
msgid "No category"
msgstr "Добавить категорию"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Удалить категорию"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Изменения вступят в силу после перезагрузки расширения."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Удалять все связанные настройки при удалении папки приложений."
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
#, fuzzy
msgid "Use categories"
msgstr "Дополнительные категории"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Больше информации про \"дополнительные категории\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Сообщить об ошибках, предложить идеи"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Это расширение может быть деактивировано, после того как вы окончательно "
"настроите организацию своих приложений."
#~ msgid "Standard specification"
#~ msgstr "Стандартная спецификация"
#~ msgid "This appfolder already exists."
#~ msgstr "Эта папка приложений уже существует"

View File

@ -0,0 +1,124 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-09-15 16:40+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: sr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Додај у"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Нова фаскила програма"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Уклони ставку"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Уклони ставку"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Откажи"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Направи"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Обриши"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
#, fuzzy
msgid "Other category?"
msgstr "Уклони категорију"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
#, fuzzy
msgid "No category"
msgstr "Додај категорију"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Уклони категорију"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Измене ће ступити на снагу по поновном учитавању проширења."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Обриши све припадајуће поставке при брисању проширења"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
#, fuzzy
msgid "Use categories"
msgstr "Додатне категорије"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
#~ msgid "This appfolder already exists."
#~ msgstr "Ова фасцикла програма већ постоји."

View File

@ -0,0 +1,124 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2017-09-15 16:40+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: sr@latin\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Dodaj u"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Nova faskila programa"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Ukloni stavku"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr ""
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Ukloni stavku"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Otkaži"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Napravi"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Obriši"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
#, fuzzy
msgid "Other category?"
msgstr "Ukloni kategoriju"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
#, fuzzy
msgid "No category"
msgstr "Dodaj kategoriju"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Ukloni kategoriju"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Izmene će stupiti na snagu po ponovnom učitavanju proširenja."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Obriši sve pripadajuće postavke pri brisanju proširenja"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:56
#, fuzzy
msgid "Use categories"
msgstr "Dodatne kategorije"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr ""
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
#~ msgid "This appfolder already exists."
#~ msgstr "Ova fascikla programa već postoji."

View File

@ -0,0 +1,118 @@
# Uygulama klasörleme Türkçe çeviri.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Serdar Sağlam <teknomobil@yandex.com>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: v13\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2019-01-19 12:10+0300\n"
"Last-Translator: Serdar Sağlam <teknomobil@yandex.com>\n"
"Language-Team: Türkçe <teknomobil@yandex.com>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Klasör Grubuna Taşı"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Yeni Klasör Grubu"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Buradan Kaldır"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Yeni klasör oluştur"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, javascript-format
msgid "Remove from %s"
msgstr "Buradan Kaldır %s"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "İptal"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr "Oluştur"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Sil"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Onayla"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Klasör İsmi:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Kategoriler:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Diğer Kategori?"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Kategori Yok"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
msgid "Select a category…"
msgstr "Kategori Seç…"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Değişiklikler,eklenti yeniden başladıktan sonra etkili olacaktır."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Ayarlar"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Kategoriler"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Bir klasör grubu silindiğinde tüm ilgili ayarları silin"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr "Sürükle ve bırak işlevine ek olarak sağ tıklama menülerini kullanın"
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Kategorileri Kullan"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Hakkında daha fazla bilgi \"ek kategoriler\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Yeni bir fikir veya hata bildirin"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr ""
"Menüleri istediğiniz şekilde düzenlendiğinde bu uzantı devre dışı "
"bırakılabilir."
#~ msgid "Standard specification"
#~ msgstr "Standart şartname"

View File

@ -0,0 +1,123 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
"PO-Revision-Date: 2018-12-19 05:43+0200\n"
"Last-Translator: Igor Gordiichuk <igor_ck@outlook.com>\n"
"Language-Team: \n"
"Language: uk_UA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: appfolders-manager@maestroschan.fr/extension.js:83
msgid "Add to"
msgstr "Додати до"
#: appfolders-manager@maestroschan.fr/extension.js:85
msgid "New AppFolder"
msgstr "Нова тека програм"
#: appfolders-manager@maestroschan.fr/extension.js:139
msgid "Remove from"
msgstr "Вилучити з"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
msgid "Create a new folder"
msgstr "Створити нову теку"
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
#, fuzzy, javascript-format
msgid "Remove from %s"
msgstr "Вилучити з "
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
msgid "Cancel"
msgstr "Скасувати"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
msgid "Create"
msgstr ""
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
msgid "Delete"
msgstr "Вилучити"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
msgid "Apply"
msgstr "Застосовувати"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
msgid "Folder's name:"
msgstr "Назва теки:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
msgid "Categories:"
msgstr "Категорії:"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
msgid "Other category?"
msgstr "Інша категорія?"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
msgid "No category"
msgstr "Без категорії"
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
#, fuzzy
msgid "Select a category…"
msgstr "Обрати категорію"
#: appfolders-manager@maestroschan.fr/prefs.js:31
msgid "Modifications will be effective after reloading the extension."
msgstr "Зміни буде застосовано після перезавантаження розширення."
#: appfolders-manager@maestroschan.fr/prefs.js:38
msgid "Main settings"
msgstr "Основні налаштування"
#: appfolders-manager@maestroschan.fr/prefs.js:39
msgid "Categories"
msgstr "Категорії"
#: appfolders-manager@maestroschan.fr/prefs.js:46
msgid "Delete all related settings when an appfolder is deleted"
msgstr "Вилучити всі пов'язані налаштування, коли видаляється тека"
#: appfolders-manager@maestroschan.fr/prefs.js:48
msgid "Use the right-click menus in addition to the drag-and-drop"
msgstr "Використовувати контекстне меню додатково до перетягування мишкою"
#: appfolders-manager@maestroschan.fr/prefs.js:56
msgid "Use categories"
msgstr "Використати категорії"
#: appfolders-manager@maestroschan.fr/prefs.js:59
msgid "More informations about \"additional categories\""
msgstr "Більше інформації про \"додаткові категорії\""
#: appfolders-manager@maestroschan.fr/prefs.js:74
msgid "Report bugs or ideas"
msgstr "Повідомити про ваду або запропонувати ідею"
#: appfolders-manager@maestroschan.fr/prefs.js:85
msgid ""
"This extension can be deactivated once your applications are organized as "
"wished."
msgstr "Це розширення можна деактивувати, якщо програми було впорядковано."
#~ msgid "Standard specification"
#~ msgstr "Стандартні категорії"
#~ msgid "This appfolder already exists."
#~ msgstr "Ця тека програм вже існує."

View File

@ -0,0 +1,15 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "An easy way to arrange your applications in folders, directly from the applications grid. Create folders and add/remove apps using drag-and-drop, rename/delete them with a right-click.",
"gettext-domain": "appfolders-manager",
"name": "Appfolders Management extension",
"shell-version": [
"3.26",
"3.28",
"3.30",
"3.32"
],
"url": "https://github.com/maoschanz/appfolders-manager-gnome-extension",
"uuid": "appfolders-manager@maestroschan.fr",
"version": 16
}

View File

@ -0,0 +1,153 @@
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Gettext = imports.gettext.domain('appfolders-manager');
const _ = Gettext.gettext;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
//-----------------------------------------------
const appfoldersManagerSettingsWidget = new GObject.Class({
Name: 'appfoldersManager.Prefs.Widget',
GTypeName: 'appfoldersManagerPrefsWidget',
Extends: Gtk.Box,
_init: function (params) {
this.parent(params);
this.margin = 30;
this.spacing = 18;
this.set_orientation(Gtk.Orientation.VERTICAL);
this._settings = Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager');
this._settings.set_boolean('debug', this._settings.get_boolean('debug'));
//----------------------------
let labelMain = new Gtk.Label({
label: _("Modifications will be effective after reloading the extension."),
use_markup: true,
wrap: true,
halign: Gtk.Align.START
});
this.add(labelMain);
let generalSection = this.add_section(_("Main settings"));
let categoriesSection = this.add_section(_("Categories"));
//----------------------------
// let autoDeleteBox = this.build_switch('auto-deletion',
// _("Delete automatically empty folders"));
let deleteAllBox = this.build_switch('total-deletion',
_("Delete all related settings when an appfolder is deleted"));
let menusBox = this.build_switch('extend-menus',
_("Use the right-click menus in addition to the drag-and-drop"));
// this.add_row(autoDeleteBox, generalSection);
this.add_row(deleteAllBox, generalSection);
this.add_row(menusBox, generalSection);
//-------------------------
let categoriesBox = this.build_switch('categories', _("Use categories"));
let categoriesLinkButton = new Gtk.LinkButton({
label: _("More informations about \"additional categories\""),
uri: "https://standards.freedesktop.org/menu-spec/latest/apas02.html"
});
this.add_row(categoriesBox, categoriesSection);
this.add_row(categoriesLinkButton, categoriesSection);
//-------------------------
let aboutBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
let about_label = new Gtk.Label({
label: '(v' + Me.metadata.version.toString() + ')',
halign: Gtk.Align.START
});
let url_button = new Gtk.LinkButton({
label: _("Report bugs or ideas"),
uri: Me.metadata.url.toString()
});
aboutBox.pack_start(url_button, false, false, 0);
aboutBox.pack_end(about_label, false, false, 0);
this.pack_end(aboutBox, false, false, 0);
//-------------------------
let desacLabel = new Gtk.Label({
label: _("This extension can be deactivated once your applications are organized as wished."),
wrap: true,
halign: Gtk.Align.CENTER
});
this.pack_end(desacLabel, false, false, 0);
},
add_section: function (titre) {
let section = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
margin: 6,
spacing: 6,
});
let frame = new Gtk.Frame({
label: titre,
label_xalign: 0.1,
});
frame.add(section);
this.add(frame);
return section;
},
add_row: function (filledbox, section) {
section.add(filledbox);
},
build_switch: function (key, label) {
let rowLabel = new Gtk.Label({
label: label,
halign: Gtk.Align.START,
wrap: true,
visible: true,
});
let rowSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER });
rowSwitch.set_state(this._settings.get_boolean(key));
rowSwitch.connect('notify::active', (widget) => {
this._settings.set_boolean(key, widget.active);
});
let rowBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 15,
margin: 6,
visible: true,
});
rowBox.pack_start(rowLabel, false, false, 0);
rowBox.pack_end(rowSwitch, false, false, 0);
return rowBox;
},
});
//-----------------------------------------------
function init() {
Convenience.initTranslations();
}
//I guess this is like the "enable" in extension.js : something called each
//time he user try to access the settings' window
function buildPrefsWidget () {
let widget = new appfoldersManagerSettingsWidget();
widget.show_all();
return widget;
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="appfolders-manager">
<schema id="org.gnome.shell.extensions.appfolders-manager" path="/org/gnome/shell/extensions/appfolders-manager/">
<key type="b" name="total-deletion">
<default>true</default>
<summary>Complete deletion of an appfolder</summary>
<description>if a deleted appfolder should be 100% deleted (false = restauration is possible)</description>
</key>
<key type="b" name="categories">
<default>true</default>
<summary>Use categories</summary>
<description>If the interface for managing categories should be shown.</description>
</key>
<key type="b" name="debug">
<default>false</default>
<summary>Debug key</summary>
<description>this is not supposed to be activated by the user</description>
</key>
<key type="b" name="extend-menus">
<default>true</default>
<summary>Show items in right-click menus</summary>
<description>The legacy interface, with submenus in the right-click menu on application icons, can be shown in addition to the default drag-and-drop behavior.</description>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,56 @@
.dropAreaLabel {
font-size: 24px;
font-weight: bold;
}
.framedArea {
background-color: rgba(255,255,255,0.0);
border-color: rgba(255,255,255,1.0);
border-width: 1px;
color: rgba(255, 255, 255, 1.0);
}
.shadowedAreaTop {
background-gradient-start: rgba(0, 0, 0, 0.7);
background-gradient-end: rgba(0, 0, 0, 0.1);
background-gradient-direction: vertical;
color: rgba(255, 255, 255, 1.0);
}
.shadowedAreaBottom {
background-gradient-start: rgba(0, 0, 0, 0.1);
background-gradient-end: rgba(0, 0, 0, 0.7);
background-gradient-direction: vertical;
color: rgba(255, 255, 255, 1.0);
}
.folderArea {
background-gradient-start: rgba(0, 0, 0, 0.4);
background-gradient-end: rgba(0, 0, 0, 0.0);
background-gradient-direction: vertical;
border-color: rgba(255,255,255,1.0);
border-radius: 4px;
border-width: 2px;
color: rgba(255, 255, 255, 1.0);
}
.insensitiveArea {
background-color: rgba(0, 0, 0, 0.1);
color: rgba(100, 100, 100, 0.6);
}
.appCategoryBox {
background-color: rgba(100, 100, 100, 0.3);
border-radius: 3px;
margin: 3px;
padding: 2px;
padding-left: 6px;
}
.appCategoryDeleteBtn {
background-color: rgba(100, 100, 100, 0.3);
border-radius: 3px;
}

View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2014, 2018, Andreas Fuchs
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,37 @@
Gnome-Shell Extension Audio-Output-Switcher
===========================================
This extension adds a little entry to the status-menu that shows the currently
selected pulse-audio-output device. Clicking on that will open a submenu with
all available output devices and let's you choose which one to use.
Since version 5, it also provides a shortcut to quickly switch output sources:
`<Super>`+`q`
Most importantly this extension is as simple as possible. Therefore it does not
include an input switcher or similar.
See Audio-Input-Switcher (https://github.com/anduchs/audio-input-switcher)
extension for microphone selection.
Install
-------
Either via
https://extensions.gnome.org
or via
git clone https://github.com/adaxi/audio-output-switcher.git ~/.local/share/gnome-shell/extensions/audio-output-switcher@anduchs
Then resart the gnome-shell via `Alt+F2` then `r` and enable the extension using gnome-tweak-tool
To update later, just issue
(cd ~/.local/share/gnome-shell/extensions/audio-output-switcher@anduchs && git pull)
Credits
-------
Originally authored by @anduchs.

View File

@ -0,0 +1,128 @@
const ExtensionUtils = imports.misc.extensionUtils;
const Meta = imports.gi.Meta;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Shell = imports.gi.Shell;
const Me = ExtensionUtils.getCurrentExtension();
const Utils = Me.imports.utils;
const AudioOutputSubMenu = class AudioOutputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
constructor() {
super("Audio Output: Connecting...", true);
this._control = Main.panel.statusArea.aggregateMenu._volume._control;
this._controlSignal = this._control.connect('default-sink-changed', () => {
this._updateDefaultSink();
});
this._updateDefaultSink();
this.menu.connect('open-state-changed', (menu, isOpen) => {
if (isOpen)
this._updateSinkList();
});
//Unless there is at least one item here, no 'open' will be emitted...
let item = new PopupMenu.PopupMenuItem('Connecting...');
this.menu.addMenuItem(item);
}
_updateDefaultSink() {
let defsink = this._control.get_default_sink();
//Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
if (!defsink)
this.label.set_text("Other");
else
this.label.set_text(defsink.get_description());
}
_updateSinkList() {
this.menu.removeAll();
let defsink = this._control.get_default_sink();
let sinklist = this._control.get_sinks();
let control = this._control;
for (let i=0; i<sinklist.length; i++) {
let sink = sinklist[i];
if (sink === defsink)
continue;
let item = new PopupMenu.PopupMenuItem(sink.get_description());
item.connect('activate', () => {
control.set_default_sink(sink);
});
this.menu.addMenuItem(item);
}
if (sinklist.length == 0 ||
(sinklist.length == 1 && sinklist[0] === defsink)) {
item = new PopupMenu.PopupMenuItem("No more Devices");
this.menu.addMenuItem(item);
}
}
destroy() {
this._control.disconnect(this._controlSignal);
this.parent();
}
}
let sinkIndex = 0;
let settings = null;
let audioOutputSubMenu = null;
function init () {
sinkIndex = 0;
settings = Utils.getSettings();
}
function enable () {
if (audioOutputSubMenu) {
return;
}
audioOutputSubMenu = new AudioOutputSubMenu();
//Try to add the output-switcher right below the output slider...
let volMen = Main.panel.statusArea.aggregateMenu._volume._volumeMenu;
let items = volMen._getMenuItems();
let i = 0;
while (i < items.length)
if (items[i] === volMen._output.item)
break;
else
i++;
volMen.addMenuItem(audioOutputSubMenu, i+1);
//Add keyboard shortcut for fast switching
let keyBindingMode = null;
if (Shell.ActionMode) {
//KeyBindingMode was renamed to ActionMode in Gnome 3.15.3
keyBindingMode = Shell.ActionMode.ALL;
} else {
keyBindingMode = Shell.KeyBindingMode.ALL;
}
Main.wm.addKeybinding("switch-next-audio-output",
settings,
Meta.KeyBindingFlags.NONE,
keyBindingMode,
function(display, screen, window, binding) {
let control = Main.panel.statusArea.aggregateMenu._volume._control;
let sinklist = control.get_sinks();
if (sinklist.length === 0) {
return;
}
sinkIndex++
if (sinkIndex >= sinklist.length) {
sinkIndex = 0
}
let sink = sinklist[sinkIndex];
control.set_default_sink(sink);
}
);
}
function disable () {
audioOutputSubMenu.destroy();
audioOutputSubMenu = null;
}

View File

@ -0,0 +1,12 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "Adds a switch for choosing audio output to the system menu.",
"name": "Audio Output Switcher",
"settings-schema": "org.gnome.shell.extensions.audio-output-switcher",
"shell-version": [
"3.32"
],
"url": "http://github.com/adaxi/audio-output-switcher",
"uuid": "audio-output-switcher@anduchs",
"version": 9
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema id="org.gnome.shell.extensions.audio-output-switcher" path="/org/gnome/shell/extensions/audio-output-switcher/">
<key type="as" name="switch-next-audio-output">
<default><![CDATA[['<Super>q']]]></default>
<summary>Switch to the next audio output device</summary>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,92 @@
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GNOME nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const Gettext = imports.gettext;
const Gio = imports.gi.Gio;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
/**
* initTranslations:
* @domain: (optional): the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
*/
function initTranslations(domain) {
let extension = ExtensionUtils.getCurrentExtension();
domain = domain || extension.metadata['gettext-domain'];
// check if this extension was built with "make zip-file", and thus
// has the locale files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell
let localeDir = extension.dir.get_child('locale');
if (localeDir.query_exists(null))
Gettext.bindtextdomain(domain, localeDir.get_path());
else
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}
/**
* getSettings:
* @schema: (optional): the GSettings schema id
*
* Builds and return a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is not provided, it is taken from
* metadata['settings-schema'].
*/
function getSettings(schema) {
let extension = ExtensionUtils.getCurrentExtension();
schema = schema || extension.metadata['settings-schema'];
const GioSSS = Gio.SettingsSchemaSource;
// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
else
schemaSource = GioSSS.get_default();
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension '
+ extension.metadata.uuid + '. Please check your installation.');
return new Gio.Settings({ settings_schema: schemaObj });
}

View File

@ -0,0 +1,164 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2019 Andrea Azzaronea <andrea.azzarone@canonical.com>
*
* 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 3 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/>.
*/
const { Clutter, GObject, GLib, Gio, St } = imports.gi;
const Signals = imports.signals;
const Dialog = imports.ui.dialog;
const Gettext = imports.gettext.domain('desktop-icons');
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
const Extension = Me.imports.extension;
const _ = Gettext.gettext;
const DIALOG_GROW_TIME = 0.1;
var CreateFolderDialog = class extends ModalDialog.ModalDialog {
constructor() {
super({ styleClass: 'create-folder-dialog' });
this._buildLayout();
}
_buildLayout() {
let label = new St.Label({ style_class: 'create-folder-dialog-label',
text: _('New folder name') });
this.contentLayout.add(label, { x_align: St.Align.START });
this._entry = new St.Entry({ style_class: 'create-folder-dialog-entry',
can_focus: true });
this._entry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
this._entry.clutter_text.connect('text-changed', this._onTextChanged.bind(this));
ShellEntry.addContextMenu(this._entry);
this.contentLayout.add(this._entry);
this.setInitialKeyFocus(this._entry);
this._errorBox = new St.BoxLayout({ style_class: 'create-folder-dialog-error-box',
visible: false });
this.contentLayout.add(this._errorBox, { expand: true });
this._errorMessage = new St.Label({ style_class: 'create-folder-dialog-error-label' });
this._errorMessage.clutter_text.line_wrap = true;
this._errorBox.add(this._errorMessage, { expand: true,
x_align: St.Align.START,
x_fill: false,
y_align: St.Align.MIDDLE,
y_fill: false });
this._createButton = this.addButton({ action: this._onCreateButton.bind(this),
label: _('Create') });
this.addButton({ action: this.close.bind(this),
label: _('Cancel'),
key: Clutter.Escape });
this._onTextChanged();
}
_showError(message) {
this._errorMessage.set_text(message);
if (!this._errorBox.visible) {
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
let parentActor = this._errorBox.get_parent();
Tweener.addTween(parentActor,
{ height: parentActor.height + errorBoxNaturalHeight,
time: DIALOG_GROW_TIME,
transition: 'easeOutQuad',
onComplete: () => {
parentActor.set_height(-1);
this._errorBox.show();
}
});
}
}
_hideError() {
if (this._errorBox.visible) {
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
let parentActor = this._errorBox.get_parent();
Tweener.addTween(parentActor,
{ height: parentActor.height - errorBoxNaturalHeight,
time: DIALOG_GROW_TIME,
transition: 'easeOutQuad',
onComplete: () => {
parentActor.set_height(-1);
this._errorBox.hide();
this._errorMessage.set_text('');
}
});
}
}
_onCreateButton() {
this._onEntryActivate();
}
_onEntryActivate() {
if (!this._createButton.reactive)
return;
this.emit('response', this._entry.get_text());
this.close();
}
_onTextChanged() {
let text = this._entry.get_text();
let is_valid = true;
let found_name = false;
for(let name of Extension.desktopManager.getDesktopFileNames()) {
if (name === text) {
found_name = true;
break;
}
}
if (text.trim().length == 0) {
is_valid = false;
this._hideError();
} else if (text.includes('/')) {
is_valid = false;
this._showError(_('Folder names cannot contain “/”.'));
} else if (text === '.') {
is_valid = false;
this._showError(_('A folder cannot be called “.”.'));
} else if (text === '..') {
is_valid = false;
this._showError(_('A folder cannot be called “..”.'));
} else if (text.startsWith('.')) {
this._showError(_('Folders with “.” at the beginning of their name are hidden.'));
} else if (found_name) {
this._showError(_('There is already a file or folder with that name.'));
is_valid = false;
} else {
this._hideError();
}
this._createButton.reactive = is_valid;
}
};
Signals.addSignalMethods(CreateFolderDialog.prototype);

View File

@ -0,0 +1,35 @@
#!/usr/bin/gjs
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2018 Sergio Costas <rastersoft@gmail.com>
*
* 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 3 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/>.
*/
const GnomeDesktop = imports.gi.GnomeDesktop;
const Gio = imports.gi.Gio;
let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE);
let file = Gio.File.new_for_path(ARGV[0]);
let fileUri = file.get_uri();
let fileInfo = file.query_info('standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
let modifiedTime = fileInfo.get_attribute_uint64('time::modified');
let thumbnailPixbuf = thumbnailFactory.generate_thumbnail(fileUri, fileInfo.get_content_type());
if (thumbnailPixbuf == null)
thumbnailFactory.create_failed_thumbnail(fileUri, modifiedTime);
else
thumbnailFactory.save_thumbnail(thumbnailPixbuf, fileUri, modifiedTime);

View File

@ -0,0 +1,103 @@
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
var NautilusFileOperationsProxy;
var FreeDesktopFileManagerProxy;
const NautilusFileOperationsInterface = `<node>
<interface name='org.gnome.Nautilus.FileOperations'>
<method name='CopyURIs'>
<arg name='URIs' type='as' direction='in'/>
<arg name='Destination' type='s' direction='in'/>
</method>
<method name='MoveURIs'>
<arg name='URIs' type='as' direction='in'/>
<arg name='Destination' type='s' direction='in'/>
</method>
<method name='EmptyTrash'>
</method>
<method name='TrashFiles'>
<arg name='URIs' type='as' direction='in'/>
</method>
<method name='CreateFolder'>
<arg name='URI' type='s' direction='in'/>
</method>
<method name='RenameFile'>
<arg name='URI' type='s' direction='in'/>
<arg name='NewName' type='s' direction='in'/>
</method>
<method name='Undo'>
</method>
<method name='Redo'>
</method>
<property name='UndoStatus' type='i' access='read'/>
</interface>
</node>`;
const NautilusFileOperationsProxyInterface = Gio.DBusProxy.makeProxyWrapper(NautilusFileOperationsInterface);
const FreeDesktopFileManagerInterface = `<node>
<interface name='org.freedesktop.FileManager1'>
<method name='ShowItems'>
<arg name='URIs' type='as' direction='in'/>
<arg name='StartupId' type='s' direction='in'/>
</method>
<method name='ShowItemProperties'>
<arg name='URIs' type='as' direction='in'/>
<arg name='StartupId' type='s' direction='in'/>
</method>
</interface>
</node>`;
const FreeDesktopFileManagerProxyInterface = Gio.DBusProxy.makeProxyWrapper(FreeDesktopFileManagerInterface);
function init() {
NautilusFileOperationsProxy = new NautilusFileOperationsProxyInterface(
Gio.DBus.session,
'org.gnome.Nautilus',
'/org/gnome/Nautilus',
(proxy, error) => {
if (error) {
log('Error connecting to Nautilus');
}
}
);
FreeDesktopFileManagerProxy = new FreeDesktopFileManagerProxyInterface(
Gio.DBus.session,
'org.freedesktop.FileManager1',
'/org/freedesktop/FileManager1',
(proxy, error) => {
if (error) {
log('Error connecting to Nautilus');
}
}
);
}
function openFileWithOtherApplication(filePath) {
let fdList = new Gio.UnixFDList();
let channel = GLib.IOChannel.new_file(filePath, "r");
fdList.append(channel.unix_get_fd());
channel.set_close_on_unref(true);
let builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sv}"));
let options = builder.end();
let parameters = GLib.Variant.new_tuple([GLib.Variant.new_string("0"),
GLib.Variant.new_handle(0),
options]);
Gio.bus_get(Gio.BusType.SESSION, null,
(source, result) => {
let dbus_connection = Gio.bus_get_finish(result);
dbus_connection.call_with_unix_fd_list("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.OpenURI",
"OpenFile",
parameters,
GLib.VariantType.new("o"),
Gio.DBusCallFlags.NONE,
-1,
fdList,
null,
null);
}
);
}

View File

@ -0,0 +1,692 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Gtk = imports.gi.Gtk;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const BoxPointer = imports.ui.boxpointer;
const PopupMenu = imports.ui.popupMenu;
const GrabHelper = imports.ui.grabHelper;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const CreateFolderDialog = Me.imports.createFolderDialog;
const Extension = Me.imports.extension;
const FileItem = Me.imports.fileItem;
const Prefs = Me.imports.prefs;
const DBusUtils = Me.imports.dbusUtils;
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
const Util = imports.misc.util;
const Clipboard = St.Clipboard.get_default();
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
const Gettext = imports.gettext.domain('desktop-icons');
const _ = Gettext.gettext;
/* From NautilusFileUndoManagerState */
var UndoStatus = {
NONE: 0,
UNDO: 1,
REDO: 2,
};
var StoredCoordinates = {
PRESERVE: 0,
OVERWRITE:1,
ASSIGN:2,
};
class Placeholder extends St.Bin {
constructor() {
super();
}
}
var DesktopGrid = class {
constructor(bgManager) {
this._bgManager = bgManager;
this._fileItemHandlers = new Map();
this._fileItems = [];
this.layout = new Clutter.GridLayout({
orientation: Clutter.Orientation.VERTICAL,
column_homogeneous: true,
row_homogeneous: true
});
this._actorLayout = new Clutter.BinLayout({
x_align: Clutter.BinAlignment.FIXED,
y_align: Clutter.BinAlignment.FIXED
});
this.actor = new St.Widget({
layout_manager: this._actorLayout
});
this.actor._delegate = this;
this._grid = new St.Widget({
name: 'DesktopGrid',
layout_manager: this.layout,
reactive: true,
x_expand: true,
y_expand: true,
can_focus: true,
opacity: 255
});
this.actor.add_child(this._grid);
this._renamePopup = new RenamePopup(this);
this.actor.add_child(this._renamePopup.actor);
this._bgManager._container.add_child(this.actor);
this.actor.connect('destroy', () => this._onDestroy());
let monitorIndex = bgManager._monitorIndex;
this._monitorConstraint = new Layout.MonitorConstraint({
index: monitorIndex,
work_area: true
});
this.actor.add_constraint(this._monitorConstraint);
this._addDesktopBackgroundMenu();
this._bgDestroyedId = bgManager.backgroundActor.connect('destroy',
() => this._backgroundDestroyed());
this._grid.connect('button-press-event', (actor, event) => this._onPressButton(actor, event));
this._grid.connect('key-press-event', this._onKeyPress.bind(this));
this._grid.connect('allocation-changed', () => Extension.desktopManager.scheduleReLayoutChildren());
}
_onKeyPress(actor, event) {
if (global.stage.get_key_focus() != actor)
return Clutter.EVENT_PROPAGATE;
let symbol = event.get_key_symbol();
let isCtrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
let isShift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0;
if (isCtrl && isShift && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) {
this._doRedo();
return Clutter.EVENT_STOP;
}
else if (isCtrl && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) {
this._doUndo();
return Clutter.EVENT_STOP;
}
else if (isCtrl && [Clutter.C, Clutter.c].indexOf(symbol) > -1) {
Extension.desktopManager.doCopy();
return Clutter.EVENT_STOP;
}
else if (isCtrl && [Clutter.X, Clutter.x].indexOf(symbol) > -1) {
Extension.desktopManager.doCut();
return Clutter.EVENT_STOP;
}
else if (isCtrl && [Clutter.V, Clutter.v].indexOf(symbol) > -1) {
this._doPaste();
return Clutter.EVENT_STOP;
}
else if (symbol == Clutter.Return) {
Extension.desktopManager.doOpen();
return Clutter.EVENT_STOP;
}
else if (symbol == Clutter.Delete) {
Extension.desktopManager.doTrash();
return Clutter.EVENT_STOP;
} else if (symbol == Clutter.F2) {
// Support renaming other grids file items.
Extension.desktopManager.doRename();
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_backgroundDestroyed() {
this._bgDestroyedId = 0;
if (this._bgManager == null)
return;
if (this._bgManager._backgroundSource) {
this._bgDestroyedId = this._bgManager.backgroundActor.connect('destroy',
() => this._backgroundDestroyed());
} else {
this.actor.destroy();
}
}
_onDestroy() {
if (this._bgDestroyedId && this._bgManager.backgroundActor != null)
this._bgManager.backgroundActor.disconnect(this._bgDestroyedId);
this._bgDestroyedId = 0;
this._bgManager = null;
}
_onNewFolderClicked() {
let dialog = new CreateFolderDialog.CreateFolderDialog();
dialog.connect('response', (dialog, name) => {
let dir = DesktopIconsUtil.getDesktopDir().get_child(name);
DBusUtils.NautilusFileOperationsProxy.CreateFolderRemote(dir.get_uri(),
(result, error) => {
if (error)
throw new Error('Error creating new folder: ' + error.message);
}
);
});
dialog.open();
}
_parseClipboardText(text) {
if (text === null)
return [false, false, null];
let lines = text.split('\n');
let [mime, action, ...files] = lines;
if (mime != 'x-special/nautilus-clipboard')
return [false, false, null];
if (!(['copy', 'cut'].includes(action)))
return [false, false, null];
let isCut = action == 'cut';
/* Last line is empty due to the split */
if (files.length <= 1)
return [false, false, null];
/* Remove last line */
files.pop();
return [true, isCut, files];
}
_doPaste() {
Clipboard.get_text(CLIPBOARD_TYPE,
(clipboard, text) => {
let [valid, is_cut, files] = this._parseClipboardText(text);
if (!valid)
return;
let desktopDir = `${DesktopIconsUtil.getDesktopDir().get_uri()}`;
if (is_cut) {
DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(files, desktopDir,
(result, error) => {
if (error)
throw new Error('Error moving files: ' + error.message);
}
);
} else {
DBusUtils.NautilusFileOperationsProxy.CopyURIsRemote(files, desktopDir,
(result, error) => {
if (error)
throw new Error('Error copying files: ' + error.message);
}
);
}
}
);
}
_onPasteClicked() {
this._doPaste();
}
_doUndo() {
DBusUtils.NautilusFileOperationsProxy.UndoRemote(
(result, error) => {
if (error)
throw new Error('Error performing undo: ' + error.message);
}
);
}
_onUndoClicked() {
this._doUndo();
}
_doRedo() {
DBusUtils.NautilusFileOperationsProxy.RedoRemote(
(result, error) => {
if (error)
throw new Error('Error performing redo: ' + error.message);
}
);
}
_onRedoClicked() {
this._doRedo();
}
_onOpenDesktopInFilesClicked() {
Gio.AppInfo.launch_default_for_uri_async(DesktopIconsUtil.getDesktopDir().get_uri(),
null, null,
(source, result) => {
try {
Gio.AppInfo.launch_default_for_uri_finish(result);
} catch (e) {
log('Error opening Desktop in Files: ' + e.message);
}
}
);
}
_onOpenTerminalClicked() {
let desktopPath = DesktopIconsUtil.getDesktopDir().get_path();
DesktopIconsUtil.launchTerminal(desktopPath);
}
_syncUndoRedo() {
this._undoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.UNDO;
this._redoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.REDO;
}
_undoStatusChanged(proxy, properties, test) {
if ('UndoStatus' in properties.deep_unpack())
this._syncUndoRedo();
}
_createDesktopBackgroundMenu() {
let menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor,
0, St.Side.TOP);
menu.addAction(_("New Folder"), () => this._onNewFolderClicked());
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._pasteMenuItem = menu.addAction(_("Paste"), () => this._onPasteClicked());
this._undoMenuItem = menu.addAction(_("Undo"), () => this._onUndoClicked());
this._redoMenuItem = menu.addAction(_("Redo"), () => this._onRedoClicked());
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
menu.addAction(_("Show Desktop in Files"), () => this._onOpenDesktopInFilesClicked());
menu.addAction(_("Open in Terminal"), () => this._onOpenTerminalClicked());
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
menu.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop');
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
menu.addSettingsAction(_("Display Settings"), 'gnome-display-panel.desktop');
menu.addSettingsAction(_("Settings"), 'gnome-control-center.desktop');
menu.actor.add_style_class_name('background-menu');
Main.layoutManager.uiGroup.add_child(menu.actor);
menu.actor.hide();
menu._propertiesChangedId = DBusUtils.NautilusFileOperationsProxy.connect('g-properties-changed',
this._undoStatusChanged.bind(this));
this._syncUndoRedo();
menu.connect('destroy',
() => DBusUtils.NautilusFileOperationsProxy.disconnect(menu._propertiesChangedId));
menu.connect('open-state-changed',
(popupm, isOpen) => {
if (isOpen) {
Clipboard.get_text(CLIPBOARD_TYPE,
(clipBoard, text) => {
let [valid, is_cut, files] = this._parseClipboardText(text);
this._pasteMenuItem.setSensitive(valid);
}
);
}
}
);
this._pasteMenuItem.setSensitive(false);
return menu;
}
_openMenu(x, y) {
Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
this.actor._desktopBackgroundMenu.open(BoxPointer.PopupAnimation.NONE);
/* Since the handler is in the press event it needs to ignore the release event
* to not immediately close the menu on release
*/
this.actor._desktopBackgroundManager.ignoreRelease();
}
_addFileItemTo(fileItem, column, row, coordinatesAction) {
let placeholder = this.layout.get_child_at(column, row);
placeholder.child = fileItem.actor;
this._fileItems.push(fileItem);
let selectedId = fileItem.connect('selected', this._onFileItemSelected.bind(this));
let renameId = fileItem.connect('rename-clicked', this.doRename.bind(this));
this._fileItemHandlers.set(fileItem, [selectedId, renameId]);
/* If this file is new in the Desktop and hasn't yet
* fixed coordinates, store the new possition to ensure
* that the next time it will be shown in the same possition.
* Also store the new possition if it has been moved by the user,
* and not triggered by a screen change.
*/
if ((fileItem.savedCoordinates == null) || (coordinatesAction == StoredCoordinates.OVERWRITE)) {
let [fileX, fileY] = placeholder.get_transformed_position();
fileItem.savedCoordinates = [Math.round(fileX), Math.round(fileY)];
}
}
addFileItemCloseTo(fileItem, x, y, coordinatesAction) {
let [column, row] = this._getEmptyPlaceClosestTo(x, y, coordinatesAction);
this._addFileItemTo(fileItem, column, row, coordinatesAction);
}
_getEmptyPlaceClosestTo(x, y, coordinatesAction) {
let maxColumns = this._getMaxColumns();
let maxRows = this._getMaxRows();
let [actorX, actorY] = this._grid.get_transformed_position();
let actorWidth = this._grid.allocation.x2 - this._grid.allocation.x1;
let actorHeight = this._grid.allocation.y2 - this._grid.allocation.y1;
let placeX = Math.round((x - actorX) * maxColumns / actorWidth);
let placeY = Math.round((y - actorY) * maxRows / actorHeight);
placeX = DesktopIconsUtil.clamp(placeX, 0, maxColumns - 1);
placeY = DesktopIconsUtil.clamp(placeY, 0, maxRows - 1);
if (this.layout.get_child_at(placeX, placeY).child == null)
return [placeX, placeY];
let found = false;
let resColumn = null;
let resRow = null;
let minDistance = Infinity;
for (let column = 0; column < maxColumns; column++) {
for (let row = 0; row < maxRows; row++) {
let placeholder = this.layout.get_child_at(column, row);
if (placeholder.child != null)
continue;
let [proposedX, proposedY] = placeholder.get_transformed_position();
if (coordinatesAction == StoredCoordinates.ASSIGN)
return [column, row];
let distance = DesktopIconsUtil.distanceBetweenPoints(proposedX, proposedY, x, y);
if (distance < minDistance) {
found = true;
minDistance = distance;
resColumn = column;
resRow = row;
}
}
}
if (!found)
throw new Error(`Not enough place at monitor ${this._bgManager._monitorIndex}`);
return [resColumn, resRow];
}
removeFileItem(fileItem) {
let index = this._fileItems.indexOf(fileItem);
if (index > -1)
this._fileItems.splice(index, 1);
else
throw new Error('Error removing children from container');
let [column, row] = this._getPosOfFileItem(fileItem);
let placeholder = this.layout.get_child_at(column, row);
placeholder.child = null;
let [selectedId, renameId] = this._fileItemHandlers.get(fileItem);
fileItem.disconnect(selectedId);
fileItem.disconnect(renameId);
this._fileItemHandlers.delete(fileItem);
}
_fillPlaceholders() {
for (let column = 0; column < this._getMaxColumns(); column++) {
for (let row = 0; row < this._getMaxRows(); row++) {
this.layout.attach(new Placeholder(), column, row, 1, 1);
}
}
}
reset() {
let tmpFileItemsCopy = this._fileItems.slice();
for (let fileItem of tmpFileItemsCopy)
this.removeFileItem(fileItem);
this._grid.remove_all_children();
this._fillPlaceholders();
}
_onStageMotion(actor, event) {
if (this._drawingRubberBand) {
let [x, y] = event.get_coords();
this._updateRubberBand(x, y);
this._selectFromRubberband(x, y);
}
return Clutter.EVENT_PROPAGATE;
}
_onPressButton(actor, event) {
let button = event.get_button();
let [x, y] = event.get_coords();
this._grid.grab_key_focus();
if (button == 1) {
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
if (!shiftPressed && !controlPressed)
Extension.desktopManager.clearSelection();
let [gridX, gridY] = this._grid.get_transformed_position();
Extension.desktopManager.startRubberBand(x, y, gridX, gridY);
return Clutter.EVENT_STOP;
}
if (button == 3) {
this._openMenu(x, y);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_addDesktopBackgroundMenu() {
this.actor._desktopBackgroundMenu = this._createDesktopBackgroundMenu();
this.actor._desktopBackgroundManager = new PopupMenu.PopupMenuManager({ actor: this.actor });
this.actor._desktopBackgroundManager.addMenu(this.actor._desktopBackgroundMenu);
this.actor.connect('destroy', () => {
this.actor._desktopBackgroundMenu.destroy();
this.actor._desktopBackgroundMenu = null;
this.actor._desktopBackgroundManager = null;
});
}
_getMaxColumns() {
let gridWidth = this._grid.allocation.x2 - this._grid.allocation.x1;
return Math.floor(gridWidth / Prefs.get_desired_width(St.ThemeContext.get_for_stage(global.stage).scale_factor));
}
_getMaxRows() {
let gridHeight = this._grid.allocation.y2 - this._grid.allocation.y1;
return Math.floor(gridHeight / Prefs.get_desired_height(St.ThemeContext.get_for_stage(global.stage).scale_factor));
}
acceptDrop(source, actor, x, y, time) {
/* Coordinates are relative to the grid, we want to transform them to
* absolute coordinates to work across monitors */
let [gridX, gridY] = this.actor.get_transformed_position();
let [absoluteX, absoluteY] = [x + gridX, y + gridY];
return Extension.desktopManager.acceptDrop(absoluteX, absoluteY);
}
_getPosOfFileItem(itemToFind) {
if (itemToFind == null)
throw new Error('Error at _getPosOfFileItem: child cannot be null');
let found = false;
let maxColumns = this._getMaxColumns();
let maxRows = this._getMaxRows();
let column = 0;
let row = 0;
for (column = 0; column < maxColumns; column++) {
for (row = 0; row < maxRows; row++) {
let item = this.layout.get_child_at(column, row);
if (item.child && item.child._delegate.file.equal(itemToFind.file)) {
found = true;
break;
}
}
if (found)
break;
}
if (!found)
throw new Error('Position of file item was not found');
return [column, row];
}
_onFileItemSelected(fileItem, keepCurrentSelection, addToSelection) {
this._grid.grab_key_focus();
}
doRename(fileItem) {
this._renamePopup.onFileItemRenameClicked(fileItem);
}
};
var RenamePopup = class {
constructor(grid) {
this._source = null;
this._isOpen = false;
this._renameEntry = new St.Entry({ hint_text: _("Enter file name…"),
can_focus: true,
x_expand: true });
this._renameEntry.clutter_text.connect('activate', this._onRenameAccepted.bind(this));
this._renameOkButton= new St.Button({ label: _("OK"),
style_class: 'app-view-control button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
x_expand: true });
this._renameCancelButton = new St.Button({ label: _("Cancel"),
style_class: 'app-view-control button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
x_expand: true });
this._renameCancelButton.connect('clicked', () => { this._onRenameCanceled(); });
this._renameOkButton.connect('clicked', () => { this._onRenameAccepted(); });
let renameButtonsBoxLayout = new Clutter.BoxLayout({ homogeneous: true });
let renameButtonsBox = new St.Widget({ layout_manager: renameButtonsBoxLayout,
x_expand: true });
renameButtonsBox.add_child(this._renameCancelButton);
renameButtonsBox.add_child(this._renameOkButton);
let renameContentLayout = new Clutter.BoxLayout({ spacing: 6,
orientation: Clutter.Orientation.VERTICAL });
let renameContent = new St.Widget({ style_class: 'rename-popup',
layout_manager: renameContentLayout,
x_expand: true });
renameContent.add_child(this._renameEntry);
renameContent.add_child(renameButtonsBox);
this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP, { can_focus: false, x_expand: false });
this.actor = this._boxPointer.actor;
this.actor.style_class = 'popup-menu-boxpointer';
this.actor.add_style_class_name('popup-menu');
this.actor.visible = false;
this._boxPointer.bin.set_child(renameContent);
this._grabHelper = new GrabHelper.GrabHelper(grid.actor, { actionMode: Shell.ActionMode.POPUP });
this._grabHelper.addActor(this.actor);
}
_popup() {
if (this._isOpen)
return;
this._isOpen = this._grabHelper.grab({ actor: this.actor,
onUngrab: this._popdown.bind(this) });
if (!this._isOpen) {
this._grabHelper.ungrab({ actor: this.actor });
return;
}
this._boxPointer.setPosition(this._source.actor, 0.5);
if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION))
this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
BoxPointer.PopupAnimation.SLIDE);
else
this._boxPointer.open(BoxPointer.PopupAnimation.FADE |
BoxPointer.PopupAnimation.SLIDE);
this.emit('open-state-changed', true);
}
_popdown() {
if (!this._isOpen)
return;
this._grabHelper.ungrab({ actor: this.actor });
if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION))
this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
BoxPointer.PopupAnimation.SLIDE);
else
this._boxPointer.close(BoxPointer.PopupAnimation.FADE |
BoxPointer.PopupAnimation.SLIDE);
this._isOpen = false;
this.emit('open-state-changed', false);
}
onFileItemRenameClicked(fileItem) {
this._source = fileItem;
this._renameEntry.text = fileItem.displayName;
this._popup();
this._renameEntry.grab_key_focus();
this._renameEntry.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
let extensionOffset = DesktopIconsUtil.getFileExtensionOffset(fileItem.displayName, fileItem.isDirectory);
this._renameEntry.clutter_text.set_selection(0, extensionOffset);
}
_onRenameAccepted() {
this._popdown();
DBusUtils.NautilusFileOperationsProxy.RenameFileRemote(this._source.file.get_uri(),
this._renameEntry.get_text(),
(result, error) => {
if (error)
throw new Error('Error renaming file: ' + error.message);
}
);
}
_onRenameCanceled() {
this._popdown();
}
};
Signals.addSignalMethods(RenamePopup.prototype);

View File

@ -0,0 +1,123 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Prefs = Me.imports.prefs;
const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal';
const EXEC_KEY = 'exec';
var DEFAULT_ATTRIBUTES = 'metadata::*,standard::*,access::*,time::modified,unix::mode';
function getDesktopDir() {
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
return Gio.File.new_for_commandline_arg(desktopPath);
}
function clamp(value, min, max) {
return Math.max(Math.min(value, max), min);
};
function launchTerminal(workdir) {
let terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA });
let exec = terminalSettings.get_string(EXEC_KEY);
let argv = [exec, `--working-directory=${workdir}`];
/* The following code has been extracted from GNOME Shell's
* source code in Misc.Util.trySpawn function and modified to
* set the working directory.
*
* https://gitlab.gnome.org/GNOME/gnome-shell/blob/gnome-3-30/js/misc/util.js
*/
var success, pid;
try {
[success, pid] = GLib.spawn_async(workdir, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
} catch (err) {
/* Rewrite the error in case of ENOENT */
if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) {
throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT,
message: _("Command not found") });
} else if (err instanceof GLib.Error) {
// The exception from gjs contains an error string like:
// Error invoking GLib.spawn_command_line_async: Failed to
// execute child process "foo" (No such file or directory)
// We are only interested in the part in the parentheses. (And
// we can't pattern match the text, since it gets localized.)
let message = err.message.replace(/.*\((.+)\)/, '$1');
throw new (err.constructor)({ code: err.code,
message: message });
} else {
throw err;
}
}
// Dummy child watch; we don't want to double-fork internally
// because then we lose the parent-child relationship, which
// can break polkit. See https://bugzilla.redhat.com//show_bug.cgi?id=819275
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, () => {});
}
function distanceBetweenPoints(x, y, x2, y2) {
return (Math.pow(x - x2, 2) + Math.pow(y - y2, 2));
}
function getExtraFolders() {
let extraFolders = new Array();
if (Prefs.settings.get_boolean('show-home')) {
extraFolders.push([Gio.File.new_for_commandline_arg(GLib.get_home_dir()), Prefs.FileType.USER_DIRECTORY_HOME]);
}
if (Prefs.settings.get_boolean('show-trash')) {
extraFolders.push([Gio.File.new_for_uri('trash:///'), Prefs.FileType.USER_DIRECTORY_TRASH]);
}
return extraFolders;
}
function getFileExtensionOffset(filename, isDirectory) {
let offset = filename.length;
if (!isDirectory) {
let doubleExtensions = ['.gz', '.bz2', '.sit', '.Z', '.bz', '.xz'];
for (let extension of doubleExtensions) {
if (filename.endsWith(extension)) {
offset -= extension.length;
filename = filename.substring(0, offset);
break;
}
}
let lastDot = filename.lastIndexOf('.');
if (lastDot > 0)
offset = lastDot;
}
return offset;
}
function getGtkClassBackgroundColor(classname, state) {
let widget = new Gtk.WidgetPath();
widget.append_type(Gtk.Widget);
let context = new Gtk.StyleContext();
context.set_path(widget);
context.add_class(classname);
return context.get_background_color(state);
}

View File

@ -0,0 +1,752 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Gtk = imports.gi.Gtk;
const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Animation = imports.ui.animation;
const Background = imports.ui.background;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const GrabHelper = imports.ui.grabHelper;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Extension = Me.imports.extension;
const DesktopGrid = Me.imports.desktopGrid;
const FileItem = Me.imports.fileItem;
const Prefs = Me.imports.prefs;
const DBusUtils = Me.imports.dbusUtils;
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
const Clipboard = St.Clipboard.get_default();
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
var S_IWOTH = 0x00002;
function getDpy() {
return global.screen || global.display;
}
function findMonitorIndexForPos(x, y) {
return getDpy().get_monitor_index_for_rect(new Meta.Rectangle({x, y}));
}
var DesktopManager = GObject.registerClass({
Properties: {
'writable-by-others': GObject.ParamSpec.boolean(
'writable-by-others',
'WritableByOthers',
'Whether the desktop\'s directory can be written by others (o+w unix permission)',
GObject.ParamFlags.READABLE,
false
)
}
}, class DesktopManager extends GObject.Object {
_init(params) {
super._init(params);
this._layoutChildrenId = 0;
this._deleteChildrenId = 0;
this._monitorDesktopDir = null;
this._desktopMonitorCancellable = null;
this._desktopGrids = {};
this._fileItemHandlers = new Map();
this._fileItems = new Map();
this._dragCancelled = false;
this._queryFileInfoCancellable = null;
this._unixMode = null;
this._writableByOthers = null;
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons());
this._rubberBand = new St.Widget({ style_class: 'rubber-band' });
this._rubberBand.hide();
Main.layoutManager._backgroundGroup.add_child(this._rubberBand);
this._grabHelper = new GrabHelper.GrabHelper(global.stage);
this._addDesktopIcons();
this._monitorDesktopFolder();
this.settingsId = Prefs.settings.connect('changed', () => this._recreateDesktopIcons());
this.gtkSettingsId = Prefs.gtkSettings.connect('changed', (obj, key) => {
if (key == 'show-hidden')
this._recreateDesktopIcons();
});
this._selection = new Set();
this._currentSelection = new Set();
this._inDrag = false;
this._dragXStart = Number.POSITIVE_INFINITY;
this._dragYStart = Number.POSITIVE_INFINITY;
}
startRubberBand(x, y) {
this._rubberBandInitialX = x;
this._rubberBandInitialY = y;
this._initRubberBandColor();
this._updateRubberBand(x, y);
this._rubberBand.show();
this._grabHelper.grab({ actor: global.stage });
Extension.lockActivitiesButton = true;
this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => {
this.endRubberBand();
});
this._rubberBandId = global.stage.connect('motion-event', (actor, event) => {
/* In some cases, when the user starts a rubberband selection and ends it
* (by releasing the left button) over a window instead of doing it over
* the desktop, the stage doesn't receive the "button-release" event.
* This happens currently with, at least, Dash to Dock extension, but
* it probably also happens with other applications or extensions.
* To fix this, we also end the rubberband selection if we detect mouse
* motion in the stage without the left button pressed during a
* rubberband selection.
* */
let button = event.get_state();
if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
this.endRubberBand();
return;
}
[x, y] = event.get_coords();
this._updateRubberBand(x, y);
let x0, y0, x1, y1;
if (x >= this._rubberBandInitialX) {
x0 = this._rubberBandInitialX;
x1 = x;
} else {
x1 = this._rubberBandInitialX;
x0 = x;
}
if (y >= this._rubberBandInitialY) {
y0 = this._rubberBandInitialY;
y1 = y;
} else {
y1 = this._rubberBandInitialY;
y0 = y;
}
for (let [fileUri, fileItem] of this._fileItems) {
fileItem.emit('selected', true, true,
fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
}
});
}
endRubberBand() {
this._rubberBand.hide();
Extension.lockActivitiesButton = false;
this._grabHelper.ungrab();
global.stage.disconnect(this._rubberBandId);
global.stage.disconnect(this._stageReleaseEventId);
this._rubberBandId = 0;
this._stageReleaseEventId = 0;
this._selection = new Set([...this._selection, ...this._currentSelection]);
this._currentSelection.clear();
}
_updateRubberBand(currentX, currentY) {
let x = this._rubberBandInitialX < currentX ? this._rubberBandInitialX
: currentX;
let y = this._rubberBandInitialY < currentY ? this._rubberBandInitialY
: currentY;
let width = Math.abs(this._rubberBandInitialX - currentX);
let height = Math.abs(this._rubberBandInitialY - currentY);
/* TODO: Convert to gobject.set for 3.30 */
this._rubberBand.set_position(x, y);
this._rubberBand.set_size(width, height);
}
_recreateDesktopIcons() {
this._destroyDesktopIcons();
this._addDesktopIcons();
}
_addDesktopIcons() {
forEachBackgroundManager(bgManager => {
let newGrid = new DesktopGrid.DesktopGrid(bgManager);
newGrid.actor.connect('destroy', (actor) => {
// if a grid loses its actor, remove it from the grid list
for (let grid in this._desktopGrids)
if (this._desktopGrids[grid].actor == actor) {
delete this._desktopGrids[grid];
break;
}
});
this._desktopGrids[bgManager._monitorIndex] = newGrid;
});
this._scanFiles();
}
_destroyDesktopIcons() {
Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy());
this._desktopGrids = {};
}
/**
* Initialize rubberband color from the GTK rubberband class
* */
_initRubberBandColor() {
let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('rubberband', Gtk.StateFlags.NORMAL);
let background_color =
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.4)';
this._rubberBand.set_style('background-color: ' + background_color);
}
async _scanFiles() {
for (let [fileItem, id] of this._fileItemHandlers)
fileItem.disconnect(id);
this._fileItemHandlers = new Map();
if (!this._unixMode) {
let desktopDir = DesktopIconsUtil.getDesktopDir();
let fileInfo = desktopDir.query_info(Gio.FILE_ATTRIBUTE_UNIX_MODE,
Gio.FileQueryInfoFlags.NONE,
null);
this._unixMode = fileInfo.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
}
try {
let tmpFileItems = new Map();
for (let [file, info, extra] of await this._enumerateDesktop()) {
let fileItem = new FileItem.FileItem(file, info, extra);
tmpFileItems.set(fileItem.file.get_uri(), fileItem);
let id = fileItem.connect('selected',
this._onFileItemSelected.bind(this));
this._fileItemHandlers.set(fileItem, id);
}
this._fileItems = tmpFileItems;
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
log(`Error loading desktop files ${e.message}`);
return;
}
this.scheduleReLayoutChildren();
}
getDesktopFileNames () {
let fileList = [];
for (let [uri, item] of this._fileItems) {
fileList.push(item.fileName);
}
return fileList;
}
_enumerateDesktop() {
return new Promise((resolve, reject) => {
if (this._desktopEnumerateCancellable)
this._desktopEnumerateCancellable.cancel();
this._desktopEnumerateCancellable = new Gio.Cancellable();
let desktopDir = DesktopIconsUtil.getDesktopDir();
desktopDir.enumerate_children_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
this._desktopEnumerateCancellable,
(source, result) => {
try {
let fileEnum = source.enumerate_children_finish(result);
let resultGenerator = function *() {
let info;
for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) {
yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras];
}
while ((info = fileEnum.next_file(null)))
yield [fileEnum.get_child(info), info, Prefs.FileType.NONE];
}.bind(this);
resolve(resultGenerator());
} catch (e) {
reject(e);
}
});
});
}
_monitorDesktopFolder() {
if (this._monitorDesktopDir) {
this._monitorDesktopDir.cancel();
this._monitorDesktopDir = null;
}
let desktopDir = DesktopIconsUtil.getDesktopDir();
this._monitorDesktopDir = desktopDir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null);
this._monitorDesktopDir.set_rate_limit(1000);
this._monitorDesktopDir.connect('changed', (obj, file, otherFile, eventType) => this._updateDesktopIfChanged(file, otherFile, eventType));
}
checkIfSpecialFilesAreSelected() {
for (let fileItem of this._selection) {
if (fileItem.isSpecial)
return true;
}
return false;
}
getNumberOfSelectedItems() {
return this._selection.size;
}
get writableByOthers() {
return this._writableByOthers;
}
_setWritableByOthers(value) {
if (value == this._writableByOthers)
return;
this._writableByOthers = value
this.notify('writable-by-others');
}
_updateDesktopIfChanged (file, otherFile, eventType) {
let {
DELETED, MOVED_IN, MOVED_OUT, CREATED, RENAMED, CHANGES_DONE_HINT, ATTRIBUTE_CHANGED
} = Gio.FileMonitorEvent;
let fileUri = file.get_uri();
let fileItem = null;
if (this._fileItems.has(fileUri))
fileItem = this._fileItems.get(fileUri);
switch(eventType) {
case RENAMED:
this._fileItems.delete(fileUri);
this._fileItems.set(otherFile.get_uri(), fileItem);
fileItem.onFileRenamed(otherFile);
return;
case CHANGES_DONE_HINT:
case ATTRIBUTE_CHANGED:
/* a file changed, rather than the desktop itself */
let desktopDir = DesktopIconsUtil.getDesktopDir();
if (file.get_uri() != desktopDir.get_uri())
return;
if (this._queryFileInfoCancellable)
this._queryFileInfoCancellable.cancel();
file.query_info_async(Gio.FILE_ATTRIBUTE_UNIX_MODE,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
this._queryFileInfoCancellable,
(source, result) => {
try {
let info = source.query_info_finish(result);
this._queryFileInfoCancellable = null;
this._unixMode = info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
if (this._writableByOthers)
log(`desktop-icons: Desktop is writable by others - will not allow launching any desktop files`);
} catch(error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
global.log('Error getting desktop unix mode: ' + error);
}
});
return;
}
// Only get a subset of events we are interested in.
// Note that CREATED will emit a CHANGES_DONE_HINT
if (![DELETED, MOVED_IN, MOVED_OUT, CREATED].includes(eventType))
return;
this._recreateDesktopIcons();
}
_setupDnD() {
this._draggableContainer = new St.Widget({
visible: true,
width: 1,
height: 1,
x: 0,
y: 0,
style_class: 'draggable'
});
this._draggableContainer._delegate = this;
this._draggable = DND.makeDraggable(this._draggableContainer,
{
manualMode: true,
dragActorOpacity: 100
});
this._draggable.connect('drag-cancelled', () => this._onDragCancelled());
this._draggable.connect('drag-end', () => this._onDragEnd());
this._draggable._dragActorDropped = event => this._dragActorDropped(event);
}
dragStart() {
if (this._inDrag) {
return;
}
this._setupDnD();
let event = Clutter.get_current_event();
let [x, y] = event.get_coords();
[this._dragXStart, this._dragYStart] = event.get_coords();
this._inDrag = true;
for (let fileItem of this._selection) {
let clone = new Clutter.Clone({
source: fileItem.actor,
reactive: false
});
clone.x = fileItem.actor.get_transformed_position()[0];
clone.y = fileItem.actor.get_transformed_position()[1];
this._draggableContainer.add_child(clone);
}
Main.layoutManager.uiGroup.add_child(this._draggableContainer);
this._draggable.startDrag(x, y, global.get_current_time(), event.get_event_sequence());
}
_onDragCancelled() {
let event = Clutter.get_current_event();
let [x, y] = event.get_coords();
this._dragCancelled = true;
}
_onDragEnd() {
this._inDrag = false;
Main.layoutManager.uiGroup.remove_child(this._draggableContainer);
}
_dragActorDropped(event) {
let [dropX, dropY] = event.get_coords();
let target = this._draggable._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
dropX, dropY);
// We call observers only once per motion with the innermost
// target actor. If necessary, the observer can walk the
// parent itself.
let dropEvent = {
dropActor: this._draggable._dragActor,
targetActor: target,
clutterEvent: event
};
for (let dragMonitor of DND.dragMonitors) {
let dropFunc = dragMonitor.dragDrop;
if (dropFunc)
switch (dropFunc(dropEvent)) {
case DragDropResult.FAILURE:
case DragDropResult.SUCCESS:
return true;
case DragDropResult.CONTINUE:
continue;
}
}
// At this point it is too late to cancel a drag by destroying
// the actor, the fate of which is decided by acceptDrop and its
// side-effects
this._draggable._dragCancellable = false;
let destroyActor = false;
while (target) {
if (target._delegate && target._delegate.acceptDrop) {
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
if (target._delegate.acceptDrop(this._draggable.actor._delegate,
this._draggable._dragActor,
targX,
targY,
event.get_time())) {
// If it accepted the drop without taking the actor,
// handle it ourselves.
if (this._draggable._dragActor.get_parent() == Main.uiGroup) {
if (this._draggable._restoreOnSuccess) {
this._draggable._restoreDragActor(event.get_time());
return true;
}
else {
// We need this in order to make sure drag-end is fired
destroyActor = true;
}
}
this._draggable._dragInProgress = false;
getDpy().set_cursor(Meta.Cursor.DEFAULT);
this._draggable.emit('drag-end', event.get_time(), true);
if (destroyActor) {
this._draggable._dragActor.destroy();
}
this._draggable._dragComplete();
return true;
}
}
target = target.get_parent();
}
this._draggable._cancelDrag(event.get_time());
return true;
}
acceptDrop(xEnd, yEnd) {
let savedCoordinates = new Map();
let [xDiff, yDiff] = [xEnd - this._dragXStart, yEnd - this._dragYStart];
/* Remove all items before dropping new ones, so we can freely reposition
* them.
*/
for (let item of this._selection) {
let [itemX, itemY] = item.actor.get_transformed_position();
let monitorIndex = findMonitorIndexForPos(itemX, itemY);
savedCoordinates.set(item, [itemX, itemY]);
this._desktopGrids[monitorIndex].removeFileItem(item);
}
for (let item of this._selection) {
let [itemX, itemY] = savedCoordinates.get(item);
/* Set the new ideal position where the item drop should happen */
let newFileX = Math.round(xDiff + itemX);
let newFileY = Math.round(yDiff + itemY);
let monitorIndex = findMonitorIndexForPos(newFileX, newFileY);
this._desktopGrids[monitorIndex].addFileItemCloseTo(item, newFileX, newFileY, DesktopGrid.StoredCoordinates.OVERWRITE);
}
return true;
}
selectionDropOnFileItem (fileItemDestination) {
if (!fileItemDestination.isDirectory)
return false;
let droppedUris = [];
for (let fileItem of this._selection) {
if (fileItem.isSpecial)
return false;
if (fileItemDestination.file.get_uri() == fileItem.file.get_uri())
return false;
droppedUris.push(fileItem.file.get_uri());
}
if (droppedUris.length == 0)
return true;
DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(droppedUris,
fileItemDestination.file.get_uri(),
(result, error) => {
if (error)
throw new Error('Error moving files: ' + error.message);
}
);
for (let fileItem of this._selection) {
fileItem.state = FileItem.State.GONE;
}
this._recreateDesktopIcons();
return true;
}
_resetGridsAndScheduleLayout() {
this._deleteChildrenId = 0;
Object.values(this._desktopGrids).forEach((grid) => grid.reset());
this._layoutChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._layoutChildren());
return GLib.SOURCE_REMOVE;
}
scheduleReLayoutChildren() {
if (this._deleteChildrenId != 0)
return;
if (this._layoutChildrenId != 0) {
GLib.source_remove(this._layoutChildrenId);
this._layoutChildrenId = 0;
}
this._deleteChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._resetGridsAndScheduleLayout());
}
_addFileItemCloseTo(item) {
let coordinates;
let x = 0;
let y = 0;
let coordinatesAction = DesktopGrid.StoredCoordinates.ASSIGN;
if (item.savedCoordinates != null) {
[x, y] = item.savedCoordinates;
coordinatesAction = DesktopGrid.StoredCoordinates.PRESERVE;
}
let monitorIndex = findMonitorIndexForPos(x, y);
let desktopGrid = this._desktopGrids[monitorIndex];
try {
desktopGrid.addFileItemCloseTo(item, x, y, coordinatesAction);
} catch (e) {
log(`Error adding children to desktop: ${e.message}`);
}
}
_layoutChildren() {
let showHidden = Prefs.gtkSettings.get_boolean('show-hidden');
/*
* Paint the icons in two passes:
* * first pass paints those that have their coordinates defined in the metadata
* * second pass paints those new files that still don't have their definitive coordinates
*/
for (let [fileUri, fileItem] of this._fileItems) {
if (fileItem.savedCoordinates == null)
continue;
if (fileItem.state != FileItem.State.NORMAL)
continue;
if (!showHidden && fileItem.isHidden)
continue;
this._addFileItemCloseTo(fileItem);
}
for (let [fileUri, fileItem] of this._fileItems) {
if (fileItem.savedCoordinates !== null)
continue;
if (fileItem.state != FileItem.State.NORMAL)
continue;
if (!showHidden && fileItem.isHidden)
continue;
this._addFileItemCloseTo(fileItem);
}
this._layoutChildrenId = 0;
return GLib.SOURCE_REMOVE;
}
doRename() {
if (this._selection.size != 1)
return;
let item = [...this._selection][0];
if (item.canRename())
item.doRename();
}
doOpen() {
for (let fileItem of this._selection)
fileItem.doOpen();
}
doTrash() {
DBusUtils.NautilusFileOperationsProxy.TrashFilesRemote([...this._selection].map((x) => { return x.file.get_uri(); }),
(source, error) => {
if (error)
throw new Error('Error trashing files on the desktop: ' + error.message);
}
);
}
doEmptyTrash() {
DBusUtils.NautilusFileOperationsProxy.EmptyTrashRemote( (source, error) => {
if (error)
throw new Error('Error trashing files on the desktop: ' + error.message);
});
}
_onFileItemSelected(fileItem, keepCurrentSelection, rubberBandSelection, addToSelection) {
if (!keepCurrentSelection && !this._inDrag)
this.clearSelection();
let selection = keepCurrentSelection && rubberBandSelection ? this._currentSelection : this._selection;
if (addToSelection)
selection.add(fileItem);
else
selection.delete(fileItem);
for (let [fileUri, fileItem] of this._fileItems)
fileItem.isSelected = this._currentSelection.has(fileItem) || this._selection.has(fileItem);
}
clearSelection() {
for (let [fileUri, fileItem] of this._fileItems)
fileItem.isSelected = false;
this._selection = new Set();
this._currentSelection = new Set();
}
_getClipboardText(isCopy) {
let action = isCopy ? 'copy' : 'cut';
let text = `x-special/nautilus-clipboard\n${action}\n${
[...this._selection].map(s => s.file.get_uri()).join('\n')
}\n`;
return text;
}
doCopy() {
Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(true));
}
doCut() {
Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(false));
}
destroy() {
if (this._monitorDesktopDir)
this._monitorDesktopDir.cancel();
this._monitorDesktopDir = null;
if (this.settingsId)
Prefs.settings.disconnect(this.settingsId);
this.settingsId = 0;
if (this.gtkSettingsId)
Prefs.gtkSettings.disconnect(this.gtkSettingsId);
this.gtkSettingsId = 0;
if (this._layoutChildrenId)
GLib.source_remove(this._layoutChildrenId);
this._layoutChildrenId = 0;
if (this._deleteChildrenId)
GLib.source_remove(this._deleteChildrenId);
this._deleteChildrenId = 0;
if (this._monitorsChangedId)
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
if (this._stageReleaseEventId)
global.stage.disconnect(this._stageReleaseEventId);
this._stageReleaseEventId = 0;
if (this._rubberBandId)
global.stage.disconnect(this._rubberBandId);
this._rubberBandId = 0;
this._rubberBand.destroy();
if (this._queryFileInfoCancellable)
this._queryFileInfoCancellable.cancel();
Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy());
this._desktopGrids = {}
}
});
function forEachBackgroundManager(func) {
Main.layoutManager._bgManagers.forEach(func);
}

View File

@ -0,0 +1,71 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Prefs = Me.imports.prefs;
const { DesktopManager } = Me.imports.desktopManager;
const DBusUtils = Me.imports.dbusUtils;
var desktopManager = null;
var addBackgroundMenuOrig = null;
var _startupPreparedId;
var lockActivitiesButton = false;
var oldShouldToggleByCornerOrButtonFunction = null;
function init() {
addBackgroundMenuOrig = Main.layoutManager._addBackgroundMenu;
Prefs.initTranslations();
}
function newShouldToggleByCornerOrButton() {
if (lockActivitiesButton)
return false;
else
return oldShouldToggleByCornerOrButtonFunction.bind(Main.overview);
}
function enable() {
// register a new function to allow to lock the Activities button when doing a rubberband selection
oldShouldToggleByCornerOrButtonFunction = Main.overview.shouldToggleByCornerOrButton;
Main.overview.shouldToggleByCornerOrButton = newShouldToggleByCornerOrButton;
// wait until the startup process has ended
if (Main.layoutManager._startingUp)
_startupPreparedId = Main.layoutManager.connect('startup-complete', () => innerEnable(true));
else
innerEnable(false);
}
function innerEnable(disconnectSignal) {
if (disconnectSignal)
Main.layoutManager.disconnect(_startupPreparedId);
DBusUtils.init();
Prefs.init();
Main.layoutManager._addBackgroundMenu = function() {};
desktopManager = new DesktopManager();
}
function disable() {
desktopManager.destroy();
Main.layoutManager._addBackgroundMenu = addBackgroundMenuOrig;
Main.overview.shouldToggleByCornerOrButton = oldShouldToggleByCornerOrButtonFunction;
}

View File

@ -0,0 +1,800 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Gtk = imports.gi.Gtk;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const St = imports.gi.St;
const Pango = imports.gi.Pango;
const Meta = imports.gi.Meta;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Cogl = imports.gi.Cogl;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Background = imports.ui.background;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Extension = Me.imports.extension;
const Prefs = Me.imports.prefs;
const DBusUtils = Me.imports.dbusUtils;
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
const Gettext = imports.gettext.domain('desktop-icons');
const _ = Gettext.gettext;
const DRAG_TRESHOLD = 8;
var S_IXUSR = 0o00100;
var S_IWOTH = 0o00002;
var State = {
NORMAL: 0,
GONE: 1,
};
var FileItem = class {
constructor(file, fileInfo, fileExtra) {
this._fileExtra = fileExtra;
this._loadThumbnailDataCancellable = null;
this._thumbnailScriptWatch = 0;
this._setMetadataCancellable = null;
this._queryFileInfoCancellable = null;
this._isSpecial = this._fileExtra != Prefs.FileType.NONE;
this._file = file;
this._savedCoordinates = null;
let savedCoordinates = fileInfo.get_attribute_as_string('metadata::nautilus-icon-position');
if (savedCoordinates != null)
this._savedCoordinates = savedCoordinates.split(',').map(x => Number(x));
this._state = State.NORMAL;
this.actor = new St.Bin({ visible: true });
this.actor.set_fill(true, true);
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this.actor.set_height(Prefs.get_desired_height(scaleFactor));
this.actor.set_width(Prefs.get_desired_width(scaleFactor));
this.actor._delegate = this;
this.actor.connect('destroy', () => this._onDestroy());
this._container = new St.BoxLayout({ reactive: true,
track_hover: true,
can_focus: true,
style_class: 'file-item',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.FILL,
vertical: true });
this.actor.set_child(this._container);
this._icon = new St.Bin();
this._icon.set_height(Prefs.get_icon_size() * scaleFactor);
this._iconContainer = new St.Bin({ visible: true });
this._iconContainer.child = this._icon;
this._container.add_child(this._iconContainer);
this._label = new St.Label({
style_class: 'name-label'
});
this._container.add_child(this._label);
let clutterText = this._label.get_clutter_text();
/* TODO: Convert to gobject.set for 3.30 */
clutterText.set_line_wrap(true);
clutterText.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
clutterText.set_ellipsize(Pango.EllipsizeMode.END);
this._container.connect('button-press-event', (actor, event) => this._onPressButton(actor, event));
this._container.connect('motion-event', (actor, event) => this._onMotion(actor, event));
this._container.connect('leave-event', (actor, event) => this._onLeave(actor, event));
this._container.connect('button-release-event', (actor, event) => this._onReleaseButton(actor, event));
/* Set the metadata and update relevant UI */
this._updateMetadataFromFileInfo(fileInfo);
if (this._isDesktopFile) {
/* watch for the executable bit being removed or added */
this._monitorDesktopFile = this._file.monitor_file(Gio.FileMonitorFlags.NONE, null);
this._monitorDesktopFileId = this._monitorDesktopFile.connect('changed',
(obj, file, otherFile, eventType) => {
switch(eventType) {
case Gio.FileMonitorEvent.ATTRIBUTE_CHANGED:
this._refreshMetadataAsync(true);
break;
}
});
}
this._createMenu();
this._updateIcon();
this._isSelected = false;
this._primaryButtonPressed = false;
if (this._attributeCanExecute && !this._isDesktopFile)
this._execLine = this.file.get_path();
if (fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) {
// if this icon is the trash, monitor the state of the directory to update the icon
this._trashChanged = false;
this._trashInitializeCancellable = null;
this._scheduleTrashRefreshId = 0;
this._monitorTrashDir = this._file.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null);
this._monitorTrashId = this._monitorTrashDir.connect('changed', (obj, file, otherFile, eventType) => {
switch(eventType) {
case Gio.FileMonitorEvent.DELETED:
case Gio.FileMonitorEvent.MOVED_OUT:
case Gio.FileMonitorEvent.CREATED:
case Gio.FileMonitorEvent.MOVED_IN:
if (this._queryTrashInfoCancellable || this._scheduleTrashRefreshId) {
if (this._scheduleTrashRefreshId)
GLib.source_remove(this._scheduleTrashRefreshId);
this._scheduleTrashRefreshId = Mainloop.timeout_add(200, () => this._refreshTrashIcon());
} else {
this._refreshTrashIcon();
}
break;
}
});
}
Extension.desktopManager.connect('notify::writable-by-others', () => {
if (!this._isDesktopFile)
return;
this._refreshMetadataAsync(true);
});
}
_onDestroy() {
/* Regular file data */
if (this._setMetadataCancellable)
this._setMetadataCancellable.cancel();
if (this._queryFileInfoCancellable)
this._queryFileInfoCancellable.cancel();
/* Thumbnailing */
if (this._thumbnailScriptWatch)
GLib.source_remove(this._thumbnailScriptWatch);
if (this._loadThumbnailDataCancellable)
this._loadThumbnailDataCancellable.cancel();
/* Desktop file */
if (this._monitorDesktopFileId) {
this._monitorDesktopFile.disconnect(this._monitorDesktopFileId);
this._monitorDesktopFile.cancel();
}
/* Trash */
if (this._monitorTrashDir) {
this._monitorTrashDir.disconnect(this._monitorTrashId);
this._monitorTrashDir.cancel();
}
if (this._queryTrashInfoCancellable)
this._queryTrashInfoCancellable.cancel();
if (this._scheduleTrashRefreshId)
GLib.source_remove(this._scheduleTrashRefreshId);
}
_refreshMetadataAsync(rebuild) {
if (this._queryFileInfoCancellable)
this._queryFileInfoCancellable.cancel();
this._queryFileInfoCancellable = new Gio.Cancellable();
this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
this._queryFileInfoCancellable,
(source, result) => {
try {
let newFileInfo = source.query_info_finish(result);
this._queryFileInfoCancellable = null;
this._updateMetadataFromFileInfo(newFileInfo);
if (rebuild) {
this._createMenu();
this._updateIcon();
}
} catch(error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
global.log("Error getting the file info: " + error);
}
});
}
_updateMetadataFromFileInfo(fileInfo) {
this._fileInfo = fileInfo;
let oldLabelText = this._label.text;
this._displayName = fileInfo.get_attribute_as_string('standard::display-name');
this._attributeCanExecute = fileInfo.get_attribute_boolean('access::can-execute');
this._unixmode = fileInfo.get_attribute_uint32('unix::mode')
this._writableByOthers = (this._unixmode & S_IWOTH) != 0;
this._trusted = fileInfo.get_attribute_as_string('metadata::trusted') == 'true';
this._attributeContentType = fileInfo.get_content_type();
this._isDesktopFile = this._attributeContentType == 'application/x-desktop';
if (this._isDesktopFile && this._writableByOthers)
log(`desktop-icons: File ${this._displayName} is writable by others - will not allow launching`);
if (this._isDesktopFile) {
this._desktopFile = Gio.DesktopAppInfo.new_from_filename(this._file.get_path());
if (!this._desktopFile) {
log(`Couldnt parse ${this._displayName} as a desktop file, will treat it as a regular file.`);
this._isDesktopFile = false;
}
}
if (this.displayName != oldLabelText) {
this._label.text = this.displayName;
}
this._fileType = fileInfo.get_file_type();
this._isDirectory = this._fileType == Gio.FileType.DIRECTORY;
this._isSpecial = this._fileExtra != Prefs.FileType.NONE;
this._isHidden = fileInfo.get_is_hidden() | fileInfo.get_is_backup();
this._isSymlink = fileInfo.get_is_symlink();
this._modifiedTime = this._fileInfo.get_attribute_uint64("time::modified");
/*
* This is a glib trick to detect broken symlinks. If a file is a symlink, the filetype
* points to the final file, unless it is broken; thus if the file type is SYMBOLIC_LINK,
* it must be a broken link.
* https://developer.gnome.org/gio/stable/GFile.html#g-file-query-info
*/
this._isBrokenSymlink = this._isSymlink && this._fileType == Gio.FileType.SYMBOLIC_LINK
}
onFileRenamed(file) {
this._file = file;
this._refreshMetadataAsync(false);
}
_updateIcon() {
if (this._fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) {
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
return;
}
let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE);
if (thumbnailFactory.can_thumbnail(this._file.get_uri(),
this._attributeContentType,
this._modifiedTime)) {
let thumbnail = thumbnailFactory.lookup(this._file.get_uri(), this._modifiedTime);
if (thumbnail == null) {
if (!thumbnailFactory.has_valid_failed_thumbnail(this._file.get_uri(),
this._modifiedTime)) {
let argv = [];
argv.push(GLib.build_filenamev([ExtensionUtils.getCurrentExtension().path,
'createThumbnail.js']));
argv.push(this._file.get_path());
let [success, pid] = GLib.spawn_async(null, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, null);
if (this._thumbnailScriptWatch)
GLib.source_remove(this._thumbnailScriptWatch);
this._thumbnailScriptWatch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT,
pid,
(pid, exitCode) => {
if (exitCode == 0)
this._updateIcon();
else
global.log('Failed to generate thumbnail for ' + this._filePath);
GLib.spawn_close_pid(pid);
return false;
}
);
}
} else {
if (this._loadThumbnailDataCancellable)
this._loadThumbnailDataCancellable.cancel();
this._loadThumbnailDataCancellable = new Gio.Cancellable();
let thumbnailFile = Gio.File.new_for_path(thumbnail);
thumbnailFile.load_bytes_async(this._loadThumbnailDataCancellable,
(source, result) => {
try {
let [thumbnailData, etag_out] = source.load_bytes_finish(result);
let thumbnailStream = Gio.MemoryInputStream.new_from_bytes(thumbnailData);
let thumbnailPixbuf = GdkPixbuf.Pixbuf.new_from_stream(thumbnailStream, null);
if (thumbnailPixbuf != null) {
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let thumbnailImage = new Clutter.Image();
thumbnailImage.set_data(thumbnailPixbuf.get_pixels(),
thumbnailPixbuf.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
thumbnailPixbuf.width,
thumbnailPixbuf.height,
thumbnailPixbuf.rowstride
);
let icon = new Clutter.Actor();
icon.set_content(thumbnailImage);
let width = Prefs.get_desired_width(scaleFactor);
let height = Prefs.get_icon_size() * scaleFactor;
let aspectRatio = thumbnailPixbuf.width / thumbnailPixbuf.height;
if ((width / height) > aspectRatio)
icon.set_size(height * aspectRatio, height);
else
icon.set_size(width, width / aspectRatio);
this._icon.child = icon;
}
} catch (error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
global.log('Error while loading thumbnail: ' + error);
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
}
}
}
);
}
}
if (this._isBrokenSymlink) {
this._icon.child = this._createEmblemedStIcon(null, 'text-x-generic');
} else {
if (this.trustedDesktopFile && this._desktopFile.has_key('Icon'))
this._icon.child = this._createEmblemedStIcon(null, this._desktopFile.get_string('Icon'));
else
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
}
}
_refreshTrashIcon() {
if (this._queryTrashInfoCancellable)
this._queryTrashInfoCancellable.cancel();
this._queryTrashInfoCancellable = new Gio.Cancellable();
this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
this._queryTrashInfoCancellable,
(source, result) => {
try {
this._fileInfo = source.query_info_finish(result);
this._queryTrashInfoCancellable = null;
this._updateIcon();
} catch(error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
global.log('Error getting the number of files in the trash: ' + error);
}
});
this._scheduleTrashRefreshId = 0;
return false;
}
get file() {
return this._file;
}
get isHidden() {
return this._isHidden;
}
_createEmblemedStIcon(icon, iconName) {
if (icon == null) {
if (GLib.path_is_absolute(iconName)) {
let iconFile = Gio.File.new_for_commandline_arg(iconName);
icon = new Gio.FileIcon({ file: iconFile });
} else {
icon = Gio.ThemedIcon.new_with_default_fallbacks(iconName);
}
}
let itemIcon = Gio.EmblemedIcon.new(icon, null);
if (this._isSymlink) {
if (this._isBrokenSymlink)
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-unreadable')));
else
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link')));
} else if (this.trustedDesktopFile) {
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link')));
}
return new St.Icon({ gicon: itemIcon,
icon_size: Prefs.get_icon_size()
});
}
doRename() {
if (!this.canRename()) {
log (`Error: ${this.file.get_uri()} cannot be renamed`);
return;
}
this.emit('rename-clicked');
}
doOpen() {
if (this._isBrokenSymlink) {
log(`Error: Cant open ${this.file.get_uri()} because it is a broken symlink.`);
return;
}
if (this.trustedDesktopFile) {
this._desktopFile.launch_uris_as_manager([], null, GLib.SpawnFlags.SEARCH_PATH, null, null);
return;
}
if (this._attributeCanExecute && !this._isDirectory && !this._isDesktopFile) {
if (this._execLine)
Util.spawnCommandLine(this._execLine);
return;
}
Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(),
null, null,
(source, result) => {
try {
Gio.AppInfo.launch_default_for_uri_finish(result);
} catch (e) {
log('Error opening file ' + this.file.get_uri() + ': ' + e.message);
}
}
);
}
_onCopyClicked() {
Extension.desktopManager.doCopy();
}
_onCutClicked() {
Extension.desktopManager.doCut();
}
_onShowInFilesClicked() {
DBusUtils.FreeDesktopFileManagerProxy.ShowItemsRemote([this.file.get_uri()], '',
(result, error) => {
if (error)
log('Error showing file on desktop: ' + error.message);
}
);
}
_onPropertiesClicked() {
DBusUtils.FreeDesktopFileManagerProxy.ShowItemPropertiesRemote([this.file.get_uri()], '',
(result, error) => {
if (error)
log('Error showing properties: ' + error.message);
}
);
}
_onMoveToTrashClicked() {
Extension.desktopManager.doTrash();
}
_onEmptyTrashClicked() {
Extension.desktopManager.doEmptyTrash();
}
get _allowLaunchingText() {
if (this.trustedDesktopFile)
return _("Dont Allow Launching");
return _("Allow Launching");
}
get metadataTrusted() {
return this._trusted;
}
set metadataTrusted(value) {
this._trusted = value;
let info = new Gio.FileInfo();
info.set_attribute_string('metadata::trusted',
value ? 'true' : 'false');
this._file.set_attributes_async(info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_LOW,
null,
(source, result) => {
try {
source.set_attributes_finish(result);
this._refreshMetadataAsync(true);
} catch(e) {
log(`Failed to set metadata::trusted: ${e.message}`);
}
});
}
_onAllowDisallowLaunchingClicked() {
this.metadataTrusted = !this.trustedDesktopFile;
/*
* we're marking as trusted, make the file executable too. note that we
* do not ever remove the executable bit, since we don't know who set
* it.
*/
if (this.metadataTrusted && !this._attributeCanExecute) {
let info = new Gio.FileInfo();
let newUnixMode = this._unixmode | S_IXUSR;
info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, newUnixMode);
this._file.set_attributes_async(info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_LOW,
null,
(source, result) => {
try {
source.set_attributes_finish (result);
} catch(e) {
log(`Failed to set unix mode: ${e.message}`);
}
});
}
}
canRename() {
return !this.trustedDesktopFile && this._fileExtra == Prefs.FileType.NONE;
}
_doOpenWith() {
DBusUtils.openFileWithOtherApplication(this.file.get_path());
}
_getSelectionStyle() {
let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('view', Gtk.StateFlags.SELECTED);
let background_color =
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.6)';
let border_color =
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.8)';
return 'background-color: ' + background_color + ';' +
'border-color: ' + border_color + ';'
}
_createMenu() {
this._menuManager = new PopupMenu.PopupMenuManager({ actor: this.actor });
let side = St.Side.LEFT;
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
side = St.Side.RIGHT;
this._menu = new PopupMenu.PopupMenu(this.actor, 0.5, side);
this._menu.addAction(_('Open'), () => this.doOpen());
switch (this._fileExtra) {
case Prefs.FileType.NONE:
if (!this._isDirectory)
this._actionOpenWith = this._menu.addAction(_('Open With Other Application'), () => this._doOpenWith());
else
this._actionOpenWith = null;
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._actionCut = this._menu.addAction(_('Cut'), () => this._onCutClicked());
this._actionCopy = this._menu.addAction(_('Copy'), () => this._onCopyClicked());
if (this.canRename())
this._menu.addAction(_('Rename…'), () => this.doRename());
this._actionTrash = this._menu.addAction(_('Move to Trash'), () => this._onMoveToTrashClicked());
if (this._isDesktopFile && !Extension.desktopManager.writableByOthers && !this._writableByOthers) {
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._allowLaunchingMenuItem = this._menu.addAction(this._allowLaunchingText,
() => this._onAllowDisallowLaunchingClicked());
}
break;
case Prefs.FileType.USER_DIRECTORY_TRASH:
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._menu.addAction(_('Empty Trash'), () => this._onEmptyTrashClicked());
break;
default:
break;
}
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._menu.addAction(_('Properties'), () => this._onPropertiesClicked());
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._menu.addAction(_('Show in Files'), () => this._onShowInFilesClicked());
if (this._isDirectory && this.file.get_path() != null)
this._actionOpenInTerminal = this._menu.addAction(_('Open in Terminal'), () => this._onOpenTerminalClicked());
this._menuManager.addMenu(this._menu);
Main.layoutManager.uiGroup.add_child(this._menu.actor);
this._menu.actor.hide();
}
_onOpenTerminalClicked () {
DesktopIconsUtil.launchTerminal(this.file.get_path());
}
_onPressButton(actor, event) {
let button = event.get_button();
if (button == 3) {
if (!this.isSelected)
this.emit('selected', false, false, true);
this._menu.toggle();
if (this._actionOpenWith) {
let allowOpenWith = (Extension.desktopManager.getNumberOfSelectedItems() == 1);
this._actionOpenWith.setSensitive(allowOpenWith);
}
let specialFilesSelected = Extension.desktopManager.checkIfSpecialFilesAreSelected();
if (this._actionCut)
this._actionCut.setSensitive(!specialFilesSelected);
if (this._actionCopy)
this._actionCopy.setSensitive(!specialFilesSelected);
if (this._actionTrash)
this._actionTrash.setSensitive(!specialFilesSelected);
return Clutter.EVENT_STOP;
} else if (button == 1) {
if (event.get_click_count() == 1) {
let [x, y] = event.get_coords();
this._primaryButtonPressed = true;
this._buttonPressInitialX = x;
this._buttonPressInitialY = y;
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
if (!this.isSelected) {
this.emit('selected', shiftPressed || controlPressed, false, true);
}
}
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_onLeave(actor, event) {
this._primaryButtonPressed = false;
}
_onMotion(actor, event) {
let [x, y] = event.get_coords();
if (this._primaryButtonPressed) {
let xDiff = x - this._buttonPressInitialX;
let yDiff = y - this._buttonPressInitialY;
let distance = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));
if (distance > DRAG_TRESHOLD) {
// Don't need to track anymore this if we start drag, and also
// avoids reentrance here
this._primaryButtonPressed = false;
let event = Clutter.get_current_event();
let [x, y] = event.get_coords();
Extension.desktopManager.dragStart();
}
}
return Clutter.EVENT_PROPAGATE;
}
_onReleaseButton(actor, event) {
let button = event.get_button();
if (button == 1) {
// primaryButtonPressed is TRUE only if the user has pressed the button
// over an icon, and if (s)he has not started a drag&drop operation
if (this._primaryButtonPressed) {
this._primaryButtonPressed = false;
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
if ((event.get_click_count() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed)
this.doOpen();
this.emit('selected', shiftPressed || controlPressed, false, true);
return Clutter.EVENT_STOP;
}
if ((event.get_click_count() == 2) && (!Prefs.CLICK_POLICY_SINGLE))
this.doOpen();
}
return Clutter.EVENT_PROPAGATE;
}
get savedCoordinates() {
return this._savedCoordinates;
}
_onSetMetadataFileFinished(source, result) {
try {
let [success, info] = source.set_attributes_finish(result);
} catch (error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
log('Error setting metadata to desktop files ', error);
}
}
set savedCoordinates(pos) {
if (this._setMetadataCancellable)
this._setMetadataCancellable.cancel();
this._setMetadataCancellable = new Gio.Cancellable();
this._savedCoordinates = [pos[0], pos[1]];
let info = new Gio.FileInfo();
info.set_attribute_string('metadata::nautilus-icon-position',
`${pos[0]},${pos[1]}`);
this.file.set_attributes_async(info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
this._setMetadataCancellable,
(source, result) => this._onSetMetadataFileFinished(source, result)
);
}
intersectsWith(argX, argY, argWidth, argHeight) {
let rect = new Meta.Rectangle({ x: argX, y: argY, width: argWidth, height: argHeight });
let [containerX, containerY] = this._container.get_transformed_position();
let boundingBox = new Meta.Rectangle({ x: containerX,
y: containerY,
width: this._container.allocation.x2 - this._container.allocation.x1,
height: this._container.allocation.y2 - this._container.allocation.y1 });
let [intersects, _] = rect.intersect(boundingBox);
return intersects;
}
set isSelected(isSelected) {
isSelected = !!isSelected;
if (isSelected == this._isSelected)
return;
if (isSelected) {
this._container.set_style(this._getSelectionStyle());
} else {
this._container.set_style('background-color: transparent');
this._container.set_style('border-color: transparent');
}
this._isSelected = isSelected;
}
get isSelected() {
return this._isSelected;
}
get isSpecial() {
return this._isSpecial;
}
get state() {
return this._state;
}
set state(state) {
if (state == this._state)
return;
this._state = state;
}
get isDirectory() {
return this._isDirectory;
}
get trustedDesktopFile() {
return this._isDesktopFile &&
this._attributeCanExecute &&
this.metadataTrusted &&
!Extension.desktopManager.writableByOthers &&
!this._writableByOthers;
}
get fileName() {
return this._fileInfo.get_name();
}
get displayName() {
if (this.trustedDesktopFile)
return this._desktopFile.get_name();
return this._displayName || null;
}
acceptDrop() {
return Extension.desktopManager.selectionDropOnFileItem(this);
}
};
Signals.addSignalMethods(FileItem.prototype);

View File

@ -0,0 +1,11 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "Add icons to the desktop",
"name": "Desktop Icons",
"shell-version": [
"3.30.0"
],
"url": "",
"uuid": "desktop-icons@csoriano",
"version": 14
}

View File

@ -0,0 +1,159 @@
/* Desktop Icons GNOME Shell extension
*
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
*
* 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 3 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/>.
*/
const Gtk = imports.gi.Gtk;
const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio;
const GioSSS = Gio.SettingsSchemaSource;
const ExtensionUtils = imports.misc.extensionUtils;
const Gettext = imports.gettext;
const Config = imports.misc.config;
var _ = Gettext.domain('desktop-icons').gettext;
const SCHEMA_NAUTILUS = 'org.gnome.nautilus.preferences';
const SCHEMA_GTK = 'org.gtk.Settings.FileChooser';
const SCHEMA = 'org.gnome.shell.extensions.desktop-icons';
const ICON_SIZE = { 'small': 48, 'standard': 64, 'large': 96 };
const ICON_WIDTH = { 'small': 120, 'standard': 128, 'large': 128 };
const ICON_HEIGHT = { 'small': 98, 'standard': 114, 'large': 146 };
var FileType = {
NONE: null,
USER_DIRECTORY_HOME: 'show-home',
USER_DIRECTORY_TRASH: 'show-trash',
}
var nautilusSettings;
var gtkSettings;
var settings;
// This is already in Nautilus settings, so it should not be made tweakable here
var CLICK_POLICY_SINGLE = false;
function initTranslations() {
let extension = ExtensionUtils.getCurrentExtension();
let localedir = extension.dir.get_child('locale');
if (localedir.query_exists(null))
Gettext.bindtextdomain('desktop-icons', localedir.get_path());
else
Gettext.bindtextdomain('desktop-icons', Config.LOCALEDIR);
}
function init() {
let schemaSource = GioSSS.get_default();
let schemaGtk = schemaSource.lookup(SCHEMA_GTK, true);
gtkSettings = new Gio.Settings({ settings_schema: schemaGtk });
let schemaObj = schemaSource.lookup(SCHEMA_NAUTILUS, true);
if (!schemaObj) {
nautilusSettings = null;
} else {
nautilusSettings = new Gio.Settings({ settings_schema: schemaObj });;
nautilusSettings.connect('changed', _onNautilusSettingsChanged);
_onNautilusSettingsChanged();
}
settings = get_schema(SCHEMA);
}
function get_schema(schema) {
let extension = ExtensionUtils.getCurrentExtension();
// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false);
else
schemaSource = GioSSS.get_default();
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension ' + extension.metadata.uuid + '. Please check your installation.');
return new Gio.Settings({ settings_schema: schemaObj });
}
function buildPrefsWidget() {
initTranslations();
let frame = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, border_width: 10, spacing: 10 });
frame.add(buildSelector('icon-size', _("Size for the desktop icons"), { 'small': _("Small"), 'standard': _("Standard"), 'large': _("Large") }));
frame.add(buildSwitcher('show-home', _("Show the personal folder in the desktop")));
frame.add(buildSwitcher('show-trash', _("Show the trash icon in the desktop")));
frame.show_all();
return frame;
}
function buildSwitcher(key, labelText) {
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
let label = new Gtk.Label({ label: labelText, xalign: 0 });
let switcher = new Gtk.Switch({ active: settings.get_boolean(key) });
settings.bind(key, switcher, 'active', 3);
hbox.pack_start(label, true, true, 0);
hbox.add(switcher);
return hbox;
}
function buildSelector(key, labelText, elements) {
let listStore = new Gtk.ListStore();
listStore.set_column_types ([GObject.TYPE_STRING, GObject.TYPE_STRING]);
let schemaKey = settings.settings_schema.get_key(key);
let values = schemaKey.get_range().get_child_value(1).get_child_value(0).get_strv();
for (let val of values) {
let iter = listStore.append();
let visibleText = val;
if (visibleText in elements)
visibleText = elements[visibleText];
listStore.set (iter, [0, 1], [visibleText, val]);
}
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
let label = new Gtk.Label({ label: labelText, xalign: 0 });
let combo = new Gtk.ComboBox({model: listStore});
let rendererText = new Gtk.CellRendererText();
combo.pack_start (rendererText, false);
combo.add_attribute (rendererText, 'text', 0);
combo.set_id_column(1);
settings.bind(key, combo, 'active-id', 3);
hbox.pack_start(label, true, true, 0);
hbox.add(combo);
return hbox;
}
function _onNautilusSettingsChanged() {
CLICK_POLICY_SINGLE = nautilusSettings.get_string('click-policy') == 'single';
}
function get_icon_size() {
// this one doesn't need scaling because Gnome Shell automagically scales the icons
return ICON_SIZE[settings.get_string('icon-size')];
}
function get_desired_width(scale_factor) {
return ICON_WIDTH[settings.get_string('icon-size')] * scale_factor;
}
function get_desired_height(scale_factor) {
return ICON_HEIGHT[settings.get_string('icon-size')] * scale_factor;
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="desktop-icons">
<enum id="org.gnome.shell.extension.desktop-icons.ZoomLevel">
<value value="0" nick="small"/>
<value value="1" nick="standard"/>
<value value="2" nick="large"/>
</enum>
<schema path="/org/gnome/shell/extensions/desktop-icons/" id="org.gnome.shell.extensions.desktop-icons">
<key name="icon-size" enum="org.gnome.shell.extension.desktop-icons.ZoomLevel">
<default>'standard'</default>
<summary>Icon size</summary>
<description>Set the size for the desktop icons.</description>
</key>
<key type="b" name="show-home">
<default>true</default>
<summary>Show personal folder</summary>
<description>Show the personal folder in the desktop.</description>
</key>
<key type="b" name="show-trash">
<default>true</default>
<summary>Show trash icon</summary>
<description>Show the trash icon in the desktop.</description>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,38 @@
.file-item {
padding: 4px;
border: 1px;
margin: 1px;
}
.file-item:hover {
background-color: rgba(238, 238, 238, 0.2);
}
.name-label {
text-shadow: 1px 1px black;
color: white;
text-align: center;
}
.draggable {
background-color: red;
}
.rename-popup {
min-width: 300px;
margin: 6px;
}
.create-folder-dialog-entry {
width: 20em;
margin-bottom: 6px;
}
.create-folder-dialog-label {
padding-bottom: .4em;
}
.create-folder-dialog-error-box {
padding-top: 16px;
spacing: 6px;
}

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<path style="fill:#bebebe" d="M 8 1 C 7.45 1 7 1.45 7 2 C 7 2.03 6.9998 2.0696 7.0098 2.0996 C 6.513 2.1994 6.0506 2.385 5.6191 2.6191 L 13 10 L 13 9 L 13 7 C 13 4.57 11.28 2.5596 8.9902 2.0996 C 9.0002 2.0696 9 2.03 9 2 C 9 1.45 8.55 1 8 1 z M 4.0078 4.0078 C 3.3813 4.8419 3 5.8717 3 7 L 3 11.5 L 1 13.5 L 1 14 L 3 14 L 13 14 L 14 14 L 4.0078 4.0078 z M 6.2695 15 C 6.6295 15.62 7.29 16 8 16 C 8.71 16 9.3705 15.62 9.7305 15 L 6.2695 15 z"/>
<path style="fill:#bebebe" d="M 2.5,1.5 15,14 13.5,15.5 1,3 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 596 B

View File

@ -0,0 +1,84 @@
class Extension {
constructor(dnd, toggle, indicator, remote, audio){
this.dnd = dnd;
this.toggle = toggle;
this.indicator = indicator;
this.remote = remote;
this.audio = audio;
this.enabled = false;
this.toggle.setToggleState(this.dnd.isEnabled());
this.toggle.show();
this.toggle.onToggleStateChanged(() => {
if (this.toggle.getToggleState()){
this.enable();
} else {
this.disable();
}
});
// this.dndID = this.dnd.addStatusListener((dndEnabled) => this._setDND(dndEnabled));
this.remoteID = this.remote.addRemoteListener((dndEnabled) => this._setDND(dndEnabled));
this._setDND(this.remote.getRemote());
}
_setDND(enabled){
if (enabled){
this.enable();
} else {
this.disable();
}
}
/**
* Enable do not disturb mode
*/
enable(){
if (this.enabled){
return;
}
this.enabled = true;
this.dnd.enable();
this.toggle.setToggleState(true);
this.indicator.show();
this.remote.setRemote(true);
this.audio.mute();
}
/**
* Disable do not disturb mode
*/
disable(){
if (!this.enabled){
return;
}
this.enabled = false;
this.dnd.disable();
this.toggle.setToggleState(false);
this.indicator.hide();
this.remote.setRemote(false);
this.audio.unmute();
}
/**
* @return {Boolean} true if it is enabled, otherwise false
*/
isEnabled(){
return this.enabled;
}
/**
* Destroy the extension and its components
*/
destroy(){
this.enabled = false;
// this.dnd.removeStatusListener(this.dndID);
this.remote.removeRemoteListener(this.remoteID);
this.toggle.destroy();
this.indicator.destroy();
this.dnd.disable();
}
}

View File

@ -0,0 +1,76 @@
const BUSY = 2;
const AVAILABLE = 0;
/**
* A class which can enable do not disturb mode
*/
class DoNotDisturb {
/**
* Constructor
* @param {Presence} presence the interface to the Gnome Presence API
*/
constructor(presence){
this.presence = presence;
this.listeners = [];
this.presenceListernerID = -1;
}
/**
* Enable do not disturb mode
*/
enable(){
this.presence.status = BUSY;
}
/**
* Disable do not disturb mode
*/
disable(){
this.presence.status = AVAILABLE;
}
/**
* @return {Boolean} true if do not disturb mode is on, otherwise false
*/
isEnabled(){
return this.presence.status == BUSY;
}
/**
* Add a status listener for when the do not disturb mode is toggled
* @param {[Boolean => ()]} listener the listener to add
* @return {Integer} the ID of the listener
*/
addStatusListener(listener){
if (listener == null){
return -1;
}
if (this.listeners.length == 0){
this.presenceListernerID = this.presence.addStatusListener((status) => {
this.listeners.forEach((fn) => {
fn(status == BUSY);
});
});
}
listener(this.isEnabled());
return this.listeners.push(listener) - 1;
}
/**
* Remove the status listener with the ID
* @param {Integer} id the ID of the status listener
*/
removeStatusListener(id){
if (id < 0 || id >= this.listeners.length){
return;
}
this.listeners.splice(id, 1);
if (this.listeners.length == 0) {
this.presence.removeStatusListener(this.presenceListernerID);
}
}
}

View File

@ -0,0 +1,30 @@
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Settings = Me.imports.settings;
const System = Me.imports.system;
const Widget = Me.imports.widgets;
const DND = Me.imports.doNotDisturb;
const Extension = Me.imports.dndExtension.Extension;
/**
* Called when the extension is loaded.
*/
function init() {}
/**
* Enable the do not disturb extension. Adds all UI elements and monitors the settings object.
*/
function enable() {
var dnd = new DND.DoNotDisturb(new System.GnomePresence());
var toggle = new Widget.DoNotDisturbToggle();
var indicator = new Widget.DoNotDisturbIcon(new Settings.SettingsManager(), new System.NotificationManager());
var remote = new Settings.RemoteAPI();
var audio = new System.AudioManager(new Settings.SettingsManager());
this.extension = new Extension(dnd, toggle, indicator, remote, audio);
}
/**
* Disables the extension. Tears down all UI components.
*/
function disable() {
this.extension.destroy();
}

View File

@ -0,0 +1,33 @@
/* From https://github.com/pop-os/gnome-shell-extension-pop-suspend-button */
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
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/>.
**/
const GLib = imports.gi.GLib;
const Gettext = imports.gettext;
const Config = imports.misc.config;
function initTranslations(extension) {
let localeDir = extension.dir.get_child('locale').get_path();
// Extension installed in .local
if (GLib.file_test(localeDir, GLib.FileTest.EXISTS)) {
Gettext.bindtextdomain('gnome-shell-extension-do-not-disturb', localeDir);
}
// Extension installed system-wide
else {
Gettext.bindtextdomain('gnome-shell-extension-do-not-disturb',
Config.LOCALEDIR);
}
}

View File

@ -0,0 +1,46 @@
msgid ""
msgstr ""
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-09 18:13+0200\n"
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
"Last-Translator: \n"
"Language-Team: German <mike@barkmin.eu>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.2\n"
#: prefs.js:31
msgid "Enabled Icon"
msgstr "Icon anzeigen"
#: prefs.js:31
msgid "Show an indicator icon when do not disturb is enabled."
msgstr "Zeige ein Icon, wenn nicht stören aktiviert ist."
#: prefs.js:38
msgid "Count"
msgstr "Anzahl"
#: prefs.js:39
msgid "Dot"
msgstr "Punkt"
#: prefs.js:40
msgid "Nothing"
msgstr "Nichts"
#: prefs.js:50
msgid "Mute Sounds"
msgstr "Audio stumm schalten"
#: prefs.js:50
msgid "Mutes all sound when do not disturb is enabled."
msgstr "Audio stumm schalten, wenn nicht stören aktiviert ist."
#: widgets.js:40
msgid "Do not disturb"
msgstr "Nicht stören"

View File

@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
"Language-Team: Spanish <kylecorry31@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.2\n"
"Last-Translator: \n"
"Language: es\n"
#: widgets.js:39
msgid "Do not disturb"
msgstr "No interrumpir"

View File

@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
"Language-Team: French <kylecorry31@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.2\n"
"Last-Translator: \n"
"Language: fr\n"
#: widgets.js:36
msgid "Do not disturb"
msgstr "Ne pas déranger"

View File

@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
"Language-Team: Portuguese <kylecorry31@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.2\n"
"Last-Translator: \n"
"Language: pt\n"
#: widgets.js:39
msgid "Do not disturb"
msgstr "Não perturbe"

View File

@ -0,0 +1,20 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "Activate or deactivate do not disturb mode",
"name": "Do Not Disturb",
"original-author": "kylecorry31@gmail.com",
"settings-schema": "org.gnome.shell.extensions.kylecorry31-do-not-disturb",
"shell-version": [
"3.18",
"3.20",
"3.22",
"3.24",
"3.26",
"3.28",
"3.30",
"3.32"
],
"url": "https://github.com/kylecorry31/gnome-shell-extension-do-not-disturb",
"uuid": "donotdisturb@kylecorry31.github.io",
"version": 12
}

View File

@ -0,0 +1,109 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
// Adapted from lockkeys@vaina.lt and https://github.com/pop-os/gnome-shell-extension-pop-suspend-button
const Gtk = imports.gi.Gtk;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Gettext = imports.gettext.domain('gnome-shell-extension-do-not-disturb');
const _ = Gettext.gettext;
const Settings = Me.imports.settings;
const Lib = Me.imports.lib;
function init() {}
/**
* Builds the GTK widget which displays all of the application specific settings.
*
* @returns {Gtk.Box} - The frame to display.
*/
function buildPrefsWidget() {
let settings = new Settings.SettingsManager();
let frame = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
border_width: 10,
margin: 20,
spacing: 8
});
var box = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL
});
frame.add(createSwitch(settings.shouldShowIcon(), (b) => {settings.setShowIcon(b); if (b) { box.show(); } else { box.hide(); } }, _("Enabled Icon"), _("Show an indicator icon when do not disturb is enabled.")));
var indicatorLbl = new Gtk.Label({
label: "Notification Indicator",
xalign: 0
});
var showCountRadio = createRadioButton(settings.showCount, (b) => { settings.showCount = b }, _("Count"));
var showDotRadio = createRadioButton(settings.showDot, (b) => { settings.showDot = b }, _("Dot"), showCountRadio);
var showNothingRadio = createRadioButton(!(settings.showCount || settings.showDot), (b) => { }, _("Nothing"), showCountRadio);
box.pack_start(indicatorLbl, true, true, 0);
box.add(showCountRadio);
box.add(showDotRadio);
box.add(showNothingRadio);
frame.add(box);
frame.add(createSwitch(settings.shouldMuteSound(), (b) => settings.setShouldMuteSound(b), _("Mute Sounds"), _("Mutes all sound when do not disturb is enabled.")));
frame.show_all();
if (!settings.shouldShowIcon()){
box.hide();
}
return frame;
}
function createRadioButton(active, set, text, group){
var widget;
if (group){
widget = Gtk.RadioButton.new_with_label_from_widget(group, text);
widget.set_active(active);
} else {
widget = new Gtk.RadioButton({
active: active,
label: text
});
}
widget.connect('notify::active', function(switch_widget) {
set(switch_widget.active);
});
return widget;
}
/**
* Creates a switch setting.
*
* @param {boolean} active - The starting state of the switch.
* @param {(boolean) => ()} set - The setter function which is passed the value of the switch on state change.
* @param {string} text - The label of the widget.
* @param {string} tooltip - The description text to display on hover.
* @returns {Gtk.Box} - The widget containing the switch and label.
*/
function createSwitch(active, set, text, tooltip) {
let box = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL
});
let label = new Gtk.Label({
label: text,
xalign: 0,
tooltip_text: tooltip
});
let widget = new Gtk.Switch({
active: active
});
widget.connect('notify::active', function(switch_widget) {
set(switch_widget.active);
});
box.pack_start(label, true, true, 0);
box.add(widget);
return box;
}
Lib.initTranslations(Me);

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="org-gnome-shell-extensions-kylecorry31-do-not-disturb">
<schema path="/org/gnome/shell/extensions/kylecorry31-do-not-disturb/" id="org.gnome.shell.extensions.kylecorry31-do-not-disturb">
<key type="b" name="show-icon">
<default>true</default>
<summary>Show an indicator icon when do not disturb is enabled.</summary>
<description></description>
</key>
<key type="b" name="mute-sounds">
<default>false</default>
<summary>Mutes all sound when do not disturb is enabled.</summary>
<description></description>
</key>
<key type="b" name="show-dot">
<default>false</default>
<summary>Shows a dot next to the date when notifications arrive during do not disturb mode.</summary>
<description></description>
</key>
<key type="b" name="show-count">
<default>true</default>
<summary>Displays the number of notifications during do not disturb mode.</summary>
<description></description>
</key>
<key type="b" name="do-not-disturb">
<default>false</default>
<summary>Activate the do not disturb mode.</summary>
<description></description>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,219 @@
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const GLib = imports.gi.GLib;
const Me = imports.misc.extensionUtils.getCurrentExtension();
/**
* A class which handles all interactions with the settings.
*/
class SettingsManager {
/**
* Represents a settings repository, where settings can be modified and read.
* @constructor
*/
constructor() {
this.connections = [];
this._appSettings = _getSettings();
}
/**
* Enable or disable the icon in the system panel when do not disturb mode is enabled.
*
* @param {boolean} showIcon - True if the icon should be shown, false otherwise.
*/
setShowIcon(showIcon) {
this._appSettings.set_boolean('show-icon', showIcon);
}
/**
* Determines if the icon should be shown or not.
*
* @returns {boolean} - True if the icon should be shown when do not disturb is enabled, false otherwise.
*/
shouldShowIcon() {
return this._appSettings.get_boolean('show-icon');
}
/**
* Calls a function when the status of the show icon setting has changed.
*
* @param {() => ()} fn - The function to call when the show icon setting is changed.
*/
onShowIconChanged(fn) {
var id = this._appSettings.connect('changed::show-icon', fn);
this.connections.push(id);
}
/**
* Determines if the sound should be muted when do not disturb is enabled.
*
* @returns {boolean} - True if the sound should be muted when do not disturb is enabled, false otherwise.
*/
shouldMuteSound() {
return this._appSettings.get_boolean('mute-sounds');
}
/**
* Enable or disable the muting of sound when do not disturb mode is enabled.
*
* @param {boolean} muteSound - True if the sound should be muted when do not disturb is enabled, false otherwise.
*/
setShouldMuteSound(muteSound) {
this._appSettings.set_boolean('mute-sounds', muteSound);
}
/**
* Calls a function when the status of the mute sounds setting has changed.
*
* @param {() => ()} fn - The function to call when the mute sounds setting is changed.
*/
onMuteSoundChanged(fn) {
var id = this._appSettings.connect('changed::mute-sounds', fn);
this.connections.push(id);
}
/**
* Sets whether to show the count of notifications or not.
* @param {Boolean} newShowCount True if the notification count should be shown.
*/
set showCount(newShowCount){
this._appSettings.set_boolean('show-count', newShowCount);
}
/**
* Determines whether to show the notification count.
* @return {Boolean} True if the notification count should be shown.
*/
get showCount(){
return this._appSettings.get_boolean('show-count');
}
/**
* Calls a function when the status of the show count setting has changed.
*
* @param {() => ()} fn - The function to call when the show count setting is changed.
*/
onShowCountChanged(fn){
var id = this._appSettings.connect('changed::show-count', fn);
this.connections.push(id);
}
/**
* Sets whether to show an indicator of hidden notifications or not.
* @param {Boolean} newShowDot True if the notification dot should be shown.
*/
set showDot(newShowDot){
this._appSettings.set_boolean('show-dot', newShowDot);
}
/**
* Determines whether to show the notification dot.
* @return {Boolean} True if the notification dot should be shown.
*/
get showDot(){
return this._appSettings.get_boolean('show-dot');
}
/**
* Calls a function when the status of the show dot setting has changed.
*
* @param {() => ()} fn - The function to call when the show dot setting is changed.
*/
onShowDotChanged(fn){
var id = this._appSettings.connect('changed::show-dot', fn);
this.connections.push(id);
}
disconnectAll() {
this.connections.forEach((id) => {
this._appSettings.disconnect(id);
});
this.connections = [];
}
}
class RemoteAPI {
constructor() {
this._appSettings = _getSettings();
this.listeners = [];
this.id = this._appSettings.connect('changed::do-not-disturb', () => {
this.listeners.forEach((fn) => fn(this.getRemote()));
});
}
/**
* Calls a function when the status of the do not disturb setting has changed.
*
* @param {() => ()} listener - The function to call when the do not disturb setting is changed.
*/
addRemoteListener(listener) {
if (listener == null){
return -1;
}
if (this.listeners.length == 0){
this.id = this._appSettings.connect('changed::do-not-disturb', () => {
this.listeners.forEach((fn) => fn(this.getRemote()));
});
}
return this.listeners.push(listener) - 1;
}
removeRemoteListener(id) {
if (id < 0 || id >= this.listeners.length){
return;
}
this.listeners.splice(id, 1);
if (this.listeners.length == 0) {
this._appSettings.disconnect(this.id);
}
}
/**
* @return {Boolean} true if the external do not disturb is on, false otherwise
*/
getRemote() {
return this._appSettings.get_boolean('do-not-disturb');
}
/**
* @param {Boolean} dnd true if the external do not disturb should be on, false otherwise
*/
setRemote(dnd) {
this._appSettings.set_boolean('do-not-disturb', dnd);
}
}
/**
* A helper function to get the application specific settings. Adapted
* from the System76 Pop Suspend Button extension: https://github.com/pop-os/gnome-shell-extension-pop-suspend-button
*
* @returns {Gio.Settings} - The application specific settings object.
*/
function _getSettings() {
let schemaName = 'org.gnome.shell.extensions.kylecorry31-do-not-disturb';
let schemaDir = Me.dir.get_child('schemas').get_path();
// Extension installed in .local
if (GLib.file_test(schemaDir + '/gschemas.compiled', GLib.FileTest.EXISTS)) {
let schemaSource = Gio.SettingsSchemaSource.new_from_directory(schemaDir,
Gio.SettingsSchemaSource.get_default(),
false);
let schema = schemaSource.lookup(schemaName, false);
return new Gio.Settings({
settings_schema: schema
});
}
// Extension installed system-wide
else {
if (Gio.Settings.list_schemas().indexOf(schemaName) == -1)
throw "Schema \"%s\" not found.".format(schemaName);
return new Gio.Settings({
schema: schemaName
});
}
}

View File

@ -0,0 +1,21 @@
.clear-button {
margin: 0 16px;
}
.do-not-disturb:active {
background-color: inherit !important;
}
.do-not-disturb-icon {
color: rgba(255, 255, 255, 0.6);
-st-icon-style: symbolic;
}
.hide-dot {
color: transparent;
width: 0;
}
.notification-count {
color: rgba(255, 255, 255, 0.6);
}

View File

@ -0,0 +1,156 @@
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const GLib = imports.gi.GLib;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const GnomeSession = imports.misc.gnomeSession;
const Settings = Me.imports.settings;
/**
* A class for interacting with the Gnome Presence API.
*/
class GnomePresence {
/**
* Construct a new GnomePresence proxy.
*/
constructor() {
this._presence = new GnomeSession.Presence();
}
/**
* Set the status of GnomePresence.
* @param {number} newStatus A GnomeSession.PresenceStatus status constant to switch to.
*/
set status(newStatus) {
this._presence.SetStatusSync(newStatus);
}
/**
* Get the status of GnomePresence.
* @return {number} The current GnomeSession.PresenceStatus status.
*/
get status() {
return this._presence.status;
}
/**
* Add a listener to the GnomePresence status.
* @param {Function} fn The function to run when the status is changed (passed the current status).
* @return {number} The ID of the listener, used by removeStatusListener.
*/
addStatusListener(fn) {
return this._presence.connectSignal('StatusChanged', (proxy, _sender, [status]) => {
if (proxy.status != status) {
fn(status);
}
});
}
/**
* Remove a status listener to the GnomePresence status.
* @param {number} listenerID The ID of the listener to remove.
*/
removeStatusListener(listenerID) {
this._presence.disconnectSignal(listenerID);
}
}
/**
* A class for managing the audio on Gnome.
*/
class AudioManager {
constructor(settingsManager){
this._settings = settingsManager;
this.shouldMute = this._settings.shouldMuteSound();
this.muted = false;
this._settings.onMuteSoundChanged(() => {
var shouldMute = this._settings.shouldMuteSound();
if (this.muted && shouldMute){
this._internalMute();
} else if (this.muted && !shouldMute){
this._internalUnmute();
}
this.shouldMute = shouldMute;
});
}
_internalMute(){
_runCmd(["amixer", "-q", "-D", "pulse", "sset", "Master", "mute"]);
}
_internalUnmute(){
_runCmd(["amixer", "-q", "-D", "pulse", "sset", "Master", "unmute"]);
}
/**
* Mute the audio stream.
*/
mute() {
this.muted = true;
if (this.shouldMute){
this._internalMute();
}
}
/**
* Unmute the audio stream.
*/
unmute() {
this.muted = false;
if (this.shouldMute){
this._internalUnmute();
}
}
}
class NotificationManager {
constructor() {
}
/**
* Get the current number of notifications in the system tray.
* @return {number} The number of notifications.
*/
get notificationCount(){
var count = 0;
Main.messageTray.getSources().forEach(n => count += n.count);
return count;
}
/**
* Determines if there are any notifications.
* @return {Boolean} True if there are notifications, false otherwise
*/
get hasNotifications(){
return notificationCount() > 0;
}
/**
* Add a listener for when the notification count changes.
* @param {Function} fn The function to call when the notification count changes (passed the current notification count).
* @return {number array} The IDs of the listeners.
*/
addNotificationCountListener(fn){
var id1 = Main.messageTray.connect('source-added', () => fn(this.notificationCount));
var id2 = Main.messageTray.connect('source-removed', () => fn(this.notificationCount));
var id3 = Main.messageTray.connect('queue-changed', () => fn(this.notificationCount));
return [id1, id2, id3];
}
/**
* Remove a notification count listener.
* @param {number array} ids The ID of the listener to remove.
*/
removeNotificationCountListener(ids){
ids.forEach((id) => {
Main.messageTray.disconnect(id);
})
}
}
function _runCmd(cmd) {
GLib.spawn_sync(null, cmd, null, GLib.SpawnFlags.SEARCH_PATH, null);
}

View File

@ -0,0 +1,236 @@
const Lang = imports.lang;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;
const Gtk = imports.gi.Gtk;
const Gettext = imports.gettext.domain('gnome-shell-extension-do-not-disturb');
const _ = Gettext.gettext;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Lib = Me.imports.lib;
/**
* A class which handles the UI of the do not disturb toggle.
*/
class DoNotDisturbToggle {
/**
* Represents a do not disturb toggle in the calendar/notification popup.
* @constructor
*/
constructor() {
this._connections = [];
}
/**
* Shows the do not disturb toggle in the calendar/notification popup.
*/
show() {
this._clearButton = Main.panel.statusArea.dateMenu._messageList._clearButton;
this._calendarBox = this._clearButton.get_parent();
this._clearBox = new St.BoxLayout({
vertical: false,
x_expand: true,
y_expand: false
});
this._disturbToggle = new PopupMenu.PopupSwitchMenuItem(_("Do not disturb"));
this._disturbToggle.actor.add_style_class_name('do-not-disturb');
this._disturbToggle.actor.set_x_expand(true);
this._disturbToggle.actor.track_hover = false;
this._disturbToggle.actor.set_x_align(Clutter.ActorAlign.START);
this._disturbToggle.actor.remove_child(this._disturbToggle.label);
this._disturbToggle.actor.add_child(this._disturbToggle.label);
this._disturbToggle.label.set_y_align(Clutter.ActorAlign.CENTER);
this._disturbToggle.actor.remove_child(this._disturbToggle._ornamentLabel);
this._clearBox.add_actor(this._disturbToggle.actor);
this._clearButton.reparent(this._clearBox);
this._clearButton.add_style_class_name('clear-button');
this._calendarBox.add_actor(this._clearBox);
}
/**
* Destroys all UI elements of the toggle and returns the clear button to its proper location.
*/
destroy() {
if (this._disturbToggle) {
this._connections.forEach((id) => {
this._disturbToggle.disconnect(id);
});
this._connections = [];
this._disturbToggle.destroy();
this._disturbToggle = 0;
}
if (this._clearButton) {
this._clearButton.reparent(this._calendarBox);
this._clearButton.remove_style_class_name('clear-button');
}
if (this._clearBox) {
this._clearBox.destroy();
this._clearBox = 0;
}
}
/**
* Sets the activation state of the toggle.
*
* @param {boolean} state - The state of the toggle: true for on, false for off.
*/
setToggleState(state) {
if (this._disturbToggle) {
this._disturbToggle.setToggleState(state);
}
}
/**
* Get the activation state of the toggle.
*
* @returns {boolean} - True if the toggle is on, false otherwise.
*/
getToggleState() {
if (this._disturbToggle) {
return this._disturbToggle._switch.state;
}
return false;
}
/**
* Calls a function when the toggle state changes.
*
* @param {() => ()} fn - The function to call when the toggle state changes.
*/
onToggleStateChanged(fn) {
if (this._disturbToggle) {
var id = this._disturbToggle.connect("toggled", (item, event) => fn());
this._connections.push(id);
}
}
}
/**
* A class which handles the UI of the do not disturb status icon.
*/
class DoNotDisturbIcon {
/**
* Represents a do not disturb icon in the system status area of the panel.
* @constructor
*/
constructor(settingsManager, notificationCounter) {
this._settings = settingsManager;
this.notificationCounter = notificationCounter;
this._indicatorArea = Main.panel._centerBox; //statusArea.aggregateMenu._indicators;
let icon = "notification-disabled-symbolic";
let fallback = "user-busy-symbolic";
this._enabledIcon = new St.Icon({
icon_name: icon,
fallback_icon_name: fallback,
style_class: 'popup-menu-icon do-not-disturb-icon'
});
this._countLbl = new St.Label();
this.updateCount(0);
this._countLbl.add_style_class_name("notification-count");
this._iconBox = new St.BoxLayout();
this._iconBox.add_actor(this._enabledIcon);
this._iconBox.add_actor(this._countLbl);
this.showDot = this._settings.showDot;
this.showCount = this._settings.showCount;
this.showIcon = this._settings.shouldShowIcon();
this.shown = false;
this.count = this.notificationCounter.notificationCount;
this.updateCount(this.count);
this._settings.onShowIconChanged(() => {
this.showIcon = this._settings.shouldShowIcon();
if (this.shown){
this.hide();
this.show();
}
});
this._settings.onShowCountChanged(() => {
this.showCount = this._settings.showCount;
this.updateCount(this.count);
});
this._settings.onShowDotChanged(() => {
this.showDot = this._settings.showDot;
this.updateCount(this.count);
});
this.notificationListenerID = this.notificationCounter.addNotificationCountListener((count) => {
this.updateCount(count);
});
}
updateCount(newCount){
this.count = newCount;
if (newCount == 0){
this._countLbl.add_style_class_name("hide-dot");
} else {
if (this.showCount){
this._countLbl.set_text("" + newCount);
this._countLbl.remove_style_class_name("hide-dot");
} else if(this.showDot){
this._countLbl.set_text("\u25CF");
this._countLbl.remove_style_class_name("hide-dot");
} else {
this._countLbl.add_style_class_name("hide-dot");
}
}
}
/**
* Shows the status icon.
*/
show() {
if (this.showIcon){
this._indicatorArea.add_child(this._iconBox);
Main.panel.statusArea.dateMenu._indicator.actor.add_style_class_name("hide-dot");
}
this.shown = true;
}
/**
* Hides the status icon.
*/
hide() {
Main.panel.statusArea.dateMenu._indicator.actor.remove_style_class_name("hide-dot");
if (this._iconBox.get_parent()) {
this._indicatorArea.remove_child(this._iconBox);
}
this.shown = false;
}
/**
* Destroys the status icon and removes it from the system status area.
*/
destroy() {
if (this._enabledIcon) {
if (this._iconBox.get_parent()) {
this._indicatorArea.remove_child(this._iconBox);
}
this._countLbl.destroy();
this._countLbl = 0;
this._enabledIcon.destroy();
this._enabledIcon = 0;
this._iconBox.destroy();
this._iconBox = 0;
}
this._settings.disconnectAll();
this.notificationCounter.removeNotificationCountListener(this.notificationListenerID);
}
}
Lib.initTranslations(Me);

Some files were not shown because too many files have changed in this diff Show More