Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
216d932ccc | ||
|
|
f6c8dd38ce | ||
|
|
34717961b6 | ||
|
|
770649ff0a | ||
|
|
35e98e2061 | ||
|
|
557250216a | ||
|
|
bb69c75c70 | ||
|
|
f46cab04cb | ||
|
|
de47b50149 | ||
|
|
d396f3af52 | ||
|
|
2a447787a5 | ||
|
|
16011c0aec |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,4 +4,5 @@ gschemas.compiled
|
||||
zorin-taskbar@zorinos.com*.zip
|
||||
*.mo
|
||||
po/zorin-taskbar.pot
|
||||
Settings.ui.h
|
||||
ui/*.ui.h
|
||||
node_modules/
|
||||
|
||||
34
Makefile
34
Makefile
@@ -1,11 +1,11 @@
|
||||
# Basic Makefile
|
||||
|
||||
UUID = zorin-taskbar@zorinos.com
|
||||
BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md
|
||||
EXTRA_MODULES = appIcons.js convenience.js panel.js panelManager.js proximity.js intellihide.js progress.js panelPositions.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js utils.js Settings.ui
|
||||
EXTRA_IMAGES = show-desktop-symbolic.svg
|
||||
MODULES = src/*.js src/stylesheet.css metadata.json COPYING README.md
|
||||
UI_MODULES = ui/*.ui
|
||||
IMAGES = ./*
|
||||
|
||||
TOLOCALIZE = prefs.js appIcons.js
|
||||
TOLOCALIZE = src/extension.js src/prefs.js src/appIcons.js src/taskbar.js
|
||||
MSGSRC = $(wildcard po/*.po)
|
||||
ifeq ($(strip $(DESTDIR)),)
|
||||
INSTALLBASE = $(HOME)/.local/share/gnome-shell/extensions
|
||||
@@ -18,7 +18,7 @@ INSTALLNAME = zorin-taskbar@zorinos.com
|
||||
# in the metadata and in the generated zip-file.
|
||||
ifdef VERSION
|
||||
else
|
||||
VERSION = 40
|
||||
VERSION = 65
|
||||
endif
|
||||
|
||||
ifdef TARGET
|
||||
@@ -44,11 +44,17 @@ mergepo: potfile
|
||||
msgmerge -U $$l ./po/zorin-taskbar.pot; \
|
||||
done;
|
||||
|
||||
./po/zorin-taskbar.pot: $(TOLOCALIZE) Settings.ui
|
||||
./po/zorin-taskbar.pot: $(TOLOCALIZE)
|
||||
mkdir -p po
|
||||
xgettext -k_ -kN_ -o po/zorin-taskbar.pot --package-name "Zorin Taskbar" $(TOLOCALIZE)
|
||||
intltool-extract --type=gettext/glade Settings.ui
|
||||
xgettext -k_ -kN_ --join-existing -o po/zorin-taskbar.pot Settings.ui.h
|
||||
xgettext -k_ -kN_ -o po/zorin-taskbar.pot --package-name "Zorin Taskbar" $(TOLOCALIZE) --from-code=UTF-8
|
||||
|
||||
for l in $(UI_MODULES) ; do \
|
||||
intltool-extract --type=gettext/glade $$l; \
|
||||
xgettext -k_ -kN_ -o po/zorin-taskbar.pot $$l.h --join-existing --from-code=UTF-8; \
|
||||
rm -rf $$l.h; \
|
||||
done;
|
||||
|
||||
sed -i -e 's/&\#10;/\\n/g' po/zorin-taskbar.pot
|
||||
|
||||
./po/%.mo: ./po/%.po
|
||||
msgfmt -c $< -o $@
|
||||
@@ -71,14 +77,12 @@ zip-file: _build
|
||||
_build: all
|
||||
-rm -fR ./_build
|
||||
mkdir -p _build
|
||||
cp $(BASE_MODULES) $(EXTRA_MODULES) _build
|
||||
|
||||
ifeq ($(TARGET),ego)
|
||||
find _build -name '*.js' -exec sed -i '/\/\/!start-update/,/\/\/!end-update/d' {} +
|
||||
endif
|
||||
cp $(MODULES) _build
|
||||
mkdir -p _build/ui
|
||||
cp $(UI_MODULES) _build/ui
|
||||
|
||||
mkdir -p _build/img
|
||||
cd img ; cp $(EXTRA_IMAGES) ../_build/img/
|
||||
cd img ; cp $(IMAGES) ../_build/img/
|
||||
mkdir -p _build/schemas
|
||||
cp schemas/*.xml _build/schemas/
|
||||
cp schemas/gschemas.compiled _build/schemas/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Zorin Taskbar
|
||||
The official taskbar for Zorin OS.
|
||||
|
||||
Based on the [Dash to Panel](https://github.com/home-sweet-gnome/dash-to-panel) Gnome Shell extension and the [Dash to Dock](https://github.com/micheleg/dash-to-dock) extension by micheleg.
|
||||
Re-based on the [Dash to Panel](https://github.com/home-sweet-gnome/dash-to-panel) GNOME Shell extension. Dash to Panel was initially based on the original version of Zorin Taskbar from 2016, with some code derived from the [Dash to Dock](https://github.com/micheleg/dash-to-dock) extension by micheleg.
|
||||
|
||||
3159
Settings.ui
3159
Settings.ui
File diff suppressed because it is too large
Load Diff
1736
appIcons.js
1736
appIcons.js
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Gettext = imports.gettext;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
/**
|
||||
* 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
|
||||
});
|
||||
}
|
||||
195
debian/changelog
vendored
195
debian/changelog
vendored
@@ -1,3 +1,198 @@
|
||||
gnome-shell-extension-zorin-taskbar (68.4) noble; urgency=medium
|
||||
|
||||
* Applied monitor selection and reset geometry fixes
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Thu, 31 Jul 2025 19:40:58 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.3) noble; urgency=medium
|
||||
|
||||
* Removed code to handle overview startup animation
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Mon, 07 Jul 2025 12:56:41 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.2.3) noble; urgency=medium
|
||||
|
||||
* Fixed logic error when adjusting panel menu buttons
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Fri, 04 Jul 2025 20:36:32 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.2.2) noble; urgency=medium
|
||||
|
||||
* Added settings schema to metadata file
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Fri, 06 Jun 2025 23:03:34 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.2.1) noble; urgency=medium
|
||||
|
||||
* Updated debian control file
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Fri, 23 May 2025 20:05:49 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.2) noble; urgency=medium
|
||||
|
||||
* Corrected code to detect Tiling Shell gap offset
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sat, 10 May 2025 23:52:12 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68.1) noble; urgency=medium
|
||||
|
||||
* Adjusted app icon margins and padding
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Fri, 02 May 2025 14:19:18 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (68) noble; urgency=medium
|
||||
|
||||
* Re-based on upstream version 68
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 29 Apr 2025 20:36:02 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (65.3) noble; urgency=medium
|
||||
|
||||
* Changed activities button default position
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sun, 02 Mar 2025 19:39:09 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (65.2) noble; urgency=medium
|
||||
|
||||
* Separated floating rounded theme from intellihide as an independent
|
||||
styling option
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Thu, 27 Feb 2025 13:57:03 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (65.1) noble; urgency=medium
|
||||
|
||||
* Fixed various bugs
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 26 Feb 2025 12:20:34 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (65) noble; urgency=medium
|
||||
|
||||
* Re-based on upstream version 65
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 25 Feb 2025 22:29:43 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.11) jammy; urgency=medium
|
||||
|
||||
* Updated French translations
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sun, 20 Oct 2024 17:58:19 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.10) jammy; urgency=medium
|
||||
|
||||
* Bug fix for window previews
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Mon, 09 Sep 2024 17:44:12 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.9) jammy; urgency=medium
|
||||
|
||||
* Increased window preview leave timeout to 250ms
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 03 Sep 2024 14:04:19 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.8) jammy; urgency=medium
|
||||
|
||||
* Fixed barrier code
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Mon, 12 Aug 2024 23:38:18 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.7) jammy; urgency=medium
|
||||
|
||||
* Added link to Application Switching settings to set workspace and
|
||||
monitor isolation behaviour
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 15 May 2024 19:21:08 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.6) jammy; urgency=medium
|
||||
|
||||
* Correctly handle desktop icons when calculating proximity
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 27 Feb 2024 20:11:37 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.5) jammy; urgency=medium
|
||||
|
||||
* Fixed show desktop functionality
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 13 Dec 2023 17:22:08 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.4) jammy; urgency=medium
|
||||
|
||||
* Fixed regression with shortcuts overlay
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 13 Dec 2023 16:21:07 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.3) jammy; urgency=medium
|
||||
|
||||
* Adjusted shortcut-num-keys default setting
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 12 Dec 2023 22:21:55 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.2) jammy; urgency=medium
|
||||
|
||||
* Fixed floating theme centering bug
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sun, 10 Dec 2023 18:41:12 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.1) jammy; urgency=medium
|
||||
|
||||
* Moved isolate settings to GNOME Control Center
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sat, 18 Nov 2023 19:23:50 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.0.2) jammy; urgency=medium
|
||||
|
||||
* Removed blue background on favorite apps when dragging app icons
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 01 Nov 2023 12:39:55 +0000
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56.0.1) jammy; urgency=medium
|
||||
|
||||
* Corrected Makefile
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 31 May 2023 01:23:02 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (56) jammy; urgency=medium
|
||||
|
||||
* Re-based on upstream version 56 as at commit
|
||||
9274982189f2d5306afaf29f274d007f0cd12d48
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Wed, 31 May 2023 00:14:18 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.23) focal; urgency=medium
|
||||
|
||||
* Changed default window preview size to 200px
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sat, 16 Oct 2021 12:44:27 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.22) focal; urgency=medium
|
||||
|
||||
* Fixed Spanish translations
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sun, 22 Aug 2021 16:45:29 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.21) focal; urgency=medium
|
||||
|
||||
* Updated Spanish translations
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Sun, 22 Aug 2021 15:49:06 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.20) focal; urgency=medium
|
||||
|
||||
* Corrected translation string
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 27 Jul 2021 22:37:26 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.19) focal; urgency=medium
|
||||
|
||||
* Corrected translation strings
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 27 Jul 2021 22:35:25 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.18) focal; urgency=medium
|
||||
|
||||
* Updated translations
|
||||
|
||||
-- Artyom Zorin <azorin@zoringroup.com> Tue, 27 Jul 2021 22:29:20 +0100
|
||||
|
||||
gnome-shell-extension-zorin-taskbar (40.17) focal; urgency=medium
|
||||
|
||||
* Fixed notification badge sizing on 200% scaled displays
|
||||
|
||||
8
debian/control
vendored
8
debian/control
vendored
@@ -2,12 +2,12 @@ Source: gnome-shell-extension-zorin-taskbar
|
||||
Section: gnome
|
||||
Priority: optional
|
||||
Maintainer: Artyom Zorin <azorin@zoringroup.com>
|
||||
Build-Depends: debhelper-compat (= 12), libglib2.0-bin, zip
|
||||
Standards-Version: 4.5.0
|
||||
Build-Depends: debhelper-compat (= 13), libglib2.0-bin, zip
|
||||
Standards-Version: 4.6.0
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: gnome-shell-extension-zorin-taskbar
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, gnome-shell (>= 3.18)
|
||||
Depends: ${misc:Depends}, gnome-shell (>= 46), gnome-shell (<< 49~)
|
||||
Description: Zorin Taskbar extension
|
||||
A taskbar extension for the Zorin Desktop environment.
|
||||
A taskbar extension for the Zorin OS desktop.
|
||||
|
||||
10
debian/copyright
vendored
10
debian/copyright
vendored
@@ -2,8 +2,8 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: gnome-shell-extension-zorin-taskbar
|
||||
|
||||
Files: *
|
||||
Copyright: 2016-2020, Jason DeRose (https://github.com/jderose9)
|
||||
2016-2020, Zorin OS Technologies Ltd.
|
||||
Copyright: 2016-2025, Jason DeRose (https://github.com/jderose9)
|
||||
2016-2025, Zorin OS Technologies Ltd.
|
||||
License: GPL-2+
|
||||
|
||||
Files: po/cs.po
|
||||
@@ -52,8 +52,12 @@ Files: po/tr.po
|
||||
Copyright: 2018 Serdar Sağlam <teknomobil@yandex.com>
|
||||
License: GPL-2+
|
||||
|
||||
Files: po/sk.po
|
||||
Copyright: 2021 Jose Riha <jose1711@gmail.com>
|
||||
License: GPL-2+
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2017-2020 Jonathan Carter <jcc@debian.org>
|
||||
Copyright: 2017-2021 Jonathan Carter <jcc@debian.org>
|
||||
License: GPL-2+
|
||||
|
||||
License: GPL-2+
|
||||
|
||||
140
extension.js
140
extension.js
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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 Main = imports.ui.main;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
const PanelManager = Me.imports.panelManager;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
const ZORIN_DASH_UUID = 'zorin-dash@zorinos.com';
|
||||
|
||||
let panelManager;
|
||||
let oldDash;
|
||||
let extensionChangedHandler;
|
||||
let disabledZorinDash;
|
||||
let extensionSystem = (Main.extensionManager || imports.ui.extensionSystem);
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations(Utils.TRANSLATION_DOMAIN);
|
||||
|
||||
//create an object that persists until gnome-shell is restarted, even if the extension is disabled
|
||||
Me.persistentStorage = {};
|
||||
}
|
||||
|
||||
function enable() {
|
||||
// The Zorin Dash extension might get enabled after this extension
|
||||
extensionChangedHandler = extensionSystem.connect('extension-state-changed', (data, extension) => {
|
||||
if (extension.uuid === ZORIN_DASH_UUID && extension.state === 1) {
|
||||
_enable();
|
||||
}
|
||||
});
|
||||
|
||||
//create a global object that can emit signals and conveniently expose functionalities to other extensions
|
||||
global.zorinTaskbar = {};
|
||||
Signals.addSignalMethods(global.zorinTaskbar);
|
||||
|
||||
_enable();
|
||||
}
|
||||
|
||||
function _enable() {
|
||||
let zorinDash = Main.extensionManager ?
|
||||
Main.extensionManager.lookup(ZORIN_DASH_UUID) : //gnome-shell >= 3.33.4
|
||||
ExtensionUtils.extensions[ZORIN_DASH_UUID];
|
||||
|
||||
if (zorinDash && zorinDash.stateObj && zorinDash.stateObj.dockManager) {
|
||||
// Disable Zorin Dash
|
||||
let extensionOrder = (extensionSystem.extensionOrder || extensionSystem._extensionOrder);
|
||||
|
||||
Utils.getStageTheme().get_theme().unload_stylesheet(zorinDash.stylesheet);
|
||||
zorinDash.stateObj.disable();
|
||||
disabledZorinDash = true;
|
||||
zorinDash.state = 2; //ExtensionState.DISABLED
|
||||
extensionOrder.splice(extensionOrder.indexOf(ZORIN_DASH_UUID), 1);
|
||||
|
||||
//reset to prevent conflicts with the zorin-dash
|
||||
if (panelManager) {
|
||||
disable(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (panelManager) return; //already initialized
|
||||
|
||||
Me.settings = Convenience.getSettings('org.gnome.shell.extensions.zorin-taskbar');
|
||||
Me.desktopSettings = Convenience.getSettings('org.gnome.desktop.interface');
|
||||
|
||||
panelManager = new PanelManager.dtpPanelManager();
|
||||
|
||||
panelManager.enable();
|
||||
|
||||
Utils.removeKeybinding('open-application-menu');
|
||||
Utils.addKeybinding(
|
||||
'open-application-menu',
|
||||
new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Lang.bind(this, function() {
|
||||
panelManager.primaryPanel.taskbar.popupFocusedAppSecondaryMenu();
|
||||
}),
|
||||
Shell.ActionMode.NORMAL | Shell.ActionMode.POPUP
|
||||
);
|
||||
|
||||
// Pretend I'm the dash: meant to make appgrd swarm animation come from the
|
||||
// right position of the appShowButton.
|
||||
oldDash = Main.overview._dash;
|
||||
Main.overview._dash = panelManager.primaryPanel.taskbar;
|
||||
}
|
||||
|
||||
function disable(reset) {
|
||||
panelManager.disable();
|
||||
Main.overview._dash = oldDash;
|
||||
Me.settings.run_dispose();
|
||||
Me.desktopSettings.run_dispose();
|
||||
|
||||
delete Me.settings;
|
||||
oldDash = null;
|
||||
panelManager = null;
|
||||
|
||||
Utils.removeKeybinding('open-application-menu');
|
||||
Utils.addKeybinding(
|
||||
'open-application-menu',
|
||||
new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Lang.bind(Main.wm, Main.wm._toggleAppMenu),
|
||||
Shell.ActionMode.NORMAL | Shell.ActionMode.POPUP
|
||||
);
|
||||
|
||||
if (!reset) {
|
||||
extensionSystem.disconnect(extensionChangedHandler);
|
||||
delete global.zorinTaskbar;
|
||||
|
||||
// Re-enable Zorin Dash if it was disabled by Zorin Taskbar
|
||||
if (disabledZorinDash && Main.sessionMode.allowExtensions) {
|
||||
(extensionSystem._callExtensionEnable || extensionSystem.enableExtension).call(extensionSystem, ZORIN_DASH_UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
441
intellihide.js
441
intellihide.js
@@ -1,441 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
var GrabHelper = imports.ui.grabHelper;
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const OverviewControls = imports.ui.overviewControls;
|
||||
const PointerWatcher = imports.ui.pointerWatcher;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Panel = Me.imports.panel;
|
||||
const Proximity = Me.imports.proximity;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
var INTELLIHIDE_PRESSURE_THRESHOLD = 100;
|
||||
var INTELLIHIDE_PRESSURE_TIME = 1000;
|
||||
var INTELLIHIDE_ANIMATION_TIME = 200;
|
||||
var INTELLIHIDE_CLOSE_DELAY = 400;
|
||||
var INTELLIHIDE_ENABLE_START_DELAY = 2000;
|
||||
|
||||
//timeout intervals
|
||||
const CHECK_POINTER_MS = 200;
|
||||
const CHECK_GRAB_MS = 400;
|
||||
const POST_ANIMATE_MS = 50;
|
||||
const MIN_UPDATE_MS = 250;
|
||||
|
||||
//timeout names
|
||||
const T1 = 'checkGrabTimeout';
|
||||
const T2 = 'limitUpdateTimeout';
|
||||
const T3 = 'postAnimateTimeout';
|
||||
const T4 = 'panelBoxClipTimeout';
|
||||
|
||||
var SIDE_CONTROLS_ANIMATION_TIME = OverviewControls.SIDE_CONTROLS_ANIMATION_TIME / (OverviewControls.SIDE_CONTROLS_ANIMATION_TIME > 1 ? 1000 : 1);
|
||||
|
||||
var Hold = {
|
||||
NONE: 0,
|
||||
TEMPORARY: 1,
|
||||
PERMANENT: 2
|
||||
};
|
||||
|
||||
var Intellihide = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.Intellihide',
|
||||
|
||||
_init: function(dtpPanel) {
|
||||
this._dtpPanel = dtpPanel;
|
||||
this._panelBox = dtpPanel.panelBox;
|
||||
this._panelManager = dtpPanel.panelManager;
|
||||
this._proximityManager = this._panelManager.proximityManager;
|
||||
this._holdStatus = Hold.NONE;
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler();
|
||||
|
||||
this._intellihideChangedId = Me.settings.connect('changed::intellihide', () => this._changeEnabledStatus());
|
||||
this._intellihideOnlySecondaryChangedId = Me.settings.connect('changed::intellihide-only-secondary', () => this._changeEnabledStatus());
|
||||
|
||||
this.enabled = false;
|
||||
this._changeEnabledStatus();
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this.enabled = true;
|
||||
this._monitor = this._dtpPanel.monitor;
|
||||
this._animationDestination = -1;
|
||||
this._pendingUpdate = false;
|
||||
this._hoveredOut = false;
|
||||
this._windowOverlap = false;
|
||||
this._translationProp = 'translation_' + (this._dtpPanel.checkIfVertical() ? 'x' : 'y');
|
||||
|
||||
this._panelBox.translation_y = 0;
|
||||
this._panelBox.translation_x = 0;
|
||||
|
||||
this._setTrackPanel(true);
|
||||
this._bindGeneralSignals();
|
||||
|
||||
if (Me.settings.get_boolean('intellihide-hide-from-windows')) {
|
||||
this._proximityWatchId = this._proximityManager.createWatch(
|
||||
this._panelBox.get_parent(),
|
||||
Proximity.Mode[Me.settings.get_string('intellihide-behaviour')],
|
||||
0, 0,
|
||||
overlap => {
|
||||
this._windowOverlap = overlap;
|
||||
this._queueUpdatePanelPosition();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this._setRevealMechanism();
|
||||
this._queueUpdatePanelPosition();
|
||||
this._toggleFloatingRoundedTheme();
|
||||
},
|
||||
|
||||
disable: function(reset) {
|
||||
if (this._proximityWatchId) {
|
||||
this._proximityManager.removeWatch(this._proximityWatchId);
|
||||
}
|
||||
|
||||
this._setTrackPanel(false);
|
||||
|
||||
this._signalsHandler.destroy();
|
||||
this._timeoutsHandler.destroy();
|
||||
|
||||
this._removeRevealMechanism();
|
||||
|
||||
this._revealPanel(!reset);
|
||||
|
||||
this.enabled = false;
|
||||
|
||||
if (this._panelBox.has_style_class_name('floating')) {
|
||||
this._panelBox.remove_style_class_name('floating');
|
||||
|
||||
this._resetPanelGeometry();
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Me.settings.disconnect(this._intellihideChangedId);
|
||||
Me.settings.disconnect(this._intellihideOnlySecondaryChangedId);
|
||||
|
||||
if (this.enabled) {
|
||||
this.disable();
|
||||
}
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
this[this._holdStatus & Hold.PERMANENT ? 'release' : 'revealAndHold'](Hold.PERMANENT);
|
||||
},
|
||||
|
||||
revealAndHold: function(holdStatus) {
|
||||
if (this.enabled && !this._holdStatus) {
|
||||
this._revealPanel();
|
||||
}
|
||||
|
||||
this._holdStatus |= holdStatus;
|
||||
},
|
||||
|
||||
release: function(holdStatus) {
|
||||
this._holdStatus -= holdStatus;
|
||||
|
||||
if (this.enabled && !this._holdStatus) {
|
||||
this._queueUpdatePanelPosition();
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.disable(true);
|
||||
this.enable();
|
||||
},
|
||||
|
||||
_toggleFloatingRoundedTheme: function() {
|
||||
if (Me.settings.get_boolean('intellihide-floating-rounded-theme')) {
|
||||
if (!this._panelBox.has_style_class_name('floating'))
|
||||
this._panelBox.add_style_class_name('floating');
|
||||
} else {
|
||||
if (this._panelBox.has_style_class_name('floating'))
|
||||
this._panelBox.remove_style_class_name('floating');
|
||||
}
|
||||
|
||||
this._resetPanelGeometry();
|
||||
},
|
||||
|
||||
_resetPanelGeometry: function() {
|
||||
this._dtpPanel.geom = this._dtpPanel.getGeometry();
|
||||
this._dtpPanel._setPanelGhostSize();
|
||||
this._dtpPanel._setPanelPosition();
|
||||
this._dtpPanel.dynamicTransparency.updateExternalStyle();
|
||||
},
|
||||
|
||||
_changeEnabledStatus: function() {
|
||||
let intellihide = Me.settings.get_boolean('intellihide');
|
||||
let onlySecondary = Me.settings.get_boolean('intellihide-only-secondary');
|
||||
let enabled = intellihide && !(this._dtpPanel.isPrimary && onlySecondary);
|
||||
|
||||
if (this.enabled !== enabled) {
|
||||
this[enabled ? 'enable' : 'disable']();
|
||||
}
|
||||
},
|
||||
|
||||
_bindGeneralSignals: function() {
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
this._dtpPanel.taskbar,
|
||||
'menu-closed',
|
||||
() => this._panelBox.sync_hover()
|
||||
],
|
||||
[
|
||||
Me.settings,
|
||||
[
|
||||
'changed::intellihide-use-pressure',
|
||||
'changed::intellihide-hide-from-windows',
|
||||
'changed::intellihide-behaviour'
|
||||
],
|
||||
() => this.reset()
|
||||
],
|
||||
[
|
||||
Me.settings,
|
||||
[
|
||||
'changed::intellihide-floating-rounded-theme'
|
||||
],
|
||||
() => this._toggleFloatingRoundedTheme()
|
||||
],
|
||||
[
|
||||
this._panelBox,
|
||||
'notify::hover',
|
||||
() => this._onHoverChanged()
|
||||
],
|
||||
[
|
||||
this._dtpPanel.taskbar.previewMenu,
|
||||
'open-state-changed',
|
||||
() => this._queueUpdatePanelPosition()
|
||||
],
|
||||
[
|
||||
Main.overview,
|
||||
[
|
||||
'showing',
|
||||
'hiding'
|
||||
],
|
||||
() => this._queueUpdatePanelPosition()
|
||||
]
|
||||
);
|
||||
},
|
||||
|
||||
_onHoverChanged: function() {
|
||||
this._hoveredOut = !this._panelBox.hover;
|
||||
this._queueUpdatePanelPosition();
|
||||
},
|
||||
|
||||
_setTrackPanel: function(enable) {
|
||||
let trackedIndex = Main.layoutManager._findActor(this._panelBox);
|
||||
let actorData = Main.layoutManager._trackedActors[trackedIndex]
|
||||
|
||||
actorData.affectsStruts = !enable;
|
||||
actorData.trackFullscreen = !enable;
|
||||
|
||||
this._panelBox.track_hover = enable;
|
||||
this._panelBox.reactive = enable;
|
||||
this._panelBox.visible = enable ? enable : this._panelBox.visible;
|
||||
|
||||
Main.layoutManager._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_setRevealMechanism: function() {
|
||||
if (global.display.supports_extended_barriers() && Me.settings.get_boolean('intellihide-use-pressure')) {
|
||||
this._edgeBarrier = this._createBarrier();
|
||||
this._pressureBarrier = new Layout.PressureBarrier(
|
||||
INTELLIHIDE_PRESSURE_THRESHOLD,
|
||||
INTELLIHIDE_PRESSURE_TIME,
|
||||
Shell.ActionMode.NORMAL
|
||||
);
|
||||
this._pressureBarrier.addBarrier(this._edgeBarrier);
|
||||
this._signalsHandler.add([this._pressureBarrier, 'trigger', () => this._queueUpdatePanelPosition(true)]);
|
||||
} else {
|
||||
this._pointerWatch = PointerWatcher.getPointerWatcher()
|
||||
.addWatch(CHECK_POINTER_MS, (x, y) => this._checkMousePointer(x, y));
|
||||
}
|
||||
},
|
||||
|
||||
_removeRevealMechanism: function() {
|
||||
if (this._pointerWatch) {
|
||||
PointerWatcher.getPointerWatcher()._removeWatch(this._pointerWatch);
|
||||
}
|
||||
|
||||
if (this._pressureBarrier) {
|
||||
this._pressureBarrier.destroy();
|
||||
this._edgeBarrier.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_createBarrier: function() {
|
||||
let position = this._dtpPanel.geom.position;
|
||||
let opts = { display: global.display };
|
||||
|
||||
if (this._dtpPanel.checkIfVertical()) {
|
||||
opts.y1 = this._monitor.y;
|
||||
opts.y2 = this._monitor.y + this._monitor.height;
|
||||
opts.x1 = opts.x2 = this._monitor.x;
|
||||
} else {
|
||||
opts.x1 = this._monitor.x;
|
||||
opts.x2 = this._monitor.x + this._monitor.width;
|
||||
opts.y1 = opts.y2 = this._monitor.y;
|
||||
}
|
||||
|
||||
if (position == St.Side.TOP) {
|
||||
opts.directions = Meta.BarrierDirection.POSITIVE_Y;
|
||||
} else if (position == St.Side.BOTTOM) {
|
||||
opts.y1 = opts.y2 = opts.y1 + this._monitor.height;
|
||||
opts.directions = Meta.BarrierDirection.NEGATIVE_Y;
|
||||
} else if (position == St.Side.LEFT) {
|
||||
opts.directions = Meta.BarrierDirection.POSITIVE_X;
|
||||
} else {
|
||||
opts.x1 = opts.x2 = opts.x1 + this._monitor.width;
|
||||
opts.directions = Meta.BarrierDirection.NEGATIVE_X;
|
||||
}
|
||||
|
||||
return new Meta.Barrier(opts);
|
||||
},
|
||||
|
||||
_checkMousePointer: function(x, y) {
|
||||
let position = this._dtpPanel.geom.position;
|
||||
|
||||
if (!this._panelBox.hover && !Main.overview.visible &&
|
||||
((position == St.Side.TOP && y <= this._monitor.y + 1) ||
|
||||
(position == St.Side.BOTTOM && y >= this._monitor.y + this._monitor.height - 1) ||
|
||||
(position == St.Side.LEFT && x <= this._monitor.x + 1) ||
|
||||
(position == St.Side.RIGHT && x >= this._monitor.x + this._monitor.width - 1)) &&
|
||||
((x >= this._monitor.x && x < this._monitor.x + this._monitor.width) &&
|
||||
(y >= this._monitor.y && y < this._monitor.y + this._monitor.height))) {
|
||||
this._queueUpdatePanelPosition(true);
|
||||
}
|
||||
},
|
||||
|
||||
_queueUpdatePanelPosition: function(fromRevealMechanism) {
|
||||
if (!fromRevealMechanism && this._timeoutsHandler.getId(T2) && !Main.overview.visible) {
|
||||
//unless this is a mouse interaction or entering/leaving the overview, limit the number
|
||||
//of updates, but remember to update again when the limit timeout is reached
|
||||
this._pendingUpdate = true;
|
||||
} else if (!this._holdStatus) {
|
||||
this._checkIfShouldBeVisible(fromRevealMechanism) ? this._revealPanel() : this._hidePanel();
|
||||
this._timeoutsHandler.add([T2, MIN_UPDATE_MS, () => this._endLimitUpdate()]);
|
||||
}
|
||||
},
|
||||
|
||||
_endLimitUpdate: function() {
|
||||
if (this._pendingUpdate) {
|
||||
this._pendingUpdate = false;
|
||||
this._queueUpdatePanelPosition();
|
||||
}
|
||||
},
|
||||
|
||||
_checkIfShouldBeVisible: function(fromRevealMechanism) {
|
||||
if (Main.overview.visibleTarget || this._dtpPanel.taskbar.previewMenu.opened ||
|
||||
this._panelBox.get_hover() || this._checkIfGrab()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fromRevealMechanism) {
|
||||
let mouseBtnIsPressed = global.get_pointer()[2] & Clutter.ModifierType.BUTTON1_MASK;
|
||||
|
||||
//the user is trying to reveal the panel
|
||||
if (this._monitor.inFullscreen && !mouseBtnIsPressed) {
|
||||
return Me.settings.get_boolean('intellihide-show-in-fullscreen');
|
||||
}
|
||||
|
||||
return !mouseBtnIsPressed;
|
||||
}
|
||||
|
||||
if (!Me.settings.get_boolean('intellihide-hide-from-windows')) {
|
||||
return this._panelBox.hover;
|
||||
}
|
||||
|
||||
return !this._windowOverlap;
|
||||
},
|
||||
|
||||
_checkIfGrab: function() {
|
||||
if (GrabHelper._grabHelperStack.some(gh => gh._owner == this._dtpPanel.panel.actor)) {
|
||||
//there currently is a grab on a child of the panel, check again soon to catch its release
|
||||
this._timeoutsHandler.add([T1, CHECK_GRAB_MS, () => this._queueUpdatePanelPosition()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
_revealPanel: function(immediate) {
|
||||
if (!this._panelBox.visible) {
|
||||
this._panelBox.visible = true;
|
||||
this._dtpPanel.taskbar._shownInitially = false;
|
||||
}
|
||||
|
||||
this._animatePanel(0, immediate);
|
||||
},
|
||||
|
||||
_hidePanel: function(immediate) {
|
||||
let position = this._dtpPanel.geom.position;
|
||||
let size = this._panelBox[position == St.Side.LEFT || position == St.Side.RIGHT ? 'width' : 'height'];
|
||||
let coefficient = position == St.Side.TOP || position == St.Side.LEFT ? -1 : 1;
|
||||
|
||||
this._animatePanel(size * coefficient, immediate);
|
||||
},
|
||||
|
||||
_animatePanel: function(destination, immediate) {
|
||||
let animating = Utils.isAnimating(this._panelBox, this._translationProp);
|
||||
|
||||
if (!((animating && destination === this._animationDestination) ||
|
||||
(!animating && destination === this._panelBox[this._translationProp]))) {
|
||||
//the panel isn't already at, or animating to the asked destination
|
||||
if (animating) {
|
||||
Utils.stopAnimations(this._panelBox);
|
||||
}
|
||||
|
||||
this._animationDestination = destination;
|
||||
|
||||
if (immediate) {
|
||||
this._panelBox[this._translationProp] = destination;
|
||||
this._panelBox.visible = !destination;
|
||||
} else {
|
||||
let tweenOpts = {
|
||||
//when entering/leaving the overview, use its animation time instead of the one from the settings
|
||||
time: Main.overview.visible ?
|
||||
SIDE_CONTROLS_ANIMATION_TIME :
|
||||
INTELLIHIDE_ANIMATION_TIME * 0.001,
|
||||
//only delay the animation when hiding the panel after the user hovered out
|
||||
delay: destination != 0 && this._hoveredOut ? INTELLIHIDE_CLOSE_DELAY * 0.001 : 0,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: () => {
|
||||
this._panelBox.visible = !destination;
|
||||
Main.layoutManager._queueUpdateRegions();
|
||||
this._timeoutsHandler.add([T3, POST_ANIMATE_MS, () => this._queueUpdatePanelPosition()]);
|
||||
}
|
||||
};
|
||||
|
||||
tweenOpts[this._translationProp] = destination;
|
||||
Utils.animate(this._panelBox, tweenOpts);
|
||||
}
|
||||
}
|
||||
|
||||
this._hoveredOut = false;
|
||||
},
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"extension-id": "zorin-taskbar",
|
||||
"uuid": "zorin-taskbar@zorinos.com",
|
||||
"name": "Zorin Taskbar",
|
||||
"description": "A taskbar extension for the Zorin Desktop environment.",
|
||||
"shell-version": [ "3.18", "3.20", "3.22", "3.24", "3.26", "3.28", "3.30", "3.32", "3.34", "3.36", "3.38" ],
|
||||
"gettext-domain": "zorin-taskbar",
|
||||
"version": 9999
|
||||
"extension-id": "zorin-taskbar",
|
||||
"uuid": "zorin-taskbar@zorinos.com",
|
||||
"name": "Zorin Taskbar",
|
||||
"description": "A taskbar extension for the Zorin OS desktop.",
|
||||
"shell-version": [ "46", "47", "48" ],
|
||||
"gettext-domain": "zorin-taskbar",
|
||||
"settings-schema": "org.gnome.shell.extensions.zorin-taskbar",
|
||||
"version": 68
|
||||
}
|
||||
|
||||
396
overview.js
396
overview.js
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Intellihide = Me.imports.intellihide;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Mainloop = imports.mainloop;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const ViewSelector = imports.ui.viewSelector;
|
||||
|
||||
const Meta = imports.gi.Meta;
|
||||
|
||||
const GS_HOTKEYS_KEY = 'switch-to-application-';
|
||||
const OVERLAY_TIMEOUT = 750;
|
||||
const SHORTCUT_TIMEOUT = 2000;
|
||||
|
||||
//timeout names
|
||||
const T1 = 'swipeEndTimeout';
|
||||
|
||||
var dtpOverview = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.Overview',
|
||||
|
||||
_init: function() {
|
||||
this._numHotkeys = 10;
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler();
|
||||
},
|
||||
|
||||
enable : function(panel) {
|
||||
this._panel = panel;
|
||||
this.taskbar = panel.taskbar;
|
||||
|
||||
this._injectionsHandler = new Utils.InjectionsHandler();
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
|
||||
this._optionalWorkspaceIsolation();
|
||||
this._optionalHotKeys();
|
||||
this._optionalNumberOverlay();
|
||||
this._toggleDash();
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
this._signalsHandler.destroy();
|
||||
this._injectionsHandler.destroy();
|
||||
|
||||
this._toggleDash(true);
|
||||
|
||||
// Remove key bindings
|
||||
this._disableHotKeys();
|
||||
this._disableExtraShortcut();
|
||||
},
|
||||
|
||||
_toggleDash: function(visible) {
|
||||
// To hide the dash, set its width to 1, so it's almost not taken into account by code
|
||||
// calculaing the reserved space in the overview. The reason to keep it at 1 is
|
||||
// to allow its visibility change to trigger an allocaion of the appGrid which
|
||||
// in turn is triggergin the appsIcon spring animation, required when no other
|
||||
// actors has this effect, i.e in horizontal mode and without the workspaceThumnails
|
||||
// 1 static workspace only)
|
||||
|
||||
if (visible === undefined) {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
let visibilityFunc = visible ? 'show' : 'hide';
|
||||
let width = visible ? -1 : 1;
|
||||
let overviewControls = Main.overview._overview._controls || Main.overview._controls;
|
||||
|
||||
overviewControls.dash.actor[visibilityFunc]();
|
||||
overviewControls.dash.actor.set_width(width);
|
||||
|
||||
// This force the recalculation of the icon size
|
||||
overviewControls.dash._maxHeight = -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Isolate overview to open new windows for inactive apps
|
||||
*/
|
||||
_optionalWorkspaceIsolation: function() {
|
||||
let label = 'optionalWorkspaceIsolation';
|
||||
|
||||
this._signalsHandler.add([
|
||||
Me.settings,
|
||||
'changed::isolate-workspaces',
|
||||
Lang.bind(this, function() {
|
||||
this._panel.panelManager.allPanels.forEach(p => p.taskbar.resetAppIcons());
|
||||
|
||||
if (Me.settings.get_boolean('isolate-workspaces'))
|
||||
Lang.bind(this, enable)();
|
||||
else
|
||||
Lang.bind(this, disable)();
|
||||
})
|
||||
]);
|
||||
|
||||
if (Me.settings.get_boolean('isolate-workspaces'))
|
||||
Lang.bind(this, enable)();
|
||||
|
||||
function enable() {
|
||||
this._injectionsHandler.removeWithLabel(label);
|
||||
|
||||
this._injectionsHandler.addWithLabel(label, [
|
||||
Shell.App.prototype,
|
||||
'activate',
|
||||
IsolatedOverview
|
||||
]);
|
||||
|
||||
this._signalsHandler.removeWithLabel(label);
|
||||
|
||||
this._signalsHandler.addWithLabel(label, [
|
||||
global.window_manager,
|
||||
'switch-workspace',
|
||||
() => this._panel.panelManager.allPanels.forEach(p => p.taskbar.handleIsolatedWorkspaceSwitch())
|
||||
]);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
this._signalsHandler.removeWithLabel(label);
|
||||
this._injectionsHandler.removeWithLabel(label);
|
||||
}
|
||||
|
||||
function IsolatedOverview() {
|
||||
// These lines take care of Nautilus for icons on Desktop
|
||||
let activeWorkspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace();
|
||||
let windows = this.get_windows().filter(w => w.get_workspace().index() == activeWorkspace.index());
|
||||
|
||||
if (windows.length > 0 &&
|
||||
(!(windows.length == 1 && windows[0].skip_taskbar) ||
|
||||
this.is_on_workspace(activeWorkspace)))
|
||||
return Main.activateWindow(windows[0]);
|
||||
|
||||
return this.open_new_window(-1);
|
||||
}
|
||||
},
|
||||
|
||||
// Hotkeys
|
||||
_activateApp: function(appIndex) {
|
||||
let seenApps = {};
|
||||
let apps = [];
|
||||
|
||||
this.taskbar._getAppIcons().forEach(function(appIcon) {
|
||||
if (!seenApps[appIcon.app]) {
|
||||
apps.push(appIcon);
|
||||
}
|
||||
|
||||
seenApps[appIcon.app] = (seenApps[appIcon.app] || 0) + 1;
|
||||
});
|
||||
|
||||
this._showOverlay();
|
||||
|
||||
if (appIndex < apps.length) {
|
||||
let appIcon = apps[appIndex];
|
||||
let seenAppCount = seenApps[appIcon.app];
|
||||
let windowCount = appIcon.window || appIcon._hotkeysCycle ? seenAppCount : appIcon._nWindows;
|
||||
|
||||
if (Me.settings.get_boolean('shortcut-previews') && windowCount > 1 &&
|
||||
!(Clutter.get_current_event().get_state() & ~(Clutter.ModifierType.MOD1_MASK | Clutter.ModifierType.MOD4_MASK))) { //ignore the alt (MOD1_MASK) and super key (MOD4_MASK)
|
||||
if (this._hotkeyPreviewCycleInfo && this._hotkeyPreviewCycleInfo.appIcon != appIcon) {
|
||||
this._endHotkeyPreviewCycle();
|
||||
}
|
||||
|
||||
if (!this._hotkeyPreviewCycleInfo) {
|
||||
this._hotkeyPreviewCycleInfo = {
|
||||
appIcon: appIcon,
|
||||
currentWindow: appIcon.window,
|
||||
keyFocusOutId: appIcon.actor.connect('key-focus-out', () => appIcon.actor.grab_key_focus()),
|
||||
capturedEventId: global.stage.connect('captured-event', (actor, e) => {
|
||||
if (e.type() == Clutter.EventType.KEY_RELEASE && e.get_key_symbol() == (Clutter.KEY_Super_L || Clutter.Super_L)) {
|
||||
this._endHotkeyPreviewCycle(true);
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
})
|
||||
};
|
||||
|
||||
appIcon._hotkeysCycle = appIcon.window;
|
||||
appIcon.window = null;
|
||||
appIcon._previewMenu.open(appIcon);
|
||||
appIcon.actor.grab_key_focus();
|
||||
}
|
||||
|
||||
appIcon._previewMenu.focusNext();
|
||||
} else {
|
||||
// Activate with button = 1, i.e. same as left click
|
||||
let button = 1;
|
||||
this._endHotkeyPreviewCycle();
|
||||
appIcon.activate(button, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_endHotkeyPreviewCycle: function(focusWindow) {
|
||||
if (this._hotkeyPreviewCycleInfo) {
|
||||
global.stage.disconnect(this._hotkeyPreviewCycleInfo.capturedEventId);
|
||||
this._hotkeyPreviewCycleInfo.appIcon.actor.disconnect(this._hotkeyPreviewCycleInfo.keyFocusOutId);
|
||||
|
||||
if (focusWindow) {
|
||||
this._hotkeyPreviewCycleInfo.appIcon._previewMenu.activateFocused();
|
||||
}
|
||||
|
||||
this._hotkeyPreviewCycleInfo.appIcon.window = this._hotkeyPreviewCycleInfo.currentWindow;
|
||||
delete this._hotkeyPreviewCycleInfo.appIcon._hotkeysCycle;
|
||||
this._hotkeyPreviewCycleInfo = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_optionalHotKeys: function() {
|
||||
this._hotKeysEnabled = false;
|
||||
if (Me.settings.get_boolean('hot-keys'))
|
||||
this._enableHotKeys();
|
||||
|
||||
this._signalsHandler.add([
|
||||
Me.settings,
|
||||
'changed::hot-keys',
|
||||
Lang.bind(this, function() {
|
||||
if (Me.settings.get_boolean('hot-keys'))
|
||||
Lang.bind(this, this._enableHotKeys)();
|
||||
else
|
||||
Lang.bind(this, this._disableHotKeys)();
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
_resetHotkeys: function() {
|
||||
this._disableHotKeys();
|
||||
this._enableHotKeys();
|
||||
},
|
||||
|
||||
_enableHotKeys: function() {
|
||||
if (this._hotKeysEnabled)
|
||||
return;
|
||||
|
||||
//3.32 introduced app hotkeys, disable them to prevent conflicts
|
||||
if (Main.wm._switchToApplication) {
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Utils.removeKeybinding(GS_HOTKEYS_KEY + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup keyboard bindings for taskbar elements
|
||||
let shortcutNumKeys = Me.settings.get_string('shortcut-num-keys');
|
||||
let bothNumKeys = shortcutNumKeys == 'BOTH';
|
||||
let keys = [];
|
||||
|
||||
if (bothNumKeys || shortcutNumKeys == 'NUM_ROW') {
|
||||
keys.push('app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-'); // Regular numbers
|
||||
}
|
||||
|
||||
if (bothNumKeys || shortcutNumKeys == 'NUM_KEYPAD') {
|
||||
keys.push('app-hotkey-kp-', 'app-shift-hotkey-kp-', 'app-ctrl-hotkey-kp-'); // Key-pad numbers
|
||||
}
|
||||
|
||||
keys.forEach( function(key) {
|
||||
for (let i = 0; i < this._numHotkeys; i++) {
|
||||
let appNum = i;
|
||||
|
||||
Utils.addKeybinding(key + (i + 1), Me.settings, () => this._activateApp(appNum));
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._hotKeysEnabled = true;
|
||||
|
||||
if (Me.settings.get_string('hotkeys-overlay-combo') === 'ALWAYS')
|
||||
this.taskbar.toggleNumberOverlay(true);
|
||||
},
|
||||
|
||||
_disableHotKeys: function() {
|
||||
if (!this._hotKeysEnabled)
|
||||
return;
|
||||
|
||||
let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-', // Regular numbers
|
||||
'app-hotkey-kp-', 'app-shift-hotkey-kp-', 'app-ctrl-hotkey-kp-']; // Key-pad numbers
|
||||
keys.forEach( function(key) {
|
||||
for (let i = 0; i < this._numHotkeys; i++) {
|
||||
Utils.removeKeybinding(key + (i + 1));
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (Main.wm._switchToApplication) {
|
||||
let gsSettings = new Gio.Settings({ schema_id: imports.ui.windowManager.SHELL_KEYBINDINGS_SCHEMA });
|
||||
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Utils.addKeybinding(GS_HOTKEYS_KEY + i, gsSettings, Main.wm._switchToApplication.bind(Main.wm));
|
||||
}
|
||||
}
|
||||
|
||||
this._hotKeysEnabled = false;
|
||||
|
||||
this.taskbar.toggleNumberOverlay(false);
|
||||
},
|
||||
|
||||
_optionalNumberOverlay: function() {
|
||||
// Enable extra shortcut
|
||||
if (Me.settings.get_boolean('hot-keys'))
|
||||
this._enableExtraShortcut();
|
||||
|
||||
this._signalsHandler.add([
|
||||
Me.settings,
|
||||
'changed::hot-keys',
|
||||
Lang.bind(this, this._checkHotkeysOptions)
|
||||
], [
|
||||
Me.settings,
|
||||
'changed::hotkeys-overlay-combo',
|
||||
Lang.bind(this, function() {
|
||||
if (Me.settings.get_boolean('hot-keys') && Me.settings.get_string('hotkeys-overlay-combo') === 'ALWAYS')
|
||||
this.taskbar.toggleNumberOverlay(true);
|
||||
else
|
||||
this.taskbar.toggleNumberOverlay(false);
|
||||
})
|
||||
], [
|
||||
Me.settings,
|
||||
'changed::shortcut-num-keys',
|
||||
() => this._resetHotkeys()
|
||||
]);
|
||||
},
|
||||
|
||||
_checkHotkeysOptions: function() {
|
||||
if (Me.settings.get_boolean('hot-keys'))
|
||||
this._enableExtraShortcut();
|
||||
else
|
||||
this._disableExtraShortcut();
|
||||
},
|
||||
|
||||
_enableExtraShortcut: function() {
|
||||
Utils.addKeybinding('shortcut', Me.settings, () => this._showOverlay(true));
|
||||
},
|
||||
|
||||
_disableExtraShortcut: function() {
|
||||
Utils.removeKeybinding('shortcut');
|
||||
},
|
||||
|
||||
_showOverlay: function(overlayFromShortcut) {
|
||||
//wait for intellihide timeout initialization
|
||||
if (!this._panel.intellihide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart the counting if the shortcut is pressed again
|
||||
if (this._numberOverlayTimeoutId) {
|
||||
Mainloop.source_remove(this._numberOverlayTimeoutId);
|
||||
this._numberOverlayTimeoutId = 0;
|
||||
}
|
||||
|
||||
let hotkey_option = Me.settings.get_string('hotkeys-overlay-combo');
|
||||
|
||||
if (hotkey_option === 'NEVER')
|
||||
return;
|
||||
|
||||
if (hotkey_option === 'TEMPORARILY' || overlayFromShortcut)
|
||||
this.taskbar.toggleNumberOverlay(true);
|
||||
|
||||
this._panel.intellihide.revealAndHold(Intellihide.Hold.TEMPORARY);
|
||||
|
||||
let timeout = OVERLAY_TIMEOUT;
|
||||
|
||||
if (overlayFromShortcut) {
|
||||
timeout = SHORTCUT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Hide the overlay/dock after the timeout
|
||||
this._numberOverlayTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, function() {
|
||||
this._numberOverlayTimeoutId = 0;
|
||||
|
||||
if (hotkey_option != 'ALWAYS') {
|
||||
this.taskbar.toggleNumberOverlay(false);
|
||||
}
|
||||
|
||||
this._panel.intellihide.release(Intellihide.Hold.TEMPORARY);
|
||||
}));
|
||||
}
|
||||
});
|
||||
927
panelManager.js
927
panelManager.js
@@ -1,927 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
*
|
||||
* Code to re-anchor the panel was taken from Thoma5 BottomPanel:
|
||||
* https://github.com/Thoma5/gnome-shell-extension-bottompanel
|
||||
*
|
||||
* Pattern for moving clock based on Frippery Move Clock by R M Yorston
|
||||
* http://frippery.org/extensions/
|
||||
*
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Overview = Me.imports.overview;
|
||||
const Panel = Me.imports.panel;
|
||||
const Pos = Me.imports.panelPositions;
|
||||
const Proximity = Me.imports.proximity;
|
||||
const Taskbar = Me.imports.taskbar;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const Lang = imports.lang;
|
||||
const Gi = imports._gi;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Dash = imports.ui.dash;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const Layout = imports.ui.layout;
|
||||
const WM = imports.ui.windowManager;
|
||||
const WorkspacesView = imports.ui.workspacesView;
|
||||
|
||||
var dtpPanelManager = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.PanelManager',
|
||||
|
||||
_init: function() {
|
||||
this.overview = new Overview.dtpOverview();
|
||||
this.panelsElementPositions = {};
|
||||
|
||||
this._saveMonitors();
|
||||
|
||||
Utils.getAppDisplayViews().forEach(v => {
|
||||
Utils.wrapActor(v.view);
|
||||
Utils.wrapActor(v.view._grid);
|
||||
});
|
||||
},
|
||||
|
||||
enable: function(reset) {
|
||||
this.panelPositions = Pos.getSettingsPositions(Me.settings, 'panel-positions');
|
||||
this.dtpPrimaryMonitor = Main.layoutManager.primaryMonitor; // The real primary monitor should always have the main panel
|
||||
this.proximityManager = new Proximity.ProximityManager();
|
||||
|
||||
Utils.wrapActor(Main.panel);
|
||||
Utils.wrapActor(Main.overview.dash || 0);
|
||||
|
||||
this.primaryPanel = this._createPanel(this.dtpPrimaryMonitor, false);
|
||||
this.allPanels = [ this.primaryPanel ];
|
||||
|
||||
this.overview.enable(this.primaryPanel);
|
||||
|
||||
if (Me.settings.get_boolean('multi-monitors')) {
|
||||
Main.layoutManager.monitors.filter(m => m != this.dtpPrimaryMonitor).forEach(m => {
|
||||
this.allPanels.push(this._createPanel(m, true));
|
||||
});
|
||||
}
|
||||
|
||||
global.zorinTaskbar.panels = this.allPanels;
|
||||
global.zorinTaskbar.emit('panels-created');
|
||||
|
||||
this.allPanels.forEach(p => {
|
||||
let panelPosition = p.getPosition();
|
||||
let leftOrRight = (panelPosition == St.Side.LEFT || panelPosition == St.Side.RIGHT);
|
||||
|
||||
p.panelBox.set_size(
|
||||
leftOrRight ? -1 : p.geom.w + p.geom.lrPadding,
|
||||
leftOrRight ? p.geom.h + p.geom.tbPadding : -1
|
||||
);
|
||||
|
||||
this._findPanelMenuButtons(p.panelBox).forEach(pmb => this._adjustPanelMenuButton(pmb, p.monitor, panelPosition));
|
||||
|
||||
p.taskbar.iconAnimator.start();
|
||||
});
|
||||
|
||||
//in 3.32, BoxPointer now inherits St.Widget
|
||||
if (BoxPointer.BoxPointer.prototype.vfunc_get_preferred_height) {
|
||||
let panelManager = this;
|
||||
|
||||
Utils.hookVfunc(BoxPointer.BoxPointer.prototype, 'get_preferred_height', function(forWidth) {
|
||||
let alloc = { min_size: 0, natural_size: 0 };
|
||||
|
||||
[alloc.min_size, alloc.natural_size] = this.vfunc_get_preferred_height(forWidth);
|
||||
|
||||
return panelManager._getBoxPointerPreferredHeight(this, alloc);
|
||||
});
|
||||
}
|
||||
|
||||
this._updatePanelElementPositions();
|
||||
this.setFocusedMonitor(this.dtpPrimaryMonitor);
|
||||
|
||||
if (this.primaryPanel.checkIfVertical()) {
|
||||
Main.wm._getPositionForDirection = newGetPositionForDirection;
|
||||
}
|
||||
|
||||
if (reset) return;
|
||||
|
||||
this._oldViewSelectorAnimateIn = Main.overview.viewSelector._animateIn;
|
||||
Main.overview.viewSelector._animateIn = Lang.bind(this.primaryPanel, newViewSelectorAnimateIn);
|
||||
this._oldViewSelectorAnimateOut = Main.overview.viewSelector._animateOut;
|
||||
Main.overview.viewSelector._animateOut = Lang.bind(this.primaryPanel, newViewSelectorAnimateOut);
|
||||
|
||||
if (Config.PACKAGE_VERSION > '3.35.1') {
|
||||
this._oldDoSpringAnimation = AppDisplay.BaseAppView.prototype._doSpringAnimation;
|
||||
AppDisplay.BaseAppView.prototype._doSpringAnimation = newDoSpringAnimation;
|
||||
}
|
||||
|
||||
this._oldUpdatePanelBarrier = Main.layoutManager._updatePanelBarrier;
|
||||
Main.layoutManager._updatePanelBarrier = (panel) => {
|
||||
let panelUpdates = panel ? [panel] : this.allPanels;
|
||||
|
||||
panelUpdates.forEach(p => newUpdatePanelBarrier.call(Main.layoutManager, p));
|
||||
};
|
||||
Main.layoutManager._updatePanelBarrier();
|
||||
|
||||
this._oldUpdateHotCorners = Main.layoutManager._updateHotCorners;
|
||||
Main.layoutManager._updateHotCorners = Lang.bind(Main.layoutManager, newUpdateHotCorners);
|
||||
Main.layoutManager._updateHotCorners();
|
||||
|
||||
if (Main.layoutManager._interfaceSettings) {
|
||||
this._enableHotCornersId = Main.layoutManager._interfaceSettings.connect('changed::enable-hot-corners', () => Main.layoutManager._updateHotCorners());
|
||||
}
|
||||
|
||||
this._oldOverviewRelayout = Main.overview._relayout;
|
||||
Main.overview._relayout = Lang.bind(Main.overview, this._newOverviewRelayout);
|
||||
|
||||
this._oldUpdateWorkspacesViews = Main.overview.viewSelector._workspacesDisplay._updateWorkspacesViews;
|
||||
Main.overview.viewSelector._workspacesDisplay._updateWorkspacesViews = Lang.bind(Main.overview.viewSelector._workspacesDisplay, this._newUpdateWorkspacesViews);
|
||||
|
||||
this._oldGetShowAppsButton = Main.overview.getShowAppsButton;
|
||||
Main.overview.getShowAppsButton = this._newGetShowAppsButton.bind(this);
|
||||
|
||||
this._needsDashItemContainerAllocate = !Dash.DashItemContainer.prototype.hasOwnProperty('vfunc_allocate');
|
||||
|
||||
if (this._needsDashItemContainerAllocate) {
|
||||
Utils.hookVfunc(Dash.DashItemContainer.prototype, 'allocate', this._newDashItemContainerAllocate);
|
||||
}
|
||||
|
||||
// Since Gnome 3.8 dragging an app without having opened the overview before cause the attemp to
|
||||
//animate a null target since some variables are not initialized when the viewSelector is created
|
||||
if(Main.overview.viewSelector._activePage == null)
|
||||
Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage;
|
||||
|
||||
LookingGlass.LookingGlass.prototype._oldResize = LookingGlass.LookingGlass.prototype._resize;
|
||||
LookingGlass.LookingGlass.prototype._resize = _newLookingGlassResize;
|
||||
|
||||
LookingGlass.LookingGlass.prototype._oldOpen = LookingGlass.LookingGlass.prototype.open;
|
||||
LookingGlass.LookingGlass.prototype.open = _newLookingGlassOpen;
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
|
||||
if (Config.PACKAGE_VERSION > '3.35.9') {
|
||||
let currentAppsView;
|
||||
|
||||
this._oldAnimateIconPosition = IconGrid.animateIconPosition;
|
||||
IconGrid.animateIconPosition = newAnimateIconPosition.bind(this);
|
||||
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
Utils.DisplayWrapper.getScreen(),
|
||||
'window-entered-monitor',
|
||||
() => this._needsIconAllocate = 1
|
||||
]
|
||||
);
|
||||
|
||||
Utils.getAppDisplayViews().forEach(v => {
|
||||
if (!v.control || v.control.has_style_pseudo_class('checked')) {
|
||||
currentAppsView = v;
|
||||
}
|
||||
|
||||
if (v.control) {
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
v.control,
|
||||
'clicked',
|
||||
() => {
|
||||
this._needsIconAllocate = currentAppsView != v;
|
||||
currentAppsView = v;
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
v.view,
|
||||
'notify::visible',
|
||||
() => this._needsIconAllocate = !(currentAppsView != v && !v.view.visible)
|
||||
],
|
||||
[
|
||||
v.view._grid,
|
||||
'animation-done',
|
||||
() => this._needsIconAllocate = 0
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//listen settings
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
Me.settings,
|
||||
[
|
||||
'changed::multi-monitors',
|
||||
'changed::isolate-monitors',
|
||||
'changed::panel-positions'
|
||||
],
|
||||
() => this._reset()
|
||||
],
|
||||
[
|
||||
Me.settings,
|
||||
'changed::panel-element-positions',
|
||||
() => this._updatePanelElementPositions()
|
||||
],
|
||||
[
|
||||
Me.settings,
|
||||
'changed::intellihide-key-toggle-text',
|
||||
() => this._setKeyBindings(true)
|
||||
],
|
||||
[
|
||||
Utils.DisplayWrapper.getMonitorManager(),
|
||||
'monitors-changed',
|
||||
() => {
|
||||
if (Main.layoutManager.primaryMonitor) {
|
||||
this._saveMonitors();
|
||||
this._reset();
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
Panel.panelBoxes.forEach(c => this._signalsHandler.add(
|
||||
[Main.panel[c], 'actor-added', (parent, child) => this._adjustPanelMenuButton(this._getPanelMenuButton(child), this.primaryPanel.monitor, this.primaryPanel.getPosition())]
|
||||
));
|
||||
|
||||
this._setKeyBindings(true);
|
||||
},
|
||||
|
||||
disable: function(reset) {
|
||||
this.overview.disable();
|
||||
this.proximityManager.destroy();
|
||||
|
||||
this.allPanels.forEach(p => {
|
||||
p.taskbar.iconAnimator.pause();
|
||||
|
||||
this._findPanelMenuButtons(p.panelBox).forEach(pmb => {
|
||||
if (pmb.menu._boxPointer._dtpGetPreferredHeightId) {
|
||||
pmb.menu._boxPointer._container.disconnect(pmb.menu._boxPointer._dtpGetPreferredHeightId);
|
||||
}
|
||||
|
||||
pmb.menu._boxPointer.sourceActor = pmb.menu._boxPointer._dtpSourceActor;
|
||||
delete pmb.menu._boxPointer._dtpSourceActor;
|
||||
pmb.menu._boxPointer._userArrowSide = St.Side.TOP;
|
||||
})
|
||||
|
||||
this._removePanelBarriers(p);
|
||||
|
||||
p.disable();
|
||||
|
||||
let clipContainer = p.panelBox.get_parent();
|
||||
|
||||
Main.layoutManager._untrackActor(p.panelBox);
|
||||
Main.layoutManager.removeChrome(clipContainer);
|
||||
|
||||
if (p.isStandalone) {
|
||||
p.panelBox.destroy();
|
||||
} else {
|
||||
p.panelBox.remove_child(p);
|
||||
p.remove_child(p.panel.actor);
|
||||
p.panelBox.add(p.panel.actor);
|
||||
|
||||
p.panelBox.set_position(clipContainer.x, clipContainer.y);
|
||||
|
||||
clipContainer.remove_child(p.panelBox);
|
||||
Main.layoutManager.addChrome(p.panelBox, { affectsStruts: true, trackFullscreen: true });
|
||||
}
|
||||
});
|
||||
|
||||
if (BoxPointer.BoxPointer.prototype.vfunc_get_preferred_height) {
|
||||
Utils.hookVfunc(BoxPointer.BoxPointer.prototype, 'get_preferred_height', BoxPointer.BoxPointer.prototype.vfunc_get_preferred_height);
|
||||
}
|
||||
|
||||
delete Main.wm._getPositionForDirection;
|
||||
|
||||
if (Main.layoutManager.primaryMonitor) {
|
||||
Main.layoutManager.panelBox.set_position(Main.layoutManager.primaryMonitor.x, Main.layoutManager.primaryMonitor.y);
|
||||
Main.layoutManager.panelBox.set_size(Main.layoutManager.primaryMonitor.width, -1);
|
||||
}
|
||||
|
||||
if (reset) return;
|
||||
|
||||
this._setKeyBindings(false);
|
||||
|
||||
this._signalsHandler.destroy();
|
||||
|
||||
Main.layoutManager._updateHotCorners = this._oldUpdateHotCorners;
|
||||
Main.layoutManager._updateHotCorners();
|
||||
|
||||
if (this._enableHotCornersId) {
|
||||
Main.layoutManager._interfaceSettings.disconnect(this._enableHotCornersId);
|
||||
}
|
||||
|
||||
Main.layoutManager._updatePanelBarrier = this._oldUpdatePanelBarrier;
|
||||
Main.layoutManager._updatePanelBarrier();
|
||||
|
||||
Main.overview.viewSelector._animateIn = this._oldViewSelectorAnimateIn;
|
||||
Main.overview.viewSelector._animateOut = this._oldViewSelectorAnimateOut;
|
||||
|
||||
Main.overview._relayout = this._oldOverviewRelayout;
|
||||
Main.overview._relayout();
|
||||
|
||||
Main.overview.viewSelector._workspacesDisplay._updateWorkspacesViews = this._oldUpdateWorkspacesViews;
|
||||
|
||||
Utils.getPanelGhost().set_size(-1, -1);
|
||||
|
||||
if (this._needsDashItemContainerAllocate) {
|
||||
Utils.hookVfunc(Dash.DashItemContainer.prototype, 'allocate', function(box, flags) { this.vfunc_allocate(box, flags); });
|
||||
}
|
||||
|
||||
if (this._oldDoSpringAnimation) {
|
||||
AppDisplay.BaseAppView.prototype._doSpringAnimation = this._oldDoSpringAnimation;
|
||||
}
|
||||
|
||||
if (this._oldAnimateIconPosition) {
|
||||
IconGrid.animateIconPosition = this._oldAnimateIconPosition;
|
||||
}
|
||||
|
||||
LookingGlass.LookingGlass.prototype._resize = LookingGlass.LookingGlass.prototype._oldResize;
|
||||
delete LookingGlass.LookingGlass.prototype._oldResize;
|
||||
|
||||
LookingGlass.LookingGlass.prototype.open = LookingGlass.LookingGlass.prototype._oldOpen;
|
||||
delete LookingGlass.LookingGlass.prototype._oldOpen
|
||||
},
|
||||
|
||||
setFocusedMonitor: function(monitor, ignoreRelayout) {
|
||||
this._needsIconAllocate = 1;
|
||||
|
||||
if (!this.checkIfFocusedMonitor(monitor)) {
|
||||
Main.overview.viewSelector._workspacesDisplay._primaryIndex = monitor.index;
|
||||
|
||||
Main.overview._overview.clear_constraints();
|
||||
Main.overview._overview.add_constraint(new Layout.MonitorConstraint({ index: monitor.index }));
|
||||
|
||||
if (ignoreRelayout) return;
|
||||
|
||||
this._newOverviewRelayout.call(Main.overview);
|
||||
}
|
||||
},
|
||||
|
||||
_saveMonitors: function() {
|
||||
//Mutter meta_monitor_manager_get_primary_monitor (global.display.get_primary_monitor()) doesn't return the same
|
||||
//monitor as GDK gdk_screen_get_primary_monitor (imports.gi.Gdk.Screen.get_default().get_primary_monitor()).
|
||||
//Since the Mutter function is what's used in gnome-shell and we can't access it from the settings dialog, store
|
||||
//the monitors information in a setting so we can use the same monitor indexes as the ones in gnome-shell
|
||||
let primaryIndex = Main.layoutManager.primaryIndex;
|
||||
let monitors = [primaryIndex];
|
||||
|
||||
Main.layoutManager.monitors.filter(m => m.index != primaryIndex).forEach(m => monitors.push(m.index));
|
||||
Me.settings.set_value('available-monitors', new GLib.Variant('ai', monitors));
|
||||
},
|
||||
|
||||
checkIfFocusedMonitor: function(monitor) {
|
||||
return Main.overview.viewSelector._workspacesDisplay._primaryIndex == monitor.index;
|
||||
},
|
||||
|
||||
_createPanel: function(monitor, isStandalone) {
|
||||
let panelBox;
|
||||
let panel;
|
||||
let clipContainer = new Clutter.Actor();
|
||||
|
||||
if (isStandalone) {
|
||||
panelBox = new St.BoxLayout({ name: 'panelBox' });
|
||||
} else {
|
||||
panelBox = Main.layoutManager.panelBox;
|
||||
Main.layoutManager._untrackActor(panelBox);
|
||||
panelBox.remove_child(Main.panel.actor);
|
||||
Main.layoutManager.removeChrome(panelBox);
|
||||
}
|
||||
|
||||
Main.layoutManager.addChrome(clipContainer, { affectsInputRegion: false });
|
||||
clipContainer.add_child(panelBox);
|
||||
Main.layoutManager.trackChrome(panelBox, { trackFullscreen: true, affectsStruts: true, affectsInputRegion: true });
|
||||
|
||||
panel = new Panel.dtpPanel(this, monitor, panelBox, isStandalone);
|
||||
panelBox.add(panel);
|
||||
panel.enable();
|
||||
|
||||
panelBox.visible = true;
|
||||
if (monitor.inFullscreen) {
|
||||
panelBox.hide();
|
||||
}
|
||||
panelBox.set_position(0, 0);
|
||||
|
||||
return panel;
|
||||
},
|
||||
|
||||
_reset: function() {
|
||||
this.disable(true);
|
||||
this.allPanels = [];
|
||||
this.enable(true);
|
||||
},
|
||||
|
||||
_updatePanelElementPositions: function() {
|
||||
this.panelsElementPositions = Pos.getSettingsPositions(Me.settings, 'panel-element-positions');
|
||||
this.allPanels.forEach(p => p.updateElementPositions());
|
||||
},
|
||||
|
||||
_adjustPanelMenuButton: function(button, monitor, arrowSide) {
|
||||
if (button) {
|
||||
Utils.wrapActor(button);
|
||||
button.menu._boxPointer._dtpSourceActor = button.menu._boxPointer.sourceActor;
|
||||
button.menu._boxPointer.sourceActor = button.actor;
|
||||
button.menu._boxPointer._userArrowSide = arrowSide;
|
||||
button.menu._boxPointer._dtpInPanel = 1;
|
||||
|
||||
if (!button.menu._boxPointer.vfunc_get_preferred_height) {
|
||||
button.menu._boxPointer._dtpGetPreferredHeightId = button.menu._boxPointer._container.connect('get-preferred-height', (actor, forWidth, alloc) => {
|
||||
this._getBoxPointerPreferredHeight(button.menu._boxPointer, alloc, monitor);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getBoxPointerPreferredHeight: function(boxPointer, alloc, monitor) {
|
||||
if (boxPointer._dtpInPanel && boxPointer.sourceActor && Me.settings.get_boolean('intellihide')) {
|
||||
monitor = monitor || Main.layoutManager.findMonitorForActor(boxPointer.sourceActor);
|
||||
let panel = Utils.find(global.zorinTaskbar.panels, p => p.monitor == monitor);
|
||||
let excess = alloc.natural_size + panel.dtpSize + 10 - monitor.height; // 10 is arbitrary
|
||||
|
||||
if (excess > 0) {
|
||||
alloc.natural_size -= excess;
|
||||
}
|
||||
}
|
||||
|
||||
return [alloc.min_size, alloc.natural_size];
|
||||
},
|
||||
|
||||
_findPanelMenuButtons: function(container) {
|
||||
let panelMenuButtons = [];
|
||||
let panelMenuButton;
|
||||
|
||||
let find = parent => parent.get_children().forEach(c => {
|
||||
if ((panelMenuButton = this._getPanelMenuButton(c))) {
|
||||
panelMenuButtons.push(panelMenuButton);
|
||||
}
|
||||
|
||||
find(c);
|
||||
});
|
||||
|
||||
find(container);
|
||||
|
||||
return panelMenuButtons;
|
||||
},
|
||||
|
||||
_removePanelBarriers: function(panel) {
|
||||
if (panel.isStandalone && panel._rightPanelBarrier) {
|
||||
panel._rightPanelBarrier.destroy();
|
||||
}
|
||||
|
||||
if (panel._leftPanelBarrier) {
|
||||
panel._leftPanelBarrier.destroy();
|
||||
delete panel._leftPanelBarrier;
|
||||
}
|
||||
},
|
||||
|
||||
_getPanelMenuButton: function(obj) {
|
||||
return obj._delegate && obj._delegate instanceof PanelMenu.Button ? obj._delegate : 0;
|
||||
},
|
||||
|
||||
_setKeyBindings: function(enable) {
|
||||
let keys = {
|
||||
'intellihide-key-toggle': () => this.allPanels.forEach(p => p.intellihide.toggle())
|
||||
};
|
||||
|
||||
Object.keys(keys).forEach(k => {
|
||||
Utils.removeKeybinding(k);
|
||||
|
||||
if (enable) {
|
||||
Utils.addKeybinding(k, Me.settings, keys[k], Shell.ActionMode.NORMAL);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_newOverviewRelayout: function() {
|
||||
// To avoid updating the position and size of the workspaces
|
||||
// we just hide the overview. The positions will be updated
|
||||
// when it is next shown.
|
||||
this.hide();
|
||||
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.overview.viewSelector._workspacesDisplay._primaryIndex);
|
||||
|
||||
this._coverPane.set_position(0, workArea.y);
|
||||
this._coverPane.set_size(workArea.width, workArea.height);
|
||||
|
||||
this._updateBackgrounds();
|
||||
},
|
||||
|
||||
_newUpdateWorkspacesViews: function() {
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].destroy();
|
||||
|
||||
this._workspacesViews = [];
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let workspaces;
|
||||
let view;
|
||||
if (this._workspacesOnlyOnPrimary && i != Main.layoutManager.primaryIndex) {
|
||||
view = new WorkspacesView.ExtraWorkspaceView(i);
|
||||
view.getActiveWorkspace = view.getActiveWorkspace || function() { return this._workspace; };
|
||||
workspaces = [view._workspace];
|
||||
} else {
|
||||
view = new WorkspacesView.WorkspacesView(i, this._scrollAdjustment || 0);
|
||||
workspaces = view._workspaces;
|
||||
}
|
||||
|
||||
Utils.wrapActor(view);
|
||||
view.actor.connect('scroll-event', this._onScrollEvent.bind(this));
|
||||
if (i == Main.layoutManager.primaryIndex && view.scrollAdjustment) {
|
||||
this._scrollAdjustment = view.scrollAdjustment;
|
||||
this._scrollAdjustment.connect('notify::value',
|
||||
this._scrollValueChanged.bind(this));
|
||||
}
|
||||
|
||||
workspaces.forEach(w => w.setFullGeometry = geom => w._fullGeometry = geom);
|
||||
this._workspacesViews.push(view);
|
||||
}
|
||||
|
||||
this._workspacesViews.forEach(wv => Main.layoutManager.overviewGroup.add_actor(wv.actor));
|
||||
|
||||
if (this._syncWorkspacesFullGeometry) {
|
||||
//gnome-shell 3.36.4
|
||||
if (this._fullGeometry)
|
||||
this._syncWorkspacesFullGeometry();
|
||||
if (this._actualGeometry)
|
||||
this._syncWorkspacesActualGeometry();
|
||||
} else if (this._updateWorkspacesFullGeometry) {
|
||||
this._updateWorkspacesFullGeometry();
|
||||
this._updateWorkspacesActualGeometry();
|
||||
}
|
||||
},
|
||||
|
||||
_newGetShowAppsButton: function() {
|
||||
let focusedMonitorIndex = Utils.findIndex(this.allPanels, p => this.checkIfFocusedMonitor(p.monitor));
|
||||
|
||||
return this.allPanels[focusedMonitorIndex].taskbar.showAppsButton;
|
||||
},
|
||||
|
||||
_newDashItemContainerAllocate: function(box, flags) {
|
||||
if (this.child == null)
|
||||
return;
|
||||
|
||||
Utils.setAllocation(this, box, flags);
|
||||
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] = this.child.get_preferred_size();
|
||||
let [childScaleX, childScaleY] = this.child.get_scale();
|
||||
|
||||
let childWidth = Math.min(natChildWidth * childScaleX, availWidth);
|
||||
let childHeight = Math.min(natChildHeight * childScaleY, availHeight);
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
childBox.x1 = (availWidth - childWidth) / 2;
|
||||
childBox.y1 = (availHeight - childHeight) / 2;
|
||||
childBox.x2 = childBox.x1 + childWidth;
|
||||
childBox.y2 = childBox.y1 + childHeight;
|
||||
|
||||
Utils.allocate(this.child, childBox, flags);
|
||||
},
|
||||
});
|
||||
|
||||
// This class drives long-running icon animations, to keep them running in sync
|
||||
// with each other.
|
||||
var IconAnimator = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.IconAnimator',
|
||||
|
||||
_init: function(actor) {
|
||||
this._count = 0;
|
||||
this._started = false;
|
||||
this._animations = {
|
||||
dance: [],
|
||||
};
|
||||
this._timeline = new Clutter.Timeline({
|
||||
duration: 3000,
|
||||
repeat_count: -1,
|
||||
});
|
||||
|
||||
/* Just use the construction property when no need to support 3.36 */
|
||||
if (this._timeline.set_actor)
|
||||
this._timeline.set_actor(actor);
|
||||
|
||||
this._timeline.connect('new-frame', () => {
|
||||
const progress = this._timeline.get_progress();
|
||||
const danceRotation = progress < 1/6 ? 15*Math.sin(progress*24*Math.PI) : 0;
|
||||
const dancers = this._animations.dance;
|
||||
for (let i = 0, iMax = dancers.length; i < iMax; i++) {
|
||||
dancers[i].target.rotation_angle_z = danceRotation;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._timeline.stop();
|
||||
this._timeline = null;
|
||||
for (const name in this._animations) {
|
||||
const pairs = this._animations[name];
|
||||
for (let i = 0, iMax = pairs.length; i < iMax; i++) {
|
||||
const pair = pairs[i];
|
||||
pair.target.disconnect(pair.targetDestroyId);
|
||||
}
|
||||
}
|
||||
this._animations = null;
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
if (this._started && this._count > 0) {
|
||||
this._timeline.stop();
|
||||
}
|
||||
this._started = false;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
if (!this._started && this._count > 0) {
|
||||
this._timeline.start();
|
||||
}
|
||||
this._started = true;
|
||||
},
|
||||
|
||||
addAnimation: function(target, name) {
|
||||
const targetDestroyId = target.connect('destroy', () => this.removeAnimation(target, name));
|
||||
this._animations[name].push({ target, targetDestroyId });
|
||||
if (this._started && this._count === 0) {
|
||||
this._timeline.start();
|
||||
}
|
||||
this._count++;
|
||||
},
|
||||
|
||||
removeAnimation: function(target, name) {
|
||||
const pairs = this._animations[name];
|
||||
for (let i = 0, iMax = pairs.length; i < iMax; i++) {
|
||||
const pair = pairs[i];
|
||||
if (pair.target === target) {
|
||||
target.disconnect(pair.targetDestroyId);
|
||||
pairs.splice(i, 1);
|
||||
this._count--;
|
||||
if (this._started && this._count === 0) {
|
||||
this._timeline.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function newViewSelectorAnimateIn(oldPage) {
|
||||
if (oldPage)
|
||||
oldPage.hide();
|
||||
|
||||
let vs = Main.overview.viewSelector;
|
||||
|
||||
vs.emit('page-empty');
|
||||
|
||||
vs._activePage.show();
|
||||
|
||||
if (vs._activePage == vs._appsPage && oldPage == vs._workspacesPage) {
|
||||
// Restore opacity, in case we animated via _fadePageOut
|
||||
vs._activePage.opacity = 255;
|
||||
let animate = Me.settings.get_boolean('animate-show-apps');
|
||||
if(animate)
|
||||
vs.appDisplay.animate(IconGrid.AnimationDirection.IN);
|
||||
} else {
|
||||
vs._fadePageIn();
|
||||
}
|
||||
}
|
||||
|
||||
function newViewSelectorAnimateOut(page) {
|
||||
let oldPage = page;
|
||||
let vs = Main.overview.viewSelector;
|
||||
|
||||
if (page == vs._appsPage &&
|
||||
vs._activePage == vs._workspacesPage &&
|
||||
!Main.overview.animationInProgress) {
|
||||
let animate = Me.settings.get_boolean('animate-show-apps');
|
||||
if(animate)
|
||||
vs.appDisplay.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this,
|
||||
function() {
|
||||
vs._animateIn(oldPage)
|
||||
}));
|
||||
else
|
||||
vs._animateIn(oldPage)
|
||||
} else {
|
||||
vs._fadePageOut(page);
|
||||
}
|
||||
}
|
||||
|
||||
function newGetPositionForDirection(direction, fromWs, toWs) {
|
||||
let [xDest, yDest] = WM.WindowManager.prototype._getPositionForDirection(direction, fromWs, toWs);
|
||||
|
||||
if (direction == Meta.MotionDirection.UP ||
|
||||
direction == Meta.MotionDirection.UP_LEFT ||
|
||||
direction == Meta.MotionDirection.UP_RIGHT) {
|
||||
yDest -= Main.panel.height;
|
||||
} else if (direction != Meta.MotionDirection.LEFT &&
|
||||
direction != Meta.MotionDirection.RIGHT) {
|
||||
yDest += Main.panel.height;
|
||||
}
|
||||
|
||||
return [xDest, yDest];
|
||||
}
|
||||
|
||||
function newDoSpringAnimation(animationDirection) {
|
||||
this._grid.opacity = 255;
|
||||
this._grid.animateSpring(animationDirection, Main.overview.getShowAppsButton());
|
||||
}
|
||||
|
||||
function newAnimateIconPosition(icon, box, flags, nChangedIcons) {
|
||||
if (this._needsIconAllocate) {
|
||||
Utils.allocate(icon, box, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._oldAnimateIconPosition(icon, box, flags, nChangedIcons);;
|
||||
}
|
||||
|
||||
function newUpdateHotCorners() {
|
||||
// destroy old hot corners
|
||||
this.hotCorners.forEach(function(corner) {
|
||||
if (corner)
|
||||
corner.destroy();
|
||||
});
|
||||
this.hotCorners = [];
|
||||
|
||||
//global.settings is ubuntu specific setting to disable the hot corner (Tweak tool > Top Bar > Activities Overview Hot Corner)
|
||||
//this._interfaceSettings is for the setting to disable the hot corner introduced in gnome-shell 3.34
|
||||
if ((global.settings.list_keys().indexOf('enable-hot-corners') >= 0 && !global.settings.get_boolean('enable-hot-corners')) ||
|
||||
(this._interfaceSettings && !this._interfaceSettings.get_boolean('enable-hot-corners'))) {
|
||||
this.emit('hot-corners-changed');
|
||||
return;
|
||||
}
|
||||
|
||||
// build new hot corners
|
||||
for (let i = 0; i < this.monitors.length; i++) {
|
||||
let panel = Utils.find(global.zorinTaskbar.panels, p => p.monitor.index == i);
|
||||
let panelPosition = panel ? panel.getPosition() : St.Side.BOTTOM;
|
||||
let panelTopLeft = panelPosition == St.Side.TOP || panelPosition == St.Side.LEFT;
|
||||
let monitor = this.monitors[i];
|
||||
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
|
||||
let cornerY = monitor.y;
|
||||
|
||||
let haveTopLeftCorner = true;
|
||||
|
||||
// If the panel is on the bottom, unless this is explicitly forced, don't add a topleft
|
||||
// hot corner unless it is actually a top left panel. Otherwise, it stops the mouse
|
||||
// as you are dragging across. In the future, maybe we will automatically move the
|
||||
// hotcorner to the bottom when the panel is positioned at the bottom
|
||||
if (i != this.primaryIndex || !panelTopLeft) {
|
||||
// Check if we have a top left (right for RTL) corner.
|
||||
// I.e. if there is no monitor directly above or to the left(right)
|
||||
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
|
||||
let besideY = cornerY;
|
||||
let aboveX = cornerX;
|
||||
let aboveY = cornerY - 1;
|
||||
|
||||
for (let j = 0; j < this.monitors.length; j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
let otherMonitor = this.monitors[j];
|
||||
if (besideX >= otherMonitor.x &&
|
||||
besideX < otherMonitor.x + otherMonitor.width &&
|
||||
besideY >= otherMonitor.y &&
|
||||
besideY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
if (aboveX >= otherMonitor.x &&
|
||||
aboveX < otherMonitor.x + otherMonitor.width &&
|
||||
aboveY >= otherMonitor.y &&
|
||||
aboveY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (haveTopLeftCorner) {
|
||||
let corner = new Layout.HotCorner(this, monitor, cornerX, cornerY);
|
||||
|
||||
corner.setBarrierSize = size => corner.__proto__.setBarrierSize.call(corner, Math.min(size, 32));
|
||||
corner.setBarrierSize(panel ? panel.dtpSize : 32);
|
||||
this.hotCorners.push(corner);
|
||||
} else {
|
||||
this.hotCorners.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('hot-corners-changed');
|
||||
}
|
||||
|
||||
function newUpdatePanelBarrier(panel) {
|
||||
let barriers = {
|
||||
_rightPanelBarrier: [(panel.isStandalone ? panel : this)],
|
||||
_leftPanelBarrier: [panel]
|
||||
};
|
||||
|
||||
Object.keys(barriers).forEach(k => {
|
||||
let obj = barriers[k][0];
|
||||
|
||||
if (obj[k]) {
|
||||
obj[k].destroy();
|
||||
obj[k] = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.primaryMonitor || !panel.panelBox.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
let barrierSize = Math.min(10, panel.panelBox.height);
|
||||
let fixed1 = panel.monitor.y;
|
||||
let fixed2 = panel.monitor.y + barrierSize;
|
||||
|
||||
if (panel.checkIfVertical()) {
|
||||
barriers._rightPanelBarrier.push(panel.monitor.y + panel.monitor.height, Meta.BarrierDirection.POSITIVE_Y);
|
||||
barriers._leftPanelBarrier.push(panel.monitor.y, Meta.BarrierDirection.NEGATIVE_Y);
|
||||
} else {
|
||||
barriers._rightPanelBarrier.push(panel.monitor.x + panel.monitor.width, Meta.BarrierDirection.NEGATIVE_X);
|
||||
barriers._leftPanelBarrier.push(panel.monitor.x, Meta.BarrierDirection.POSITIVE_X);
|
||||
}
|
||||
|
||||
switch (panel.getPosition()) {
|
||||
//values are initialized as St.Side.TOP
|
||||
case St.Side.BOTTOM:
|
||||
fixed1 = panel.monitor.y + panel.monitor.height - barrierSize;
|
||||
fixed2 = panel.monitor.y + panel.monitor.height;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
fixed1 = panel.monitor.x;
|
||||
fixed2 = panel.monitor.x + barrierSize;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
fixed1 = panel.monitor.x + panel.monitor.width;
|
||||
fixed2 = panel.monitor.x + panel.monitor.width - barrierSize;
|
||||
break;
|
||||
}
|
||||
|
||||
//remove left barrier if it overlaps one of the hotcorners
|
||||
for (let k in this.hotCorners) {
|
||||
let hc = this.hotCorners[k];
|
||||
|
||||
if (hc && hc._monitor == panel.monitor &&
|
||||
((fixed1 == hc._x || fixed2 == hc._x) || fixed1 == hc._y || fixed2 == hc._y)) {
|
||||
delete barriers._leftPanelBarrier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(barriers).forEach(k => {
|
||||
let barrierOptions = {
|
||||
display: global.display,
|
||||
directions: barriers[k][2]
|
||||
};
|
||||
|
||||
barrierOptions[panel.varCoord.c1] = barrierOptions[panel.varCoord.c2] = barriers[k][1];
|
||||
barrierOptions[panel.fixedCoord.c1] = fixed1;
|
||||
barrierOptions[panel.fixedCoord.c2] = fixed2;
|
||||
|
||||
barriers[k][0][k] = new Meta.Barrier(barrierOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function _newLookingGlassResize() {
|
||||
let primaryMonitorPanel = Utils.find(global.zorinTaskbar.panels, p => p.monitor == Main.layoutManager.primaryMonitor);
|
||||
let topOffset = primaryMonitorPanel.getPosition() == St.Side.TOP ? primaryMonitorPanel.dtpSize + 8 : 32;
|
||||
|
||||
this._oldResize();
|
||||
Utils.wrapActor(this);
|
||||
Utils.wrapActor(this._objInspector);
|
||||
|
||||
this._hiddenY = Main.layoutManager.primaryMonitor.y + topOffset - this.actor.height;
|
||||
this._targetY = this._hiddenY + this.actor.height;
|
||||
this.actor.y = this._hiddenY;
|
||||
|
||||
this._objInspector.actor.set_position(this.actor.x + Math.floor(this.actor.width * 0.1), this._targetY + Math.floor(this.actor.height * 0.1));
|
||||
}
|
||||
|
||||
function _newLookingGlassOpen() {
|
||||
if (this._open)
|
||||
return;
|
||||
|
||||
this._resize();
|
||||
this._oldOpen();
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
var SHOW_APPS_BTN = 'showAppsButton';
|
||||
var ACTIVITIES_BTN = 'activitiesButton';
|
||||
var TASKBAR = 'taskbar';
|
||||
var DATE_MENU = 'dateMenu';
|
||||
var SYSTEM_MENU = 'systemMenu';
|
||||
var LEFT_BOX = 'leftBox';
|
||||
var CENTER_BOX = 'centerBox';
|
||||
var RIGHT_BOX = 'rightBox';
|
||||
var DESKTOP_BTN = 'desktopButton';
|
||||
|
||||
var STACKED_TL = 'stackedTL';
|
||||
var STACKED_BR = 'stackedBR';
|
||||
var CENTERED = 'centered';
|
||||
var CENTERED_MONITOR = 'centerMonitor';
|
||||
|
||||
var TOP = 'TOP';
|
||||
var BOTTOM = 'BOTTOM';
|
||||
var LEFT = 'LEFT';
|
||||
var RIGHT = 'RIGHT';
|
||||
|
||||
var defaults = [
|
||||
{ element: LEFT_BOX, visible: true, position: STACKED_TL },
|
||||
{ element: SHOW_APPS_BTN, visible: false, position: STACKED_TL },
|
||||
{ element: ACTIVITIES_BTN, visible: true, position: STACKED_TL },
|
||||
{ element: TASKBAR, visible: true, position: STACKED_TL },
|
||||
{ element: CENTER_BOX, visible: true, position: STACKED_BR },
|
||||
{ element: RIGHT_BOX, visible: true, position: STACKED_BR },
|
||||
{ element: SYSTEM_MENU, visible: true, position: STACKED_BR },
|
||||
{ element: DATE_MENU, visible: true, position: STACKED_BR },
|
||||
{ element: DESKTOP_BTN, visible: false, position: STACKED_BR },
|
||||
];
|
||||
|
||||
var optionDialogFunctions = {};
|
||||
|
||||
optionDialogFunctions[SHOW_APPS_BTN] = '_showShowAppsButtonOptions';
|
||||
optionDialogFunctions[DATE_MENU] = '_showDateMenuOptions';
|
||||
optionDialogFunctions[DESKTOP_BTN] = '_showDesktopButtonOptions';
|
||||
|
||||
function getSettingsPositions(settings, setting) {
|
||||
var positions = null;
|
||||
|
||||
try {
|
||||
positions = JSON.parse(settings.get_string(setting));
|
||||
} catch(e) {
|
||||
log('Error parsing positions: ' + e.message);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
function checkIfCentered(position) {
|
||||
return position == CENTERED || position == CENTERED_MONITOR;
|
||||
}
|
||||
176
panelStyle.js
176
panelStyle.js
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
* Ideas for recursing child actors and assigning inline styles
|
||||
* are based on code from the StatusAreaHorizontalSpacing extension
|
||||
* https://bitbucket.org/mathematicalcoffee/status-area-horizontal-spacing-gnome-shell-extension
|
||||
* mathematical.coffee@gmail.com
|
||||
*/
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Panel = Me.imports.panel;
|
||||
const Taskbar = Me.imports.taskbar;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
var dtpPanelStyle = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.PanelStyle',
|
||||
|
||||
_init: function() {
|
||||
|
||||
},
|
||||
|
||||
enable : function(panel) {
|
||||
this.panel = panel;
|
||||
|
||||
this._applyStyles();
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
this._removeStyles();
|
||||
},
|
||||
|
||||
_applyStyles: function() {
|
||||
this._rightBoxOperations = [];
|
||||
|
||||
// center box has been moved next to the right box and will be treated the same
|
||||
this._centerBoxOperations = this._rightBoxOperations;
|
||||
|
||||
this._leftBoxOperations = [];
|
||||
|
||||
this._applyStylesRecursively();
|
||||
|
||||
/* connect signal */
|
||||
this._rightBoxActorAddedID = this.panel._rightBox.connect('actor-added',
|
||||
Lang.bind(this, function (container, actor) {
|
||||
if(this._rightBoxOperations.length && !this._ignoreAddedChild)
|
||||
this._recursiveApply(actor, this._rightBoxOperations);
|
||||
|
||||
this._ignoreAddedChild = 0;
|
||||
})
|
||||
);
|
||||
this._centerBoxActorAddedID = this.panel._centerBox.connect('actor-added',
|
||||
Lang.bind(this, function (container, actor) {
|
||||
if(this._centerBoxOperations.length && !this._ignoreAddedChild)
|
||||
this._recursiveApply(actor, this._centerBoxOperations);
|
||||
|
||||
this._ignoreAddedChild = 0;
|
||||
})
|
||||
);
|
||||
this._leftBoxActorAddedID = this.panel._leftBox.connect('actor-added',
|
||||
Lang.bind(this, function (container, actor) {
|
||||
if(this._leftBoxOperations.length)
|
||||
this._recursiveApply(actor, this._leftBoxOperations);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_removeStyles: function() {
|
||||
/* disconnect signal */
|
||||
if (this._rightBoxActorAddedID)
|
||||
this.panel._rightBox.disconnect(this._rightBoxActorAddedID);
|
||||
if (this._centerBoxActorAddedID)
|
||||
this.panel._centerBox.disconnect(this._centerBoxActorAddedID);
|
||||
if (this._leftBoxActorAddedID)
|
||||
this.panel._leftBox.disconnect(this._leftBoxActorAddedID);
|
||||
|
||||
this._restoreOriginalStyle(this.panel._rightBox);
|
||||
this._restoreOriginalStyle(this.panel._centerBox);
|
||||
this._restoreOriginalStyle(this.panel._leftBox);
|
||||
|
||||
this._applyStylesRecursively(true);
|
||||
},
|
||||
|
||||
_applyStylesRecursively: function(restore) {
|
||||
/*recurse actors */
|
||||
if(this._rightBoxOperations.length) {
|
||||
// add the system menu as we move it from the rightbox to the panel to position it independently
|
||||
let children = this.panel._rightBox.get_children().concat([this.panel.statusArea.aggregateMenu.container]);
|
||||
for(let i in children)
|
||||
this._recursiveApply(children[i], this._rightBoxOperations, restore);
|
||||
}
|
||||
|
||||
if(this._centerBoxOperations.length) {
|
||||
// add the date menu as we move it from the centerbox to the panel to position it independently
|
||||
let children = this.panel._centerBox.get_children().concat([this.panel.statusArea.dateMenu.container]);
|
||||
for(let i in children)
|
||||
this._recursiveApply(children[i], this._centerBoxOperations, restore);
|
||||
}
|
||||
|
||||
if(this._leftBoxOperations.length) {
|
||||
let children = this.panel._leftBox.get_children();
|
||||
for(let i in children)
|
||||
this._recursiveApply(children[i], this._leftBoxOperations, restore);
|
||||
}
|
||||
},
|
||||
|
||||
_recursiveApply: function(actor, operations, restore) {
|
||||
for(let i in operations) {
|
||||
let o = operations[i];
|
||||
if(o.compareFn(actor))
|
||||
if(restore)
|
||||
o.restoreFn ? o.restoreFn(actor) : this._restoreOriginalStyle(actor);
|
||||
else
|
||||
o.applyFn(actor, i);
|
||||
}
|
||||
|
||||
if(actor.get_children) {
|
||||
let children = actor.get_children();
|
||||
for(let i in children) {
|
||||
this._recursiveApply(children[i], operations, restore);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_restoreOriginalStyle: function(actor) {
|
||||
if (actor._dtp_original_inline_style !== undefined) {
|
||||
actor.set_style(actor._dtp_original_inline_style);
|
||||
delete actor._dtp_original_inline_style;
|
||||
delete actor._dtp_style_overrides;
|
||||
}
|
||||
|
||||
if (actor.has_style_class_name('panel-button')) {
|
||||
this._refreshPanelButton(actor);
|
||||
}
|
||||
},
|
||||
|
||||
_refreshPanelButton: function(actor) {
|
||||
if (actor.visible && imports.misc.config.PACKAGE_VERSION >= '3.34.0') {
|
||||
//force gnome 3.34+ to refresh (having problem with the -natural-hpadding)
|
||||
let parent = actor.get_parent();
|
||||
let children = parent.get_children();
|
||||
let actorIndex = 0;
|
||||
|
||||
if (children.length > 1) {
|
||||
actorIndex = children.indexOf(actor);
|
||||
}
|
||||
|
||||
this._ignoreAddedChild = [this.panel._centerBox, this.panel._rightBox].indexOf(parent) >= 0;
|
||||
|
||||
parent.remove_child(actor);
|
||||
parent.insert_child_at_index(actor, actorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
6
po/af.po
6
po/af.po
@@ -12,3 +12,9 @@ msgstr "Lêers"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Instellings"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Wys Rekenaar-Ikoon"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Wys Rekenaar-Ikoon vulling (px)"
|
||||
|
||||
9
po/ar.po
9
po/ar.po
@@ -15,3 +15,12 @@ msgstr "الملفات"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "الإعدادات"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "أظهر أيقونات سطح المكتب"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "أظهر زر سطح المكتب المساحة المتروكة (بيكسل)"
|
||||
|
||||
msgid "Isolate Workspaces and Monitors in Application Switching settings"
|
||||
msgstr "عزل مساحات العمل والشاشات في إعدادات تبديل التطبيقات"
|
||||
|
||||
6
po/be.po
6
po/be.po
@@ -15,3 +15,9 @@ msgstr "Файлы"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройкі"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Паказаць значок працоўнага стала"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Пракладка Паказаць кнопку працоўнага стала (px)"
|
||||
|
||||
6
po/bg.po
6
po/bg.po
@@ -15,3 +15,9 @@ msgstr "Файлове"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Показване на икона на работния плот"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Бутон Покажи работния плот - подложка (px)"
|
||||
|
||||
6
po/bn.po
6
po/bn.po
@@ -12,3 +12,9 @@ msgstr "সিস্টেম পর্যবেক্ষক"
|
||||
|
||||
msgid "Files"
|
||||
msgstr "ফাইল"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "ডেস্কটপ আইকন দেখান"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Show Desktop button প্যাডিং (px আকারে)"
|
||||
|
||||
6
po/ca.po
6
po/ca.po
@@ -15,3 +15,9 @@ msgstr "Fitxers"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Paràmetres"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Mostrar l'icona de l'Escriptori"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Farciment Mostra el botó de l'escriptori (px)"
|
||||
|
||||
6
po/da.po
6
po/da.po
@@ -15,3 +15,9 @@ msgstr "Filer"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Indstillinger"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Vis skrivebords ikon"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Vis skrivebords knap afstand (px)"
|
||||
|
||||
6
po/el.po
6
po/el.po
@@ -15,3 +15,9 @@ msgstr "Αρχεία"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Ρυθμίσεις"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Εμφάνιση επιφάνειας εργασίας"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Εμφάνισε διαχωριστικό"
|
||||
|
||||
6
po/et.po
6
po/et.po
@@ -15,3 +15,9 @@ msgstr "Failid"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Sätted"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Kuva töölauaikoon"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Täitmine Kuva töölaua nupp (px)"
|
||||
|
||||
6
po/fi.po
6
po/fi.po
@@ -15,3 +15,9 @@ msgstr "Tiedostot"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Asetukset"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Näytä työpöytä-kuvake"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Näytä työpöytä painikkeen asettelu (px)"
|
||||
|
||||
6
po/ga.po
6
po/ga.po
@@ -15,3 +15,9 @@ msgstr "Comhaid"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Socruithe"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Taispeáin deilbhín Deisce"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Stuáil Taispeáin cnaipe Deisce (px)"
|
||||
|
||||
6
po/gu.po
6
po/gu.po
@@ -15,3 +15,9 @@ msgstr "ફાઇલો"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "સુયોજનો"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "ડેસ્કટ .પ ચિહ્ન બતાવો"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ડેસ્કટtopપ બટન બતાવો પેડિંગ (પીએક્સ)"
|
||||
|
||||
6
po/he.po
6
po/he.po
@@ -15,3 +15,9 @@ msgstr "קבצים"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "הגדרות"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "הצגת סמל שולחן העבודה"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ריפוד הצגת כפתור שולחן עבודה (פיקסלים)"
|
||||
|
||||
9
po/hi.po
9
po/hi.po
@@ -15,3 +15,12 @@ msgstr "फ़ाइल"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "सेटिंग्स"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "डेस्कटॉप आइकन दिखाएं"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "डेस्कटॉप बटन दिखाएं पैडिंग (पीएक्स)"
|
||||
|
||||
msgid "Isolate Workspaces and Monitors in Application Switching settings"
|
||||
msgstr "एप्लिकेशन स्विचिंग सेटिंग्स में कार्यस्थलों और मॉनिटर को अलग करें"
|
||||
|
||||
6
po/hr.po
6
po/hr.po
@@ -15,3 +15,9 @@ msgstr "Datoteke"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Postavke"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Prikaži ikonu radne površine"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Podmetač Prikaži gumb radne površine (px)"
|
||||
|
||||
9
po/id.po
9
po/id.po
@@ -15,3 +15,12 @@ msgstr "Berkas"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Pengaturan"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Tampilkan ikon desktop"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Tampilkan tombol desktop padding (px)"
|
||||
|
||||
msgid "Isolate Workspaces and Monitors in Application Switching settings"
|
||||
msgstr "Isolasi Ruang Kerja dan Monitor dalam pengaturan Pengalihan Aplikasi"
|
||||
|
||||
6
po/is.po
6
po/is.po
@@ -15,3 +15,9 @@ msgstr "Skrár"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Stillingar"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Sýna skjáborðstáknið"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "„Sýna skjáborðshnapp“ padding (px)"
|
||||
|
||||
6
po/ka.po
6
po/ka.po
@@ -9,3 +9,9 @@ msgstr ""
|
||||
|
||||
msgid "System Monitor"
|
||||
msgstr "სისტემის მონიტორი"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "აჩვენე დესკტოპის იკონები"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ღილაკი აჩვენე დესკტოპი"
|
||||
|
||||
6
po/kn.po
6
po/kn.po
@@ -15,3 +15,9 @@ msgstr "ಕಡತಗಳು"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "ಸಿದ್ಧತೆಗಳು"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "ಡೆಸ್ಕ್ಟಾಪ್ ಐಕಾನ್ ತೋರಿಸಿ"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ಡೆಸ್ಕ್ಟಾಪ್ ಬಟನ್ ತೋರಿಸು ಪ್ಯಾಡಿಂಗ್ (ಪಿಎಕ್ಸ್)"
|
||||
|
||||
6
po/lt.po
6
po/lt.po
@@ -15,3 +15,9 @@ msgstr "Failai"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Nustatymai"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Rodyti Darbalaukio ikoną"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Rodyti Darbalaukio mygtuką"
|
||||
|
||||
6
po/lv.po
6
po/lv.po
@@ -15,3 +15,9 @@ msgstr "Datnes"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Iestatījumi"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Rādīt darbvirsmas ikonu"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Rādīt darbvirsmas pogu polsterējums (px)"
|
||||
|
||||
6
po/mk.po
6
po/mk.po
@@ -12,3 +12,9 @@ msgstr "Надгледувач на системот"
|
||||
|
||||
msgid "Files"
|
||||
msgstr "Датотеки"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Покажете ја иконата за работната површина"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Пополнување „Покажи копче за работна површина“ (px)"
|
||||
|
||||
6
po/ms.po
6
po/ms.po
@@ -15,3 +15,9 @@ msgstr "Fail"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Tetapan"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Tunjukkan ikon Atas Meja"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Pemadatan Tunjuk butang Atas Meja (px)"
|
||||
|
||||
6
po/ne.po
6
po/ne.po
@@ -15,3 +15,9 @@ msgstr "फाइलहरू"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "सेटिङ"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "डेस्कटप प्रतिमा देखाउनुहोस्"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "डेस्कटप बटन देखाउनुहोस् प्याडि ((px)"
|
||||
|
||||
6
po/pa.po
6
po/pa.po
@@ -15,3 +15,9 @@ msgstr "ਫਾਇਲਾਂ"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "ਸੈਟਿੰਗ"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "ਡੈਸਕਟਾਪ ਆਈਕਾਨ ਵੇਖਾਓ"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ਡੈਸਕਟਾਪ ਬਟਨ ਦਿਖਾਓ ਭਰਾਵ (px)"
|
||||
|
||||
9
po/pt.po
9
po/pt.po
@@ -15,3 +15,12 @@ msgstr "Ficheiros"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Definições"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Mostrar ícone da Área de Trabalho"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Preenchimento do botão Mostrar Área de Trabalho (px)"
|
||||
|
||||
msgid "Isolate Workspaces and Monitors in Application Switching settings"
|
||||
msgstr "Isolar espaços de trabalho e monitores nas configurações de Comutação de aplicações"
|
||||
|
||||
1738
po/pt_BR.po
1738
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
6
po/ro.po
6
po/ro.po
@@ -15,3 +15,9 @@ msgstr "Fișiere"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Configurări"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Afișați pictograma Desktop"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Afișați umplerea butonului desktop (px)"
|
||||
|
||||
6
po/sl.po
6
po/sl.po
@@ -15,3 +15,9 @@ msgstr "Datoteke"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Nastavitve"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Pokaži ikono za namizje"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Pokaži ikono za namizje razmik (px)"
|
||||
|
||||
6
po/sq.po
6
po/sq.po
@@ -9,3 +9,9 @@ msgstr ""
|
||||
|
||||
msgid "System Monitor"
|
||||
msgstr "Monitori i sistemit"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Shfaq ikonën e desktopit"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Mbushja Shfaq butonin e desktopit (px)"
|
||||
|
||||
6
po/sr.po
6
po/sr.po
@@ -15,3 +15,9 @@ msgstr "Датотеке"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Подешавања"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Прикажи икону радне површине"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Подлога „Прикажи дугме радне површине“ (пк)"
|
||||
|
||||
6
po/ta.po
6
po/ta.po
@@ -15,3 +15,9 @@ msgstr "கோப்புகள்"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "அமைவுகள்"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "டெஸ்க்டாப் ஐகானைக் காட்டு"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "டெஸ்க்டாப் பொத்தானைக் காட்டு திணிப்பு (px)"
|
||||
|
||||
6
po/te.po
6
po/te.po
@@ -15,3 +15,9 @@ msgstr "దస్త్రాలు"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "అమరికలు"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "డెస్క్టాప్ చిహ్నాన్ని చూపించు"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "డెస్క్టాప్ బటన్ చూపించు పాడింగ్ (px)"
|
||||
|
||||
6
po/th.po
6
po/th.po
@@ -15,3 +15,9 @@ msgstr "แฟ้ม"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "ตั้งค่า"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "แสดงไอคอนเดสก์ท็อป"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "ช่องว่างภายใน แสดงปุ่มเดสก์ท็อป (px)"
|
||||
|
||||
6
po/vi.po
6
po/vi.po
@@ -15,3 +15,9 @@ msgstr "Tập tin"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Cài đặt"
|
||||
|
||||
msgid "Show Desktop icon"
|
||||
msgstr "Hiển thị biểu tượng Desktop"
|
||||
|
||||
msgid "Show Desktop button padding (px)"
|
||||
msgstr "Phần đệm Show Desktop button (px)"
|
||||
|
||||
2226
po/zh_CN.po
2226
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
1979
po/zh_TW.po
1979
po/zh_TW.po
File diff suppressed because it is too large
Load Diff
600
progress.js
600
progress.js
@@ -1,600 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Gio = imports.gi.Gio;
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
|
||||
var ProgressManager = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.ProgressManager',
|
||||
|
||||
_init: function() {
|
||||
|
||||
this._entriesByDBusName = {};
|
||||
|
||||
this._launcher_entry_dbus_signal_id =
|
||||
Gio.DBus.session.signal_subscribe(null, // sender
|
||||
'com.canonical.Unity.LauncherEntry', // iface
|
||||
null, // member
|
||||
null, // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
this._onEntrySignalReceived.bind(this));
|
||||
|
||||
this._dbus_name_owner_changed_signal_id =
|
||||
Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender
|
||||
'org.freedesktop.DBus', // interface
|
||||
'NameOwnerChanged', // member
|
||||
'/org/freedesktop/DBus', // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
this._onDBusNameOwnerChanged.bind(this));
|
||||
|
||||
this._acquireUnityDBus();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._launcher_entry_dbus_signal_id) {
|
||||
Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id);
|
||||
}
|
||||
|
||||
if (this._dbus_name_owner_changed_signal_id) {
|
||||
Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id);
|
||||
}
|
||||
|
||||
this._releaseUnityDBus();
|
||||
},
|
||||
|
||||
size: function() {
|
||||
return Object.keys(this._entriesByDBusName).length;
|
||||
},
|
||||
|
||||
lookupByDBusName: function(dbusName) {
|
||||
return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null;
|
||||
},
|
||||
|
||||
lookupById: function(appId) {
|
||||
let ret = [];
|
||||
for (let dbusName in this._entriesByDBusName) {
|
||||
let entry = this._entriesByDBusName[dbusName];
|
||||
if (entry && entry.appId() == appId) {
|
||||
ret.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
addEntry: function(entry) {
|
||||
let existingEntry = this.lookupByDBusName(entry.dbusName());
|
||||
if (existingEntry) {
|
||||
existingEntry.update(entry);
|
||||
} else {
|
||||
this._entriesByDBusName[entry.dbusName()] = entry;
|
||||
this.emit('progress-entry-added', entry);
|
||||
}
|
||||
},
|
||||
|
||||
removeEntry: function(entry) {
|
||||
delete this._entriesByDBusName[entry.dbusName()]
|
||||
this.emit('progress-entry-removed', entry);
|
||||
},
|
||||
|
||||
_acquireUnityDBus: function() {
|
||||
if (!this._unity_bus_id) {
|
||||
Gio.DBus.session.own_name('com.canonical.Unity',
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
|
||||
}
|
||||
},
|
||||
|
||||
_releaseUnityDBus: function() {
|
||||
if (this._unity_bus_id) {
|
||||
Gio.DBus.session.unown_name(this._unity_bus_id);
|
||||
this._unity_bus_id = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_onEntrySignalReceived: function(connection, sender_name, object_path,
|
||||
interface_name, signal_name, parameters, user_data) {
|
||||
if (!parameters || !signal_name)
|
||||
return;
|
||||
|
||||
if (signal_name == 'Update') {
|
||||
if (!sender_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleUpdateRequest(sender_name, parameters);
|
||||
}
|
||||
},
|
||||
|
||||
_onDBusNameOwnerChanged: function(connection, sender_name, object_path,
|
||||
interface_name, signal_name, parameters, user_data) {
|
||||
if (!parameters || !this.size())
|
||||
return;
|
||||
|
||||
let [name, before, after] = parameters.deep_unpack();
|
||||
|
||||
if (!after) {
|
||||
if (this._entriesByDBusName.hasOwnProperty(before)) {
|
||||
this.removeEntry(this._entriesByDBusName[before]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_handleUpdateRequest: function(senderName, parameters) {
|
||||
if (!senderName || !parameters) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [appUri, properties] = parameters.deep_unpack();
|
||||
let appId = appUri.replace(/(^\w+:|^)\/\//, '');
|
||||
let entry = this.lookupByDBusName(senderName);
|
||||
|
||||
if (entry) {
|
||||
entry.setDBusName(senderName);
|
||||
entry.update(properties);
|
||||
} else {
|
||||
let entry = new AppProgress(senderName, appId, properties);
|
||||
this.addEntry(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ProgressManager.prototype);
|
||||
|
||||
var AppProgress = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.AppProgress',
|
||||
|
||||
_init: function(dbusName, appId, properties) {
|
||||
this._dbusName = dbusName;
|
||||
this._appId = appId;
|
||||
this._count = 0;
|
||||
this._countVisible = false;
|
||||
this._progress = 0.0;
|
||||
this._progressVisible = false;
|
||||
this._urgent = false;
|
||||
this.update(properties);
|
||||
},
|
||||
|
||||
appId: function() {
|
||||
return this._appId;
|
||||
},
|
||||
|
||||
dbusName: function() {
|
||||
return this._dbusName;
|
||||
},
|
||||
|
||||
count: function() {
|
||||
return this._count;
|
||||
},
|
||||
|
||||
setCount: function(count) {
|
||||
if (this._count != count) {
|
||||
this._count = count;
|
||||
this.emit('count-changed', this._count);
|
||||
}
|
||||
},
|
||||
|
||||
countVisible: function() {
|
||||
return this._countVisible;
|
||||
},
|
||||
|
||||
setCountVisible: function(countVisible) {
|
||||
if (this._countVisible != countVisible) {
|
||||
this._countVisible = countVisible;
|
||||
this.emit('count-visible-changed', this._countVisible);
|
||||
}
|
||||
},
|
||||
|
||||
progress: function() {
|
||||
return this._progress;
|
||||
},
|
||||
|
||||
setProgress: function(progress) {
|
||||
if (this._progress != progress) {
|
||||
this._progress = progress;
|
||||
this.emit('progress-changed', this._progress);
|
||||
}
|
||||
},
|
||||
|
||||
progressVisible: function() {
|
||||
return this._progressVisible;
|
||||
},
|
||||
|
||||
setProgressVisible: function(progressVisible) {
|
||||
if (this._progressVisible != progressVisible) {
|
||||
this._progressVisible = progressVisible;
|
||||
this.emit('progress-visible-changed', this._progressVisible);
|
||||
}
|
||||
},
|
||||
|
||||
urgent: function() {
|
||||
return this._urgent;
|
||||
},
|
||||
|
||||
setUrgent: function(urgent) {
|
||||
if (this._urgent != urgent) {
|
||||
this._urgent = urgent;
|
||||
this.emit('urgent-changed', this._urgent);
|
||||
}
|
||||
},
|
||||
|
||||
setDBusName: function(dbusName) {
|
||||
if (this._dbusName != dbusName) {
|
||||
let oldName = this._dbusName;
|
||||
this._dbusName = dbusName;
|
||||
this.emit('dbus-name-changed', oldName);
|
||||
}
|
||||
},
|
||||
|
||||
update: function(other) {
|
||||
if (other instanceof AppProgress) {
|
||||
this.setDBusName(other.dbusName())
|
||||
this.setCount(other.count());
|
||||
this.setCountVisible(other.countVisible());
|
||||
this.setProgress(other.progress());
|
||||
this.setProgressVisible(other.progressVisible())
|
||||
this.setUrgent(other.urgent());
|
||||
} else {
|
||||
for (let property in other) {
|
||||
if (other.hasOwnProperty(property)) {
|
||||
if (property == 'count') {
|
||||
this.setCount(other[property].get_int64());
|
||||
} else if (property == 'count-visible') {
|
||||
this.setCountVisible(Me.settings.get_boolean('progress-show-count') && other[property].get_boolean());
|
||||
} else if (property == 'progress') {
|
||||
this.setProgress(other[property].get_double());
|
||||
} else if (property == 'progress-visible') {
|
||||
this.setProgressVisible(Me.settings.get_boolean('progress-show-bar') && other[property].get_boolean());
|
||||
} else if (property == 'urgent') {
|
||||
this.setUrgent(other[property].get_boolean());
|
||||
} else {
|
||||
// Not implemented yet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(AppProgress.prototype);
|
||||
|
||||
|
||||
var ProgressIndicator = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.ProgressIndicator',
|
||||
|
||||
_init: function(source, progressManager) {
|
||||
this._source = source;
|
||||
this._progressManager = progressManager;
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
|
||||
this._sourceDestroyId = this._source.actor.connect('destroy', () => {
|
||||
this._signalsHandler.destroy();
|
||||
});
|
||||
|
||||
this._notificationBadgeLabel = new St.Label({ style_class: 'badge' });
|
||||
this._notificationBadgeBin = new St.Bin({
|
||||
child: this._notificationBadgeLabel,
|
||||
x_align: Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.START,
|
||||
x_expand: true, y_expand: true
|
||||
});
|
||||
this._notificationBadgeLabel.add_style_class_name('notification-badge');
|
||||
this._notificationBadgeCount = 0;
|
||||
this._notificationBadgeBin.hide();
|
||||
|
||||
this._source._dtpIconContainer.add_child(this._notificationBadgeBin);
|
||||
this._source._dtpIconContainer.connect('notify::allocation', this.updateNotificationBadge.bind(this));
|
||||
|
||||
this._progressManagerEntries = [];
|
||||
this._progressManager.lookupById(this._source.app.id).forEach(
|
||||
(entry) => {
|
||||
this.insertEntry(entry);
|
||||
}
|
||||
);
|
||||
|
||||
this._signalsHandler.add([
|
||||
this._progressManager,
|
||||
'progress-entry-added',
|
||||
this._onEntryAdded.bind(this)
|
||||
], [
|
||||
this._progressManager,
|
||||
'progress-entry-removed',
|
||||
this._onEntryRemoved.bind(this)
|
||||
]);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._source.actor.disconnect(this._sourceDestroyId);
|
||||
this._signalsHandler.destroy();
|
||||
},
|
||||
|
||||
_onEntryAdded: function(appProgress, entry) {
|
||||
if (!entry || !entry.appId())
|
||||
return;
|
||||
if (this._source && this._source.app && this._source.app.id == entry.appId()) {
|
||||
this.insertEntry(entry);
|
||||
}
|
||||
},
|
||||
|
||||
_onEntryRemoved: function(appProgress, entry) {
|
||||
if (!entry || !entry.appId())
|
||||
return;
|
||||
|
||||
if (this._source && this._source.app && this._source.app.id == entry.appId()) {
|
||||
this.removeEntry(entry);
|
||||
}
|
||||
},
|
||||
|
||||
updateNotificationBadge: function() {
|
||||
this._source.updateNumberOverlay(this._notificationBadgeBin);
|
||||
this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE;
|
||||
},
|
||||
|
||||
_notificationBadgeCountToText: function(count) {
|
||||
if (count <= 9999) {
|
||||
return count.toString();
|
||||
} else if (count < 1e5) {
|
||||
let thousands = count / 1e3;
|
||||
return thousands.toFixed(1).toString() + "k";
|
||||
} else if (count < 1e6) {
|
||||
let thousands = count / 1e3;
|
||||
return thousands.toFixed(0).toString() + "k";
|
||||
} else if (count < 1e8) {
|
||||
let millions = count / 1e6;
|
||||
return millions.toFixed(1).toString() + "M";
|
||||
} else if (count < 1e9) {
|
||||
let millions = count / 1e6;
|
||||
return millions.toFixed(0).toString() + "M";
|
||||
} else {
|
||||
let billions = count / 1e9;
|
||||
return billions.toFixed(1).toString() + "B";
|
||||
}
|
||||
},
|
||||
|
||||
setNotificationBadge: function(count) {
|
||||
this._notificationBadgeCount = count;
|
||||
let text = this._notificationBadgeCountToText(count);
|
||||
this._notificationBadgeLabel.set_text(text);
|
||||
},
|
||||
|
||||
toggleNotificationBadge: function(activate) {
|
||||
if (activate && this._notificationBadgeCount > 0) {
|
||||
this.updateNotificationBadge();
|
||||
this._notificationBadgeBin.show();
|
||||
}
|
||||
else
|
||||
this._notificationBadgeBin.hide();
|
||||
},
|
||||
|
||||
_showProgressOverlay: function() {
|
||||
if (this._progressOverlayArea) {
|
||||
this._updateProgressOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true});
|
||||
this._progressOverlayArea.add_style_class_name('progress-bar');
|
||||
this._progressOverlayArea.connect('repaint', () => {
|
||||
this._drawProgressOverlay(this._progressOverlayArea);
|
||||
});
|
||||
|
||||
this._source._iconContainer.add_child(this._progressOverlayArea);
|
||||
let node = this._progressOverlayArea.get_theme_node();
|
||||
|
||||
let [hasColor, color] = node.lookup_color('-progress-bar-background', false);
|
||||
if (hasColor)
|
||||
this._progressbar_background = color
|
||||
else
|
||||
this._progressbar_background = new Clutter.Color({red: 204, green: 204, blue: 204, alpha: 255});
|
||||
|
||||
[hasColor, color] = node.lookup_color('-progress-bar-border', false);
|
||||
if (hasColor)
|
||||
this._progressbar_border = color;
|
||||
else
|
||||
this._progressbar_border = new Clutter.Color({red: 230, green: 230, blue: 230, alpha: 255});
|
||||
|
||||
this._updateProgressOverlay();
|
||||
},
|
||||
|
||||
_hideProgressOverlay: function() {
|
||||
if (this._progressOverlayArea)
|
||||
this._progressOverlayArea.destroy();
|
||||
|
||||
this._progressOverlayArea = null;
|
||||
this._progressbar_background = null;
|
||||
this._progressbar_border = null;
|
||||
},
|
||||
|
||||
_updateProgressOverlay: function() {
|
||||
|
||||
if (this._progressOverlayArea) {
|
||||
this._progressOverlayArea.queue_repaint();
|
||||
}
|
||||
},
|
||||
|
||||
_drawProgressOverlay: function(area) {
|
||||
let scaleFactor = Utils.getScaleFactor();
|
||||
let [surfaceWidth, surfaceHeight] = area.get_surface_size();
|
||||
let cr = area.get_context();
|
||||
|
||||
let iconSize = this._source.icon.iconSize * scaleFactor;
|
||||
|
||||
let x = Math.floor((surfaceWidth - iconSize) / 2);
|
||||
let y = Math.floor((surfaceHeight - iconSize) / 2);
|
||||
|
||||
let lineWidth = Math.floor(1.0 * scaleFactor);
|
||||
let padding = Math.floor(iconSize * 0.05);
|
||||
let width = iconSize - 2.0*padding;
|
||||
let height = Math.floor(Math.min(18.0*scaleFactor, 0.20*iconSize));
|
||||
x += padding;
|
||||
y += iconSize - height - padding;
|
||||
|
||||
cr.setLineWidth(lineWidth);
|
||||
|
||||
// Draw the outer stroke
|
||||
let stroke = new Cairo.LinearGradient(0, y, 0, y + height);
|
||||
let fill = null;
|
||||
stroke.addColorStopRGBA(1.0, 1.0, 1.0, 1.0, 0.1);
|
||||
stroke.addColorStopRGBA(1.0, 1.0, 1.0, 1.0, 0.1);
|
||||
Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill);
|
||||
|
||||
// Draw the background
|
||||
x += lineWidth;
|
||||
y += lineWidth;
|
||||
width -= 2.0*lineWidth;
|
||||
height -= 2.0*lineWidth;
|
||||
|
||||
stroke = null;
|
||||
fill = new Cairo.LinearGradient(0, y, 0, y + height);
|
||||
fill.addColorStopRGBA(0.0, 0.0, 0.0, 0.0, 1.0);
|
||||
fill.addColorStopRGBA(0.0, 0.0, 0.0, 0.0, 1.0);
|
||||
Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill);
|
||||
|
||||
// Draw the finished bar
|
||||
x += lineWidth;
|
||||
y += lineWidth;
|
||||
width -= 2.0*lineWidth;
|
||||
height -= 2.0*lineWidth;
|
||||
|
||||
let finishedWidth = Math.ceil(this._progress * width);
|
||||
|
||||
let bg = this._progressbar_background;
|
||||
let bd = this._progressbar_border;
|
||||
|
||||
stroke = Cairo.SolidPattern.createRGBA(bd.red/255, bd.green/255, bd.blue/255, bd.alpha/255);
|
||||
fill = Cairo.SolidPattern.createRGBA(bg.red/255, bg.green/255, bg.blue/255, bg.alpha/255);
|
||||
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
Utils.drawRoundedLine(cr, x + lineWidth/2.0 + width - finishedWidth, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill);
|
||||
else
|
||||
Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill);
|
||||
|
||||
cr.$dispose();
|
||||
},
|
||||
|
||||
setProgress: function(progress) {
|
||||
this._progress = Math.min(Math.max(progress, 0.0), 1.0);
|
||||
this._updateProgressOverlay();
|
||||
},
|
||||
|
||||
toggleProgressOverlay: function(activate) {
|
||||
if (activate) {
|
||||
this._showProgressOverlay();
|
||||
}
|
||||
else {
|
||||
this._hideProgressOverlay();
|
||||
}
|
||||
},
|
||||
|
||||
insertEntry: function(appProgress) {
|
||||
if (!appProgress || this._progressManagerEntries.indexOf(appProgress) !== -1)
|
||||
return;
|
||||
|
||||
this._progressManagerEntries.push(appProgress);
|
||||
this._selectEntry(appProgress);
|
||||
},
|
||||
|
||||
removeEntry: function(appProgress) {
|
||||
if (!appProgress || this._progressManagerEntries.indexOf(appProgress) == -1)
|
||||
return;
|
||||
|
||||
this._progressManagerEntries.splice(this._progressManagerEntries.indexOf(appProgress), 1);
|
||||
|
||||
if (this._progressManagerEntries.length > 0) {
|
||||
this._selectEntry(this._progressManagerEntries[this._progressManagerEntries.length-1]);
|
||||
} else {
|
||||
this.setNotificationBadge(0);
|
||||
this.toggleNotificationBadge(false);
|
||||
this.setProgress(0);
|
||||
this.toggleProgressOverlay(false);
|
||||
this.setUrgent(false);
|
||||
}
|
||||
},
|
||||
|
||||
_selectEntry: function(appProgress) {
|
||||
if (!appProgress)
|
||||
return;
|
||||
|
||||
this._signalsHandler.removeWithLabel('progress-entry');
|
||||
|
||||
this._signalsHandler.addWithLabel('progress-entry',
|
||||
[
|
||||
appProgress,
|
||||
'count-changed',
|
||||
(appProgress, value) => {
|
||||
this.setNotificationBadge(value);
|
||||
}
|
||||
], [
|
||||
appProgress,
|
||||
'count-visible-changed',
|
||||
(appProgress, value) => {
|
||||
this.toggleNotificationBadge(value);
|
||||
}
|
||||
], [
|
||||
appProgress,
|
||||
'progress-changed',
|
||||
(appProgress, value) => {
|
||||
this.setProgress(value);
|
||||
}
|
||||
], [
|
||||
appProgress,
|
||||
'progress-visible-changed',
|
||||
(appProgress, value) => {
|
||||
this.toggleProgressOverlay(value);
|
||||
}
|
||||
], [
|
||||
appProgress,
|
||||
'urgent-changed',
|
||||
(appProgress, value) => this.setUrgent(value)
|
||||
]);
|
||||
|
||||
this.setNotificationBadge(appProgress.count());
|
||||
this.toggleNotificationBadge(appProgress.countVisible());
|
||||
this.setProgress(appProgress.progress());
|
||||
this.toggleProgressOverlay(appProgress.progressVisible());
|
||||
|
||||
this._isUrgent = false;
|
||||
},
|
||||
|
||||
setUrgent(urgent) {
|
||||
const icon = this._source.icon._iconBin;
|
||||
if (urgent) {
|
||||
if (!this._isUrgent) {
|
||||
icon.set_pivot_point(0.5, 0.5);
|
||||
this._source.iconAnimator.addAnimation(icon, 'dance');
|
||||
this._isUrgent = true;
|
||||
}
|
||||
} else {
|
||||
if (this._isUrgent) {
|
||||
this._source.iconAnimator.removeAnimation(icon, 'dance');
|
||||
this._isUrgent = false;
|
||||
}
|
||||
icon.rotation_angle_z = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
265
proximity.js
265
proximity.js
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
//timeout intervals
|
||||
const MIN_UPDATE_MS = 200;
|
||||
|
||||
//timeout names
|
||||
const T1 = 'limitUpdateTimeout';
|
||||
|
||||
var Mode = {
|
||||
ALL_WINDOWS: 0,
|
||||
FOCUSED_WINDOWS: 1,
|
||||
MAXIMIZED_WINDOWS: 2
|
||||
};
|
||||
|
||||
var ProximityWatch = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.ProximityWatch',
|
||||
|
||||
_init: function(actor, mode, xThreshold, yThreshold, handler) {
|
||||
this.actor = actor;
|
||||
this.overlap = 0;
|
||||
this.mode = mode;
|
||||
this.threshold = [xThreshold, yThreshold];
|
||||
this.handler = handler;
|
||||
|
||||
this._allocationChangedId = actor.connect('notify::allocation', () => this._update());
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.actor.disconnect(this._allocationChangedId);
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
this.monitorIndex = Main.layoutManager.findIndexForActor(this.actor);
|
||||
|
||||
this._updateWatchRect();
|
||||
},
|
||||
|
||||
_updateWatchRect: function() {
|
||||
let [actorX, actorY] = this.actor.get_position();
|
||||
|
||||
this.rect = new Meta.Rectangle({
|
||||
x: actorX - this.threshold[0],
|
||||
y: actorY - this.threshold[1],
|
||||
width: this.actor.width + this.threshold[0] * 2,
|
||||
height: this.actor.height + this.threshold[1] * 2
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
var ProximityManager = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.ProximityManager',
|
||||
|
||||
_init: function() {
|
||||
this._counter = 1;
|
||||
this._watches = {};
|
||||
this._focusedWindowInfo = null;
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler();
|
||||
|
||||
this._bindSignals();
|
||||
this._setFocusedWindow();
|
||||
},
|
||||
|
||||
createWatch: function(actor, mode, xThreshold, yThreshold, handler) {
|
||||
let watch = new ProximityWatch(actor, mode, xThreshold, yThreshold, handler);
|
||||
|
||||
this._watches[this._counter] = watch;
|
||||
this.update();
|
||||
|
||||
return this._counter++;
|
||||
},
|
||||
|
||||
removeWatch: function(id) {
|
||||
if (this._watches[id]) {
|
||||
this._watches[id].destroy();
|
||||
delete this._watches[id];
|
||||
}
|
||||
},
|
||||
|
||||
update: function() {
|
||||
this._queueUpdate(true);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._signalsHandler.destroy();
|
||||
this._timeoutsHandler.destroy();
|
||||
this._disconnectFocusedWindow();
|
||||
Object.keys(this._watches).forEach(id => this.removeWatch(id));
|
||||
},
|
||||
|
||||
_bindSignals: function() {
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
global.window_manager,
|
||||
'switch-workspace',
|
||||
() => Object.keys(this._watches).forEach(id => this._watches[id].overlap = 0)
|
||||
],
|
||||
[
|
||||
Main.overview,
|
||||
'hidden',
|
||||
() => this._queueUpdate()
|
||||
],
|
||||
[
|
||||
global.display,
|
||||
'notify::focus-window',
|
||||
() => {
|
||||
this._setFocusedWindow();
|
||||
this._queueUpdate();
|
||||
}
|
||||
],
|
||||
[
|
||||
global.window_group,
|
||||
[
|
||||
'actor-added',
|
||||
'actor-removed'
|
||||
],
|
||||
() => this._queueUpdate()
|
||||
]
|
||||
);
|
||||
},
|
||||
|
||||
_setFocusedWindow: function() {
|
||||
this._disconnectFocusedWindow();
|
||||
|
||||
let focusedWindow = global.display.focus_window;
|
||||
|
||||
if (focusedWindow) {
|
||||
let focusedWindowInfo = this._getFocusedWindowInfo(focusedWindow);
|
||||
|
||||
if (focusedWindowInfo && this._checkIfHandledWindowType(focusedWindowInfo.metaWindow)) {
|
||||
focusedWindowInfo.allocationId = focusedWindowInfo.window.connect('notify::allocation', () => this._queueUpdate());
|
||||
focusedWindowInfo.destroyId = focusedWindowInfo.window.connect('destroy', () => this._disconnectFocusedWindow(true));
|
||||
|
||||
this._focusedWindowInfo = focusedWindowInfo;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getFocusedWindowInfo: function(focusedWindow) {
|
||||
let window = focusedWindow.get_compositor_private();
|
||||
let focusedWindowInfo;
|
||||
|
||||
if (window) {
|
||||
focusedWindowInfo = { window: window };
|
||||
focusedWindowInfo.metaWindow = focusedWindowInfo.window.get_meta_window();
|
||||
|
||||
if (focusedWindow.is_attached_dialog()) {
|
||||
let mainMetaWindow = focusedWindow.get_transient_for();
|
||||
|
||||
if (focusedWindowInfo.metaWindow.get_frame_rect().height < mainMetaWindow.get_frame_rect().height) {
|
||||
focusedWindowInfo.window = mainMetaWindow.get_compositor_private();
|
||||
focusedWindowInfo.metaWindow = mainMetaWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return focusedWindowInfo;
|
||||
},
|
||||
|
||||
_disconnectFocusedWindow: function(destroy) {
|
||||
if (this._focusedWindowInfo && !destroy) {
|
||||
this._focusedWindowInfo.window.disconnect(this._focusedWindowInfo.allocationId);
|
||||
this._focusedWindowInfo.window.disconnect(this._focusedWindowInfo.destroyId);
|
||||
}
|
||||
|
||||
this._focusedWindowInfo = null;
|
||||
},
|
||||
|
||||
_getHandledWindows: function() {
|
||||
return global.get_window_actors()
|
||||
.filter(w => w.visible)
|
||||
.map(w => w.get_meta_window())
|
||||
.filter(mw => this._checkIfHandledWindow(mw));
|
||||
},
|
||||
|
||||
_checkIfHandledWindow: function(metaWindow) {
|
||||
return metaWindow && !metaWindow.minimized &&
|
||||
this._checkIfHandledWindowType(metaWindow);
|
||||
},
|
||||
|
||||
_checkIfHandledWindowType: function(metaWindow) {
|
||||
let metaWindowType = metaWindow.get_window_type();
|
||||
|
||||
//https://www.roojs.org/seed/gir-1.2-gtk-3.0/seed/Meta.WindowType.html
|
||||
return metaWindowType <= Meta.WindowType.SPLASHSCREEN &&
|
||||
metaWindowType != Meta.WindowType.DESKTOP;
|
||||
},
|
||||
|
||||
_queueUpdate: function(noDelay) {
|
||||
if (!noDelay && this._timeoutsHandler.getId(T1)) {
|
||||
//limit the number of updates
|
||||
this._pendingUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this._timeoutsHandler.add([T1, MIN_UPDATE_MS, () => this._endLimitUpdate()]);
|
||||
|
||||
let metaWindows = this._getHandledWindows();
|
||||
|
||||
Object.keys(this._watches).forEach(id => {
|
||||
let watch = this._watches[id];
|
||||
let overlap = this._update(watch, metaWindows);
|
||||
|
||||
if (overlap !== watch.overlap) {
|
||||
watch.handler(overlap);
|
||||
watch.overlap = overlap;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_endLimitUpdate: function() {
|
||||
if (this._pendingUpdate) {
|
||||
this._pendingUpdate = false;
|
||||
this._queueUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
_update: function(watch, metaWindows) {
|
||||
if (watch.mode === Mode.FOCUSED_WINDOWS) {
|
||||
return (this._focusedWindowInfo &&
|
||||
this._checkIfHandledWindow(this._focusedWindowInfo.metaWindow) &&
|
||||
this._checkProximity(this._focusedWindowInfo.metaWindow, watch));
|
||||
} else if (watch.mode === Mode.MAXIMIZED_WINDOWS) {
|
||||
return metaWindows.some(mw => mw.maximized_vertically && mw.maximized_horizontally &&
|
||||
mw.get_monitor() == watch.monitorIndex);
|
||||
}
|
||||
|
||||
//Mode.ALL_WINDOWS
|
||||
return metaWindows.some(mw => this._checkProximity(mw, watch));
|
||||
},
|
||||
|
||||
_checkProximity: function(metaWindow, watch) {
|
||||
return metaWindow.get_frame_rect().overlap(watch.rect);
|
||||
},
|
||||
});
|
||||
@@ -17,6 +17,8 @@
|
||||
<value value='4' nick='CYCLE-MIN'/>
|
||||
<value value='5' nick='QUIT'/>
|
||||
<value value='6' nick='TOGGLE-SHOWPREVIEW'/>
|
||||
<value value='7' nick='TOGGLE-CYCLE'/>
|
||||
<value value='8' nick='TOGGLE-SPREAD'/>
|
||||
</enum>
|
||||
<enum id='org.gnome.shell.extensions.zorin-taskbar.scrollAction'>
|
||||
<value value='0' nick='NOTHING'/>
|
||||
@@ -42,13 +44,6 @@
|
||||
<value value='1' nick='FOCUSED_WINDOWS'/>
|
||||
<value value='2' nick='MAXIMIZED_WINDOWS'/>
|
||||
</enum>
|
||||
<enum id='org.gnome.shell.extensions.zorin-taskbar.fontWeight'>
|
||||
<value value='0' nick='inherit'/>
|
||||
<value value='1' nick='normal'/>
|
||||
<value value='2' nick='lighter'/>
|
||||
<value value='3' nick='bold'/>
|
||||
<value value='4' nick='bolder'/>
|
||||
</enum>
|
||||
<enum id='org.gnome.shell.extensions.zorin-taskbar.hotkeyNumberKeys'>
|
||||
<value value='0' nick='NUM_ROW'/>
|
||||
<value value='1' nick='NUM_KEYPAD'/>
|
||||
@@ -57,7 +52,7 @@
|
||||
<schema path="/org/gnome/shell/extensions/zorin-taskbar/" id="org.gnome.shell.extensions.zorin-taskbar">
|
||||
<key name="panel-position" enum="org.gnome.shell.extensions.zorin-taskbar.position">
|
||||
<default>'BOTTOM'</default>
|
||||
<summary>Panel position</summary>
|
||||
<summary>Panel position (Deprecated)</summary>
|
||||
<description>Panel is shown on the Bottom or Top of the screen.</description>
|
||||
</key>
|
||||
<key name="panel-element-positions-monitors-sync" type="b">
|
||||
@@ -75,9 +70,24 @@
|
||||
<summary>Panel element positions</summary>
|
||||
<description>Panel element positions (JSON).</description>
|
||||
</key>
|
||||
<key type="s" name="panel-lengths">
|
||||
<default>'{}'</default>
|
||||
<summary>Percentages of screen edge for panel to span, -1 for dynamic length (dock mode)</summary>
|
||||
<description>Length of the panels, in percent (JSON).</description>
|
||||
</key>
|
||||
<key type="s" name="panel-anchors">
|
||||
<default>'{}'</default>
|
||||
<summary>Positions along screen edge</summary>
|
||||
<description>Where to show the panels if it is not the full length of the screen edge (JSON).</description>
|
||||
</key>
|
||||
<key type="s" name="panel-sizes">
|
||||
<default>'{}'</default>
|
||||
<summary>Panel sizes</summary>
|
||||
<description>Sizes of panels, in pixels.</description>
|
||||
</key>
|
||||
<key type="i" name="panel-size">
|
||||
<default>48</default>
|
||||
<summary>Panel size</summary>
|
||||
<summary>Panel size (Deprecated)</summary>
|
||||
<description>Set the size of the panel.</description>
|
||||
</key>
|
||||
<key name="dot-style-focused" enum="org.gnome.shell.extensions.zorin-taskbar.dotStyle">
|
||||
@@ -90,26 +100,55 @@
|
||||
<summary>Style of the running indicator (unfocused)</summary>
|
||||
<description>Style of the running indicator for the icon for applications which are not currently focused</description>
|
||||
</key>
|
||||
<key type="b" name="dot-color-dominant">
|
||||
<key type="b" name="stockgs-keep-top-panel">
|
||||
<default>false</default>
|
||||
<summary>Running indicator dominant color</summary>
|
||||
<description>Whether to use the app icon's dominant color for .app-well-running-dot</description>
|
||||
<summary>Keep top panel</summary>
|
||||
<description>Whether to keep the stock gnome-shell top panel</description>
|
||||
</key>
|
||||
<key type="b" name="stockgs-panelbtn-click-only">
|
||||
<default>false</default>
|
||||
<summary>Panel menu buttons require click</summary>
|
||||
<description>Whether to activate the panel menu buttons on hover or on click</description>
|
||||
</key>
|
||||
<key type="b" name="taskbar-locked">
|
||||
<default>false</default>
|
||||
<summary>Lock the taskbar</summary>
|
||||
<description>Specifies if the user can modify the taskbar</description>
|
||||
</key>
|
||||
<key type="i" name="panel-margin">
|
||||
<default>0</default>
|
||||
<summary>Panel margin</summary>
|
||||
</key>
|
||||
<key type="b" name="trans-use-custom-opacity">
|
||||
<default>false</default>
|
||||
<summary>Custom background color</summary>
|
||||
<description>Replace current theme background color for the panel</description>
|
||||
</key>
|
||||
<key type="b" name="trans-use-dynamic-opacity">
|
||||
<default>false</default>
|
||||
<summary>Dynamic opacity</summary>
|
||||
<description>Enable dynamic opacity</description>
|
||||
</key>
|
||||
<key type="d" name="trans-panel-opacity">
|
||||
<default>0.4</default>
|
||||
<summary>Panel opacity</summary>
|
||||
<description>Custom opacity for the panel</description>
|
||||
</key>
|
||||
<key name="trans-dynamic-behavior" enum="org.gnome.shell.extensions.zorin-taskbar.proximityBehavior">
|
||||
<default>'ALL_WINDOWS'</default>
|
||||
<summary>Dynamic opacity behavior</summary>
|
||||
<description>Dictates which window type affects the panel opacity</description>
|
||||
</key>
|
||||
<key type="i" name="trans-dynamic-distance">
|
||||
<default>20</default>
|
||||
<summary>Distance to change opacity</summary>
|
||||
<description>The distance a window needs to be from the panel to change opacity</description>
|
||||
</key>
|
||||
<key type="d" name="trans-dynamic-anim-target">
|
||||
<default>0.8</default>
|
||||
<summary>Modified panel opacity</summary>
|
||||
<description>Modified opacity for the panel when a window is near</description>
|
||||
</key>
|
||||
<key type="b" name="intellihide">
|
||||
<default>false</default>
|
||||
<summary>Intellihide</summary>
|
||||
@@ -132,9 +171,13 @@
|
||||
</key>
|
||||
<key type="b" name="intellihide-show-in-fullscreen">
|
||||
<default>false</default>
|
||||
<summary>Intellihide pressure</summary>
|
||||
<summary>Allow revealing the panel while in fullscreen</summary>
|
||||
<description>Allow the panel to be revealed while an application is in fullscreen mode</description>
|
||||
</key>
|
||||
<key type="b" name="intellihide-show-on-notification">
|
||||
<default>false</default>
|
||||
<summary>Reveal the panel on notification</summary>
|
||||
</key>
|
||||
<key type="b" name="intellihide-only-secondary">
|
||||
<default>false</default>
|
||||
<summary>Intellihide only secondary</summary>
|
||||
@@ -150,20 +193,9 @@
|
||||
<summary>Keybinding toggle intellihide</summary>
|
||||
<description>Keybinding to reveal the panel while in intellihide mode</description>
|
||||
</key>
|
||||
<key type="b" name="intellihide-floating-rounded-theme">
|
||||
<default>true</default>
|
||||
<summary>Floating rounded theme</summary>
|
||||
<description>Display the panel with a floating rounded theme while in intellihide mode</description>
|
||||
</key>
|
||||
<key type="b" name="show-apps-override-escape">
|
||||
<default>true</default>
|
||||
<summary>Override escape key</summary>
|
||||
<description>Override the escape key to return to the desktop when entering the overview using the Show Applications button</description>
|
||||
</key>
|
||||
<key type="b" name="animate-show-apps">
|
||||
<default>true</default>
|
||||
<summary>Animate Show Applications from the desktop</summary>
|
||||
<description>Animate Show Applications from the desktop</description>
|
||||
<key type="i" name="intellihide-persisted-state">
|
||||
<default>-1</default>
|
||||
<summary>Persisted intellihide hold status. -1 means the option is disabled</summary>
|
||||
</key>
|
||||
<key type="as" name="panel-context-menu-commands">
|
||||
<default>[]</default>
|
||||
@@ -216,7 +248,7 @@
|
||||
<description>Peek a window upon hover for some time</description>
|
||||
</key>
|
||||
<key type="i" name="window-preview-size">
|
||||
<default>250</default>
|
||||
<default>200</default>
|
||||
<summary>Window previews size</summary>
|
||||
<description>Preferred window previews size</description>
|
||||
</key>
|
||||
@@ -250,11 +282,6 @@
|
||||
<summary>Display panels on all monitors</summary>
|
||||
<description>Specifies if a panel is shown on every monitors</description>
|
||||
</key>
|
||||
<key type="ai" name="available-monitors">
|
||||
<default>[]</default>
|
||||
<summary>Available monitors</summary>
|
||||
<description>Available gnome-shell (Mutter) monitors, internal use</description>
|
||||
</key>
|
||||
<key type="b" name="isolate-monitors">
|
||||
<default>false</default>
|
||||
<summary>Provide monitor isolation</summary>
|
||||
@@ -263,7 +290,7 @@
|
||||
<key type="b" name="show-favorites-all-monitors">
|
||||
<default>true</default>
|
||||
<summary>Display the favorites on all monitors</summary>
|
||||
<description>Specifies if every panel should display the favorite applications. If false, the favorite appplications are only displayed on the primary monitor.</description>
|
||||
<description>Specifies if every panel should display the favorite applications. If false, the favorite applications are only displayed on the primary monitor.</description>
|
||||
</key>
|
||||
<key type="b" name="customize-click">
|
||||
<default>true</default>
|
||||
@@ -308,6 +335,10 @@
|
||||
<summary>Middle click preview to close window</summary>
|
||||
<description>Middle click on the window preview to close that window</description>
|
||||
</key>
|
||||
<key type="i" name="global-border-radius">
|
||||
<default>0</default>
|
||||
<summary>Border radius of panel elements</summary>
|
||||
</key>
|
||||
<key type="s" name="shortcut-text">
|
||||
<default>"<Super>q"</default>
|
||||
<summary>Keybinding to show the dock and the number overlay.</summary>
|
||||
@@ -339,7 +370,7 @@
|
||||
<description>When multiple instances of the application are available, show their window previews</description>
|
||||
</key>
|
||||
<key name="shortcut-num-keys" enum="org.gnome.shell.extensions.zorin-taskbar.hotkeyNumberKeys">
|
||||
<default>'BOTH'</default>
|
||||
<default>'NUM_ROW'</default>
|
||||
<summary>Hotkeys number keys</summary>
|
||||
<description>Which number keys are used for the hotkeys</description>
|
||||
</key>
|
||||
@@ -773,5 +804,9 @@
|
||||
<summary>Show badge count on app icon</summary>
|
||||
<description>Whether to show badge count overlay on app icon, for supported applications.</description>
|
||||
</key>
|
||||
<key type="s" name="target-prefs-page">
|
||||
<default>''</default>
|
||||
<summary>The preferences page name to display</summary>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
|
||||
2030
src/appIcons.js
Normal file
2030
src/appIcons.js
Normal file
File diff suppressed because it is too large
Load Diff
171
src/desktopIconsIntegration.js
Normal file
171
src/desktopIconsIntegration.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* The code in this file is distributed under a "1-clause BSD license",
|
||||
* which makes it compatible with GPLv2 and GPLv3 too, and others.
|
||||
*
|
||||
* License text:
|
||||
*
|
||||
* Copyright (C) 2021 Sergio Costas (rastersoft@gmail.com)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Integration class
|
||||
*
|
||||
* This class must be added to other extensions in order to integrate
|
||||
* them with Desktop Icons NG. It allows an extension to notify how much margin
|
||||
* it uses in each side of each monitor.
|
||||
*
|
||||
* DON'T SEND PATCHES TO THIS FILE TO THE EXTENSION MAINTAINER. SEND THEM TO
|
||||
* DESKTOP ICONS NG MAINTAINER: https://gitlab.com/rastersoft/desktop-icons-ng
|
||||
*
|
||||
* In the *enable()* function, create a *DesktopIconsUsableAreaClass()*
|
||||
* object with
|
||||
*
|
||||
* new DesktopIconsIntegration.DesktopIconsUsableAreaClass(object);
|
||||
*
|
||||
* Now, in the *disable()* function just call to the *destroy()* method before
|
||||
* nullifying the pointer. You must create a new object in enable() the next
|
||||
* time the extension is enabled.
|
||||
*
|
||||
* In your code, every time you change the margins, you should call first to
|
||||
* *resetMargins()* method to clear the current margins, and then call to
|
||||
* *setMargins(...)* method as many times as you need to set the margins in each
|
||||
* monitor. You don't need to call it for all the monitors, only for those where
|
||||
* you are painting something. If you don't set values for a monitor, they will
|
||||
* be considered zero.
|
||||
*
|
||||
* The margins values are relative to the monitor border.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
import GLib from 'gi://GLib'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js'
|
||||
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'
|
||||
|
||||
const IDENTIFIER_UUID = '130cbc66-235c-4bd6-8571-98d2d8bba5e2'
|
||||
|
||||
export class DesktopIconsUsableAreaClass {
|
||||
_checkIfExtensionIsEnabled(extension) {
|
||||
return (
|
||||
extension?.state === ExtensionUtils.ExtensionState.ENABLED ||
|
||||
extension?.state === ExtensionUtils.ExtensionState.ACTIVE
|
||||
)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const Me = Extension.lookupByURL(import.meta.url)
|
||||
this._UUID = Me.uuid
|
||||
this._extensionManager = Main.extensionManager
|
||||
this._timedMarginsID = 0
|
||||
this._margins = {}
|
||||
this._emID = this._extensionManager.connect(
|
||||
'extension-state-changed',
|
||||
(_obj, extension) => {
|
||||
if (!extension) return
|
||||
|
||||
// If an extension is being enabled and lacks the DesktopIconsUsableArea object, we can avoid launching a refresh
|
||||
if (this._checkIfExtensionIsEnabled(extension)) {
|
||||
this._sendMarginsToExtension(extension)
|
||||
return
|
||||
}
|
||||
// if the extension is being disabled, we must do a full refresh, because if there were other extensions originally
|
||||
// loaded after that extension, those extensions will be disabled and enabled again without notification
|
||||
this._changedMargins()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or updates the top, bottom, left and right margins for a
|
||||
* monitor. Values are measured from the monitor border (and NOT from
|
||||
* the workspace border).
|
||||
*
|
||||
* @param {int} monitor Monitor number to which set the margins.
|
||||
* A negative value means "the primary monitor".
|
||||
* @param {int} top Top margin in pixels
|
||||
* @param {int} bottom Bottom margin in pixels
|
||||
* @param {int} left Left margin in pixels
|
||||
* @param {int} right Right margin in pixels
|
||||
*/
|
||||
setMargins(monitor, top, bottom, left, right) {
|
||||
this._margins[monitor] = {
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
this._changedMargins()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current margins. Must be called before configuring the monitors
|
||||
* margins with setMargins().
|
||||
*/
|
||||
resetMargins() {
|
||||
this._margins = {}
|
||||
this._changedMargins()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all the signals and removes the margins.
|
||||
*/
|
||||
destroy() {
|
||||
if (this._emID) {
|
||||
this._extensionManager.disconnect(this._emID)
|
||||
this._emID = 0
|
||||
}
|
||||
if (this._timedMarginsID) {
|
||||
GLib.source_remove(this._timedMarginsID)
|
||||
this._timedMarginsID = 0
|
||||
}
|
||||
this._margins = null
|
||||
this._changedMargins()
|
||||
}
|
||||
|
||||
_changedMargins() {
|
||||
if (this._timedMarginsID) {
|
||||
GLib.source_remove(this._timedMarginsID)
|
||||
}
|
||||
this._timedMarginsID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
|
||||
this._sendMarginsToAll()
|
||||
this._timedMarginsID = 0
|
||||
return GLib.SOURCE_REMOVE
|
||||
})
|
||||
}
|
||||
|
||||
_sendMarginsToAll() {
|
||||
this._extensionManager
|
||||
.getUuids()
|
||||
.forEach((uuid) =>
|
||||
this._sendMarginsToExtension(this._extensionManager.lookup(uuid)),
|
||||
)
|
||||
}
|
||||
|
||||
_sendMarginsToExtension(extension) {
|
||||
// check that the extension is an extension that has the logic to accept
|
||||
// working margins
|
||||
if (!this._checkIfExtensionIsEnabled(extension)) return
|
||||
|
||||
const usableArea = extension?.stateObj?.DesktopIconsUsableArea
|
||||
if (usableArea?.uuid === IDENTIFIER_UUID)
|
||||
usableArea.setMarginsForExtension(this._UUID, this._margins)
|
||||
}
|
||||
}
|
||||
166
src/extension.js
Normal file
166
src/extension.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Gio from 'gi://Gio'
|
||||
import GLib from 'gi://GLib'
|
||||
import Shell from 'gi://Shell'
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import { EventEmitter } from 'resource:///org/gnome/shell/misc/signals.js'
|
||||
import {
|
||||
Extension,
|
||||
gettext as _,
|
||||
} from 'resource:///org/gnome/shell/extensions/extension.js'
|
||||
import * as PanelSettings from './panelSettings.js'
|
||||
|
||||
import * as PanelManager from './panelManager.js'
|
||||
import * as AppIcons from './appIcons.js'
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
const ZORIN_DASH_UUID = 'zorin-dash@zorinos.com'
|
||||
export const ZORIN_TILING_SHELL_UUID = 'zorin-tiling-shell@zorinos.com'
|
||||
|
||||
let panelManager
|
||||
let startupCompleteHandler
|
||||
let zorinDashDelayId = 0
|
||||
|
||||
export let DTP_EXTENSION = null
|
||||
export let SETTINGS = null
|
||||
export let TILINGSETTINGS = null
|
||||
export let SHELLSETTINGS = null
|
||||
export let DESKTOPSETTINGS = null
|
||||
export let TERMINALSETTINGS = null
|
||||
export let NOTIFICATIONSSETTINGS = null
|
||||
export let PERSISTENTSTORAGE = null
|
||||
export let EXTENSION_PATH = null
|
||||
export let tracker = null
|
||||
|
||||
export default class ZorinTaskbarExtension extends Extension {
|
||||
constructor(metadata) {
|
||||
super(metadata)
|
||||
|
||||
//create an object that persists until gnome-shell is restarted, even if the extension is disabled
|
||||
PERSISTENTSTORAGE = {}
|
||||
}
|
||||
|
||||
async enable() {
|
||||
DTP_EXTENSION = this
|
||||
SETTINGS = this.getSettings('org.gnome.shell.extensions.zorin-taskbar')
|
||||
try {
|
||||
TILINGSETTINGS = new Gio.Settings({
|
||||
schema_id: 'org.gnome.shell.extensions.zorin-tiling-shell',
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
SHELLSETTINGS = new Gio.Settings({
|
||||
schema_id: 'org.gnome.shell',
|
||||
})
|
||||
DESKTOPSETTINGS = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.interface',
|
||||
})
|
||||
TERMINALSETTINGS = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.default-applications.terminal',
|
||||
})
|
||||
NOTIFICATIONSSETTINGS = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.notifications',
|
||||
})
|
||||
EXTENSION_PATH = this.path
|
||||
|
||||
tracker = Shell.WindowTracker.get_default()
|
||||
|
||||
//create a global object that can emit signals and conveniently expose functionalities to other extensions
|
||||
global.zorinTaskbar = new EventEmitter()
|
||||
|
||||
await PanelSettings.init(SETTINGS)
|
||||
|
||||
// To remove later, try to map settings using monitor indexes to monitor ids
|
||||
PanelSettings.adjustMonitorSettings(SETTINGS)
|
||||
|
||||
this.enableGlobalStyles()
|
||||
|
||||
let completeEnable = () => {
|
||||
panelManager = new PanelManager.PanelManager()
|
||||
panelManager.enable()
|
||||
zorinDashDelayId = 0
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
}
|
||||
|
||||
// disable Zorin Dash if present
|
||||
if (Main.extensionManager._extensionOrder.indexOf(ZORIN_DASH_UUID) >= 0) {
|
||||
let disabled = global.settings.get_strv('disabled-extensions')
|
||||
|
||||
if (disabled.indexOf(ZORIN_DASH_UUID) < 0) {
|
||||
disabled.push(ZORIN_DASH_UUID)
|
||||
global.settings.set_strv('disabled-extensions', disabled)
|
||||
|
||||
// wait a bit so Zorin Dash can disable itself and restore the showappsbutton
|
||||
zorinDashDelayId = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
200,
|
||||
completeEnable,
|
||||
)
|
||||
}
|
||||
} else completeEnable()
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (zorinDashDelayId) GLib.Source.remove(zorinDashDelayId)
|
||||
|
||||
panelManager.disable()
|
||||
|
||||
DTP_EXTENSION = null
|
||||
SETTINGS = null
|
||||
SHELLSETTINGS = null
|
||||
DESKTOPSETTINGS = null
|
||||
TERMINALSETTINGS = null
|
||||
panelManager = null
|
||||
|
||||
delete global.zorinTaskbar
|
||||
|
||||
this.disableGlobalStyles()
|
||||
|
||||
AppIcons.resetRecentlyClickedApp()
|
||||
|
||||
if (startupCompleteHandler) {
|
||||
Main.layoutManager.disconnect(startupCompleteHandler)
|
||||
startupCompleteHandler = null
|
||||
}
|
||||
}
|
||||
|
||||
resetGlobalStyles() {
|
||||
this.disableGlobalStyles()
|
||||
this.enableGlobalStyles()
|
||||
}
|
||||
|
||||
enableGlobalStyles() {
|
||||
let globalBorderRadius = SETTINGS.get_int('global-border-radius')
|
||||
|
||||
if (globalBorderRadius)
|
||||
Main.layoutManager.uiGroup.add_style_class_name(
|
||||
`br${globalBorderRadius * 5}`,
|
||||
)
|
||||
}
|
||||
|
||||
disableGlobalStyles() {
|
||||
;['br5', 'br10', 'br15', 'br20', 'br25'].forEach((c) =>
|
||||
Main.layoutManager.uiGroup.remove_style_class_name(c),
|
||||
)
|
||||
}
|
||||
}
|
||||
529
src/intellihide.js
Normal file
529
src/intellihide.js
Normal file
@@ -0,0 +1,529 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
import Clutter from 'gi://Clutter'
|
||||
import Meta from 'gi://Meta'
|
||||
import Shell from 'gi://Shell'
|
||||
import St from 'gi://St'
|
||||
|
||||
import * as GrabHelper from 'resource:///org/gnome/shell/ui/grabHelper.js'
|
||||
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js'
|
||||
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js'
|
||||
|
||||
import * as Proximity from './proximity.js'
|
||||
import * as Utils from './utils.js'
|
||||
import { SETTINGS, NOTIFICATIONSSETTINGS } from './extension.js'
|
||||
|
||||
const INTELLIHIDE_PRESSURE_THRESHOLD = 100
|
||||
const INTELLIHIDE_PRESSURE_TIME = 1000
|
||||
const INTELLIHIDE_ANIMATION_TIME = 200
|
||||
const INTELLIHIDE_CLOSE_DELAY = 400
|
||||
const INTELLIHIDE_ENABLE_START_DELAY = 2000
|
||||
|
||||
//timeout intervals
|
||||
const CHECK_POINTER_MS = 200
|
||||
const CHECK_GRAB_MS = 400
|
||||
const POST_ANIMATE_MS = 50
|
||||
const MIN_UPDATE_MS = 250
|
||||
|
||||
//timeout names
|
||||
const T1 = 'checkGrabTimeout'
|
||||
const T2 = 'limitUpdateTimeout'
|
||||
const T3 = 'postAnimateTimeout'
|
||||
const T4 = 'enableStartTimeout'
|
||||
|
||||
const SIDE_CONTROLS_ANIMATION_TIME =
|
||||
OverviewControls.SIDE_CONTROLS_ANIMATION_TIME /
|
||||
(OverviewControls.SIDE_CONTROLS_ANIMATION_TIME > 1 ? 1000 : 1)
|
||||
|
||||
export const Hold = {
|
||||
NONE: 0,
|
||||
TEMPORARY: 1,
|
||||
PERMANENT: 2,
|
||||
NOTIFY: 4,
|
||||
}
|
||||
|
||||
export const Intellihide = class {
|
||||
constructor(dtpPanel) {
|
||||
this._dtpPanel = dtpPanel
|
||||
this._panelBox = dtpPanel.panelBox
|
||||
this._panelManager = dtpPanel.panelManager
|
||||
this._proximityManager = this._panelManager.proximityManager
|
||||
this._holdStatus = Hold.NONE
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler()
|
||||
|
||||
this._intellihideChangedId = SETTINGS.connect('changed::intellihide', () =>
|
||||
this._changeEnabledStatus(),
|
||||
)
|
||||
this._intellihideOnlySecondaryChangedId = SETTINGS.connect(
|
||||
'changed::intellihide-only-secondary',
|
||||
() => this._changeEnabledStatus(),
|
||||
)
|
||||
|
||||
this.enabled = false
|
||||
this._changeEnabledStatus()
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.enabled = true
|
||||
this._monitor = this._dtpPanel.monitor
|
||||
this._animationDestination = -1
|
||||
this._pendingUpdate = false
|
||||
this._hoveredOut = false
|
||||
this._windowOverlap = false
|
||||
this._translationProp =
|
||||
'translation_' + (this._dtpPanel.checkIfVertical() ? 'x' : 'y')
|
||||
|
||||
this._panelBox.translation_y = 0
|
||||
this._panelBox.translation_x = 0
|
||||
|
||||
this._setTrackPanel(true)
|
||||
this._bindGeneralSignals()
|
||||
|
||||
if (SETTINGS.get_boolean('intellihide-hide-from-windows')) {
|
||||
this._proximityWatchId = this._proximityManager.createWatch(
|
||||
this._panelBox.get_parent(),
|
||||
this._dtpPanel.monitor.index,
|
||||
Proximity.Mode[SETTINGS.get_string('intellihide-behaviour')],
|
||||
0,
|
||||
0,
|
||||
(overlap) => {
|
||||
this._windowOverlap = overlap
|
||||
this._queueUpdatePanelPosition()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
this._setRevealMechanism()
|
||||
|
||||
let lastState = SETTINGS.get_int('intellihide-persisted-state')
|
||||
|
||||
if (lastState > -1) {
|
||||
this._holdStatus = lastState
|
||||
|
||||
if (lastState == Hold.NONE && Main.layoutManager._startingUp)
|
||||
this._signalsHandler.add([
|
||||
this._panelBox,
|
||||
'notify::mapped',
|
||||
() => this._hidePanel(true),
|
||||
])
|
||||
else this._queueUpdatePanelPosition()
|
||||
} else
|
||||
// -1 means that the option to persist hold isn't activated, so normal start
|
||||
this._timeoutsHandler.add([
|
||||
T4,
|
||||
INTELLIHIDE_ENABLE_START_DELAY,
|
||||
() => this._queueUpdatePanelPosition(),
|
||||
])
|
||||
}
|
||||
|
||||
disable(reset) {
|
||||
this.enabled = false
|
||||
|
||||
if (this._proximityWatchId) {
|
||||
this._proximityManager.removeWatch(this._proximityWatchId)
|
||||
}
|
||||
|
||||
this._setTrackPanel(false)
|
||||
|
||||
this._signalsHandler.destroy()
|
||||
this._timeoutsHandler.destroy()
|
||||
|
||||
this._removeRevealMechanism()
|
||||
|
||||
this._revealPanel(!reset)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
SETTINGS.disconnect(this._intellihideChangedId)
|
||||
SETTINGS.disconnect(this._intellihideOnlySecondaryChangedId)
|
||||
|
||||
if (this.enabled) {
|
||||
this.disable()
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this[this._holdStatus & Hold.PERMANENT ? 'release' : 'revealAndHold'](
|
||||
Hold.PERMANENT,
|
||||
)
|
||||
}
|
||||
|
||||
revealAndHold(holdStatus) {
|
||||
if (
|
||||
!this.enabled ||
|
||||
(holdStatus == Hold.NOTIFY &&
|
||||
(!SETTINGS.get_boolean('intellihide-show-on-notification') ||
|
||||
!NOTIFICATIONSSETTINGS.get_boolean('show-banners')))
|
||||
)
|
||||
return
|
||||
|
||||
if (!this._holdStatus) this._revealPanel()
|
||||
|
||||
this._holdStatus |= holdStatus
|
||||
|
||||
this._maybePersistHoldStatus()
|
||||
}
|
||||
|
||||
release(holdStatus) {
|
||||
if (!this.enabled) return
|
||||
|
||||
if (this._holdStatus & holdStatus) this._holdStatus -= holdStatus
|
||||
|
||||
if (!this._holdStatus) {
|
||||
this._maybePersistHoldStatus()
|
||||
this._queueUpdatePanelPosition()
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.disable(true)
|
||||
this.enable()
|
||||
}
|
||||
|
||||
_changeEnabledStatus() {
|
||||
let intellihide = SETTINGS.get_boolean('intellihide')
|
||||
let onlySecondary = SETTINGS.get_boolean('intellihide-only-secondary')
|
||||
let enabled = intellihide && !(this._dtpPanel.isPrimary && onlySecondary)
|
||||
|
||||
if (this.enabled !== enabled) {
|
||||
this[enabled ? 'enable' : 'disable']()
|
||||
}
|
||||
}
|
||||
|
||||
_maybePersistHoldStatus() {
|
||||
if (SETTINGS.get_int('intellihide-persisted-state') > -1)
|
||||
SETTINGS.set_int(
|
||||
'intellihide-persisted-state',
|
||||
this._holdStatus & Hold.PERMANENT ? Hold.PERMANENT : Hold.NONE,
|
||||
)
|
||||
}
|
||||
|
||||
_bindGeneralSignals() {
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
this._dtpPanel.taskbar,
|
||||
['menu-closed', 'end-drag'],
|
||||
() => {
|
||||
this._panelBox.sync_hover()
|
||||
this._onHoverChanged()
|
||||
},
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::intellihide-use-pressure',
|
||||
'changed::intellihide-hide-from-windows',
|
||||
'changed::intellihide-behaviour',
|
||||
],
|
||||
() => this.reset(),
|
||||
],
|
||||
[this._panelBox, 'notify::hover', () => this._onHoverChanged()],
|
||||
[
|
||||
this._dtpPanel.taskbar.previewMenu,
|
||||
'open-state-changed',
|
||||
() => this._queueUpdatePanelPosition(),
|
||||
],
|
||||
[
|
||||
Main.overview,
|
||||
['showing', 'hiding'],
|
||||
() => this._queueUpdatePanelPosition(),
|
||||
],
|
||||
)
|
||||
|
||||
if (Meta.is_wayland_compositor()) {
|
||||
this._signalsHandler.add([
|
||||
this._panelBox,
|
||||
'notify::visible',
|
||||
() => Utils.setDisplayUnredirect(!this._panelBox.visible),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
_onHoverChanged() {
|
||||
this._hoveredOut = !this._panelBox.hover
|
||||
this._queueUpdatePanelPosition()
|
||||
}
|
||||
|
||||
_setTrackPanel(enable) {
|
||||
let actorData = Utils.getTrackedActorData(this._panelBox)
|
||||
|
||||
actorData.affectsStruts = !enable
|
||||
actorData.trackFullscreen = !enable
|
||||
|
||||
Main.layoutManager.panelBox.reactive = enable
|
||||
Main.layoutManager.panelBox.track_hover = enable
|
||||
|
||||
this._panelBox.track_hover = enable
|
||||
this._panelBox.reactive = enable
|
||||
this._panelBox.visible = enable ? enable : this._panelBox.visible
|
||||
|
||||
Main.layoutManager._queueUpdateRegions()
|
||||
}
|
||||
|
||||
_setRevealMechanism() {
|
||||
let barriers = Meta.BackendCapabilities.BARRIERS
|
||||
|
||||
if (
|
||||
(global.backend.capabilities & barriers) === barriers &&
|
||||
SETTINGS.get_boolean('intellihide-use-pressure')
|
||||
) {
|
||||
this._edgeBarrier = this._createBarrier()
|
||||
this._pressureBarrier = new Layout.PressureBarrier(
|
||||
INTELLIHIDE_PRESSURE_THRESHOLD,
|
||||
INTELLIHIDE_PRESSURE_TIME,
|
||||
Shell.ActionMode.NORMAL,
|
||||
)
|
||||
this._pressureBarrier.addBarrier(this._edgeBarrier)
|
||||
this._signalsHandler.add([
|
||||
this._pressureBarrier,
|
||||
'trigger',
|
||||
() => this._queueUpdatePanelPosition(true),
|
||||
])
|
||||
} else {
|
||||
this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(
|
||||
CHECK_POINTER_MS,
|
||||
(x, y) => this._checkMousePointer(x, y),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_removeRevealMechanism() {
|
||||
if (this._pointerWatch) {
|
||||
PointerWatcher.getPointerWatcher()._removeWatch(this._pointerWatch)
|
||||
}
|
||||
|
||||
if (this._pressureBarrier) {
|
||||
this._pressureBarrier.destroy()
|
||||
this._edgeBarrier.destroy()
|
||||
|
||||
this._pressureBarrier = 0
|
||||
}
|
||||
}
|
||||
|
||||
_createBarrier() {
|
||||
let position = this._dtpPanel.geom.position
|
||||
let opts = { backend: global.backend }
|
||||
|
||||
if (this._dtpPanel.checkIfVertical()) {
|
||||
opts.y1 = this._monitor.y
|
||||
opts.y2 = this._monitor.y + this._monitor.height
|
||||
opts.x1 = opts.x2 = this._monitor.x
|
||||
} else {
|
||||
opts.x1 = this._monitor.x
|
||||
opts.x2 = this._monitor.x + this._monitor.width
|
||||
opts.y1 = opts.y2 = this._monitor.y
|
||||
}
|
||||
|
||||
if (position == St.Side.TOP) {
|
||||
opts.directions = Meta.BarrierDirection.POSITIVE_Y
|
||||
} else if (position == St.Side.BOTTOM) {
|
||||
opts.y1 = opts.y2 = opts.y1 + this._monitor.height
|
||||
opts.directions = Meta.BarrierDirection.NEGATIVE_Y
|
||||
} else if (position == St.Side.LEFT) {
|
||||
opts.directions = Meta.BarrierDirection.POSITIVE_X
|
||||
} else {
|
||||
opts.x1 = opts.x2 = opts.x1 + this._monitor.width
|
||||
opts.directions = Meta.BarrierDirection.NEGATIVE_X
|
||||
}
|
||||
|
||||
return new Meta.Barrier(opts)
|
||||
}
|
||||
|
||||
_checkMousePointer(x, y) {
|
||||
let position = this._dtpPanel.geom.position
|
||||
|
||||
if (
|
||||
!this._panelBox.hover &&
|
||||
!Main.overview.visible &&
|
||||
((position == St.Side.TOP && y <= this._monitor.y + 1) ||
|
||||
(position == St.Side.BOTTOM &&
|
||||
y >= this._monitor.y + this._monitor.height - 1) ||
|
||||
(position == St.Side.LEFT && x <= this._monitor.x + 1) ||
|
||||
(position == St.Side.RIGHT &&
|
||||
x >= this._monitor.x + this._monitor.width - 1)) &&
|
||||
x >= this._monitor.x &&
|
||||
x < this._monitor.x + this._monitor.width &&
|
||||
y >= this._monitor.y &&
|
||||
y < this._monitor.y + this._monitor.height
|
||||
) {
|
||||
this._queueUpdatePanelPosition(true)
|
||||
}
|
||||
}
|
||||
|
||||
_queueUpdatePanelPosition(fromRevealMechanism) {
|
||||
if (
|
||||
!fromRevealMechanism &&
|
||||
this._timeoutsHandler.getId(T2) &&
|
||||
!Main.overview.visible
|
||||
) {
|
||||
//unless this is a mouse interaction or entering/leaving the overview, limit the number
|
||||
//of updates, but remember to update again when the limit timeout is reached
|
||||
this._pendingUpdate = true
|
||||
} else if (!this._holdStatus) {
|
||||
this._checkIfShouldBeVisible(fromRevealMechanism)
|
||||
? this._revealPanel()
|
||||
: this._hidePanel()
|
||||
this._timeoutsHandler.add([
|
||||
T2,
|
||||
MIN_UPDATE_MS,
|
||||
() => this._endLimitUpdate(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
_endLimitUpdate() {
|
||||
if (this._pendingUpdate) {
|
||||
this._pendingUpdate = false
|
||||
this._queueUpdatePanelPosition()
|
||||
}
|
||||
}
|
||||
|
||||
_checkIfShouldBeVisible(fromRevealMechanism) {
|
||||
if (
|
||||
Main.overview.visibleTarget ||
|
||||
this._dtpPanel.taskbar.previewMenu.opened ||
|
||||
this._dtpPanel.taskbar._dragMonitor ||
|
||||
this._panelBox.get_hover() ||
|
||||
(this._dtpPanel.geom.position == St.Side.TOP &&
|
||||
Main.layoutManager.panelBox.get_hover()) ||
|
||||
this._checkIfGrab()
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (fromRevealMechanism) {
|
||||
let mouseBtnIsPressed =
|
||||
global.get_pointer()[2] & Clutter.ModifierType.BUTTON1_MASK
|
||||
|
||||
//the user is trying to reveal the panel
|
||||
if (this._monitor.inFullscreen && !mouseBtnIsPressed) {
|
||||
return SETTINGS.get_boolean('intellihide-show-in-fullscreen')
|
||||
}
|
||||
|
||||
return !mouseBtnIsPressed
|
||||
}
|
||||
|
||||
if (!SETTINGS.get_boolean('intellihide-hide-from-windows')) {
|
||||
return this._panelBox.hover
|
||||
}
|
||||
|
||||
return !this._windowOverlap
|
||||
}
|
||||
|
||||
_checkIfGrab() {
|
||||
let isGrab
|
||||
|
||||
if (GrabHelper._grabHelperStack)
|
||||
// gnome-shell < 42
|
||||
isGrab = GrabHelper._grabHelperStack.some(
|
||||
(gh) => gh._owner == this._dtpPanel.panel,
|
||||
)
|
||||
else if (global.stage.get_grab_actor) {
|
||||
// gnome-shell >= 42
|
||||
let grabActor = global.stage.get_grab_actor()
|
||||
let sourceActor = grabActor?._sourceActor || grabActor
|
||||
|
||||
isGrab =
|
||||
sourceActor &&
|
||||
(sourceActor == Main.layoutManager.dummyCursor ||
|
||||
this._dtpPanel.statusArea.quickSettings?.menu.actor.contains(
|
||||
sourceActor,
|
||||
) ||
|
||||
this._dtpPanel.panel.contains(sourceActor))
|
||||
}
|
||||
|
||||
if (isGrab)
|
||||
//there currently is a grab on a child of the panel, check again soon to catch its release
|
||||
this._timeoutsHandler.add([
|
||||
T1,
|
||||
CHECK_GRAB_MS,
|
||||
() => this._queueUpdatePanelPosition(),
|
||||
])
|
||||
|
||||
return isGrab
|
||||
}
|
||||
|
||||
_revealPanel(immediate) {
|
||||
if (!this._panelBox.visible) {
|
||||
this._panelBox.visible = true
|
||||
this._dtpPanel.taskbar._shownInitially = false
|
||||
}
|
||||
|
||||
this._animatePanel(0, immediate)
|
||||
}
|
||||
|
||||
_hidePanel(immediate) {
|
||||
let position = this._dtpPanel.geom.position
|
||||
let size =
|
||||
this._panelBox[
|
||||
position == St.Side.LEFT || position == St.Side.RIGHT
|
||||
? 'width'
|
||||
: 'height'
|
||||
]
|
||||
let coefficient =
|
||||
position == St.Side.TOP || position == St.Side.LEFT ? -1 : 1
|
||||
|
||||
this._animatePanel(size * coefficient, immediate)
|
||||
}
|
||||
|
||||
_animatePanel(destination, immediate) {
|
||||
if (destination === this._animationDestination) return
|
||||
|
||||
Utils.stopAnimations(this._panelBox)
|
||||
this._animationDestination = destination
|
||||
|
||||
if (immediate) {
|
||||
this._panelBox[this._translationProp] = destination
|
||||
this._panelBox.visible = !destination
|
||||
} else if (destination !== this._panelBox[this._translationProp]) {
|
||||
let tweenOpts = {
|
||||
//when entering/leaving the overview, use its animation time instead of the one from the settings
|
||||
time: Main.overview.visible
|
||||
? SIDE_CONTROLS_ANIMATION_TIME
|
||||
: INTELLIHIDE_ANIMATION_TIME * 0.001,
|
||||
//only delay the animation when hiding the panel after the user hovered out
|
||||
delay:
|
||||
destination != 0 && this._hoveredOut
|
||||
? INTELLIHIDE_CLOSE_DELAY * 0.001
|
||||
: 0,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: () => {
|
||||
this._panelBox.visible = !destination
|
||||
Main.layoutManager._queueUpdateRegions()
|
||||
this._timeoutsHandler.add([
|
||||
T3,
|
||||
POST_ANIMATE_MS,
|
||||
() => this._queueUpdatePanelPosition(),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
tweenOpts[this._translationProp] = destination
|
||||
Utils.animate(this._panelBox, tweenOpts)
|
||||
}
|
||||
|
||||
this._hoveredOut = false
|
||||
}
|
||||
}
|
||||
216
src/notificationsMonitor.js
Normal file
216
src/notificationsMonitor.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
import Gio from 'gi://Gio'
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'
|
||||
import { EventEmitter } from 'resource:///org/gnome/shell/misc/signals.js'
|
||||
|
||||
import { tracker } from './extension.js'
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
const knownIdMappings = {
|
||||
'org.gnome.Evolution': [/^org\.gnome\.[eE]volution([.-].+)?$/g],
|
||||
}
|
||||
|
||||
export const NotificationsMonitor = class extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this._state = {}
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
|
||||
// pretty much useless, but might as well keep it for now
|
||||
this._launcherEntryId = Gio.DBus.session.signal_subscribe(
|
||||
null, // sender
|
||||
'com.canonical.Unity.LauncherEntry', // iface
|
||||
'Update', // member
|
||||
null, // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
(
|
||||
connection,
|
||||
senderName,
|
||||
objectPath,
|
||||
interfaceName,
|
||||
signalName,
|
||||
parameters,
|
||||
) => this._handleLauncherUpdate(senderName, parameters),
|
||||
)
|
||||
|
||||
this._signalsHandler.add([
|
||||
tracker,
|
||||
'notify::focus-app',
|
||||
() => {
|
||||
let appId = tracker.focus_app?.id
|
||||
|
||||
// reset notifications from message tray on app focus
|
||||
if (tracker.focus_app && this._state[appId])
|
||||
this._updateState(tracker.focus_app.id, this._getDefaultState(), true)
|
||||
},
|
||||
])
|
||||
this._acquireUnityDBus()
|
||||
|
||||
this._checkNotifications()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._launcherEntryId)
|
||||
Gio.DBus.session.signal_unsubscribe(this._launcherEntryId)
|
||||
|
||||
this._releaseUnityDBus()
|
||||
this._signalsHandler.destroy()
|
||||
}
|
||||
|
||||
_updateState(appId, state, ignoreMapping) {
|
||||
// depending of the notification source, some app id end
|
||||
// with ".desktop" and some don't ¯\_(ツ)_/¯
|
||||
appId = appId.replace('.desktop', '')
|
||||
|
||||
// some app have different source app id, deamon and such,
|
||||
// but it maps to a desktop app so match those here
|
||||
if (!ignoreMapping && !knownIdMappings[appId])
|
||||
appId =
|
||||
Object.keys(knownIdMappings).find((k) =>
|
||||
knownIdMappings[k].some((regex) => appId.match(regex)),
|
||||
) || appId
|
||||
|
||||
appId = `${appId}.desktop`
|
||||
this._state[appId] = this._state[appId] || this._getDefaultState()
|
||||
|
||||
if (this._mergeState(appId, state)) this.emit(`update-${appId}`)
|
||||
}
|
||||
|
||||
_getDefaultState() {
|
||||
return {
|
||||
count: 0, // Unity
|
||||
trayCount: 0, // MessageTray
|
||||
trayUrgent: false, // MessageTray
|
||||
urgent: false, // Unity add MessageTray combined
|
||||
total: 0, // Unity add MessageTray combined
|
||||
}
|
||||
}
|
||||
|
||||
getState(app) {
|
||||
return this._state[app.id]
|
||||
}
|
||||
|
||||
_mergeState(appId, state) {
|
||||
let currenState = JSON.stringify(this._state[appId])
|
||||
|
||||
this._state[appId] = Object.assign(this._state[appId], state)
|
||||
|
||||
if (tracker.focus_app?.id == appId) {
|
||||
this._state[appId].count = 0
|
||||
this._state[appId].trayCount = 0
|
||||
}
|
||||
|
||||
this._state[appId].urgent =
|
||||
state.urgent ||
|
||||
(this._state[appId].trayUrgent && this._state[appId].trayCount) ||
|
||||
false
|
||||
|
||||
this._state[appId].total =
|
||||
((this._state[appId]['count-visible'] || 0) &&
|
||||
(this._state[appId].count || 0)) + (this._state[appId].trayCount || 0)
|
||||
|
||||
return currenState != JSON.stringify(this._state[appId])
|
||||
}
|
||||
|
||||
_acquireUnityDBus() {
|
||||
if (!this._unityBusId) {
|
||||
this._unityBusId = Gio.DBus.session.own_name(
|
||||
'com.canonical.Unity',
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_releaseUnityDBus() {
|
||||
if (this._unityBusId) {
|
||||
Gio.DBus.session.unown_name(this._unityBusId)
|
||||
this._unityBusId = 0
|
||||
}
|
||||
}
|
||||
|
||||
_handleLauncherUpdate(senderName, parameters) {
|
||||
if (!senderName || !parameters) return
|
||||
|
||||
let [appUri, properties] = parameters.deep_unpack()
|
||||
let appId = appUri.replace(/(^\w+:|^)\/\//, '')
|
||||
let updates = {}
|
||||
|
||||
// https://wiki.ubuntu.com/Unity/LauncherAPI#Low_level_DBus_API:_com.canonical.Unity.LauncherEntry
|
||||
for (let property in properties)
|
||||
updates[property] = properties[property].unpack()
|
||||
|
||||
this._updateState(appId, updates)
|
||||
}
|
||||
|
||||
_checkNotifications() {
|
||||
let getSourceId = (source) =>
|
||||
source?._appId ||
|
||||
source?.app?.id ||
|
||||
(source?.policy instanceof MessageTray.NotificationApplicationPolicy &&
|
||||
source.policy.id)
|
||||
let addSource = (tray, source) => {
|
||||
let appId = getSourceId(source)
|
||||
let updateTray = () => {
|
||||
this._updateState(appId, {
|
||||
trayCount: source.count, // always source.unseenCount might be less annoying
|
||||
trayUrgent: !!source.notifications.find((n) => {
|
||||
return (
|
||||
n.urgency > MessageTray.Urgency.NORMAL ||
|
||||
source.constructor.name == 'WindowAttentionSource' // private type from gnome-shell
|
||||
)
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
if (!appId) return
|
||||
|
||||
this._signalsHandler.addWithLabel(appId, [
|
||||
source,
|
||||
'notify::count',
|
||||
updateTray,
|
||||
])
|
||||
|
||||
updateTray()
|
||||
}
|
||||
|
||||
this._signalsHandler.add(
|
||||
[Main.messageTray, 'source-added', addSource],
|
||||
[
|
||||
Main.messageTray,
|
||||
'source-removed',
|
||||
(tray, source) => {
|
||||
let appId = getSourceId(source)
|
||||
|
||||
if (appId) this._signalsHandler.removeWithLabel(appId)
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
Main.messageTray.getSources().forEach((s) => addSource(null, s))
|
||||
}
|
||||
}
|
||||
518
src/overview.js
Normal file
518
src/overview.js
Normal file
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
import * as Intellihide from './intellihide.js'
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
import Clutter from 'gi://Clutter'
|
||||
import Gio from 'gi://Gio'
|
||||
import Shell from 'gi://Shell'
|
||||
import St from 'gi://St'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as WindowManager from 'resource:///org/gnome/shell/ui/windowManager.js'
|
||||
import { WindowPreview } from 'resource:///org/gnome/shell/ui/windowPreview.js'
|
||||
import { InjectionManager } from 'resource:///org/gnome/shell/extensions/extension.js'
|
||||
import { SETTINGS } from './extension.js'
|
||||
|
||||
const GS_SWITCH_HOTKEYS_KEY = 'switch-to-application-'
|
||||
const GS_OPEN_HOTKEYS_KEY = 'open-new-window-application-'
|
||||
const OVERLAY_TIMEOUT = 750
|
||||
const SHORTCUT_TIMEOUT = 2000
|
||||
|
||||
// When the dash is shown, workspace window preview bottom labels go over it (default
|
||||
// gnome-shell behavior), but when the extension hides the dash, leave some space
|
||||
// so those labels don't go over a bottom panel
|
||||
const LABEL_MARGIN = 60
|
||||
|
||||
//timeout names
|
||||
const T1 = 'swipeEndTimeout'
|
||||
const T2 = 'numberOverlayTimeout'
|
||||
|
||||
export const Overview = class {
|
||||
constructor(panelManager) {
|
||||
this._injectionManager = new InjectionManager()
|
||||
this._numHotkeys = 10
|
||||
this._panelManager = panelManager
|
||||
}
|
||||
|
||||
enable(primaryPanel) {
|
||||
this._panel = primaryPanel
|
||||
this.taskbar = primaryPanel.taskbar
|
||||
|
||||
this._injectionsHandler = new Utils.InjectionsHandler()
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler()
|
||||
|
||||
this._optionalWorkspaceIsolation()
|
||||
this._optionalHotKeys()
|
||||
this._optionalNumberOverlay()
|
||||
|
||||
this.toggleDash()
|
||||
this._adaptAlloc()
|
||||
|
||||
this._signalsHandler.add([
|
||||
SETTINGS,
|
||||
['changed::panel-sizes'],
|
||||
() => this.toggleDash(),
|
||||
])
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._signalsHandler.destroy()
|
||||
this._injectionsHandler.destroy()
|
||||
this._timeoutsHandler.destroy()
|
||||
this._injectionManager.clear()
|
||||
|
||||
this.toggleDash(true)
|
||||
|
||||
// Remove key bindings
|
||||
this._disableHotKeys()
|
||||
this._disableExtraShortcut()
|
||||
}
|
||||
|
||||
toggleDash(visible) {
|
||||
if (visible === undefined) {
|
||||
visible = false
|
||||
}
|
||||
|
||||
let visibilityFunc = visible ? 'show' : 'hide'
|
||||
let height = visible ? -1 : LABEL_MARGIN * Utils.getScaleFactor()
|
||||
let overviewControls = Main.overview._overview._controls
|
||||
|
||||
overviewControls.dash[visibilityFunc]()
|
||||
overviewControls.dash.set_height(height)
|
||||
}
|
||||
|
||||
_adaptAlloc() {
|
||||
let overviewControls = Main.overview._overview._controls
|
||||
|
||||
this._injectionManager.overrideMethod(
|
||||
Object.getPrototypeOf(overviewControls),
|
||||
'vfunc_allocate',
|
||||
(originalAllocate) => (box) => {
|
||||
let focusedPanel = this._panel.panelManager.focusedMonitorPanel
|
||||
|
||||
if (focusedPanel) {
|
||||
let position = focusedPanel.geom.position
|
||||
let isBottom = position == St.Side.BOTTOM
|
||||
|
||||
if (focusedPanel.intellihide?.enabled) {
|
||||
// Panel intellihide is enabled (struts aren't taken into account on overview allocation),
|
||||
// dynamically modify the overview box to follow the reveal/hide animation
|
||||
let { transitioning, finalState, progress } =
|
||||
overviewControls._stateAdjustment.getStateTransitionParams()
|
||||
let size =
|
||||
focusedPanel.geom[focusedPanel.checkIfVertical() ? 'w' : 'h'] *
|
||||
(transitioning
|
||||
? Math.abs((finalState != 0 ? 0 : 1) - progress)
|
||||
: 1)
|
||||
|
||||
if (isBottom || position == St.Side.RIGHT)
|
||||
box[focusedPanel.fixedCoord.c2] -= size
|
||||
else box[focusedPanel.fixedCoord.c1] += size
|
||||
} else if (isBottom)
|
||||
// The default overview allocation takes into account external
|
||||
// struts, everywhere but the bottom where the dash is usually fixed anyway.
|
||||
// If there is a bottom panel under the dash location, give it some space here
|
||||
box.y2 -= focusedPanel.geom.outerSize
|
||||
}
|
||||
|
||||
originalAllocate.call(overviewControls, box)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Isolate overview to open new windows for inactive apps
|
||||
*/
|
||||
_optionalWorkspaceIsolation() {
|
||||
let label = 'optionalWorkspaceIsolation'
|
||||
|
||||
let enable = () => {
|
||||
this._injectionsHandler.removeWithLabel(label)
|
||||
|
||||
this._injectionsHandler.addWithLabel(label, [
|
||||
Shell.App.prototype,
|
||||
'activate',
|
||||
IsolatedOverview,
|
||||
])
|
||||
|
||||
this._signalsHandler.removeWithLabel(label)
|
||||
|
||||
this._signalsHandler.addWithLabel(label, [
|
||||
global.window_manager,
|
||||
'switch-workspace',
|
||||
() =>
|
||||
this._panel.panelManager.allPanels.forEach((p) =>
|
||||
p.taskbar.handleIsolatedWorkspaceSwitch(),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
let disable = () => {
|
||||
this._signalsHandler.removeWithLabel(label)
|
||||
this._injectionsHandler.removeWithLabel(label)
|
||||
}
|
||||
|
||||
function IsolatedOverview() {
|
||||
// These lines take care of Nautilus for icons on Desktop
|
||||
let activeWorkspace =
|
||||
Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace()
|
||||
let windows = this.get_windows().filter(
|
||||
(w) => w.get_workspace().index() == activeWorkspace.index(),
|
||||
)
|
||||
|
||||
if (
|
||||
windows.length > 0 &&
|
||||
(!(windows.length == 1 && windows[0].skip_taskbar) ||
|
||||
this.is_on_workspace(activeWorkspace))
|
||||
)
|
||||
return Main.activateWindow(windows[0])
|
||||
|
||||
return this.open_new_window(-1)
|
||||
}
|
||||
|
||||
this._signalsHandler.add([
|
||||
SETTINGS,
|
||||
'changed::isolate-workspaces',
|
||||
() => {
|
||||
this._panel.panelManager.allPanels.forEach((p) =>
|
||||
p.taskbar.resetAppIcons(),
|
||||
)
|
||||
|
||||
if (SETTINGS.get_boolean('isolate-workspaces')) enable()
|
||||
else disable()
|
||||
},
|
||||
])
|
||||
|
||||
if (SETTINGS.get_boolean('isolate-workspaces')) enable()
|
||||
}
|
||||
|
||||
// Hotkeys
|
||||
_activateApp(appIndex, modifiers) {
|
||||
let seenApps = {}
|
||||
let apps = []
|
||||
|
||||
this.taskbar._getAppIcons().forEach((appIcon) => {
|
||||
if (!seenApps[appIcon.app] || this.taskbar.allowSplitApps) {
|
||||
apps.push(appIcon)
|
||||
}
|
||||
|
||||
seenApps[appIcon.app] = (seenApps[appIcon.app] || 0) + 1
|
||||
})
|
||||
|
||||
this._showOverlay()
|
||||
|
||||
if (appIndex < apps.length) {
|
||||
let appIcon = apps[appIndex]
|
||||
let seenAppCount = seenApps[appIcon.app]
|
||||
let windowCount =
|
||||
appIcon.window || appIcon._hotkeysCycle
|
||||
? seenAppCount
|
||||
: appIcon._nWindows
|
||||
|
||||
if (
|
||||
SETTINGS.get_boolean('shortcut-previews') &&
|
||||
windowCount > 1 &&
|
||||
!(
|
||||
modifiers &
|
||||
~(Clutter.ModifierType.MOD1_MASK | Clutter.ModifierType.SUPER_MASK)
|
||||
)
|
||||
) {
|
||||
//ignore the alt (MOD1_MASK) and super key (SUPER_MASK)
|
||||
if (
|
||||
this._hotkeyPreviewCycleInfo &&
|
||||
this._hotkeyPreviewCycleInfo.appIcon != appIcon
|
||||
) {
|
||||
this._endHotkeyPreviewCycle()
|
||||
}
|
||||
|
||||
if (!this._hotkeyPreviewCycleInfo) {
|
||||
this._hotkeyPreviewCycleInfo = {
|
||||
appIcon: appIcon,
|
||||
currentWindow: appIcon.window,
|
||||
keyFocusOutId: appIcon.connect('key-focus-out', () =>
|
||||
appIcon.grab_key_focus(),
|
||||
),
|
||||
capturedEventId: global.stage.connect(
|
||||
'captured-event',
|
||||
(actor, e) => {
|
||||
if (
|
||||
e.type() == Clutter.EventType.KEY_RELEASE &&
|
||||
e.get_key_symbol() == (Clutter.KEY_Super_L || Clutter.Super_L)
|
||||
) {
|
||||
this._endHotkeyPreviewCycle(true)
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
appIcon._hotkeysCycle = appIcon.window
|
||||
appIcon.window = null
|
||||
appIcon._previewMenu.open(appIcon, true)
|
||||
appIcon.grab_key_focus()
|
||||
}
|
||||
|
||||
appIcon._previewMenu.focusNext()
|
||||
} else {
|
||||
// Activate with button = 1, i.e. same as left click
|
||||
let button = 1
|
||||
this._endHotkeyPreviewCycle()
|
||||
appIcon.activate(button, modifiers, !this.taskbar.allowSplitApps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_endHotkeyPreviewCycle(focusWindow) {
|
||||
if (this._hotkeyPreviewCycleInfo) {
|
||||
global.stage.disconnect(this._hotkeyPreviewCycleInfo.capturedEventId)
|
||||
this._hotkeyPreviewCycleInfo.appIcon.disconnect(
|
||||
this._hotkeyPreviewCycleInfo.keyFocusOutId,
|
||||
)
|
||||
|
||||
if (focusWindow) {
|
||||
this._hotkeyPreviewCycleInfo.appIcon._previewMenu.activateFocused()
|
||||
} else this._hotkeyPreviewCycleInfo.appIcon._previewMenu.close()
|
||||
|
||||
this._hotkeyPreviewCycleInfo.appIcon.window =
|
||||
this._hotkeyPreviewCycleInfo.currentWindow
|
||||
delete this._hotkeyPreviewCycleInfo.appIcon._hotkeysCycle
|
||||
this._hotkeyPreviewCycleInfo = 0
|
||||
}
|
||||
}
|
||||
|
||||
_optionalHotKeys() {
|
||||
this._hotKeysEnabled = false
|
||||
if (SETTINGS.get_boolean('hot-keys')) this._enableHotKeys()
|
||||
|
||||
this._signalsHandler.add([
|
||||
SETTINGS,
|
||||
'changed::hot-keys',
|
||||
() => {
|
||||
if (SETTINGS.get_boolean('hot-keys')) this._enableHotKeys()
|
||||
else this._disableHotKeys()
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
_resetHotkeys() {
|
||||
this._disableHotKeys()
|
||||
this._enableHotKeys()
|
||||
}
|
||||
|
||||
_enableHotKeys() {
|
||||
if (this._hotKeysEnabled) return
|
||||
|
||||
// Setup keyboard bindings for taskbar elements
|
||||
let shortcutNumKeys = SETTINGS.get_string('shortcut-num-keys')
|
||||
let bothNumKeys = shortcutNumKeys == 'BOTH'
|
||||
let numRowKeys = shortcutNumKeys == 'NUM_ROW'
|
||||
let keys = []
|
||||
let prefixModifiers = Clutter.ModifierType.SUPER_MASK
|
||||
|
||||
//3.32 introduced app hotkeys, disable them to prevent conflicts
|
||||
if (Main.wm._switchToApplication) {
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Utils.removeKeybinding(GS_SWITCH_HOTKEYS_KEY + i)
|
||||
|
||||
if (bothNumKeys || numRowKeys)
|
||||
Utils.removeKeybinding(GS_OPEN_HOTKEYS_KEY + i)
|
||||
}
|
||||
}
|
||||
|
||||
if (SETTINGS.get_string('hotkey-prefix-text') == 'SuperAlt')
|
||||
prefixModifiers |= Clutter.ModifierType.MOD1_MASK
|
||||
|
||||
if (bothNumKeys || numRowKeys) {
|
||||
keys.push('app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-') // Regular numbers
|
||||
}
|
||||
|
||||
if (bothNumKeys || shortcutNumKeys == 'NUM_KEYPAD') {
|
||||
keys.push('app-hotkey-kp-', 'app-shift-hotkey-kp-', 'app-ctrl-hotkey-kp-') // Key-pad numbers
|
||||
}
|
||||
|
||||
keys.forEach(function (key) {
|
||||
let modifiers = prefixModifiers
|
||||
|
||||
// for some reason, in gnome-shell >= 40 Clutter.get_current_event() is now empty
|
||||
// for keyboard events. Create here the modifiers that are needed in appicon.activate
|
||||
modifiers |=
|
||||
key.indexOf('-shift-') >= 0 ? Clutter.ModifierType.SHIFT_MASK : 0
|
||||
modifiers |=
|
||||
key.indexOf('-ctrl-') >= 0 ? Clutter.ModifierType.CONTROL_MASK : 0
|
||||
|
||||
for (let i = 0; i < this._numHotkeys; i++) {
|
||||
let appNum = i
|
||||
|
||||
Utils.addKeybinding(key + (i + 1), SETTINGS, () =>
|
||||
this._activateApp(appNum, modifiers),
|
||||
)
|
||||
}
|
||||
}, this)
|
||||
|
||||
this._hotKeysEnabled = true
|
||||
|
||||
if (SETTINGS.get_string('hotkeys-overlay-combo') === 'ALWAYS')
|
||||
this._toggleHotkeysNumberOverlay(true)
|
||||
}
|
||||
|
||||
_disableHotKeys() {
|
||||
if (!this._hotKeysEnabled) return
|
||||
|
||||
let shortcutNumKeys = SETTINGS.get_string('shortcut-num-keys')
|
||||
let keys = [
|
||||
'app-hotkey-',
|
||||
'app-shift-hotkey-',
|
||||
'app-ctrl-hotkey-', // Regular numbers
|
||||
'app-hotkey-kp-',
|
||||
'app-shift-hotkey-kp-',
|
||||
'app-ctrl-hotkey-kp-', // Key-pad numbers
|
||||
]
|
||||
|
||||
keys.forEach(function (key) {
|
||||
for (let i = 0; i < this._numHotkeys; i++) {
|
||||
Utils.removeKeybinding(key + (i + 1))
|
||||
}
|
||||
}, this)
|
||||
|
||||
if (Main.wm._switchToApplication) {
|
||||
let gsSettings = new Gio.Settings({
|
||||
schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA,
|
||||
})
|
||||
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Utils.addKeybinding(
|
||||
GS_SWITCH_HOTKEYS_KEY + i,
|
||||
gsSettings,
|
||||
Main.wm._switchToApplication.bind(Main.wm),
|
||||
)
|
||||
|
||||
if (shortcutNumKeys == 'BOTH' || shortcutNumKeys == 'NUM_ROW')
|
||||
Utils.addKeybinding(
|
||||
GS_OPEN_HOTKEYS_KEY + i,
|
||||
gsSettings,
|
||||
Main.wm._openNewApplicationWindow.bind(Main.wm),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this._hotKeysEnabled = false
|
||||
|
||||
this._toggleHotkeysNumberOverlay(false)
|
||||
}
|
||||
|
||||
_optionalNumberOverlay() {
|
||||
// Enable extra shortcut
|
||||
if (SETTINGS.get_boolean('hot-keys')) this._enableExtraShortcut()
|
||||
|
||||
this._signalsHandler.add(
|
||||
[SETTINGS, 'changed::hot-keys', this._checkHotkeysOptions.bind(this)],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::hotkeys-overlay-combo',
|
||||
],
|
||||
() => {
|
||||
if (
|
||||
SETTINGS.get_boolean('hot-keys') &&
|
||||
SETTINGS.get_string('hotkeys-overlay-combo') === 'ALWAYS'
|
||||
)
|
||||
this._toggleHotkeysNumberOverlay(true)
|
||||
else this._toggleHotkeysNumberOverlay(false, true)
|
||||
},
|
||||
],
|
||||
[SETTINGS, 'changed::shortcut-num-keys', () => this._resetHotkeys()],
|
||||
)
|
||||
}
|
||||
|
||||
_checkHotkeysOptions() {
|
||||
if (SETTINGS.get_boolean('hot-keys')) this._enableExtraShortcut()
|
||||
else this._disableExtraShortcut()
|
||||
}
|
||||
|
||||
_enableExtraShortcut() {
|
||||
Utils.addKeybinding('shortcut', SETTINGS, () => this._showOverlay(true))
|
||||
}
|
||||
|
||||
_disableExtraShortcut() {
|
||||
Utils.removeKeybinding('shortcut')
|
||||
}
|
||||
|
||||
_showOverlay(overlayFromShortcut) {
|
||||
//wait for intellihide timeout initialization
|
||||
if (!this._panel.intellihide) {
|
||||
return
|
||||
}
|
||||
|
||||
// Restart the counting if the shortcut is pressed again
|
||||
let hotkey_option = SETTINGS.get_string('hotkeys-overlay-combo')
|
||||
let temporarily = hotkey_option === 'TEMPORARILY'
|
||||
let timeout = overlayFromShortcut ? SHORTCUT_TIMEOUT : OVERLAY_TIMEOUT
|
||||
|
||||
if (hotkey_option === 'NEVER' || (!timeout && temporarily)) return
|
||||
|
||||
if (temporarily || overlayFromShortcut)
|
||||
this._toggleHotkeysNumberOverlay(true)
|
||||
|
||||
this._panel.intellihide.revealAndHold(Intellihide.Hold.TEMPORARY)
|
||||
|
||||
// Hide the overlay/dock after the timeout
|
||||
this._timeoutsHandler.add([
|
||||
T2,
|
||||
timeout,
|
||||
() => {
|
||||
if (hotkey_option != 'ALWAYS') {
|
||||
this._toggleHotkeysNumberOverlay(false)
|
||||
}
|
||||
|
||||
this._panel.intellihide.release(Intellihide.Hold.TEMPORARY)
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
_toggleHotkeysNumberOverlay(show, reset) {
|
||||
// this.taskbar is the primary taskbar
|
||||
this.taskbar.toggleHotkeysNumberOverlay(show)
|
||||
|
||||
if (reset) {
|
||||
// on secondary panels, show the overlay on icons matching the ones
|
||||
// found on the primary panel (see Taksbar.hotkeyAppNumbers)
|
||||
this._panelManager.allPanels.forEach((p) => {
|
||||
if (p.isPrimary) return
|
||||
|
||||
p.taskbar.toggleHotkeysNumberOverlay(show)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_onSwipeBegin() {
|
||||
this._swiping = true
|
||||
return true
|
||||
}
|
||||
|
||||
_onSwipeEnd() {
|
||||
this._timeoutsHandler.add([T1, 0, () => (this._swiping = false)])
|
||||
return true
|
||||
}
|
||||
}
|
||||
1552
src/panel.js
Normal file
1552
src/panel.js
Normal file
File diff suppressed because it is too large
Load Diff
1085
src/panelManager.js
Normal file
1085
src/panelManager.js
Normal file
File diff suppressed because it is too large
Load Diff
70
src/panelPositions.js
Normal file
70
src/panelPositions.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
export const SHOW_APPS_BTN = 'showAppsButton'
|
||||
export const ACTIVITIES_BTN = 'activitiesButton'
|
||||
export const TASKBAR = 'taskbar'
|
||||
export const DATE_MENU = 'dateMenu'
|
||||
export const SYSTEM_MENU = 'systemMenu'
|
||||
export const LEFT_BOX = 'leftBox'
|
||||
export const CENTER_BOX = 'centerBox'
|
||||
export const RIGHT_BOX = 'rightBox'
|
||||
export const DESKTOP_BTN = 'desktopButton'
|
||||
|
||||
export const STACKED_TL = 'stackedTL'
|
||||
export const STACKED_BR = 'stackedBR'
|
||||
export const CENTERED = 'centered'
|
||||
export const CENTERED_MONITOR = 'centerMonitor'
|
||||
|
||||
export const TOP = 'TOP'
|
||||
export const BOTTOM = 'BOTTOM'
|
||||
export const LEFT = 'LEFT'
|
||||
export const RIGHT = 'RIGHT'
|
||||
|
||||
export const START = 'START'
|
||||
export const MIDDLE = 'MIDDLE'
|
||||
export const END = 'END'
|
||||
|
||||
export const defaults = [
|
||||
{ element: LEFT_BOX, visible: true, position: STACKED_TL },
|
||||
{ element: SHOW_APPS_BTN, visible: false, position: STACKED_TL },
|
||||
{ element: TASKBAR, visible: true, position: STACKED_TL },
|
||||
{ element: CENTER_BOX, visible: true, position: STACKED_BR },
|
||||
{ element: ACTIVITIES_BTN, visible: true, position: STACKED_BR },
|
||||
{ element: RIGHT_BOX, visible: true, position: STACKED_BR },
|
||||
{ element: SYSTEM_MENU, visible: true, position: STACKED_BR },
|
||||
{ element: DATE_MENU, visible: true, position: STACKED_BR },
|
||||
{ element: DESKTOP_BTN, visible: false, position: STACKED_BR },
|
||||
]
|
||||
|
||||
export const anchorToPosition = {
|
||||
[START]: STACKED_TL,
|
||||
[MIDDLE]: CENTERED_MONITOR,
|
||||
[END]: STACKED_BR,
|
||||
}
|
||||
|
||||
export const optionDialogFunctions = {}
|
||||
|
||||
optionDialogFunctions[DATE_MENU] = '_showDateMenuOptions'
|
||||
optionDialogFunctions[DESKTOP_BTN] = '_showDesktopButtonOptions'
|
||||
|
||||
export function checkIfCentered(position) {
|
||||
return position == CENTERED || position == CENTERED_MONITOR
|
||||
}
|
||||
299
src/panelSettings.js
Normal file
299
src/panelSettings.js
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import Gio from 'gi://Gio'
|
||||
|
||||
import * as Pos from './panelPositions.js'
|
||||
|
||||
const displayConfigWrapper = Gio.DBusProxy.makeProxyWrapper(
|
||||
`<node>
|
||||
<interface name="org.gnome.Mutter.DisplayConfig">
|
||||
<signal name="MonitorsChanged" />
|
||||
<method name="GetCurrentState">
|
||||
<arg name="serial" direction="out" type="u" />
|
||||
<arg name="monitors" direction="out" type="a((ssss)a(siiddada{sv})a{sv})" />
|
||||
<arg name="logical_monitors" direction="out" type="a(iiduba(ssss)a{sv})" />
|
||||
<arg name="properties" direction="out" type="a{sv}" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>`,
|
||||
)
|
||||
|
||||
// the module variables here are different in the settings dialog (gjs process)
|
||||
// and in gnome-shell (gnome-shell process)
|
||||
let useCache = false
|
||||
let cache = {}
|
||||
let monitorIdToIndex = {}
|
||||
let monitorIndexToId = {}
|
||||
|
||||
export var displayConfigProxy = null
|
||||
export var availableMonitors = []
|
||||
|
||||
export async function init(settings) {
|
||||
useCache = true
|
||||
|
||||
await setMonitorsInfo(settings)
|
||||
}
|
||||
|
||||
export function clearCache(setting) {
|
||||
if (setting) {
|
||||
cache[setting] = null
|
||||
return
|
||||
}
|
||||
|
||||
cache = {}
|
||||
}
|
||||
|
||||
/** Return object representing a settings value that is stored as JSON. */
|
||||
export function getSettingsJson(settings, setting) {
|
||||
try {
|
||||
if (useCache && cache[setting]) return cache[setting]
|
||||
|
||||
let res = JSON.parse(settings.get_string(setting))
|
||||
|
||||
cache[setting] = res
|
||||
|
||||
return res
|
||||
} catch (e) {
|
||||
console.log('Error parsing positions: ' + e.message)
|
||||
}
|
||||
}
|
||||
/** Write value object as JSON to setting in settings. */
|
||||
export function setSettingsJson(settings, setting, value) {
|
||||
try {
|
||||
const json = JSON.stringify(value)
|
||||
settings.set_string(setting, json)
|
||||
cache[setting] = value
|
||||
} catch (e) {
|
||||
console.log('Error serializing setting: ' + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Previously, the monitor index was used as an id to persist per monitor
|
||||
// settings. Since these indexes are unreliable AF, switch to use the monitor
|
||||
// serial as its id while keeping it backward compatible.
|
||||
function getMonitorSetting(settings, settingName, monitorIndex, fallback) {
|
||||
let monitorId = monitorIndexToId[monitorIndex]
|
||||
|
||||
settings = getSettingsJson(settings, settingName)
|
||||
|
||||
return (
|
||||
settings[monitorId] ||
|
||||
settings[monitorIndex] ||
|
||||
settings[availableMonitors[monitorIndex]?.id] ||
|
||||
fallback
|
||||
)
|
||||
}
|
||||
|
||||
function setMonitorSetting(settings, settingName, monitorIndex, value) {
|
||||
let monitorId = monitorIndexToId[monitorIndex]
|
||||
let usedId = monitorId || monitorIndex
|
||||
|
||||
let currentSettings = getSettingsJson(settings, settingName)
|
||||
|
||||
if (monitorId) delete currentSettings[monitorIndex]
|
||||
|
||||
currentSettings[usedId] = value
|
||||
setSettingsJson(settings, settingName, currentSettings)
|
||||
}
|
||||
|
||||
/** Returns size of panel on a specific monitor, in pixels. */
|
||||
export function getPanelSize(settings, monitorIndex) {
|
||||
// Pull in deprecated setting if panel-sizes does not have setting for monitor.
|
||||
return getMonitorSetting(
|
||||
settings,
|
||||
'panel-sizes',
|
||||
monitorIndex,
|
||||
settings.get_int('panel-size') || 48,
|
||||
)
|
||||
}
|
||||
|
||||
export function setPanelSize(settings, monitorIndex, value) {
|
||||
if (!(Number.isInteger(value) && value <= 128 && value >= 16)) {
|
||||
console.log('Not setting invalid panel size: ' + value)
|
||||
return
|
||||
}
|
||||
|
||||
setMonitorSetting(settings, 'panel-sizes', monitorIndex, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of panel on a specific monitor, as a whole number percent,
|
||||
* from settings. e.g. 100, or -1 for a dynamic panel length
|
||||
*/
|
||||
export function getPanelLength(settings, monitorIndex) {
|
||||
return getMonitorSetting(settings, 'panel-lengths', monitorIndex, 100)
|
||||
}
|
||||
|
||||
export function setPanelLength(settings, monitorIndex, value) {
|
||||
if (
|
||||
!(Number.isInteger(value) && ((value <= 100 && value >= 20) || value == -1))
|
||||
) {
|
||||
console.log('Not setting invalid panel length: ' + value, new Error().stack)
|
||||
return
|
||||
}
|
||||
|
||||
setMonitorSetting(settings, 'panel-lengths', monitorIndex, value)
|
||||
}
|
||||
|
||||
/** Returns position of panel on a specific monitor. */
|
||||
export function getPanelPosition(settings, monitorIndex) {
|
||||
return getMonitorSetting(
|
||||
settings,
|
||||
'panel-positions',
|
||||
monitorIndex,
|
||||
settings.get_string('panel-position') || Pos.BOTTOM,
|
||||
)
|
||||
}
|
||||
|
||||
export function setPanelPosition(settings, monitorIndex, value) {
|
||||
if (
|
||||
!(
|
||||
value === Pos.TOP ||
|
||||
value === Pos.BOTTOM ||
|
||||
value === Pos.LEFT ||
|
||||
value === Pos.RIGHT
|
||||
)
|
||||
) {
|
||||
console.log('Not setting invalid panel position: ' + value)
|
||||
return
|
||||
}
|
||||
|
||||
setMonitorSetting(settings, 'panel-positions', monitorIndex, value)
|
||||
}
|
||||
|
||||
/** Returns anchor location of panel on a specific monitor. */
|
||||
export function getPanelAnchor(settings, monitorIndex) {
|
||||
return getMonitorSetting(settings, 'panel-anchors', monitorIndex, Pos.MIDDLE)
|
||||
}
|
||||
|
||||
export function setPanelAnchor(settings, monitorIndex, value) {
|
||||
if (!(value === Pos.START || value === Pos.MIDDLE || value === Pos.END)) {
|
||||
console.log('Not setting invalid panel anchor: ' + value)
|
||||
return
|
||||
}
|
||||
|
||||
setMonitorSetting(settings, 'panel-anchors', monitorIndex, value)
|
||||
}
|
||||
|
||||
export function getPanelElementPositions(settings, monitorIndex) {
|
||||
return getMonitorSetting(
|
||||
settings,
|
||||
'panel-element-positions',
|
||||
monitorIndex,
|
||||
Pos.defaults,
|
||||
)
|
||||
}
|
||||
|
||||
export function setPanelElementPositions(settings, monitorIndex, value) {
|
||||
setMonitorSetting(settings, 'panel-element-positions', monitorIndex, value)
|
||||
}
|
||||
|
||||
export function setMonitorsInfo(settings) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let monitorInfos = []
|
||||
let saveMonitorState = (proxy) => {
|
||||
proxy.GetCurrentStateRemote((displayInfo, e) => {
|
||||
if (e) return reject(`Error getting display state: ${e}`)
|
||||
|
||||
let ids = {}
|
||||
|
||||
//https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml#L347
|
||||
displayInfo[2].forEach((logicalMonitor, i) => {
|
||||
let [connector, vendor, product, serial] = logicalMonitor[5][0]
|
||||
let id = i
|
||||
let primary = logicalMonitor[4]
|
||||
|
||||
// if by any chance 2 monitors have the same id, use the connector string
|
||||
// instead, which should be unique but varies between x11 and wayland :(
|
||||
// worst case scenario, resort to using the dumbass index
|
||||
if (vendor && serial) id = `${vendor}-${serial}`
|
||||
|
||||
if (ids[id]) id = connector && !ids[connector] ? connector : i
|
||||
|
||||
monitorInfos.push({
|
||||
id,
|
||||
product,
|
||||
primary,
|
||||
})
|
||||
|
||||
monitorIdToIndex[id] = i
|
||||
monitorIndexToId[i] = id
|
||||
ids[id] = 1
|
||||
})
|
||||
|
||||
_saveMonitors(settings, monitorInfos)
|
||||
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
if (!displayConfigProxy)
|
||||
displayConfigProxy = new displayConfigWrapper(
|
||||
Gio.DBus.session,
|
||||
'org.gnome.Mutter.DisplayConfig',
|
||||
'/org/gnome/Mutter/DisplayConfig',
|
||||
(proxy, e) => {
|
||||
if (e) return reject(`Error creating display proxy: ${e}`)
|
||||
|
||||
saveMonitorState(proxy)
|
||||
},
|
||||
)
|
||||
else saveMonitorState(displayConfigProxy)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _saveMonitors(settings, monitorInfos) {
|
||||
/* Commented out as Zorin Taskbar always uses gnome-shell primary monitor
|
||||
let keyPrimary = 'primary-monitor'
|
||||
let dtpPrimaryMonitor = settings.get_string(keyPrimary)
|
||||
|
||||
// convert previously saved index to monitor id
|
||||
if (dtpPrimaryMonitor.match(/^\d{1,2}$/) && monitorInfos[dtpPrimaryMonitor])
|
||||
settings.set_string(keyPrimary, monitorInfos[dtpPrimaryMonitor].id)
|
||||
*/
|
||||
|
||||
availableMonitors = Object.freeze(monitorInfos)
|
||||
}
|
||||
|
||||
// this is for backward compatibility, to remove in a few versions
|
||||
export function adjustMonitorSettings(settings) {
|
||||
let updateSettings = (settingName) => {
|
||||
let monitorSettings = getSettingsJson(settings, settingName)
|
||||
let updatedSettings = {}
|
||||
|
||||
Object.keys(monitorSettings).forEach((key) => {
|
||||
let initialKey = key
|
||||
|
||||
if (key.match(/^\d{1,2}$/)) key = monitorIndexToId[key] || key
|
||||
|
||||
updatedSettings[key] = monitorSettings[initialKey]
|
||||
})
|
||||
|
||||
setSettingsJson(settings, settingName, updatedSettings)
|
||||
}
|
||||
|
||||
updateSettings('panel-sizes')
|
||||
updateSettings('panel-lengths')
|
||||
updateSettings('panel-positions')
|
||||
updateSettings('panel-anchors')
|
||||
updateSettings('panel-element-positions')
|
||||
}
|
||||
168
src/panelStyle.js
Normal file
168
src/panelStyle.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
* Ideas for recursing child actors and assigning inline styles
|
||||
* are based on code from the StatusAreaHorizontalSpacing extension
|
||||
* https://bitbucket.org/mathematicalcoffee/status-area-horizontal-spacing-gnome-shell-extension
|
||||
* mathematical.coffee@gmail.com
|
||||
*/
|
||||
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
export const PanelStyle = class {
|
||||
enable(panel) {
|
||||
this.panel = panel
|
||||
|
||||
this._applyStyles()
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._removeStyles()
|
||||
}
|
||||
|
||||
_applyStyles() {
|
||||
this._rightBoxOperations = []
|
||||
|
||||
// center box has been moved next to the right box and will be treated the same
|
||||
this._centerBoxOperations = this._rightBoxOperations
|
||||
|
||||
this._leftBoxOperations = []
|
||||
|
||||
this._applyStylesRecursively()
|
||||
|
||||
/* connect signal */
|
||||
this._rightBoxActorAddedID = this.panel._rightBox.connect(
|
||||
'child-added',
|
||||
(container, actor) => {
|
||||
if (this._rightBoxOperations.length && !this._ignoreAddedChild)
|
||||
this._recursiveApply(actor, this._rightBoxOperations)
|
||||
|
||||
this._ignoreAddedChild = 0
|
||||
},
|
||||
)
|
||||
this._centerBoxActorAddedID = this.panel._centerBox.connect(
|
||||
'child-added',
|
||||
(container, actor) => {
|
||||
if (this._centerBoxOperations.length && !this._ignoreAddedChild)
|
||||
this._recursiveApply(actor, this._centerBoxOperations)
|
||||
|
||||
this._ignoreAddedChild = 0
|
||||
},
|
||||
)
|
||||
this._leftBoxActorAddedID = this.panel._leftBox.connect(
|
||||
'child-added',
|
||||
(container, actor) => {
|
||||
if (this._leftBoxOperations.length)
|
||||
this._recursiveApply(actor, this._leftBoxOperations)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
_removeStyles() {
|
||||
/* disconnect signal */
|
||||
if (this._rightBoxActorAddedID)
|
||||
this.panel._rightBox.disconnect(this._rightBoxActorAddedID)
|
||||
if (this._centerBoxActorAddedID)
|
||||
this.panel._centerBox.disconnect(this._centerBoxActorAddedID)
|
||||
if (this._leftBoxActorAddedID)
|
||||
this.panel._leftBox.disconnect(this._leftBoxActorAddedID)
|
||||
|
||||
this._restoreOriginalStyle(this.panel._rightBox)
|
||||
this._restoreOriginalStyle(this.panel._centerBox)
|
||||
this._restoreOriginalStyle(this.panel._leftBox)
|
||||
|
||||
this._applyStylesRecursively(true)
|
||||
}
|
||||
|
||||
_applyStylesRecursively(restore) {
|
||||
/*recurse actors */
|
||||
if (this._rightBoxOperations.length) {
|
||||
// add the system menu as we move it from the rightbox to the panel to position it independently
|
||||
let children = this.panel._rightBox
|
||||
.get_children()
|
||||
.concat([
|
||||
this.panel.statusArea[Utils.getSystemMenuInfo().name].container,
|
||||
])
|
||||
for (let i in children)
|
||||
this._recursiveApply(children[i], this._rightBoxOperations, restore)
|
||||
}
|
||||
|
||||
if (this._centerBoxOperations.length) {
|
||||
// add the date menu as we move it from the centerbox to the panel to position it independently
|
||||
let children = this.panel._centerBox
|
||||
.get_children()
|
||||
.concat([this.panel.statusArea.dateMenu.container])
|
||||
for (let i in children)
|
||||
this._recursiveApply(children[i], this._centerBoxOperations, restore)
|
||||
}
|
||||
|
||||
if (this._leftBoxOperations.length) {
|
||||
let children = this.panel._leftBox.get_children()
|
||||
for (let i in children)
|
||||
this._recursiveApply(children[i], this._leftBoxOperations, restore)
|
||||
}
|
||||
}
|
||||
|
||||
_recursiveApply(actor, operations, restore) {
|
||||
for (let i in operations) {
|
||||
let o = operations[i]
|
||||
if (o.compareFn(actor))
|
||||
if (restore)
|
||||
o.restoreFn ? o.restoreFn(actor) : this._restoreOriginalStyle(actor)
|
||||
else o.applyFn(actor, i)
|
||||
}
|
||||
|
||||
if (actor.get_children) {
|
||||
let children = actor.get_children()
|
||||
for (let i in children) {
|
||||
this._recursiveApply(children[i], operations, restore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_restoreOriginalStyle(actor) {
|
||||
if (actor._dtp_original_inline_style !== undefined) {
|
||||
actor.set_style(actor._dtp_original_inline_style)
|
||||
delete actor._dtp_original_inline_style
|
||||
delete actor._dtp_style_overrides
|
||||
}
|
||||
|
||||
if (actor.has_style_class_name('panel-button')) {
|
||||
this._refreshPanelButton(actor)
|
||||
}
|
||||
}
|
||||
|
||||
_refreshPanelButton(actor) {
|
||||
if (actor.visible) {
|
||||
//force gnome 3.34+ to refresh (having problem with the -natural-hpadding)
|
||||
let parent = actor.get_parent()
|
||||
let children = parent.get_children()
|
||||
let actorIndex = 0
|
||||
|
||||
if (children.length > 1) {
|
||||
actorIndex = children.indexOf(actor)
|
||||
}
|
||||
|
||||
this._ignoreAddedChild =
|
||||
[this.panel._centerBox, this.panel._rightBox].indexOf(parent) >= 0
|
||||
|
||||
parent.remove_child(actor)
|
||||
parent.insert_child_at_index(actor, actorIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
1672
src/prefs.js
Normal file
1672
src/prefs.js
Normal file
File diff suppressed because it is too large
Load Diff
285
src/proximity.js
Normal file
285
src/proximity.js
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
import Meta from 'gi://Meta'
|
||||
import Mtk from 'gi://Mtk'
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
//timeout intervals
|
||||
const MIN_UPDATE_MS = 200
|
||||
|
||||
//timeout names
|
||||
const T1 = 'limitUpdateTimeout'
|
||||
|
||||
export const Mode = {
|
||||
ALL_WINDOWS: 0,
|
||||
FOCUSED_WINDOWS: 1,
|
||||
MAXIMIZED_WINDOWS: 2,
|
||||
}
|
||||
|
||||
export class ProximityWatch {
|
||||
constructor(actor, monitorIndex, mode, xThreshold, yThreshold, handler) {
|
||||
this.actor = actor
|
||||
this.monitorIndex = monitorIndex
|
||||
this.overlap = false
|
||||
this.mode = mode
|
||||
this.threshold = [xThreshold, yThreshold]
|
||||
this.handler = handler
|
||||
|
||||
this._allocationChangedId = actor.connect('notify::allocation', () =>
|
||||
this._updateWatchRect(),
|
||||
)
|
||||
|
||||
this._updateWatchRect()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.actor.disconnect(this._allocationChangedId)
|
||||
}
|
||||
|
||||
_updateWatchRect() {
|
||||
let [actorX, actorY] = this.actor.get_position()
|
||||
|
||||
this.rect = new Mtk.Rectangle({
|
||||
x: actorX - this.threshold[0],
|
||||
y: actorY - this.threshold[1],
|
||||
width: this.actor.width + this.threshold[0] * 2,
|
||||
height: this.actor.height + this.threshold[1] * 2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const ProximityManager = class {
|
||||
constructor() {
|
||||
this._counter = 1
|
||||
this._watches = {}
|
||||
this._focusedWindowInfo = null
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler()
|
||||
|
||||
this._bindSignals()
|
||||
this._setFocusedWindow()
|
||||
}
|
||||
|
||||
createWatch(actor, monitorIndex, mode, xThreshold, yThreshold, handler) {
|
||||
let watch = new ProximityWatch(
|
||||
actor,
|
||||
monitorIndex,
|
||||
mode,
|
||||
xThreshold,
|
||||
yThreshold,
|
||||
handler,
|
||||
)
|
||||
|
||||
this._watches[this._counter] = watch
|
||||
this.update()
|
||||
|
||||
return this._counter++
|
||||
}
|
||||
|
||||
removeWatch(id) {
|
||||
if (this._watches[id]) {
|
||||
this._watches[id].destroy()
|
||||
delete this._watches[id]
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
this._queueUpdate(true)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._signalsHandler.destroy()
|
||||
this._timeoutsHandler.destroy()
|
||||
this._disconnectFocusedWindow()
|
||||
Object.keys(this._watches).forEach((id) => this.removeWatch(id))
|
||||
}
|
||||
|
||||
_bindSignals() {
|
||||
this._signalsHandler.add(
|
||||
[global.window_manager, 'switch-workspace', () => this._queueUpdate()],
|
||||
[Main.overview, 'hidden', () => this._queueUpdate()],
|
||||
[
|
||||
global.display,
|
||||
'notify::focus-window',
|
||||
() => {
|
||||
this._setFocusedWindow()
|
||||
this._queueUpdate()
|
||||
},
|
||||
],
|
||||
[global.display, 'restacked', () => this._queueUpdate()],
|
||||
)
|
||||
}
|
||||
|
||||
_setFocusedWindow() {
|
||||
this._disconnectFocusedWindow()
|
||||
|
||||
let focusedWindow = global.display.focus_window
|
||||
|
||||
if (focusedWindow) {
|
||||
let focusedWindowInfo = this._getFocusedWindowInfo(focusedWindow)
|
||||
|
||||
if (
|
||||
focusedWindowInfo &&
|
||||
this._checkIfHandledWindowType(focusedWindowInfo.metaWindow)
|
||||
) {
|
||||
focusedWindowInfo.allocationId = focusedWindowInfo.window.connect(
|
||||
'notify::allocation',
|
||||
() => this._queueUpdate(),
|
||||
)
|
||||
focusedWindowInfo.destroyId = focusedWindowInfo.window.connect(
|
||||
'destroy',
|
||||
() => this._disconnectFocusedWindow(true),
|
||||
)
|
||||
|
||||
this._focusedWindowInfo = focusedWindowInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getFocusedWindowInfo(focusedWindow) {
|
||||
let window = focusedWindow.get_compositor_private()
|
||||
let focusedWindowInfo
|
||||
|
||||
if (window) {
|
||||
focusedWindowInfo = { window: window }
|
||||
focusedWindowInfo.metaWindow = focusedWindow
|
||||
|
||||
if (focusedWindow.is_attached_dialog()) {
|
||||
let mainMetaWindow = focusedWindow.get_transient_for()
|
||||
|
||||
if (
|
||||
focusedWindowInfo.metaWindow.get_frame_rect().height <
|
||||
mainMetaWindow.get_frame_rect().height
|
||||
) {
|
||||
focusedWindowInfo.window = mainMetaWindow.get_compositor_private()
|
||||
focusedWindowInfo.metaWindow = mainMetaWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return focusedWindowInfo
|
||||
}
|
||||
|
||||
_disconnectFocusedWindow(destroy) {
|
||||
if (this._focusedWindowInfo && !destroy) {
|
||||
this._focusedWindowInfo.window.disconnect(
|
||||
this._focusedWindowInfo.allocationId,
|
||||
)
|
||||
this._focusedWindowInfo.window.disconnect(
|
||||
this._focusedWindowInfo.destroyId,
|
||||
)
|
||||
}
|
||||
|
||||
this._focusedWindowInfo = null
|
||||
}
|
||||
|
||||
_getHandledWindows() {
|
||||
return Utils.getCurrentWorkspace()
|
||||
.list_windows()
|
||||
.filter((mw) => this._checkIfHandledWindow(mw))
|
||||
}
|
||||
|
||||
_checkIfHandledWindow(metaWindow) {
|
||||
return (
|
||||
metaWindow &&
|
||||
!metaWindow.minimized &&
|
||||
!metaWindow.customJS_ding &&
|
||||
metaWindow.window_type != Meta.WindowType.DESKTOP &&
|
||||
this._checkIfHandledWindowType(metaWindow)
|
||||
)
|
||||
}
|
||||
|
||||
_checkIfHandledWindowType(metaWindow) {
|
||||
let metaWindowType = metaWindow.get_window_type()
|
||||
|
||||
//https://www.roojs.org/seed/gir-1.2-gtk-3.0/seed/Meta.WindowType.html
|
||||
return (
|
||||
metaWindowType <= Meta.WindowType.SPLASHSCREEN &&
|
||||
metaWindowType != Meta.WindowType.DESKTOP
|
||||
)
|
||||
}
|
||||
|
||||
_queueUpdate(noDelay) {
|
||||
if (!noDelay && this._timeoutsHandler.getId(T1)) {
|
||||
//limit the number of updates
|
||||
this._pendingUpdate = true
|
||||
return
|
||||
}
|
||||
|
||||
this._timeoutsHandler.add([T1, MIN_UPDATE_MS, () => this._endLimitUpdate()])
|
||||
|
||||
let metaWindows = this._getHandledWindows()
|
||||
|
||||
Object.keys(this._watches).forEach((id) => {
|
||||
let watch = this._watches[id]
|
||||
let overlap = !!this._update(watch, metaWindows)
|
||||
|
||||
if (overlap !== watch.overlap) {
|
||||
watch.handler(overlap)
|
||||
watch.overlap = overlap
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_endLimitUpdate() {
|
||||
if (this._pendingUpdate) {
|
||||
this._pendingUpdate = false
|
||||
this._queueUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
_update(watch, metaWindows) {
|
||||
if (watch.mode === Mode.FOCUSED_WINDOWS)
|
||||
return (
|
||||
this._focusedWindowInfo &&
|
||||
this._checkIfHandledWindow(this._focusedWindowInfo.metaWindow) &&
|
||||
this._checkProximity(this._focusedWindowInfo.metaWindow, watch)
|
||||
)
|
||||
|
||||
if (watch.mode === Mode.MAXIMIZED_WINDOWS)
|
||||
return metaWindows.some(
|
||||
(mw) =>
|
||||
mw.maximized_vertically &&
|
||||
mw.maximized_horizontally &&
|
||||
mw.get_monitor() == watch.monitorIndex,
|
||||
)
|
||||
|
||||
//Mode.ALL_WINDOWS
|
||||
return metaWindows.some((mw) => this._checkProximity(mw, watch))
|
||||
}
|
||||
|
||||
_checkProximity(metaWindow, watch) {
|
||||
let windowRect = metaWindow.get_frame_rect()
|
||||
|
||||
return (
|
||||
windowRect.overlap(watch.rect) &&
|
||||
((!watch.threshold[0] && !watch.threshold[1]) ||
|
||||
metaWindow.get_monitor() == watch.monitorIndex ||
|
||||
windowRect.overlap(
|
||||
global.display.get_monitor_geometry(watch.monitorIndex),
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
202
src/stylesheet.css
Normal file
202
src/stylesheet.css
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
#zorintaskbarTaskbar .dash-item-container > StWidget,
|
||||
.zorintaskbarMainPanel .dash-item-container .show-apps {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .overview-tile,
|
||||
.zorintaskbarMainPanel .overview-tile {
|
||||
background: none;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .overview-tile .overview-label {
|
||||
/* must match TITLE_RIGHT_PADDING in apppicons.js */
|
||||
padding-right: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel .dash-item-container .show-apps .overview-icon {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#zorintaskbarTaskbar .dash-item-container .overview-tile:hover,
|
||||
#zorintaskbarTaskbar .dash-item-container .overview-tile .dtp-container .overview-icon,
|
||||
.zorintaskbarMainPanel .dash-item-container .show-apps:hover .overview-icon {
|
||||
background: none;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .overview-tile .favorite {
|
||||
/*background-color: rgba(80, 150, 255, 0.4);*/
|
||||
}
|
||||
|
||||
#zorintaskbarTaskbar .scrollview-fade {
|
||||
background-gradient-end: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.zorintaskbarSecondaryMenu {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button.vertical *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button > *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.vertical > *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.vertical .system-status-icon,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display > *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display .clock {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display {
|
||||
-natural-hpadding: 0;
|
||||
-minimum-hpadding: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList {
|
||||
spacing: 0em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .popup-menu-item {
|
||||
padding: 0;
|
||||
border-radius: 5px;
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .window-box {
|
||||
padding: 0;
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .preview-window-title {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.popup-menu.panel-menu {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#panel #panelLeft, #panel #panelCenter {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .badge {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
padding: 0.2em 0.5em;
|
||||
border-radius: 1em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin: 0 0 0 2px;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .number-overlay {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .notification-badge {
|
||||
background-color: rgba(255,0,0,0.8);
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .progress-bar {
|
||||
/* Customization of the progress bar style, e.g.:
|
||||
-progress-bar-background: rgba(0.8, 0.8, 0.8, 1);
|
||||
-progress-bar-border: rgba(0.9, 0.9, 0.9, 1); */
|
||||
}
|
||||
|
||||
.preview-container,
|
||||
#preview-menu {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* border radius, grrr no css variables in ST */
|
||||
#uiGroup.br5 .zorintaskbarPanel.dock,
|
||||
#uiGroup.br5 .show-apps,
|
||||
#uiGroup.br5 .dtp-container,
|
||||
#uiGroup.br5 .dtp-dots-container {
|
||||
border-radius: 5px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#uiGroup.br5 .zorintaskbarMainPanel .panel-button,
|
||||
#uiGroup.br5 .zorintaskbarMainPanel .panel-button.clock-display .clock {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#uiGroup.br10 .zorintaskbarPanel.dock,
|
||||
#uiGroup.br10 .show-apps,
|
||||
#uiGroup.br10 .dtp-container,
|
||||
#uiGroup.br10 .dtp-dots-container {
|
||||
border-radius: 10px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#uiGroup.br10 .zorintaskbarMainPanel .panel-button,
|
||||
#uiGroup.br10 .zorintaskbarMainPanel .panel-button.clock-display .clock {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#uiGroup.br15 .zorintaskbarPanel.dock,
|
||||
#uiGroup.br15 .show-apps,
|
||||
#uiGroup.br15 .dtp-container,
|
||||
#uiGroup.br15 .dtp-dots-container {
|
||||
border-radius: 15px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#uiGroup.br15 .zorintaskbarMainPanel .panel-button,
|
||||
#uiGroup.br15 .zorintaskbarMainPanel .panel-button.clock-display .clock {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
#uiGroup.br20 .zorintaskbarPanel.dock,
|
||||
#uiGroup.br20 .show-apps,
|
||||
#uiGroup.br20 .dtp-container,
|
||||
#uiGroup.br20 .dtp-dots-container {
|
||||
border-radius: 20px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#uiGroup.br20 .zorintaskbarMainPanel .panel-button,
|
||||
#uiGroup.br20 .zorintaskbarMainPanel .panel-button.clock-display .clock {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
#uiGroup.br25 .zorintaskbarPanel.dock,
|
||||
#uiGroup.br25 .show-apps,
|
||||
#uiGroup.br25 .dtp-container,
|
||||
#uiGroup.br25 .dtp-dots-container {
|
||||
border-radius: 25px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#uiGroup.br25 .zorintaskbarMainPanel .panel-button,
|
||||
#uiGroup.br25 .zorintaskbarMainPanel .panel-button.clock-display .clock {
|
||||
border-radius: 25px;
|
||||
}
|
||||
1479
src/taskbar.js
Normal file
1479
src/taskbar.js
Normal file
File diff suppressed because it is too large
Load Diff
199
src/transparency.js
Normal file
199
src/transparency.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
import GdkPixbuf from 'gi://GdkPixbuf'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import St from 'gi://St'
|
||||
|
||||
import * as Proximity from './proximity.js'
|
||||
import * as Utils from './utils.js'
|
||||
import { SETTINGS } from './extension.js'
|
||||
|
||||
const TRANS_DYNAMIC_DISTANCE = 20
|
||||
|
||||
export const DynamicTransparency = class {
|
||||
constructor(dtpPanel) {
|
||||
this._dtpPanel = dtpPanel
|
||||
this._proximityManager = dtpPanel.panelManager.proximityManager
|
||||
this._proximityWatchId = 0
|
||||
this.currentBackgroundColor = 0
|
||||
|
||||
this._initialPanelStyle = dtpPanel.panel.get_style()
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
this._bindSignals()
|
||||
|
||||
this._updateAllAndSet()
|
||||
this._updateProximityWatch()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._signalsHandler.destroy()
|
||||
this._proximityManager.removeWatch(this._proximityWatchId)
|
||||
|
||||
this._dtpPanel.panel.set_style(this._initialPanelStyle)
|
||||
}
|
||||
|
||||
updateExternalStyle() {
|
||||
this._setBackground()
|
||||
}
|
||||
|
||||
_bindSignals() {
|
||||
this._signalsHandler.add(
|
||||
[Utils.getStageTheme(), 'changed', () => this._updateAllAndSet()],
|
||||
[Main.overview, ['showing', 'hiding'], () => this._updateAlphaAndSet()],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::trans-use-custom-opacity',
|
||||
'changed::trans-panel-opacity',
|
||||
'changed::trans-dynamic-anim-target',
|
||||
'changed::trans-use-dynamic-opacity',
|
||||
],
|
||||
() => this._updateAlphaAndSet(),
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::trans-dynamic-behavior',
|
||||
'changed::trans-use-dynamic-opacity',
|
||||
],
|
||||
() => this._updateProximityWatch(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
_updateProximityWatch() {
|
||||
this._proximityManager.removeWatch(this._proximityWatchId)
|
||||
|
||||
if (SETTINGS.get_boolean('trans-use-dynamic-opacity')) {
|
||||
let isVertical = this._dtpPanel.checkIfVertical()
|
||||
let threshold = TRANS_DYNAMIC_DISTANCE
|
||||
|
||||
this._windowOverlap = false
|
||||
this._updateAlphaAndSet()
|
||||
|
||||
this._proximityWatchId = this._proximityManager.createWatch(
|
||||
this._dtpPanel.panelBox.get_parent(),
|
||||
this._dtpPanel.monitor.index,
|
||||
Proximity.Mode[SETTINGS.get_string('trans-dynamic-behavior')],
|
||||
isVertical ? threshold : 0,
|
||||
isVertical ? 0 : threshold,
|
||||
(overlap) => {
|
||||
this._windowOverlap = overlap
|
||||
this._updateAlphaAndSet()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_updateAllAndSet() {
|
||||
let themeBackground = this._getThemeBackground(true)
|
||||
|
||||
this._updateColor(themeBackground)
|
||||
this._updateAlpha(themeBackground)
|
||||
this._setBackground()
|
||||
this._setActorStyle()
|
||||
}
|
||||
|
||||
_updateAlphaAndSet() {
|
||||
this._updateAlpha()
|
||||
this._setBackground()
|
||||
}
|
||||
|
||||
_updateColor(themeBackground) {
|
||||
this.backgroundColorRgb = themeBackground || this._getThemeBackground()
|
||||
}
|
||||
|
||||
_updateAlpha(themeBackground) {
|
||||
if (
|
||||
this._windowOverlap &&
|
||||
!Main.overview.visibleTarget &&
|
||||
SETTINGS.get_boolean('trans-use-dynamic-opacity')
|
||||
) {
|
||||
this.alpha = SETTINGS.get_double('trans-dynamic-anim-target')
|
||||
} else {
|
||||
this.alpha = SETTINGS.get_boolean('trans-use-custom-opacity')
|
||||
? SETTINGS.get_double('trans-panel-opacity')
|
||||
: (themeBackground || this._getThemeBackground()).alpha * 0.003921569 // 1 / 255 = 0.003921569
|
||||
}
|
||||
}
|
||||
|
||||
_setBackground() {
|
||||
this.currentBackgroundColor = Utils.getrgbaColor(
|
||||
this.backgroundColorRgb,
|
||||
this.alpha,
|
||||
)
|
||||
|
||||
let transition = 'transition-duration: 300ms;'
|
||||
|
||||
this._dtpPanel.set_style(
|
||||
'background-color: ' + this.currentBackgroundColor + transition,
|
||||
)
|
||||
}
|
||||
|
||||
_setActorStyle() {
|
||||
this._dtpPanel.panel.set_style(
|
||||
'background: none; ' +
|
||||
'border-image: none; ' +
|
||||
'background-image: none; ' +
|
||||
'transition-duration: 300ms;'
|
||||
)
|
||||
}
|
||||
|
||||
_getThemeBackground(reload) {
|
||||
if (reload || !this._themeBackground) {
|
||||
let fakePanel = new St.Bin({ name: 'panel' })
|
||||
Main.uiGroup.add_child(fakePanel)
|
||||
let fakeTheme = fakePanel.get_theme_node()
|
||||
this._themeBackground =
|
||||
this._getBackgroundImageColor(fakeTheme) ||
|
||||
fakeTheme.get_background_color()
|
||||
Main.uiGroup.remove_child(fakePanel)
|
||||
}
|
||||
|
||||
return this._themeBackground
|
||||
}
|
||||
|
||||
_getBackgroundImageColor(theme) {
|
||||
let bg = null
|
||||
|
||||
try {
|
||||
let imageFile =
|
||||
theme.get_background_image() || theme.get_border_image()?.get_file()
|
||||
|
||||
if (imageFile) {
|
||||
let imageBuf = GdkPixbuf.Pixbuf.new_from_file(imageFile.get_path())
|
||||
let pixels = imageBuf.get_pixels()
|
||||
|
||||
bg = {
|
||||
red: pixels[0],
|
||||
green: pixels[1],
|
||||
blue: pixels[2],
|
||||
alpha: pixels[3],
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return bg
|
||||
}
|
||||
}
|
||||
969
src/utils.js
Normal file
969
src/utils.js
Normal file
@@ -0,0 +1,969 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
import Clutter from 'gi://Clutter'
|
||||
import Cogl from 'gi://Cogl'
|
||||
import GdkPixbuf from 'gi://GdkPixbuf'
|
||||
import Gio from 'gi://Gio'
|
||||
import GLib from 'gi://GLib'
|
||||
import Graphene from 'gi://Graphene'
|
||||
import Meta from 'gi://Meta'
|
||||
import Shell from 'gi://Shell'
|
||||
import St from 'gi://St'
|
||||
import * as Config from 'resource:///org/gnome/shell/misc/config.js'
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'
|
||||
|
||||
const SCROLL_TIME = Util.SCROLL_TIME / (Util.SCROLL_TIME > 1 ? 1000 : 1)
|
||||
|
||||
// simplify global signals and function injections handling
|
||||
// abstract class
|
||||
export const BasicHandler = class {
|
||||
constructor() {
|
||||
this._storage = new Object()
|
||||
}
|
||||
|
||||
add(/*unlimited 3-long array arguments*/) {
|
||||
// convert arguments object to array, concatenate with generic
|
||||
let args = [].concat('generic', [].slice.call(arguments))
|
||||
// call addWithLabel with ags as if they were passed arguments
|
||||
this.addWithLabel.apply(this, args)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (let label in this._storage) this.removeWithLabel(label)
|
||||
}
|
||||
|
||||
addWithLabel(label /* plus unlimited 3-long array arguments*/) {
|
||||
if (this._storage[label] == undefined) this._storage[label] = new Array()
|
||||
|
||||
// skip first element of the arguments
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
let item = this._storage[label]
|
||||
let handlers = this._create(arguments[i])
|
||||
|
||||
for (let j = 0, l = handlers.length; j < l; ++j) {
|
||||
item.push(handlers[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeWithLabel(label) {
|
||||
if (this._storage[label]) {
|
||||
for (let i = 0; i < this._storage[label].length; i++) {
|
||||
this._remove(this._storage[label][i])
|
||||
}
|
||||
|
||||
delete this._storage[label]
|
||||
}
|
||||
}
|
||||
|
||||
hasLabel(label) {
|
||||
return !!this._storage[label]
|
||||
}
|
||||
|
||||
/* Virtual methods to be implemented by subclass */
|
||||
// create single element to be stored in the storage structure
|
||||
_create() {
|
||||
throw new Error('no implementation of _create in ' + this)
|
||||
}
|
||||
|
||||
// correctly delete single element
|
||||
_remove() {
|
||||
throw new Error('no implementation of _remove in ' + this)
|
||||
}
|
||||
}
|
||||
|
||||
// Manage global signals
|
||||
export const GlobalSignalsHandler = class extends BasicHandler {
|
||||
_create(item) {
|
||||
let handlers = []
|
||||
|
||||
item[1] = [].concat(item[1])
|
||||
|
||||
for (let i = 0, l = item[1].length; i < l; ++i) {
|
||||
let object = item[0]
|
||||
let event = item[1][i]
|
||||
let callback = item[2]
|
||||
try {
|
||||
let id = object.connect(event, callback)
|
||||
|
||||
handlers.push([object, id])
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
return handlers
|
||||
}
|
||||
|
||||
_remove(item) {
|
||||
item[0].disconnect(item[1])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage function injection: both instances and prototype can be overridden
|
||||
* and restored
|
||||
*/
|
||||
export const InjectionsHandler = class extends BasicHandler {
|
||||
_create(item) {
|
||||
let object = item[0]
|
||||
let name = item[1]
|
||||
let injectedFunction = item[2]
|
||||
let original = object[name]
|
||||
|
||||
object[name] = injectedFunction
|
||||
return [[object, name, injectedFunction, original]]
|
||||
}
|
||||
|
||||
_remove(item) {
|
||||
let object = item[0]
|
||||
let name = item[1]
|
||||
let original = item[3]
|
||||
object[name] = original
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage timeouts: the added timeouts have their id reset on completion
|
||||
*/
|
||||
export const TimeoutsHandler = class extends BasicHandler {
|
||||
_create(item) {
|
||||
let name = item[0]
|
||||
let delay = item[1]
|
||||
let timeoutHandler = item[2]
|
||||
|
||||
this._remove(item)
|
||||
|
||||
this[name] = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
|
||||
this[name] = 0
|
||||
timeoutHandler()
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
})
|
||||
|
||||
return [[name]]
|
||||
}
|
||||
|
||||
remove(name) {
|
||||
this._remove([name])
|
||||
}
|
||||
|
||||
_remove(item) {
|
||||
let name = item[0]
|
||||
|
||||
if (this[name]) {
|
||||
GLib.Source.remove(this[name])
|
||||
this[name] = 0
|
||||
}
|
||||
}
|
||||
|
||||
getId(name) {
|
||||
return this[name] ? this[name] : 0
|
||||
}
|
||||
}
|
||||
|
||||
export function createBoxLayout(options) {
|
||||
if (options && 'vertical' in options) {
|
||||
let vertical = options.vertical
|
||||
|
||||
delete options.vertical
|
||||
setBoxLayoutVertical(options, vertical)
|
||||
}
|
||||
|
||||
return new St.BoxLayout(options)
|
||||
}
|
||||
|
||||
export function setBoxLayoutVertical(box, vertical) {
|
||||
if (Config.PACKAGE_VERSION >= '48')
|
||||
// https://mutter.gnome.org/clutter/enum.Orientation.html
|
||||
box.orientation = vertical ? 1 : 0
|
||||
else box.vertical = vertical
|
||||
}
|
||||
|
||||
export function getBoxLayoutVertical(box) {
|
||||
return Config.PACKAGE_VERSION >= '48' ? box.orientation == 1 : box.vertical
|
||||
}
|
||||
|
||||
// This is wrapper to maintain compatibility with GNOME-Shell 3.30+ as well as
|
||||
// previous versions.
|
||||
export const DisplayWrapper = {
|
||||
getScreen() {
|
||||
return global.screen || global.display
|
||||
},
|
||||
|
||||
getWorkspaceManager() {
|
||||
return global.screen || global.workspace_manager
|
||||
},
|
||||
|
||||
getMonitorManager() {
|
||||
return global.screen || global.backend.get_monitor_manager()
|
||||
},
|
||||
}
|
||||
|
||||
let unredirectEnabled = true
|
||||
export const setDisplayUnredirect = (enable) => {
|
||||
let v48 = Config.PACKAGE_VERSION >= '48'
|
||||
|
||||
if (enable && !unredirectEnabled)
|
||||
v48
|
||||
? global.compositor.enable_unredirect()
|
||||
: Meta.enable_unredirect_for_display(global.display)
|
||||
else if (!enable && unredirectEnabled)
|
||||
v48
|
||||
? global.compositor.disable_unredirect()
|
||||
: Meta.disable_unredirect_for_display(global.display)
|
||||
|
||||
unredirectEnabled = enable
|
||||
}
|
||||
|
||||
export const getSystemMenuInfo = function () {
|
||||
return {
|
||||
name: 'quickSettings',
|
||||
constructor: Main.panel.statusArea.quickSettings.constructor,
|
||||
}
|
||||
}
|
||||
|
||||
export function getOverviewWorkspaces() {
|
||||
let workspaces = []
|
||||
|
||||
Main.overview._overview._controls._workspacesDisplay._workspacesViews.forEach(
|
||||
(wv) =>
|
||||
(workspaces = [
|
||||
...workspaces,
|
||||
...(wv._workspaces || []), // WorkspacesDisplay --> WorkspacesView (primary monitor)
|
||||
...(wv._workspacesView?._workspaces || []), // WorkspacesDisplay --> SecondaryMonitorDisplay --> WorkspacesView
|
||||
...(wv._workspacesView?._workspace // WorkspacesDisplay --> SecondaryMonitorDisplay --> ExtraWorkspaceView
|
||||
? [wv._workspacesView?._workspace]
|
||||
: []),
|
||||
]),
|
||||
)
|
||||
|
||||
return workspaces
|
||||
}
|
||||
|
||||
export const getCurrentWorkspace = function () {
|
||||
return DisplayWrapper.getWorkspaceManager().get_active_workspace()
|
||||
}
|
||||
|
||||
export const getWorkspaceByIndex = function (index) {
|
||||
return DisplayWrapper.getWorkspaceManager().get_workspace_by_index(index)
|
||||
}
|
||||
|
||||
export const getWorkspaceCount = function () {
|
||||
return DisplayWrapper.getWorkspaceManager().n_workspaces
|
||||
}
|
||||
|
||||
export const getStageTheme = function () {
|
||||
return St.ThemeContext.get_for_stage(global.stage)
|
||||
}
|
||||
|
||||
export const getScaleFactor = function () {
|
||||
return getStageTheme().scale_factor || 1
|
||||
}
|
||||
|
||||
export const findIndex = function (array, predicate) {
|
||||
if (array) {
|
||||
if (Array.prototype.findIndex) {
|
||||
return array.findIndex(predicate)
|
||||
}
|
||||
|
||||
for (let i = 0, l = array.length; i < l; ++i) {
|
||||
if (predicate(array[i])) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
export const find = function (array, predicate) {
|
||||
let index = findIndex(array, predicate)
|
||||
|
||||
if (index > -1) {
|
||||
return array[index]
|
||||
}
|
||||
}
|
||||
|
||||
export const mergeObjects = function (main, bck) {
|
||||
for (const prop in bck) {
|
||||
if (!Object.hasOwn(main, prop) && Object.hasOwn(bck, prop)) {
|
||||
main[prop] = bck[prop]
|
||||
}
|
||||
}
|
||||
|
||||
return main
|
||||
}
|
||||
|
||||
export const getTrackedActorData = (actor) => {
|
||||
let trackedIndex = Main.layoutManager._findActor(actor)
|
||||
|
||||
if (trackedIndex >= 0) return Main.layoutManager._trackedActors[trackedIndex]
|
||||
}
|
||||
|
||||
export const getTransformedAllocation = function (actor) {
|
||||
let extents = actor.get_transformed_extents()
|
||||
let topLeft = extents.get_top_left()
|
||||
let bottomRight = extents.get_bottom_right()
|
||||
|
||||
return { x1: topLeft.x, x2: bottomRight.x, y1: topLeft.y, y2: bottomRight.y }
|
||||
}
|
||||
|
||||
export const setClip = function (actor, x, y, width, height, offsetX, offsetY) {
|
||||
actor.set_clip(offsetX || 0, offsetY || 0, width, height)
|
||||
actor.set_position(x, y)
|
||||
actor.set_size(width, height)
|
||||
}
|
||||
|
||||
export const addKeybinding = function (key, settings, handler, modes) {
|
||||
if (!Main.wm._allowedKeybindings[key]) {
|
||||
Main.wm.addKeybinding(
|
||||
key,
|
||||
settings,
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
modes || Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
|
||||
handler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const removeKeybinding = function (key) {
|
||||
if (Main.wm._allowedKeybindings[key]) {
|
||||
Main.wm.removeKeybinding(key)
|
||||
}
|
||||
}
|
||||
|
||||
export const getrgbColor = function (color) {
|
||||
color =
|
||||
typeof color === 'string' ? ColorUtils.color_from_string(color)[1] : color
|
||||
|
||||
return { red: color.red, green: color.green, blue: color.blue }
|
||||
}
|
||||
|
||||
export const getrgbaColor = function (color, alpha, offset) {
|
||||
if (alpha <= 0) {
|
||||
return 'transparent; '
|
||||
}
|
||||
|
||||
let rgb = getrgbColor(color)
|
||||
|
||||
if (offset) {
|
||||
;['red', 'green', 'blue'].forEach((k) => {
|
||||
rgb[k] = Math.min(255, Math.max(0, rgb[k] + offset))
|
||||
|
||||
if (rgb[k] == color[k]) {
|
||||
rgb[k] = Math.min(255, Math.max(0, rgb[k] - offset))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
'rgba(' +
|
||||
rgb.red +
|
||||
',' +
|
||||
rgb.green +
|
||||
',' +
|
||||
rgb.blue +
|
||||
',' +
|
||||
Math.floor(alpha * 100) * 0.01 +
|
||||
'); '
|
||||
)
|
||||
}
|
||||
|
||||
export const checkIfColorIsBright = function (color) {
|
||||
let rgb = getrgbColor(color)
|
||||
let brightness = 0.2126 * rgb.red + 0.7152 * rgb.green + 0.0722 * rgb.blue
|
||||
|
||||
return brightness > 128
|
||||
}
|
||||
|
||||
export const getMouseScrollDirection = function (event) {
|
||||
let direction
|
||||
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
case Clutter.ScrollDirection.LEFT:
|
||||
direction = 'up'
|
||||
break
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
case Clutter.ScrollDirection.RIGHT:
|
||||
direction = 'down'
|
||||
break
|
||||
}
|
||||
|
||||
return direction
|
||||
}
|
||||
|
||||
export function getAllMetaWindows() {
|
||||
return global.get_window_actors().map((w) => w.meta_window)
|
||||
}
|
||||
|
||||
export const checkIfWindowHasTransient = function (window) {
|
||||
let hasTransient
|
||||
|
||||
window.foreach_transient(() => (hasTransient = true))
|
||||
|
||||
return hasTransient
|
||||
}
|
||||
|
||||
export const activateSiblingWindow = function (
|
||||
windows,
|
||||
direction,
|
||||
startWindow,
|
||||
) {
|
||||
let windowIndex = windows.indexOf(global.display.focus_window)
|
||||
let nextWindowIndex =
|
||||
windowIndex < 0
|
||||
? startWindow
|
||||
? windows.indexOf(startWindow)
|
||||
: 0
|
||||
: windowIndex + (direction == 'up' ? -1 : 1)
|
||||
|
||||
if (nextWindowIndex == windows.length) {
|
||||
nextWindowIndex = 0
|
||||
} else if (nextWindowIndex < 0) {
|
||||
nextWindowIndex = windows.length - 1
|
||||
}
|
||||
|
||||
if (windowIndex != nextWindowIndex) {
|
||||
Main.activateWindow(windows[nextWindowIndex])
|
||||
}
|
||||
}
|
||||
|
||||
export const animateWindowOpacity = function (window, tweenOpts) {
|
||||
//there currently is a mutter bug with the windowactor opacity, starting with 3.34
|
||||
//https://gitlab.gnome.org/GNOME/mutter/issues/836
|
||||
|
||||
//since 3.36, a workaround is to use the windowactor's child for the fade animation
|
||||
//this leaves a "shadow" on the desktop, so the windowactor needs to be hidden
|
||||
//when the animation is complete
|
||||
let visible = tweenOpts.opacity > 0
|
||||
let windowActor = window
|
||||
let initialOpacity = window.opacity
|
||||
|
||||
window = windowActor.get_first_child() || windowActor
|
||||
|
||||
if (!windowActor.visible && visible) {
|
||||
window.opacity = 0
|
||||
windowActor.visible = visible
|
||||
tweenOpts.opacity = Math.min(initialOpacity, tweenOpts.opacity)
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
tweenOpts.onComplete = () => {
|
||||
windowActor.visible = visible
|
||||
window.opacity = initialOpacity
|
||||
}
|
||||
}
|
||||
|
||||
animate(window, tweenOpts)
|
||||
}
|
||||
|
||||
export const animate = function (actor, options) {
|
||||
//the original animations used Tweener instead of Clutter animations, so we
|
||||
//use "time" and "delay" properties defined in seconds, as opposed to Clutter
|
||||
//animations "duration" and "delay" which are defined in milliseconds
|
||||
if (options.delay) {
|
||||
options.delay = options.delay * 1000
|
||||
}
|
||||
|
||||
options.duration = options.time * 1000
|
||||
delete options.time
|
||||
|
||||
if (options.transition) {
|
||||
//map Tweener easing equations to Clutter animation modes
|
||||
options.mode =
|
||||
{
|
||||
easeInCubic: Clutter.AnimationMode.EASE_IN_CUBIC,
|
||||
easeInOutCubic: Clutter.AnimationMode.EASE_IN_OUT_CUBIC,
|
||||
easeInOutQuad: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
||||
easeOutQuad: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
}[options.transition] || Clutter.AnimationMode.LINEAR
|
||||
|
||||
delete options.transition
|
||||
}
|
||||
|
||||
let params = [options]
|
||||
|
||||
if ('value' in options && actor instanceof St.Adjustment) {
|
||||
params.unshift(options.value)
|
||||
delete options.value
|
||||
}
|
||||
|
||||
actor.ease.apply(actor, params)
|
||||
}
|
||||
|
||||
export const stopAnimations = function (actor) {
|
||||
actor.remove_all_transitions()
|
||||
}
|
||||
|
||||
export const getIndicators = function (delegate) {
|
||||
if (delegate instanceof St.BoxLayout) {
|
||||
return delegate
|
||||
}
|
||||
|
||||
return delegate.indicators
|
||||
}
|
||||
|
||||
export const getPoint = function (coords) {
|
||||
return new Graphene.Point(coords)
|
||||
}
|
||||
|
||||
export const notify = function (
|
||||
title,
|
||||
body,
|
||||
sourceIconName,
|
||||
notificationIcon,
|
||||
action,
|
||||
isTransient,
|
||||
) {
|
||||
let source = MessageTray.getSystemSource()
|
||||
let notification = new MessageTray.Notification({
|
||||
source,
|
||||
title,
|
||||
body,
|
||||
isTransient: isTransient || false,
|
||||
gicon: notificationIcon || null,
|
||||
})
|
||||
|
||||
if (sourceIconName) source.iconName = sourceIconName
|
||||
|
||||
if (action) {
|
||||
if (!(action instanceof Array)) {
|
||||
action = [action]
|
||||
}
|
||||
|
||||
action.forEach((a) => notification.addAction(a.text, a.func))
|
||||
}
|
||||
|
||||
source.addNotification(notification)
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a copy of the same function in utils.js, but also adjust horizontal scrolling
|
||||
* and perform few further cheks on the current value to avoid changing the values when
|
||||
* it would be clamp to the current one in any case.
|
||||
* Return the amount of shift applied
|
||||
*/
|
||||
export const ensureActorVisibleInScrollView = function (
|
||||
scrollView,
|
||||
actor,
|
||||
fadeSize,
|
||||
onComplete,
|
||||
) {
|
||||
const vadjustment = scrollView.vadjustment
|
||||
const hadjustment = scrollView.hadjustment
|
||||
let [vvalue, , vupper, , , vpageSize] = vadjustment.get_values()
|
||||
let [hvalue, , hupper, , , hpageSize] = hadjustment.get_values()
|
||||
|
||||
let [hvalue0, vvalue0] = [hvalue, vvalue]
|
||||
|
||||
let voffset = fadeSize
|
||||
let hoffset = fadeSize
|
||||
|
||||
let box = actor.get_allocation_box()
|
||||
let y1 = box.y1,
|
||||
y2 = box.y2,
|
||||
x1 = box.x1,
|
||||
x2 = box.x2
|
||||
|
||||
let parent = actor.get_parent()
|
||||
while (parent != scrollView) {
|
||||
if (!parent) throw new Error('actor not in scroll view')
|
||||
|
||||
let box = parent.get_allocation_box()
|
||||
y1 += box.y1
|
||||
y2 += box.y1
|
||||
x1 += box.x1
|
||||
x2 += box.x1
|
||||
parent = parent.get_parent()
|
||||
}
|
||||
|
||||
if (y1 < vvalue + voffset) vvalue = Math.max(0, y1 - voffset)
|
||||
else if (vvalue < vupper - vpageSize && y2 > vvalue + vpageSize - voffset)
|
||||
vvalue = Math.min(vupper - vpageSize, y2 + voffset - vpageSize)
|
||||
|
||||
if (x1 < hvalue + hoffset) hvalue = Math.max(0, x1 - hoffset)
|
||||
else if (hvalue < hupper - hpageSize && x2 > hvalue + hpageSize - hoffset)
|
||||
hvalue = Math.min(hupper - hpageSize, x2 + hoffset - hpageSize)
|
||||
|
||||
let tweenOpts = {
|
||||
time: SCROLL_TIME,
|
||||
onComplete: onComplete || (() => {}),
|
||||
transition: 'easeOutQuad',
|
||||
}
|
||||
|
||||
if (vvalue !== vvalue0) {
|
||||
animate(vadjustment, mergeObjects(tweenOpts, { value: vvalue }))
|
||||
}
|
||||
|
||||
if (hvalue !== hvalue0) {
|
||||
animate(hadjustment, mergeObjects(tweenOpts, { value: hvalue }))
|
||||
}
|
||||
|
||||
return [hvalue - hvalue0, vvalue - vvalue0]
|
||||
}
|
||||
|
||||
/**
|
||||
* ColorUtils is adapted from https://github.com/micheleg/dash-to-dock
|
||||
*/
|
||||
let colorNs = Clutter.Color ? Clutter : Cogl
|
||||
|
||||
export const ColorUtils = {
|
||||
color_from_string: colorNs.color_from_string,
|
||||
Color: colorNs.Color,
|
||||
|
||||
colorLuminance(r, g, b, dlum) {
|
||||
// Darken or brighten color by a fraction dlum
|
||||
// Each rgb value is modified by the same fraction.
|
||||
// Return "#rrggbb" strin
|
||||
|
||||
let rgbString = '#'
|
||||
|
||||
rgbString += ColorUtils._decimalToHex(
|
||||
Math.round(Math.min(Math.max(r * (1 + dlum), 0), 255)),
|
||||
2,
|
||||
)
|
||||
rgbString += ColorUtils._decimalToHex(
|
||||
Math.round(Math.min(Math.max(g * (1 + dlum), 0), 255)),
|
||||
2,
|
||||
)
|
||||
rgbString += ColorUtils._decimalToHex(
|
||||
Math.round(Math.min(Math.max(b * (1 + dlum), 0), 255)),
|
||||
2,
|
||||
)
|
||||
|
||||
return rgbString
|
||||
},
|
||||
|
||||
_decimalToHex(d, padding) {
|
||||
// Convert decimal to an hexadecimal string adding the desired padding
|
||||
|
||||
let hex = d.toString(16)
|
||||
while (hex.length < padding) hex = '0' + hex
|
||||
return hex
|
||||
},
|
||||
|
||||
HSVtoRGB(h, s, v) {
|
||||
// Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]).
|
||||
// Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
// here with h = [0,1] instead of [0, 360]
|
||||
// Accept either (h,s,v) independently or {h:h, s:s, v:v} object.
|
||||
// Return {r:r, g:g, b:b} object.
|
||||
|
||||
if (arguments.length === 1) {
|
||||
s = h.s
|
||||
v = h.v
|
||||
h = h.h
|
||||
}
|
||||
|
||||
let r, g, b
|
||||
let c = v * s
|
||||
let h1 = h * 6
|
||||
let x = c * (1 - Math.abs((h1 % 2) - 1))
|
||||
let m = v - c
|
||||
|
||||
if (h1 <= 1) (r = c + m), (g = x + m), (b = m)
|
||||
else if (h1 <= 2) (r = x + m), (g = c + m), (b = m)
|
||||
else if (h1 <= 3) (r = m), (g = c + m), (b = x + m)
|
||||
else if (h1 <= 4) (r = m), (g = x + m), (b = c + m)
|
||||
else if (h1 <= 5) (r = x + m), (g = m), (b = c + m)
|
||||
else (r = c + m), (g = m), (b = x + m)
|
||||
|
||||
return {
|
||||
r: Math.round(r * 255),
|
||||
g: Math.round(g * 255),
|
||||
b: Math.round(b * 255),
|
||||
}
|
||||
},
|
||||
|
||||
RGBtoHSV(r, g, b) {
|
||||
// Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]).
|
||||
// Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
// here with h = [0,1] instead of [0, 360]
|
||||
// Accept either (r,g,b) independently or {r:r, g:g, b:b} object.
|
||||
// Return {h:h, s:s, v:v} object.
|
||||
|
||||
if (arguments.length === 1) {
|
||||
r = r.r
|
||||
g = r.g
|
||||
b = r.b
|
||||
}
|
||||
|
||||
let h, s, v
|
||||
|
||||
let M = Math.max(r, g, b)
|
||||
let m = Math.min(r, g, b)
|
||||
let c = M - m
|
||||
|
||||
if (c == 0) h = 0
|
||||
else if (M == r) h = ((g - b) / c) % 6
|
||||
else if (M == g) h = (b - r) / c + 2
|
||||
else h = (r - g) / c + 4
|
||||
|
||||
h = h / 6
|
||||
v = M / 255
|
||||
if (M !== 0) s = c / M
|
||||
else s = 0
|
||||
|
||||
return { h: h, s: s, v: v }
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* DominantColorExtractor is adapted from https://github.com/micheleg/dash-to-dock
|
||||
*/
|
||||
let themeLoader = null
|
||||
let iconCacheMap = new Map()
|
||||
const MAX_CACHED_ITEMS = 1000
|
||||
const BATCH_SIZE_TO_DELETE = 50
|
||||
const DOMINANT_COLOR_ICON_SIZE = 64
|
||||
|
||||
export const DominantColorExtractor = class {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the pixel buffer for the current icon, if not fail gracefully
|
||||
*/
|
||||
_getIconPixBuf() {
|
||||
let iconTexture = this._app.create_icon_texture(16)
|
||||
|
||||
if (themeLoader === null) {
|
||||
themeLoader = new St.IconTheme()
|
||||
}
|
||||
|
||||
// Unable to load the icon texture, use fallback
|
||||
if (iconTexture instanceof St.Icon === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
iconTexture = iconTexture.get_gicon()
|
||||
|
||||
// Unable to load the icon texture, use fallback
|
||||
if (iconTexture === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (iconTexture instanceof Gio.FileIcon) {
|
||||
// Use GdkPixBuf to load the pixel buffer from the provided file path
|
||||
return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path())
|
||||
}
|
||||
|
||||
// Get the pixel buffer from the icon theme
|
||||
if (iconTexture instanceof Gio.ThemedIcon) {
|
||||
let icon_info = themeLoader.lookup_icon(
|
||||
iconTexture.get_names()[0],
|
||||
DOMINANT_COLOR_ICON_SIZE,
|
||||
0,
|
||||
)
|
||||
|
||||
if (icon_info !== null) {
|
||||
return icon_info.load_icon()
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The backlight color choosing algorithm was mostly ported to javascript from the
|
||||
* Unity7 C++ source of Canonicals:
|
||||
* https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp
|
||||
* so it more or less works the same way.
|
||||
*/
|
||||
_getColorPalette() {
|
||||
if (iconCacheMap.get(this._app.get_id())) {
|
||||
// We already know the answer
|
||||
return iconCacheMap.get(this._app.get_id())
|
||||
}
|
||||
|
||||
let pixBuf = this._getIconPixBuf()
|
||||
if (pixBuf == null) return null
|
||||
|
||||
let pixels = pixBuf.get_pixels()
|
||||
|
||||
let total = 0,
|
||||
rTotal = 0,
|
||||
gTotal = 0,
|
||||
bTotal = 0
|
||||
|
||||
let resample_y = 1,
|
||||
resample_x = 1
|
||||
|
||||
// Resampling of large icons
|
||||
// We resample icons larger than twice the desired size, as the resampling
|
||||
// to a size s
|
||||
// DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE,
|
||||
// most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is tipycally
|
||||
// a multiple of it.
|
||||
let width = pixBuf.get_width()
|
||||
let height = pixBuf.get_height()
|
||||
|
||||
// Resample
|
||||
if (height >= 2 * DOMINANT_COLOR_ICON_SIZE)
|
||||
resample_y = Math.floor(height / DOMINANT_COLOR_ICON_SIZE)
|
||||
|
||||
if (width >= 2 * DOMINANT_COLOR_ICON_SIZE)
|
||||
resample_x = Math.floor(width / DOMINANT_COLOR_ICON_SIZE)
|
||||
|
||||
if (resample_x !== 1 || resample_y !== 1)
|
||||
pixels = this._resamplePixels(pixels, resample_x, resample_y)
|
||||
|
||||
// computing the limit outside the for (where it would be repeated at each iteration)
|
||||
// for performance reasons
|
||||
let limit = pixels.length
|
||||
for (let offset = 0; offset < limit; offset += 4) {
|
||||
let r = pixels[offset],
|
||||
g = pixels[offset + 1],
|
||||
b = pixels[offset + 2],
|
||||
a = pixels[offset + 3]
|
||||
|
||||
let saturation = Math.max(r, g, b) - Math.min(r, g, b)
|
||||
let relevance = 0.1 * 255 * 255 + 0.9 * a * saturation
|
||||
|
||||
rTotal += r * relevance
|
||||
gTotal += g * relevance
|
||||
bTotal += b * relevance
|
||||
|
||||
total += relevance
|
||||
}
|
||||
|
||||
total = total * 255
|
||||
|
||||
let r = rTotal / total,
|
||||
g = gTotal / total,
|
||||
b = bTotal / total
|
||||
|
||||
let hsv = ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255)
|
||||
|
||||
if (hsv.s > 0.15) hsv.s = 0.65
|
||||
hsv.v = 0.9
|
||||
|
||||
let rgb = ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v)
|
||||
|
||||
// Cache the result.
|
||||
let backgroundColor = {
|
||||
lighter: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, 0.2),
|
||||
original: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, 0),
|
||||
darker: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, -0.5),
|
||||
}
|
||||
|
||||
if (iconCacheMap.size >= MAX_CACHED_ITEMS) {
|
||||
//delete oldest cached values (which are in order of insertions)
|
||||
let ctr = 0
|
||||
for (let key of iconCacheMap.keys()) {
|
||||
if (++ctr > BATCH_SIZE_TO_DELETE) break
|
||||
iconCacheMap.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
iconCacheMap.set(this._app.get_id(), backgroundColor)
|
||||
|
||||
return backgroundColor
|
||||
}
|
||||
|
||||
/**
|
||||
* Downsample large icons before scanning for the backlight color to
|
||||
* improve performance.
|
||||
*
|
||||
* @param pixBuf
|
||||
* @param pixels
|
||||
* @param resampleX
|
||||
* @param resampleY
|
||||
*
|
||||
* @return [];
|
||||
*/
|
||||
_resamplePixels(pixels, resampleX, resampleY) {
|
||||
let resampledPixels = []
|
||||
// computing the limit outside the for (where it would be repeated at each iteration)
|
||||
// for performance reasons
|
||||
let limit = pixels.length / (resampleX * resampleY) / 4
|
||||
for (let i = 0; i < limit; i++) {
|
||||
let pixel = i * resampleX * resampleY
|
||||
|
||||
resampledPixels.push(pixels[pixel * 4])
|
||||
resampledPixels.push(pixels[pixel * 4 + 1])
|
||||
resampledPixels.push(pixels[pixel * 4 + 2])
|
||||
resampledPixels.push(pixels[pixel * 4 + 3])
|
||||
}
|
||||
|
||||
return resampledPixels
|
||||
}
|
||||
}
|
||||
|
||||
export const drawRoundedLine = function (
|
||||
cr,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
isRoundLeft,
|
||||
isRoundRight,
|
||||
stroke,
|
||||
fill,
|
||||
) {
|
||||
if (height > width) {
|
||||
y += Math.floor((height - width) / 2.0)
|
||||
height = width
|
||||
}
|
||||
|
||||
height = 2.0 * Math.floor(height / 2.0)
|
||||
|
||||
const leftRadius = isRoundLeft ? height / 2.0 : 0.0
|
||||
const rightRadius = isRoundRight ? height / 2.0 : 0.0
|
||||
|
||||
cr.moveTo(x + width - rightRadius, y)
|
||||
cr.lineTo(x + leftRadius, y)
|
||||
if (isRoundLeft)
|
||||
cr.arcNegative(
|
||||
x + leftRadius,
|
||||
y + leftRadius,
|
||||
leftRadius,
|
||||
-Math.PI / 2,
|
||||
Math.PI / 2,
|
||||
)
|
||||
else cr.lineTo(x, y + height)
|
||||
cr.lineTo(x + width - rightRadius, y + height)
|
||||
if (isRoundRight)
|
||||
cr.arcNegative(
|
||||
x + width - rightRadius,
|
||||
y + rightRadius,
|
||||
rightRadius,
|
||||
Math.PI / 2,
|
||||
-Math.PI / 2,
|
||||
)
|
||||
else cr.lineTo(x + width, y)
|
||||
cr.closePath()
|
||||
|
||||
if (fill != null) {
|
||||
cr.setSource(fill)
|
||||
cr.fillPreserve()
|
||||
}
|
||||
if (stroke != null) cr.setSource(stroke)
|
||||
cr.stroke()
|
||||
}
|
||||
1362
src/windowPreview.js
Normal file
1362
src/windowPreview.js
Normal file
File diff suppressed because it is too large
Load Diff
140
stylesheet.css
140
stylesheet.css
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Dock extension by micheleg
|
||||
* and code from the Dash to Panel extension
|
||||
* Some code was also adapted from the upstream Gnome Shell source code.
|
||||
*/
|
||||
|
||||
#zorintaskbarTaskbar .dash-item-container > StWidget {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .app-well-app .overview-icon,
|
||||
.zorintaskbarMainPanel .show-apps .overview-icon {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .app-well-app .overview-label {
|
||||
/* must match TITLE_RIGHT_PADDING in apppicons.js */
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .app-well-app .favorite {
|
||||
background-color: rgba(80, 150, 255, 0.4);
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .app-well-app-running-dot {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarTaskbar .scrollview-fade {
|
||||
background-gradient-end: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.zorintaskbarSecondaryMenu {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button.vertical *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.zorintaskbarMainPanel.vertical .panel-button > *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.vertical > *,
|
||||
.zorintaskbarMainPanel.vertical .panel-button.clock-display > * {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList {
|
||||
spacing: 0em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .popup-menu-item {
|
||||
padding: 0;
|
||||
border-radius: 5px;
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .window-box {
|
||||
padding: 0;
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
#zorintaskbarThumbnailList .preview-window-title {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.popup-menu.panel-menu {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#panel #panelLeft, #panel #panelCenter {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
#panelBox.floating {
|
||||
padding: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#panelBox.floating #panel,
|
||||
#panelBox.floating .panel-button {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.panel-corner.hidden:active, .panel-corner.hidden:overview, .panel-corner.hidden:focus {
|
||||
-panel-corner-border-color: rgba(0, 0, 0, .001);
|
||||
}
|
||||
#zorintaskbarScrollview .badge {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .number-overlay {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .notification-badge {
|
||||
background-color: rgba(255,0,0,0.8);
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#zorintaskbarScrollview .progress-bar {
|
||||
/* Customization of the progress bar style, e.g.:
|
||||
-progress-bar-background: rgba(0.8, 0.8, 0.8, 1);
|
||||
-progress-bar-border: rgba(0.9, 0.9, 0.9, 1);
|
||||
*/
|
||||
}
|
||||
|
||||
.preview-container,
|
||||
#preview-menu {
|
||||
border-radius: 10px;
|
||||
}
|
||||
1270
taskbar.js
1270
taskbar.js
File diff suppressed because it is too large
Load Diff
175
transparency.js
175
transparency.js
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Zorin Taskbar extension for Zorin OS.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Credits:
|
||||
* This file is based on code from the Dash to Panel extension
|
||||
*/
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Panel = Me.imports.panel;
|
||||
const Proximity = Me.imports.proximity;
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
var DynamicTransparency = Utils.defineClass({
|
||||
Name: 'ZorinTaskbar.DynamicTransparency',
|
||||
|
||||
_init: function(dtpPanel) {
|
||||
this._dtpPanel = dtpPanel;
|
||||
this.currentBackgroundColor = 0;
|
||||
|
||||
this._initialPanelStyle = dtpPanel.panel.actor.get_style();
|
||||
|
||||
if (this._dtpPanel.geom.position == St.Side.TOP) {
|
||||
this._initialPanelCornerStyle = dtpPanel.panel._leftCorner.actor.get_style();
|
||||
}
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||||
this._bindSignals();
|
||||
|
||||
this._updateAllAndSet();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._signalsHandler.destroy();
|
||||
|
||||
this._dtpPanel.panel.actor.set_style(this._initialPanelStyle);
|
||||
|
||||
if (this._dtpPanel.geom.position == St.Side.TOP) {
|
||||
this._dtpPanel.panel._leftCorner.actor.set_style(this._initialPanelCornerStyle);
|
||||
this._dtpPanel.panel._rightCorner.actor.set_style(this._initialPanelCornerStyle);
|
||||
}
|
||||
},
|
||||
|
||||
updateExternalStyle: function() {
|
||||
this._updateComplementaryStyles();
|
||||
this._setBackground();
|
||||
},
|
||||
|
||||
_bindSignals: function() {
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
Utils.getStageTheme(),
|
||||
'changed',
|
||||
() => this._updateAllAndSet()
|
||||
],
|
||||
[
|
||||
Main.overview,
|
||||
[
|
||||
'showing',
|
||||
'hiding'
|
||||
],
|
||||
() => this._updateAlphaAndSet()
|
||||
],
|
||||
[
|
||||
Me.settings,
|
||||
[
|
||||
'changed::trans-use-custom-opacity',
|
||||
'changed::trans-panel-opacity'
|
||||
],
|
||||
() => this._updateAlphaAndSet()
|
||||
]
|
||||
);
|
||||
},
|
||||
|
||||
_updateAllAndSet: function() {
|
||||
let themeBackground = this._getThemeBackground(true);
|
||||
|
||||
this._updateColor(themeBackground);
|
||||
this._updateAlpha(themeBackground);
|
||||
this._updateComplementaryStyles();
|
||||
this._setBackground();
|
||||
this._setActorStyle();
|
||||
},
|
||||
|
||||
_updateAlphaAndSet: function() {
|
||||
this._updateAlpha();
|
||||
this._setBackground();
|
||||
},
|
||||
|
||||
_updateComplementaryStyles: function() {
|
||||
let panelThemeNode = this._dtpPanel.panel.actor.get_theme_node();
|
||||
|
||||
this._complementaryStyles = 'border-radius: ' + panelThemeNode.get_border_radius(0) + 'px;';
|
||||
},
|
||||
|
||||
_updateColor: function(themeBackground) {
|
||||
this.backgroundColorRgb = (themeBackground || this._getThemeBackground());
|
||||
},
|
||||
|
||||
_updateAlpha: function(themeBackground) {
|
||||
this.alpha = Me.settings.get_boolean('trans-use-custom-opacity') ?
|
||||
Me.settings.get_double('trans-panel-opacity') :
|
||||
(themeBackground || this._getThemeBackground()).alpha * 0.003921569; // 1 / 255 = 0.003921569
|
||||
},
|
||||
|
||||
_setBackground: function() {
|
||||
this.currentBackgroundColor = Utils.getrgbaColor(this.backgroundColorRgb, this.alpha);
|
||||
|
||||
let transition = 'transition-duration: 200ms;';
|
||||
let cornerStyle = '-panel-corner-background-color: ' + this.currentBackgroundColor + transition;
|
||||
|
||||
this._dtpPanel.set_style('background-color: ' + this.currentBackgroundColor + transition + this._complementaryStyles);
|
||||
|
||||
if (this._dtpPanel.geom.position == St.Side.TOP) {
|
||||
this._dtpPanel.panel._leftCorner.actor.set_style(cornerStyle);
|
||||
this._dtpPanel.panel._rightCorner.actor.set_style(cornerStyle);
|
||||
}
|
||||
},
|
||||
|
||||
_setActorStyle: function() {
|
||||
this._dtpPanel.panel.actor.set_style(
|
||||
'background: none; ' +
|
||||
'border-image: none; ' +
|
||||
'background-image: none;'
|
||||
);
|
||||
},
|
||||
|
||||
_getThemeBackground: function(reload) {
|
||||
if (reload || !this._themeBackground) {
|
||||
let fakePanel = new St.Bin({ name: 'panel' });
|
||||
Main.uiGroup.add_child(fakePanel);
|
||||
let fakeTheme = fakePanel.get_theme_node();
|
||||
this._themeBackground = this._getBackgroundImageColor(fakeTheme) || fakeTheme.get_background_color();
|
||||
Main.uiGroup.remove_child(fakePanel);
|
||||
}
|
||||
|
||||
return this._themeBackground;
|
||||
},
|
||||
|
||||
_getBackgroundImageColor: function(theme) {
|
||||
let bg = null;
|
||||
|
||||
try {
|
||||
let imageFile = theme.get_background_image() || theme.get_border_image().get_file();
|
||||
|
||||
if (imageFile) {
|
||||
let imageBuf = GdkPixbuf.Pixbuf.new_from_file(imageFile.get_path());
|
||||
let pixels = imageBuf.get_pixels();
|
||||
|
||||
bg = { red: pixels[0], green: pixels[1], blue: pixels[2], alpha: pixels[3] };
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
return bg;
|
||||
}
|
||||
});
|
||||
56
ui/BoxDynamicOpacityOptions.ui
Normal file
56
ui/BoxDynamicOpacityOptions.ui
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
|
||||
<object class="GtkAdjustment" id="trans_opacity_min_adjustment">
|
||||
<property name="upper">100</property>
|
||||
<property name="step_increment">5</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
|
||||
<object class="GtkBox" id="box_dynamic_opacity_options">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">600</property>
|
||||
<property name="spacing">24</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="margin-start">32</property>
|
||||
<property name="margin-end">32</property>
|
||||
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">The panel background opacity is affected by</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="trans_options_window_type_combo">
|
||||
<property name="valign">center</property>
|
||||
<items>
|
||||
<item id="ALL_WINDOWS" translatable="yes">All windows</item>
|
||||
<item id="FOCUSED_WINDOWS" translatable="yes">Focused windows</item>
|
||||
<item id="MAXIMIZED_WINDOWS" translatable="yes">Maximized windows</item>
|
||||
</items>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Change opacity to (%)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="trans_options_min_opacity_spinbutton">
|
||||
<property name="text" translatable="yes">0</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">trans_opacity_min_adjustment</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
</interface>
|
||||
72
ui/BoxGroupAppsOptions.ui
Normal file
72
ui/BoxGroupAppsOptions.ui
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
|
||||
<object class="GtkAdjustment" id="group_apps_label_max_width_adjustment">
|
||||
<property name="upper">1000</property>
|
||||
<property name="step_increment">10</property>
|
||||
<property name="page_increment">100</property>
|
||||
</object>
|
||||
|
||||
<object class="GtkBox" id="box_group_apps_options">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">600</property>
|
||||
<property name="spacing">24</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="margin-start">32</property>
|
||||
<property name="margin-end">32</property>
|
||||
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Maximum width (px) of the application titles</property>
|
||||
<property name="subtitle" translatable="yes">(default is 160)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="group_apps_label_max_width_spinbutton">
|
||||
<property name="valign">center</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="text">0</property>
|
||||
<property name="adjustment">group_apps_label_max_width_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Use a fixed width for the application titles</property>
|
||||
<property name="subtitle" translatable="yes">The application titles all have the same width, even if their texts are shorter than the maximum width. The maximum width value is used as the fixed width.</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="group_apps_use_fixed_width_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Use the favorite icons as application launchers</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="group_apps_use_launchers_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
</interface>
|
||||
117
ui/BoxIntellihideOptions.ui
Normal file
117
ui/BoxIntellihideOptions.ui
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<interface>
|
||||
<!-- interface-name BoxIntellihideOptions.ui -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="libadwaita" version="1.3"/>
|
||||
<object class="GtkBox" id="box_intellihide_options">
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="margin-end">32</property>
|
||||
<property name="margin-start">32</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">24</property>
|
||||
<property name="width-request">600</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Only hide the panel when it is obstructed by windows</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_window_hide_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="intellihide_behaviour_options">
|
||||
<property name="title" translatable="yes">The panel hides from</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="intellihide_behaviour_combo">
|
||||
<property name="valign">center</property>
|
||||
<items>
|
||||
<item id="ALL_WINDOWS" translatable="yes">All windows</item>
|
||||
<item id="FOCUSED_WINDOWS" translatable="yes">Focused windows</item>
|
||||
<item id="MAXIMIZED_WINDOWS" translatable="yes">Maximized windows</item>
|
||||
</items>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Require pressure at the edge of the screen to reveal the panel</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_use_pressure_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Allow the panel to be revealed while in fullscreen mode</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_show_in_fullscreen_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="grid_intellihide_only_secondary">
|
||||
<property name="subtitle" translatable="yes">(requires multi-monitors option)</property>
|
||||
<property name="title" translatable="yes">Only hide secondary panels</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_only_secondary_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="subtitle" translatable="yes">Syntax: &lt;Shift&gt;, &lt;Ctrl&gt;, &lt;Alt&gt;, &lt;Super&gt;</property>
|
||||
<property name="title" translatable="yes">Keyboard shortcut to reveal and hold the panel</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="intellihide_toggle_entry">
|
||||
<property name="placeholder-text" translatable="yes">e.g. <Super>i</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="width-chars">12</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="grid_intellihide_persist_state">
|
||||
<property name="title" translatable="yes">Persist state across restarts</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_persist_state_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="subtitle" translatable="yes">(respects "Do Not Disturb" and requires show notification counter badge option)</property>
|
||||
<property name="title" translatable="yes">Reveal and hold the panel on notification</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="intellihide_show_on_notification_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user