13 Commits

Author SHA1 Message Date
Artyom Zorin
582b8b280b Bump to version 68.5.1 2025-08-05 22:50:54 +01:00
Artyom Zorin
ded853bdc2 Bump to version 68.5 2025-08-04 18:37:22 +01:00
Artyom Zorin
216d932ccc Bump to version 68.4 2025-07-31 20:01:46 +01:00
Artyom Zorin
f6c8dd38ce Bump to version 68.3 2025-07-07 13:00:30 +01:00
Artyom Zorin
34717961b6 Bump to version 68.2.3 2025-07-04 20:38:38 +01:00
Artyom Zorin
770649ff0a Updated metadata 2025-06-06 23:05:40 +01:00
Artyom Zorin
35e98e2061 Bump to version 68.2 2025-05-10 23:55:10 +01:00
Artyom Zorin
557250216a Bump to version 68.1 2025-05-02 14:22:16 +01:00
Artyom Zorin
bb69c75c70 Bump to version 65.2 2025-02-27 15:46:57 +00:00
Artyom Zorin
f46cab04cb Bump to version 56.11 2024-10-20 18:00:25 +01:00
Artyom Zorin
de47b50149 Updated French translations based on contribution from Zac0511 2024-10-20 17:55:55 +01:00
Artyom Zorin
d396f3af52 Bump to verison 56.10 2024-09-09 17:46:17 +01:00
Artyom Zorin
2a447787a5 Bump to version 56.9 2024-09-03 14:08:20 +01:00
110 changed files with 47329 additions and 29136 deletions

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@ gschemas.compiled
zorin-taskbar@zorinos.com*.zip
*.mo
po/zorin-taskbar.pot
Settings.ui.h
ui/*.ui.h
node_modules/

View File

@@ -1,16 +1,19 @@
# 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)),)
INSTALLTYPE = local
INSTALLBASE = $(HOME)/.local/share/gnome-shell/extensions
else
INSTALLTYPE = system
INSTALLBASE = $(DESTDIR)/usr/share/gnome-shell/extensions
SHARE_PREFIX = $(DESTDIR)/usr/share
endif
INSTALLNAME = zorin-taskbar@zorinos.com
@@ -18,7 +21,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 +47,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 $@
@@ -59,6 +68,12 @@ install-local: _build
rm -rf $(INSTALLBASE)/$(INSTALLNAME)
mkdir -p $(INSTALLBASE)/$(INSTALLNAME)
cp -r ./_build/* $(INSTALLBASE)/$(INSTALLNAME)/
ifeq ($(INSTALLTYPE),system)
rm -r $(INSTALLBASE)/$(INSTALLNAME)/schemas $(INSTALLBASE)/$(INSTALLNAME)/locale
mkdir -p $(SHARE_PREFIX)/glib-2.0/schemas $(SHARE_PREFIX)/locale
cp -r ./schemas/*gschema.* $(SHARE_PREFIX)/glib-2.0/schemas
cp -r ./_build/locale/* $(SHARE_PREFIX)/locale
endif
-rm -fR _build
echo done
@@ -71,14 +86,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/

View File

@@ -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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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
});
}

189
debian/changelog vendored
View File

@@ -1,3 +1,192 @@
gnome-shell-extension-zorin-taskbar (68.5.1) noble; urgency=medium
* Minor code cleanups
-- Artyom Zorin <azorin@zoringroup.com> Tue, 05 Aug 2025 22:47:20 +0100
gnome-shell-extension-zorin-taskbar (68.5) noble; urgency=medium
* Rebased on upstream commit 16e16c11ce08abc3c9f0bf922bbc08e17b2c1f08
-- Artyom Zorin <azorin@zoringroup.com> Mon, 04 Aug 2025 13:46:11 +0100
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

8
debian/control vendored
View File

@@ -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
View File

@@ -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+

View File

@@ -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);
}
}
}

View File

@@ -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;
},
});

View File

@@ -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
}

View File

@@ -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);
}));
}
});

1446
panel.js

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);
}
}
});

View File

@@ -18,6 +18,3 @@ msgstr "Wys Rekenaar-Ikoon"
msgid "Show Desktop button padding (px)"
msgstr "Wys Rekenaar-Ikoon vulling (px)"
msgid "Floating rounded theme"
msgstr "Swewende afgeronde tema"

View File

@@ -22,5 +22,5 @@ msgstr "أظهر أيقونات سطح المكتب"
msgid "Show Desktop button padding (px)"
msgstr "أظهر زر سطح المكتب المساحة المتروكة (بيكسل)"
msgid "Floating rounded theme"
msgstr "مظهر دائري عائم"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "عزل مساحات العمل والشاشات في إعدادات تبديل التطبيقات"

View File

@@ -21,6 +21,3 @@ msgstr "Паказаць значок працоўнага стала"
msgid "Show Desktop button padding (px)"
msgstr "Пракладка Паказаць кнопку працоўнага стала (px)"
msgid "Floating rounded theme"
msgstr "Плаваючая круглявая тэма"

View File

@@ -21,6 +21,3 @@ msgstr "Показване на икона на работния плот"
msgid "Show Desktop button padding (px)"
msgstr "Бутон Покажи работния плот - подложка (px)"
msgid "Floating rounded theme"
msgstr "Плаваща заоблена тема"

View File

@@ -18,6 +18,3 @@ msgstr "ডেস্কটপ আইকন দেখান"
msgid "Show Desktop button padding (px)"
msgstr "Show Desktop button প্যাডিং (px আকারে)"
msgid "Floating rounded theme"
msgstr "ভাসমান বৃত্তাকার থিম"

View File

@@ -21,6 +21,3 @@ msgstr "Mostrar l'icona de l'Escriptori"
msgid "Show Desktop button padding (px)"
msgstr "Farciment Mostra el botó de l'escriptori (px)"
msgid "Floating rounded theme"
msgstr "Tema flotant arrodonit"

2184
po/cs.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Vis skrivebords ikon"
msgid "Show Desktop button padding (px)"
msgstr "Vis skrivebords knap afstand (px)"
msgid "Floating rounded theme"
msgstr "Flydende afrundet tema"

2302
po/de.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Εμφάνιση επιφάνειας εργασίας"
msgid "Show Desktop button padding (px)"
msgstr "Εμφάνισε διαχωριστικό"
msgid "Floating rounded theme"
msgstr "Κυμαινόμενο στρογγυλεμένο θέμα"

2064
po/es.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Kuva töölauaikoon"
msgid "Show Desktop button padding (px)"
msgstr "Täitmine Kuva töölaua nupp (px)"
msgid "Floating rounded theme"
msgstr "Ujuv ümardatud teema"

1489
po/fa.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Näytä työpöytä-kuvake"
msgid "Show Desktop button padding (px)"
msgstr "Näytä työpöytä painikkeen asettelu (px)"
msgid "Floating rounded theme"
msgstr "Kelluva ja pyöristetty teema"

2256
po/fr.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Taispeáin deilbhín Deisce"
msgid "Show Desktop button padding (px)"
msgstr "Stuáil Taispeáin cnaipe Deisce (px)"
msgid "Floating rounded theme"
msgstr "Téama chothromú ar snámh"

2242
po/gl.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "ડેસ્કટ .પ ચિહ્ન બતાવો"
msgid "Show Desktop button padding (px)"
msgstr "ડેસ્કટtopપ બટન બતાવો પેડિંગ (પીએક્સ)"
msgid "Floating rounded theme"
msgstr "ફ્લોટિંગ ગોળાકાર થીમ"

View File

@@ -21,6 +21,3 @@ msgstr "הצגת סמל שולחן העבודה"
msgid "Show Desktop button padding (px)"
msgstr "ריפוד הצגת כפתור שולחן עבודה (פיקסלים)"
msgid "Floating rounded theme"
msgstr "ערכת נושא מעוגלת עם ריחוף"

View File

@@ -22,5 +22,5 @@ msgstr "डेस्कटॉप आइकन दिखाएं"
msgid "Show Desktop button padding (px)"
msgstr "डेस्कटॉप बटन दिखाएं पैडिंग (पीएक्स)"
msgid "Floating rounded theme"
msgstr "फ़्लोटिंग गोलाकार थीम"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "एप्लिकेशन स्विचिंग सेटिंग्स में कार्यस्थलों और मॉनिटर को अलग करें"

View File

@@ -21,6 +21,3 @@ msgstr "Prikaži ikonu radne površine"
msgid "Show Desktop button padding (px)"
msgstr "Podmetač Prikaži gumb radne površine (px)"
msgid "Floating rounded theme"
msgstr "Plutajuća zaobljena tema"

2089
po/hu.po

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,5 @@ msgstr "Tampilkan ikon desktop"
msgid "Show Desktop button padding (px)"
msgstr "Tampilkan tombol desktop padding (px)"
msgid "Floating rounded theme"
msgstr "Tema bulat mengambang"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "Isolasi Ruang Kerja dan Monitor dalam pengaturan Pengalihan Aplikasi"

View File

@@ -21,6 +21,3 @@ msgstr "Sýna skjáborðstáknið"
msgid "Show Desktop button padding (px)"
msgstr "„Sýna skjáborðshnapp“ padding (px)"
msgid "Floating rounded theme"
msgstr "Fljótandi ávalið þema"

2196
po/it.po

File diff suppressed because it is too large Load Diff

4381
po/ja.po

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,3 @@ msgstr "აჩვენე დესკტოპის იკონები"
msgid "Show Desktop button padding (px)"
msgstr "ღილაკი აჩვენე დესკტოპი"
msgid "Floating rounded theme"
msgstr "მოფარფატე მომრგვალო თემა"

1732
po/kk.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "ಡೆಸ್ಕ್ಟಾಪ್ ಐಕಾನ್ ತೋರಿಸಿ"
msgid "Show Desktop button padding (px)"
msgstr "ಡೆಸ್ಕ್ಟಾಪ್ ಬಟನ್ ತೋರಿಸು ಪ್ಯಾಡಿಂಗ್ (ಪಿಎಕ್ಸ್)"
msgid "Floating rounded theme"
msgstr "ತೇಲುವ ದುಂಡಾದ ಥೀಮ್"

1814
po/ko.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Rodyti Darbalaukio ikoną"
msgid "Show Desktop button padding (px)"
msgstr "Rodyti Darbalaukio mygtuką"
msgid "Floating rounded theme"
msgstr "Plūduriuojanti apskrita tema"

View File

@@ -21,6 +21,3 @@ msgstr "Rādīt darbvirsmas ikonu"
msgid "Show Desktop button padding (px)"
msgstr "Rādīt darbvirsmas pogu polsterējums (px)"
msgid "Floating rounded theme"
msgstr "Peldoša noapaļota tēma"

View File

@@ -18,6 +18,3 @@ msgstr "Покажете ја иконата за работната површ
msgid "Show Desktop button padding (px)"
msgstr "Пополнување „Покажи копче за работна површина“ (px)"
msgid "Floating rounded theme"
msgstr "Лебдечка заоблена тема"

View File

@@ -21,6 +21,3 @@ msgstr "Tunjukkan ikon Atas Meja"
msgid "Show Desktop button padding (px)"
msgstr "Pemadatan Tunjuk butang Atas Meja (px)"
msgid "Floating rounded theme"
msgstr "Tema bundar terapung"

View File

@@ -21,6 +21,3 @@ msgstr "डेस्कटप प्रतिमा देखाउनुहो
msgid "Show Desktop button padding (px)"
msgstr "डेस्कटप बटन देखाउनुहोस् प्याडि ((px)"
msgid "Floating rounded theme"
msgstr "फ्लोटिंग गोल थिम"

1632
po/nl.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "ਡੈਸਕਟਾਪ ਆਈਕਾਨ ਵੇਖਾਓ"
msgid "Show Desktop button padding (px)"
msgstr "ਡੈਸਕਟਾਪ ਬਟਨ ਦਿਖਾਓ ਭਰਾਵ (px)"
msgid "Floating rounded theme"
msgstr "ਫਲੋਟਿੰਗ ਗੋਲ ਥੀਮ"

4163
po/pl.po

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,5 @@ msgstr "Mostrar ícone da Área de Trabalho"
msgid "Show Desktop button padding (px)"
msgstr "Preenchimento do botão Mostrar Área de Trabalho (px)"
msgid "Floating rounded theme"
msgstr "Tema arredondado flutuante"
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"

File diff suppressed because it is too large Load Diff

2300
po/ru.po

File diff suppressed because it is too large Load Diff

1455
po/sk.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Pokaži ikono za namizje"
msgid "Show Desktop button padding (px)"
msgstr "Pokaži ikono za namizje razmik (px)"
msgid "Floating rounded theme"
msgstr "Plavajoča zaokrožena tema"

View File

@@ -15,6 +15,3 @@ msgstr "Shfaq ikonën e desktopit"
msgid "Show Desktop button padding (px)"
msgstr "Mbushja Shfaq butonin e desktopit (px)"
msgid "Floating rounded theme"
msgstr "Temë lundruese e rrumbullakosur"

View File

@@ -21,6 +21,3 @@ msgstr "Прикажи икону радне површине"
msgid "Show Desktop button padding (px)"
msgstr "Подлога „Прикажи дугме радне површине“ (пк)"
msgid "Floating rounded theme"
msgstr "Плутајућа заобљена тема"

2238
po/sv.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "டெஸ்க்டாப் ஐகானைக் காட்டு"
msgid "Show Desktop button padding (px)"
msgstr "டெஸ்க்டாப் பொத்தானைக் காட்டு திணிப்பு (px)"
msgid "Floating rounded theme"
msgstr "மிதக்கும் வட்டமான தீம்"

View File

@@ -21,6 +21,3 @@ msgstr "డెస్క్‌టాప్ చిహ్నాన్ని చూ
msgid "Show Desktop button padding (px)"
msgstr "డెస్క్‌టాప్ బటన్ చూపించు పాడింగ్ (px)"
msgid "Floating rounded theme"
msgstr "తేలియాడే గుండ్రని థీమ్"

View File

@@ -21,6 +21,3 @@ msgstr "แสดงไอคอนเดสก์ท็อป"
msgid "Show Desktop button padding (px)"
msgstr "ช่องว่างภายใน แสดงปุ่มเดสก์ท็อป (px)"
msgid "Floating rounded theme"
msgstr "ธีมกลมลอย"

2518
po/tr.po

File diff suppressed because it is too large Load Diff

1891
po/uk.po

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,3 @@ msgstr "Hiển thị biểu tượng Desktop"
msgid "Show Desktop button padding (px)"
msgstr "Phần đệm Show Desktop button (px)"
msgid "Floating rounded theme"
msgstr "Theme có dạng bo tròn nỗi"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1037
prefs.js

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}
});

View File

@@ -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);
},
});

View File

@@ -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,75 @@
<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="trans-use-border">
<default>false</default>
<summary>Display border</summary>
<description>Display a border between panel and the rest of the desktop</description>
</key>
<key type="i" name="trans-border-width">
<default>1</default>
<summary>Width of panel border</summary>
<description>Customize the width of the panel border</description>
</key>
<key type="b" name="trans-border-use-custom-color">
<default>false</default>
<summary>Override panel border color</summary>
<description>Replace current panel border color</description>
</key>
<key type="s" name="trans-border-custom-color">
<default>"rgba(200,200,200,0.2)"</default>
<summary>Custom panel border color</summary>
<description>Custom panel border color</description>
</key>
<key type="b" name="intellihide">
<default>false</default>
<summary>Intellihide</summary>
@@ -117,24 +176,49 @@
</key>
<key type="b" name="intellihide-hide-from-windows">
<default>true</default>
<summary>Only hide from windows</summary>
<summary>Only hide from overlapping windows</summary>
<description>Dictates if the dash should only hide when in conflict with windows</description>
</key>
<key type="b" name="intellihide-hide-from-monitor-windows">
<default>false</default>
<summary>Only hide from windows on monitor</summary>
</key>
<key name="intellihide-behaviour" enum="org.gnome.shell.extensions.zorin-taskbar.proximityBehavior">
<default>'FOCUSED_WINDOWS'</default>
<summary>Intellihide behaviour</summary>
<description>Dictates how to intelligently hide the panel</description>
</key>
<key type="b" name="intellihide-use-pointer">
<default>true</default>
<summary>Intellihide mouse pointer</summary>
<description>The mouse pointer next to the edge of the screen reveals the panel</description>
</key>
<key type="b" name="intellihide-use-pointer-limit-size">
<default>false</default>
<summary>Limit to panel length</summary>
</key>
<key type="b" name="intellihide-revealed-hover">
<default>true</default>
<summary>Panel stays revealed when hovered</summary>
</key>
<key type="b" name="intellihide-revealed-hover-limit-size">
<default>false</default>
<summary>Limit to panel length</summary>
</key>
<key type="b" name="intellihide-use-pressure">
<default>false</default>
<summary>Intellihide pressure</summary>
<description>To reveal the panel, pressure needs to be applied to the edege of the screen</description>
<description>To reveal the panel, pressure needs to be applied to the edge of the screen</description>
</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 +234,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 +289,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 +323,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 +331,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 +376,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>"&lt;Super&gt;q"</default>
<summary>Keybinding to show the dock and the number overlay.</summary>
@@ -339,7 +411,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 +845,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>

2049
src/appIcons.js Normal file

File diff suppressed because it is too large Load Diff

View 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)
}
}

164
src/extension.js Normal file
View File

@@ -0,0 +1,164 @@
/*
* 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 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)
zorinDashDelayId = 0
panelManager?.disable()
DTP_EXTENSION = null
SETTINGS = null
TILINGSETTINGS = null
SHELLSETTINGS = null
DESKTOPSETTINGS = null
TERMINALSETTINGS = null
NOTIFICATIONSSETTINGS = null
panelManager = null
tracker = null
delete global.zorinTaskbar
this.disableGlobalStyles()
AppIcons.resetRecentlyClickedApp()
}
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),
)
}
}

593
src/intellihide.js Normal file
View File

@@ -0,0 +1,593 @@
/*
* 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 Mtk from 'gi://Mtk'
import Shell from 'gi://Shell'
import St from 'gi://St'
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_REVEAL_DELAY = 0
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._hover = false
this._hoveredOut = false
this._windowOverlap = false
this._translationProp =
'translation_' + (this._dtpPanel.geom.vertical ? 'x' : 'y')
this._panelBox.translation_y = 0
this._panelBox.translation_x = 0
this._setTrackPanel(true)
this._bindGeneralSignals()
if (this._hidesFromWindows()) {
let watched = SETTINGS.get_boolean('intellihide-hide-from-windows')
? this._panelBox.get_parent()
: new Mtk.Rectangle({
x: this._monitor.x,
y: this._monitor.y,
width: this._monitor.width,
height: this._monitor.height,
})
this._proximityWatchId = this._proximityManager.createWatch(
watched,
this._dtpPanel.monitor.index,
Proximity.Mode[SETTINGS.get_string('intellihide-behaviour')],
0,
0,
(overlap) => {
this._windowOverlap = overlap
this._queueUpdatePanelPosition()
},
)
}
if (SETTINGS.get_boolean('intellihide-use-pointer'))
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
this._hover = false
if (this._proximityWatchId) {
this._proximityManager.removeWatch(this._proximityWatchId)
}
this._setTrackPanel(false)
this._removeRevealMechanism()
this._revealPanel(!reset)
this._signalsHandler.destroy()
this._timeoutsHandler.destroy()
}
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, immediate) {
if (
!this.enabled ||
(holdStatus == Hold.NOTIFY &&
(!SETTINGS.get_boolean('intellihide-show-on-notification') ||
!NOTIFICATIONSSETTINGS.get_boolean('show-banners')))
)
return
if (!this._holdStatus) this._revealPanel(immediate)
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()
}
_hidesFromWindows() {
return (
SETTINGS.get_boolean('intellihide-hide-from-windows') ||
SETTINGS.get_boolean('intellihide-hide-from-monitor-windows')
)
}
_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._queueUpdatePanelPosition(),
],
[
SETTINGS,
[
'changed::intellihide-use-pointer',
'changed::intellihide-use-pressure',
'changed::intellihide-hide-from-windows',
'changed::intellihide-hide-from-monitor-windows',
'changed::intellihide-behaviour'
],
() => this.reset(),
],
[
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),
])
}
}
_setTrackPanel(enable) {
let actorData = Utils.getTrackedActorData(this._panelBox)
actorData.affectsStruts = !enable
actorData.trackFullscreen = !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',
() => {
let [x, y] = global.get_pointer()
if (this._pointerIn(x, y, 1, 'intellihide-use-pointer-limit-size'))
this._queueUpdatePanelPosition(true)
else this._pressureBarrier._isTriggered = false
},
])
}
this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(
CHECK_POINTER_MS,
(x, y) => this._checkMousePointer(x, y),
)
}
_removeRevealMechanism() {
if (this._pointerWatch) {
PointerWatcher.getPointerWatcher()._removeWatch(this._pointerWatch)
this._pointerWatch = 0
}
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.geom.vertical) {
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) {
if (
!this._pressureBarrier &&
!this._hover &&
!Main.overview.visible &&
this._pointerIn(x, y, 1, 'intellihide-use-pointer-limit-size')
) {
this._hover = true
this._queueUpdatePanelPosition(true)
} else if (this._panelBox.visible) {
let keepRevealedOnHover = SETTINGS.get_boolean(
'intellihide-revealed-hover',
)
let fixedOffset = keepRevealedOnHover
? this._dtpPanel.geom.outerSize + this._dtpPanel.geom.topOffset
: 1
let hover = this._pointerIn(
x,
y,
fixedOffset,
'intellihide-revealed-hover-limit-size',
)
if (hover == this._hover) return
this._hoveredOut = !hover
this._hover = hover
this._queueUpdatePanelPosition()
}
}
_pointerIn(x, y, fixedOffset, limitSizeSetting) {
let geom = this._dtpPanel.geom
let position = geom.position
let varCoordX1 = this._monitor.x
let varCoordY1 = this._monitor.y + geom.gsTopPanelHeight // if vertical, ignore the original GS panel if present
let varOffset = {}
if (geom.dockMode && SETTINGS.get_boolean(limitSizeSetting)) {
let alloc = this._dtpPanel.allocation
if (!geom.dynamic) {
// when fixed, use the panel clipcontainer which is positioned
// relative to the stage itself
varCoordX1 = geom.x
varCoordY1 = geom.y
varOffset[this._dtpPanel.varCoord.c2] =
alloc[this._dtpPanel.varCoord.c2] - alloc[this._dtpPanel.varCoord.c1]
} else {
// when dynamic, the panel clipcontainer spans the whole monitor edge
// and the panel is positioned relatively to the clipcontainer
varOffset[this._dtpPanel.varCoord.c1] =
alloc[this._dtpPanel.varCoord.c1]
varOffset[this._dtpPanel.varCoord.c2] =
alloc[this._dtpPanel.varCoord.c2]
}
}
return (
((position == St.Side.TOP && y <= this._monitor.y + fixedOffset) ||
(position == St.Side.BOTTOM &&
y >= this._monitor.y + this._monitor.height - fixedOffset) ||
(position == St.Side.LEFT && x <= this._monitor.x + fixedOffset) ||
(position == St.Side.RIGHT &&
x >= this._monitor.x + this._monitor.width - fixedOffset)) &&
x >= varCoordX1 + (varOffset.x1 || 0) &&
x < varCoordX1 + (varOffset.x2 || this._monitor.width) &&
y >= varCoordY1 + (varOffset.y1 || 0) &&
y < varCoordY1 + (varOffset.y2 || this._monitor.height)
)
}
_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._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 (!this._hidesFromWindows()) {
return this._hover
}
return !this._windowOverlap
}
_checkIfGrab() {
let grabActor = global.stage.get_grab_actor()
let sourceActor = grabActor?._sourceActor || grabActor
let 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,
() => (this._dtpPanel.taskbar._shownInitially = true),
)
}
_hidePanel(immediate) {
let position = this._dtpPanel.geom.position
let size = this._panelBox[this._dtpPanel.geom.vertical ? 'width' : 'height']
let coefficient =
position == St.Side.TOP || position == St.Side.LEFT ? -1 : 1
this._animatePanel(size * coefficient, immediate)
}
_animatePanel(destination, immediate, onComplete) {
if (destination === this._animationDestination) return
Utils.stopAnimations(this._panelBox)
this._animationDestination = destination
let update = () =>
this._timeoutsHandler.add([
T3,
POST_ANIMATE_MS,
() => {
Main.layoutManager._queueUpdateRegions()
this._queueUpdatePanelPosition()
},
])
if (immediate) {
this._panelBox[this._translationProp] = destination
this._panelBox.visible = !destination
update()
} else if (destination !== this._panelBox[this._translationProp]) {
let delay = 0
if (destination != 0 && this._hoveredOut)
delay = INTELLIHIDE_CLOSE_DELAY * 0.001
else if (destination == 0)
delay = INTELLIHIDE_REVEAL_DELAY * 0.001
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,
transition: 'easeOutQuad',
onComplete: () => {
this._panelBox.visible = !destination
onComplete ? onComplete() : null
update()
},
}
tweenOpts[this._translationProp] = destination
Utils.animate(this._panelBox, tweenOpts)
}
this._hoveredOut = false
}
}

216
src/notificationsMonitor.js Normal file
View 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
View 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.geom.vertical ? '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
}
}

1578
src/panel.js Normal file

File diff suppressed because it is too large Load Diff

1120
src/panelManager.js Normal file

File diff suppressed because it is too large Load Diff

70
src/panelPositions.js Normal file
View 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
View 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
View 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)
}
}
}

1855
src/prefs.js Normal file

File diff suppressed because it is too large Load Diff

299
src/proximity.js Normal file
View 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/>.
*
* 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,
}
class ProximityRectWatch {
constructor(rect, monitorIndex, mode, xThreshold, yThreshold, handler) {
this.rect = rect
this.monitorIndex = monitorIndex
this.overlap = false
this.mode = mode
this.threshold = [xThreshold, yThreshold]
this.handler = handler
}
destroy() {}
}
class ProximityActorWatch extends ProximityRectWatch {
constructor(actor, monitorIndex, mode, xThreshold, yThreshold, handler) {
super(null, monitorIndex, mode, xThreshold, yThreshold, handler)
this.actor = actor
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(watched, monitorIndex, mode, xThreshold, yThreshold, handler) {
let constr =
watched instanceof Mtk.Rectangle
? ProximityRectWatch
: ProximityActorWatch
let watch = new constr(
watched,
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),
))
)
}
}

207
src/stylesheet.css Normal file
View File

@@ -0,0 +1,207 @@
/*
* 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 .zorintaskbarPanel.dock .zorintaskbarMainPanel,
#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 .zorintaskbarPanel.dock .zorintaskbarMainPanel,
#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 .zorintaskbarPanel.dock .zorintaskbarMainPanel,
#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 .zorintaskbarPanel.dock .zorintaskbarMainPanel,
#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 .zorintaskbarPanel.dock .zorintaskbarMainPanel,
#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

File diff suppressed because it is too large Load Diff

245
src/transparency.js Normal file
View File

@@ -0,0 +1,245 @@
/*
* 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._setStyle()
}
_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-use-border',
'changed::trans-border-use-custom-color',
'changed::trans-border-custom-color',
'changed::trans-border-width',
],
() => this._updateBorderAndSet(),
],
[
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.geom.vertical
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._updateBorder()
this._updateBackground()
this._setStyle()
}
_updateAlphaAndSet() {
this._updateAlpha()
this._updateBackground()
this._setStyle()
}
_updateBorderAndSet() {
this._updateBorder()
this._setStyle()
}
_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
}
}
_updateBorder() {
let rgba = this._dtpPanel._getDefaultLineColor(
Utils.checkIfColorIsBright(this.backgroundColorRgb),
) // supply parameter manually or else an exception (something is undefined) will arise
const isLineCustom = SETTINGS.get_boolean('trans-border-use-custom-color')
rgba = isLineCustom
? SETTINGS.get_string('trans-border-custom-color')
: rgba
const showBorder = SETTINGS.get_boolean('trans-use-border')
const borderWidth = SETTINGS.get_int('trans-border-width')
const position = this._dtpPanel.getPosition()
let borderPosition = ''
if (position == St.Side.LEFT) {
borderPosition = 'right'
}
if (position == St.Side.RIGHT) {
borderPosition = 'left'
}
if (position == St.Side.TOP) {
borderPosition = 'bottom'
}
if (position == St.Side.BOTTOM) {
borderPosition = 'top'
}
const style = `border: 0 solid ${rgba}; border-${borderPosition}-width:${borderWidth}px; `
this._borderStyle = showBorder ? style : ''
}
_updateBackground() {
this.currentBackgroundColor = Utils.getrgbaColor(
this.backgroundColorRgb,
this.alpha,
)
this._backgroundStyle = `background-color: ${this.currentBackgroundColor}`
}
_setStyle() {
const transition = 'transition-duration: 300ms;'
this._dtpPanel.panel.set_style(
transition +
this._backgroundStyle +
this._borderStyle,
)
console.log('Set DTP Panel style to', this._dtpPanel.panel.get_style())
}
_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
View 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()
}

1384
src/windowPreview.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
});

View 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
View 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>

199
ui/BoxIntellihideOptions.ui Normal file
View File

@@ -0,0 +1,199 @@
<?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 from windows</property>
<child>
<object class="GtkBox">
<property name="margin-end">10</property>
<child>
<object class="GtkCheckButton" id="intellihide_window_hide_button">
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Overlapping</property>
<property name="name">4</property>
<property name="use-markup">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkCheckButton" id="intellihide_window_monitor_hide_button">
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">On same monitor</property>
<property name="name">4</property>
<property name="use-markup">True</property>
</object>
</child>
</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">Touching the monitor's edge with the pointer reveals the panel</property>
<child>
<object class="GtkSwitch" id="intellihide_use_pointer_switch">
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="margin-start">10</property>
<child>
<object class="GtkCheckButton" id="intellihide_use_pointer_limit_button">
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Limit to panel length</property>
<property name="name">4</property>
<property name="use-markup">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="intellihide_revealed_hover_options">
<property name="title" translatable="yes">Hovering the panel area keeps the panel revealed</property>
<child>
<object class="GtkSwitch" id="intellihide_revealed_hover_switch">
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="margin-start">10</property>
<child>
<object class="GtkCheckButton" id="intellihide_revealed_hover_limit_button">
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Limit to panel length</property>
<property name="name">4</property>
<property name="use-markup">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="intellihide_use_pressure_options">
<property name="title" translatable="yes">Require pressure at the edge of the monitor 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: &amp;lt;Shift&amp;gt;, &amp;lt;Ctrl&amp;gt;, &amp;lt;Alt&amp;gt;, &amp;lt;Super&amp;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. &lt;Super&gt;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>

103
ui/BoxMiddleClickOptions.ui Normal file
View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkBox" id="box_middle_click_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">Shift+Click action</property>
<property name="subtitle" translatable="yes">When set to minimize, double clicking minimizes all the windows of the application.</property>
<child>
<object class="GtkComboBoxText" id="shift_click_action_combo">
<property name="valign">center</property>
<items>
<item id="RAISE" translatable="yes">Raise windows</item>
<item id="MINIMIZE" translatable="yes">Minimize window</item>
<item id="LAUNCH" translatable="yes">Launch new instance</item>
<item id="CYCLE" translatable="yes">Cycle through windows</item>
<item id="CYCLE-MIN" translatable="yes">Cycle windows + minimize</item>
<item id="TOGGLE-SHOWPREVIEW" translatable="yes">Toggle single / Preview multiple</item>
<item id="TOGGLE-CYCLE" translatable="yes">Toggle single / Cycle multiple</item>
<item id="TOGGLE-SPREAD" translatable="yes">Toggle single / Spread multiple</item>
<item id="QUIT" translatable="yes">Quit</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Middle-Click action</property>
<property name="subtitle" translatable="yes">Behavior for Middle-Click.</property>
<child>
<object class="GtkComboBoxText" id="middle_click_action_combo">
<property name="valign">center</property>
<items>
<item id="RAISE" translatable="yes">Raise windows</item>
<item id="MINIMIZE" translatable="yes">Minimize window</item>
<item id="LAUNCH" translatable="yes">Launch new instance</item>
<item id="CYCLE" translatable="yes">Cycle through windows</item>
<item id="CYCLE-MIN" translatable="yes">Cycle windows + minimize</item>
<item id="TOGGLE-SHOWPREVIEW" translatable="yes">Toggle single / Preview multiple</item>
<item id="TOGGLE-CYCLE" translatable="yes">Toggle single / Cycle multiple</item>
<item id="TOGGLE-SPREAD" translatable="yes">Toggle single / Spread multiple</item>
<item id="QUIT" translatable="yes">Quit</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Shift+Middle-Click action</property>
<property name="subtitle" translatable="yes">Behavior for Shift+Middle-Click.</property>
<child>
<object class="GtkComboBoxText" id="shift_middle_click_action_combo">
<property name="valign">center</property>
<items>
<item id="RAISE" translatable="yes">Raise windows</item>
<item id="MINIMIZE" translatable="yes">Minimize window</item>
<item id="LAUNCH" translatable="yes">Launch new instance</item>
<item id="CYCLE" translatable="yes">Cycle through windows</item>
<item id="CYCLE-MIN" translatable="yes">Cycle windows + minimize</item>
<item id="TOGGLE-SHOWPREVIEW" translatable="yes">Toggle single / Preview multiple</item>
<item id="TOGGLE-CYCLE" translatable="yes">Toggle single / Cycle multiple</item>
<item id="TOGGLE-SPREAD" translatable="yes">Toggle single / Spread multiple</item>
<item id="QUIT" translatable="yes">Quit</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

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