Compare commits
8 Commits
debian/mas
...
3.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
651f4542d8 | ||
|
|
f1eb846b47 | ||
|
|
7428d1702f | ||
|
|
8d2e8c8cb1 | ||
|
|
5673baa6b5 | ||
|
|
6833713da0 | ||
|
|
b5d143fabb | ||
|
|
6cd0ad710d |
@@ -1,3 +0,0 @@
|
||||
extends:
|
||||
- ./lint/eslintrc-gjs.yml
|
||||
- ./lint/eslintrc-shell.yml
|
||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Makefile
|
||||
Makefile.in
|
||||
Makefile.in.in
|
||||
configure
|
||||
config.log
|
||||
config.status
|
||||
aclocal.m4
|
||||
autom4te.cache/
|
||||
po/gnome-shell-extensions.pot
|
||||
po/POTFILES
|
||||
po/stamp-it
|
||||
staging/
|
||||
zip-files/
|
||||
|
||||
*~
|
||||
*.gmo
|
||||
metadata.json
|
||||
*.gschema.xml
|
||||
*.gschema.valid
|
||||
169
.gitlab-ci.yml
169
.gitlab-ci.yml
@@ -1,169 +0,0 @@
|
||||
include:
|
||||
- remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/bbe5232986c9b98eb1efe62484e07216f7d1a4df/templates/fedora.yml'
|
||||
- remote: "https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/6f86b8bcb0cd5168c32779c4fea9a893c4a0c046/templates/ci-fairy.yml"
|
||||
|
||||
stages:
|
||||
- pre_review
|
||||
- prepare
|
||||
- review
|
||||
- build
|
||||
- deploy
|
||||
|
||||
default:
|
||||
image: registry.gitlab.gnome.org/gnome/gnome-shell/fedora/35:2022-02-18.0
|
||||
# Cancel jobs if newer commits are pushed to the branch
|
||||
interruptible: true
|
||||
# Auto-retry jobs in case of infra failures
|
||||
retry:
|
||||
max: 1
|
||||
when:
|
||||
- 'runner_system_failure'
|
||||
- 'stuck_or_timeout_failure'
|
||||
- 'scheduler_failure'
|
||||
- 'api_failure'
|
||||
|
||||
variables:
|
||||
FDO_UPSTREAM_REPO: GNOME/gnome-shell-extensions
|
||||
LINT_LOG: "eslint-report.xml"
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
- if: '$CI_MERGE_REQUEST_IID'
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
|
||||
.pipeline_guard: &pipeline_guard
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
- if: '$CI_COMMIT_BRANCH =~ /^gnome-[0-9-]+$/'
|
||||
- when: 'manual'
|
||||
|
||||
.gnome-shell-extensions.fedora:34:
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: 34
|
||||
FDO_DISTRIBUTION_TAG: '2021-08-31.0'
|
||||
FDO_DISTRIBUTION_PACKAGES: >
|
||||
meson git gettext sassc
|
||||
|
||||
.prereview_req: &prereview_req
|
||||
needs:
|
||||
- check_commit_log
|
||||
- check-merge-request
|
||||
|
||||
check_commit_log:
|
||||
extends:
|
||||
- .fdo.ci-fairy
|
||||
stage: pre_review
|
||||
script:
|
||||
- if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ;
|
||||
then
|
||||
ci-fairy check-commits --junit-xml=commit-message-junit-report.xml ;
|
||||
else
|
||||
echo "Not a merge request" ;
|
||||
fi
|
||||
<<: *pipeline_guard
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- commit-message-junit-report.xml
|
||||
reports:
|
||||
junit: commit-message-junit-report.xml
|
||||
|
||||
check-merge-request:
|
||||
extends:
|
||||
- .fdo.ci-fairy
|
||||
stage: pre_review
|
||||
script:
|
||||
- if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ;
|
||||
then
|
||||
ci-fairy check-merge-request --require-allow-collaboration --junit-xml=check-merge-request-report.xml ;
|
||||
else
|
||||
echo "Not a merge request" ;
|
||||
fi
|
||||
<<: *pipeline_guard
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- check-merge-request-report.xml
|
||||
reports:
|
||||
junit: check-merge-request-report.xml
|
||||
|
||||
build-fedora-container:
|
||||
extends:
|
||||
- .fdo.container-build@fedora@x86_64
|
||||
- .gnome-shell-extensions.fedora:34
|
||||
stage: prepare
|
||||
<<: *prereview_req
|
||||
|
||||
js_check:
|
||||
stage: review
|
||||
<<: *prereview_req
|
||||
script:
|
||||
- gjs-check-syntax
|
||||
|
||||
eslint:
|
||||
stage: review
|
||||
<<: *prereview_req
|
||||
script:
|
||||
- export NODE_PATH=$(npm root -g)
|
||||
- ./.gitlab-ci/run-eslint --output-file ${LINT_LOG} --format junit --stdout
|
||||
artifacts:
|
||||
paths:
|
||||
- ${LINT_LOG}
|
||||
reports:
|
||||
junit: ${LINT_LOG}
|
||||
|
||||
build-bundles:
|
||||
stage: build
|
||||
<<: *prereview_req
|
||||
script:
|
||||
- ./export-zips.sh
|
||||
artifacts:
|
||||
name: 'Extension bundles'
|
||||
expose_as: 'Get Extension bundles here'
|
||||
paths:
|
||||
- zip-files/
|
||||
|
||||
fedora-build:
|
||||
extends:
|
||||
- .fdo.distribution-image@fedora
|
||||
- .gnome-shell-extensions.fedora:34
|
||||
stage: build
|
||||
needs:
|
||||
- build-fedora-container
|
||||
script:
|
||||
- meson setup build --werror -Dextension_set=all -Dclassic_mode=true
|
||||
- meson compile -C build
|
||||
- meson test -C build
|
||||
- meson install -C build
|
||||
artifacts:
|
||||
paths:
|
||||
- build
|
||||
|
||||
fedora-dist:
|
||||
extends:
|
||||
- .fdo.distribution-image@fedora
|
||||
- .gnome-shell-extensions.fedora:34
|
||||
stage: deploy
|
||||
needs:
|
||||
- fedora-build
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
script:
|
||||
- meson dist -C build
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
changes:
|
||||
- "**/meson.build"
|
||||
- meson/*
|
||||
|
||||
fedora-dist-tarball:
|
||||
extends: fedora-dist
|
||||
artifacts:
|
||||
expose_as: 'Get tarball here'
|
||||
paths:
|
||||
- build/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
@@ -1,13 +0,0 @@
|
||||
patterns:
|
||||
deny:
|
||||
- regex: '^$CI_MERGE_REQUEST_PROJECT_URL/(-/)?merge_requests/$CI_MERGE_REQUEST_IID$'
|
||||
message: Commit message must not contain a link to its own merge request
|
||||
- regex: '^extensions/'
|
||||
message: Commit message subject should not be prefixed with 'extensions/', use the extension name instead
|
||||
where: subject
|
||||
- regex: '^[^:]+: [a-z]'
|
||||
message: "Commit message subject should be properly Capitalized. E.g. 'window: Marginalize extradicity'"
|
||||
where: subject
|
||||
- regex: '^\S*\.js:'
|
||||
message: Commit message subject prefix should not include .js
|
||||
where: subject
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {ESLint} = require('eslint');
|
||||
|
||||
console.log(`Running ESLint version ${ESLint.version}...`);
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function hasOption(...names) {
|
||||
return process.argv.some(arg => names.includes(arg));
|
||||
}
|
||||
|
||||
function getOption(...names) {
|
||||
const optIndex =
|
||||
process.argv.findIndex(arg => names.includes(arg)) + 1;
|
||||
|
||||
if (optIndex === 0)
|
||||
return undefined;
|
||||
|
||||
return process.argv[optIndex];
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
const outputOption = getOption('--output-file', '-o');
|
||||
const outputPath = outputOption ? path.resolve(outputOption) : null;
|
||||
|
||||
const sourceDir = path.dirname(process.argv[1]);
|
||||
process.chdir(path.resolve(sourceDir, '..'));
|
||||
|
||||
const sources = ['extensions'];
|
||||
const eslint = new ESLint();
|
||||
|
||||
const results = await eslint.lintFiles(sources);
|
||||
const formatter = await eslint.loadFormatter(getOption('--format', '-f'));
|
||||
const resultText = formatter.format(results);
|
||||
|
||||
if (outputPath) {
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true});
|
||||
fs.writeFileSync(outputPath, resultText);
|
||||
|
||||
if (hasOption('--stdout')) {
|
||||
const consoleFormatter = await eslint.loadFormatter();
|
||||
console.log(consoleFormatter.format(results));
|
||||
}
|
||||
} else {
|
||||
console.log(resultText);
|
||||
}
|
||||
|
||||
process.exitCode = results.some(r => r.errorCount > 0) ? 1 : 0;
|
||||
})().catch((error) => {
|
||||
process.exitCode = 1;
|
||||
console.error(error);
|
||||
});
|
||||
31
HACKING
Normal file
31
HACKING
Normal file
@@ -0,0 +1,31 @@
|
||||
--- Creating a New Extension ---
|
||||
|
||||
To create a new extension, add a subdirectory in extensions.
|
||||
Then create a Makefile.am like the one in example, replacing
|
||||
the EXTENSION_ID with the basename of your extension, which
|
||||
must match the UUID in metadata.json.
|
||||
If you need additional files, add them to EXTENSION_EXTRA.
|
||||
|
||||
Then modify extensions/Makefile.am and configure.ac. It should
|
||||
be pretty self-explanatory.
|
||||
|
||||
Don't forget to add any translatable file to po/POTFILES.in, and
|
||||
then you're done.
|
||||
The Gettext domain you should choose is gnome-shell-extensions,
|
||||
not gnome-shell, unless you're sure there is the string you
|
||||
need in gnome-shell.
|
||||
|
||||
--- Coding Style ---
|
||||
|
||||
Generally, we follow GJS coding style (you can find it at
|
||||
http://git.gnome.org/browse/gjs/tree/doc/Style_Guide.txt), which
|
||||
in short is: indent 4 spaces, no tabs, space after comma, no space
|
||||
after function call.
|
||||
|
||||
The Emacs mode line for this
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
Imports should be at the top, in two groups, one for standard
|
||||
imports (like imports.lang or imports.dbus) and introspection,
|
||||
the other for Shell API. Within the same group, put everything
|
||||
in alphabetic order.
|
||||
31
HACKING.md
31
HACKING.md
@@ -1,31 +0,0 @@
|
||||
## Creating a New Extension
|
||||
|
||||
To create a new extension, add a subdirectory in extensions. Then create
|
||||
a meson.build from the provided [template](extensions/meson.build.template).
|
||||
If you need additional sources, add them to extension_sources. Similarily add
|
||||
GSettings schemas to extension_schemas and other files to extension_data.
|
||||
|
||||
Then modify the [toplevel Meson file](meson.build) to add the new
|
||||
extension name in the appropriate set (that is one of classic_extensions,
|
||||
default_extensions or all_extensions).
|
||||
|
||||
Don't forget to add any translatable file to po/POTFILES.in, and
|
||||
then you're done.
|
||||
The Gettext domain you should choose is gnome-shell-extensions,
|
||||
not gnome-shell, unless you're sure there is the string you
|
||||
need in gnome-shell.
|
||||
|
||||
## Coding Style
|
||||
|
||||
Generally, we follow [GJS coding style][coding-style], which in short is:
|
||||
indent 4 spaces, no tabs, space after comma, no space after function call.
|
||||
|
||||
The Emacs mode line for this
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
Imports should be at the top, in two groups, one for standard
|
||||
imports (like imports.lang or imports.dbus) and introspection,
|
||||
the other for Shell API. Within the same group, put everything
|
||||
in alphabetic order.
|
||||
|
||||
[coding-style]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/Style_Guide.md
|
||||
45
Makefile.am
Normal file
45
Makefile.am
Normal file
@@ -0,0 +1,45 @@
|
||||
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
|
||||
|
||||
SUBDIRS = extensions po
|
||||
|
||||
EXTRA_DIST = lib/convenience.js
|
||||
|
||||
DISTCHECK_CONFIGURE_FLAGS = --enable-extensions=all
|
||||
|
||||
include include.mk
|
||||
|
||||
zip-file: all
|
||||
-rm -fR $(builddir)/_build
|
||||
-rm -fR $(builddir)/zip-files
|
||||
$(MKDIR_P) $(builddir)/_build; \
|
||||
$(MKDIR_P) $(builddir)/zip-files; \
|
||||
$(MAKE) install DESTDIR="$(abs_builddir)/_build"; \
|
||||
for i in $(ENABLED_EXTENSIONS); do \
|
||||
mv "$(builddir)/_build$(topextensiondir)/$${i}$(extensionbase)" "$(builddir)/_build/"; \
|
||||
cp -r "$(builddir)/_build$(datadir)/locale" "$(builddir)/_build/$${i}$(extensionbase)"; \
|
||||
cp "$(srcdir)/COPYING" -t "$(builddir)/_build/$${i}$(extensionbase)"; \
|
||||
if [ -f "$(builddir)/_build$(datadir)/glib-2.0/schemas/$(gschemabase).$${i}.gschema.xml" ]; then \
|
||||
$(MKDIR_P) "$(builddir)/_build/$${i}$(extensionbase)/schemas"; \
|
||||
mv "$(builddir)/_build$(datadir)/glib-2.0/schemas/$(gschemabase).$${i}.gschema.xml" "$(builddir)/_build/$${i}$(extensionbase)/schemas"; \
|
||||
glib-compile-schemas "$(builddir)/_build/$${i}$(extensionbase)/schemas"; \
|
||||
fi; \
|
||||
(cd "$(builddir)/_build/$${i}$(extensionbase)"; \
|
||||
zip -qr "$(abs_builddir)/zip-files/$${i}$(extensionbase).shell-extension.zip" .; \
|
||||
); \
|
||||
done
|
||||
-rm -fR $(builddir)/_build
|
||||
|
||||
localprefix = $(HOME)/.local/share/gnome-shell/extensions
|
||||
|
||||
local-install: zip-file
|
||||
for i in $(ENABLED_EXTENSIONS); do \
|
||||
uuid="$${i}$(extensionbase)"; \
|
||||
zip_file="$(abs_builddir)/zip-files/$${uuid}.shell-extension.zip"; \
|
||||
if [ -d "$(localprefix)/$${uuid}" ]; then \
|
||||
rm -fR "$(localprefix)/$${uuid}"; \
|
||||
fi; \
|
||||
$(MKDIR_P) $(localprefix)/$${uuid}; \
|
||||
(cd $(localprefix)/$${uuid}; \
|
||||
unzip -q $${zip_file}; \
|
||||
); \
|
||||
done
|
||||
92
README
Normal file
92
README
Normal file
@@ -0,0 +1,92 @@
|
||||
GNOME Shell Extensions is a collection of extensions providing additional
|
||||
and optional functionality to GNOME Shell.
|
||||
Since GNOME Shell is not API stable, extensions work only against a very
|
||||
specific version of the shell, usually the same as this package (see
|
||||
"configure --version"). Also, since extensions are built from many
|
||||
individual contributors, we cannot guarantee stability or quality for any
|
||||
specific extension.
|
||||
For these reasons, distributions are advised to avoid installing or packaging
|
||||
this module by defaul.
|
||||
|
||||
For more information about GNOME Shell Extensions
|
||||
http://live.gnome.org/GnomeShell/Extensions
|
||||
|
||||
For general information about GNOME Shell
|
||||
http://live.gnome.org/GnomeShell
|
||||
|
||||
Bugs should be reported at http://bugzilla.gnome.org against the 'gnome-shell'
|
||||
product, with the 'extensions' component.
|
||||
|
||||
Extensions
|
||||
==========
|
||||
|
||||
alternate-tab
|
||||
|
||||
Lets you use classic Alt+Tab (window-based instead of app-based) in GNOME Shell.
|
||||
|
||||
alternative-status-menu
|
||||
|
||||
For those who want a power off item visible at all the time, replaces GNOME Shell
|
||||
status menu with one featuring separate Suspend and Power Off. Adds the ability to
|
||||
hibernate as well.
|
||||
|
||||
apps-menu
|
||||
|
||||
Lets you reach an application using gnome 2.x style menu on the panel.
|
||||
|
||||
auto-move-windows
|
||||
|
||||
Lets you manage your workspaces more easily, assigning a specific workspace to
|
||||
each application as soon as it creates a window, in a manner configurable with a
|
||||
GSettings key.
|
||||
|
||||
dock
|
||||
|
||||
Shows a dock-style task switcher on the right side of the screen.
|
||||
|
||||
drive-menu
|
||||
Shows a status menu for rapid unmount and power off of external storage devices
|
||||
(i.e. pendrives)
|
||||
|
||||
example
|
||||
|
||||
A minimal example illustrating how to write extensions.
|
||||
|
||||
gajim
|
||||
|
||||
Integration with Gajim, a Jabber/XMPP instant messaging client.
|
||||
|
||||
native-window-placement
|
||||
|
||||
An alternative algorithm for layouting the thumbnails in the windows overview, that
|
||||
more closely reflects the actual positions and sizes.
|
||||
|
||||
places-menu
|
||||
|
||||
Shows a status Indicator for navigating to Places.
|
||||
|
||||
systemMonitor
|
||||
|
||||
An message tray indicator showing CPU and memory loads.
|
||||
|
||||
user-theme
|
||||
|
||||
Loads a shell theme from ~/.themes/<name>/gnome-shell.
|
||||
|
||||
windowsNavigator
|
||||
|
||||
Allow keyboard selection of windows and workspaces in overlay mode.
|
||||
|
||||
xrandr-indicator
|
||||
|
||||
Replace the GTK+ based indicator from gnome-settings-daemon with
|
||||
a native one. Lets the user rotate the laptop monitor and open
|
||||
display preferences quickly.
|
||||
|
||||
License
|
||||
=======
|
||||
GNOME Shell Extensions are distributed under the terms of the GNU General Public License,
|
||||
version 2 or later. See the COPYING file for details.
|
||||
Individual extensions may be licensed under different terms, see each source
|
||||
file for details.
|
||||
|
||||
96
README.md
96
README.md
@@ -1,96 +0,0 @@
|
||||
# GNOME Shell Extensions
|
||||
|
||||
GNOME Shell Extensions is a collection of extensions providing additional
|
||||
and optional functionality to GNOME Shell.
|
||||
|
||||
Since GNOME Shell is not API stable, extensions work only against a very
|
||||
specific version of the shell, usually the same as this package (see
|
||||
"configure --version"). The extensions in this package are supported by GNOME
|
||||
and will be updated to reflect future API changes in GNOME Shell.
|
||||
|
||||
The GNOME wiki has more information about [GNOME Shell Extensions][project-page],
|
||||
as well as some general information about [GNOME Shell][shell-page].
|
||||
|
||||
Bugs should be reported to the GNOME [bug tracking system][bug-tracker].
|
||||
|
||||
## Extensions
|
||||
|
||||
* alternate-tab (**OBSOLETE**)
|
||||
|
||||
Lets you use classic Alt+Tab (window-based instead of app-based) in GNOME Shell.
|
||||
This extension is obsolete since GNOME 3.30, see [this blogpost][alternatetab-post]
|
||||
for further details.
|
||||
|
||||
* apps-menu
|
||||
|
||||
Lets you reach an application using gnome 2.x style menu on the panel.
|
||||
|
||||
* auto-move-windows
|
||||
|
||||
Lets you manage your workspaces more easily, assigning a specific workspace to
|
||||
each application as soon as it creates a window, in a manner configurable with a
|
||||
GSettings key.
|
||||
|
||||
* drive-menu
|
||||
|
||||
Shows a status menu for rapid unmount and power off of external storage devices
|
||||
(i.e. pendrives)
|
||||
|
||||
* launch-new-instance
|
||||
|
||||
Changes application icons to always launch a new instance when activated.
|
||||
|
||||
* native-window-placement
|
||||
|
||||
An alternative algorithm for layouting the thumbnails in the windows overview, that
|
||||
more closely reflects the actual positions and sizes.
|
||||
|
||||
* places-menu
|
||||
|
||||
Shows a status Indicator for navigating to Places.
|
||||
|
||||
* screenshot-window-sizer
|
||||
|
||||
Adds a shortcut for resizing the focus window to a size that is suitable for GNOME Software screenshots
|
||||
|
||||
* user-theme
|
||||
|
||||
Loads a shell theme from ~/.themes/<name>/gnome-shell.
|
||||
|
||||
* window-list
|
||||
|
||||
Adds a bottom panel with a traditional window list.
|
||||
|
||||
* windowsNavigator
|
||||
|
||||
Allow keyboard selection of windows and workspaces in overlay mode.
|
||||
|
||||
* workspace-indicator
|
||||
|
||||
Adds a simple workspace switcher to the top bar.
|
||||
|
||||
## Default branch
|
||||
|
||||
The default development branch is `main`. If you still have a local
|
||||
checkout under the old name, use:
|
||||
```sh
|
||||
git checkout master
|
||||
git branch -m master main
|
||||
git fetch
|
||||
git branch --unset-upstream
|
||||
git branch -u origin/master
|
||||
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
GNOME Shell Extensions are distributed under the terms of the GNU General
|
||||
Public License, version 2 or later. See the [COPYING file][license] for details.
|
||||
Individual extensions may be licensed under different terms, see each source
|
||||
file for details.
|
||||
|
||||
[project-page]: https://wiki.gnome.org/Projects/GnomeShell/Extensions
|
||||
[shell-page]: https://wiki.gnome.org/Projects/GnomeShell
|
||||
[bug-tracker]: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues
|
||||
[license]: COPYING
|
||||
[alternatetab-post]: https://blogs.gnome.org/fmuellner/2018/10/11/the-future-of-alternatetab-and-why-you-need-not-worry/
|
||||
20
autogen.sh
Executable file
20
autogen.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Run this to generate all the initial makefiles, etc.
|
||||
|
||||
srcdir=`dirname $0`
|
||||
test -z "$srcdir" && srcdir=.
|
||||
|
||||
PKG_NAME="gnome-shell-extensions"
|
||||
|
||||
test -f $srcdir/configure.ac || {
|
||||
echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
|
||||
echo " top-level gnome-shell-extensions directory"
|
||||
exit 1
|
||||
}
|
||||
|
||||
which gnome-autogen.sh || {
|
||||
echo "You need to install gnome-common from GNOME Git (or from"
|
||||
echo "your OS vendor's package manager)."
|
||||
exit 1
|
||||
}
|
||||
. gnome-autogen.sh
|
||||
3
config/.gitignore
vendored
Normal file
3
config/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
install-sh
|
||||
mkinstalldirs
|
||||
missing
|
||||
92
configure.ac
Normal file
92
configure.ac
Normal file
@@ -0,0 +1,92 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell-extensions],[3.6.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell&component=extensions])
|
||||
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_AUX_DIR([config])
|
||||
|
||||
AM_INIT_AUTOMAKE([1.10 dist-bzip2 no-dist-gzip foreign tar-ustar])
|
||||
|
||||
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
|
||||
|
||||
GETTEXT_PACKAGE=gnome-shell-extensions
|
||||
AC_SUBST(GETTEXT_PACKAGE)
|
||||
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
||||
[The prefix for our gettext translation domains.])
|
||||
IT_PROG_INTLTOOL(0.26)
|
||||
|
||||
PKG_PROG_PKG_CONFIG([0.22])
|
||||
|
||||
GLIB_GSETTINGS
|
||||
|
||||
SHELL_VERSION="$PACKAGE_VERSION"
|
||||
shell_major=`echo "$PACKAGE_VERSION" | cut -d'.' -f1`
|
||||
shell_minor=`echo "$PACKAGE_VERSION" | cut -d'.' -f2`
|
||||
if test "$(($shell_minor % 2))" -eq 0; then
|
||||
SHELL_VERSION="$shell_major.$shell_minor"
|
||||
fi
|
||||
AC_SUBST([SHELL_VERSION])
|
||||
|
||||
dnl keep this in alphabetic order
|
||||
dnl by default, install only extensions that do not change completely the shell experience,
|
||||
dnl that don't require GSettings and that don't require external packages for typelibs
|
||||
dnl (so basically only menus, status icons, search providers, overview tabs, message tray sources, etc.)
|
||||
DEFAULT_EXTENSIONS="alternative-status-menu apps-menu dock drive-menu gajim places-menu windowsNavigator workspace-indicator"
|
||||
ALL_EXTENSIONS="$DEFAULT_EXTENSIONS alternate-tab auto-move-windows example native-window-placement systemMonitor user-theme xrandr-indicator"
|
||||
AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS])
|
||||
AC_ARG_ENABLE([extensions],
|
||||
[AS_HELP_STRING([--enable-extensions],[Space separated list of extensions to enable.
|
||||
The default is to build all extensions that can be installed in the home directory and have no external depedencies.
|
||||
Use "all" to enable all available extensions.])],
|
||||
[],
|
||||
[enable_extensions=$DEFAULT_EXTENSIONS]
|
||||
)
|
||||
if test x"$enable_extensions" = xall; then
|
||||
enable_extensions="$ALL_EXTENSIONS"
|
||||
fi
|
||||
|
||||
ENABLED_EXTENSIONS=
|
||||
for e in $enable_extensions; do
|
||||
case $e in
|
||||
systemMonitor)
|
||||
PKG_CHECK_MODULES(GTOP, libgtop-2.0 >= 2.28.3,
|
||||
[ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"],
|
||||
[AC_MSG_WARN([libgtop-2.0 not found, disabling systemMonitor])])
|
||||
;;
|
||||
xrandr-indicator)
|
||||
PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0 >= 2.91.6,
|
||||
[ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"],
|
||||
[AC_MSG_WARN([gnome-desktop-3.0 not found, disabling xrandr-indicator])])
|
||||
;;
|
||||
dnl keep this in alphabetic order
|
||||
alternate-tab|alternative-status-menu|apps-menu|auto-move-windows|dock|drive-menu|example|gajim|native-window-placement|places-menu|user-theme|windowsNavigator|workspace-indicator)
|
||||
ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR([invalid extension $e])
|
||||
esac
|
||||
done
|
||||
|
||||
AC_SUBST(ENABLED_EXTENSIONS, [$ENABLED_EXTENSIONS])
|
||||
|
||||
dnl Please keep this sorted alphabetically
|
||||
AC_CONFIG_FILES([
|
||||
extensions/alternate-tab/Makefile
|
||||
extensions/alternative-status-menu/Makefile
|
||||
extensions/apps-menu/Makefile
|
||||
extensions/auto-move-windows/Makefile
|
||||
extensions/dock/Makefile
|
||||
extensions/drive-menu/Makefile
|
||||
extensions/example/Makefile
|
||||
extensions/gajim/Makefile
|
||||
extensions/native-window-placement/Makefile
|
||||
extensions/places-menu/Makefile
|
||||
extensions/systemMonitor/Makefile
|
||||
extensions/user-theme/Makefile
|
||||
extensions/windowsNavigator/Makefile
|
||||
extensions/workspace-indicator/Makefile
|
||||
extensions/xrandr-indicator/Makefile
|
||||
extensions/Makefile
|
||||
Makefile
|
||||
po/Makefile.in
|
||||
])
|
||||
AC_OUTPUT
|
||||
@@ -1,9 +0,0 @@
|
||||
[org.gnome.mutter:GNOME-Classic]
|
||||
dynamic-workspaces=false
|
||||
|
||||
[org.gnome.desktop.wm.preferences:GNOME-Classic]
|
||||
button-layout='appmenu:minimize,maximize,close'
|
||||
|
||||
[org.gnome.desktop.wm.keybindings:GNOME-Classic]
|
||||
switch-applications=[]
|
||||
switch-windows=['<Super>Tab','<Alt>Tab']
|
||||
@@ -1,2 +0,0 @@
|
||||
To update the gnome-shell-sass submodule to latest upstream commit:
|
||||
git submodule update --rebase
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"parentMode": "user",
|
||||
"colorScheme": "force-light",
|
||||
"hasOverview": false,
|
||||
"showWelcomeDialog": false,
|
||||
"enabledExtensions": [@CLASSIC_EXTENSIONS@],
|
||||
"panel": { "left": [],
|
||||
"center": [],
|
||||
"right": ["a11y", "keyboard", "dateMenu", "quickSettings"]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=GNOME Classic on Wayland
|
||||
Comment=This session logs you into GNOME Classic
|
||||
Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Classic;GNOME;
|
||||
X-GDM-SessionRegisters=true
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=GNOME Classic on Xorg
|
||||
Comment=This session logs you into GNOME Classic
|
||||
Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Classic;GNOME;
|
||||
X-GDM-SessionRegisters=true
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=GNOME Classic
|
||||
Comment=This session logs you into GNOME Classic
|
||||
Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Classic;GNOME;
|
||||
X-GDM-SessionRegisters=true
|
||||
@@ -1,50 +0,0 @@
|
||||
session_desktop_base = 'gnome-classic'
|
||||
|
||||
session_desktops = [
|
||||
session_desktop_base,
|
||||
session_desktop_base + '-xorg',
|
||||
session_desktop_base + '-wayland',
|
||||
]
|
||||
|
||||
foreach name: session_desktops
|
||||
session_desktop = name + '.desktop'
|
||||
if name.endswith('-xorg')
|
||||
session_instdir = xsessiondir
|
||||
elif name.endswith('-wayland')
|
||||
session_instdir = wlsessiondir
|
||||
else
|
||||
# FIXME: The same target can not be copied into two directories.
|
||||
# There is a workaround in meson/session-post-install.py until proper
|
||||
# solution arises:
|
||||
# https://github.com/mesonbuild/meson/issues/2416
|
||||
session_instdir = xsessiondir
|
||||
#session_instdir = [ xesssiondir, wlsessiondir ]
|
||||
endif
|
||||
i18n.merge_file(
|
||||
input: session_desktop + '.in',
|
||||
output: session_desktop,
|
||||
po_dir: '../po',
|
||||
install: true,
|
||||
install_dir: session_instdir,
|
||||
type: 'desktop'
|
||||
)
|
||||
endforeach
|
||||
|
||||
classic_uuids = []
|
||||
foreach e : classic_extensions
|
||||
classic_uuids += e + uuid_suffix
|
||||
endforeach
|
||||
|
||||
mode_conf = configuration_data()
|
||||
mode_conf.set('CLASSIC_EXTENSIONS', '"' + '", "'.join(classic_uuids) + '"')
|
||||
|
||||
mode_file = 'classic.json'
|
||||
configure_file(
|
||||
input: mode_file + '.in',
|
||||
output: mode_file,
|
||||
configuration: mode_conf,
|
||||
install_dir: modedir
|
||||
)
|
||||
|
||||
classic_override = '00_org.gnome.shell.extensions.classic.gschema.override'
|
||||
install_data(classic_override, install_dir: schemadir)
|
||||
916
debian/changelog
vendored
916
debian/changelog
vendored
@@ -1,916 +0,0 @@
|
||||
gnome-shell-extensions (45~beta-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bícha <jbicha@ubuntu.com> Mon, 21 Aug 2023 10:17:23 -0400
|
||||
|
||||
gnome-shell-extensions (44.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* Upload to unstable (part of transition: #1043144)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Sun, 20 Aug 2023 23:04:08 +0100
|
||||
|
||||
gnome-shell-extensions (44.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* debian/control.in: Bump minimum meson to 0.58.0
|
||||
* debian/control.in: Build-Depend on libglib2.0-bin
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Mon, 27 Mar 2023 08:50:32 -0400
|
||||
|
||||
gnome-shell-extensions (44~rc-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Tue, 07 Mar 2023 12:20:00 -0500
|
||||
|
||||
gnome-shell-extensions (44~beta-1) experimental; urgency=medium
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* New upstream release
|
||||
|
||||
[ Debian Janitor ]
|
||||
* Remove constraints unnecessary since buster (oldstable)
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Wed, 15 Feb 2023 11:01:26 -0500
|
||||
|
||||
gnome-shell-extensions (43.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Tue, 22 Nov 2022 14:14:48 -0500
|
||||
|
||||
gnome-shell-extensions (43.0-2) unstable; urgency=medium
|
||||
|
||||
* Release to unstable
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Tue, 27 Sep 2022 19:13:41 -0400
|
||||
|
||||
gnome-shell-extensions (43.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Mon, 19 Sep 2022 10:57:41 -0400
|
||||
|
||||
gnome-shell-extensions (43~rc-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Tue, 06 Sep 2022 14:39:00 -0400
|
||||
|
||||
gnome-shell-extensions (43~beta-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Sun, 21 Aug 2022 10:59:20 -0400
|
||||
|
||||
gnome-shell-extensions (42.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release (LP: #1981606)
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Wed, 13 Jul 2022 17:49:05 +0200
|
||||
|
||||
gnome-shell-extensions (42.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Fri, 03 Jun 2022 09:23:28 -0400
|
||||
|
||||
gnome-shell-extensions (42.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release (LP: #1972365)
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Mon, 09 May 2022 08:09:32 -0400
|
||||
|
||||
gnome-shell-extensions (42.0-2) unstable; urgency=medium
|
||||
|
||||
* Release to unstable
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Mon, 28 Mar 2022 11:18:12 -0400
|
||||
|
||||
gnome-shell-extensions (42.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jeremy.bicha@canonical.com> Mon, 14 Mar 2022 11:30:18 -0400
|
||||
|
||||
gnome-shell-extensions (42~rc-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jeremy.bicha@canonical.com> Tue, 08 Mar 2022 17:54:41 -0500
|
||||
|
||||
gnome-shell-extensions (42~beta-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release:
|
||||
- Use libadwaita for preferences
|
||||
- window-list: Fix on-screen keyboard
|
||||
- native-window-placement: Fix distorted layout in app grid
|
||||
* debian/control: Depend on libadwaita and gtk-4 (instead of gtk-3)
|
||||
* debian/patches: Drop applied upstream
|
||||
|
||||
-- Marco Trevisan (Treviño) <marco@ubuntu.com> Wed, 23 Feb 2022 07:35:20 +0100
|
||||
|
||||
gnome-shell-extensions (41.1-2) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* d/p/meson-Drop-unused-argument-for-i18n.merge_file.patch:
|
||||
Backport patch from upstream to fix FBTFS with Meson 0.61
|
||||
(Closes: #1005534)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Tue, 15 Feb 2022 11:18:30 +0000
|
||||
|
||||
gnome-shell-extensions (41.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Sun, 12 Dec 2021 20:30:17 -0500
|
||||
|
||||
gnome-shell-extensions (41.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* Upload to unstable
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Sat, 16 Oct 2021 20:06:04 +0100
|
||||
|
||||
gnome-shell-extensions (41.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* debian/control.in: Bump minimum meson to 0.53.0
|
||||
* Bump Standards-Version to 4.6.0
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Sun, 10 Oct 2021 17:04:15 -0400
|
||||
|
||||
gnome-shell-extensions (40.4-3) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* Release to unstable (transition: #992870)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Sat, 11 Sep 2021 21:52:59 +0100
|
||||
|
||||
gnome-shell-extensions (40.4-2) experimental; urgency=medium
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* New upstream release
|
||||
* debian/copyright: Add gnome-classic.css to Files-Excluded
|
||||
* Revert "debian/control: Don't build depend on sassc"
|
||||
* This ensures we compile the CSS file from source.
|
||||
|
||||
[ Marco Trevisan (Treviño) ]
|
||||
* debian/control: Support pre-release versions of gnome-shell
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Sun, 29 Aug 2021 09:16:04 -0400
|
||||
|
||||
gnome-shell-extensions (40.1-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* debian/control:
|
||||
- Update runtime dependencies to match code.
|
||||
Found grepping code via:
|
||||
grep "imports\.gi" --include "*.js" -rh -B2 . \
|
||||
| grep -o "\b[A-Z][A-Za-z]\+\b" | sed "s/,\?\s*$//g" | sort -u
|
||||
- Don't build depend on sassc.
|
||||
Since we compile from tarball, this is not needed anymore given that
|
||||
compiled css are provided already.
|
||||
- Build-depend on dh-sequence-gnome 0.22.
|
||||
It's the first version that supports new GNOME versioning schema.
|
||||
|
||||
-- Marco Trevisan (Treviño) <marco@ubuntu.com> Sun, 30 May 2021 20:05:13 +0200
|
||||
|
||||
gnome-shell-extensions (3.38.2-1) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream release
|
||||
* Standards-Version: 4.5.1 (no changes required)
|
||||
* d/gbp.conf: Use upstream/3.38.x branch.
|
||||
Version 40~alpha was already released upstream.
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Thu, 03 Dec 2020 10:17:58 +0000
|
||||
|
||||
gnome-shell-extensions (3.38.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* Recommend gnome-shell-extension-prefs instead of gnome-tweaks
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Tue, 06 Oct 2020 17:44:30 -0400
|
||||
|
||||
gnome-shell-extensions (3.38.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* Revert "debian/watch: Watch for unstable releases"
|
||||
* Release to unstable
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 25 Sep 2020 23:00:58 +0100
|
||||
|
||||
gnome-shell-extensions (3.38.0-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream release
|
||||
- Translation updates
|
||||
* d/upstream/metadata: Add
|
||||
* Update standards version to 4.5.0, no changes needed
|
||||
* Fix gnome-classic regression in 3.37.91-1.
|
||||
gnome-session no longer takes the `--session gnome-classic` argument
|
||||
when running in classic mode.
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Tue, 22 Sep 2020 11:49:24 +0100
|
||||
|
||||
gnome-shell-extensions (3.37.91-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release:
|
||||
- drive-menu: Emphasize eject buttons
|
||||
- user-theme: Add preference dialog
|
||||
- window-list:
|
||||
+ Fix inconsistent state in preference dialog
|
||||
+ auto-move: Modernize preference dialogs
|
||||
- workspace-indicator: Overhaul preference dialog
|
||||
- user-theme: Support session mode styles
|
||||
- Adjust to gnome-shell changes
|
||||
* debian/patches: Refresh
|
||||
* debian/control: B-D on debhelper 13
|
||||
* debian/rules: Don't override dh_missing equal default behavior
|
||||
* debian/control: Remove useless dependency on clutter gir
|
||||
|
||||
-- Marco Trevisan (Treviño) <marco@ubuntu.com> Thu, 27 Aug 2020 17:39:42 +0200
|
||||
|
||||
gnome-shell-extensions (3.36.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Laurent Bigonville <bigon@debian.org> Sun, 03 May 2020 10:02:18 +0200
|
||||
|
||||
gnome-shell-extensions (3.36.1-1) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream release
|
||||
- Update Ukrainian translation
|
||||
- Update Gnome Shell CSS
|
||||
* Upload to unstable (part of transition: #954422)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 10 Apr 2020 15:12:29 +0100
|
||||
|
||||
gnome-shell-extensions (3.36.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream translation release
|
||||
|
||||
-- Iain Lane <laney@debian.org> Mon, 16 Mar 2020 12:39:02 +0000
|
||||
|
||||
gnome-shell-extensions (3.35.91-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
- Fixes and adjustments to latest gnome-shell
|
||||
|
||||
-- Marco Trevisan (Treviño) <marco@ubuntu.com> Mon, 24 Feb 2020 18:29:29 +0000
|
||||
|
||||
gnome-shell-extensions (3.34.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* debian/control.in: Bump Standards-Version to 4.4.1 (no further changes)
|
||||
|
||||
-- Laurent Bigonville <bigon@debian.org> Mon, 30 Dec 2019 00:51:13 +0100
|
||||
|
||||
gnome-shell-extensions (3.34.1-2) unstable; urgency=high
|
||||
|
||||
* New upstream release
|
||||
- Fix GNOME Classic session's compatibility with gnome-setting-daemon 3.34
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Tue, 08 Oct 2019 22:49:14 -0400
|
||||
|
||||
gnome-shell-extensions (3.34.0-2) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Upload to unstable.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Mon, 30 Sep 2019 17:27:38 +0200
|
||||
|
||||
gnome-shell-extensions (3.34.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream translation release
|
||||
|
||||
-- Iain Lane <laney@debian.org> Tue, 10 Sep 2019 10:59:31 +0100
|
||||
|
||||
gnome-shell-extensions (3.33.92-1) experimental; urgency=medium
|
||||
|
||||
* New upstream bugfix / translation releases
|
||||
|
||||
-- Iain Lane <laney@debian.org> Thu, 05 Sep 2019 18:22:14 +0100
|
||||
|
||||
gnome-shell-extensions (3.33.90-1) experimental; urgency=medium
|
||||
|
||||
[ Marco Trevisan (Treviño) ]
|
||||
* New upstream release
|
||||
+ window-list: Support showing windows from all workspaces
|
||||
+ Make GNOME Classic more classic:
|
||||
- Disable GNOME 3 overview
|
||||
- Add window picker button to window list
|
||||
- Style improvements and fixes
|
||||
- Support horizontal workspace layout in window list
|
||||
- Add draggable previews to window list workspace switcher
|
||||
- Arrange workspaces horizontally
|
||||
+ workspace-indicator: Support horizontal workspace layout
|
||||
+ workspace-indicator: Add draggable previews
|
||||
+ Fix windowsNavigator extension after ES6 port
|
||||
+ screenshot-window-sizer: Add phone screenshot sizes
|
||||
|
||||
[ Iain Lane ]
|
||||
* rules: Build all extensions via the upstream build system. We don't need
|
||||
to hardcode the list to build, as the build system provides a way to build
|
||||
'all' extensions.
|
||||
* rules: Build with --fail-missing
|
||||
* compat, control, rules: Move to compat 12 and specifying via build-deps
|
||||
|
||||
-- Iain Lane <laney@debian.org> Tue, 13 Aug 2019 11:59:22 +0100
|
||||
|
||||
gnome-shell-extensions (3.32.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Iain Lane <laney@debian.org> Tue, 12 Mar 2019 16:34:14 +0000
|
||||
|
||||
gnome-shell-extensions (3.31.92-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Iain Lane <laney@debian.org> Wed, 06 Mar 2019 15:40:48 +0000
|
||||
|
||||
gnome-shell-extensions (3.31.90-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
+ Misc. bug fixes and cleanups
|
||||
+ Remove obsolete alternate-tab extension
|
||||
+ Adjust to gnome-shell changes
|
||||
* debian/watch: Watch for unstable releases
|
||||
* rules: alternate-tab is dropped; stop trying to enable it
|
||||
|
||||
-- Iain Lane <laney@debian.org> Thu, 21 Feb 2019 10:33:12 +0000
|
||||
|
||||
gnome-shell-extensions (3.30.1-1) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream release
|
||||
* Bump Standards-Version to 4.2.1
|
||||
* d/p/gnome-session-classic-wrapper-script.patch:
|
||||
Re-word to avoid a Lintian warning
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 02 Nov 2018 09:26:47 +0000
|
||||
|
||||
gnome-shell-extensions (3.30.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* Release to unstable
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Wed, 05 Sep 2018 12:34:40 -0400
|
||||
|
||||
gnome-shell-extensions (3.29.91-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream development release
|
||||
* Bump Standards-Version to 4.2.0
|
||||
* Sort dependency lists (wrap-and-sort -a)
|
||||
* d/copyright: Remove unnecessary sentence fragment
|
||||
* d/p/series: Remove commented-out line
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Mon, 20 Aug 2018 20:27:57 +0100
|
||||
|
||||
gnome-shell-extensions (3.29.90-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream development release
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Thu, 02 Aug 2018 10:46:44 +0100
|
||||
|
||||
gnome-shell-extensions (3.29.3+really3.29.3-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* d/watch: Watch for development versions
|
||||
* Re-upload to experimental with a higher version than
|
||||
3.29.3+really3.28.1-1, which reverted the incorrect upload of the
|
||||
previous version to unstable
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 27 Jul 2018 23:56:10 +0100
|
||||
|
||||
gnome-shell-extensions (3.29.3-1) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
|
||||
[ Arnaud Rebillout ]
|
||||
* New upstream release
|
||||
|
||||
[ Simon McVittie ]
|
||||
* Bump Standards-Version to 4.1.5
|
||||
* Set Rules-Requires-Root to no
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 27 Jul 2018 09:37:34 +0100
|
||||
|
||||
gnome-shell-extensions (3.28.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
* Bump Standards-Version to 4.1.4
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Mon, 14 May 2018 21:51:46 -0400
|
||||
|
||||
gnome-shell-extensions (3.28.0-2) unstable; urgency=medium
|
||||
|
||||
* Fix typo in configure flag that resulted in missing
|
||||
auto-move-windows, native-window-placement, & user-theme extensions
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Mon, 19 Mar 2018 18:20:53 -0400
|
||||
|
||||
gnome-shell-extensions (3.28.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Sun, 18 Mar 2018 20:17:17 -0400
|
||||
|
||||
gnome-shell-extensions (3.27.92-2) unstable; urgency=medium
|
||||
|
||||
* Release to unstable
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Sat, 10 Mar 2018 19:37:45 -0500
|
||||
|
||||
gnome-shell-extensions (3.27.92-1) experimental; urgency=medium
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* New upstream release candidate
|
||||
* Drop explicit dependency on mutter's gir. gnome-shell-extensions depends
|
||||
on gnome-shell which depends on the gir. Dropping it will make transitions
|
||||
easier.
|
||||
|
||||
[ Simon McVittie ]
|
||||
* Recommend gnome-tweaks instead of transitional gnome-tweak-tool
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Mon, 05 Mar 2018 21:02:01 -0500
|
||||
|
||||
gnome-shell-extensions (3.27.91-1) experimental; urgency=medium
|
||||
|
||||
* New upstream development release
|
||||
* Build with meson
|
||||
* Build-Depend on sassc
|
||||
* Bump debhelper compat to 11
|
||||
* Drop places-menu-Don-t-force-dispose-of-uninitialized-proxies.patch &
|
||||
dont-require-nautilus-classic.patch: Applied in new release
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Fri, 23 Feb 2018 19:49:59 -0500
|
||||
|
||||
gnome-shell-extensions (3.26.2-3) unstable; urgency=medium
|
||||
|
||||
* Team upload
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* Update Vcs fields for migration to https://salsa.debian.org/
|
||||
|
||||
[ Simon McVittie ]
|
||||
* d/patches: Re-export with gbp pq export
|
||||
* d/p/places-menu-Don-t-force-dispose-of-uninitialized-proxies.patch:
|
||||
Take patch from upstream to avoid frequent tracebacks from the Places
|
||||
menu with gjs >= 1.50.2-3 (Closes: #888608)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Tue, 30 Jan 2018 09:15:54 +0000
|
||||
|
||||
gnome-shell-extensions (3.26.2-2) unstable; urgency=medium
|
||||
|
||||
* Update Vcs fields for conversion to git
|
||||
* Add debian/gbp.conf
|
||||
* Bump Standards-Version to 4.1.2
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Fri, 15 Dec 2017 15:19:14 -0500
|
||||
|
||||
gnome-shell-extensions (3.26.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Sun, 05 Nov 2017 20:17:03 +0100
|
||||
|
||||
gnome-shell-extensions (3.26.1-2) unstable; urgency=medium
|
||||
|
||||
* Release to unstable
|
||||
* Bump Standards-Version to 4.1.1
|
||||
|
||||
-- Jeremy Bicha <jbicha@debian.org> Fri, 13 Oct 2017 16:49:30 -0400
|
||||
|
||||
gnome-shell-extensions (3.26.1-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream stable release
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Fri, 06 Oct 2017 11:01:11 +0100
|
||||
|
||||
gnome-shell-extensions (3.26.0-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream stable release
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Wed, 13 Sep 2017 11:33:19 +0100
|
||||
|
||||
gnome-shell-extensions (3.25.91-1) experimental; urgency=medium
|
||||
|
||||
* Team upload
|
||||
* New upstream release, for GNOME Shell 3.25.91
|
||||
- d/p/adapt-to-gsd324.patch: Drop, applied upstream
|
||||
- Switch dependency to gir1.2-mutter-1
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Tue, 05 Sep 2017 16:43:01 +0100
|
||||
|
||||
gnome-shell-extensions (3.22.2-2) unstable; urgency=medium
|
||||
|
||||
* Add adapt-to-gsd324.patch (Closes: #869948):
|
||||
- Adjust gnome-session file for gnome-settings-daemon 3.24
|
||||
* debian/control.in:
|
||||
- Bump Standards-Version to 4.0.0
|
||||
- Explicitly depend on gnome-settings-daemon >= 3.24
|
||||
- Drop version from gnome-tweak-tool recommends
|
||||
- Depend on gnome-session-bin instead of gnome-session (LP: #1702832)
|
||||
|
||||
-- Jeremy Bicha <jbicha@ubuntu.com> Sun, 06 Aug 2017 17:38:06 -0400
|
||||
|
||||
gnome-shell-extensions (3.22.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Thu, 10 Nov 2016 19:36:44 +0100
|
||||
|
||||
gnome-shell-extensions (3.22.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Tue, 11 Oct 2016 17:58:31 +0200
|
||||
|
||||
gnome-shell-extensions (3.22.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Tue, 20 Sep 2016 01:59:50 +0200
|
||||
|
||||
gnome-shell-extensions (3.21.92-1) unstable; urgency=medium
|
||||
|
||||
* New upstream development release.
|
||||
* Replace Build-Depends gnome-common with pkg-config and gettext.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Tue, 13 Sep 2016 22:13:19 +0200
|
||||
|
||||
gnome-shell-extensions (3.21.91-1) unstable; urgency=medium
|
||||
|
||||
* New upstream beta release.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Tue, 30 Aug 2016 19:57:32 +0200
|
||||
|
||||
gnome-shell-extensions (3.21.90-1) experimental; urgency=medium
|
||||
|
||||
[ Andreas Henriksson ]
|
||||
* New upstream beta release.
|
||||
* Update build-dependencies according to configure.ac changes:
|
||||
- drop intltool, now gettext is used instead.
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* Convert from cdbs to dh
|
||||
* Add debian/docs to install NEWS and README
|
||||
* Bump dh compat to 10
|
||||
* Update Vcs fields
|
||||
* Refresh patches
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Sun, 21 Aug 2016 05:36:17 +0200
|
||||
|
||||
gnome-shell-extensions (3.21.4-1) experimental; urgency=medium
|
||||
|
||||
* New upstream development release.
|
||||
* Stop hard-coding Victor Seva in the Uploaders field
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Fri, 12 Aug 2016 16:28:33 +0200
|
||||
|
||||
gnome-shell-extensions (3.20.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Bump Standards-Version to 3.9.8.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Wed, 11 May 2016 15:21:35 +0200
|
||||
|
||||
gnome-shell-extensions (3.20.0-2) unstable; urgency=medium
|
||||
|
||||
* Upload to unstable.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Sun, 17 Apr 2016 00:25:25 +0200
|
||||
|
||||
gnome-shell-extensions (3.20.0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Fri, 25 Mar 2016 10:48:00 +0100
|
||||
|
||||
gnome-shell-extensions (3.19.92-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Update dont-require-nautilus-classic.patch to apply.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Thu, 17 Mar 2016 12:51:13 +0100
|
||||
|
||||
gnome-shell-extensions (3.18.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Bump Standards-Version to 3.9.7.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Sun, 06 Mar 2016 22:38:57 +0100
|
||||
|
||||
gnome-shell-extensions (3.18.3-2) unstable; urgency=medium
|
||||
|
||||
* Add Breaks/Replaces gnome-shell-common (<< 3.18) (Closes: #808906)
|
||||
- Helps upgrades from Jessie by allowing overwriting
|
||||
/usr/share/gnome-shell/theme/calendar-today.svg et.al.
|
||||
which was previously shipped in gnome-shell-common.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Wed, 10 Feb 2016 01:24:42 +0100
|
||||
|
||||
gnome-shell-extensions (3.18.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Thu, 14 Jan 2016 22:49:16 +0100
|
||||
|
||||
gnome-shell-extensions (3.18.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Fri, 13 Nov 2015 00:04:09 +0100
|
||||
|
||||
gnome-shell-extensions (3.18.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Fri, 16 Oct 2015 22:09:01 +0200
|
||||
|
||||
gnome-shell-extensions (3.18.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Laurent Bigonville <bigon@debian.org> Sun, 11 Oct 2015 16:29:18 +0200
|
||||
|
||||
gnome-shell-extensions (3.17.92-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release candidate.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Fri, 18 Sep 2015 17:28:06 +0200
|
||||
|
||||
gnome-shell-extensions (3.16.2-1) unstable; urgency=medium
|
||||
|
||||
[ Emilio Pozuelo Monfort ]
|
||||
* debian/gnome-shell-extensions.gsettings-override:
|
||||
+ Dropped, no longer needed as the enabled extension no longer exists.
|
||||
Thanks Artur Rona for noticing. (Closes: #782747)
|
||||
|
||||
[ Michael Biebl ]
|
||||
* New upstream release.
|
||||
* Update Homepage.
|
||||
* Bump debhelper compatibility level to 9.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Thu, 02 Jul 2015 19:13:32 +0200
|
||||
|
||||
gnome-shell-extensions (3.16.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* debian/control.in,
|
||||
debian/rules:
|
||||
+ The system monitor extension was removed. Drop the libgtop
|
||||
build and runtime dependencies and stop enabling it.
|
||||
* debian/patches/apps-center-labels.patch,
|
||||
debian/patches/window-list-pointerInNotification.patch,
|
||||
debian/patches/menu-arrows-icons.patch:
|
||||
+ Dropped, merged upstream.
|
||||
* debian/patches/dont-require-nautilus-classic.patch:
|
||||
+ Updated for the new version.
|
||||
|
||||
-- Emilio Pozuelo Monfort <pochu@debian.org> Sun, 14 Jun 2015 15:23:59 +0200
|
||||
|
||||
gnome-shell-extensions (3.14.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream bugfix release.
|
||||
* menu-arrows-icons: new patch. Make arrows consistent with the rest
|
||||
of the shell.
|
||||
* apps-center-labels.patch: patch from upstream git. Center labels
|
||||
vertically in the applications menu.
|
||||
* window-list-pointerInNotification.patch: patch from upstream git.
|
||||
Update window-list extension for an older shell API change.
|
||||
|
||||
-- Josselin Mouette <joss@debian.org> Sun, 30 Nov 2014 16:06:59 +0100
|
||||
|
||||
gnome-shell-extensions (3.14.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* debian/control.in: Bump Standards-Version to 3.9.6 (no further changes)
|
||||
* Add missing dependencies against gir packages, including gir1.2-gmenu-3.0,
|
||||
this should fix the apps-menu extension for some people (Closes: #765460).
|
||||
|
||||
-- Laurent Bigonville <bigon@debian.org> Sun, 19 Oct 2014 21:17:58 +0200
|
||||
|
||||
gnome-shell-extensions (3.14.0-2) unstable; urgency=medium
|
||||
|
||||
* Drop xrandr from EXTENSIONS_DISABLED, this extension was removed
|
||||
upstream.
|
||||
* Add new screenshot-window-sizer extension to EXTENSIONS_ENABLED.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Wed, 24 Sep 2014 01:15:32 +0200
|
||||
|
||||
gnome-shell-extensions (3.14.0-1) unstable; urgency=medium
|
||||
|
||||
* debian/watch: only scan for stable releases.
|
||||
* New upstream release.
|
||||
* Upload to unstable.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Tue, 23 Sep 2014 15:46:30 +0200
|
||||
|
||||
gnome-shell-extensions (3.13.91-1) experimental; urgency=medium
|
||||
|
||||
* New upstream development release.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Sat, 06 Sep 2014 11:16:39 -0700
|
||||
|
||||
gnome-shell-extensions (3.12.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Bump Standards-Version to 3.9.5
|
||||
* Upload to unstable.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Tue, 15 Jul 2014 00:06:09 +0200
|
||||
|
||||
gnome-shell-extensions (3.12.0-1) experimental; urgency=low
|
||||
|
||||
[ Jean Schurger ]
|
||||
* New upstream release (3.10.1)
|
||||
* debian/rules: remove one of the 'windowsNavigator' extension listed twice
|
||||
and the 'alternative-system-menu' extension (does not exists anymore)
|
||||
* debian/copyright: Update 'Format:' line
|
||||
|
||||
[ Andreas Henriksson ]
|
||||
* New upstream release (3.12.0)
|
||||
* debian/local/gnome-session-classic,
|
||||
debian/patches/gnome-session-classic-wrapper-script.patch:
|
||||
- update script to include new GNOME_SHELL_SESSION_MODE env variable
|
||||
and update patch to apply again.
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Fri, 04 Apr 2014 21:43:18 +0200
|
||||
|
||||
gnome-shell-extensions (3.8.4-2) unstable; urgency=low
|
||||
|
||||
* Upload to unstable.
|
||||
|
||||
-- Emilio Pozuelo Monfort <pochu@debian.org> Sun, 13 Oct 2013 18:11:33 +0200
|
||||
|
||||
gnome-shell-extensions (3.8.4-1) experimental; urgency=low
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* New upstream release
|
||||
* Dropped patches applied in new version:
|
||||
- fix-hibernate.patch
|
||||
- fix-applications-menu-resolution-change.patch
|
||||
- look-in-data-home-for-themes.patch
|
||||
|
||||
[ Michael Biebl ]
|
||||
* Add a wrapper script to start the GNOME Classic session as currently
|
||||
Xsession doesn't allow to run gnome-session with custom arguments due to
|
||||
#653327.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Fri, 11 Oct 2013 18:55:23 +0200
|
||||
|
||||
gnome-shell-extensions (3.8.3.1-1) experimental; urgency=low
|
||||
|
||||
[ Jeremy Bicha ]
|
||||
* New upstream release
|
||||
- default-min-max and static-workspaces extensions have been dropped.
|
||||
Use Classic Mode or tweak org.gnome.shell.overrides in dconf-editor
|
||||
* debian/control.in:
|
||||
- Depend on gnome-session 3.8, required for the new Classic mode.
|
||||
- Update homepage
|
||||
* debian/patches/dont-require-nautilus-classic.patch:
|
||||
- Don't require nautilus-classic since it forces desktop icons
|
||||
* debian/patches/fix-applications-menu-resolution-change.patch:
|
||||
- Backport commit to not break applications menu when screen resolution
|
||||
changes
|
||||
* debian/patches/fix-hibernate.patch:
|
||||
- Backport patch to fix checking whether hibernate is allowed
|
||||
* debian/patches/look-in-data-home-for-themes.patch:
|
||||
- Backport commit to also look in XDG_DATA_HOME (usually ~/.local/share/)
|
||||
for user themes
|
||||
* debian/rules:
|
||||
- Run autoreconf
|
||||
|
||||
-- Andreas Henriksson <andreas@fatal.se> Fri, 16 Aug 2013 20:25:30 +0200
|
||||
|
||||
gnome-shell-extensions (3.8.2-1) experimental; urgency=low
|
||||
|
||||
* Team upload
|
||||
|
||||
[ Victor Seva ]
|
||||
* Recommends gnome-tweak-tool
|
||||
|
||||
[ Jean Schurger ]
|
||||
* New upstream release 3.7.92 (LP: #1017979, #1059152).
|
||||
* Enabled new extensions.
|
||||
* Dropped 'dock' and 'gajim'.
|
||||
* Use ./configure instead of autoreconf.
|
||||
* Bumped Standards-Version to 3.9.4.
|
||||
* Updated Vcs-Svn.
|
||||
* debian/control.in
|
||||
- Added runtime dependency to 'gvfs' (>= 1.16.0).
|
||||
The 'Places' extension rely on a 'gvfs' linked to 'udisks2'.
|
||||
|
||||
[ Simon McVittie ]
|
||||
* New upstream release 3.8.2.
|
||||
* Install the "classic mode"
|
||||
* Enable windowNavigator extension (this means we have everything except
|
||||
example and xrandr, the same as in the Ubuntu gnome3-team's PPA)
|
||||
|
||||
-- Simon McVittie <smcv@debian.org> Thu, 23 May 2013 09:07:23 +0100
|
||||
|
||||
gnome-shell-extensions (3.4.0-2) unstable; urgency=low
|
||||
|
||||
* Upload to unstable.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Wed, 30 May 2012 13:22:54 +0200
|
||||
|
||||
gnome-shell-extensions (3.4.0-1) experimental; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
* Remove 01_status-menu_disable_accounts.patch: The alternative-status-menu
|
||||
extension no longer recreates the complete user menu but reuses the one
|
||||
from gnome-shell, so we can't easily get rid of the "Online Accounts" menu
|
||||
entry. We will patch gnome-shell directly instead.
|
||||
* Remove 02-Revert-all-remove-all-GSettings-usage.patch and
|
||||
03-Revert-Remove-all-references-to-localedir-from-metad.patch, no longer
|
||||
required.
|
||||
* Bump Standards-Version to 3.9.3
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Mon, 21 May 2012 00:24:56 +0200
|
||||
|
||||
gnome-shell-extensions (3.2.3-1) unstable; urgency=low
|
||||
|
||||
[ Josselin Mouette ]
|
||||
* gnome-shell-extensions.gsettings-override: enable the alternative
|
||||
status menu by default. Closes: #648112.
|
||||
* Use ${gnome:Version} to generate strict dependencies, it’s very
|
||||
unlikely that extensions remain compatible after a major upgrade.
|
||||
|
||||
[ Michael Biebl ]
|
||||
* Upload to unstable.
|
||||
|
||||
[ Josselin Mouette ]
|
||||
* 01_status-menu_disable_accounts.patch: new patch. Drop the unusable
|
||||
advertisement for Google. It is already available in the control
|
||||
center anyway.
|
||||
|
||||
[ Michael Biebl ]
|
||||
* New upstream release.
|
||||
* Drop patches which have been merged upstream:
|
||||
- debian/patches/upstream/*
|
||||
- debian/patches/fix-*
|
||||
* Refresh 01_status-menu_disable_accounts.patch.
|
||||
* Add 02-Revert-all-remove-all-GSettings-usage.patch: Use GSettings since we
|
||||
install the extensions system-wide.
|
||||
* Use dh-autoreconf to generate the build system.
|
||||
* Add 03-Revert-Remove-all-references-to-localedir-from-metad.patch: Use
|
||||
locales from system-wide location.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Sat, 11 Feb 2012 23:28:53 +0100
|
||||
|
||||
gnome-shell-extensions (3.2.0-1) experimental; urgency=low
|
||||
|
||||
[ Victor Seva ]
|
||||
* Initial release (Closes: #627515)
|
||||
Package based on the work by Bilal Akhtar <bilalakhtar@ubuntu.com>
|
||||
* debian/copyright
|
||||
- Added myself
|
||||
- Fixed syntax-error-in-dep5-copyright syntax error
|
||||
* debian/control.in
|
||||
- Added myself as maintainer.
|
||||
- Fix gnome-shell dependency.
|
||||
- Fix gir1.2-gtop-2.0 dependency for systemMonitor ext.
|
||||
* debian/rules
|
||||
- Added uploaders.mk gnome-version.mk includes.
|
||||
- Removed unused variables.
|
||||
* debian/watch added.
|
||||
* No more gnome-shell-extension-* packages.
|
||||
* debian/patches/upstream
|
||||
- patch_967aee7aad2accfb38d91ab56b6c5e91d86a2722.diff
|
||||
popup menu fix
|
||||
- patch_66242aa76a5d59fb4659551575c1fbb42e50b8fb.diff
|
||||
dock fix.
|
||||
- patch_4c5a36e4c0cbe38f2e26b6b3c8b02e88b4b939f7.diff
|
||||
patch_2bba98d6214cffae2eb5cecb9d7c1f6b6d244052.diff
|
||||
systemMonitor: Properly enable/disable
|
||||
* debian/patches
|
||||
- fix_dock_gnome-shell_version.diff
|
||||
- fix_alternate-tab_661281.diff
|
||||
- fix_alternate-tab_gnome-shell_version.diff
|
||||
* debian/rules
|
||||
- EXTENSIONS_ENABLED, EXTENSIONS_DISABLED variables
|
||||
- disabled xrandr-indicator
|
||||
|
||||
[ Michael Biebl ]
|
||||
* debian/watch: Track .xz tarballs.
|
||||
* debian/control.in:
|
||||
- Set pkg-gnome-maintainers@lists.alioth.debian.org as Maintainer and add
|
||||
Victor Seva <linuxmaniac@torreviejawireless.org> to Uploaders.
|
||||
- Wrap (Build-)Depends.
|
||||
- Add Vcs-Svn and Vcs-Browser field.
|
||||
* debian/rules:
|
||||
- Include gnome-get-source.mk.
|
||||
|
||||
-- Michael Biebl <biebl@debian.org> Sat, 12 Nov 2011 18:32:22 +0100
|
||||
50
debian/control
vendored
50
debian/control
vendored
@@ -1,50 +0,0 @@
|
||||
# This file is autogenerated. DO NOT EDIT!
|
||||
#
|
||||
# Modifications should be made to debian/control.in instead.
|
||||
# This file is regenerated automatically in the clean target.
|
||||
Source: gnome-shell-extensions
|
||||
Section: gnome
|
||||
Priority: optional
|
||||
Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
|
||||
Uploaders: Iain Lane <laney@debian.org>, Laurent Bigonville <bigon@debian.org>, Marco Trevisan (Treviño) <marco@ubuntu.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-gnome (>= 0.22),
|
||||
libglib2.0-bin,
|
||||
meson (>= 0.58.0),
|
||||
sassc
|
||||
Rules-Requires-Root: no
|
||||
Standards-Version: 4.6.0
|
||||
Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-shell-extensions
|
||||
Vcs-Git: https://salsa.debian.org/gnome-team/gnome-shell-extensions.git
|
||||
Homepage: https://wiki.gnome.org/Projects/GnomeShell/Extensions
|
||||
|
||||
Package: gnome-shell-extensions
|
||||
Architecture: all
|
||||
Depends: gir1.2-adw-1,
|
||||
gir1.2-atk-1.0,
|
||||
gir1.2-glib-2.0,
|
||||
gir1.2-gmenu-3.0,
|
||||
gir1.2-graphene-1.0 (>= 1.10.2),
|
||||
gir1.2-gtk-4.0,
|
||||
gir1.2-pango-1.0,
|
||||
gnome-session-bin (>= 3.8),
|
||||
gnome-settings-daemon (>= 3.24),
|
||||
gnome-shell (<< ${gnome:NextVersion}),
|
||||
gnome-shell (>= ${gnome:Version}),
|
||||
gvfs (>= 1.16.0),
|
||||
${misc:Depends}
|
||||
Recommends: gnome-shell-extension-prefs
|
||||
Replaces: gnome-shell-common (<< 3.18)
|
||||
Breaks: gnome-shell-common (<< 3.18)
|
||||
Description: Extensions to extend functionality of GNOME Shell
|
||||
The GNOME Shell redefines user interactions with the GNOME desktop. In
|
||||
particular, it offers new paradigms for launching applications,
|
||||
accessing documents, and organizing open windows in GNOME. Later, it
|
||||
will introduce a new applets eco-system and offer new solutions for
|
||||
other desktop features, such as notifications and contacts management.
|
||||
The GNOME Shell is intended to replace functions handled by the GNOME
|
||||
Panel and by the window manager in previous versions of GNOME. The GNOME
|
||||
Shell has rich visual effects enabled by new graphical technologies.
|
||||
.
|
||||
GNOME Shell is extensible using extensions. This package contains
|
||||
official GNOME Shell extensions.
|
||||
46
debian/control.in
vendored
46
debian/control.in
vendored
@@ -1,46 +0,0 @@
|
||||
Source: gnome-shell-extensions
|
||||
Section: gnome
|
||||
Priority: optional
|
||||
Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
|
||||
Uploaders: @GNOME_TEAM@
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-gnome (>= 0.22),
|
||||
libglib2.0-bin,
|
||||
meson (>= 0.58.0),
|
||||
sassc
|
||||
Rules-Requires-Root: no
|
||||
Standards-Version: 4.6.0
|
||||
Vcs-Browser: https://salsa.debian.org/gnome-team/gnome-shell-extensions
|
||||
Vcs-Git: https://salsa.debian.org/gnome-team/gnome-shell-extensions.git
|
||||
Homepage: https://wiki.gnome.org/Projects/GnomeShell/Extensions
|
||||
|
||||
Package: gnome-shell-extensions
|
||||
Architecture: all
|
||||
Depends: gir1.2-adw-1,
|
||||
gir1.2-atk-1.0,
|
||||
gir1.2-glib-2.0,
|
||||
gir1.2-gmenu-3.0,
|
||||
gir1.2-graphene-1.0 (>= 1.10.2),
|
||||
gir1.2-gtk-4.0,
|
||||
gir1.2-pango-1.0,
|
||||
gnome-session-bin (>= 3.8),
|
||||
gnome-settings-daemon (>= 3.24),
|
||||
gnome-shell (<< ${gnome:NextVersion}),
|
||||
gnome-shell (>= ${gnome:Version}),
|
||||
gvfs (>= 1.16.0),
|
||||
${misc:Depends}
|
||||
Recommends: gnome-shell-extension-prefs
|
||||
Replaces: gnome-shell-common (<< 3.18)
|
||||
Breaks: gnome-shell-common (<< 3.18)
|
||||
Description: Extensions to extend functionality of GNOME Shell
|
||||
The GNOME Shell redefines user interactions with the GNOME desktop. In
|
||||
particular, it offers new paradigms for launching applications,
|
||||
accessing documents, and organizing open windows in GNOME. Later, it
|
||||
will introduce a new applets eco-system and offer new solutions for
|
||||
other desktop features, such as notifications and contacts management.
|
||||
The GNOME Shell is intended to replace functions handled by the GNOME
|
||||
Panel and by the window manager in previous versions of GNOME. The GNOME
|
||||
Shell has rich visual effects enabled by new graphical technologies.
|
||||
.
|
||||
GNOME Shell is extensible using extensions. This package contains
|
||||
official GNOME Shell extensions.
|
||||
30
debian/copyright
vendored
30
debian/copyright
vendored
@@ -1,30 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: gnome-shell-extensions
|
||||
Upstream-Contact: 2011 Giovanni Campagna <gcampagna@src.gnome.org>
|
||||
Source: https://download.gnome.org/sources/gnome-shell-extensions/
|
||||
Files-Excluded: data/gnome-classic.css
|
||||
Comment: Build that generated file from source
|
||||
|
||||
Files: *
|
||||
Copyright: 2011 Giovanni Campagna <gcampagna@src.gnome.org>
|
||||
License: GPL-2+
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2011 Victor Seva <linuxmaniac@torreviejawireless.org>
|
||||
2011 Bilal Akhtar <bilalakhtar@ubuntu.com>
|
||||
License: GPL-2+
|
||||
|
||||
License: GPL-2+
|
||||
This package 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 package 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.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
|
||||
2
debian/docs
vendored
2
debian/docs
vendored
@@ -1,2 +0,0 @@
|
||||
NEWS
|
||||
README.md
|
||||
17
debian/gbp.conf
vendored
17
debian/gbp.conf
vendored
@@ -1,17 +0,0 @@
|
||||
[DEFAULT]
|
||||
pristine-tar = True
|
||||
debian-branch = debian/latest
|
||||
upstream-branch = upstream/latest
|
||||
|
||||
[buildpackage]
|
||||
sign-tags = True
|
||||
|
||||
[dch]
|
||||
multimaint-merge = True
|
||||
|
||||
[import-orig]
|
||||
postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit
|
||||
upstream-vcs-tag = %(version%~%.)s
|
||||
|
||||
[pq]
|
||||
patch-numbers = False
|
||||
1
debian/install
vendored
1
debian/install
vendored
@@ -1 +0,0 @@
|
||||
debian/local/gnome-session-classic usr/bin
|
||||
2
debian/local/gnome-session-classic
vendored
2
debian/local/gnome-session-classic
vendored
@@ -1,2 +0,0 @@
|
||||
#! /bin/sh
|
||||
env GNOME_SHELL_SESSION_MODE=classic gnome-session "$@"
|
||||
@@ -1,30 +0,0 @@
|
||||
From: Michael Biebl <biebl@debian.org>
|
||||
Date: Tue, 30 Jan 2018 09:04:03 +0000
|
||||
Subject: Use a wrapper script to start GNOME classic session
|
||||
|
||||
Xsession currently doesn't allow passing more then one argument, as it
|
||||
otherwise fails with
|
||||
Xsession: unable to launch "gnome-session --session classic" X session ---
|
||||
"gnome-session --session classic" not found; falling back to default session.
|
||||
|
||||
This is due to [1]. Add a wrapper script to start the GNOME classic session
|
||||
as a workaround. Once [1] is fixed, this should be removed again.
|
||||
|
||||
[1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=653327.
|
||||
---
|
||||
data/gnome-classic.desktop.in | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/data/gnome-classic.desktop.in b/data/gnome-classic.desktop.in
|
||||
index 13da2b5..d627655 100644
|
||||
--- a/data/gnome-classic.desktop.in
|
||||
+++ b/data/gnome-classic.desktop.in
|
||||
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Name=GNOME Classic
|
||||
Comment=This session logs you into GNOME Classic
|
||||
-Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session
|
||||
+Exec=gnome-session-classic
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Classic;GNOME;
|
||||
1
debian/patches/series
vendored
1
debian/patches/series
vendored
@@ -1 +0,0 @@
|
||||
gnome-session-classic-wrapper-script.patch
|
||||
9
debian/rules
vendored
9
debian/rules
vendored
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- \
|
||||
-Dextension_set=all \
|
||||
-Dclassic_mode=true
|
||||
1
debian/source/format
vendored
1
debian/source/format
vendored
@@ -1 +0,0 @@
|
||||
3.0 (quilt)
|
||||
5
debian/upstream/metadata
vendored
5
debian/upstream/metadata
vendored
@@ -1,5 +0,0 @@
|
||||
---
|
||||
Bug-Database: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues
|
||||
Bug-Submit: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/new
|
||||
Repository: https://gitlab.gnome.org/GNOME/gnome-shell-extensions.git
|
||||
Repository-Browse: https://gitlab.gnome.org/GNOME/gnome-shell-extensions
|
||||
4
debian/watch
vendored
4
debian/watch
vendored
@@ -1,4 +0,0 @@
|
||||
version=4
|
||||
opts="searchmode=plain, uversionmangle=s/\.(alpha|beta|rc)/~$1/, downloadurlmangle=s|cache.json||" \
|
||||
https://download.gnome.org/sources/@PACKAGE@/cache.json \
|
||||
[\d.]+/@PACKAGE@-([\d.]+)@ARCHIVE_EXT@
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
srcdir=`dirname $0`
|
||||
srcdir=`(cd $srcdir && pwd)`
|
||||
|
||||
builddir=`mktemp -p $srcdir -d _build.XXXXXX` || exit 1
|
||||
installdir=`mktemp -p $srcdir -d _install.XXXXXX` || exit 1
|
||||
|
||||
meson setup --prefix=$installdir -Dextension_set=all $srcdir $builddir
|
||||
meson install -C $builddir
|
||||
|
||||
rm -rf $srcdir/zip-files
|
||||
mkdir $srcdir/zip-files
|
||||
|
||||
extensiondir=$installdir/share/gnome-shell/extensions
|
||||
schemadir=$installdir/share/glib-2.0/schemas
|
||||
|
||||
for f in $extensiondir/*; do
|
||||
name=`basename ${f%%@*}`
|
||||
uuid=$name@gnome-shell-extensions.gcampax.github.com
|
||||
schema=$schemadir/org.gnome.shell.extensions.$name.gschema.xml
|
||||
|
||||
olddomain=gnome-shell-extensions
|
||||
newdomain=gnome-shell-extension-$name
|
||||
sed -i "/gettext-domain/ s:$olddomain:$newdomain:" $f/metadata.json
|
||||
|
||||
xgettext --from-code=UTF-8 --output-dir=$builddir --output=$name.pot $f/*.js
|
||||
|
||||
if [ -f $builddir/$name.pot ]; then
|
||||
mkdir $f/po
|
||||
for l in $(<$srcdir/po/LINGUAS); do
|
||||
msgmerge --quiet --output-file=$f/po/$l.po \
|
||||
$srcdir/po/$l.po $builddir/$name.pot
|
||||
done
|
||||
fi
|
||||
|
||||
cp $srcdir/NEWS $srcdir/COPYING $f
|
||||
sources=(NEWS COPYING $(cd $f; ls *.js))
|
||||
|
||||
[ -f $schema ] || unset schema
|
||||
|
||||
gnome-extensions pack ${sources[@]/#/--extra-source=} \
|
||||
${schema:+--schema=$schema} --out-dir=$srcdir/zip-files $f
|
||||
done
|
||||
|
||||
rm -rf $builddir
|
||||
rm -rf $installdir
|
||||
18
extension.mk
Normal file
18
extension.mk
Normal file
@@ -0,0 +1,18 @@
|
||||
include $(top_srcdir)/include.mk
|
||||
|
||||
dist_extension_DATA = extension.js stylesheet.css $(EXTRA_MODULES)
|
||||
nodist_extension_DATA = metadata.json $(top_srcdir)/lib/convenience.js $(EXTRA_EXTENSION)
|
||||
|
||||
EXTRA_DIST = metadata.json.in
|
||||
|
||||
metadata.json: metadata.json.in $(top_builddir)/config.status
|
||||
$(AM_V_GEN) sed \
|
||||
-e "s|[@]extension_id@|$(EXTENSION_ID)|" \
|
||||
-e "s|[@]uuid@|$(uuid)|" \
|
||||
-e "s|[@]gschemaname@|$(gschemaname)|" \
|
||||
-e "s|[@]gettext_domain@|$(GETTEXT_PACKAGE)|" \
|
||||
-e "s|[@]shell_current@|$(SHELL_VERSION)|" \
|
||||
-e "s|[@]url@|$(extensionurl)|" \
|
||||
$< > $@
|
||||
|
||||
CLEANFILES = metadata.json
|
||||
3
extensions/Makefile.am
Normal file
3
extensions/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
DIST_SUBDIRS = $(ALL_EXTENSIONS)
|
||||
|
||||
SUBDIRS = $(ENABLED_EXTENSIONS)
|
||||
6
extensions/alternate-tab/Makefile.am
Normal file
6
extensions/alternate-tab/Makefile.am
Normal file
@@ -0,0 +1,6 @@
|
||||
EXTENSION_ID = alternate-tab
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
443
extensions/alternate-tab/extension.js
Normal file
443
extensions/alternate-tab/extension.js
Normal file
@@ -0,0 +1,443 @@
|
||||
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
/* most of the code is borrowed from
|
||||
* > js/ui/altTab.js <
|
||||
* of the gnome-shell source code
|
||||
*/
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = function(e) { return e };
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
let settings;
|
||||
|
||||
const AppIconMode = {
|
||||
THUMBNAIL_ONLY: 1,
|
||||
APP_ICON_ONLY: 2,
|
||||
BOTH: 3,
|
||||
};
|
||||
|
||||
const SETTINGS_APP_ICON_MODE = 'app-icon-mode';
|
||||
const SETTINGS_CURRENT_WORKSPACE_ONLY = 'current-workspace-only';
|
||||
|
||||
function mod(a, b) {
|
||||
return ((a+b) % b);
|
||||
}
|
||||
|
||||
const AltTabPopup = new Lang.Class({
|
||||
Name: 'AlternateTab.AltTabPopup',
|
||||
|
||||
_init : function(settings) {
|
||||
this._settings = settings;
|
||||
|
||||
this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
|
||||
reactive: true });
|
||||
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
|
||||
this._haveModal = false;
|
||||
|
||||
this._currentWindow = 0;
|
||||
this._motionTimeoutId = 0;
|
||||
this._initialDelayTimeoutId = 0;
|
||||
|
||||
// Initially disable hover so we ignore the enter-event if
|
||||
// the switcher appears underneath the current pointer location
|
||||
this._disableHover();
|
||||
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
alloc.min_size = global.screen_width;
|
||||
alloc.natural_size = global.screen_width;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
alloc.min_size = global.screen_height;
|
||||
alloc.natural_size = global.screen_height;
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||
let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||
let hPadding = leftPadding + rightPadding;
|
||||
|
||||
// Allocate the appSwitcher
|
||||
// We select a size based on an icon size that does not overflow the screen
|
||||
let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
|
||||
let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
|
||||
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
||||
childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
|
||||
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||
this._appSwitcher.actor.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
show : function(backward, binding, mask) {
|
||||
let windows;
|
||||
|
||||
if (!settings.get_boolean(SETTINGS_CURRENT_WORKSPACE_ONLY)) {
|
||||
// This is roughly what meta_display_get_tab_list does, except
|
||||
// that it doesn't filter on workspace
|
||||
// See in particular src/core/window-private.h for the filters
|
||||
windows = global.get_window_actors().map(function(actor) {
|
||||
return actor.meta_window;
|
||||
}).filter(function(win) {
|
||||
return !win.is_override_redirect() &&
|
||||
win.get_window_type() != Meta.WindowType.DESKTOP &&
|
||||
win.get_window_type() != Meta.WindowType.DOCK;
|
||||
}).sort(function(one, two) {
|
||||
return two.get_user_time() - one.get_user_time();
|
||||
});
|
||||
} else {
|
||||
windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, global.screen,
|
||||
global.screen.get_active_workspace());
|
||||
}
|
||||
|
||||
if (!windows.length) {
|
||||
this.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Main.pushModal(this.actor)) {
|
||||
// Probably someone else has a pointer grab, try again with keyboard only
|
||||
if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this._haveModal = true;
|
||||
this._modifierMask = AltTab.primaryModifier(mask);
|
||||
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
|
||||
|
||||
this._appSwitcher = new WindowList(windows, this._settings);
|
||||
this.actor.add_actor(this._appSwitcher.actor);
|
||||
this._appSwitcher.connect('item-activated', Lang.bind(this, this._windowActivated));
|
||||
this._appSwitcher.connect('item-entered', Lang.bind(this, this._windowEntered));
|
||||
|
||||
// make the initial selection
|
||||
this._currentWindow = 0;
|
||||
if (backward)
|
||||
this._select(this._previousWindow());
|
||||
else
|
||||
this._select(this._nextWindow());
|
||||
|
||||
this.actor.opacity = 0;
|
||||
this.actor.show();
|
||||
|
||||
// There's a race condition; if the user released Alt before
|
||||
// we got the grab, then we won't be notified. (See
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
|
||||
// details.) So we check now. (Have to do this after updating
|
||||
// selection.)
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
if (!(mods & this._modifierMask)) {
|
||||
this._finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We delay showing the popup so that fast Alt+Tab users aren't
|
||||
// disturbed by the popup briefly flashing.
|
||||
this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT,
|
||||
Lang.bind(this, function () {
|
||||
this.actor.opacity = 255;
|
||||
this._initialDelayTimeoutId = 0;
|
||||
}));
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
_windowActivated : function(thumbnailList, n) {
|
||||
let win = this._appSwitcher.windows[n];
|
||||
Main.activateWindow(win);
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_finish : function() {
|
||||
let win = this._appSwitcher.windows[this._currentWindow];
|
||||
Main.activateWindow(win);
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_keyPressEvent : function(actor, event) {
|
||||
let keysym = event.get_key_symbol();
|
||||
let event_state = event.get_state();
|
||||
let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
|
||||
let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
|
||||
|
||||
this._disableHover();
|
||||
|
||||
if (keysym == Clutter.Escape) {
|
||||
this.destroy();
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS ||
|
||||
action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
||||
this._select(backwards ? this._previousWindow() : this._nextWindow());
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD ||
|
||||
action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
||||
this._select(this._previousWindow());
|
||||
} else {
|
||||
if (keysym == Clutter.Left)
|
||||
this._select(this._previousWindow());
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._nextWindow());
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_keyReleaseEvent : function(actor, event) {
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
let state = mods & this._modifierMask;
|
||||
|
||||
if (state == 0)
|
||||
this._finish();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onScroll : function(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
if (direction == Clutter.ScrollDirection.UP)
|
||||
this._select(this._previousWindow());
|
||||
else if (direction == Clutter.ScrollDirection.DOWN)
|
||||
this._select(this._nextWindow());
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_clickedOutside : function(actor, event) {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_windowEntered : function(windowSwitcher, n) {
|
||||
if (!this._mouseActive)
|
||||
return;
|
||||
|
||||
this._select(n);
|
||||
},
|
||||
|
||||
_disableHover : function() {
|
||||
this._mouseActive = false;
|
||||
|
||||
if (this._motionTimeoutId != 0)
|
||||
Mainloop.source_remove(this._motionTimeoutId);
|
||||
|
||||
this._motionTimeoutId = Mainloop.timeout_add(AltTab.DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
|
||||
},
|
||||
|
||||
_mouseTimedOut : function() {
|
||||
this._motionTimeoutId = 0;
|
||||
this._mouseActive = true;
|
||||
},
|
||||
|
||||
_popModal: function() {
|
||||
if (this._haveModal) {
|
||||
Main.popModal(this.actor);
|
||||
this._haveModal = false;
|
||||
}
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this._popModal();
|
||||
if (this.actor.visible) {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: AltTab.POPUP_FADE_OUT_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, this._finishDestroy),
|
||||
});
|
||||
} else
|
||||
this._finishDestroy();
|
||||
},
|
||||
|
||||
_finishDestroy : function() {
|
||||
if (this._motionTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._motionTimeoutId);
|
||||
this._motionTimeoutId = 0;
|
||||
}
|
||||
|
||||
if (this._initialDelayTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._initialDelayTimeoutId);
|
||||
this._initialDelayTimeoutId = 0;
|
||||
}
|
||||
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_select : function(window) {
|
||||
this._currentWindow = window;
|
||||
this._appSwitcher.highlight(window);
|
||||
},
|
||||
|
||||
_nextWindow: function() {
|
||||
return mod(this._currentWindow + 1, this._appSwitcher.windows.length);
|
||||
},
|
||||
|
||||
_previousWindow: function() {
|
||||
return mod(this._currentWindow - 1, this._appSwitcher.windows.length);
|
||||
},
|
||||
});
|
||||
|
||||
const WindowIcon = new Lang.Class({
|
||||
Name: 'AlternateTab.WindowIcon',
|
||||
|
||||
_init: function(window, settings) {
|
||||
this.window = window;
|
||||
this._settings = settings;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
|
||||
vertical: true });
|
||||
this.icon = null;
|
||||
this._iconBin = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||
|
||||
this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
|
||||
this.label = new St.Label({ text: window.get_title() });
|
||||
this.actor.add(this.label, { x_fill: false });
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
this.app = tracker.get_window_app(window);
|
||||
|
||||
let mutterWindow = this.window.get_compositor_private();
|
||||
let windowTexture = mutterWindow.get_texture();
|
||||
let [width, height] = windowTexture.get_size();
|
||||
let scale, size;
|
||||
|
||||
this._iconBin.destroy_all_children();
|
||||
|
||||
switch (this._settings.get_enum(SETTINGS_APP_ICON_MODE)) {
|
||||
case AppIconMode.THUMBNAIL_ONLY:
|
||||
scale = Math.min(1.0, 128 / width, 128 / height);
|
||||
size = 128;
|
||||
this.clone = new Clutter.Clone({ source: windowTexture,
|
||||
width: width * scale,
|
||||
height: height * scale,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
// usual hack for the usual bug in ClutterBinLayout...
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
this._iconBin.add_actor(this.clone);
|
||||
break;
|
||||
|
||||
case AppIconMode.BOTH:
|
||||
scale = Math.min(1.0, 128 / width, 128 / height);
|
||||
size = 128;
|
||||
this.clone = new Clutter.Clone({ source: windowTexture,
|
||||
width: width * scale,
|
||||
height: height * scale,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
// usual hack for the usual bug in ClutterBinLayout...
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
this._iconBin.add_actor(this.clone);
|
||||
|
||||
if (this.app) {
|
||||
this.appIcon = this.app.create_icon_texture(size / 2);
|
||||
this.appIcon.x_expand = this.appIcon.y_expand = true;
|
||||
this.appIcon.x_align = Clutter.ActorAlign.END;
|
||||
this.appIcon.y_align = Clutter.ActorAlign.END;
|
||||
this._iconBin.add_actor(this.appIcon);
|
||||
}
|
||||
break;
|
||||
|
||||
case AppIconMode.APP_ICON_ONLY:
|
||||
size = 96;
|
||||
if (this.app) {
|
||||
this.appIcon = this.app.create_icon_texture(size);
|
||||
this.appIcon.x_expand = this.appIcon.y_expand = true;
|
||||
} else {
|
||||
this.appIcon = new St.Icon({ icon_name: 'icon-missing',
|
||||
icon_size: size,
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
}
|
||||
this._iconBin.add_actor(this.appIcon);
|
||||
}
|
||||
|
||||
this._iconBin.set_size(size, size);
|
||||
}
|
||||
});
|
||||
|
||||
const WindowList = new Lang.Class({
|
||||
Name: 'AlternateTab.WindowList',
|
||||
Extends: AltTab.SwitcherList,
|
||||
|
||||
_init : function(windows, settings) {
|
||||
this.parent(true);
|
||||
|
||||
this.windows = windows;
|
||||
this.icons = [];
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let win = windows[i];
|
||||
let icon = new WindowIcon(win, settings);
|
||||
|
||||
this.addItem(icon.actor, icon.label);
|
||||
this.icons.push(icon);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function doAltTab(display, screen, window, binding) {
|
||||
if (Main.wm._workspaceSwitcherPopup)
|
||||
Main.wm._workspaceSwitcherPopup.actor.hide();
|
||||
|
||||
let modifiers = binding.get_modifiers()
|
||||
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||
|
||||
let popup = new AltTabPopup(settings);
|
||||
if (!popup.show(backwards, binding.get_name(), binding.get_mask()))
|
||||
popup.destroy();
|
||||
}
|
||||
|
||||
function init(metadata) {
|
||||
Convenience.initTranslations();
|
||||
settings = Convenience.getSettings();
|
||||
}
|
||||
|
||||
function enable() {
|
||||
Meta.keybindings_set_custom_handler('switch-windows', doAltTab);
|
||||
Meta.keybindings_set_custom_handler('switch-group', doAltTab);
|
||||
Meta.keybindings_set_custom_handler('switch-windows-backward', doAltTab);
|
||||
Meta.keybindings_set_custom_handler('switch-group-backward', doAltTab);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
Meta.keybindings_set_custom_handler('switch-windows', Lang.bind(Main.wm, Main.wm._startAppSwitcher));
|
||||
Meta.keybindings_set_custom_handler('switch-group', Lang.bind(Main.wm, Main.wm._startAppSwitcher));
|
||||
Meta.keybindings_set_custom_handler('switch-windows-backward', Lang.bind(Main.wm, Main.wm._startAppSwitcher));
|
||||
Meta.keybindings_set_custom_handler('switch-group-backward', Lang.bind(Main.wm, Main.wm._startAppSwitcher));
|
||||
}
|
||||
11
extensions/alternate-tab/metadata.json.in
Normal file
11
extensions/alternate-tab/metadata.json.in
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extension-id": "@extension_id@",
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "AlternateTab",
|
||||
"description": "A replacement for Alt-Tab, allows to cycle between windows and does not group by application",
|
||||
"original-authors": [ "jw@bargsten.org", "thomas.bouffon@gmail.com" ],
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<enum id="org.gnome.shell.extensions.alternate-tab.AppIconMode">
|
||||
<value value="1" nick="thumbnail_only"/>
|
||||
<value value="2" nick="app_icon_only"/>
|
||||
<value value="3" nick="both"/>
|
||||
</enum>
|
||||
<schema id="org.gnome.shell.extensions.alternate-tab" path="/org/gnome/shell/extensions/alternate-tab/">
|
||||
<key name="app-icon-mode" enum="org.gnome.shell.extensions.alternate-tab.AppIconMode">
|
||||
<default>'both'</default>
|
||||
<_summary>The application icon mode.</_summary>
|
||||
<_description>
|
||||
Configures how the windows are shown in the switcher. Valid possibilities
|
||||
are 'thumbnail-only' (shows a thumbnail of the window), 'app-icon-only'
|
||||
(shows only the application icon) or 'both'.
|
||||
</_description>
|
||||
</key>
|
||||
<key type="b" name="current-workspace-only">
|
||||
<default>false</default>
|
||||
<summary>Limit switcher to current workspace.</summary>
|
||||
<description>
|
||||
If true, only windows from the current workspace are shown in the switcher.
|
||||
Otherwise, all windows are included.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
84
extensions/alternate-tab/prefs.js
Normal file
84
extensions/alternate-tab/prefs.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
/* most of the code is borrowed from
|
||||
* > js/ui/altTab.js <
|
||||
* of the gnome-shell source code
|
||||
*/
|
||||
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = function(e) { return e };
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const SETTINGS_APP_ICON_MODE = 'app-icon-mode';
|
||||
const SETTINGS_CURRENT_WORKSPACE_ONLY = 'current-workspace-only';
|
||||
|
||||
const MODES = {
|
||||
thumbnail_only: N_("Thumbnail only"),
|
||||
app_icon_only: N_("Application icon only"),
|
||||
both: N_("Thumbnail and application icon"),
|
||||
};
|
||||
|
||||
const AltTabSettingsWidget = new GObject.Class({
|
||||
Name: 'AlternateTab.Prefs.AltTabSettingsWidget',
|
||||
GTypeName: 'AltTabSettingsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
_init : function(params) {
|
||||
this.parent(params);
|
||||
this.margin = 10;
|
||||
this.orientation = Gtk.Orientation.VERTICAL;
|
||||
|
||||
this._settings = Convenience.getSettings();
|
||||
|
||||
let presentLabel = _("Present windows as");
|
||||
this.add(new Gtk.Label({ label: presentLabel, sensitive: true,
|
||||
margin_bottom: 10, margin_top: 5 }));
|
||||
|
||||
let top = 1;
|
||||
let radio = null;
|
||||
let currentMode = this._settings.get_string(SETTINGS_APP_ICON_MODE);
|
||||
for (let mode in MODES) {
|
||||
// copy the mode variable because it has function scope, not block scope
|
||||
// so cannot be used in a closure
|
||||
let modeCapture = mode;
|
||||
let name = Gettext.gettext(MODES[mode]);
|
||||
|
||||
radio = new Gtk.RadioButton({ group: radio, label: name, valign: Gtk.Align.START });
|
||||
radio.connect('toggled', Lang.bind(this, function(widget) {
|
||||
if (widget.active)
|
||||
this._settings.set_string(SETTINGS_APP_ICON_MODE, modeCapture);
|
||||
}));
|
||||
this.add(radio);
|
||||
|
||||
if (mode == currentMode)
|
||||
radio.active = true;
|
||||
top += 1;
|
||||
}
|
||||
|
||||
let check = new Gtk.CheckButton({ label: _("Show only windows in the current workspace"),
|
||||
margin_top: 12 });
|
||||
this._settings.bind(SETTINGS_CURRENT_WORKSPACE_ONLY, check, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
this.add(check);
|
||||
},
|
||||
});
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
let widget = new AltTabSettingsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
1
extensions/alternate-tab/stylesheet.css
Normal file
1
extensions/alternate-tab/stylesheet.css
Normal file
@@ -0,0 +1 @@
|
||||
/* This extensions requires no special styling */
|
||||
4
extensions/alternative-status-menu/Makefile.am
Normal file
4
extensions/alternative-status-menu/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
EXTENSION_ID = alternative-status-menu
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
152
extensions/alternative-status-menu/extension.js
Normal file
152
extensions/alternative-status-menu/extension.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/* -*- mode: js2 - indent-tabs-mode: nil - js2-basic-offset: 4 -*- */
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const LOCK_ENABLED_KEY = 'lock-enabled';
|
||||
|
||||
let suspend_item = null;
|
||||
let hibernate_item = null;
|
||||
let poweroff_item = null;
|
||||
let suspend_signal_id = 0, hibernate_signal_id = 0;
|
||||
let settings = null;
|
||||
let setting_changed_id = 0;
|
||||
|
||||
function updateSuspend(object, pspec, item) {
|
||||
item.actor.visible = object.get_can_suspend() && settings.get_boolean('allow-suspend');
|
||||
}
|
||||
|
||||
function updateHibernate(object, pspec, item) {
|
||||
item.actor.visible = object.get_can_hibernate() && settings.get_boolean('allow-hibernate');
|
||||
}
|
||||
|
||||
function onSuspendActivate(item) {
|
||||
Main.overview.hide();
|
||||
|
||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
||||
Main.screenShield.disconnect(tmpId);
|
||||
|
||||
this._upClient.suspend_sync(null);
|
||||
}));
|
||||
|
||||
Main.screenShield.lock(true);
|
||||
} else {
|
||||
this._upClient.suspend_sync(null);
|
||||
}
|
||||
}
|
||||
|
||||
function onHibernateActivate(item) {
|
||||
Main.overview.hide();
|
||||
|
||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
||||
Main.screenShield.disconnect(tmpId);
|
||||
|
||||
this._upClient.hibernate_sync(null);
|
||||
}));
|
||||
|
||||
Main.screenShield.lock(true);
|
||||
} else {
|
||||
this._upClient.hibernate_sync(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Put your extension initialization code here
|
||||
function init(metadata) {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
function enable() {
|
||||
let statusMenu = Main.panel.statusArea.userMenu;
|
||||
|
||||
settings = Convenience.getSettings();
|
||||
|
||||
let children = statusMenu.menu._getMenuItems();
|
||||
let index = children.length;
|
||||
|
||||
/* find and destroy the old entry */
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (children[i] == statusMenu._suspendOrPowerOffItem) {
|
||||
children[i].destroy();
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* add the new entries */
|
||||
suspend_item = new PopupMenu.PopupMenuItem(_("Suspend"));
|
||||
suspend_item.connect('activate', Lang.bind(statusMenu, onSuspendActivate));
|
||||
suspend_signal_id = statusMenu._upClient.connect('notify::can-suspend', Lang.bind(statusMenu, updateSuspend, suspend_item));
|
||||
updateSuspend(statusMenu._upClient, null, suspend_item);
|
||||
|
||||
hibernate_item = new PopupMenu.PopupMenuItem(_("Hibernate"));
|
||||
hibernate_item.connect('activate', Lang.bind(statusMenu, onHibernateActivate));
|
||||
hibernate_signal_id = statusMenu._upClient.connect('notify::can-hibernate', Lang.bind(statusMenu, updateHibernate, hibernate_item));
|
||||
updateHibernate(statusMenu._upClient, null, hibernate_item);
|
||||
|
||||
poweroff_item = new PopupMenu.PopupMenuItem(_("Power Off"));
|
||||
poweroff_item.connect('activate', Lang.bind(statusMenu, function() {
|
||||
this._session.ShutdownRemote();
|
||||
}));
|
||||
|
||||
/* insert the entries at the found position */
|
||||
statusMenu.menu.addMenuItem(suspend_item, index);
|
||||
statusMenu.menu.addMenuItem(hibernate_item, index + 1);
|
||||
statusMenu.menu.addMenuItem(poweroff_item, index + 2);
|
||||
|
||||
// clear out this to avoid criticals (we don't mess with
|
||||
// updateSuspendOrPowerOff)
|
||||
statusMenu._suspendOrPowerOffItem = null;
|
||||
|
||||
setting_changed_id = settings.connect('changed', function() {
|
||||
updateSuspend(statusMenu._upClient, null, suspend_item);
|
||||
updateHibernate(statusMenu._upClient, null, hibernate_item);
|
||||
});
|
||||
}
|
||||
|
||||
function disable() {
|
||||
let statusMenu = Main.panel.statusArea.userMenu;
|
||||
|
||||
let children = statusMenu.menu._getMenuItems();
|
||||
let index = children.length;
|
||||
|
||||
/* find the index for the previously created suspend entry */
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (children[i] == suspend_item) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* disconnect signals */
|
||||
statusMenu._upClient.disconnect(suspend_signal_id);
|
||||
statusMenu._upClient.disconnect(hibernate_signal_id);
|
||||
suspend_signal_id = hibernate_signal_id = 0;
|
||||
|
||||
settings.disconnect(setting_changed_id);
|
||||
setting_changed_id = 0;
|
||||
settings = null;
|
||||
|
||||
/* destroy the entries we had created */
|
||||
suspend_item.destroy();
|
||||
hibernate_item.destroy();
|
||||
poweroff_item.destroy();
|
||||
|
||||
/* create a new suspend/poweroff entry */
|
||||
/* empty strings are fine for the labels, since we immediately call updateSuspendOrPowerOff */
|
||||
let item = new PopupMenu.PopupAlternatingMenuItem("", "");
|
||||
/* restore the userMenu field */
|
||||
statusMenu._suspendOrPowerOffItem = item;
|
||||
statusMenu.menu.addMenuItem(item, index);
|
||||
item.connect('activate', Lang.bind(statusMenu, statusMenu._onSuspendOrPowerOffActivate));
|
||||
statusMenu._updateSuspendOrPowerOff();
|
||||
}
|
||||
10
extensions/alternative-status-menu/metadata.json.in
Normal file
10
extensions/alternative-status-menu/metadata.json.in
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extension-id": "@extension_id@",
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Alternative Status Menu",
|
||||
"description": "Replaces GNOME Shell Status Menu with one showing Suspend/Hibernate and Power Off as separate items",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<schema id="org.gnome.shell.extensions.alternative-status-menu" path="/org/gnome/shell/extensions/alternative-status-menu/">
|
||||
<key name="allow-suspend" type="b">
|
||||
<default>true</default>
|
||||
<_summary>Enable suspending</_summary>
|
||||
<_description>Control the visibility of the Suspend menu item</_description>
|
||||
</key>
|
||||
<key name="allow-hibernate" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Enable hibernating</_summary>
|
||||
<_description>Control the visibility of the Hibernate menu item</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
3
extensions/alternative-status-menu/stylesheet.css
Normal file
3
extensions/alternative-status-menu/stylesheet.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.popup-alternating-menu-item:alternate {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
3
extensions/apps-menu/Makefile.am
Normal file
3
extensions/apps-menu/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = apps-menu
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,684 +1,111 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
import Atk from 'gi://Atk';
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GMenu from 'gi://GMenu';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk';
|
||||
import Meta from 'gi://Meta';
|
||||
import Shell from 'gi://Shell';
|
||||
import St from 'gi://St';
|
||||
import {EventEmitter} from 'resource:///org/gnome/shell/misc/signals.js';
|
||||
|
||||
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
const GMenu = imports.gi.GMenu;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
import * as DND from 'resource:///org/gnome/shell/ui/dnd.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 PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const appSys = Shell.AppSystem.get_default();
|
||||
const ICON_SIZE = 28;
|
||||
|
||||
const APPLICATION_ICON_SIZE = 32;
|
||||
const HORIZ_FACTOR = 5;
|
||||
const MENU_HEIGHT_OFFSET = 132;
|
||||
const NAVIGATION_REGION_OVERSHOOT = 50;
|
||||
const AppMenuItem = new Lang.Class({
|
||||
Name: 'AppsMenu.AppMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish');
|
||||
Gio._promisify(Gio._LocalFilePrototype, 'set_attributes_async', 'set_attributes_finish');
|
||||
_init: function (app, params) {
|
||||
this.parent(params);
|
||||
|
||||
class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor(button, app) {
|
||||
super();
|
||||
this._app = app;
|
||||
this._button = button;
|
||||
this.label = new St.Label({ text: app.get_name() });
|
||||
this.addActor(this.label);
|
||||
this._icon = app.create_icon_texture(ICON_SIZE);
|
||||
this.addActor(this._icon, { expand: false });
|
||||
},
|
||||
|
||||
this._iconBin = new St.Bin();
|
||||
this.add_child(this._iconBin);
|
||||
activate: function (event) {
|
||||
this._app.activate_full(-1, event.get_time());
|
||||
|
||||
let appLabel = new St.Label({
|
||||
text: app.get_name(),
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_child(appLabel);
|
||||
this.label_actor = appLabel;
|
||||
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
textureCache.connectObject('icon-theme-changed',
|
||||
() => this._updateIcon(), this);
|
||||
this._updateIcon();
|
||||
|
||||
this._delegate = this;
|
||||
let draggable = DND.makeDraggable(this);
|
||||
|
||||
let maybeStartDrag = draggable._maybeStartDrag;
|
||||
draggable._maybeStartDrag = event => {
|
||||
if (this._dragEnabled)
|
||||
return maybeStartDrag.call(draggable, event);
|
||||
return false;
|
||||
};
|
||||
this.parent(event);
|
||||
}
|
||||
|
||||
activate(event) {
|
||||
this._app.open_new_window(-1);
|
||||
this._button.selectCategory(null);
|
||||
this._button.menu.toggle();
|
||||
super.activate(event);
|
||||
});
|
||||
|
||||
Main.overview.hide();
|
||||
}
|
||||
const ApplicationsButton = new Lang.Class({
|
||||
Name: 'AppsMenu.ApplicationsButton',
|
||||
Extends: PanelMenu.SystemStatusButton,
|
||||
|
||||
setActive(active, params) {
|
||||
if (active)
|
||||
this._button.scrollToButton(this);
|
||||
super.setActive(active, params);
|
||||
}
|
||||
_init: function() {
|
||||
this.parent('start-here-symbolic');
|
||||
|
||||
setDragEnabled(enabled) {
|
||||
this._dragEnabled = enabled;
|
||||
}
|
||||
this._appSys = Shell.AppSystem.get_default();
|
||||
this._installedChangedId = this._appSys.connect('installed-changed', Lang.bind(this, this._refresh));
|
||||
|
||||
getDragActor() {
|
||||
return this._app.create_icon_texture(APPLICATION_ICON_SIZE);
|
||||
}
|
||||
|
||||
getDragActorSource() {
|
||||
return this._iconBin;
|
||||
}
|
||||
|
||||
_updateIcon() {
|
||||
let icon = this.getDragActor();
|
||||
icon.style_class = 'icon-dropshadow';
|
||||
this._iconBin.set_child(icon);
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor(button, category) {
|
||||
super();
|
||||
this._category = category;
|
||||
this._button = button;
|
||||
|
||||
this._oldX = -1;
|
||||
this._oldY = -1;
|
||||
|
||||
let name;
|
||||
if (this._category)
|
||||
name = this._category.get_name();
|
||||
else
|
||||
name = _('Favorites');
|
||||
|
||||
this.add_child(new St.Label({text: name}));
|
||||
this.connect('motion-event', this._onMotionEvent.bind(this));
|
||||
this.connect('notify::active', this._onActiveChanged.bind(this));
|
||||
}
|
||||
|
||||
activate(event) {
|
||||
this._button.selectCategory(this._category);
|
||||
this._button.scrollToCatButton(this);
|
||||
super.activate(event);
|
||||
}
|
||||
|
||||
_isNavigatingSubmenu([x, y]) {
|
||||
let [posX, posY] = this.get_transformed_position();
|
||||
|
||||
if (this._oldX === -1) {
|
||||
this._oldX = x;
|
||||
this._oldY = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
let deltaX = Math.abs(x - this._oldX);
|
||||
let deltaY = Math.abs(y - this._oldY);
|
||||
|
||||
this._oldX = x;
|
||||
this._oldY = y;
|
||||
|
||||
// If it lies outside the x-coordinates then it is definitely outside.
|
||||
if (posX > x || posX + this.width < x)
|
||||
return false;
|
||||
|
||||
// If it lies inside the menu item then it is definitely inside.
|
||||
if (posY <= y && posY + this.height >= y)
|
||||
return true;
|
||||
|
||||
// We want the keep-up triangle only if the movement is more
|
||||
// horizontal than vertical.
|
||||
if (deltaX * HORIZ_FACTOR < deltaY)
|
||||
return false;
|
||||
|
||||
// Check whether the point lies inside triangle ABC, and a similar
|
||||
// triangle on the other side of the menu item.
|
||||
//
|
||||
// +---------------------+
|
||||
// | menu item |
|
||||
// A +---------------------+ C
|
||||
// P |
|
||||
// B
|
||||
|
||||
// Ensure that the point P always lies below line AC so that we can
|
||||
// only check for triangle ABC.
|
||||
if (posY > y) {
|
||||
let offset = posY - y;
|
||||
y = posY + this.height + offset;
|
||||
}
|
||||
|
||||
// Ensure that A is (0, 0).
|
||||
x -= posX;
|
||||
y -= posY + this.height;
|
||||
|
||||
// Check which side of line AB the point P lies on by taking the
|
||||
// cross-product of AB and AP. See:
|
||||
// http://stackoverflow.com/questions/3461453/determine-which-side-of-a-line-a-point-lies
|
||||
if (this.width * y - NAVIGATION_REGION_OVERSHOOT * x <= 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onMotionEvent(actor, event) {
|
||||
if (!this._grab) {
|
||||
this._oldX = -1;
|
||||
this._oldY = -1;
|
||||
const grab = global.stage.grab(this);
|
||||
if (grab.get_seat_state() !== Clutter.GrabState.NONE)
|
||||
this._grab = grab;
|
||||
else
|
||||
grab.dismiss();
|
||||
}
|
||||
this.hover = true;
|
||||
|
||||
if (this._isNavigatingSubmenu(event.get_coords()))
|
||||
return true;
|
||||
|
||||
this._oldX = -1;
|
||||
this._oldY = -1;
|
||||
this.hover = false;
|
||||
this._grab?.dismiss();
|
||||
delete this._grab;
|
||||
|
||||
let source = event.get_source();
|
||||
if (source instanceof St.Widget)
|
||||
source.sync_hover();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onActiveChanged() {
|
||||
if (!this.active)
|
||||
return;
|
||||
|
||||
this._button.selectCategory(this._category);
|
||||
this._button.scrollToCatButton(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationsMenu extends PopupMenu.PopupMenu {
|
||||
constructor(sourceActor, arrowAlignment, arrowSide, button) {
|
||||
super(sourceActor, arrowAlignment, arrowSide);
|
||||
this._button = button;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isOpen)
|
||||
this._button.selectCategory(null);
|
||||
super.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopTarget extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._desktop = null;
|
||||
this._desktopDestroyedId = 0;
|
||||
|
||||
this._windowAddedId =
|
||||
global.window_group.connect('actor-added',
|
||||
this._onWindowAdded.bind(this));
|
||||
|
||||
global.get_window_actors().forEach(a => {
|
||||
this._onWindowAdded(a.get_parent(), a);
|
||||
});
|
||||
}
|
||||
|
||||
get hasDesktop() {
|
||||
return this._desktop !== null;
|
||||
}
|
||||
|
||||
_onWindowAdded(group, actor) {
|
||||
if (!(actor instanceof Meta.WindowActor))
|
||||
return;
|
||||
|
||||
if (actor.meta_window.get_window_type() === Meta.WindowType.DESKTOP)
|
||||
this._setDesktop(actor);
|
||||
}
|
||||
|
||||
_setDesktop(desktop) {
|
||||
if (this._desktop) {
|
||||
this._desktop.disconnectObject(this);
|
||||
delete this._desktop._delegate;
|
||||
}
|
||||
|
||||
this._desktop = desktop;
|
||||
this.emit('desktop-changed');
|
||||
|
||||
if (this._desktop) {
|
||||
this._desktop.connectObject('destroy', () => {
|
||||
this._setDesktop(null);
|
||||
}, this);
|
||||
this._desktop._delegate = this;
|
||||
}
|
||||
}
|
||||
|
||||
_getSourceAppInfo(source) {
|
||||
if (!(source instanceof ApplicationMenuItem))
|
||||
return null;
|
||||
return source._app.app_info;
|
||||
}
|
||||
|
||||
async _markTrusted(file) {
|
||||
let modeAttr = Gio.FILE_ATTRIBUTE_UNIX_MODE;
|
||||
let trustedAttr = 'metadata::trusted';
|
||||
let queryFlags = Gio.FileQueryInfoFlags.NONE;
|
||||
let ioPriority = GLib.PRIORITY_DEFAULT;
|
||||
|
||||
try {
|
||||
let info = await file.query_info_async(modeAttr, queryFlags, ioPriority, null);
|
||||
|
||||
let mode = info.get_attribute_uint32(modeAttr) | 0o100;
|
||||
info.set_attribute_uint32(modeAttr, mode);
|
||||
info.set_attribute_string(trustedAttr, 'yes');
|
||||
await file.set_attributes_async(info, queryFlags, ioPriority, null);
|
||||
|
||||
// Hack: force nautilus to reload file info
|
||||
info = new Gio.FileInfo();
|
||||
info.set_attribute_uint64(
|
||||
Gio.FILE_ATTRIBUTE_TIME_ACCESS, GLib.get_real_time());
|
||||
try {
|
||||
await file.set_attributes_async(info, queryFlags, ioPriority, null);
|
||||
} catch (e) {
|
||||
log(`Failed to update access time: ${e.message}`);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`Failed to mark file as trusted: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
global.window_group.disconnectObject(this);
|
||||
this._setDesktop(null);
|
||||
}
|
||||
|
||||
handleDragOver(source, _actor, _x, _y, _time) {
|
||||
let appInfo = this._getSourceAppInfo(source);
|
||||
if (!appInfo)
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
|
||||
return DND.DragMotionResult.COPY_DROP;
|
||||
}
|
||||
|
||||
acceptDrop(source, _actor, _x, _y, _time) {
|
||||
let appInfo = this._getSourceAppInfo(source);
|
||||
if (!appInfo)
|
||||
return false;
|
||||
|
||||
this.emit('app-dropped');
|
||||
|
||||
let desktop = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
|
||||
|
||||
let src = Gio.File.new_for_path(appInfo.get_filename());
|
||||
let dst = Gio.File.new_for_path(GLib.build_filenamev([desktop, src.get_basename()]));
|
||||
|
||||
try {
|
||||
// copy_async() isn't introspectable :-(
|
||||
src.copy(dst, Gio.FileCopyFlags.OVERWRITE, null, null);
|
||||
this._markTrusted(dst);
|
||||
} catch (e) {
|
||||
log(`Failed to copy to desktop: ${e.message}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationsButton extends PanelMenu.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(1.0, null, false);
|
||||
|
||||
this.setMenu(new ApplicationsMenu(this, 1.0, St.Side.TOP, this));
|
||||
Main.panel.menuManager.addMenu(this.menu);
|
||||
|
||||
// At this moment applications menu is not keyboard navigable at
|
||||
// all (so not accessible), so it doesn't make sense to set as
|
||||
// role ATK_ROLE_MENU like other elements of the panel.
|
||||
this.accessible_role = Atk.Role.LABEL;
|
||||
|
||||
this._label = new St.Label({
|
||||
text: _('Applications'),
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
|
||||
this.add_actor(this._label);
|
||||
this.name = 'panelApplications';
|
||||
this.label_actor = this._label;
|
||||
|
||||
Main.overview.connectObject(
|
||||
'showing', () => this.add_accessible_state(Atk.StateType.CHECKED),
|
||||
'hiding', () => this.remove_accessible_state(Atk.StateType.CHECKED),
|
||||
this);
|
||||
|
||||
Main.wm.addKeybinding(
|
||||
'apps-menu-toggle-menu',
|
||||
Extension.lookupByURL(import.meta.url).getSettings(),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
|
||||
() => this.menu.toggle());
|
||||
|
||||
this._desktopTarget = new DesktopTarget();
|
||||
this._desktopTarget.connect('app-dropped', () => {
|
||||
this.menu.close();
|
||||
});
|
||||
this._desktopTarget.connect('desktop-changed', () => {
|
||||
this._applicationsButtons.forEach(item => {
|
||||
item.setDragEnabled(this._desktopTarget.hasDesktop);
|
||||
});
|
||||
});
|
||||
|
||||
this._tree = new GMenu.Tree({menu_basename: 'applications.menu'});
|
||||
this._tree.connectObject('changed',
|
||||
() => this._onTreeChanged(), this);
|
||||
|
||||
this._applicationsButtons = new Map();
|
||||
this.reloadFlag = false;
|
||||
this._createLayout();
|
||||
this._display();
|
||||
appSys.connectObject('installed-changed',
|
||||
() => this._onTreeChanged(), this);
|
||||
}
|
||||
},
|
||||
|
||||
_onTreeChanged() {
|
||||
if (this.menu.isOpen) {
|
||||
this._redisplay();
|
||||
this.mainBox.show();
|
||||
} else {
|
||||
this.reloadFlag = true;
|
||||
}
|
||||
}
|
||||
destroy: function() {
|
||||
this._appSys.disconnect(this._installedChangedId);
|
||||
|
||||
_createVertSeparator() {
|
||||
let separator = new St.DrawingArea({
|
||||
style_class: 'calendar-vertical-separator',
|
||||
pseudo_class: 'highlighted',
|
||||
});
|
||||
separator.connect('repaint', this._onVertSepRepaint.bind(this));
|
||||
return separator;
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
super._onDestroy();
|
||||
|
||||
delete this._tree;
|
||||
|
||||
Main.wm.removeKeybinding('apps-menu-toggle-menu');
|
||||
|
||||
this._desktopTarget.destroy();
|
||||
}
|
||||
|
||||
_onMenuKeyPress(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol === Clutter.KEY_Left || symbol === Clutter.KEY_Right) {
|
||||
let direction = symbol === Clutter.KEY_Left
|
||||
? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
|
||||
if (this.menu.actor.navigate_focus(global.stage.key_focus, direction, false))
|
||||
return true;
|
||||
}
|
||||
return super._onMenuKeyPress(actor, event);
|
||||
}
|
||||
|
||||
_onVertSepRepaint(area) {
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let stippleColor = themeNode.get_color('-stipple-color');
|
||||
let stippleWidth = themeNode.get_length('-stipple-width');
|
||||
let x = Math.floor(width / 2) + 0.5;
|
||||
cr.moveTo(x, 0);
|
||||
cr.lineTo(x, height);
|
||||
Clutter.cairo_set_source_color(cr, stippleColor);
|
||||
cr.setDash([1, 3], 1); // Hard-code for now
|
||||
cr.setLineWidth(stippleWidth);
|
||||
cr.stroke();
|
||||
}
|
||||
|
||||
_onOpenStateChanged(menu, open) {
|
||||
if (open) {
|
||||
if (this.reloadFlag) {
|
||||
this._redisplay();
|
||||
this.reloadFlag = false;
|
||||
}
|
||||
this.mainBox.show();
|
||||
}
|
||||
super._onOpenStateChanged(menu, open);
|
||||
}
|
||||
|
||||
_redisplay() {
|
||||
this.applicationsBox.destroy_all_children();
|
||||
this.categoriesBox.destroy_all_children();
|
||||
_refresh: function() {
|
||||
this._clearAll();
|
||||
this._display();
|
||||
}
|
||||
},
|
||||
|
||||
_loadCategory(categoryId, dir) {
|
||||
let iter = dir.iter();
|
||||
let nextType;
|
||||
while ((nextType = iter.next()) !== GMenu.TreeItemType.INVALID) {
|
||||
if (nextType === GMenu.TreeItemType.ENTRY) {
|
||||
let entry = iter.get_entry();
|
||||
let id;
|
||||
try {
|
||||
id = entry.get_desktop_file_id(); // catch non-UTF8 filenames
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
let app = appSys.lookup_app(id);
|
||||
if (!app)
|
||||
app = new Shell.App({app_info: entry.get_app_info()});
|
||||
if (app.get_app_info().should_show())
|
||||
this.applicationsByCategory[categoryId].push(app);
|
||||
} else if (nextType === GMenu.TreeItemType.SEPARATOR) {
|
||||
this.applicationsByCategory[categoryId].push('separator');
|
||||
} else if (nextType === GMenu.TreeItemType.DIRECTORY) {
|
||||
let subdir = iter.get_directory();
|
||||
if (!subdir.get_is_nodisplay())
|
||||
this._loadCategory(categoryId, subdir);
|
||||
_clearAll: function() {
|
||||
this.menu.removeAll();
|
||||
},
|
||||
|
||||
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
|
||||
// (taken from js/ui/appDisplay.js in core shell)
|
||||
_loadCategory: function(dir, menu) {
|
||||
var iter = dir.iter();
|
||||
var nextType;
|
||||
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
||||
if (nextType == GMenu.TreeItemType.ENTRY) {
|
||||
var entry = iter.get_entry();
|
||||
var app = this._appSys.lookup_app_by_tree_entry(entry);
|
||||
if (!entry.get_app_info().get_nodisplay())
|
||||
menu.addMenuItem(new AppMenuItem(app));
|
||||
} else if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
||||
this._loadCategory(iter.get_directory(), menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
scrollToButton(button) {
|
||||
let appsScrollBoxAdj = this.applicationsScrollBox.get_vscroll_bar().get_adjustment();
|
||||
let appsScrollBoxAlloc = this.applicationsScrollBox.get_allocation_box();
|
||||
let currentScrollValue = appsScrollBoxAdj.get_value();
|
||||
let boxHeight = appsScrollBoxAlloc.y2 - appsScrollBoxAlloc.y1;
|
||||
let buttonAlloc = button.get_allocation_box();
|
||||
let newScrollValue = currentScrollValue;
|
||||
if (currentScrollValue > buttonAlloc.y1 - 10)
|
||||
newScrollValue = buttonAlloc.y1 - 10;
|
||||
if (boxHeight + currentScrollValue < buttonAlloc.y2 + 10)
|
||||
newScrollValue = buttonAlloc.y2 - boxHeight + 10;
|
||||
if (newScrollValue !== currentScrollValue)
|
||||
appsScrollBoxAdj.set_value(newScrollValue);
|
||||
}
|
||||
_display : function() {
|
||||
let tree = this._appSys.get_tree();
|
||||
let root = tree.get_root_directory();
|
||||
|
||||
scrollToCatButton(button) {
|
||||
let catsScrollBoxAdj = this.categoriesScrollBox.get_vscroll_bar().get_adjustment();
|
||||
let catsScrollBoxAlloc = this.categoriesScrollBox.get_allocation_box();
|
||||
let currentScrollValue = catsScrollBoxAdj.get_value();
|
||||
let boxHeight = catsScrollBoxAlloc.y2 - catsScrollBoxAlloc.y1;
|
||||
let buttonAlloc = button.get_allocation_box();
|
||||
let newScrollValue = currentScrollValue;
|
||||
if (currentScrollValue > buttonAlloc.y1 - 10)
|
||||
newScrollValue = buttonAlloc.y1 - 10;
|
||||
if (boxHeight + currentScrollValue < buttonAlloc.y2 + 10)
|
||||
newScrollValue = buttonAlloc.y2 - boxHeight + 10;
|
||||
if (newScrollValue !== currentScrollValue)
|
||||
catsScrollBoxAdj.set_value(newScrollValue);
|
||||
}
|
||||
|
||||
_createLayout() {
|
||||
let section = new PopupMenu.PopupMenuSection();
|
||||
this.menu.addMenuItem(section);
|
||||
this.mainBox = new St.BoxLayout({vertical: false});
|
||||
this.leftBox = new St.BoxLayout({vertical: true});
|
||||
this.applicationsScrollBox = new St.ScrollView({
|
||||
style_class: 'apps-menu vfade',
|
||||
x_expand: true,
|
||||
});
|
||||
this.applicationsScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
let vscroll = this.applicationsScrollBox.get_vscroll_bar();
|
||||
vscroll.connect('scroll-start', () => {
|
||||
this.menu.passEvents = true;
|
||||
});
|
||||
vscroll.connect('scroll-stop', () => {
|
||||
this.menu.passEvents = false;
|
||||
});
|
||||
this.categoriesScrollBox = new St.ScrollView({
|
||||
style_class: 'vfade',
|
||||
});
|
||||
this.categoriesScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
vscroll = this.categoriesScrollBox.get_vscroll_bar();
|
||||
vscroll.connect('scroll-start', () => (this.menu.passEvents = true));
|
||||
vscroll.connect('scroll-stop', () => (this.menu.passEvents = false));
|
||||
this.leftBox.add_child(this.categoriesScrollBox);
|
||||
|
||||
this.applicationsBox = new St.BoxLayout({vertical: true});
|
||||
this.applicationsScrollBox.add_actor(this.applicationsBox);
|
||||
this.categoriesBox = new St.BoxLayout({vertical: true});
|
||||
this.categoriesScrollBox.add_actor(this.categoriesBox);
|
||||
|
||||
this.mainBox.add(this.leftBox);
|
||||
this.mainBox.add_child(this._createVertSeparator());
|
||||
this.mainBox.add_child(this.applicationsScrollBox);
|
||||
section.actor.add_actor(this.mainBox);
|
||||
}
|
||||
|
||||
_display() {
|
||||
this._applicationsButtons.clear();
|
||||
this.mainBox.style = 'width: 35em;';
|
||||
this.mainBox.hide();
|
||||
|
||||
// Load categories
|
||||
this.applicationsByCategory = {};
|
||||
this._tree.load_sync();
|
||||
let root = this._tree.get_root_directory();
|
||||
let categoryMenuItem = new CategoryMenuItem(this, null);
|
||||
this.categoriesBox.add_actor(categoryMenuItem);
|
||||
let iter = root.iter();
|
||||
let nextType;
|
||||
while ((nextType = iter.next()) !== GMenu.TreeItemType.INVALID) {
|
||||
if (nextType !== GMenu.TreeItemType.DIRECTORY)
|
||||
continue;
|
||||
|
||||
let dir = iter.get_directory();
|
||||
if (dir.get_is_nodisplay())
|
||||
continue;
|
||||
|
||||
let categoryId = dir.get_menu_id();
|
||||
this.applicationsByCategory[categoryId] = [];
|
||||
this._loadCategory(categoryId, dir);
|
||||
if (this.applicationsByCategory[categoryId].length > 0) {
|
||||
categoryMenuItem = new CategoryMenuItem(this, dir);
|
||||
this.categoriesBox.add_actor(categoryMenuItem);
|
||||
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
||||
if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
||||
let dir = iter.get_directory();
|
||||
let item = new PopupMenu.PopupSubMenuMenuItem(dir.get_name());
|
||||
this._loadCategory(dir, item.menu);
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Load applications
|
||||
this._displayButtons(this._listApplications(null));
|
||||
|
||||
let themeContext = St.ThemeContext.get_for_stage(global.stage);
|
||||
let scaleFactor = themeContext.scale_factor;
|
||||
let categoriesHeight = this.categoriesBox.height / scaleFactor;
|
||||
let height = Math.round(categoriesHeight) + MENU_HEIGHT_OFFSET;
|
||||
this.mainBox.style += `height: ${height}px`;
|
||||
}
|
||||
});
|
||||
|
||||
selectCategory(dir) {
|
||||
this.applicationsBox.get_children().forEach(c => {
|
||||
if (c._delegate instanceof PopupMenu.PopupSeparatorMenuItem)
|
||||
c._delegate.destroy();
|
||||
else
|
||||
this.applicationsBox.remove_actor(c);
|
||||
});
|
||||
let appsMenuButton;
|
||||
|
||||
if (dir)
|
||||
this._displayButtons(this._listApplications(dir.get_menu_id()));
|
||||
else
|
||||
this._displayButtons(this._listApplications(null));
|
||||
}
|
||||
|
||||
_displayButtons(apps) {
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i];
|
||||
let item;
|
||||
if (app instanceof Shell.App)
|
||||
item = this._applicationsButtons.get(app);
|
||||
else
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
if (!item) {
|
||||
item = new ApplicationMenuItem(this, app);
|
||||
item.setDragEnabled(this._desktopTarget.hasDesktop);
|
||||
this._applicationsButtons.set(app, item);
|
||||
}
|
||||
if (!item.get_parent())
|
||||
this.applicationsBox.add_actor(item);
|
||||
}
|
||||
}
|
||||
|
||||
_listApplications(categoryMenuId) {
|
||||
let applist;
|
||||
|
||||
if (categoryMenuId) {
|
||||
applist = this.applicationsByCategory[categoryMenuId];
|
||||
} else {
|
||||
applist = global.settings.get_strv('favorite-apps')
|
||||
.map(id => appSys.lookup_app(id))
|
||||
.filter(app => app);
|
||||
}
|
||||
|
||||
return applist;
|
||||
}
|
||||
function enable() {
|
||||
appsMenuButton = new ApplicationsButton();
|
||||
Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left');
|
||||
}
|
||||
|
||||
export default class AppsMenuExtension extends Extension {
|
||||
enable() {
|
||||
this._appsMenuButton = new ApplicationsButton();
|
||||
const index = Main.sessionMode.panel.left.indexOf('activities') + 1;
|
||||
Main.panel.addToStatusArea(
|
||||
'apps-menu', this._appsMenuButton, index, 'left');
|
||||
}
|
||||
|
||||
disable() {
|
||||
Main.panel.menuManager.removeMenu(this._appsMenuButton.menu);
|
||||
this._appsMenuButton.destroy();
|
||||
delete this._appsMenuButton;
|
||||
}
|
||||
function disable() {
|
||||
appsMenuButton.destroy();
|
||||
}
|
||||
|
||||
function init() {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
extension_data += files('stylesheet.css')
|
||||
extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
@@ -4,8 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Applications Menu",
|
||||
"description": "Add a category-based menu for applications.\nThis extension is part of Classic Mode and is officially supported by GNOME. Please do not report bugs using the form below, use GNOME's GitLab instance instead.",
|
||||
"original-authors": [ "e2002@bk.ru", "debarshir@gnome.org" ],
|
||||
"description": "Add a gnome 2.x style menu for applications",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<schema id="org.gnome.shell.extensions.apps-menu"
|
||||
path="/org/gnome/shell/extensions/apps-menu/">
|
||||
<key name="apps-menu-toggle-menu" type="as">
|
||||
<default>["<Alt>F1"]</default>
|
||||
<summary>Keybinding to open the applications menu</summary>
|
||||
<description>
|
||||
Keybinding to open the applications menu.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
@@ -1,7 +1 @@
|
||||
.apps-menu:ltr {
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
.apps-menu:rtl {
|
||||
padding-left: 3px;
|
||||
}
|
||||
/* none used*/
|
||||
|
||||
7
extensions/auto-move-windows/Makefile.am
Normal file
7
extensions/auto-move-windows/Makefile.am
Normal file
@@ -0,0 +1,7 @@
|
||||
EXTENSION_ID = auto-move-windows
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
|
||||
@@ -1,136 +1,167 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Start apps on custom workspaces
|
||||
|
||||
import Shell from 'gi://Shell';
|
||||
const Glib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
const Main = imports.ui.main;
|
||||
|
||||
class WindowMover {
|
||||
constructor(settings) {
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const SETTINGS_KEY = 'application-list';
|
||||
|
||||
let settings;
|
||||
|
||||
const WindowMover = new Lang.Class({
|
||||
Name: 'AutoMoveWindows.WindowMover',
|
||||
|
||||
_init: function() {
|
||||
this._settings = settings;
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
this._appConfigs = new Map();
|
||||
this._appData = new Map();
|
||||
this._windowTracker = Shell.WindowTracker.get_default();
|
||||
|
||||
this._appSystem.connectObject('installed-changed',
|
||||
() => this._updateAppData(), this);
|
||||
let display = global.screen.get_display();
|
||||
// Connect after so the handler from ShellWindowTracker has already run
|
||||
this._windowCreatedId = display.connect_after('window-created', Lang.bind(this, this._findAndMove));
|
||||
},
|
||||
|
||||
this._settings.connect('changed', this._updateAppConfigs.bind(this));
|
||||
this._updateAppConfigs();
|
||||
}
|
||||
|
||||
_updateAppConfigs() {
|
||||
this._appConfigs.clear();
|
||||
|
||||
this._settings.get_strv('application-list').forEach(v => {
|
||||
let [appId, num] = v.split(':');
|
||||
this._appConfigs.set(appId, parseInt(num) - 1);
|
||||
});
|
||||
|
||||
this._updateAppData();
|
||||
}
|
||||
|
||||
_updateAppData() {
|
||||
let ids = [...this._appConfigs.keys()];
|
||||
let removedApps = [...this._appData.keys()]
|
||||
.filter(a => !ids.includes(a.id));
|
||||
removedApps.forEach(app => {
|
||||
app.disconnectObject(this);
|
||||
this._appData.delete(app);
|
||||
});
|
||||
|
||||
let addedApps = ids
|
||||
.map(id => this._appSystem.lookup_app(id))
|
||||
.filter(app => app && !this._appData.has(app));
|
||||
addedApps.forEach(app => {
|
||||
app.connectObject('window-changed',
|
||||
this._appWindowsChanged.bind(this), this);
|
||||
this._appData.set(app, {windows: app.get_windows()});
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._appSystem.disconnectObject(this);
|
||||
|
||||
if (this._settings) {
|
||||
this._settings.run_dispose();
|
||||
this._settings = null;
|
||||
destroy: function() {
|
||||
if (this._windowCreatedId) {
|
||||
global.screen.get_display().disconnect(this._windowCreatedId);
|
||||
this._windowCreatedId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
this._appConfigs.clear();
|
||||
this._updateAppData();
|
||||
}
|
||||
_ensureAtLeastWorkspaces: function(num, window) {
|
||||
for (let j = global.screen.n_workspaces; j <= num; j++) {
|
||||
window.change_workspace_by_index(j-1, false, global.get_current_time());
|
||||
global.screen.append_new_workspace(false, 0);
|
||||
}
|
||||
},
|
||||
|
||||
_moveWindow(window, workspaceNum) {
|
||||
if (window.skip_taskbar || window.is_on_all_workspaces())
|
||||
_findAndMove: function(display, window, noRecurse) {
|
||||
if (!this._windowTracker.is_window_interesting(window))
|
||||
return;
|
||||
|
||||
// ensure we have the required number of workspaces
|
||||
let workspaceManager = global.workspace_manager;
|
||||
for (let i = workspaceManager.n_workspaces; i <= workspaceNum; i++) {
|
||||
window.change_workspace_by_index(i - 1, false);
|
||||
workspaceManager.append_new_workspace(false, 0);
|
||||
let spaces = this._settings.get_strv(SETTINGS_KEY);
|
||||
|
||||
let app = this._windowTracker.get_window_app(window);
|
||||
if (!app) {
|
||||
if (!noRecurse) {
|
||||
// window is not tracked yet
|
||||
Mainloop.idle_add(Lang.bind(this, function() {
|
||||
this._findAndMove(display, window, true);
|
||||
return false;
|
||||
}));
|
||||
} else
|
||||
log ('Cannot find application for window');
|
||||
return;
|
||||
}
|
||||
let app_id = app.get_id();
|
||||
for ( let j = 0 ; j < spaces.length; j++ ) {
|
||||
let apps_to_space = spaces[j].split(":");
|
||||
// Match application id
|
||||
if (apps_to_space[0] == app_id) {
|
||||
let workspace_num = parseInt(apps_to_space[1]) - 1;
|
||||
|
||||
window.change_workspace_by_index(workspaceNum, false);
|
||||
}
|
||||
if (workspace_num >= global.screen.n_workspaces)
|
||||
this._ensureAtLeastWorkspaces(workspace_num, window);
|
||||
|
||||
_appWindowsChanged(app) {
|
||||
let data = this._appData.get(app);
|
||||
let windows = app.get_windows();
|
||||
|
||||
// If get_compositor_private() returns non-NULL on a removed windows,
|
||||
// the window still exists and is just moved to a different workspace
|
||||
// or something; assume it'll be added back immediately, so keep it
|
||||
// to avoid moving it again
|
||||
windows.push(...data.windows.filter(w => {
|
||||
return !windows.includes(w) && w.get_compositor_private() !== null;
|
||||
}));
|
||||
|
||||
let workspaceNum = this._appConfigs.get(app.id);
|
||||
windows.filter(w => !data.windows.includes(w)).forEach(window => {
|
||||
this._moveWindow(window, workspaceNum);
|
||||
});
|
||||
data.windows = windows;
|
||||
}
|
||||
}
|
||||
|
||||
export default class AutoMoveExtension extends Extension {
|
||||
enable() {
|
||||
this._prevCheckWorkspaces = Main.wm._workspaceTracker._checkWorkspaces;
|
||||
Main.wm._workspaceTracker._checkWorkspaces =
|
||||
this._getCheckWorkspaceOverride(this._prevCheckWorkspaces);
|
||||
this._windowMover = new WindowMover(this.getSettings());
|
||||
}
|
||||
|
||||
disable() {
|
||||
Main.wm._workspaceTracker._checkWorkspaces = this._prevCheckWorkspaces;
|
||||
this._windowMover.destroy();
|
||||
delete this._windowMover;
|
||||
}
|
||||
|
||||
_getCheckWorkspaceOverride(originalMethod) {
|
||||
/* eslint-disable no-invalid-this */
|
||||
return function () {
|
||||
const keepAliveWorkspaces = [];
|
||||
let foundNonEmpty = false;
|
||||
for (let i = this._workspaces.length - 1; i >= 0; i--) {
|
||||
if (!foundNonEmpty) {
|
||||
foundNonEmpty = this._workspaces[i].list_windows().some(
|
||||
w => !w.is_on_all_workspaces());
|
||||
} else if (!this._workspaces[i]._keepAliveId) {
|
||||
keepAliveWorkspaces.push(this._workspaces[i]);
|
||||
}
|
||||
window.change_workspace_by_index(workspace_num, false, global.get_current_time());
|
||||
}
|
||||
|
||||
// make sure the original method only removes empty workspaces at the end
|
||||
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1));
|
||||
originalMethod.call(this);
|
||||
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId);
|
||||
|
||||
return false;
|
||||
};
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let prevCheckWorkspaces;
|
||||
let winMover;
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
settings = Convenience.getSettings();
|
||||
}
|
||||
|
||||
function myCheckWorkspaces() {
|
||||
let i;
|
||||
let emptyWorkspaces = new Array(Main._workspaces.length);
|
||||
|
||||
for (i = 0; i < Main._workspaces.length; i++) {
|
||||
let lastRemoved = Main._workspaces[i]._lastRemovedWindow;
|
||||
if (lastRemoved &&
|
||||
(lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
|
||||
lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
|
||||
lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG))
|
||||
emptyWorkspaces[i] = false;
|
||||
else
|
||||
emptyWorkspaces[i] = true;
|
||||
}
|
||||
|
||||
let windows = global.get_window_actors();
|
||||
for (i = 0; i < windows.length; i++) {
|
||||
let win = windows[i];
|
||||
|
||||
if (win.get_meta_window().is_on_all_workspaces())
|
||||
continue;
|
||||
|
||||
let workspaceIndex = win.get_workspace();
|
||||
emptyWorkspaces[workspaceIndex] = false;
|
||||
}
|
||||
|
||||
// If we don't have an empty workspace at the end, add one
|
||||
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
|
||||
global.screen.append_new_workspace(false, global.get_current_time());
|
||||
emptyWorkspaces.push(false);
|
||||
}
|
||||
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
let activeIsLast = activeWorkspaceIndex == global.screen.n_workspaces - 2;
|
||||
let removingTrailWorkspaces = (emptyWorkspaces[activeWorkspaceIndex] &&
|
||||
activeIsLast);
|
||||
// Don't enter the overview when removing multiple empty workspaces at startup
|
||||
let showOverview = (removingTrailWorkspaces &&
|
||||
!emptyWorkspaces.every(function(x) { return x; }));
|
||||
|
||||
if (removingTrailWorkspaces) {
|
||||
// "Merge" the empty workspace we are removing with the one at the end
|
||||
Main.wm.blockAnimations();
|
||||
}
|
||||
|
||||
// Delete other empty workspaces; do it from the end to avoid index changes
|
||||
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
|
||||
if (emptyWorkspaces[i])
|
||||
global.screen.remove_workspace(Main._workspaces[i], global.get_current_time());
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (removingTrailWorkspaces) {
|
||||
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
|
||||
|
||||
Main.wm.unblockAnimations();
|
||||
|
||||
if (!Main.overview.visible && showOverview)
|
||||
Main.overview.show();
|
||||
}
|
||||
|
||||
Main._checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
function enable() {
|
||||
prevCheckWorkspaces = Main._checkWorkspaces;
|
||||
if (Meta.prefs_get_dynamic_workspaces())
|
||||
Main._checkWorkspaces = myCheckWorkspaces;
|
||||
|
||||
winMover = new WindowMover();
|
||||
}
|
||||
|
||||
function disable() {
|
||||
Main._checkWorkspaces = prevCheckWorkspaces;
|
||||
winMover.destroy();
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
extension_sources += files('prefs.js')
|
||||
extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
@@ -4,7 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Auto Move Windows",
|
||||
"description": "Move applications to specific workspaces when they create windows.",
|
||||
"description": "Move applications to specific workspaces when they create windows",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"original-authors": [ "alessandro.crismani@gmail.com", "thomas.bouffon@gmail.com" ],
|
||||
"url": "@url@"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<key name="application-list" type="as">
|
||||
<!-- FIXME: should be a(su), when JS supports more of GVariant -->
|
||||
<default>[ ]</default>
|
||||
<summary>Application and workspace list</summary>
|
||||
<description>A list of strings, each containing an application id (desktop file name), followed by a colon and the workspace number</description>
|
||||
<_summary>Application and workspace list</_summary>
|
||||
<_description>A list of strings, each containing an application id (desktop file name), followed by a colon and the workspace number</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
@@ -1,348 +1,260 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Start apps on custom workspaces
|
||||
|
||||
import Adw from 'gi://Adw';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk';
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const GMenu = imports.gi.GMenu;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = function(e) { return e };
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const SETTINGS_KEY = 'application-list';
|
||||
|
||||
const WORKSPACE_MAX = 36; // compiled in limit of mutter
|
||||
|
||||
class NewItem extends GObject.Object {}
|
||||
GObject.registerClass(NewItem);
|
||||
const Columns = {
|
||||
APPINFO: 0,
|
||||
DISPLAY_NAME: 1,
|
||||
ICON: 2,
|
||||
WORKSPACE: 3,
|
||||
ADJUSTMENT: 4
|
||||
};
|
||||
|
||||
class NewItemModel extends GObject.Object {
|
||||
static [GObject.interfaces] = [Gio.ListModel];
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
const Widget = new GObject.Class({
|
||||
Name: 'AutoMoveWindows.Prefs.Widget',
|
||||
GTypeName: 'AutoMoveWindowsPrefsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
#item = new NewItem();
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this.set_orientation(Gtk.Orientation.VERTICAL);
|
||||
|
||||
vfunc_get_item_type() {
|
||||
return NewItem;
|
||||
}
|
||||
this._settings = Convenience.getSettings();
|
||||
this._settings.connect('changed', Lang.bind(this, this._refresh));
|
||||
this._changedPermitted = false;
|
||||
|
||||
vfunc_get_n_items() {
|
||||
return 1;
|
||||
}
|
||||
this._store = new Gtk.ListStore();
|
||||
this._store.set_column_types([Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon, GObject.TYPE_INT,
|
||||
Gtk.Adjustment]);
|
||||
|
||||
vfunc_get_item(_pos) {
|
||||
return this.#item;
|
||||
}
|
||||
}
|
||||
this._treeView = new Gtk.TreeView({ model: this._store,
|
||||
hexpand: true, vexpand: true });
|
||||
this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE);
|
||||
|
||||
class Rule extends GObject.Object {
|
||||
static [GObject.properties] = {
|
||||
'app-info': GObject.ParamSpec.object(
|
||||
'app-info', 'app-info', 'app-info',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
Gio.DesktopAppInfo),
|
||||
'workspace': GObject.ParamSpec.uint(
|
||||
'workspace', 'workspace', 'workspace',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
1, WORKSPACE_MAX, 1),
|
||||
};
|
||||
let appColumn = new Gtk.TreeViewColumn({ expand: true, sort_column_id: Columns.DISPLAY_NAME,
|
||||
title: _("Application") });
|
||||
let iconRenderer = new Gtk.CellRendererPixbuf;
|
||||
appColumn.pack_start(iconRenderer, false);
|
||||
appColumn.add_attribute(iconRenderer, "gicon", Columns.ICON);
|
||||
let nameRenderer = new Gtk.CellRendererText;
|
||||
appColumn.pack_start(nameRenderer, true);
|
||||
appColumn.add_attribute(nameRenderer, "text", Columns.DISPLAY_NAME);
|
||||
this._treeView.append_column(appColumn);
|
||||
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
}
|
||||
let workspaceColumn = new Gtk.TreeViewColumn({ title: _("Workspace"),
|
||||
sort_column_id: Columns.WORKSPACE });
|
||||
let workspaceRenderer = new Gtk.CellRendererSpin({ editable: true });
|
||||
workspaceRenderer.connect('edited', Lang.bind(this, this._workspaceEdited));
|
||||
workspaceColumn.pack_start(workspaceRenderer, true);
|
||||
workspaceColumn.add_attribute(workspaceRenderer, "adjustment", Columns.ADJUSTMENT);
|
||||
workspaceColumn.add_attribute(workspaceRenderer, "text", Columns.WORKSPACE);
|
||||
this._treeView.append_column(workspaceColumn);
|
||||
|
||||
class RulesList extends GObject.Object {
|
||||
static [GObject.interfaces] = [Gio.ListModel];
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
this.add(this._treeView);
|
||||
|
||||
#settings;
|
||||
#rules = [];
|
||||
#changedId;
|
||||
let toolbar = new Gtk.Toolbar();
|
||||
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
this.add(toolbar);
|
||||
|
||||
constructor(settings) {
|
||||
super();
|
||||
let newButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_NEW,
|
||||
label: _("Add rule"),
|
||||
is_important: true });
|
||||
newButton.connect('clicked', Lang.bind(this, this._createNew));
|
||||
toolbar.add(newButton);
|
||||
|
||||
this.#settings = settings;
|
||||
this.#changedId =
|
||||
this.#settings.connect(`changed::${SETTINGS_KEY}`,
|
||||
() => this.#sync());
|
||||
this.#sync();
|
||||
}
|
||||
let delButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_DELETE });
|
||||
delButton.connect('clicked', Lang.bind(this, this._deleteSelected));
|
||||
toolbar.add(delButton);
|
||||
|
||||
append(appInfo) {
|
||||
const pos = this.#rules.length;
|
||||
this._changedPermitted = true;
|
||||
this._refresh();
|
||||
},
|
||||
|
||||
this.#rules.push(new Rule({appInfo}));
|
||||
this.#saveRules();
|
||||
_createNew: function() {
|
||||
let dialog = new Gtk.Dialog({ title: _("Create new matching rule"),
|
||||
transient_for: this.get_toplevel(),
|
||||
modal: true });
|
||||
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
|
||||
dialog.add_button(_("Add"), Gtk.ResponseType.OK);
|
||||
dialog.set_default_response(Gtk.ResponseType.OK);
|
||||
|
||||
this.items_changed(pos, 0, 1);
|
||||
}
|
||||
let grid = new Gtk.Grid({ column_spacing: 10,
|
||||
row_spacing: 15,
|
||||
margin: 10 });
|
||||
dialog._appChooser = new Gtk.AppChooserWidget({ show_all: true });
|
||||
grid.attach(dialog._appChooser, 0, 0, 2, 1);
|
||||
grid.attach(new Gtk.Label({ label: _("Workspace") }),
|
||||
0, 1, 1, 1);
|
||||
let adjustment = new Gtk.Adjustment({ lower: 1,
|
||||
upper: WORKSPACE_MAX,
|
||||
step_increment: 1
|
||||
});
|
||||
dialog._spin = new Gtk.SpinButton({ adjustment: adjustment,
|
||||
snap_to_ticks: true });
|
||||
dialog._spin.set_value(1);
|
||||
grid.attach(dialog._spin, 1, 1, 1, 1);
|
||||
dialog.get_content_area().add(grid);
|
||||
|
||||
remove(id) {
|
||||
const pos = this.#rules.findIndex(r => r.appInfo.get_id() === id);
|
||||
if (pos < 0)
|
||||
return;
|
||||
dialog.connect('response', Lang.bind(this, function(dialog, id) {
|
||||
if (id != Gtk.ResponseType.OK) {
|
||||
dialog.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#rules.splice(pos, 1);
|
||||
this.#saveRules();
|
||||
let appInfo = dialog._appChooser.get_app_info();
|
||||
if (!appInfo)
|
||||
return;
|
||||
let index = Math.floor(dialog._spin.value);
|
||||
if (isNaN(index) || index < 0)
|
||||
index = 1;
|
||||
|
||||
this.items_changed(pos, 1, 0);
|
||||
}
|
||||
this._changedPermitted = false;
|
||||
if (!this._appendItem(appInfo.get_id(), index)) {
|
||||
this._changedPermitted = true;
|
||||
return;
|
||||
}
|
||||
let iter = this._store.append();
|
||||
let adj = new Gtk.Adjustment({ lower: 1,
|
||||
upper: WORKSPACE_MAX,
|
||||
step_increment: 1,
|
||||
value: index });
|
||||
this._store.set(iter,
|
||||
[Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME, Columns.WORKSPACE, Columns.ADJUSTMENT],
|
||||
[appInfo, appInfo.get_icon(), appInfo.get_display_name(), index, adj]);
|
||||
this._changedPermitted = true;
|
||||
|
||||
changeWorkspace(id, workspace) {
|
||||
const pos = this.#rules.findIndex(r => r.appInfo.get_id() === id);
|
||||
if (pos < 0)
|
||||
return;
|
||||
|
||||
this.#rules[pos].set({workspace});
|
||||
this.#saveRules();
|
||||
}
|
||||
|
||||
#saveRules() {
|
||||
this.#settings.block_signal_handler(this.#changedId);
|
||||
this.#settings.set_strv(SETTINGS_KEY,
|
||||
this.#rules.map(r => `${r.app_info.get_id()}:${r.workspace}`));
|
||||
this.#settings.unblock_signal_handler(this.#changedId);
|
||||
}
|
||||
|
||||
#sync() {
|
||||
const removed = this.#rules.length;
|
||||
|
||||
this.#rules = [];
|
||||
for (const stringRule of this.#settings.get_strv(SETTINGS_KEY)) {
|
||||
const [id, workspace] = stringRule.split(':');
|
||||
const appInfo = Gio.DesktopAppInfo.new(id);
|
||||
if (appInfo)
|
||||
this.#rules.push(new Rule({appInfo, workspace}));
|
||||
else
|
||||
log(`Invalid ID ${id}`);
|
||||
}
|
||||
this.items_changed(0, removed, this.#rules.length);
|
||||
}
|
||||
|
||||
vfunc_get_item_type() {
|
||||
return Rule;
|
||||
}
|
||||
|
||||
vfunc_get_n_items() {
|
||||
return this.#rules.length;
|
||||
}
|
||||
|
||||
vfunc_get_item(pos) {
|
||||
return this.#rules[pos] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
class AutoMoveSettingsWidget extends Adw.PreferencesGroup {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
|
||||
this.install_action('rules.add', null, self => self._addNewRule());
|
||||
this.install_action('rules.remove', 's',
|
||||
(self, name, param) => self._rules.remove(param.unpack()));
|
||||
this.install_action('rules.change-workspace', '(si)',
|
||||
(self, name, param) => self._rules.changeWorkspace(...param.deepUnpack()));
|
||||
}
|
||||
|
||||
constructor(settings) {
|
||||
super({
|
||||
title: _('Workspace Rules'),
|
||||
});
|
||||
|
||||
this._settings = settings;
|
||||
this._rules = new RulesList(this._settings);
|
||||
|
||||
const store = new Gio.ListStore({item_type: Gio.ListModel});
|
||||
const listModel = new Gtk.FlattenListModel({model: store});
|
||||
store.append(this._rules);
|
||||
store.append(new NewItemModel());
|
||||
|
||||
this._list = new Gtk.ListBox({
|
||||
selection_mode: Gtk.SelectionMode.NONE,
|
||||
css_classes: ['boxed-list'],
|
||||
});
|
||||
this.add(this._list);
|
||||
|
||||
this._list.bind_model(listModel, item => {
|
||||
return item instanceof NewItem
|
||||
? new NewRuleRow()
|
||||
: new RuleRow(item);
|
||||
});
|
||||
}
|
||||
|
||||
_addNewRule() {
|
||||
const dialog = new NewRuleDialog(this.get_root(), this._settings);
|
||||
dialog.connect('response', (dlg, id) => {
|
||||
const appInfo = id === Gtk.ResponseType.OK
|
||||
? dialog.get_widget().get_app_info() : null;
|
||||
if (appInfo)
|
||||
this._rules.append(appInfo);
|
||||
dialog.destroy();
|
||||
});
|
||||
dialog.show();
|
||||
}));
|
||||
dialog.show_all();
|
||||
},
|
||||
|
||||
_deleteSelected: function() {
|
||||
let [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
|
||||
if (any) {
|
||||
let appInfo = this._store.get_value(iter, Columns.APPINFO);
|
||||
|
||||
this._changedPermitted = false;
|
||||
this._removeItem(appInfo.get_id());
|
||||
this._store.remove(iter);
|
||||
this._changedPermitted = true;
|
||||
}
|
||||
},
|
||||
|
||||
_workspaceEdited: function(renderer, pathString, text) {
|
||||
let index = parseInt(text);
|
||||
if (isNaN(index) || index < 0)
|
||||
index = 1;
|
||||
let path = Gtk.TreePath.new_from_string(pathString);
|
||||
let [model, iter] = this._store.get_iter(path);
|
||||
let appInfo = this._store.get_value(iter, Columns.APPINFO);
|
||||
|
||||
this._changedPermitted = false;
|
||||
this._changeItem(appInfo.get_id(), index);
|
||||
this._store.set_value(iter, Columns.WORKSPACE, index);
|
||||
this._changedPermitted = true;
|
||||
},
|
||||
|
||||
_refresh: function() {
|
||||
if (!this._changedPermitted)
|
||||
// Ignore this notification, model is being modified outside
|
||||
return;
|
||||
|
||||
this._store.clear();
|
||||
|
||||
let currentItems = this._settings.get_strv(SETTINGS_KEY);
|
||||
let validItems = [ ];
|
||||
for (let i = 0; i < currentItems.length; i++) {
|
||||
let [id, index] = currentItems[i].split(':');
|
||||
let appInfo = Gio.DesktopAppInfo.new(id);
|
||||
if (!appInfo)
|
||||
continue;
|
||||
validItems.push(currentItems[i]);
|
||||
|
||||
let iter = this._store.append();
|
||||
let adj = new Gtk.Adjustment({ lower: 1,
|
||||
upper: WORKSPACE_MAX,
|
||||
step_increment: 1,
|
||||
value: index });
|
||||
this._store.set(iter,
|
||||
[Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME, Columns.WORKSPACE, Columns.ADJUSTMENT],
|
||||
[appInfo, appInfo.get_icon(), appInfo.get_display_name(), parseInt(index), adj]);
|
||||
}
|
||||
|
||||
if (validItems.length != currentItems.length) // some items were filtered out
|
||||
this._settings.set_strv(SETTINGS_KEY, validItems);
|
||||
},
|
||||
|
||||
_appendItem: function(id, workspace) {
|
||||
let currentItems = this._settings.get_strv(SETTINGS_KEY);
|
||||
let alreadyHave = currentItems.map(function(el) {
|
||||
return el.split(':')[0];
|
||||
}).indexOf(id) != -1;
|
||||
|
||||
if (alreadyHave) {
|
||||
printerr("Already have an item for this id");
|
||||
return false;
|
||||
}
|
||||
|
||||
currentItems.push(id + ':' + workspace);
|
||||
this._settings.set_strv(SETTINGS_KEY, currentItems);
|
||||
return true;
|
||||
},
|
||||
|
||||
_removeItem: function(id) {
|
||||
let currentItems = this._settings.get_strv(SETTINGS_KEY);
|
||||
let index = currentItems.map(function(el) {
|
||||
return el.split(':')[0];
|
||||
}).indexOf(id);
|
||||
|
||||
if (index < 0)
|
||||
return;
|
||||
currentItems.splice(index, 1);
|
||||
this._settings.set_strv(SETTINGS_KEY, currentItems);
|
||||
},
|
||||
|
||||
_changeItem: function(id, workspace) {
|
||||
let currentItems = this._settings.get_strv(SETTINGS_KEY);
|
||||
let index = currentItems.map(function(el) {
|
||||
return el.split(':')[0];
|
||||
}).indexOf(id);
|
||||
|
||||
if (index < 0)
|
||||
currentItems.push(id + ':' + workspace);
|
||||
else
|
||||
currentItems[index] = id + ':' + workspace;
|
||||
this._settings.set_strv(SETTINGS_KEY, currentItems);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
class WorkspaceSelector extends Gtk.Widget {
|
||||
static [GObject.properties] = {
|
||||
'number': GObject.ParamSpec.uint(
|
||||
'number', 'number', 'number',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
1, WORKSPACE_MAX, 1),
|
||||
};
|
||||
function buildPrefsWidget() {
|
||||
let widget = new Widget();
|
||||
widget.show_all();
|
||||
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
|
||||
this.set_layout_manager_type(Gtk.BoxLayout);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.layout_manager.spacing = 6;
|
||||
|
||||
const label = new Gtk.Label({
|
||||
xalign: 1,
|
||||
margin_end: 6,
|
||||
});
|
||||
this.bind_property('number',
|
||||
label, 'label',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
label.set_parent(this);
|
||||
|
||||
const buttonProps = {
|
||||
css_classes: ['circular'],
|
||||
valign: Gtk.Align.CENTER,
|
||||
};
|
||||
|
||||
this._decButton = new Gtk.Button({
|
||||
icon_name: 'list-remove-symbolic',
|
||||
...buttonProps,
|
||||
});
|
||||
this._decButton.set_parent(this);
|
||||
this._decButton.connect('clicked', () => this.number--);
|
||||
|
||||
this._incButton = new Gtk.Button({
|
||||
icon_name: 'list-add-symbolic',
|
||||
...buttonProps,
|
||||
});
|
||||
this._incButton.set_parent(this);
|
||||
this._incButton.connect('clicked', () => this.number++);
|
||||
|
||||
this.connect('notify::number', () => this._syncButtons());
|
||||
this._syncButtons();
|
||||
}
|
||||
|
||||
_syncButtons() {
|
||||
this._decButton.sensitive = this.number > 1;
|
||||
this._incButton.sensitive = this.number < WORKSPACE_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
class RuleRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor(rule) {
|
||||
const {appInfo} = rule;
|
||||
const id = appInfo.get_id();
|
||||
|
||||
super({
|
||||
activatable: false,
|
||||
title: rule.appInfo.get_display_name(),
|
||||
});
|
||||
|
||||
const icon = new Gtk.Image({
|
||||
css_classes: ['icon-dropshadow'],
|
||||
gicon: appInfo.get_icon(),
|
||||
pixel_size: 32,
|
||||
});
|
||||
this.add_prefix(icon);
|
||||
|
||||
const wsButton = new WorkspaceSelector();
|
||||
rule.bind_property('workspace',
|
||||
wsButton, 'number',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
this.add_suffix(wsButton);
|
||||
|
||||
wsButton.connect('notify::number', () => {
|
||||
this.activate_action('rules.change-workspace',
|
||||
new GLib.Variant('(si)', [id, wsButton.number]));
|
||||
});
|
||||
|
||||
const button = new Gtk.Button({
|
||||
action_name: 'rules.remove',
|
||||
action_target: new GLib.Variant('s', id),
|
||||
icon_name: 'edit-delete-symbolic',
|
||||
has_frame: false,
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
this.add_suffix(button);
|
||||
}
|
||||
}
|
||||
|
||||
class NewRuleRow extends Gtk.ListBoxRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
action_name: 'rules.add',
|
||||
child: new Gtk.Image({
|
||||
icon_name: 'list-add-symbolic',
|
||||
pixel_size: 16,
|
||||
margin_top: 12,
|
||||
margin_bottom: 12,
|
||||
margin_start: 12,
|
||||
margin_end: 12,
|
||||
}),
|
||||
});
|
||||
this.update_property(
|
||||
[Gtk.AccessibleProperty.LABEL], [_('Add Rule')]);
|
||||
}
|
||||
}
|
||||
|
||||
class NewRuleDialog extends Gtk.AppChooserDialog {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor(parent, settings) {
|
||||
super({
|
||||
transient_for: parent,
|
||||
modal: true,
|
||||
});
|
||||
|
||||
this._settings = settings;
|
||||
|
||||
this.get_widget().set({
|
||||
show_all: true,
|
||||
show_other: true, // hide more button
|
||||
});
|
||||
|
||||
this.get_widget().connect('application-selected',
|
||||
this._updateSensitivity.bind(this));
|
||||
this._updateSensitivity();
|
||||
}
|
||||
|
||||
_updateSensitivity() {
|
||||
const rules = this._settings.get_strv(SETTINGS_KEY);
|
||||
const appInfo = this.get_widget().get_app_info();
|
||||
this.set_response_sensitive(Gtk.ResponseType.OK,
|
||||
appInfo && !rules.some(i => i.startsWith(appInfo.get_id())));
|
||||
}
|
||||
}
|
||||
|
||||
export default class AutoMovePrefs extends ExtensionPreferences {
|
||||
getPreferencesWidget() {
|
||||
return new AutoMoveSettingsWidget(this.getSettings());
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
1
extensions/auto-move-windows/stylesheet.css
Normal file
1
extensions/auto-move-windows/stylesheet.css
Normal file
@@ -0,0 +1 @@
|
||||
/* This extensions requires no special styling */
|
||||
5
extensions/dock/Makefile.am
Normal file
5
extensions/dock/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = dock
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
|
||||
991
extensions/dock/extension.js
Normal file
991
extensions/dock/extension.js
Normal file
@@ -0,0 +1,991 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const AppFavorites = imports.ui.appFavorites;
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Search = imports.ui.search;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Workspace = imports.ui.workspace;
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const AltTab = imports.ui.altTab;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
// Settings
|
||||
const DOCK_POSITION_KEY = 'position';
|
||||
const DOCK_SIZE_KEY = 'size';
|
||||
const DOCK_HIDE_KEY = 'autohide';
|
||||
const DOCK_EFFECTHIDE_KEY = 'hide-effect';
|
||||
const DOCK_AUTOHIDE_ANIMATION_TIME_KEY = 'hide-effect-duration';
|
||||
const DOCK_MONITOR_KEY = 'monitor';
|
||||
|
||||
// Keep enums in sync with GSettings schemas
|
||||
const PositionMode = {
|
||||
LEFT: 0,
|
||||
RIGHT: 1
|
||||
};
|
||||
|
||||
const AutoHideEffect = {
|
||||
RESIZE: 0,
|
||||
RESCALE: 1,
|
||||
MOVE: 2
|
||||
};
|
||||
|
||||
const DND_RAISE_APP_TIMEOUT = 500;
|
||||
|
||||
// Utility function to make the dock clipped to the primary monitor
|
||||
function updateClip(actor, monitorNumber) {
|
||||
let monitor;
|
||||
if (monitorNumber > -1 && monitorNumber < Main.layoutManager.monitors.length)
|
||||
monitor = Main.layoutManager.monitors[monitorNumber];
|
||||
else
|
||||
monitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let allocation = actor.allocation;
|
||||
|
||||
// Here we implicitly assume that the stage and actor's parent
|
||||
// share the same coordinate space
|
||||
let clip = new Clutter.ActorBox({ x1: Math.max(monitor.x, allocation.x1),
|
||||
y1: Math.max(monitor.y, allocation.y1),
|
||||
x2: Math.min(monitor.x + monitor.width, allocation.x2),
|
||||
y2: Math.min(monitor.y + monitor.height, allocation.y2) });
|
||||
// Translate back into actor's coordinate space
|
||||
clip.x1 -= actor.x;
|
||||
clip.x2 -= actor.x;
|
||||
clip.y1 -= actor.y;
|
||||
clip.y2 -= actor.y;
|
||||
|
||||
// Apply the clip
|
||||
actor.set_clip(clip.x1, clip.y1, clip.x2-clip.x1, clip.y2 - clip.y1);
|
||||
}
|
||||
|
||||
/*************************************************************************************/
|
||||
/**** start resize's Dock functions *****************/
|
||||
/*************************************************************************************/
|
||||
function hideDock_size () {
|
||||
if (!this._hideable)
|
||||
return;
|
||||
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let position_x = monitor.x;
|
||||
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
Tweener.addTween(this, {
|
||||
_item_size: 1,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function () {
|
||||
height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
width = this._item_size + 4*this._spacing;
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
position_x=monitor.x-2*this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
position_x = monitor.x + (monitor.width-1-this._item_size-2*this._spacing);
|
||||
}
|
||||
this.actor.set_position (position_x,monitor.y+(monitor.height-height)/2);
|
||||
this.actor.set_size(width,height);
|
||||
|
||||
updateClip(this.actor, this._displayMonitor);
|
||||
},
|
||||
});
|
||||
|
||||
this._hidden = true;
|
||||
}
|
||||
|
||||
function showDock_size () {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
let position_x = monitor.x;
|
||||
|
||||
Tweener.addTween(this, {
|
||||
_item_size: this._settings.get_int(DOCK_SIZE_KEY),
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function () {
|
||||
height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
width = this._item_size + 4*this._spacing;
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
position_x=monitor.x-2*this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
|
||||
}
|
||||
this.actor.set_position (position_x, monitor.y+(monitor.height-height)/2);
|
||||
this.actor.set_size(width,height);
|
||||
|
||||
updateClip(this.actor, this._displayMonitor);
|
||||
}
|
||||
});
|
||||
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
function showEffectAddItem_size () {
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
Tweener.addTween(this.actor, {
|
||||
y: primary.y + (primary.height-height)/2,
|
||||
height: height,
|
||||
width: width,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function (monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
}
|
||||
|
||||
/**************************************************************************************/
|
||||
/**** start rescale's Dock functions *****************/
|
||||
/**************************************************************************************/
|
||||
function hideDock_scale () {
|
||||
if (!this._hideable)
|
||||
return;
|
||||
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let cornerX = 0;
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
cornerX=monitor.x;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
cornerX = monitor.x + monitor.width-1;
|
||||
}
|
||||
|
||||
Tweener.addTween(this.actor,{
|
||||
y: monitor.y + (monitor.height-height)/2,
|
||||
x: cornerX,
|
||||
height:height,
|
||||
width: width,
|
||||
scale_x: 0.025,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
|
||||
this._hidden = true;
|
||||
}
|
||||
|
||||
function showDock_scale () {
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let position_x = monitor.x;
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
position_x=monitor.x-2*this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
|
||||
}
|
||||
Tweener.addTween(this.actor, {
|
||||
y: monitor.y + (monitor.height-height)/2,
|
||||
x: monitor.x + position_x,
|
||||
height: height,
|
||||
width: width,
|
||||
scale_x: 1,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
function showEffectAddItem_scale () {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
Tweener.addTween(this.actor, {
|
||||
y: monitor.y + (monitor.height-height)/2,
|
||||
height: height,
|
||||
width: width,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
}
|
||||
|
||||
/**************************************************************************************/
|
||||
/**** start move Dock functions *****************/
|
||||
/**************************************************************************************/
|
||||
function hideDock_move () {
|
||||
if (!this._hideable)
|
||||
return;
|
||||
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let cornerX = 0;
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
cornerX= monitor.x - width + this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
cornerX = monitor.x + monitor.width - this._spacing;
|
||||
}
|
||||
|
||||
Tweener.addTween(this.actor,{
|
||||
x: cornerX,
|
||||
y: monitor.y + (monitor.height - height)/2,
|
||||
width: width,
|
||||
height: height,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
|
||||
this._hidden = true;
|
||||
}
|
||||
|
||||
function showDock_move () {
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let position_x = monitor.x;
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
position_x=monitor.x - 2*this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
|
||||
}
|
||||
Tweener.addTween(this.actor, {
|
||||
x: position_x,
|
||||
y: monitor.y + (monitor.height - height)/2,
|
||||
width: width,
|
||||
height: height,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
function showEffectAddItem_move () {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
Tweener.addTween(this.actor, {
|
||||
y: monitor.y + (monitor.height-height)/2,
|
||||
height: height,
|
||||
width: width,
|
||||
time: this._settings.get_double(DOCK_AUTOHIDE_ANIMATION_TIME_KEY),
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function(monitor) {
|
||||
updateClip(this, monitor);
|
||||
},
|
||||
onUpdateParams: [this._displayMonitor]
|
||||
});
|
||||
}
|
||||
|
||||
const Dock = new Lang.Class({
|
||||
Name: 'Dock.Dock',
|
||||
|
||||
_init : function() {
|
||||
this._placeholderText = null;
|
||||
this._menus = [];
|
||||
this._menuDisplays = [];
|
||||
|
||||
this._favorites = [];
|
||||
|
||||
// Load Settings
|
||||
this._settings = Convenience.getSettings();
|
||||
this._hidden = false;
|
||||
this._hideable = this._settings.get_boolean(DOCK_HIDE_KEY);
|
||||
this._displayMonitor = this._settings.get_int(DOCK_MONITOR_KEY);
|
||||
|
||||
this._spacing = 4;
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
this._nicons = 0;
|
||||
this._selectEffectFunctions(this._settings.get_enum(DOCK_EFFECTHIDE_KEY));
|
||||
|
||||
let [_x, _y, _w, _h] = this.get_start_position();
|
||||
this.actor = new St.BoxLayout({ name: 'dock', vertical: true, reactive: true,
|
||||
x: _x, y: _y, width: _w, height: _h });
|
||||
|
||||
this._grid = new Shell.GenericContainer();
|
||||
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
|
||||
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this._grid.connect('allocate', Lang.bind(this, this._allocate));
|
||||
|
||||
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
|
||||
|
||||
this._tracker = Shell.WindowTracker.get_default();
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
|
||||
this._installedChangedId = this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
|
||||
this._appFavoritesChangedId = AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
|
||||
this._appStateChangedId = this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
|
||||
|
||||
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
this.actor.hide();
|
||||
}));
|
||||
this._overviewHiddenId = Main.overview.connect('hidden', Lang.bind(this, function() {
|
||||
this.actor.show();
|
||||
}));
|
||||
Main.layoutManager.addChrome(this.actor,
|
||||
{ affectsStruts: !this._settings.get_boolean(DOCK_HIDE_KEY) });
|
||||
|
||||
//hidden
|
||||
this._settings.connect('changed::'+DOCK_POSITION_KEY, Lang.bind(this, this._redisplay));
|
||||
this._settings.connect('changed::'+DOCK_SIZE_KEY, Lang.bind(this, this._redisplay));
|
||||
this._settings.connect('changed::'+DOCK_MONITOR_KEY, Lang.bind(this, function (){
|
||||
this._displayMonitor = this._settings.get_int(DOCK_MONITOR_KEY);
|
||||
this._redisplay();
|
||||
}));
|
||||
|
||||
this._settings.connect('changed::'+DOCK_HIDE_KEY, Lang.bind(this, function (){
|
||||
Main.layoutManager.removeChrome(this.actor);
|
||||
Main.layoutManager.addChrome(this.actor,
|
||||
{ affectsStruts: !this._settings.get_boolean(DOCK_HIDE_KEY) });
|
||||
|
||||
this._hideable = this._settings.get_boolean(DOCK_HIDE_KEY);
|
||||
if (this._hideable)
|
||||
this._hideDock();
|
||||
else
|
||||
this._showDock();
|
||||
}));
|
||||
|
||||
this._settings.connect('changed::' + DOCK_EFFECTHIDE_KEY, Lang.bind(this, function () {
|
||||
let hideEffect = this._settings.get_enum(DOCK_EFFECTHIDE_KEY);
|
||||
|
||||
// restore the effects of the other functions
|
||||
switch (hideEffect) {
|
||||
case AutoHideEffect.RESCALE:
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
break;
|
||||
case AutoHideEffect.RESIZE:
|
||||
this.actor.set_scale(1, 1);
|
||||
break;
|
||||
case AutoHideEffect.MOVE:
|
||||
this.actor.set_scale(1, 1);
|
||||
this._item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
}
|
||||
|
||||
this.actor.disconnect(this._leave_event);
|
||||
this.actor.disconnect(this._enter_event);
|
||||
|
||||
this._selectEffectFunctions(hideEffect);
|
||||
|
||||
this._leave_event = this.actor.connect('leave-event', Lang.bind(this, this._hideDock));
|
||||
this._enter_event = this.actor.connect('enter-event', Lang.bind(this, this._showDock));
|
||||
this._redisplay();
|
||||
}));
|
||||
|
||||
this._leave_event = this.actor.connect('leave-event', Lang.bind(this, this._hideDock));
|
||||
this._enter_event = this.actor.connect('enter-event', Lang.bind(this, this._showDock));
|
||||
|
||||
this._hideDock();
|
||||
},
|
||||
|
||||
get_start_position: function() {
|
||||
let item_size = this._settings.get_int(DOCK_SIZE_KEY);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
if (this._displayMonitor > -1 && this._displayMonitor < Main.layoutManager.monitors.length) {
|
||||
monitor = Main.layoutManager.monitors[this._displayMonitor];
|
||||
}
|
||||
let position_x = monitor.x;
|
||||
let width = item_size + 4 * this._spacing;
|
||||
|
||||
switch (this._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
position_x=monitor.x - 2 * this._spacing;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
position_x=monitor.x + (monitor.width - item_size - 2 * this._spacing);
|
||||
}
|
||||
|
||||
return [ position_x, monitor.y, width, monitor.height ];
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._installedChangedId) {
|
||||
this._appSystem.disconnect(this._installedChangedId);
|
||||
this._installedChangedId = 0;
|
||||
}
|
||||
|
||||
if (this._appFavoritesChangedId) {
|
||||
AppFavorites.getAppFavorites().disconnect(this._appFavoritesChangedId);
|
||||
this._appFavoritesChangedId = 0;
|
||||
}
|
||||
|
||||
if (this._appStateChangedId) {
|
||||
this._appSystem.disconnect(this._appStateChangedId);
|
||||
this._appStateChangedId = 0;
|
||||
}
|
||||
|
||||
if (this._overviewShowingId) {
|
||||
Main.overview.disconnect(this._overviewShowingId);
|
||||
this._overviewShowingId = 0;
|
||||
}
|
||||
|
||||
if (this._overviewHiddenId) {
|
||||
Main.overview.disconnect(this._overviewHiddenId);
|
||||
this._overviewHiddenId = 0;
|
||||
}
|
||||
|
||||
this.actor.destroy();
|
||||
|
||||
// Break reference cycles
|
||||
this._settings.run_dispose();
|
||||
this._settings = null;
|
||||
this._appSystem = null;
|
||||
this._tracker = null;
|
||||
},
|
||||
|
||||
// fuctions hide
|
||||
_restoreHideDock: function() {
|
||||
this._hideable = this._settings.get_boolean(DOCK_HIDE_KEY);
|
||||
},
|
||||
|
||||
_disableHideDock: function() {
|
||||
this._hideable = false;
|
||||
},
|
||||
|
||||
_selectEffectFunctions: function(hideEffect) {
|
||||
switch (hideEffect) {
|
||||
case AutoHideEffect.RESCALE:
|
||||
this._hideDock = hideDock_scale;
|
||||
this._showDock = showDock_scale;
|
||||
this._showEffectAddItem = showEffectAddItem_scale;
|
||||
break;
|
||||
case AutoHideEffect.MOVE:
|
||||
this._hideDock = hideDock_move;
|
||||
this._showDock = showDock_move;
|
||||
this._showEffectAddItem = showEffectAddItem_move;
|
||||
break;
|
||||
case AutoHideEffect.RESIZE:
|
||||
default:
|
||||
this._hideDock = hideDock_size;
|
||||
this._showDock = showDock_size;
|
||||
this._showEffectAddItem = showEffectAddItem_size;
|
||||
}
|
||||
},
|
||||
|
||||
_appIdListToHash: function(apps) {
|
||||
let ids = {};
|
||||
for (let i = 0; i < apps.length; i++)
|
||||
ids[apps[i].get_id()] = apps[i];
|
||||
return ids;
|
||||
},
|
||||
|
||||
_queueRedisplay: function () {
|
||||
Main.queueDeferredWork(this._workId);
|
||||
},
|
||||
|
||||
_redisplay: function () {
|
||||
this.removeAll();
|
||||
|
||||
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
||||
|
||||
let running = this._appSystem.get_running();
|
||||
let runningIds = this._appIdListToHash(running);
|
||||
|
||||
let icons = 0;
|
||||
|
||||
let nFavorites = 0;
|
||||
for (let id in favorites) {
|
||||
let app = favorites[id];
|
||||
let display = new DockIcon(app,this);
|
||||
this.addItem(display.actor);
|
||||
nFavorites++;
|
||||
icons++;
|
||||
}
|
||||
|
||||
for (let i = 0; i < running.length; i++) {
|
||||
let app = running[i];
|
||||
if (app.get_id() in favorites)
|
||||
continue;
|
||||
let display = new DockIcon(app,this);
|
||||
icons++;
|
||||
this.addItem(display.actor);
|
||||
}
|
||||
this._nicons=icons;
|
||||
|
||||
if (this._placeholderText) {
|
||||
this._placeholderText.destroy();
|
||||
this._placeholderText = null;
|
||||
}
|
||||
|
||||
if (running.length == 0 && nFavorites == 0) {
|
||||
this._placeholderText = new St.Label({ text: _("Drag here to add favorites") });
|
||||
this.actor.add_actor(this._placeholderText);
|
||||
}
|
||||
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let height = (icons)*(this._item_size + this._spacing) + 2*this._spacing;
|
||||
let width = this._item_size + 4*this._spacing;
|
||||
|
||||
if (this._hideable && this._hidden) {
|
||||
this._hideDock();
|
||||
} else {
|
||||
if (this._settings.get_int(DOCK_SIZE_KEY) == this._item_size) {
|
||||
// only add/delete icon
|
||||
this._showEffectAddItem ();
|
||||
} else {
|
||||
// change size icon
|
||||
this._showDock ();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (grid, forHeight, alloc) {
|
||||
alloc.min_size = this._item_size;
|
||||
alloc.natural_size = this._item_size + this._spacing;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (grid, forWidth, alloc) {
|
||||
let children = this._grid.get_children();
|
||||
let nRows = children.length;
|
||||
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
|
||||
let height = nRows * this._item_size + totalSpacing;
|
||||
alloc.min_size = height;
|
||||
alloc.natural_size = height;
|
||||
},
|
||||
|
||||
_allocate: function (grid, box, flags) {
|
||||
let children = this._grid.get_children();
|
||||
|
||||
let x = box.x1 + this._spacing;
|
||||
if (this._settings.get_enum(DOCK_POSITION_KEY) == PositionMode.LEFT)
|
||||
x = box.x1 + 2*this._spacing;
|
||||
let y = box.y1 + this._spacing;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
childBox.x1 = x;
|
||||
childBox.y1 = y;
|
||||
childBox.x2 = childBox.x1 + this._item_size;
|
||||
childBox.y2 = childBox.y1 + this._item_size;
|
||||
children[i].allocate(childBox, flags);
|
||||
y += this._item_size + this._spacing;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_onStyleChanged: function() {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let [success, len] = themeNode.get_length('spacing', false);
|
||||
if (success)
|
||||
this._spacing = len;
|
||||
[success, len] = themeNode.get_length('-shell-grid-item-size', false);
|
||||
if (success)
|
||||
this._item_size = len;
|
||||
this._grid.queue_relayout();
|
||||
},
|
||||
|
||||
removeAll: function () {
|
||||
this._grid.get_children().forEach(Lang.bind(this, function (child) {
|
||||
child.destroy();
|
||||
}));
|
||||
},
|
||||
|
||||
addItem: function(actor) {
|
||||
this._grid.add_actor(actor);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Dock.prototype);
|
||||
|
||||
const DockIcon = new Lang.Class({
|
||||
Name: 'Dock.DockIcon',
|
||||
|
||||
_init : function(app, dock) {
|
||||
this._dock = dock;
|
||||
this._settings = dock._settings;
|
||||
|
||||
|
||||
this.app = app;
|
||||
this.actor = new St.Button({ style_class: 'app-well-app',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
|
||||
reactive: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._icon = new AppDisplay.AppIcon(app, { setSizeManually: true,
|
||||
showLabel: false });
|
||||
this.actor.set_child(this._icon.actor);
|
||||
this._icon.setIconSize(this._settings.get_int(DOCK_SIZE_KEY));
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
|
||||
this._menu = null;
|
||||
this._menuManager = new PopupMenu.PopupMenuManager(this);
|
||||
|
||||
this._has_focus = false;
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
tracker.connect('notify::focus-app', Lang.bind(this, this._onStateChanged));
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
this.actor.connect('notify::hover', Lang.bind(this, this._hoverChanged));
|
||||
|
||||
this._menuTimeoutId = 0;
|
||||
this._stateChangedId = this.app.connect('notify::state',
|
||||
Lang.bind(this, this._onStateChanged));
|
||||
this._onStateChanged();
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
if (this._stateChangedId > 0)
|
||||
this.app.disconnect(this._stateChangedId);
|
||||
this._stateChangedId = 0;
|
||||
this._removeMenuTimeout();
|
||||
},
|
||||
|
||||
_removeMenuTimeout: function() {
|
||||
if (this._menuTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
this._menuTimeoutId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_hoverChanged: function(actor) {
|
||||
if (actor != this.actor)
|
||||
this._has_focus = false;
|
||||
else
|
||||
this._has_focus = true;
|
||||
return false;
|
||||
},
|
||||
|
||||
_onStateChanged: function() {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let focusedApp = tracker.focus_app;
|
||||
if (this.app.state != Shell.AppState.STOPPED) {
|
||||
this.actor.add_style_class_name('running');
|
||||
if (this.app == focusedApp) {
|
||||
this.actor.add_style_class_name('focused');
|
||||
} else {
|
||||
this.actor.remove_style_class_name('focused');
|
||||
}
|
||||
} else {
|
||||
this.actor.remove_style_class_name('focused');
|
||||
this.actor.remove_style_class_name('running');
|
||||
}
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
let button = event.get_button();
|
||||
if (button == 1) {
|
||||
this._removeMenuTimeout();
|
||||
this._menuTimeoutId = Mainloop.timeout_add(AppDisplay.MENU_POPUP_TIMEOUT, Lang.bind(this, function() {
|
||||
this.popupMenu();
|
||||
}));
|
||||
} else if (button == 3) {
|
||||
this.popupMenu();
|
||||
}
|
||||
},
|
||||
|
||||
_onClicked: function(actor, button) {
|
||||
this._removeMenuTimeout();
|
||||
|
||||
if (button == 1) {
|
||||
this._onActivate(Clutter.get_current_event());
|
||||
} else if (button == 2) {
|
||||
// Last workspace is always empty
|
||||
let launchWorkspace = global.screen.get_workspace_by_index(global.screen.n_workspaces - 1);
|
||||
launchWorkspace.activate(global.get_current_time());
|
||||
this.emit('launching');
|
||||
this.app.open_new_window(-1);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getId: function() {
|
||||
return this.app.get_id();
|
||||
},
|
||||
|
||||
popupMenu: function() {
|
||||
this._removeMenuTimeout();
|
||||
this.actor.fake_release();
|
||||
|
||||
this._dock._disableHideDock();
|
||||
|
||||
if (!this._menu) {
|
||||
this._menu = new DockIconMenu(this);
|
||||
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
|
||||
this.activateWindow(window);
|
||||
}));
|
||||
this._menu.connect('open-state-changed', Lang.bind(this, function (menu, isPoppedUp) {
|
||||
if (!isPoppedUp){
|
||||
//Restore value of autohidedock
|
||||
this._dock._restoreHideDock();
|
||||
this._dock._hideDock();
|
||||
|
||||
this._onMenuPoppedDown();
|
||||
}
|
||||
}));
|
||||
|
||||
this._menuManager.addMenu(this._menu, true);
|
||||
}
|
||||
|
||||
this._menu.redisplay();
|
||||
this._menu.open();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
activateWindow: function(metaWindow) {
|
||||
if (metaWindow) {
|
||||
this._didActivateWindow = true;
|
||||
Main.activateWindow(metaWindow);
|
||||
}
|
||||
},
|
||||
|
||||
setSelected: function (isSelected) {
|
||||
this._selected = isSelected;
|
||||
if (this._selected)
|
||||
this.actor.add_style_class_name('selected');
|
||||
else
|
||||
this.actor.remove_style_class_name('selected');
|
||||
},
|
||||
|
||||
_onMenuPoppedDown: function() {
|
||||
this.actor.sync_hover();
|
||||
},
|
||||
|
||||
_getRunning: function() {
|
||||
return this.app.state != Shell.AppState.STOPPED;
|
||||
},
|
||||
|
||||
_onActivate: function (event) {
|
||||
this.emit('launching');
|
||||
let modifiers = event.get_state();
|
||||
|
||||
if (modifiers & Clutter.ModifierType.CONTROL_MASK
|
||||
&& this.app.state == Shell.AppState.RUNNING) {
|
||||
let current_workspace = global.screen.get_active_workspace().index();
|
||||
this.app.open_new_window(current_workspace);
|
||||
} else {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let focusedApp = tracker.focus_app;
|
||||
|
||||
if (this.app == focusedApp) {
|
||||
let windows = this.app.get_windows();
|
||||
let current_workspace = global.screen.get_active_workspace();
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let w = windows[i];
|
||||
if (w.get_workspace() == current_workspace)
|
||||
w.minimize();
|
||||
}
|
||||
} else {
|
||||
this.app.activate(-1);
|
||||
}
|
||||
}
|
||||
Main.overview.hide();
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(DockIcon.prototype);
|
||||
|
||||
const DockIconMenu = new Lang.Class({
|
||||
Name: 'Dock.DockIconMenu',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function(source) {
|
||||
let side;
|
||||
switch (source._settings.get_enum(DOCK_POSITION_KEY)) {
|
||||
case PositionMode.LEFT:
|
||||
side = St.Side.LEFT;
|
||||
break;
|
||||
case PositionMode.RIGHT:
|
||||
default:
|
||||
side = St.Side.RIGHT;
|
||||
}
|
||||
this.parent(source.actor, 0.5, side);
|
||||
|
||||
this._source = source;
|
||||
|
||||
this.connect('activate', Lang.bind(this, this._onActivate));
|
||||
|
||||
this.actor.add_style_class_name('dock-menu');
|
||||
|
||||
// Chain our visibility and lifecycle to that of the source
|
||||
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
||||
if (!source.actor.mapped)
|
||||
this.close();
|
||||
}));
|
||||
source.actor.connect('destroy', Lang.bind(this, function () { this.destroy(); }));
|
||||
|
||||
Main.layoutManager.addChrome(this.actor);
|
||||
},
|
||||
|
||||
redisplay: function() {
|
||||
this.removeAll();
|
||||
|
||||
let windows = this._source.app.get_windows();
|
||||
|
||||
// Display the app windows menu items and the separator between windows
|
||||
// of the current desktop and other windows.
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
|
||||
this._appendSeparator();
|
||||
separatorShown = true;
|
||||
}
|
||||
let item = this._appendMenuItem(windows[i].title);
|
||||
item._window = windows[i];
|
||||
}
|
||||
|
||||
if (windows.length > 0)
|
||||
this._appendSeparator();
|
||||
|
||||
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
||||
|
||||
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(_("New Window")) : null;
|
||||
|
||||
this._quitAppMenuItem = windows.length >0 ? this._appendMenuItem(_("Quit Application")) : null;
|
||||
|
||||
if (windows.length > 0)
|
||||
this._appendSeparator();
|
||||
this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ?
|
||||
_("Remove from Favorites")
|
||||
: _("Add to Favorites"));
|
||||
|
||||
this._highlightedItem = null;
|
||||
},
|
||||
|
||||
_appendSeparator: function () {
|
||||
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.addMenuItem(separator);
|
||||
},
|
||||
|
||||
_appendMenuItem: function(labelText) {
|
||||
// FIXME: app-well-menu-item style
|
||||
let item = new PopupMenu.PopupMenuItem(labelText);
|
||||
this.addMenuItem(item);
|
||||
return item;
|
||||
},
|
||||
|
||||
popup: function(activatingButton) {
|
||||
this._redisplay();
|
||||
this.open();
|
||||
},
|
||||
|
||||
_onActivate: function (actor, child) {
|
||||
if (child._window) {
|
||||
let metaWindow = child._window;
|
||||
this.emit('activate-window', metaWindow);
|
||||
} else if (child == this._newWindowMenuItem) {
|
||||
let current_workspace = global.screen.get_active_workspace().index();
|
||||
this._source.app.open_new_window(current_workspace);
|
||||
this.emit('activate-window', null);
|
||||
} else if (child == this._quitAppMenuItem) {
|
||||
this._source.app.request_quit();
|
||||
} else if (child == this._toggleFavoriteMenuItem) {
|
||||
let favs = AppFavorites.getAppFavorites();
|
||||
let isFavorite = favs.isFavorite(this._source.app.get_id());
|
||||
if (isFavorite)
|
||||
favs.removeFavorite(this._source.app.get_id());
|
||||
else
|
||||
favs.addFavorite(this._source.app.get_id());
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
let dock;
|
||||
|
||||
function enable() {
|
||||
dock = new Dock();
|
||||
}
|
||||
|
||||
function disable() {
|
||||
dock.destroy();
|
||||
dock = null;
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Screenshot Window Sizer",
|
||||
"description": "Resize windows for GNOME Software screenshots",
|
||||
"name": "Dock",
|
||||
"description": "A dock for the GNOME Shell -- displays favorite and running applications",
|
||||
"original-author": "tclaesson@gmail.com",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<enum id='org.gnome.shell.extensions.dock.PositionMode'>
|
||||
<value nick='left' value='0'/>
|
||||
<value nick='right' value='1'/>
|
||||
</enum>
|
||||
<enum id='org.gnome.shell.extensions.dock.AutoHideEffect'>
|
||||
<value nick='resize' value='0'/>
|
||||
<value nick='rescale' value='1'/>
|
||||
<value nick='move' value='2'/>
|
||||
</enum>
|
||||
|
||||
<schema id="org.gnome.shell.extensions.dock" path="/org/gnome/shell/extensions/dock/">
|
||||
<key name="position" enum="org.gnome.shell.extensions.dock.PositionMode">
|
||||
<default>'right'</default>
|
||||
<_summary>Position of the dock</_summary>
|
||||
<_description>Sets the position of the dock in the screen. Allowed values are 'right' or 'left'</_description>
|
||||
</key>
|
||||
<key name="size" type="i">
|
||||
<default>48</default>
|
||||
<_summary>Icon size</_summary>
|
||||
<_description>Sets icon size of the dock.</_description>
|
||||
</key>
|
||||
<key name="autohide" type="b">
|
||||
<default>true</default>
|
||||
<_summary>Enable/disable autohide</_summary>
|
||||
</key>
|
||||
<key name="hide-effect" enum="org.gnome.shell.extensions.dock.AutoHideEffect">
|
||||
<default>'move'</default>
|
||||
<_summary>Autohide effect</_summary>
|
||||
<_description>Sets the effect of the hide dock. Allowed values are 'resize', 'rescale' and 'move'</_description>
|
||||
</key>
|
||||
<key name="hide-effect-duration" type="d">
|
||||
<default>0.3</default>
|
||||
<_summary>Autohide duration</_summary>
|
||||
<_description>Sets the time duration of the autohide effect.</_description>
|
||||
</key>
|
||||
<key name="monitor" type="i">
|
||||
<default>-1</default>
|
||||
<_summary>Monitor</_summary>
|
||||
<_description>Sets monitor to display dock in. The default value (-1) is the primary monitor.</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
10
extensions/dock/stylesheet.css
Normal file
10
extensions/dock/stylesheet.css
Normal file
@@ -0,0 +1,10 @@
|
||||
#dock {
|
||||
border-radius: 9px;
|
||||
background-color: rgba(0,0,0,0.9);
|
||||
border-width: 2px;
|
||||
border-color: #5f5f5f;
|
||||
}
|
||||
|
||||
.dock-menu {
|
||||
font-size: 12px
|
||||
}
|
||||
3
extensions/drive-menu/Makefile.am
Normal file
3
extensions/drive-menu/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = drive-menu
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,208 +1,186 @@
|
||||
// Drive menu extension
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Gio from 'gi://Gio';
|
||||
import GObject from 'gi://GObject';
|
||||
import Shell from 'gi://Shell';
|
||||
import St from 'gi://St';
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
import * as ShellMountOperation from 'resource:///org/gnome/shell/ui/shellMountOperation.js';
|
||||
const Main = imports.ui.main;
|
||||
const Panel = imports.ui.panel;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
|
||||
Gio._promisify(Gio.File.prototype, 'query_filesystem_info_async');
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
class MountMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
const MountMenuItem = new Lang.Class({
|
||||
Name: 'DriveMenu.MountMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
_init: function(mount) {
|
||||
this.parent();
|
||||
|
||||
this.label = new St.Label({ text: mount.get_name() });
|
||||
this.addActor(this.label);
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this.mount = mount;
|
||||
|
||||
let ejectIcon = new St.Icon({ icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon ' });
|
||||
let ejectButton = new St.Button({ child: ejectIcon });
|
||||
ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.addActor(ejectButton);
|
||||
},
|
||||
|
||||
_eject: function() {
|
||||
let mountOp = new ShellMountOperation.ShellMountOperation(this.mount);
|
||||
|
||||
if (this.mount.can_eject())
|
||||
this.mount.eject_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._ejectFinish));
|
||||
else
|
||||
this.mount.unmount_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._unmountFinish));
|
||||
},
|
||||
|
||||
_unmountFinish: function(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish: function(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_reportFailure: function(exception) {
|
||||
let msg = _("Ejecting drive '%s' failed:").format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
Gio.AppInfo.launch_default_for_uri(this.mount.get_root().get_uri(),
|
||||
global.create_app_launch_context());
|
||||
|
||||
this.parent(event);
|
||||
}
|
||||
});
|
||||
|
||||
constructor(mount) {
|
||||
super({
|
||||
style_class: 'drive-menu-item',
|
||||
});
|
||||
const DriveMenu = new Lang.Class({
|
||||
Name: 'DriveMenu.DriveMenu',
|
||||
Extends: PanelMenu.SystemStatusButton,
|
||||
|
||||
this.label = new St.Label({
|
||||
text: mount.get_name(),
|
||||
x_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_child(this.label);
|
||||
this.label_actor = this.label;
|
||||
_init: function() {
|
||||
this.parent('media-eject-symbolic', _("Removable devices"));
|
||||
|
||||
this.mount = mount;
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
this._addedId = this._monitor.connect('mount-added', Lang.bind(this, function(monitor, mount) {
|
||||
this._addMount(mount);
|
||||
this._updateMenuVisibility();
|
||||
}));
|
||||
this._removedId = this._monitor.connect('mount-removed', Lang.bind(this, function(monitor, mount) {
|
||||
this._removeMount(mount);
|
||||
this._updateMenuVisibility();
|
||||
}));
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this._mounts = [ ];
|
||||
|
||||
let ejectIcon = new St.Icon({
|
||||
icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon',
|
||||
});
|
||||
let ejectButton = new St.Button({
|
||||
child: ejectIcon,
|
||||
style_class: 'button',
|
||||
});
|
||||
ejectButton.connect('clicked', this._eject.bind(this));
|
||||
this.add(ejectButton);
|
||||
this._monitor.get_mounts().forEach(Lang.bind(this, this._addMount));
|
||||
|
||||
this.hide();
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Open File"), function(event) {
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let app = appSystem.lookup_app('nautilus.desktop');
|
||||
app.activate_full(-1, event.get_time());
|
||||
});
|
||||
|
||||
mount.connectObject('changed',
|
||||
() => this._syncVisibility(), this);
|
||||
this._syncVisibility();
|
||||
}
|
||||
this._updateMenuVisibility();
|
||||
},
|
||||
|
||||
async _isInteresting() {
|
||||
if (!this.mount.can_eject() && !this.mount.can_unmount())
|
||||
return false;
|
||||
if (this.mount.is_shadowed())
|
||||
_updateMenuVisibility: function() {
|
||||
if (this._mounts.length > 0)
|
||||
this.actor.show();
|
||||
else
|
||||
this.actor.hide();
|
||||
},
|
||||
|
||||
_isMountInteresting: function(mount) {
|
||||
if (!mount.can_eject() && !mount.can_unmount())
|
||||
return false;
|
||||
|
||||
let volume = this.mount.get_volume();
|
||||
let volume = mount.get_volume();
|
||||
|
||||
if (volume)
|
||||
return volume.get_identifier('class') !== 'network';
|
||||
|
||||
const root = this.mount.get_root();
|
||||
|
||||
try {
|
||||
const attr = Gio.FILE_ATTRIBUTE_FILESYSTEM_REMOTE;
|
||||
const info = await root.query_filesystem_info_async(attr, null);
|
||||
return !info.get_attribute_boolean(attr);
|
||||
} catch (e) {
|
||||
log(`Failed to query filesystem: ${e.message}`);
|
||||
if (volume == null) {
|
||||
// probably a GDaemonMount, could be network or
|
||||
// local, but we can't tell; assume it's local for now
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hack, fall back to looking at GType
|
||||
return Gio._LocalFilePrototype.isPrototypeOf(root);
|
||||
}
|
||||
return volume.get_identifier('class') != 'network';
|
||||
},
|
||||
|
||||
async _syncVisibility() {
|
||||
this.visible = await this._isInteresting();
|
||||
}
|
||||
_addMount: function(mount) {
|
||||
if (!this._isMountInteresting(mount))
|
||||
return;
|
||||
|
||||
_eject() {
|
||||
let unmountArgs = [
|
||||
Gio.MountUnmountFlags.NONE,
|
||||
new ShellMountOperation.ShellMountOperation(this.mount).mountOp,
|
||||
null, // Gio.Cancellable
|
||||
];
|
||||
let item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
},
|
||||
|
||||
if (this.mount.can_eject()) {
|
||||
this.mount.eject_with_operation(...unmountArgs,
|
||||
this._ejectFinish.bind(this));
|
||||
} else {
|
||||
this.mount.unmount_with_operation(...unmountArgs,
|
||||
this._unmountFinish.bind(this));
|
||||
_removeMount: function(mount) {
|
||||
if (!this._isMountInteresting(mount))
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this._mounts.length; i++) {
|
||||
let item = this._mounts[i];
|
||||
if (item.mount == mount) {
|
||||
item.destroy();
|
||||
this._mounts.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log ('Removing a mount that was never added to the menu');
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._connectedId) {
|
||||
this._monitor.disconnect(this._connectedId);
|
||||
this._monitor.disconnect(this._disconnectedId);
|
||||
this._connectedId = 0;
|
||||
this._disconnectedId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_unmountFinish(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch (e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
});
|
||||
|
||||
_ejectFinish(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch (e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
_reportFailure(exception) {
|
||||
// TRANSLATORS: %s is the filesystem name
|
||||
let msg = _('Ejecting drive “%s” failed:').format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
}
|
||||
|
||||
activate(event) {
|
||||
let uri = this.mount.get_root().get_uri();
|
||||
let context = global.create_app_launch_context(event.get_time(), -1);
|
||||
Gio.AppInfo.launch_default_for_uri(uri, context);
|
||||
|
||||
super.activate(event);
|
||||
}
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
class DriveMenu extends PanelMenu.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
let _indicator;
|
||||
|
||||
constructor() {
|
||||
super(0.5, _('Removable devices'));
|
||||
|
||||
let icon = new St.Icon({
|
||||
icon_name: 'media-eject-symbolic',
|
||||
style_class: 'system-status-icon',
|
||||
});
|
||||
|
||||
this.add_child(icon);
|
||||
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
this._monitor.connectObject(
|
||||
'mount-added', (monitor, mount) => this._addMount(mount),
|
||||
'mount-removed', (monitor, mount) => {
|
||||
this._removeMount(mount);
|
||||
this._updateMenuVisibility();
|
||||
}, this);
|
||||
|
||||
this._mounts = [];
|
||||
|
||||
this._monitor.get_mounts().forEach(this._addMount.bind(this));
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_('Open Files'), event => {
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let app = appSystem.lookup_app('org.gnome.Nautilus.desktop');
|
||||
app.activate_full(-1, event.get_time());
|
||||
});
|
||||
|
||||
this._updateMenuVisibility();
|
||||
}
|
||||
|
||||
_updateMenuVisibility() {
|
||||
if (this._mounts.filter(i => i.visible).length > 0)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
}
|
||||
|
||||
_addMount(mount) {
|
||||
let item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
|
||||
item.connect('notify::visible', () => this._updateMenuVisibility());
|
||||
}
|
||||
|
||||
_removeMount(mount) {
|
||||
for (let i = 0; i < this._mounts.length; i++) {
|
||||
let item = this._mounts[i];
|
||||
if (item.mount === mount) {
|
||||
item.destroy();
|
||||
this._mounts.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log('Removing a mount that was never added to the menu');
|
||||
}
|
||||
function enable() {
|
||||
_indicator = new DriveMenu;
|
||||
Main.panel.addToStatusArea('drive-menu', _indicator);
|
||||
}
|
||||
|
||||
export default class PlaceMenuExtension extends Extension {
|
||||
enable() {
|
||||
this._indicator = new DriveMenu();
|
||||
Main.panel.addToStatusArea('drive-menu', this._indicator);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._indicator.destroy();
|
||||
delete this._indicator;
|
||||
}
|
||||
function disable() {
|
||||
_indicator.destroy();
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
extension_data += files('stylesheet.css')
|
||||
@@ -4,7 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Removable Drive Menu",
|
||||
"description": "A status menu for accessing and unmounting removable devices.",
|
||||
"description": "A status menu for accessing and unmounting removable devices",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
.drive-menu-item { spacing: 12px; }
|
||||
|
||||
.drive-menu-item .button {
|
||||
border-radius: 99px;
|
||||
padding: 3px;
|
||||
min-height: auto;
|
||||
}
|
||||
/* This extensions requires no custom styling */
|
||||
|
||||
6
extensions/example/Makefile.am
Normal file
6
extensions/example/Makefile.am
Normal file
@@ -0,0 +1,6 @@
|
||||
EXTENSION_ID = example
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
49
extensions/example/extension.js
Normal file
49
extensions/example/extension.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Sample extension code, makes clicking on the panel show a message
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
function _showHello() {
|
||||
let settings = Convenience.getSettings();
|
||||
let text = settings.get_string('hello-text') || _("Hello, world!");
|
||||
|
||||
let label = new St.Label({ style_class: 'helloworld-label', text: text });
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
global.stage.add_actor(label);
|
||||
label.set_position(Math.floor (monitor.width / 2 - label.width / 2), Math.floor(monitor.height / 2 - label.height / 2));
|
||||
Mainloop.timeout_add(3000, function () { label.destroy(); });
|
||||
}
|
||||
|
||||
// Put your extension initialization code here
|
||||
function init(metadata) {
|
||||
log ('Example extension initalized');
|
||||
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
let signalId;
|
||||
|
||||
function enable() {
|
||||
log ('Example extension enabled');
|
||||
|
||||
Main.panel.actor.reactive = true;
|
||||
signalId = Main.panel.actor.connect('button-release-event', _showHello);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
log ('Example extension disabled');
|
||||
|
||||
if (signalId) {
|
||||
Main.panel.actor.disconnect(signalId);
|
||||
signalId = 0;
|
||||
}
|
||||
}
|
||||
10
extensions/example/metadata.json.in
Normal file
10
extensions/example/metadata.json.in
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extension-id": "@extension_id@",
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Hello, World!",
|
||||
"description": "An example extension to show how it works. Shows Hello, world when clicking on the top panel.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<schema id="org.gnome.shell.extensions.example" path="/org/gnome/shell/extensions/example/">
|
||||
<key name="hello-text" type="s">
|
||||
<default>''</default>
|
||||
<_summary>Alternative greeting text.</_summary>
|
||||
<_description>If not empty, it contains the text that will be shown when clicking on the panel.</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
52
extensions/example/prefs.js
Normal file
52
extensions/example/prefs.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
const ExamplePrefsWidget = new GObject.Class({
|
||||
Name: 'Example.Prefs.Widget',
|
||||
GTypeName: 'ExamplePrefsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this.margin = this.row_spacing = this.column_spacing = 10;
|
||||
|
||||
// TRANSLATORS: Example is the name of the extension, should not be
|
||||
// translated
|
||||
let primaryText = _("Example aims to show how to build well behaved \
|
||||
extensions for the Shell and as such it has little functionality on its own.\n\
|
||||
Nevertheless it's possible to customize the greeting message.");
|
||||
|
||||
this.attach(new Gtk.Label({ label: primaryText, wrap: true }), 0, 0, 2, 1);
|
||||
|
||||
this.attach(new Gtk.Label({ label: '<b>' + _("Message:") + '</b>', use_markup: true }),
|
||||
0, 1, 1, 1);
|
||||
|
||||
let entry = new Gtk.Entry({ hexpand: true });
|
||||
this.attach(entry, 1, 1, 1, 1);
|
||||
|
||||
this._settings = Convenience.getSettings();
|
||||
this._settings.bind('hello-text', entry, 'text', Gio.SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
});
|
||||
|
||||
function buildPrefsWidget() {
|
||||
let widget = new ExamplePrefsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
.screenshot-sizer-message {
|
||||
/* Example stylesheet */
|
||||
.helloworld-label {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: rgba(10,10,10,0.7);
|
||||
border-radius: 5px;
|
||||
padding: .5em;
|
||||
}
|
||||
3
extensions/gajim/Makefile.am
Normal file
3
extensions/gajim/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = gajim
|
||||
|
||||
include ../../extension.mk
|
||||
339
extensions/gajim/extension.js
Normal file
339
extensions/gajim/extension.js
Normal file
@@ -0,0 +1,339 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Tp = imports.gi.TelepathyGLib;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Mainloop = imports.mainloop;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const Shell = imports.gi.Shell;
|
||||
const TelepathyClient = imports.ui.telepathyClient;
|
||||
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
// http://ntt.cc/ext/base64-Encoding-Decoding.html
|
||||
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
function decode64(input) {
|
||||
let output = "";
|
||||
let chr1, chr2, chr3;
|
||||
let enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
do {
|
||||
enc1 = keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output = output + String.fromCharCode(chr1);
|
||||
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
|
||||
chr1 = chr2 = chr3 = "";
|
||||
enc1 = enc2 = enc3 = enc4 = "";
|
||||
|
||||
} while (i < input.length);
|
||||
|
||||
return unescape(output);
|
||||
}
|
||||
|
||||
function wrappedText(text, sender, timestamp, direction) {
|
||||
if (!timestamp)
|
||||
timestamp = (Date.now() / 1000);
|
||||
return {
|
||||
messageType: Tp.ChannelTextMessageType.NORMAL,
|
||||
text: text,
|
||||
sender: sender,
|
||||
timestamp: timestamp,
|
||||
direction: direction
|
||||
};
|
||||
}
|
||||
|
||||
function Source(gajimClient, accountName, author, initialMessage) {
|
||||
this._init(gajimClient, accountName, author, initialMessage);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(gajimClient, accountName, author, initialMessage) {
|
||||
MessageTray.Source.prototype._init.call(this, author);
|
||||
this.isChat = true;
|
||||
this._author = author;
|
||||
this._gajimClient = gajimClient;
|
||||
this._accountName = accountName;
|
||||
this._initialMessage = initialMessage;
|
||||
this._iconUri = null;
|
||||
this._presence = "online";
|
||||
|
||||
this._notification = new TelepathyClient.ChatNotification(this);
|
||||
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
||||
|
||||
let jid = author.split('/')[0];
|
||||
let proxy = this._gajimClient.proxy();
|
||||
proxy.contact_infoRemote(jid, Lang.bind(this, this._gotContactInfos));
|
||||
this._statusChangeId = proxy.connect('ContactStatus',
|
||||
Lang.bind(this, this._onStatusChange));
|
||||
this._contactAbsenceId = proxy.connect('ContactAbsence',
|
||||
Lang.bind(this, this._onStatusChange));
|
||||
this._chatStateId = proxy.connect('ChatState',
|
||||
Lang.bind(this, this._onChatState));
|
||||
this._messageSentId = proxy.connect('MessageSent',
|
||||
Lang.bind(this, this._messageSent));
|
||||
this._newMessageId = proxy.connect('NewMessage',
|
||||
Lang.bind(this, this._messageReceived));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
let proxy = this._gajimClient.proxy();
|
||||
proxy.disconnect(this._statusChangeId);
|
||||
proxy.disconnect(this._contactAbsenceId);
|
||||
proxy.disconnect(this._chatStateId);
|
||||
proxy.disconnect(this._messageSentId);
|
||||
proxy.disconnect(this._newMessageId);
|
||||
MessageTray.Source.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
_gotContactInfos: function(result, excp) {
|
||||
this.title = result['FN'] || result['NICKNAME'] || result['jid'];
|
||||
|
||||
let avatarUri = null;
|
||||
if (result['PHOTO']) {
|
||||
let mimeType = result['PHOTO']['TYPE'];
|
||||
let avatarData = decode64(result['PHOTO']['BINVAL']);
|
||||
let sha = result['PHOTO']['SHA'];
|
||||
avatarUri = this._gajimClient.cacheAvatar(mimeType, sha, avatarData);
|
||||
}
|
||||
|
||||
this._iconUri = avatarUri;
|
||||
this._setSummaryIcon(this.createNotificationIcon());
|
||||
|
||||
let message = wrappedText(this._initialMessage, this._author, null, TelepathyClient.NotificationDirection.RECEIVED);
|
||||
this._notification.appendMessage(message, false);
|
||||
|
||||
if (!Main.messageTray.contains(this))
|
||||
Main.messageTray.add(this);
|
||||
|
||||
this.notify(this._notification);
|
||||
},
|
||||
|
||||
createNotificationIcon: function() {
|
||||
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||
iconBox._size = this.ICON_SIZE;
|
||||
|
||||
if (!this._iconUri) {
|
||||
iconBox.child = new St.Icon({ icon_name: 'avatar-default',
|
||||
icon_size: iconBox._size });
|
||||
} else {
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
iconBox.child = textureCache.load_uri_async(this._iconUri, iconBox._size, iconBox._size);
|
||||
}
|
||||
return iconBox;
|
||||
},
|
||||
|
||||
open: function(notification) {
|
||||
// Lookup for the messages window and display it. In the case where it's not o
|
||||
// opened yet fallback to the roster window.
|
||||
let windows = global.get_window_actors();
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let metaWindow = windows[i].metaWindow;
|
||||
if (metaWindow.get_wm_class_instance() == "gajim" &&
|
||||
metaWindow.get_role() == "messages") {
|
||||
Main.activateWindow(metaWindow);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let app = Shell.AppSystem.get_default().lookup_app('gajim.desktop');
|
||||
app.activate(-1);
|
||||
},
|
||||
|
||||
_onChatState: function(emitter, data) {
|
||||
let chatstate = data[1][5];
|
||||
if (chatstate == 'gone')
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
setChatState: function(state) {
|
||||
// Gajim DBUS API doesn't support sending chatstate yet.
|
||||
},
|
||||
|
||||
_messageReceived: function(emitter, data) {
|
||||
let author = data[1][0];
|
||||
let text = data[1][1];
|
||||
if (text && (author == this._author)) {
|
||||
let message = wrappedText(text, this._author, null, TelepathyClient.NotificationDirection.RECEIVED);
|
||||
this._notification.appendMessage(message, false);
|
||||
this.notify(this._notification);
|
||||
}
|
||||
},
|
||||
|
||||
_messageSent: function(emitter, data) {
|
||||
let recipient = data[1][0];
|
||||
let text = data[1][1];
|
||||
let chatstate = data[1][3];
|
||||
|
||||
if (text && (recipient == this._author)) {
|
||||
let message = wrappedText(text, this._author, null, TelepathyClient.NotificationDirection.SENT);
|
||||
this._notification.appendMessage(message, false);
|
||||
} else if (chatstate == 'gone')
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
notify: function() {
|
||||
|
||||
MessageTray.Source.prototype.notify.call(this, this._notification);
|
||||
},
|
||||
|
||||
respond: function(text) {
|
||||
let jid = this._author;
|
||||
let keyID = ""; // unencrypted.
|
||||
this._gajimClient.proxy().send_chat_messageRemote(jid, text, keyID, this._accountName);
|
||||
},
|
||||
|
||||
_onStatusChange: function(emitter, data) {
|
||||
if (!this.title)
|
||||
return;
|
||||
|
||||
let jid = data[1][0];
|
||||
let presence = data[1][1];
|
||||
let message = data[1][2];
|
||||
|
||||
if (jid != this._author.split('/')[0])
|
||||
return;
|
||||
|
||||
let presenceMessage, shouldNotify, title;
|
||||
title = GLib.markup_escape_text(this.title, -1);
|
||||
if (presence == "away") {
|
||||
presenceMessage = _("%s is away.").format(title);
|
||||
shouldNotify = false;
|
||||
} else if (presence == "offline") {
|
||||
presenceMessage = _("%s is offline.").format(title);
|
||||
shouldNotify = (this._presence != "offline");
|
||||
} else if (presence == "online") {
|
||||
presenceMessage = _("%s is online.").format(title);
|
||||
shouldNotify = (this._presence == "offline");
|
||||
} else if (presence == "dnd") {
|
||||
presenceMessage = _("%s is busy.").format(title);
|
||||
shouldNotify = false;
|
||||
} else
|
||||
return;
|
||||
|
||||
this._presence = presence;
|
||||
|
||||
if (message)
|
||||
presenceMessage += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
|
||||
|
||||
this._notification.appendPresence(presenceMessage, shouldNotify);
|
||||
if (shouldNotify)
|
||||
this.notify(this._notification);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const GajimIface = {
|
||||
name: 'org.gajim.dbus.RemoteInterface',
|
||||
properties: [],
|
||||
methods: [{ name: 'send_chat_message', inSignature: 'ssss', outSignature: 'b'},
|
||||
{ name: 'contact_info', inSignature: 's', outSignature: 'a{sv}'}],
|
||||
signals: [{ name: 'NewMessage', inSignature: 'av' },
|
||||
{ name: 'ChatState', inSignature: 'av' },
|
||||
{ name: 'ContactStatus', inSignature: 'av' },
|
||||
{ name: 'ContactAbsence', inSignature: 'av' },
|
||||
{ name: 'MessageSent', inSignature: 'av' }]
|
||||
};
|
||||
|
||||
let Gajim = DBus.makeProxyClass(GajimIface);
|
||||
|
||||
function GajimClient() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
GajimClient.prototype = {
|
||||
_init: function() {
|
||||
this._sources = {};
|
||||
},
|
||||
|
||||
proxy : function() {
|
||||
return this._proxy;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._cacheDir = GLib.get_user_cache_dir() + '/gnome-shell/gajim-avatars';
|
||||
let dir = Gio.file_new_for_path(this._cacheDir);
|
||||
if (!dir.query_exists(null)) {
|
||||
GLib.mkdir_with_parents(this._cacheDir, 0x1c0); // 0x1c0 = octal 0700
|
||||
}
|
||||
|
||||
this._proxy = new Gajim(DBus.session, 'org.gajim.dbus', '/org/gajim/dbus/RemoteObject');
|
||||
this._newMessageId = this._proxy.connect('NewMessage', Lang.bind(this, this._messageReceived));
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if (this._newMessageId) {
|
||||
this._proxy.disconnect(this._newMessageId);
|
||||
this._newMessageId = 0;
|
||||
}
|
||||
this._proxy = null;
|
||||
|
||||
for (let id in this._sources)
|
||||
this._sources[id].destroy();
|
||||
|
||||
this._sources = { };
|
||||
},
|
||||
|
||||
_messageReceived : function(emitter, data) {
|
||||
let author = data[1][0];
|
||||
let message = data[1][1];
|
||||
let account = data[0];
|
||||
let source = this._sources[author];
|
||||
if (!source) {
|
||||
source = new Source(this, account, author, message);
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._sources[author];
|
||||
}));
|
||||
this._sources[author] = source;
|
||||
}
|
||||
},
|
||||
|
||||
cacheAvatar : function(mimeType, sha, avatarData) {
|
||||
let ext = mimeType.split('/')[1];
|
||||
let file = this._cacheDir + '/' + sha + '.' + ext;
|
||||
let uri = GLib.filename_to_uri(file, null);
|
||||
|
||||
if (GLib.file_test(file, GLib.FileTest.EXISTS))
|
||||
return uri;
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
success = GLib.file_set_contents(file, avatarData, avatarData.length);
|
||||
} catch (e) {
|
||||
logError(e, 'Error caching avatar data');
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function init() {
|
||||
return new GajimClient();
|
||||
}
|
||||
10
extensions/gajim/metadata.json.in
Normal file
10
extensions/gajim/metadata.json.in
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extension-id": "@extension_id@",
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Gajim IM integration",
|
||||
"description": "Display Gajim incoming chats as notifications in the Shell message tray.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "http://base-art.net"
|
||||
}
|
||||
1
extensions/gajim/stylesheet.css
Normal file
1
extensions/gajim/stylesheet.css
Normal file
@@ -0,0 +1 @@
|
||||
/* none used */
|
||||
@@ -1,22 +0,0 @@
|
||||
import {AppIcon} from 'resource:///org/gnome/shell/ui/appDisplay.js';
|
||||
import {InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class Extension {
|
||||
constructor() {
|
||||
this._injectionManager = new InjectionManager();
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._injectionManager.overrideMethod(AppIcon.prototype, 'activate',
|
||||
originalMethod => {
|
||||
return function () {
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
originalMethod.call(this, 2);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._injectionManager.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extension-id": "@extension_id@",
|
||||
"uuid": "@uuid@",
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Launch new instance",
|
||||
"description": "Always launch a new instance when clicking in the dash or the application view.\nThis extension is part of Classic Mode and is officially supported by GNOME. Please do not report bugs using the form below, use GNOME's GitLab instance instead.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
|
||||
export default class Extension {
|
||||
_updateColorScheme(scheme) {
|
||||
Main.sessionMode.colorScheme = scheme;
|
||||
St.Settings.get().notify('color-scheme');
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._savedColorScheme = Main.sessionMode.colorScheme;
|
||||
this._updateColorScheme('prefer-light');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._updateColorScheme(this._savedColorScheme);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
extension_schemas = []
|
||||
js_sources = []
|
||||
|
||||
metadata_name = 'metadata.json'
|
||||
|
||||
foreach e : enabled_extensions
|
||||
uuid = e + uuid_suffix
|
||||
|
||||
metadata_conf = configuration_data()
|
||||
metadata_conf.set('extension_id', e)
|
||||
metadata_conf.set('uuid', uuid)
|
||||
metadata_conf.set('gschemaname', 'org.gnome.shell.extensions.' + e)
|
||||
metadata_conf.set('gettext_domain', gettext_domain)
|
||||
metadata_conf.set('shell_current', shell_version)
|
||||
metadata_conf.set('url', 'https://gitlab.gnome.org/GNOME/gnome-shell-extensions')
|
||||
|
||||
extension_sources = files(e + '/extension.js')
|
||||
extension_data = []
|
||||
|
||||
subdir(e)
|
||||
|
||||
install_data (extension_sources + extension_data,
|
||||
install_dir: join_paths(extensiondir, uuid)
|
||||
)
|
||||
endforeach
|
||||
|
||||
install_data (extension_schemas,
|
||||
install_dir: schemadir
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
# extension_data += files('stylesheet.css')
|
||||
# extension_sources += files('prefs.js')
|
||||
# extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
5
extensions/native-window-placement/Makefile.am
Normal file
5
extensions/native-window-placement/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = native-window-placement
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
|
||||
@@ -1,139 +1,199 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
import Clutter from 'gi://Clutter';
|
||||
// import just everything from workspace.js:
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
|
||||
import {Extension, InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
const DND = imports.ui.dnd;
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const Panel = imports.ui.panel;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import {WindowPreview} from 'resource:///org/gnome/shell/ui/windowPreview.js';
|
||||
import * as Workspace from 'resource:///org/gnome/shell/ui/workspace.js';
|
||||
const Workspace = imports.ui.workspace;
|
||||
const WindowPositionFlags = Workspace.WindowPositionFlags;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
// testing settings for natural window placement strategy:
|
||||
const WINDOW_PLACEMENT_NATURAL_FILLGAPS = true; // enlarge windows at the end to fill gaps // not implemented yet
|
||||
const WINDOW_PLACEMENT_NATURAL_GRID_FALLBACK = true; // fallback to grid mode if all windows have the same size and positions. // not implemented yet
|
||||
const WINDOW_PLACEMENT_NATURAL_ACCURACY = 20; // accuracy of window translate moves (KDE-default: 20)
|
||||
const WINDOW_PLACEMENT_NATURAL_GAPS = 5; // half of the minimum gap between windows
|
||||
const WINDOW_PLACEMENT_NATURAL_MAX_TRANSLATIONS = 5000; // safety limit for preventing endless loop if something is wrong in the algorithm
|
||||
|
||||
class Rect {
|
||||
constructor(x, y, width, height) {
|
||||
const PLACE_WINDOW_CAPTIONS_ON_TOP = true; // place window titles in overview on top of windows with overlap parameter
|
||||
|
||||
const WORKSPACE_BORDER_GAP = 10; // minimum gap between the workspace area and the workspace selector
|
||||
const WINDOW_AREA_TOP_GAP = 20; // minimum gap between the workspace area and the top border. This keeps window captions and close buttons visible. 13px (26/2) should currently be enough.
|
||||
|
||||
const BUTTON_LAYOUT_SCHEMA = 'org.gnome.shell.overrides';
|
||||
const BUTTON_LAYOUT_KEY = 'button-layout';
|
||||
|
||||
function injectToFunction(parent, name, func) {
|
||||
let origin = parent[name];
|
||||
parent[name] = function() {
|
||||
let ret;
|
||||
ret = origin.apply(this, arguments);
|
||||
if (ret === undefined)
|
||||
ret = func.apply(this, arguments);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
const Rect = new Lang.Class({
|
||||
Name: 'NativeWindowPlacement.Rect',
|
||||
|
||||
_init: function(x, y, width, height) {
|
||||
[this.x, this.y, this.width, this.height] = [x, y, width, height];
|
||||
}
|
||||
},
|
||||
|
||||
// used in _calculateWindowTransformationsNatural to replace Meta.Rectangle that is too slow.
|
||||
copy() {
|
||||
/**
|
||||
* used in _calculateWindowTransformationsNatural to replace Meta.Rectangle that is too slow.
|
||||
*/
|
||||
copy: function() {
|
||||
return new Rect(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
},
|
||||
|
||||
union(rect2) {
|
||||
union: function(rect2) {
|
||||
let dest = this.copy();
|
||||
if (rect2.x < dest.x) {
|
||||
if (rect2.x < dest.x)
|
||||
{
|
||||
dest.width += dest.x - rect2.x;
|
||||
dest.x = rect2.x;
|
||||
}
|
||||
if (rect2.y < dest.y) {
|
||||
}
|
||||
if (rect2.y < dest.y)
|
||||
{
|
||||
dest.height += dest.y - rect2.y;
|
||||
dest.y = rect2.y;
|
||||
}
|
||||
}
|
||||
if (rect2.x + rect2.width > dest.x + dest.width)
|
||||
dest.width = rect2.x + rect2.width - dest.x;
|
||||
dest.width = rect2.x + rect2.width - dest.x;
|
||||
if (rect2.y + rect2.height > dest.y + dest.height)
|
||||
dest.height = rect2.y + rect2.height - dest.y;
|
||||
dest.height = rect2.y + rect2.height - dest.y;
|
||||
|
||||
return dest;
|
||||
}
|
||||
},
|
||||
|
||||
adjusted(dx, dy, dx2, dy2) {
|
||||
adjusted: function(dx, dy, dx2, dy2) {
|
||||
let dest = this.copy();
|
||||
dest.x += dx;
|
||||
dest.y += dy;
|
||||
dest.width += -dx + dx2;
|
||||
dest.height += -dy + dy2;
|
||||
return dest;
|
||||
}
|
||||
},
|
||||
|
||||
overlap(rect2) {
|
||||
return !(this.x + this.width <= rect2.x ||
|
||||
rect2.x + rect2.width <= this.x ||
|
||||
this.y + this.height <= rect2.y ||
|
||||
rect2.y + rect2.height <= this.y);
|
||||
}
|
||||
overlap: function(rect2) {
|
||||
return !((this.x + this.width <= rect2.x) ||
|
||||
(rect2.x + rect2.width <= this.x) ||
|
||||
(this.y + this.height <= rect2.y) ||
|
||||
(rect2.y + rect2.height <= this.y));
|
||||
},
|
||||
|
||||
center() {
|
||||
center: function() {
|
||||
return [this.x + this.width / 2, this.y + this.height / 2];
|
||||
}
|
||||
},
|
||||
|
||||
translate(dx, dy) {
|
||||
translate: function(dx, dy) {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
}
|
||||
});
|
||||
|
||||
let winInjections, workspaceInjections, connectedSignals;
|
||||
|
||||
function resetState() {
|
||||
winInjections = { };
|
||||
workspaceInjections = { };
|
||||
connectedSignals = [ ];
|
||||
}
|
||||
|
||||
class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
constructor(params, settings) {
|
||||
super(params);
|
||||
this._settings = settings;
|
||||
}
|
||||
function enable() {
|
||||
resetState();
|
||||
|
||||
computeLayout(windows, _params) {
|
||||
return {
|
||||
windows,
|
||||
};
|
||||
}
|
||||
let settings = Convenience.getSettings();
|
||||
let useMoreScreen = settings.get_boolean('use-more-screen');
|
||||
let signalId = settings.connect('changed::use-more-screen', function() {
|
||||
useMoreScreen = settings.get_boolean('use-more-screen');
|
||||
});
|
||||
connectedSignals.push({ obj: settings, id: signalId });
|
||||
|
||||
/*
|
||||
/**
|
||||
* _calculateWindowTransformationsNatural:
|
||||
* @clones: Array of #MetaWindow
|
||||
*
|
||||
* Returns clones with matching target coordinates and scales to arrange windows in a natural way that no overlap exists and relative window size is preserved.
|
||||
* This function is almost a 1:1 copy of the function
|
||||
* PresentWindowsEffect::calculateWindowTransformationsNatural() from KDE, see:
|
||||
* https://projects.kde.org/projects/kde/kdebase/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp
|
||||
*/
|
||||
computeWindowSlots(layout, area) {
|
||||
Workspace.Workspace.prototype._calculateWindowTransformationsNatural = function(clones) {
|
||||
// As we are using pseudo-random movement (See "slot") we need to make sure the list
|
||||
// is always sorted the same way no matter which window is currently active.
|
||||
clones = clones.sort(function (win1, win2) {
|
||||
return win2.metaWindow.get_stable_sequence() - win1.metaWindow.get_stable_sequence();
|
||||
});
|
||||
|
||||
let areaRect = new Rect(area.x, area.y, area.width, area.height);
|
||||
let bounds = areaRect.copy();
|
||||
let clones = layout.windows;
|
||||
// Put a gap on the right edge of the workspace to separe it from the workspace selector
|
||||
let ratio = this._width / this._height;
|
||||
let x_gap = Math.max(WORKSPACE_BORDER_GAP, WINDOW_AREA_TOP_GAP * ratio);
|
||||
let y_gap = Math.max(WORKSPACE_BORDER_GAP / ratio, WINDOW_AREA_TOP_GAP);
|
||||
let area = new Rect(this._x + x_gap/2, this._y + y_gap, this._width - x_gap, this._height - y_gap);
|
||||
|
||||
let bounds = area.copy();
|
||||
|
||||
let direction = 0;
|
||||
let directions = [];
|
||||
let rects = [];
|
||||
for (let i = 0; i < clones.length; i++) {
|
||||
// save rectangles into 4-dimensional arrays representing two corners of the rectangular: [left_x, top_y, right_x, bottom_y]
|
||||
let rect = clones[i].boundingBox;
|
||||
let rect = clones[i].metaWindow.get_outer_rect();
|
||||
rects[i] = new Rect(rect.x, rect.y, rect.width, rect.height);
|
||||
bounds = bounds.union(rects[i]);
|
||||
|
||||
// This is used when the window is on the edge of the screen to try to use as much screen real estate as possible.
|
||||
directions[i] = direction;
|
||||
direction++;
|
||||
if (direction === 4)
|
||||
if (direction == 4) {
|
||||
direction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let loopCounter = 0;
|
||||
let loop_counter = 0;
|
||||
let overlap;
|
||||
do {
|
||||
overlap = false;
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
for (let j = 0; j < rects.length; j++) {
|
||||
let adjustments = [-1, -1, 1, 1]
|
||||
.map(v => (v *= WINDOW_PLACEMENT_NATURAL_GAPS));
|
||||
let iAdjusted = rects[i].adjusted(...adjustments);
|
||||
let jAdjusted = rects[j].adjusted(...adjustments);
|
||||
if (i !== j && iAdjusted.overlap(jAdjusted)) {
|
||||
loopCounter++;
|
||||
if (i != j && rects[i].adjusted(-WINDOW_PLACEMENT_NATURAL_GAPS, -WINDOW_PLACEMENT_NATURAL_GAPS,
|
||||
WINDOW_PLACEMENT_NATURAL_GAPS, WINDOW_PLACEMENT_NATURAL_GAPS).overlap(
|
||||
rects[j].adjusted(-WINDOW_PLACEMENT_NATURAL_GAPS, -WINDOW_PLACEMENT_NATURAL_GAPS,
|
||||
WINDOW_PLACEMENT_NATURAL_GAPS, WINDOW_PLACEMENT_NATURAL_GAPS))) {
|
||||
loop_counter++;
|
||||
overlap = true;
|
||||
|
||||
// TODO: something like a Point2D would be nicer here:
|
||||
|
||||
// Determine pushing direction
|
||||
let iCenter = rects[i].center();
|
||||
let jCenter = rects[j].center();
|
||||
let diff = [jCenter[0] - iCenter[0], jCenter[1] - iCenter[1]];
|
||||
let i_center = rects[i].center();
|
||||
let j_center = rects[j].center();
|
||||
let diff = [j_center[0] - i_center[0], j_center[1] - i_center[1]];
|
||||
|
||||
// Prevent dividing by zero and non-movement
|
||||
if (diff[0] === 0 && diff[1] === 0)
|
||||
if (diff[0] == 0 && diff[1] == 0)
|
||||
diff[0] = 1;
|
||||
// Try to keep screen/workspace aspect ratio
|
||||
if (bounds.height / bounds.width > areaRect.height / areaRect.width)
|
||||
if ( bounds.height / bounds.width > area.height / area.width )
|
||||
diff[0] *= 2;
|
||||
else
|
||||
diff[1] *= 2;
|
||||
@@ -148,7 +208,7 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
rects[j].translate(diff[0], diff[1]);
|
||||
|
||||
|
||||
if (this._settings.get_boolean('use-more-screen')) {
|
||||
if (useMoreScreen) {
|
||||
// Try to keep the bounding rect the same aspect as the screen so that more
|
||||
// screen real estate is utilised. We do this by splitting the screen into nine
|
||||
// equal sections, if the window center is in any of the corner sections pull the
|
||||
@@ -162,33 +222,33 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
let xSection = Math.round((rects[i].x - bounds.x) / (bounds.width / 3));
|
||||
let ySection = Math.round((rects[i].y - bounds.y) / (bounds.height / 3));
|
||||
|
||||
iCenter = rects[i].center();
|
||||
let i_center = rects[i].center();
|
||||
diff[0] = 0;
|
||||
diff[1] = 0;
|
||||
if (xSection !== 1 || ySection !== 1) { // Remove this if you want the center to pull as well
|
||||
if (xSection === 1)
|
||||
xSection = directions[i] / 2 ? 2 : 0;
|
||||
if (ySection === 1)
|
||||
ySection = directions[i] % 2 ? 2 : 0;
|
||||
if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well
|
||||
if (xSection == 1)
|
||||
xSection = (directions[i] / 2 ? 2 : 0);
|
||||
if (ySection == 1)
|
||||
ySection = (directions[i] % 2 ? 2 : 0);
|
||||
}
|
||||
if (xSection === 0 && ySection === 0) {
|
||||
diff[0] = bounds.x - iCenter[0];
|
||||
diff[1] = bounds.y - iCenter[1];
|
||||
if (xSection == 0 && ySection == 0) {
|
||||
diff[0] = bounds.x - i_center[0];
|
||||
diff[1] = bounds.y - i_center[1];
|
||||
}
|
||||
if (xSection === 2 && ySection === 0) {
|
||||
diff[0] = bounds.x + bounds.width - iCenter[0];
|
||||
diff[1] = bounds.y - iCenter[1];
|
||||
if (xSection == 2 && ySection == 0) {
|
||||
diff[0] = bounds.x + bounds.width - i_center[0];
|
||||
diff[1] = bounds.y - i_center[1];
|
||||
}
|
||||
if (xSection === 2 && ySection === 2) {
|
||||
diff[0] = bounds.x + bounds.width - iCenter[0];
|
||||
diff[1] = bounds.y + bounds.height - iCenter[1];
|
||||
if (xSection == 2 && ySection == 2) {
|
||||
diff[0] = bounds.x + bounds.width - i_center[0];
|
||||
diff[1] = bounds.y + bounds.height - i_center[1];
|
||||
}
|
||||
if (xSection === 0 && ySection === 2) {
|
||||
diff[0] = bounds.x - iCenter[0];
|
||||
diff[1] = bounds.y + bounds.height - iCenter[1];
|
||||
if (xSection == 0 && ySection == 2) {
|
||||
diff[0] = bounds.x - i_center[0];
|
||||
diff[1] = bounds.y + bounds.height - i_center[1];
|
||||
}
|
||||
if (diff[0] !== 0 || diff[1] !== 0) {
|
||||
length = Math.sqrt(diff[0] * diff[0] + diff[1] * diff[1]);
|
||||
if (diff[0] != 0 || diff[1] != 0) {
|
||||
let length = Math.sqrt(diff[0]*diff[0] + diff[1]*diff[1]);
|
||||
diff[0] *= WINDOW_PLACEMENT_NATURAL_ACCURACY / length / 2; // /2 to make it less influencing than the normal center-move above
|
||||
diff[1] *= WINDOW_PLACEMENT_NATURAL_ACCURACY / length / 2;
|
||||
rects[i].translate(diff[0], diff[1]);
|
||||
@@ -201,99 +261,252 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (overlap && loopCounter < WINDOW_PLACEMENT_NATURAL_MAX_TRANSLATIONS);
|
||||
} while (overlap && loop_counter < WINDOW_PLACEMENT_NATURAL_MAX_TRANSLATIONS);
|
||||
|
||||
// Work out scaling by getting the most top-left and most bottom-right window coords.
|
||||
let scale;
|
||||
scale = Math.min(
|
||||
areaRect.width / bounds.width,
|
||||
areaRect.height / bounds.height,
|
||||
1.0);
|
||||
scale = Math.min(area.width / bounds.width,
|
||||
area.height / bounds.height,
|
||||
1.0);
|
||||
|
||||
// Make bounding rect fill the screen size for later steps
|
||||
bounds.x -= (areaRect.width - bounds.width * scale) / 2;
|
||||
bounds.y -= (areaRect.height - bounds.height * scale) / 2;
|
||||
bounds.width = areaRect.width / scale;
|
||||
bounds.height = areaRect.height / scale;
|
||||
bounds.x = bounds.x - (area.width - bounds.width * scale) / 2;
|
||||
bounds.y = bounds.y - (area.height - bounds.height * scale) / 2;
|
||||
bounds.width = area.width / scale;
|
||||
bounds.height = area.height / scale;
|
||||
|
||||
// Move all windows back onto the screen and set their scale
|
||||
for (let i = 0; i < rects.length; i++)
|
||||
rects[i].translate(-bounds.x, -bounds.y);
|
||||
|
||||
|
||||
// rescale to workspace
|
||||
let slots = [];
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
rects[i].x = rects[i].x * scale + areaRect.x;
|
||||
rects[i].y = rects[i].y * scale + areaRect.y;
|
||||
rects[i].width *= scale;
|
||||
rects[i].height *= scale;
|
||||
|
||||
slots.push([rects[i].x, rects[i].y, rects[i].width, rects[i].height, clones[i]]);
|
||||
rects[i].translate(-bounds.x, -bounds.y);
|
||||
}
|
||||
|
||||
return slots;
|
||||
// TODO: Implement the KDE part "Try to fill the gaps by enlarging windows if they have the space" here. (If this is wanted)
|
||||
|
||||
// rescale to workspace
|
||||
let scales = [];
|
||||
|
||||
let buttonOuterHeight, captionHeight;
|
||||
let buttonOuterWidth = 0;
|
||||
|
||||
let targets = [];
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
rects[i].x = rects[i].x * scale + area.x;
|
||||
rects[i].y = rects[i].y * scale + area.y;
|
||||
|
||||
targets[i] = [rects[i].x, rects[i].y, scale];
|
||||
}
|
||||
|
||||
return [clones, targets];
|
||||
}
|
||||
workspaceInjections['_calculateWindowTransformationsNatural'] = undefined;
|
||||
|
||||
/**
|
||||
* _realPositionWindows:
|
||||
* @flags:
|
||||
* INITIAL - this is the initial positioning of the windows.
|
||||
* ANIMATE - Indicates that we need animate changing position.
|
||||
*/
|
||||
workspaceInjections['_realPositionWindows'] = Workspace.Workspace.prototype._realPositionWindows;
|
||||
Workspace.Workspace.prototype._realPositionWindows = function(flags) {
|
||||
if (this._repositionWindowsId > 0) {
|
||||
Mainloop.source_remove(this._repositionWindowsId);
|
||||
this._repositionWindowsId = 0;
|
||||
}
|
||||
|
||||
let clones = this._windows.slice();
|
||||
if (this._reservedSlot)
|
||||
clones.push(this._reservedSlot);
|
||||
|
||||
let initialPositioning = flags & WindowPositionFlags.INITIAL;
|
||||
let animate = flags & WindowPositionFlags.ANIMATE;
|
||||
|
||||
// Start the animations
|
||||
let targets = [];
|
||||
let scales = [];
|
||||
|
||||
[clones, targets] = this._calculateWindowTransformationsNatural(clones);
|
||||
|
||||
let currentWorkspace = global.screen.get_active_workspace();
|
||||
let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace;
|
||||
|
||||
for (let i = 0; i < clones.length; i++) {
|
||||
let clone = clones[i];
|
||||
let [x, y , scale] = targets[i];
|
||||
let metaWindow = clone.metaWindow;
|
||||
let mainIndex = this._lookupIndex(metaWindow);
|
||||
let overlay = this._windowOverlays[mainIndex];
|
||||
|
||||
// Positioning a window currently being dragged must be avoided;
|
||||
// we'll just leave a blank spot in the layout for it.
|
||||
if (clone.inDrag)
|
||||
continue;
|
||||
|
||||
if (overlay && initialPositioning)
|
||||
overlay.hide();
|
||||
if (animate && isOnCurrentWorkspace) {
|
||||
if (!metaWindow.showing_on_its_workspace()) {
|
||||
/* Hidden windows should fade in and grow
|
||||
* therefore we need to resize them now so they
|
||||
* can be scaled up later */
|
||||
if (initialPositioning) {
|
||||
clone.actor.opacity = 0;
|
||||
clone.actor.scale_x = 0;
|
||||
clone.actor.scale_y = 0;
|
||||
clone.actor.x = x;
|
||||
clone.actor.y = y;
|
||||
}
|
||||
|
||||
// Make the window slightly transparent to indicate it's hidden
|
||||
Tweener.addTween(clone.actor,
|
||||
{ opacity: 255,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeInQuad'
|
||||
});
|
||||
}
|
||||
|
||||
this._animateClone(clone, overlay, x, y, scale, initialPositioning);
|
||||
} else {
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
this._updateWindowOverlayPositions(clone, overlay, x, y, scale, false);
|
||||
this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// position window titles on top of windows in overlay ////
|
||||
if (settings.get_boolean('window-captions-on-top')) {
|
||||
winInjections['_init'] = Workspace.WindowOverlay.prototype._init;
|
||||
Workspace.WindowOverlay.prototype._init = function(windowClone, parentActor) {
|
||||
let metaWindow = windowClone.metaWindow;
|
||||
|
||||
this._windowClone = windowClone;
|
||||
this._parentActor = parentActor;
|
||||
this._hidden = false;
|
||||
|
||||
let title = new St.Label({ style_class: 'window-caption',
|
||||
text: metaWindow.title });
|
||||
title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
title._spacing = 0;
|
||||
title._overlap = 0;
|
||||
|
||||
this._updateCaptionId = metaWindow.connect('notify::title', Lang.bind(this, function(w) {
|
||||
this.title.text = w.title;
|
||||
}));
|
||||
|
||||
let button = new St.Button({ style_class: 'window-close' });
|
||||
button._overlap = 0;
|
||||
|
||||
this._idleToggleCloseId = 0;
|
||||
button.connect('clicked', Lang.bind(this, this._closeWindow));
|
||||
|
||||
windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
windowClone.actor.connect('enter-event', Lang.bind(this, this._onEnter));
|
||||
windowClone.actor.connect('leave-event', Lang.bind(this, this._onLeave));
|
||||
|
||||
this._windowAddedId = 0;
|
||||
windowClone.connect('zoom-start', Lang.bind(this, this.hide));
|
||||
windowClone.connect('zoom-end', Lang.bind(this, this.show));
|
||||
|
||||
button.hide();
|
||||
|
||||
this.title = title;
|
||||
this.closeButton = button;
|
||||
|
||||
parentActor.add_actor(this.title);
|
||||
parentActor.add_actor(this.closeButton);
|
||||
title.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
button.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
|
||||
// force a style change if we are already on a stage - otherwise
|
||||
// the signal will be emitted normally when we are added
|
||||
if (parentActor.get_stage())
|
||||
this._onStyleChanged();
|
||||
},
|
||||
|
||||
winInjections['chromeHeights'] = Workspace.WindowOverlay.prototype.chromeHeights;
|
||||
Workspace.WindowOverlay.prototype.chromeHeights = function () {
|
||||
return [Math.max( this.closeButton.height - this.closeButton._overlap, this.title.height - this.title._overlap),
|
||||
0];
|
||||
},
|
||||
|
||||
winInjections['updatePositions'] = Workspace.WindowOverlay.prototype.updatePositions;
|
||||
Workspace.WindowOverlay.prototype.updatePositions = function(cloneX, cloneY, cloneWidth, cloneHeight, animate) {
|
||||
let button = this.closeButton;
|
||||
let title = this.title;
|
||||
|
||||
let settings = new Gio.Settings({ schema: BUTTON_LAYOUT_SCHEMA });
|
||||
let layout = settings.get_string(BUTTON_LAYOUT_KEY);
|
||||
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
|
||||
|
||||
let split = layout.split(":");
|
||||
let side;
|
||||
if (split[0].indexOf("close") > -1)
|
||||
side = rtl ? St.Side.RIGHT : St.Side.LEFT;
|
||||
else
|
||||
side = rtl ? St.Side.LEFT : St.Side.RIGHT;
|
||||
|
||||
let buttonX;
|
||||
let buttonY = cloneY - (button.height - button._overlap);
|
||||
if (side == St.Side.LEFT)
|
||||
buttonX = cloneX - (button.width - button._overlap);
|
||||
else
|
||||
buttonX = cloneX + (cloneWidth - button._overlap);
|
||||
|
||||
if (animate)
|
||||
this._animateOverlayActor(button, Math.floor(buttonX), Math.floor(buttonY), button.width);
|
||||
else
|
||||
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
|
||||
|
||||
if (!title.fullWidth)
|
||||
title.fullWidth = title.width;
|
||||
let titleWidth = Math.min(title.fullWidth, cloneWidth);
|
||||
|
||||
let titleX = cloneX + (cloneWidth - titleWidth) / 2;
|
||||
let titleY = cloneY - title.height + title._overlap;
|
||||
|
||||
if (animate)
|
||||
this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY), titleWidth);
|
||||
else {
|
||||
title.width = titleWidth;
|
||||
title.set_position(Math.floor(titleX), Math.floor(titleY));
|
||||
}
|
||||
},
|
||||
|
||||
winInjections['_onStyleChanged'] = Workspace.WindowOverlay.prototype._onStyleChanged;
|
||||
Workspace.WindowOverlay.prototype._onStyleChanged = function() {
|
||||
let titleNode = this.title.get_theme_node();
|
||||
this.title._spacing = titleNode.get_length('-shell-caption-spacing');
|
||||
this.title._overlap = titleNode.get_length('-shell-caption-overlap');
|
||||
|
||||
let closeNode = this.closeButton.get_theme_node();
|
||||
this.closeButton._overlap = closeNode.get_length('-shell-close-overlap');
|
||||
|
||||
this._parentActor.queue_relayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class NativeWindowPlacementExtension extends Extension {
|
||||
constructor(metadata) {
|
||||
super(metadata);
|
||||
|
||||
this._injectionManager = new InjectionManager();
|
||||
}
|
||||
|
||||
enable() {
|
||||
const settings = this.getSettings();
|
||||
|
||||
const layoutProto = Workspace.WorkspaceLayout.prototype;
|
||||
const previewProto = WindowPreview.prototype;
|
||||
|
||||
this._injectionManager.overrideMethod(layoutProto, '_createBestLayout', () => {
|
||||
/* eslint-disable no-invalid-this */
|
||||
return function () {
|
||||
this._layoutStrategy = new NaturalLayoutStrategy({
|
||||
monitor: Main.layoutManager.monitors[this._monitorIndex],
|
||||
}, settings);
|
||||
return this._layoutStrategy.computeLayout(this._sortedWindows);
|
||||
};
|
||||
/* eslint-enable no-invalid-this */
|
||||
});
|
||||
|
||||
// position window titles on top of windows in overlay
|
||||
this._injectionManager.overrideMethod(previewProto, '_init', originalMethod => {
|
||||
/* eslint-disable no-invalid-this */
|
||||
return function (...args) {
|
||||
originalMethod.call(this, ...args);
|
||||
|
||||
if (!settings.get_boolean('window-captions-on-top'))
|
||||
return;
|
||||
|
||||
const alignConstraint = this._title.get_constraints().find(
|
||||
c => c.align_axis && c.align_axis === Clutter.AlignAxis.Y_AXIS);
|
||||
alignConstraint.factor = 0;
|
||||
|
||||
const bindConstraint = this._title.get_constraints().find(
|
||||
c => c.coordinate && c.coordinate === Clutter.BindCoordinate.Y);
|
||||
bindConstraint.offset = 0;
|
||||
};
|
||||
/* eslint-enable no-invalid-this */
|
||||
});
|
||||
|
||||
this._injectionManager.overrideMethod(previewProto, '_adjustOverlayOffsets', originalMethod => {
|
||||
/* eslint-disable no-invalid-this */
|
||||
return function (...args) {
|
||||
originalMethod.call(this, ...args);
|
||||
|
||||
if (settings.get_boolean('window-captions-on-top'))
|
||||
this._title.translation_y = -this._title.translation_y;
|
||||
};
|
||||
/* eslint-enable no-invalid-this */
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._injectionManager.clear();
|
||||
global.stage.queue_relayout();
|
||||
}
|
||||
function removeInjection(object, injection, name) {
|
||||
if (injection[name] === undefined)
|
||||
delete object[name];
|
||||
else
|
||||
object[name] = injection[name];
|
||||
}
|
||||
|
||||
function disable() {
|
||||
for (i in workspaceInjections)
|
||||
removeInjection(Workspace.Workspace.prototype, workspaceInjections, i);
|
||||
for (i in winInjections)
|
||||
removeInjection(Workspace.WindowOverlay.prototype, winInjections, i);
|
||||
|
||||
for each (i in connectedSignals)
|
||||
i.obj.disconnect(i.id);
|
||||
|
||||
global.stage.queue_relayout();
|
||||
resetState();
|
||||
}
|
||||
|
||||
function init() {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
@@ -4,7 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Native Window Placement",
|
||||
"description": "Arrange windows in overview in a more compact way.",
|
||||
"description": "Arrange windows in overview in a more native way",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@",
|
||||
"original-authors": [ "wepmaschda@gmx.de" ]
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<schema id="org.gnome.shell.extensions.native-window-placement" path="/org/gnome/shell/extensions/native-window-placement/">
|
||||
<key name="use-more-screen" type="b">
|
||||
<default>true</default>
|
||||
<summary>Use more screen for windows</summary>
|
||||
<description>Try to use more screen for placing window thumbnails by adapting to screen aspect ratio, and consolidating
|
||||
them further to reduce the bounding box. This setting applies only with the natural placement strategy.</description>
|
||||
<_summary>Use more screen for windows</_summary>
|
||||
<_description>Try to use more screen for placing window thumbnails by adapting to screen aspect ratio, and consolidating
|
||||
them further to reduce the bounding box. This setting applies only with the natural placement strategy.</_description>
|
||||
</key>
|
||||
<key name="window-captions-on-top" type="b">
|
||||
<default>true</default>
|
||||
<summary>Place window captions on top</summary>
|
||||
<description>If true, place window captions on top the respective thumbnail, overriding shell default of placing it at
|
||||
the bottom. Changing this setting requires restarting the shell to have any effect.</description>
|
||||
<_summary>Place window captions on top</_summary>
|
||||
<_description>If true, place window captions on top the respective thumbnail, overriding shell default of placing it at
|
||||
the bottom. Changing this setting requires restarting the shell to have any effect.</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
3
extensions/native-window-placement/stylesheet.css
Normal file
3
extensions/native-window-placement/stylesheet.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.window-caption {
|
||||
-shell-caption-overlap: 13px; /* current caption height is 26px => set it to half of it. TODO: better solution needed */
|
||||
}
|
||||
5
extensions/places-menu/Makefile.am
Normal file
5
extensions/places-menu/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = places-menu
|
||||
|
||||
EXTRA_MODULES = placeDisplay.js
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,145 +1,118 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
import Clutter from 'gi://Clutter';
|
||||
import GObject from 'gi://GObject';
|
||||
import St from 'gi://St';
|
||||
|
||||
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Panel = imports.ui.panel;
|
||||
|
||||
import {PlacesManager} from './placeDisplay.js';
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = function(x) { return x; }
|
||||
|
||||
const N_ = x => x;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
const PlaceDisplay = Me.imports.placeDisplay;
|
||||
|
||||
const PLACE_ICON_SIZE = 16;
|
||||
|
||||
class PlaceMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
const PlaceMenuItem = new Lang.Class({
|
||||
Name: 'PlaceMenuItem',
|
||||
Extends: PopupMenu.PopupMenuItem,
|
||||
|
||||
constructor(info) {
|
||||
super({
|
||||
style_class: 'place-menu-item',
|
||||
});
|
||||
this._info = info;
|
||||
_init: function(info) {
|
||||
this.parent(info.name);
|
||||
this._info = info;
|
||||
|
||||
this._icon = new St.Icon({
|
||||
gicon: info.icon,
|
||||
icon_size: PLACE_ICON_SIZE,
|
||||
});
|
||||
this.add_child(this._icon);
|
||||
this.addActor(new St.Icon({ gicon: info.icon,
|
||||
icon_size: PLACE_ICON_SIZE }),
|
||||
{ align: St.Align.END, span: -1 });
|
||||
},
|
||||
|
||||
this._label = new St.Label({
|
||||
text: info.name,
|
||||
x_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_child(this._label);
|
||||
activate: function(event) {
|
||||
this._info.launch(event.get_time());
|
||||
|
||||
if (info.isRemovable()) {
|
||||
this._ejectIcon = new St.Icon({
|
||||
icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon',
|
||||
});
|
||||
this._ejectButton = new St.Button({
|
||||
child: this._ejectIcon,
|
||||
style_class: 'button',
|
||||
});
|
||||
this._ejectButton.connect('clicked', info.eject.bind(info));
|
||||
this.add_child(this._ejectButton);
|
||||
}
|
||||
this.parent(event);
|
||||
},
|
||||
});
|
||||
|
||||
info.connectObject('changed',
|
||||
this._propertiesChanged.bind(this), this);
|
||||
}
|
||||
|
||||
activate(event) {
|
||||
this._info.launch(event.get_time());
|
||||
|
||||
super.activate(event);
|
||||
}
|
||||
|
||||
_propertiesChanged(info) {
|
||||
this._icon.gicon = info.icon;
|
||||
this._label.text = info.name;
|
||||
}
|
||||
const SECTIONS = {
|
||||
'special': N_("Places"),
|
||||
'devices': N_("Devices"),
|
||||
'bookmarks': N_("Bookmarks"),
|
||||
'network': N_("Network")
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
'special',
|
||||
'devices',
|
||||
'bookmarks',
|
||||
'network',
|
||||
];
|
||||
const PlacesMenu = new Lang.Class({
|
||||
Name: 'PlacesMenu.PlacesMenu',
|
||||
Extends: PanelMenu.SystemStatusButton,
|
||||
|
||||
class PlacesMenu extends PanelMenu.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
_init: function() {
|
||||
this.parent('folder-symbolic');
|
||||
this.placesManager = new PlaceDisplay.PlacesManager();
|
||||
|
||||
constructor() {
|
||||
super(0.5, _('Places'));
|
||||
this._sections = { };
|
||||
|
||||
let label = new St.Label({
|
||||
text: _('Places'),
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_actor(label);
|
||||
for (let foo in SECTIONS) {
|
||||
let id = foo; // stupid JS closure semantics...
|
||||
this._sections[id] = { section: new PopupMenu.PopupMenuSection(),
|
||||
title: Gettext.gettext(SECTIONS[id]) };
|
||||
this.placesManager.connect(id + '-updated', Lang.bind(this, function() {
|
||||
this._redisplay(id);
|
||||
}));
|
||||
|
||||
this.placesManager = new PlacesManager();
|
||||
this._create(id);
|
||||
this.menu.addMenuItem(this._sections[id].section);
|
||||
}
|
||||
},
|
||||
|
||||
this._sections = { };
|
||||
destroy: function() {
|
||||
this.placesManager.destroy();
|
||||
|
||||
for (let i = 0; i < SECTIONS.length; i++) {
|
||||
let id = SECTIONS[i];
|
||||
this._sections[id] = new PopupMenu.PopupMenuSection();
|
||||
this.placesManager.connect(`${id}-updated`, () => {
|
||||
this._redisplay(id);
|
||||
});
|
||||
this.parent();
|
||||
},
|
||||
|
||||
this._create(id);
|
||||
this.menu.addMenuItem(this._sections[id]);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
}
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
this.placesManager.destroy();
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
|
||||
_redisplay(id) {
|
||||
this._sections[id].removeAll();
|
||||
_redisplay: function(id) {
|
||||
this._sections[id].section.removeAll();
|
||||
this._create(id);
|
||||
}
|
||||
},
|
||||
|
||||
_create: function(id) {
|
||||
let title = new PopupMenu.PopupMenuItem(this._sections[id].title,
|
||||
{ reactive: false,
|
||||
style_class: 'popup-subtitle-menu-item' });
|
||||
this._sections[id].section.addMenuItem(title);
|
||||
|
||||
_create(id) {
|
||||
let places = this.placesManager.get(id);
|
||||
|
||||
for (let i = 0; i < places.length; i++)
|
||||
this._sections[id].addMenuItem(new PlaceMenuItem(places[i]));
|
||||
this._sections[id].section.addMenuItem(new PlaceMenuItem(places[i]));
|
||||
|
||||
this._sections[id].actor.visible = places.length > 0;
|
||||
this._sections[id].section.actor.visible = places.length > 0;
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
export default class PlacesMenuExtension extends Extension {
|
||||
enable() {
|
||||
this._indicator = new PlacesMenu();
|
||||
let _indicator;
|
||||
|
||||
let pos = Main.sessionMode.panel.left.length;
|
||||
if ('apps-menu' in Main.panel.statusArea)
|
||||
pos++;
|
||||
Main.panel.addToStatusArea('places-menu', this._indicator, pos, 'left');
|
||||
}
|
||||
function enable() {
|
||||
_indicator = new PlacesMenu;
|
||||
|
||||
disable() {
|
||||
this._indicator.destroy();
|
||||
delete this._indicator;
|
||||
}
|
||||
let pos = 1;
|
||||
if ('apps-menu' in Main.panel.statusArea)
|
||||
pos = 2;
|
||||
Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left');
|
||||
}
|
||||
|
||||
function disable() {
|
||||
_indicator.destroy();
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
extension_data += files('stylesheet.css')
|
||||
|
||||
extension_sources += files('placeDisplay.js')
|
||||
@@ -4,7 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Places Status Indicator",
|
||||
"description": "Add a menu for quickly navigating places in the system.\nThis extension is part of Classic Mode and is officially supported by GNOME. Please do not report bugs using the form below, use GNOME's GitLab instance instead.",
|
||||
"description": "Add a systems status menu for quickly navigating places in the system",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user