16 Commits
65.2 ... 70

Author SHA1 Message Date
Artyom Zorin
a40d94c14b Bump to version 70 2025-09-28 00:36:00 +01:00
Artyom Zorin
541f5f20da Bump version to 69.2 2025-09-16 16:06:38 +01:00
Artyom Zorin
250d988473 Bump to version 69.1 2025-09-15 16:45:47 +01:00
Artyom Zorin
41f92619bf Bump to version 69 2025-09-12 14:16:30 +01:00
Artyom Zorin
388febf2fd Bump to version 68.9 2025-09-08 21:42:14 +01:00
Artyom Zorin
dcec9442c2 Bump to version 68.8 2025-09-05 17:47:05 +01:00
Artyom Zorin
89fe8b9f4b Bump to version 68.7 2025-09-04 14:25:37 +01:00
Artyom Zorin
a4f86d41f8 Bump to version 68.5.3 2025-08-07 00:11:11 +01:00
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
101 changed files with 22177 additions and 15425 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,18 +1,19 @@
# Basic Makefile
UUID = zorin-taskbar@zorinos.com
BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md
EXTRA_MODULES = appIcons.js panel.js panelManager.js proximity.js intellihide.js progress.js panelPositions.js panelSettings.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js utils.js desktopIconsIntegration.js
UI_MODULES = ui/BoxDynamicOpacityOptions.ui ui/BoxGroupAppsOptions.ui ui/BoxIntellihideOptions.ui ui/BoxMiddleClickOptions.ui ui/BoxOverlayShortcut.ui ui/BoxShowDateMenuOptions.ui ui/BoxShowDesktopOptions.ui ui/BoxWindowPreviewOptions.ui ui/SettingsAction.ui ui/SettingsBehavior.ui ui/SettingsPosition.ui ui/SettingsStyle.ui
MODULES = src/*.js src/stylesheet.css metadata.json COPYING README.md
UI_MODULES = ui/*.ui
IMAGES = ./*
EXTRA_IMAGES = show-desktop-symbolic.svg
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
@@ -49,7 +50,7 @@ mergepo: potfile
./po/zorin-taskbar.pot: $(TOLOCALIZE)
mkdir -p po
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; \
@@ -67,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
@@ -79,12 +86,12 @@ zip-file: _build
_build: all
-rm -fR ./_build
mkdir -p _build
cp $(BASE_MODULES) $(EXTRA_MODULES) _build
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/

File diff suppressed because it is too large Load Diff

129
debian/changelog vendored
View File

@@ -1,3 +1,132 @@
gnome-shell-extension-zorin-taskbar (70) noble; urgency=medium
* Rebased on upstream commit 134f6f991126b96738efef303572f1db9bdec9bd
but without commit 114d87149872da003af947315d4da2f0e40d41c9
-- Artyom Zorin <azorin@zoringroup.com> Sat, 27 Sep 2025 23:17:10 +0100
gnome-shell-extension-zorin-taskbar (69.2) noble; urgency=medium
* Adjusted app icon margin
-- Artyom Zorin <azorin@zoringroup.com> Tue, 16 Sep 2025 16:03:52 +0100
gnome-shell-extension-zorin-taskbar (69.1) noble; urgency=medium
* Set prefs window width
-- Artyom Zorin <azorin@zoringroup.com> Mon, 15 Sep 2025 16:44:10 +0100
gnome-shell-extension-zorin-taskbar (69) noble; urgency=medium
* Re-based on upstream version 69
-- Artyom Zorin <azorin@zoringroup.com> Fri, 12 Sep 2025 14:14:35 +0100
gnome-shell-extension-zorin-taskbar (68.9) noble; urgency=medium
* Rebased on upstream commit 44be0a739f26d872f27e81bd57928b6f61920476
and hid panel border settings
-- Artyom Zorin <azorin@zoringroup.com> Mon, 08 Sep 2025 21:39:56 +0100
gnome-shell-extension-zorin-taskbar (68.8) noble; urgency=medium
* Fixed app grid icon styling and rebased on upstream commit
4787d12180462f0c1c90d3f38ece5921e7e31b7a
-- Artyom Zorin <azorin@zoringroup.com> Fri, 05 Sep 2025 17:44:41 +0100
gnome-shell-extension-zorin-taskbar (68.7) noble; urgency=medium
* Adjusted window preview margin and rounding
-- Artyom Zorin <azorin@zoringroup.com> Thu, 04 Sep 2025 14:22:11 +0100
gnome-shell-extension-zorin-taskbar (68.6) noble; urgency=medium
* Introduced keep gnome shell dash option
-- Artyom Zorin <azorin@zoringroup.com> Sat, 23 Aug 2025 15:04:05 +0100
gnome-shell-extension-zorin-taskbar (68.5.3) noble; urgency=medium
* Added workaround for race condition
-- Artyom Zorin <azorin@zoringroup.com> Thu, 07 Aug 2025 00:09:07 +0100
gnome-shell-extension-zorin-taskbar (68.5.2) noble; urgency=medium
* Fixed settings cache issues
-- Artyom Zorin <azorin@zoringroup.com> Wed, 06 Aug 2025 23:45:25 +0100
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

2
debian/control vendored
View File

@@ -8,6 +8,6 @@ Rules-Requires-Root: no
Package: gnome-shell-extension-zorin-taskbar
Architecture: all
Depends: ${misc:Depends}, gnome-shell (>= 46), gnome-shell (<< 48~)
Depends: ${misc:Depends}, gnome-shell (>= 46), gnome-shell (<< 49~)
Description: Zorin Taskbar extension
A taskbar extension for the Zorin OS desktop.

View File

@@ -1,165 +0,0 @@
/*
* 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);
}
}

View File

@@ -1,132 +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/>.
*
*/
import Gio from 'gi://Gio';
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 PanelManager from './panelManager.js';
import * as AppIcons from './appIcons.js';
const ZORIN_DASH_UUID = 'zorin-dash@zorinos.com';
let panelManager;
let extensionChangedHandler;
let startupCompleteHandler;
let disabledZorinDash;
let extensionSystem = Main.extensionManager;
export let DTP_EXTENSION = null;
export let SETTINGS = null;
export let DESKTOPSETTINGS = null;
export let TERMINALSETTINGS = null;
export let PERSISTENTSTORAGE = null;
export let EXTENSION_UUID = null;
export let EXTENSION_PATH = null;
export default class ZorinTaskbarExtension extends Extension {
constructor(metadata) {
super(metadata);
this._realHasOverview = Main.sessionMode.hasOverview;
//create an object that persists until gnome-shell is restarted, even if the extension is disabled
PERSISTENTSTORAGE = {};
}
enable() {
DTP_EXTENSION = this;
// 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(this);
}
});
//create a global object that can emit signals and conveniently expose functionalities to other extensions
global.zorinTaskbar = new EventEmitter();
_enable(this);
}
disable(reset = false) {
panelManager.disable();
DTP_EXTENSION = null;
SETTINGS = null;
DESKTOPSETTINGS = null;
TERMINALSETTINGS = null;
panelManager = null;
if (!reset) {
extensionSystem.disconnect(extensionChangedHandler);
if (disabledZorinDash) {
disabledZorinDash = false;
extensionSystem.enableExtension(ZORIN_DASH_UUID);
}
delete global.zorinTaskbar;
AppIcons.resetRecentlyClickedApp();
}
if (startupCompleteHandler) {
Main.layoutManager.disconnect(startupCompleteHandler);
startupCompleteHandler = null;
}
Main.sessionMode.hasOverview = this._realHasOverview;
}
}
function _enable(extension) {
let enabled = global.settings.get_strv('enabled-extensions');
if (enabled?.indexOf(ZORIN_DASH_UUID) >= 0) {
disabledZorinDash = true;
extensionSystem.disableExtension(ZORIN_DASH_UUID);
}
if (panelManager)
return panelManager.toggleDash(); // already initialized but Zorin Dash restored the original dash on disable
SETTINGS = extension.getSettings('org.gnome.shell.extensions.zorin-taskbar');
DESKTOPSETTINGS = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'});
TERMINALSETTINGS = new Gio.Settings({schema_id: 'org.gnome.desktop.default-applications.terminal'})
EXTENSION_UUID = extension.uuid
EXTENSION_PATH = extension.path
Main.layoutManager.startInOverview = false;
if (Main.layoutManager._startingUp) {
Main.sessionMode.hasOverview = false;
startupCompleteHandler = Main.layoutManager.connect('startup-complete', () => {
Main.sessionMode.hasOverview = extension._realHasOverview
});
}
panelManager = new PanelManager.PanelManager();
panelManager.enable();
}

View File

@@ -1,434 +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
*/
import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as GrabHelper from 'resource:///org/gnome/shell/ui/grabHelper.js';
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js';
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js';
import * as Proximity from './proximity.js';
import * as Utils from './utils.js';
import {SETTINGS} from './extension.js';
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 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
};
export const Intellihide = class {
constructor(dtpPanel) {
this._dtpPanel = dtpPanel;
this._panelBox = dtpPanel.panelBox;
this._panelManager = dtpPanel.panelManager;
this._proximityManager = this._panelManager.proximityManager;
this._holdStatus = Hold.NONE;
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._timeoutsHandler = new Utils.TimeoutsHandler();
this._intellihideChangedId = SETTINGS.connect('changed::intellihide', () => this._changeEnabledStatus());
this._intellihideOnlySecondaryChangedId = SETTINGS.connect('changed::intellihide-only-secondary', () => this._changeEnabledStatus());
this.enabled = false;
this._changeEnabledStatus();
}
enable() {
this.enabled = true;
this._monitor = this._dtpPanel.monitor;
this._animationDestination = -1;
this._pendingUpdate = false;
this._hoveredOut = false;
this._windowOverlap = false;
this._translationProp = 'translation_' + (this._dtpPanel.checkIfVertical() ? 'x' : 'y');
this._panelBox.translation_y = 0;
this._panelBox.translation_x = 0;
this._setTrackPanel(true);
this._bindGeneralSignals();
if (SETTINGS.get_boolean('intellihide-hide-from-windows')) {
this._proximityWatchId = this._proximityManager.createWatch(
this._panelBox.get_parent(),
this._dtpPanel.monitor.index,
Proximity.Mode[SETTINGS.get_string('intellihide-behaviour')],
0, 0,
overlap => {
this._windowOverlap = overlap;
this._queueUpdatePanelPosition();
}
);
}
this._setRevealMechanism();
this._queueUpdatePanelPosition();
}
disable(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;
}
destroy() {
SETTINGS.disconnect(this._intellihideChangedId);
SETTINGS.disconnect(this._intellihideOnlySecondaryChangedId);
if (this.enabled) {
this.disable();
}
}
toggle() {
this[this._holdStatus & Hold.PERMANENT ? 'release' : 'revealAndHold'](Hold.PERMANENT);
}
revealAndHold(holdStatus) {
if (this.enabled && !this._holdStatus) {
this._revealPanel();
}
this._holdStatus |= holdStatus;
}
release(holdStatus) {
this._holdStatus -= holdStatus;
if (this.enabled && !this._holdStatus) {
this._queueUpdatePanelPosition();
}
}
reset() {
this.disable(true);
this.enable();
}
_changeEnabledStatus() {
let intellihide = SETTINGS.get_boolean('intellihide');
let onlySecondary = SETTINGS.get_boolean('intellihide-only-secondary');
let enabled = intellihide && !(this._dtpPanel.isPrimary && onlySecondary);
if (this.enabled !== enabled) {
this[enabled ? 'enable' : 'disable']();
}
}
_bindGeneralSignals() {
this._signalsHandler.add(
[
this._dtpPanel.taskbar,
['menu-closed', 'end-drag'],
() => {
this._panelBox.sync_hover();
this._onHoverChanged();
}
],
[
SETTINGS,
[
'changed::intellihide-use-pressure',
'changed::intellihide-hide-from-windows',
'changed::intellihide-behaviour'
],
() => this.reset()
],
[
this._panelBox,
'notify::hover',
() => this._onHoverChanged()
],
[
this._dtpPanel.taskbar.previewMenu,
'open-state-changed',
() => this._queueUpdatePanelPosition()
],
[
Main.overview,
[
'showing',
'hiding'
],
() => this._queueUpdatePanelPosition()
]
);
if (Meta.is_wayland_compositor()) {
this._signalsHandler.add([
this._panelBox,
'notify::visible',
() => Utils.setDisplayUnredirect(!this._panelBox.visible)
]);
}
}
_onHoverChanged() {
this._hoveredOut = !this._panelBox.hover;
this._queueUpdatePanelPosition();
}
_setTrackPanel(enable) {
let actorData = Utils.getTrackedActorData(this._panelBox)
actorData.affectsStruts = !enable;
actorData.trackFullscreen = !enable;
this._panelBox.track_hover = enable;
this._panelBox.reactive = enable;
this._panelBox.visible = enable ? enable : this._panelBox.visible;
Main.layoutManager._queueUpdateRegions();
}
_setRevealMechanism() {
let barriers = Meta.BackendCapabilities.BARRIERS
if ((global.backend.capabilities & barriers) === barriers && SETTINGS.get_boolean('intellihide-use-pressure')) {
this._edgeBarrier = this._createBarrier();
this._pressureBarrier = new Layout.PressureBarrier(
INTELLIHIDE_PRESSURE_THRESHOLD,
INTELLIHIDE_PRESSURE_TIME,
Shell.ActionMode.NORMAL
);
this._pressureBarrier.addBarrier(this._edgeBarrier);
this._signalsHandler.add([this._pressureBarrier, 'trigger', () => this._queueUpdatePanelPosition(true)]);
} else {
this._pointerWatch = PointerWatcher.getPointerWatcher()
.addWatch(CHECK_POINTER_MS, (x, y) => this._checkMousePointer(x, y));
}
}
_removeRevealMechanism() {
if (this._pointerWatch) {
PointerWatcher.getPointerWatcher()._removeWatch(this._pointerWatch);
}
if (this._pressureBarrier) {
this._pressureBarrier.destroy();
this._edgeBarrier.destroy();
this._pressureBarrier = 0;
}
}
_createBarrier() {
let position = this._dtpPanel.geom.position;
let opts = { backend: global.backend };
if (this._dtpPanel.checkIfVertical()) {
opts.y1 = this._monitor.y;
opts.y2 = this._monitor.y + this._monitor.height;
opts.x1 = opts.x2 = this._monitor.x;
} else {
opts.x1 = this._monitor.x;
opts.x2 = this._monitor.x + this._monitor.width;
opts.y1 = opts.y2 = this._monitor.y;
}
if (position == St.Side.TOP) {
opts.directions = Meta.BarrierDirection.POSITIVE_Y;
} else if (position == St.Side.BOTTOM) {
opts.y1 = opts.y2 = opts.y1 + this._monitor.height;
opts.directions = Meta.BarrierDirection.NEGATIVE_Y;
} else if (position == St.Side.LEFT) {
opts.directions = Meta.BarrierDirection.POSITIVE_X;
} else {
opts.x1 = opts.x2 = opts.x1 + this._monitor.width;
opts.directions = Meta.BarrierDirection.NEGATIVE_X;
}
return new Meta.Barrier(opts);
}
_checkMousePointer(x, y) {
let position = this._dtpPanel.geom.position;
if (!this._panelBox.hover && !Main.overview.visible &&
((position == St.Side.TOP && y <= this._monitor.y + 1) ||
(position == St.Side.BOTTOM && y >= this._monitor.y + this._monitor.height - 1) ||
(position == St.Side.LEFT && x <= this._monitor.x + 1) ||
(position == St.Side.RIGHT && x >= this._monitor.x + this._monitor.width - 1)) &&
((x >= this._monitor.x && x < this._monitor.x + this._monitor.width) &&
(y >= this._monitor.y && y < this._monitor.y + this._monitor.height))) {
this._queueUpdatePanelPosition(true);
}
}
_queueUpdatePanelPosition(fromRevealMechanism) {
if (!fromRevealMechanism && this._timeoutsHandler.getId(T2) && !Main.overview.visible) {
//unless this is a mouse interaction or entering/leaving the overview, limit the number
//of updates, but remember to update again when the limit timeout is reached
this._pendingUpdate = true;
} else if (!this._holdStatus) {
this._checkIfShouldBeVisible(fromRevealMechanism) ? this._revealPanel() : this._hidePanel();
this._timeoutsHandler.add([T2, MIN_UPDATE_MS, () => this._endLimitUpdate()]);
}
}
_endLimitUpdate() {
if (this._pendingUpdate) {
this._pendingUpdate = false;
this._queueUpdatePanelPosition();
}
}
_checkIfShouldBeVisible(fromRevealMechanism) {
if (Main.overview.visibleTarget || this._dtpPanel.taskbar.previewMenu.opened ||
this._dtpPanel.taskbar._dragMonitor || this._panelBox.get_hover() || this._checkIfGrab()) {
return true;
}
if (fromRevealMechanism) {
let mouseBtnIsPressed = global.get_pointer()[2] & Clutter.ModifierType.BUTTON1_MASK;
//the user is trying to reveal the panel
if (this._monitor.inFullscreen && !mouseBtnIsPressed) {
return SETTINGS.get_boolean('intellihide-show-in-fullscreen');
}
return !mouseBtnIsPressed;
}
if (!SETTINGS.get_boolean('intellihide-hide-from-windows')) {
return this._panelBox.hover;
}
return !this._windowOverlap;
}
_checkIfGrab() {
let isGrab
if (GrabHelper._grabHelperStack)
// gnome-shell < 42
isGrab = GrabHelper._grabHelperStack.some(gh => gh._owner == this._dtpPanel.panel)
else if (global.stage.get_grab_actor) {
// gnome-shell >= 42
let grabActor = global.stage.get_grab_actor()
let sourceActor = grabActor?._sourceActor || grabActor
isGrab = sourceActor &&
(sourceActor == Main.layoutManager.dummyCursor ||
this._dtpPanel.statusArea.quickSettings?.menu.actor.contains(sourceActor) ||
this._dtpPanel.panel.contains(sourceActor))
}
if (isGrab)
//there currently is a grab on a child of the panel, check again soon to catch its release
this._timeoutsHandler.add([T1, CHECK_GRAB_MS, () => this._queueUpdatePanelPosition()]);
return isGrab;
}
_revealPanel(immediate) {
if (!this._panelBox.visible) {
this._panelBox.visible = true;
this._dtpPanel.taskbar._shownInitially = false;
}
this._animatePanel(0, immediate);
}
_hidePanel(immediate) {
let position = this._dtpPanel.geom.position;
let size = this._panelBox[position == St.Side.LEFT || position == St.Side.RIGHT ? 'width' : 'height'];
let coefficient = position == St.Side.TOP || position == St.Side.LEFT ? -1 : 1;
this._animatePanel(size * coefficient, immediate);
}
_animatePanel(destination, immediate) {
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

@@ -3,7 +3,8 @@
"uuid": "zorin-taskbar@zorinos.com",
"name": "Zorin Taskbar",
"description": "A taskbar extension for the Zorin OS desktop.",
"shell-version": [ "46", "47" ],
"shell-version": [ "46", "47", "48", "49" ],
"gettext-domain": "zorin-taskbar",
"version": 65
"settings-schema": "org.gnome.shell.extensions.zorin-taskbar",
"version": 68
}

View File

@@ -1,455 +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.
*/
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_HOTKEYS_KEY = 'switch-to-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() {
this._injectionManager = new InjectionManager();
this._numHotkeys = 10;
}
enable (primaryPanel) {
this._panel = primaryPanel;
this.taskbar = primaryPanel.taskbar;
this._injectionsHandler = new Utils.InjectionsHandler();
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._timeoutsHandler = new Utils.TimeoutsHandler();
this._optionalWorkspaceIsolation();
this._optionalHotKeys();
this._optionalNumberOverlay();
this.toggleDash();
this._adaptAlloc();
this._signalsHandler.add([
SETTINGS,
[
'changed::panel-sizes'
],
() => this.toggleDash()
]);
}
disable() {
this._signalsHandler.destroy();
this._injectionsHandler.destroy();
this._timeoutsHandler.destroy();
this._injectionManager.clear();
this.toggleDash(true);
// Remove key bindings
this._disableHotKeys();
this._disableExtraShortcut();
}
toggleDash(visible) {
if (visible === undefined) {
visible = false;
}
let visibilityFunc = visible ? 'show' : 'hide';
let height = visible ? -1 : LABEL_MARGIN * Utils.getScaleFactor();
let overviewControls = Main.overview._overview._controls;
overviewControls.dash[visibilityFunc]();
overviewControls.dash.set_height(height);
}
_adaptAlloc() {
let overviewControls = Main.overview._overview._controls
this._injectionManager.overrideMethod(Object.getPrototypeOf(overviewControls), 'vfunc_allocate',
(originalAllocate) =>
(box) => {
let focusedPanel = this._panel.panelManager.focusedMonitorPanel
if (focusedPanel) {
let position = focusedPanel.geom.position
let isBottom = position == St.Side.BOTTOM
if (focusedPanel.intellihide?.enabled) {
// Panel intellihide is enabled (struts aren't taken into account on overview allocation),
// dynamically modify the overview box to follow the reveal/hide animation
let { transitioning, finalState, progress } = overviewControls._stateAdjustment.getStateTransitionParams()
let size = focusedPanel.geom[focusedPanel.checkIfVertical() ? 'w' : 'h'] *
(transitioning ? Math.abs((finalState != 0 ? 0 : 1) - progress) : 1)
if (isBottom || position == St.Side.RIGHT)
box[focusedPanel.fixedCoord.c2] -= size
else
box[focusedPanel.fixedCoord.c1] += size
} else if (isBottom)
// The default overview allocation is very good and 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.h
}
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;
//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 = SETTINGS.get_string('shortcut-num-keys');
let bothNumKeys = shortcutNumKeys == 'BOTH';
let keys = [];
let prefixModifiers = Clutter.ModifierType.SUPER_MASK
if (SETTINGS.get_string('hotkey-prefix-text') == 'SuperAlt')
prefixModifiers |= Clutter.ModifierType.MOD1_MASK
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) {
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.taskbar.toggleNumberOverlay(true);
}
_disableHotKeys() {
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: 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() {
// 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.taskbar.toggleNumberOverlay(true);
else
this.taskbar.toggleNumberOverlay(false);
}
], [
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');
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._timeoutsHandler.add([T2, timeout, () => {
if (hotkey_option != 'ALWAYS') {
this.taskbar.toggleNumberOverlay(false);
}
this._panel.intellihide.release(Intellihide.Hold.TEMPORARY);
}]);
}
_onSwipeBegin() {
this._swiping = true;
return true;
}
_onSwipeEnd() {
this._timeoutsHandler.add([
T1,
0,
() => this._swiping = false
]);
return true;
}
}

1257
panel.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,756 +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.
*/
import * as Overview from './overview.js';
import * as Panel from './panel.js';
import * as PanelSettings from './panelSettings.js';
import * as Proximity from './proximity.js';
import * as Utils from './utils.js';
import * as DesktopIconsIntegration from './desktopIconsIntegration.js';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as BoxPointer from 'resource:///org/gnome/shell/ui/boxpointer.js';
import * as LookingGlass from 'resource:///org/gnome/shell/ui/lookingGlass.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
import {InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
import {SETTINGS} from './extension.js';
import {SecondaryMonitorDisplay, WorkspacesView} from 'resource:///org/gnome/shell/ui/workspacesView.js';
export const PanelManager = class {
constructor() {
this.overview = new Overview.Overview();
this.panelsElementPositions = {};
this._injectionManager = new InjectionManager();
this._saveMonitors();
}
enable(reset) {
this.allPanels = [];
this.dtpPrimaryMonitor = Main.layoutManager.primaryMonitor; // The real primary monitor should always have the main panel
this.proximityManager = new Proximity.ProximityManager();
if (this.dtpPrimaryMonitor) {
this.primaryPanel = this._createPanel(this.dtpPrimaryMonitor, SETTINGS.get_boolean('stockgs-keep-top-panel'));
this.allPanels.push(this.primaryPanel);
this.overview.enable(this.primaryPanel);
this.setFocusedMonitor(this.dtpPrimaryMonitor);
}
if (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();
});
this._setDesktopIconsMargins();
//in 3.32, BoxPointer now inherits St.Widget
if (BoxPointer.BoxPointer.prototype.vfunc_get_preferred_height) {
let panelManager = this;
this._injectionManager.overrideMethod(BoxPointer.BoxPointer.prototype, 'vfunc_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();
if (reset) return;
this._desktopIconsUsableArea = new DesktopIconsIntegration.DesktopIconsUsableAreaClass();
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 = newUpdateHotCorners.bind(Main.layoutManager);
Main.layoutManager._updateHotCorners();
if (Main.layoutManager._interfaceSettings) {
this._enableHotCornersId = Main.layoutManager._interfaceSettings.connect('changed::enable-hot-corners', () => Main.layoutManager._updateHotCorners());
}
this._oldUpdateWorkspacesViews = Main.overview._overview._controls._workspacesDisplay._updateWorkspacesViews;
Main.overview._overview._controls._workspacesDisplay._updateWorkspacesViews = this._newUpdateWorkspacesViews.bind(Main.overview._overview._controls._workspacesDisplay);
this._oldSetPrimaryWorkspaceVisible = Main.overview._overview._controls._workspacesDisplay.setPrimaryWorkspaceVisible
Main.overview._overview._controls._workspacesDisplay.setPrimaryWorkspaceVisible = this._newSetPrimaryWorkspaceVisible.bind(Main.overview._overview._controls._workspacesDisplay);
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();
//listen settings
this._signalsHandler.add(
[
SETTINGS,
[
'changed::multi-monitors',
'changed::isolate-monitors',
'changed::panel-positions',
'changed::panel-lengths',
'changed::panel-anchors',
'changed::stockgs-keep-top-panel'
],
() => this._reset()
],
[
SETTINGS,
'changed::panel-element-positions',
() => this._updatePanelElementPositions()
],
[
SETTINGS,
'changed::intellihide-key-toggle-text',
() => this._setKeyBindings(true)
],
[
SETTINGS,
'changed::panel-sizes',
() => {
GLib.idle_add(GLib.PRIORITY_LOW, () => {
this._setDesktopIconsMargins();
return GLib.SOURCE_REMOVE;
});
}
],
[
Utils.DisplayWrapper.getMonitorManager(),
'monitors-changed',
() => {
if (Main.layoutManager.primaryMonitor) {
this._saveMonitors();
this._reset();
}
}
]
);
Panel.panelBoxes.forEach(c => this._signalsHandler.add(
[
Main.panel[c],
'child-added',
(parent, child) => {
this.primaryPanel &&
child instanceof St.Bin &&
this._adjustPanelMenuButton(this._getPanelMenuButton(child.get_first_child()), this.primaryPanel.monitor, this.primaryPanel.getPosition())
}
]
));
this._setKeyBindings(true);
// keep GS overview.js from blowing away custom panel styles
if(!SETTINGS.get_boolean('stockgs-keep-top-panel'))
Object.defineProperty(Main.panel, "style", {configurable: true, set(v) {}});
}
disable(reset) {
this.primaryPanel && 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);
p.panelBox.add_child(p.panel);
p.panelBox.set_position(clipContainer.x, clipContainer.y);
clipContainer.remove_child(p.panelBox);
Main.layoutManager.addChrome(p.panelBox, { affectsStruts: true, trackFullscreen: true });
}
});
this._injectionManager.clear();
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._overview._controls._workspacesDisplay._updateWorkspacesViews = this._oldUpdateWorkspacesViews;
Main.overview._overview._controls._workspacesDisplay.setPrimaryWorkspaceVisible = this._oldSetPrimaryWorkspaceVisible;
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
delete Main.panel.style;
this._desktopIconsUsableArea.destroy();
this._desktopIconsUsableArea = null;
}
toggleDash() {
this.overview.toggleDash();
}
_setDesktopIconsMargins() {
this._desktopIconsUsableArea?.resetMargins();
this.allPanels.forEach(p => {
switch(p.geom.position) {
case St.Side.TOP:
this._desktopIconsUsableArea?.setMargins(p.monitor.index, p.geom.h, 0, 0, 0);
break;
case St.Side.BOTTOM:
this._desktopIconsUsableArea?.setMargins(p.monitor.index, 0, p.geom.h, 0, 0);
break;
case St.Side.LEFT:
this._desktopIconsUsableArea?.setMargins(p.monitor.index, 0, 0, p.geom.w, 0);
break;
case St.Side.RIGHT:
this._desktopIconsUsableArea?.setMargins(p.monitor.index, 0, 0, 0, p.geom.w);
break;
}
});
}
setFocusedMonitor(monitor) {
this.focusedMonitorPanel = this.allPanels.find(p => p.monitor == monitor)
if (!this.checkIfFocusedMonitor(monitor)) {
Main.overview._overview.clear_constraints();
Main.overview._overview.add_constraint(new Layout.MonitorConstraint({ index: monitor.index }));
Main.overview._overview._controls._workspacesDisplay._primaryIndex = monitor.index;
}
}
_newSetPrimaryWorkspaceVisible(visible) {
if (this._primaryVisible === visible)
return;
this._primaryVisible = visible;
const primaryIndex = Main.overview._overview._controls._workspacesDisplay._primaryIndex;
const primaryWorkspace = this._workspacesViews[primaryIndex];
if (primaryWorkspace)
primaryWorkspace.visible = visible;
}
_newUpdateWorkspacesViews() {
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 view;
if (i === this._primaryIndex) {
view = new WorkspacesView(i,
this._controls,
this._scrollAdjustment,
this._fitModeAdjustment,
this._overviewAdjustment);
view.visible = this._primaryVisible;
this.bind_property('opacity', view, 'opacity', GObject.BindingFlags.SYNC_CREATE);
this.add_child(view);
} else {
// No idea why atm, but we need the import at the top of this file and to use the
// full imports ns here, otherwise SecondaryMonitorDisplay can't be used ¯\_(ツ)_/¯
view = new SecondaryMonitorDisplay(i,
this._controls,
this._scrollAdjustment,
this._fitModeAdjustment,
this._overviewAdjustment);
Main.layoutManager.overviewGroup.add_child(view);
}
this._workspacesViews.push(view);
}
}
_saveMonitors() {
//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 keyMonitors = 'available-monitors';
let primaryIndex = Main.layoutManager.primaryIndex;
let newMonitors = [primaryIndex];
Main.layoutManager.monitors.filter(m => m.index != primaryIndex).forEach(m => newMonitors.push(m.index));
SETTINGS.set_value(keyMonitors, new GLib.Variant('ai', newMonitors));
}
checkIfFocusedMonitor(monitor) {
return Main.overview._overview._controls._workspacesDisplay._primaryIndex == monitor.index;
}
_createPanel(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);
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.Panel(this, monitor, panelBox, isStandalone);
panelBox.add_child(panel);
panel.enable();
panelBox.visible = true;
if (monitor.inFullscreen) {
panelBox.hide();
}
panelBox.set_position(0, 0);
return panel;
}
_reset() {
this.disable(true);
this.allPanels = [];
this.enable(true);
}
_updatePanelElementPositions() {
this.panelsElementPositions = PanelSettings.getSettingsJson(SETTINGS, 'panel-element-positions');
this.allPanels.forEach(p => p.updateElementPositions());
}
_adjustPanelMenuButton(button, monitor, arrowSide) {
if (button) {
button.menu._boxPointer._dtpSourceActor = button.menu._boxPointer.sourceActor;
button.menu._boxPointer.sourceActor = button;
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(boxPointer, alloc, monitor) {
if (boxPointer._dtpInPanel && boxPointer.sourceActor && 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(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(panel) {
if (panel.isStandalone && panel._rightPanelBarrier) {
panel._rightPanelBarrier.destroy();
}
if (panel._leftPanelBarrier) {
panel._leftPanelBarrier.destroy();
delete panel._leftPanelBarrier;
}
}
_getPanelMenuButton(obj) {
return obj instanceof PanelMenu.Button && obj.menu?._boxPointer ? obj : 0;
}
_setKeyBindings(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, SETTINGS, keys[k], Shell.ActionMode.NORMAL);
}
});
}
};
// This class drives long-running icon animations, to keep them running in sync
// with each other.
export const IconAnimator = class {
constructor(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() {
this._timeline.stop();
this._timeline = null;
for (let 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() {
if (this._started && this._count > 0) {
this._timeline.stop();
}
this._started = false;
}
start() {
if (!this._started && this._count > 0) {
this._timeline.start();
}
this._started = true;
}
addAnimation(target, name) {
const targetDestroyId = target.connect('destroy', () => this.removeAnimation(target, name));
this._animations[name].push({ target: target, targetDestroyId: targetDestroyId });
if (this._started && this._count === 0) {
this._timeline.start();
}
this._count++;
}
removeAnimation(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 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 => Object.getPrototypeOf(corner).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.NEGATIVE_Y);
barriers._leftPanelBarrier.push(panel.monitor.y, Meta.BarrierDirection.POSITIVE_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 + barrierSize;
fixed2 = panel.monitor.x;
break;
case St.Side.RIGHT:
fixed1 = panel.monitor.x + panel.monitor.width - barrierSize;
fixed2 = panel.monitor.x + panel.monitor.width;
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 = {
backend: global.backend,
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();
this._hiddenY = Main.layoutManager.primaryMonitor.y + topOffset - this.height;
this._targetY = this._hiddenY + this.height;
this.y = this._hiddenY;
this._objInspector.set_position(this.x + Math.floor(this.width * 0.1), this._targetY + Math.floor(this.height * 0.1));
}
function _newLookingGlassOpen() {
if (this._open)
return;
this._resize();
this._oldOpen();
}

View File

@@ -1,64 +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
*/
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: 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 },
];
export const optionDialogFunctions = {};
optionDialogFunctions[DATE_MENU] = '_showDateMenuOptions';
optionDialogFunctions[DESKTOP_BTN] = '_showDesktopButtonOptions';
export function checkIfCentered(position) {
return position == CENTERED || position == CENTERED_MONITOR;
}

View File

@@ -1,111 +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/>.
*/
import * as Pos from './panelPositions.js';
/** Return object representing a settings value that is stored as JSON. */
export function getSettingsJson(settings, setting) {
try {
return JSON.parse(settings.get_string(setting));
} catch(e) {
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);
} catch(e) {
log('Error serializing setting: ' + e.message);
}
}
/** Returns size of panel on a specific monitor, in pixels. */
export function getPanelSize(settings, monitorIndex) {
const sizes = getSettingsJson(settings, 'panel-sizes');
// Pull in deprecated setting if panel-sizes does not have setting for monitor.
const fallbackSize = settings.get_int('panel-size');
const theDefault = 48;
return sizes[monitorIndex] || fallbackSize || theDefault;
}
export function setPanelSize(settings, monitorIndex, value) {
if (!(Number.isInteger(value) && value <= 128 && value >= 16)) {
log('Not setting invalid panel size: ' + value);
return;
}
let sizes = getSettingsJson(settings, 'panel-sizes');
sizes[monitorIndex] = value;
setSettingsJson(settings, 'panel-sizes', sizes);
}
/**
* Returns length of panel on a specific monitor, as a whole number percent,
* from settings. e.g. 100
*/
export function getPanelLength(settings, monitorIndex) {
const lengths = getSettingsJson(settings, 'panel-lengths');
const theDefault = 100;
return lengths[monitorIndex] || theDefault;
}
export function setPanelLength(settings, monitorIndex, value) {
if (!(Number.isInteger(value) && value <= 100 && value >= 0)) {
log('Not setting invalid panel length: ' + value);
return;
}
let lengths = getSettingsJson(settings, 'panel-lengths');
lengths[monitorIndex] = value;
setSettingsJson(settings, 'panel-lengths', lengths);
}
/** Returns position of panel on a specific monitor. */
export function getPanelPosition(settings, monitorIndex) {
const positions = getSettingsJson(settings, 'panel-positions');
const fallbackPosition = settings.get_string('panel-position');
const theDefault = Pos.BOTTOM;
return positions[monitorIndex] || fallbackPosition || theDefault;
}
export function setPanelPosition(settings, monitorIndex, value) {
if (!(value === Pos.TOP || value === Pos.BOTTOM || value === Pos.LEFT
|| value === Pos.RIGHT)) {
log('Not setting invalid panel position: ' + value);
return;
}
const positions = getSettingsJson(settings, 'panel-positions');
positions[monitorIndex] = value;
setSettingsJson(settings, 'panel-positions', positions);
}
/** Returns anchor location of panel on a specific monitor. */
export function getPanelAnchor(settings, monitorIndex) {
const anchors = getSettingsJson(settings, 'panel-anchors');
const theDefault = Pos.MIDDLE;
return anchors[monitorIndex] || theDefault;
}
export function setPanelAnchor(settings, monitorIndex, value) {
if (!(value === Pos.START || value === Pos.MIDDLE || value === Pos.END)) {
log('Not setting invalid panel anchor: ' + value);
return;
}
const anchors = getSettingsJson(settings, 'panel-anchors');
anchors[monitorIndex] = value;
setSettingsJson(settings, 'panel-anchors', anchors);
}

View File

@@ -1,161 +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
*/
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);
}
}
}

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,8 +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"

View File

@@ -1412,9 +1412,6 @@ msgstr "Zobrazit ikonu plochy"
msgid "Show Desktop button padding (px)"
msgstr "vybarvení tlačítka Zobraz plochu"
msgid "Floating rounded theme"
msgstr "Plovoucí zaoblený vzhled"
#~ msgid "Top, with plugin icons collapsed to bottom"
#~ msgstr "Nahoře, ikony dole"

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"

View File

@@ -1479,9 +1479,6 @@ msgstr "Desktop-Symbol anzeigen"
msgid "Show Desktop button padding (px)"
msgstr "Padding (px) für Desktop-Symbol anzeigen"
msgid "Floating rounded theme"
msgstr "Schwebendes abgerundetes Thema"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "Isolieren Sie Arbeitsbereiche und Monitore in den Anwendungswechseleinstellungen"

View File

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

View File

@@ -1507,9 +1507,6 @@ msgstr "Mostrar Escritorio - ícono"
msgid "Show Desktop button padding (px)"
msgstr "Mostrar Escritorio - espaciado del botón (px)"
msgid "Floating rounded theme"
msgstr "Tema flotante redondeado"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "Aislar espacios de trabajo y monitores en la configuración de Cambio de aplicaciones"

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"

View File

@@ -1466,9 +1466,6 @@ msgstr "نمایش آیکون دسکتاپ"
msgid "Show Desktop button padding (px)"
msgstr "حاشیه‌ی دکمه‌ی نمایش دسکتاپ (px)"
msgid "Floating rounded theme"
msgstr "تم گرد و شناور"
#~ msgid "Show Details"
#~ msgstr "نمایش جزییات"

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"

1392
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"

View File

@@ -1505,9 +1505,6 @@ msgstr "Mostrar iconas na escritorio"
msgid "Show Desktop button padding (px)"
msgstr "Recheo Mostrar o botón do escritorio (px)"
msgid "Floating rounded theme"
msgstr "Tema redondeado flotante"
#~ msgid "Top, with plugin icons collapsed to bottom"
#~ msgstr "Arriba, coas iconas contraídas"

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,8 +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"

View File

@@ -1503,9 +1503,6 @@ msgstr "Asztali ikon megjelenítése"
msgid "Show Desktop button padding (px)"
msgstr "Asztal megjelenítése gomb margója (px)"
msgid "Floating rounded theme"
msgstr "Lebegő lekerekített téma"
#~ msgid "Current Show Applications icon"
#~ msgstr "Jelenlegi „Alkalmazások megjelenítése” ikon"

View File

@@ -22,8 +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"

View File

@@ -2,14 +2,14 @@
# This file is distributed under the same license as the Dash to Panel package.
# Enrico Bella <enricobe@hotmail.com>, 2018.
# Kowalski7cc <kowalski.7cc@gmail.com>, 2020.
# Albano Battistella <albanobattistella@gmail.com>, 2023.
# Albano Battistella <albanobattistella@gmail.com>, 2023,2025.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-08 08:31-0500\n"
"PO-Revision-Date: 2023-12-22 20:38+0200\n"
"PO-Revision-Date: 2025-09-14 13:38+0200\n"
"Last-Translator: Albano Battistella <albanoattistella@gmail.com>\n"
"Language-Team: Italian\n"
"Language: it\n"
@@ -262,9 +262,8 @@ msgid "Windows"
msgstr "Finestre"
#: appIcons.js:1786
#, fuzzy
msgid "Power options"
msgstr "Altre opzioni"
msgstr "Opzioni di alimentazione"
#: appIcons.js:1791
msgid "Event logs"
@@ -1428,9 +1427,6 @@ msgstr "Mostra le icone del Desktop"
msgid "Show Desktop button padding (px)"
msgstr "Spaziatura del pulsante Mostra Desktop (px)"
msgid "Floating rounded theme"
msgstr "Tema arrotondato fluttuante"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "Isola aree di lavoro e monitor nelle impostazioni di Cambio applicazione"

3973
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 "მოფარფატე მომრგვალო თემა"

View File

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

View File

@@ -1469,9 +1469,6 @@ msgstr "바탕 화면 아이콘 표시"
msgid "Show Desktop button padding (px)"
msgstr "바탕 화면 버튼 표시 패딩(px)"
msgid "Floating rounded theme"
msgstr "떠 다니는 둥근 테마"
#~ msgid "Current Show Applications icon"
#~ msgstr "현재 프로그램 표시 아이콘"

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 "फ्लोटिंग गोल थिम"

View File

@@ -1502,9 +1502,6 @@ msgstr "Pictogram Bureaublad weergeven"
msgid "Show Desktop button padding (px)"
msgstr "Opvulling Pictogram bureaublad weergeven (px)"
msgid "Floating rounded theme"
msgstr "Zwevend afgerond thema"
msgid "Isolate Workspaces and Monitors in Application Switching settings"
msgstr "Isoleer werkruimten en monitoren in de instellingen voor het Wisselen tussen toepassingen"

View File

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

3620
po/pl.po

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +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"

View File

@@ -709,7 +709,7 @@ msgstr "p.e. <Super>i"
#: ui/BoxOverlayShortcut.ui.h:14
#, fuzzy
msgid "Show window previews on hotkey"
msgstr "Mostrar pré-visulização da janela ao pairar"
msgstr "Mostrar pré-visualização da janela ao pairar"
#: ui/BoxOverlayShortcut.ui.h:15
msgid "Show previews when the application have multiple instances"
@@ -824,11 +824,11 @@ msgstr "Usar o botão do meio na visualização para fechar a janela "
#: ui/BoxWindowPreviewOptions.ui.h:8
msgid "Window previews preferred size (px)"
msgstr "Tamanho preferido da pré-visulização da janela (px)"
msgstr "Tamanho preferido da pré-visualização da janela (px)"
#: ui/BoxWindowPreviewOptions.ui.h:9
msgid "Window previews aspect ratio X (width)"
msgstr "Proporção da pré-visulização da janela no eixto X (comprimento)"
msgstr "Proporção da pré-visualização da janela no eixto X (comprimento)"
#: ui/BoxWindowPreviewOptions.ui.h:10
msgid "1"
@@ -920,11 +920,11 @@ msgstr "Fixo"
#: ui/BoxWindowPreviewOptions.ui.h:32
msgid "Window previews aspect ratio Y (height)"
msgstr "Proporção da pré-visulização da janela no eixto Y (altura)"
msgstr "Proporção da pré-visualização da janela no eixto Y (altura)"
#: ui/BoxWindowPreviewOptions.ui.h:33
msgid "Window previews padding (px)"
msgstr "Preenchimento da pré-visulização de janela (px)"
msgstr "Preenchimento da pré-visualização de janela (px)"
#: ui/BoxWindowPreviewOptions.ui.h:34
msgid "Use custom opacity for the previews background"
@@ -1167,12 +1167,12 @@ msgstr ""
#: ui/SettingsBehavior.ui.h:8
msgid "Show window previews on hover"
msgstr "Mostrar pré-visulização da janela ao pairar"
msgstr "Mostrar pré-visualização da janela ao pairar"
#: ui/SettingsBehavior.ui.h:9
#, fuzzy
msgid "Show tooltip on hover"
msgstr "Mostrar pré-visulização da janela ao pairar"
msgstr "Mostrar pré-visualização da janela ao pairar"
#: ui/SettingsBehavior.ui.h:10
msgid "Isolate"
@@ -1513,9 +1513,6 @@ 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"

784
po/ru.po

File diff suppressed because it is too large Load Diff

View File

@@ -1404,9 +1404,6 @@ msgstr "Zobrať ikonu plochy"
msgid "Show Desktop button padding (px)"
msgstr "Zobraziť ikonu plochy výplň (px)"
msgid "Floating rounded theme"
msgstr "Zaoblený plávajúci motív"
#, javascript-format
#~ msgid "%d ."
#~ msgstr "%d ."

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 "Плутајућа заобљена тема"

View File

@@ -1504,9 +1504,6 @@ msgstr "Visa Skrivbordsikonen"
msgid "Show Desktop button padding (px)"
msgstr "Fyllnad (px) för knappen Visa Skrivbordet"
msgid "Floating rounded theme"
msgstr "Flytande rundat tema"
#~ msgid "Top, with plugin icons collapsed to bottom"
#~ msgstr "Topp, med tilläggsikoner kollapsade till botten"

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 "ธีมกลมลอย"

1336
po/tr.po

File diff suppressed because it is too large Load Diff

1278
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"

View File

@@ -1441,9 +1441,6 @@ msgstr "显示桌面图标"
msgid "Show Desktop button padding (px)"
msgstr "“显示桌面按钮”的内边距(px)"
msgid "Floating rounded theme"
msgstr "悬浮圆角主题"
#, javascript-format
#~ msgid "%d ."
#~ msgstr "%d ."

View File

@@ -1498,9 +1498,6 @@ msgstr "顯示桌面圖示"
msgid "Show Desktop button padding (px)"
msgstr "顯示桌面按鈕 空間 (像素)"
msgid "Floating rounded theme"
msgstr "浮動圓滑主題"
#~ msgid "Show Details"
#~ msgstr "顯示詳細資訊"

1170
prefs.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,601 +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
*/
import Cairo from 'cairo';
import Gio from 'gi://Gio';
import Clutter from 'gi://Clutter';
import Pango from 'gi://Pango';
import St from 'gi://St';
import * as Utils from './utils.js';
import {SETTINGS} from './extension.js';
import {EventEmitter} from 'resource:///org/gnome/shell/misc/signals.js';
export const ProgressManager = class extends EventEmitter {
constructor() {
super();
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() {
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() {
return Object.keys(this._entriesByDBusName).length;
}
lookupByDBusName(dbusName) {
return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null;
}
lookupById(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(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(entry) {
delete this._entriesByDBusName[entry.dbusName()]
this.emit('progress-entry-removed', entry);
}
_acquireUnityDBus() {
if (!this._unity_bus_id) {
Gio.DBus.session.own_name('com.canonical.Unity',
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
}
}
_releaseUnityDBus() {
if (this._unity_bus_id) {
Gio.DBus.session.unown_name(this._unity_bus_id);
this._unity_bus_id = 0;
}
}
_onEntrySignalReceived(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(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(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);
}
}
};
export class AppProgress extends EventEmitter {
constructor(dbusName, appId, properties) {
super();
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() {
return this._appId;
}
dbusName() {
return this._dbusName;
}
count() {
return this._count;
}
setCount(count) {
if (this._count != count) {
this._count = count;
this.emit('count-changed', this._count);
}
}
countVisible() {
return this._countVisible;
}
setCountVisible(countVisible) {
if (this._countVisible != countVisible) {
this._countVisible = countVisible;
this.emit('count-visible-changed', this._countVisible);
}
}
progress() {
return this._progress;
}
setProgress(progress) {
if (this._progress != progress) {
this._progress = progress;
this.emit('progress-changed', this._progress);
}
}
progressVisible() {
return this._progressVisible;
}
setProgressVisible(progressVisible) {
if (this._progressVisible != progressVisible) {
this._progressVisible = progressVisible;
this.emit('progress-visible-changed', this._progressVisible);
}
}
urgent() {
return this._urgent;
}
setUrgent(urgent) {
if (this._urgent != urgent) {
this._urgent = urgent;
this.emit('urgent-changed', this._urgent);
}
}
setDBusName(dbusName) {
if (this._dbusName != dbusName) {
let oldName = this._dbusName;
this._dbusName = dbusName;
this.emit('dbus-name-changed', oldName);
}
}
update(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(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(SETTINGS.get_boolean('progress-show-bar') && other[property].get_boolean());
} else if (property == 'urgent') {
this.setUrgent(other[property].get_boolean());
} else {
// Not implemented yet
}
}
}
}
}
}
export const ProgressIndicator = class {
constructor(source, progressManager) {
this._source = source;
this._progressManager = progressManager;
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._sourceDestroyId = this._source.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() {
this._source.disconnect(this._sourceDestroyId);
this._signalsHandler.destroy();
}
_onEntryAdded(appProgress, entry) {
if (!entry || !entry.appId())
return;
if (this._source && this._source.app && this._source.app.id == entry.appId()) {
this.insertEntry(entry);
}
}
_onEntryRemoved(appProgress, entry) {
if (!entry || !entry.appId())
return;
if (this._source && this._source.app && this._source.app.id == entry.appId()) {
this.removeEntry(entry);
}
}
updateNotificationBadge() {
this._source.updateNumberOverlay(this._notificationBadgeBin);
this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE;
}
_notificationBadgeCountToText(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(count) {
this._notificationBadgeCount = count;
let text = this._notificationBadgeCountToText(count);
this._notificationBadgeLabel.set_text(text);
}
toggleNotificationBadge(activate) {
if (activate && this._notificationBadgeCount > 0) {
this.updateNotificationBadge();
this._notificationBadgeBin.show();
}
else
this._notificationBadgeBin.hide();
}
_showProgressOverlay() {
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 Utils.ColorUtils.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 Utils.ColorUtils.Color({red: 230, green: 230, blue: 230, alpha: 255});
this._updateProgressOverlay();
}
_hideProgressOverlay() {
if (this._progressOverlayArea)
this._progressOverlayArea.destroy();
this._progressOverlayArea = null;
this._progressbar_background = null;
this._progressbar_border = null;
}
_updateProgressOverlay() {
if (this._progressOverlayArea) {
this._progressOverlayArea.queue_repaint();
}
}
_drawProgressOverlay(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(progress) {
this._progress = Math.min(Math.max(progress, 0.0), 1.0);
this._updateProgressOverlay();
}
toggleProgressOverlay(activate) {
if (activate) {
this._showProgressOverlay();
}
else {
this._hideProgressOverlay();
}
}
insertEntry(appProgress) {
if (!appProgress || this._progressManagerEntries.indexOf(appProgress) !== -1)
return;
this._progressManagerEntries.push(appProgress);
this._selectEntry(appProgress);
}
removeEntry(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(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,260 +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
*/
import Meta from 'gi://Meta';
import Mtk from 'gi://Mtk';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Utils from './utils.js';
//timeout intervals
const MIN_UPDATE_MS = 200;
//timeout names
const T1 = 'limitUpdateTimeout';
export const Mode = {
ALL_WINDOWS: 0,
FOCUSED_WINDOWS: 1,
MAXIMIZED_WINDOWS: 2
};
export class ProximityWatch {
constructor(actor, monitorIndex, mode, xThreshold, yThreshold, handler) {
this.actor = actor;
this.monitorIndex = monitorIndex
this.overlap = false;
this.mode = mode;
this.threshold = [xThreshold, yThreshold];
this.handler = handler;
this._allocationChangedId = actor.connect('notify::allocation', () => this._updateWatchRect());
this._updateWatchRect();
}
destroy() {
this.actor.disconnect(this._allocationChangedId);
}
_updateWatchRect() {
let [actorX, actorY] = this.actor.get_position();
this.rect = new Mtk.Rectangle({
x: actorX - this.threshold[0],
y: actorY - this.threshold[1],
width: this.actor.width + this.threshold[0] * 2,
height: this.actor.height + this.threshold[1] * 2
});
}
};
export const ProximityManager = class {
constructor() {
this._counter = 1;
this._watches = {};
this._focusedWindowInfo = null;
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._timeoutsHandler = new Utils.TimeoutsHandler();
this._bindSignals();
this._setFocusedWindow();
}
createWatch(actor, monitorIndex, mode, xThreshold, yThreshold, handler) {
let watch = new ProximityWatch(actor, monitorIndex, mode, xThreshold, yThreshold, handler);
this._watches[this._counter] = watch;
this.update();
return this._counter++;
}
removeWatch(id) {
if (this._watches[id]) {
this._watches[id].destroy();
delete this._watches[id];
}
}
update() {
this._queueUpdate(true);
}
destroy() {
this._signalsHandler.destroy();
this._timeoutsHandler.destroy();
this._disconnectFocusedWindow();
Object.keys(this._watches).forEach(id => this.removeWatch(id));
}
_bindSignals() {
this._signalsHandler.add(
[
global.window_manager,
'switch-workspace',
() => this._queueUpdate()
],
[
Main.overview,
'hidden',
() => this._queueUpdate()
],
[
global.display,
'notify::focus-window',
() => {
this._setFocusedWindow();
this._queueUpdate();
}
],
[
global.display,
'restacked',
() => this._queueUpdate()
]
);
}
_setFocusedWindow() {
this._disconnectFocusedWindow();
let focusedWindow = global.display.focus_window;
if (focusedWindow) {
let focusedWindowInfo = this._getFocusedWindowInfo(focusedWindow);
if (focusedWindowInfo && this._checkIfHandledWindowType(focusedWindowInfo.metaWindow)) {
focusedWindowInfo.allocationId = focusedWindowInfo.window.connect('notify::allocation', () => this._queueUpdate());
focusedWindowInfo.destroyId = focusedWindowInfo.window.connect('destroy', () => this._disconnectFocusedWindow(true));
this._focusedWindowInfo = focusedWindowInfo;
}
}
}
_getFocusedWindowInfo(focusedWindow) {
let window = focusedWindow.get_compositor_private();
let focusedWindowInfo;
if (window) {
focusedWindowInfo = { window: window };
focusedWindowInfo.metaWindow = focusedWindow;
if (focusedWindow.is_attached_dialog()) {
let mainMetaWindow = focusedWindow.get_transient_for();
if (focusedWindowInfo.metaWindow.get_frame_rect().height < mainMetaWindow.get_frame_rect().height) {
focusedWindowInfo.window = mainMetaWindow.get_compositor_private();
focusedWindowInfo.metaWindow = mainMetaWindow;
}
}
}
return focusedWindowInfo;
}
_disconnectFocusedWindow(destroy) {
if (this._focusedWindowInfo && !destroy) {
this._focusedWindowInfo.window.disconnect(this._focusedWindowInfo.allocationId);
this._focusedWindowInfo.window.disconnect(this._focusedWindowInfo.destroyId);
}
this._focusedWindowInfo = null;
}
_getHandledWindows() {
return Utils.getCurrentWorkspace()
.list_windows()
.filter(mw => this._checkIfHandledWindow(mw));
}
_checkIfHandledWindow(metaWindow) {
return metaWindow &&
!metaWindow.minimized &&
!metaWindow.customJS_ding &&
metaWindow.window_type != Meta.WindowType.DESKTOP &&
this._checkIfHandledWindowType(metaWindow);
}
_checkIfHandledWindowType(metaWindow) {
let metaWindowType = metaWindow.get_window_type();
//https://www.roojs.org/seed/gir-1.2-gtk-3.0/seed/Meta.WindowType.html
return metaWindowType <= Meta.WindowType.SPLASHSCREEN &&
metaWindowType != Meta.WindowType.DESKTOP;
}
_queueUpdate(noDelay) {
if (!noDelay && this._timeoutsHandler.getId(T1)) {
//limit the number of updates
this._pendingUpdate = true;
return;
}
this._timeoutsHandler.add([T1, MIN_UPDATE_MS, () => this._endLimitUpdate()]);
let metaWindows = this._getHandledWindows();
Object.keys(this._watches).forEach(id => {
let watch = this._watches[id];
let overlap = !!this._update(watch, metaWindows);
if (overlap !== watch.overlap) {
watch.handler(overlap);
watch.overlap = overlap;
}
});
}
_endLimitUpdate() {
if (this._pendingUpdate) {
this._pendingUpdate = false;
this._queueUpdate();
}
}
_update(watch, metaWindows) {
if (watch.mode === Mode.FOCUSED_WINDOWS)
return (this._focusedWindowInfo &&
this._checkIfHandledWindow(this._focusedWindowInfo.metaWindow) &&
this._checkProximity(this._focusedWindowInfo.metaWindow, watch));
if (watch.mode === Mode.MAXIMIZED_WINDOWS)
return metaWindows.some(mw => mw.maximized_vertically && mw.maximized_horizontally &&
mw.get_monitor() == watch.monitorIndex);
//Mode.ALL_WINDOWS
return metaWindows.some(mw => this._checkProximity(mw, watch));
}
_checkProximity(metaWindow, watch) {
let windowRect = metaWindow.get_frame_rect();
return windowRect.overlap(watch.rect) &&
((!watch.threshold[0] && !watch.threshold[1]) ||
metaWindow.get_monitor() == watch.monitorIndex ||
windowRect.overlap(global.display.get_monitor_geometry(watch.monitorIndex)));
}
};

View File

@@ -18,6 +18,7 @@
<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'/>
@@ -71,7 +72,7 @@
</key>
<key type="s" name="panel-lengths">
<default>'{}'</default>
<summary>Percentages of screen edge for panel to span</summary>
<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">
@@ -99,10 +100,10 @@
<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-dash">
<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 dash</summary>
<description>Whether to keep the stock gnome-shell dash while in overview</description>
</key>
<key type="b" name="stockgs-keep-top-panel">
<default>false</default>
@@ -119,6 +120,10 @@
<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>
@@ -149,6 +154,26 @@
<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>
@@ -156,24 +181,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>
@@ -189,10 +239,9 @@
<summary>Keybinding toggle intellihide</summary>
<description>Keybinding to reveal the panel while in intellihide mode</description>
</key>
<key type="b" name="floating-rounded-theme">
<default>false</default>
<summary>Floating rounded theme</summary>
<description>Display the panel with a floating rounded theme</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>
@@ -279,11 +328,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>
@@ -292,7 +336,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>
@@ -337,6 +381,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>
@@ -802,5 +850,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)
}
}

173
src/extension.js Normal file
View File

@@ -0,0 +1,173 @@
/*
* 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() {
// Workaround for race condition in GNOME Shell where enable() may be called multiple times
if (this._alreadyEnabled) {
return
}
this._alreadyEnabled = true
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()
PanelSettings.clearCache()
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()
this._alreadyEnabled = false
}
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::stockgs-keep-dash', '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 = SETTINGS.get_boolean('stockgs-keep-dash')
}
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

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

300
src/panelSettings.js Normal file
View File

@@ -0,0 +1,300 @@
/*
* 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
cache = {}
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)
}
}
}

1862
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),
))
)
}
}

View File

@@ -38,13 +38,8 @@
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,
#zorintaskbarScrollview .overview-tile:hover .dtp-container.animate-appicon-hover,
.zorintaskbarMainPanel .dash-item-container .show-apps:hover .overview-icon {
background: none;
}
@@ -112,32 +107,13 @@
spacing: 0px;
}
#panelBox.floating {
background: transparent;
}
#panelBox.floating.top {
padding: 8px 8px 0 8px;
}
#panelBox.floating.right {
padding: 8px 8px 8px 0;
}
#panelBox.floating.bottom {
padding: 0 8px 8px 8px;
}
#panelBox.floating.left {
padding: 8px 0 8px 8px;
}
#panelBox.floating #panel,
#panelBox.floating .panel-button,
#panelBox.floating .panel-button.clock-display .clock {
border-radius: 10px;
}
#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 {
@@ -157,5 +133,71 @@
.preview-container,
#preview-menu {
border-radius: 15px;
}
/* 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

247
src/transparency.js Normal file
View File

@@ -0,0 +1,247 @@
/*
* This file is part of the Zorin Taskbar extension for Zorin OS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Credits:
* This file is based on code from the Dash to Panel extension
*/
import GdkPixbuf from 'gi://GdkPixbuf'
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
import St from 'gi://St'
import * as Proximity from './proximity.js'
import * as Utils from './utils.js'
import { SETTINGS } from './extension.js'
const TRANS_DYNAMIC_DISTANCE = 20
export const DynamicTransparency = class {
constructor(dtpPanel) {
this._dtpPanel = dtpPanel
this._proximityManager = dtpPanel.panelManager.proximityManager
this._proximityWatchId = 0
this.currentBackgroundColor = 0
this._initialPanelStyle = dtpPanel.panel.get_style()
this._signalsHandler = new Utils.GlobalSignalsHandler()
this._bindSignals()
this._updateAllAndSet()
this._updateProximityWatch()
}
destroy() {
this._signalsHandler.destroy()
this._proximityManager.removeWatch(this._proximityWatchId)
this._dtpPanel.panel.set_style(this._initialPanelStyle)
}
updateExternalStyle() {
this._setBackground()
}
_bindSignals() {
this._signalsHandler.add(
[Utils.getStageTheme(), 'changed', () => this._updateAllAndSet()],
[Main.overview, ['showing', 'hiding'], () => this._updateAlphaAndSet()],
[
SETTINGS,
[
'changed::trans-use-custom-opacity',
'changed::trans-panel-opacity',
'changed::trans-dynamic-anim-target',
'changed::trans-use-dynamic-opacity',
],
() => this._updateAlphaAndSet(),
],
[
SETTINGS,
[
'changed::trans-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._setBackground()
this._setBorder()
}
_updateAlphaAndSet() {
this._updateAlpha()
this._setBackground()
}
_updateBorderAndSet() {
this._updateBorder()
this._setBorder()
}
_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 : ''
}
_setBackground() {
this.currentBackgroundColor = Utils.getrgbaColor(
this.backgroundColorRgb,
this.alpha,
)
let transition = 'transition-duration: 300ms;'
this._dtpPanel.set_style(
'background-color: ' + this.currentBackgroundColor + transition,
)
}
_setBorder() {
this._dtpPanel.panel.set_style(
'background: none; ' +
'border-image: none; ' +
'background-image: none; ' +
this._borderStyle +
'transition-duration: 300ms;'
)
}
_getThemeBackground(reload) {
if (reload || !this._themeBackground) {
let fakePanel = new St.Bin({ name: 'panel' })
Main.uiGroup.add_child(fakePanel)
let fakeTheme = fakePanel.get_theme_node()
this._themeBackground =
this._getBackgroundImageColor(fakeTheme) ||
fakeTheme.get_background_color()
Main.uiGroup.remove_child(fakePanel)
}
return this._themeBackground
}
_getBackgroundImageColor(theme) {
let bg = null
try {
let imageFile =
theme.get_background_image() || theme.get_border_image()?.get_file()
if (imageFile) {
let imageBuf = GdkPixbuf.Pixbuf.new_from_file(imageFile.get_path())
let pixels = imageBuf.get_pixels()
bg = {
red: pixels[0],
green: pixels[1],
blue: pixels[2],
alpha: pixels[3],
}
}
} catch (error) {
console.log(error)
}
return bg
}
}

969
src/utils.js Normal file
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

1282
taskbar.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,200 +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
*/
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._updateComplementaryStyles();
this._setBackground();
}
_bindSignals() {
this._signalsHandler.add(
[
Utils.getStageTheme(),
'changed',
() => this._updateAllAndSet()
],
[
Main.overview,
[
'showing',
'hiding'
],
() => this._updateAlphaAndSet()
],
[
SETTINGS,
[
'changed::trans-use-custom-opacity',
'changed::trans-panel-opacity',
'changed::trans-dynamic-anim-target',
'changed::trans-use-dynamic-opacity'
],
() => this._updateAlphaAndSet()
],
[
SETTINGS,
[
'changed::trans-dynamic-behavior',
'changed::trans-use-dynamic-opacity'
],
() => this._updateProximityWatch()
]
);
}
_updateProximityWatch() {
this._proximityManager.removeWatch(this._proximityWatchId);
if (SETTINGS.get_boolean('trans-use-dynamic-opacity')) {
let isVertical = this._dtpPanel.checkIfVertical();
let threshold = TRANS_DYNAMIC_DISTANCE;
this._windowOverlap = false;
this._updateAlphaAndSet()
this._proximityWatchId = this._proximityManager.createWatch(
this._dtpPanel.panelBox.get_parent(),
this._dtpPanel.monitor.index,
Proximity.Mode[SETTINGS.get_string('trans-dynamic-behavior')],
isVertical ? threshold : 0,
isVertical ? 0 : threshold,
overlap => {
this._windowOverlap = overlap;
this._updateAlphaAndSet();
}
);
}
}
_updateAllAndSet() {
let themeBackground = this._getThemeBackground(true);
this._updateColor(themeBackground);
this._updateAlpha(themeBackground);
this._updateComplementaryStyles();
this._setBackground();
this._setActorStyle();
}
_updateAlphaAndSet() {
this._updateAlpha();
this._setBackground();
}
_updateComplementaryStyles() {
let panelThemeNode = this._dtpPanel.panel.get_theme_node();
this._complementaryStyles = 'border-radius: ' + panelThemeNode.get_border_radius(0) + 'px;';
}
_updateColor(themeBackground) {
this.backgroundColorRgb = themeBackground || this._getThemeBackground();
}
_updateAlpha(themeBackground) {
if (this._windowOverlap && !Main.overview.visibleTarget && SETTINGS.get_boolean('trans-use-dynamic-opacity')) {
this.alpha = SETTINGS.get_double('trans-dynamic-anim-target');
} else {
this.alpha = SETTINGS.get_boolean('trans-use-custom-opacity') ?
SETTINGS.get_double('trans-panel-opacity') :
(themeBackground || this._getThemeBackground()).alpha * 0.003921569; // 1 / 255 = 0.003921569
}
}
_setBackground() {
this.currentBackgroundColor = Utils.getrgbaColor(this.backgroundColorRgb, this.alpha);
let transition = 'transition-duration: 300ms;';
this._dtpPanel.set_style('background-color: ' + this.currentBackgroundColor + transition + this._complementaryStyles);
}
_setActorStyle() {
this._dtpPanel.panel.set_style(
'background: none; ' +
'border-image: none; ' +
'background-image: none; ' +
'transition-duration: 300ms;'
);
}
_getThemeBackground(reload) {
if (reload || !this._themeBackground) {
let fakePanel = new St.Bin({ name: 'panel' });
Main.uiGroup.add_child(fakePanel);
let fakeTheme = fakePanel.get_theme_node()
this._themeBackground = this._getBackgroundImageColor(fakeTheme) || fakeTheme.get_background_color();
Main.uiGroup.remove_child(fakePanel);
}
return this._themeBackground;
}
_getBackgroundImageColor(theme) {
let bg = null;
try {
let imageFile = theme.get_background_image() || theme.get_border_image().get_file();
if (imageFile) {
let imageBuf = GdkPixbuf.Pixbuf.new_from_file(imageFile.get_path());
let pixels = imageBuf.get_pixels();
bg = { red: pixels[0], green: pixels[1], blue: pixels[2], alpha: pixels[3] };
}
} catch (error) {}
return bg;
}
}

View File

@@ -1,30 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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="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>
<property name="margin-start">32</property>
<property name="margin-top">32</property>
<property name="orientation">vertical</property>
<property name="spacing">24</property>
<property name="width-request">600</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Only hide the panel when it is obstructed by windows</property>
<property name="title" translatable="yes">Only hide the panel from windows</property>
<child>
<object class="GtkSwitch" id="intellihide_window_hide_switch">
<property name="valign">center</property>
<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>
@@ -40,16 +66,67 @@
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Require pressure at the edge of the screen to reveal the panel</property>
<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>
@@ -57,13 +134,10 @@
</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>
@@ -74,11 +148,10 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="grid_intellihide_only_secondary">
<property name="title" translatable="yes">Only hide secondary panels</property>
<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>
@@ -86,24 +159,41 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Keyboard shortcut to reveal and hold the panel</property>
<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>
<property name="width_chars">12</property>
<property name="placeholder_text" translatable="yes">e.g. &lt;Super&gt;i</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -29,6 +29,7 @@
<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>
@@ -57,6 +58,7 @@
<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>
@@ -85,6 +87,7 @@
<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>
@@ -97,4 +100,4 @@
</object>
</interface>
</interface>

View File

@@ -1,23 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<!-- interface-name BoxOverlayShortcut.ui -->
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.3"/>
<object class="GtkBox" id="box_overlay_shortcut">
<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>
<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">Hotkeys prefix</property>
<property name="subtitle" translatable="yes">Hotkeys will either be Super+Number or Super+Alt+Num</property>
<property name="title" translatable="yes">Hotkeys prefix</property>
<child>
<object class="GtkComboBoxText" id="hotkey_prefix_combo">
<property name="valign">center</property>
@@ -29,11 +28,10 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Number overlay</property>
<property name="subtitle" translatable="yes">Temporarily show the application numbers over the icons when using the hotkeys.</property>
<property name="title" translatable="yes">Number overlay</property>
<child>
<object class="GtkComboBoxText" id="overlay_combo">
<property name="valign">center</property>
@@ -46,37 +44,34 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Shortcut to show the overlay for 2 seconds</property>
<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">Shortcut to show the overlay for 2 seconds</property>
<child>
<object class="GtkEntry" id="shortcut_entry">
<property name="placeholder-text" translatable="yes">e.g. &lt;Super&gt;q</property>
<property name="valign">center</property>
<property name="width_chars">12</property>
<property name="placeholder_text" translatable="yes">e.g. &lt;Super&gt;q</property>
<property name="width-chars">12</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Show window previews on hotkey</property>
<property name="subtitle" translatable="yes">Show previews when the application have multiple instances</property>
<property name="title" translatable="yes">Show window previews on hotkey</property>
<child>
<object class="GtkSwitch" id="shortcut_preview_switch">
<property name="valign">center</property>
</object>
</child>
<object class="GtkSwitch" id="shortcut_preview_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Hotkeys are activated with</property>
<property name="subtitle" translatable="yes">Select which keyboard number keys are used to activate the hotkeys</property>
<property name="title" translatable="yes">Hotkeys are activated with</property>
<child>
<object class="GtkComboBoxText" id="shortcut_num_keys_combo">
<property name="valign">center</property>
@@ -89,9 +84,7 @@
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,27 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<!-- interface-name SettingsAction.ui -->
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.6"/>
<object class="AdwPreferencesPage" id="action">
<property name="icon-name">input-mouse-symbolic</property>
<property name="title" translatable="yes">Action</property>
<property name="icon_name">view-pin-symbolic</property>
<!-- group click action -->
<child>
<object class="AdwPreferencesGroup" id="action_group_click_action">
<property name="title" translatable="yes">Click action</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Click action</property>
<property name="subtitle" translatable="yes">Behaviour when clicking on the icon of a running application.</property>
<property name="title" translatable="yes">Click action</property>
<child>
<object class="GtkButton" id="middle_click_options_button">
<property name="receives_default">True</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="middle_click_image">
<property name="icon_name">emblem-system-symbolic</property>
<property name="icon-name">emblem-system-symbolic</property>
</object>
</child>
<style>
@@ -37,6 +35,7 @@
<item id="CYCLE" translatable="yes">Cycle through windows</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="MINIMIZE" translatable="yes">Toggle windows</item>
<item id="RAISE" translatable="yes">Raise windows</item>
<item id="LAUNCH" translatable="yes">Launch new instance</item>
@@ -45,23 +44,19 @@
</child>
</object>
</child>
</object>
</child>
<!-- group scroll action -->
<child>
<object class="AdwPreferencesGroup" id="action_group_scroll_action">
<property name="title" translatable="yes">Scroll action</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Scroll icon action</property>
<property name="subtitle" translatable="yes">Behavior when mouse scrolling over an application icon.</property>
<property name="title" translatable="yes">Scroll icon action</property>
<child>
<object class="GtkComboBoxText" id="scroll_icon_combo">
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
<items>
<item id="NOTHING" translatable="yes">Do nothing</item>
<item id="CYCLE_WINDOWS" translatable="yes">Cycle windows</item>
@@ -70,26 +65,22 @@
</child>
</object>
</child>
</object>
</child>
<!-- group hotkey -->
<child>
<object class="AdwPreferencesGroup" id="action_group_hotkry">
<object class="AdwPreferencesGroup" id="action_group_hotkey">
<property name="title" translatable="yes">Hotkey overlay</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Use hotkeys to activate apps</property>
<property name="subtitle" translatable="yes">Enable Super+(0-9) as shortcuts to activate apps. It can also be used together with Shift and Ctrl.</property>
<property name="title" translatable="yes">Use hotkeys to activate apps</property>
<child>
<object class="GtkButton" id="overlay_button">
<property name="receives_default">True</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="image_overlay">
<property name="icon_name">emblem-system-symbolic</property>
<property name="icon-name">emblem-system-symbolic</property>
</object>
</child>
<style>
@@ -104,15 +95,22 @@
</child>
</object>
</child>
</object>
</child>
<!-- group gnome -->
<child>
<object class="AdwPreferencesGroup" id="finetune_group_gnome">
<property name="title" translatable="yes">Gnome functionality</property>
<child>
<object class="AdwActionRow">
<property name="subtitle" translatable="yes">(overview)</property>
<property name="title" translatable="yes">Keep original gnome-shell dash</property>
<child>
<object class="GtkSwitch" id="stockgs_dash_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Keep original gnome-shell top panel</property>
@@ -123,7 +121,6 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Activate panel menu buttons on click only</property>
@@ -135,9 +132,7 @@
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -42,7 +42,7 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Ungroup applications</property>

View File

@@ -1,201 +1,186 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<!-- interface-name SettingsPosition.ui -->
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.6"/>
<object class="GtkAdjustment" id="panel_size_adjustment">
<property name="lower">0.33</property>
<property name="page-increment">0.1</property>
<property name="step-increment">0.01</property>
<property name="upper">1</property>
<property name="step_increment">0.01</property>
<property name="page_increment">0.1</property>
</object>
<object class="GtkAdjustment" id="panel_length_adjustment">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
<property name="page-increment">0.1</property>
<property name="step-increment">0.01</property>
<property name="upper">1</property>
</object>
<object class="AdwPreferencesPage" id="position">
<property name="icon-name">find-location-symbolic</property>
<property name="title" translatable="yes">Position</property>
<property name="icon_name">find-location-symbolic</property>
<!-- group panel -->
<child>
<object class="AdwPreferencesGroup" id="position_group_panel">
<property name="title" translatable="yes">Panel</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Display panels on all monitors</property>
<child>
<object class="GtkSwitch" id="multimon_multi_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- group order and positions -->
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor">
<property name="title" translatable="yes">Order and Position on monitors</property>
<child>
<object class="AdwPreferencesRow">
<property name="title" translatable="yes">Monitor</property>
<child>
<object class="GtkBox">
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<child>
<object class="GtkCheckButton" id="taskbar_position_sync_button">
<property name="label" translatable="yes">Apply changes to all monitors</property>
<property name="receives_default">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkComboBoxText" id="taskbar_position_monitor_combo">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- group order and positions 2 -->
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor2">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel screen position</property>
<child>
<object class="GtkToggleButton" id="position_bottom_button">
<property name="label" translatable="yes">Bottom</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="active">True</property>
<signal name="clicked" handler="position_bottom_button_clicked_cb" swapped="no"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_top_button">
<property name="label" translatable="yes">Top</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="group">position_bottom_button</property>
<signal name="clicked" handler="position_top_button_clicked_cb" swapped="no"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_left_button">
<property name="label" translatable="yes">Left</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="group">position_bottom_button</property>
<signal name="clicked" handler="position_left_button_clicked_cb" swapped="no"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_right_button">
<property name="label" translatable="yes">Right</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="group">position_bottom_button</property>
<signal name="clicked" handler="position_right_button_clicked_cb" swapped="no"/>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel thickness</property>
<property name="subtitle" translatable="yes">(default is 48)</property>
<child>
<object class="GtkScale" id="panel_size_scale">
<property name="width-request">350</property>
<property name="adjustment">panel_size_adjustment</property>
<property name="round_digits">0</property>
<property name="digits">0</property>
<property name="value_pos">right</property>
<property name="draw_value">True</property>
<signal name="value-changed" handler="panel_size_scale_value_changed_cb" swapped="no"/>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel length (%)</property>
<property name="subtitle" translatable="yes">(default is 100)</property>
<child>
<object class="GtkScale" id="panel_length_scale">
<property name="width-request">350</property>
<property name="adjustment">panel_length_adjustment</property>
<property name="round_digits">0</property>
<property name="digits">0</property>
<property name="value_pos">right</property>
<property name="draw_value">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="panel_anchor_label">
<property name="title" translatable="yes">Anchor</property>
<child>
<object class="GtkComboBoxText" id="panel_anchor_combo">
<property name="valign">center</property>
<items>
<item id="START" translatable="yes">Start</item>
<item id="MIDDLE" translatable="yes">Middle</item>
<item id="END" translatable="yes">End</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- group order and positions 3 -->
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor3">
<child>
<object class="AdwPreferencesRow">
<property name="title" translatable="yes">Taskbar Display</property>
<child>
<object class="GtkListBox" id="taskbar_display_listbox">
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="visible">True</property>
<property name="selection_mode">none</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="position_group_panel">
<property name="title" translatable="yes">Panel</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Display panels on all monitors</property>
<child>
<object class="GtkSwitch" id="multimon_multi_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor">
<property name="title" translatable="yes">Order and Position on monitors</property>
<child>
<object class="AdwPreferencesRow">
<property name="title" translatable="yes">Monitor</property>
<child>
<object class="GtkBox">
<property name="margin-bottom">6</property>
<property name="margin-end">6</property>
<property name="margin-start">6</property>
<property name="margin-top">6</property>
<child>
<object class="GtkCheckButton" id="taskbar_position_sync_button">
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Apply changes to all monitors</property>
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkComboBoxText" id="taskbar_position_monitor_combo">
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor2">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel monitor position</property>
<child>
<object class="GtkToggleButton" id="position_bottom_button">
<property name="active">True</property>
<property name="label" translatable="yes">Bottom</property>
<property name="receives-default">False</property>
<property name="valign">center</property>
<signal name="clicked" handler="position_bottom_button_clicked_cb"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_top_button">
<property name="group">position_bottom_button</property>
<property name="label" translatable="yes">Top</property>
<property name="receives-default">False</property>
<property name="valign">center</property>
<signal name="clicked" handler="position_top_button_clicked_cb"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_left_button">
<property name="group">position_bottom_button</property>
<property name="label" translatable="yes">Left</property>
<property name="receives-default">False</property>
<property name="valign">center</property>
<signal name="clicked" handler="position_left_button_clicked_cb"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="position_right_button">
<property name="group">position_bottom_button</property>
<property name="label" translatable="yes">Right</property>
<property name="receives-default">False</property>
<property name="valign">center</property>
<signal name="clicked" handler="position_right_button_clicked_cb"/>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="subtitle" translatable="yes">(default is 48)</property>
<property name="title" translatable="yes">Panel thickness</property>
<child>
<object class="GtkScale" id="panel_size_scale">
<property name="adjustment">panel_size_adjustment</property>
<property name="digits">0</property>
<property name="draw-value">True</property>
<property name="round-digits">0</property>
<property name="value-pos">right</property>
<property name="width-request">300</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="subtitle" translatable="yes">(default is 100)</property>
<property name="title" translatable="yes">Panel length</property>
<child>
<object class="GtkCheckButton" id="panel_length_dynamic_button">
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Dynamic</property>
<property name="receives-default">False</property>
</object>
</child>
<child>
<object class="GtkScale" id="panel_length_scale">
<property name="adjustment">panel_length_adjustment</property>
<property name="digits">0</property>
<property name="draw-value">True</property>
<property name="round-digits">0</property>
<property name="value-pos">right</property>
<property name="width-request">300</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="panel_anchor_label">
<property name="title" translatable="yes">Anchor</property>
<child>
<object class="GtkComboBoxText" id="panel_anchor_combo">
<property name="valign">center</property>
<items>
<item id="START" translatable="yes">Start</item>
<item id="MIDDLE" translatable="yes">Middle</item>
<item id="END" translatable="yes">End</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="position_group_on_monitor3">
<child>
<object class="AdwPreferencesRow">
<property name="title" translatable="yes">Taskbar Display</property>
<child>
<object class="GtkListBox" id="taskbar_display_listbox">
<property name="margin-bottom">6</property>
<property name="margin-top">6</property>
<property name="selection-mode">none</property>
<property name="visible">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,32 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<!-- interface-name SettingsStyle.ui -->
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.6"/>
<object class="GtkAdjustment" id="panel_margin_adjustment">
<property name="lower">0.33</property>
<property name="page-increment">0.1</property>
<property name="step-increment">0.01</property>
<property name="upper">1</property>
</object>
<object class="GtkAdjustment" id="trans_opacity_adjustment">
<property name="page-increment">10</property>
<property name="step-increment">5</property>
<property name="upper">100</property>
</object>
<!--<object class="GtkAdjustment" id="trans_border_opacity_adjustment">
<property name="upper">100</property>
<property name="step_increment">5</property>
<property name="page_increment">10</property>
</object>
<object class="AdwPreferencesPage" id="style">
<property name="title" translatable="yes">Style</property>
<property name="icon_name">applications-graphics-symbolic</property>
<object class="GtkAdjustment" id="trans_border_width_adjustment">
<property name="lower">1</property>
<property name="upper">10</property>
<property name="step_increment">1</property>
<property name="page_increment">2</property>
</object>-->
<!-- group panel intellihide -->
<object class="AdwPreferencesPage" id="style">
<property name="icon-name">applications-graphics-symbolic</property>
<property name="title" translatable="yes">Style</property>
<child>
<object class="AdwPreferencesGroup" id="position_group_panel2">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel Intellihide</property>
<property name="subtitle" translatable="yes">Hide and reveal the panel according to preferences</property>
<property name="title" translatable="yes">Panel Intellihide</property>
<child>
<object class="GtkButton" id="intellihide_options_button">
<property name="receives_default">True</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="image_intellihide_options">
<property name="icon_name">emblem-system-symbolic</property>
<property name="icon-name">emblem-system-symbolic</property>
</object>
</child>
<style>
@@ -41,26 +58,46 @@
</child>
</object>
</child>
</object>
</child>
<!-- group dynamic trans2 -->
<child>
<object class="AdwPreferencesGroup" id="style_group_dynamic_trans2">
<object class="AdwPreferencesGroup" id="style_group_dynamic_trans">
<property name="title" translatable="yes">Panel style</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Floating rounded theme</property>
<property name="subtitle" translatable="yes">(default is 0)</property>
<property name="title" translatable="yes">Margin</property>
<child>
<object class="GtkSwitch" id="floating_rounded_theme_switch">
<property name="valign">center</property>
<object class="GtkScale" id="panel_margin_scale">
<property name="adjustment">panel_margin_adjustment</property>
<property name="digits">0</property>
<property name="draw-value">True</property>
<property name="round-digits">0</property>
<property name="value-pos">right</property>
<property name="width-request">300</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Border radius</property>
<child>
<object class="GtkScale" id="global_border_radius_scale">
<property name="adjustment">global_border_radius_adjustment</property>
<property name="digits">0</property>
<property name="draw-value">True</property>
<property name="round-digits">0</property>
<property name="value-pos">right</property>
<property name="width-request">300</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="style_group_dynamic_trans2">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Override panel theme background opacity</property>
@@ -71,31 +108,29 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="trans_opacity_box">
<property name="title" translatable="yes">Panel background opacity (%)</property>
<child>
<object class="GtkSpinButton" id="trans_opacity_spinbutton">
<property name="valign">center</property>
<property name="text" translatable="yes">0</property>
<property name="adjustment">trans_opacity_adjustment</property>
<property name="text" translatable="yes">0</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="trans_opacity_box2">
<property name="title" translatable="yes">Dynamic background opacity</property>
<property name="subtitle" translatable="yes">Change opacity when a window gets close to the panel</property>
<property name="title" translatable="yes">Dynamic background opacity</property>
<child>
<object class="GtkButton" id="trans_dyn_options_button">
<property name="receives_default">True</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="image_trans_dyn_options">
<property name="icon_name">emblem-system-symbolic</property>
<property name="icon-name">emblem-system-symbolic</property>
</object>
</child>
<style>
@@ -110,9 +145,59 @@
</child>
</object>
</child>
</object>
</child>
<!-- group dynamic trans4 -->
<!-- <child>
<object class="AdwPreferencesGroup" id="style_group_dynamic_trans4">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Panel border</property>
<child>
<object class="GtkSwitch" id="trans_border_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="trans_border_color_box">
<property name="title" translatable="yes">Override border color</property>
<child>
<object class="GtkColorButton" id="trans_border_color_colorbutton">
<property name="receives_default">True</property>
<property name="valign">center</property>
<property name="use_alpha">True</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="trans_border_color_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="trans_border_width_box">
<property name="title" translatable="yes">Border thickness</property>
<child>
<object class="GtkSpinButton" id="trans_border_width_spinbutton">
<property name="valign">center</property>
<property name="text" translatable="yes">1</property>
<property name="adjustment">trans_border_width_adjustment</property>
</object>
</child>
</object>
</child>
</object>
</child>-->
</object>
<object class="GtkAdjustment" id="global_border_radius_adjustment">
<property name="lower">0.33</property>
<property name="page-increment">0.1</property>
<property name="step-increment">0.01</property>
<property name="upper">1</property>
</object>
</interface>

889
utils.js
View File

@@ -1,889 +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.
*/
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 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];
}
}
/* Virtual methods to be implemented by subclass */
// create single element to be stored in the storage structure
_create(item){
throw new Error('no implementation of _create in ' + this);
}
// correctly delete single element
_remove(item){
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)
{
}
}
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;
}
};
// 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) => {
if (enable && !unredirectEnabled)
Meta.enable_unredirect_for_display(global.display);
else if (!enable && unredirectEnabled)
Meta.disable_unredirect_for_display(global.display);
unredirectEnabled = enable;
};
export const getSystemMenuInfo = function() {
return {
name: 'quickSettings',
constructor: Main.panel.statusArea.quickSettings.constructor
};
}
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 (!main.hasOwnProperty(prop) && bck.hasOwnProperty(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) {
actor.set_clip(0, 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 const checkIfWindowHasTransient = function(window) {
let hasTransient;
window.foreach_transient(t => !(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 isAnimating = function(actor, prop) {
return !!actor.get_transition(prop);
}
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(text, iconName, action, isTransient) {
let source = new MessageTray.SystemNotificationSource();
let notification = new MessageTray.Notification(source, 'Dash to Panel', text);
let notifyFunc = source.showNotification || source.notify;
if (iconName) {
source.createIcon = function() {
return new St.Icon({ icon_name: iconName });
};
}
if (action) {
if (!(action instanceof Array)) {
action = [action];
}
action.forEach(a => notification.addAction(a.text, a.func));
}
Main.messageTray.add(source);
notification.setTransient(isTransient);
notifyFunc.call(source, 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, vlower, vupper, vstepIncrement, vpageIncrement, vpageSize] = vadjustment.get_values();
let [hvalue, hlower, hupper, hstepIncrement, hpageIncrement, 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(),
offset = 0;
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.90;
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();
}

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