Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf5230ef6c | ||
|
|
1a0601dda8 | ||
|
|
9117b2266c |
@@ -1,3 +0,0 @@
|
||||
extends:
|
||||
- ./lint/eslintrc-gjs.yml
|
||||
- ./lint/eslintrc-shell.yml
|
||||
108
.gitlab-ci.yml
108
.gitlab-ci.yml
@@ -1,108 +0,0 @@
|
||||
include:
|
||||
- remote: "https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/6f86b8bcb0cd5168c32779c4fea9a893c4a0c046/templates/ci-fairy.yml"
|
||||
|
||||
image: registry.gitlab.gnome.org/gnome/gnome-shell/fedora/33:2020-11-17.0
|
||||
|
||||
stages:
|
||||
- pre_review
|
||||
- review
|
||||
- build
|
||||
|
||||
default:
|
||||
# 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:
|
||||
LINT_LOG: "eslint-report.xml"
|
||||
JS_LOG: "js-report.txt"
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
|
||||
js_check:
|
||||
stage: review
|
||||
script:
|
||||
- find extensions -name '*.js' -exec js78 -c '{}' ';' 2>&1 | tee $JS_LOG
|
||||
- (! grep -q . $JS_LOG)
|
||||
artifacts:
|
||||
paths:
|
||||
- ${JS_LOG}
|
||||
when: on_failure
|
||||
|
||||
eslint:
|
||||
stage: review
|
||||
script:
|
||||
- eslint -o $LINT_LOG -f junit extensions
|
||||
artifacts:
|
||||
paths:
|
||||
- ${LINT_LOG}
|
||||
reports:
|
||||
junit: ${LINT_LOG}
|
||||
|
||||
build-bundles:
|
||||
stage: build
|
||||
needs: ["check_commit_log"]
|
||||
script:
|
||||
- ./export-zips.sh
|
||||
artifacts:
|
||||
name: 'Extension bundles'
|
||||
expose_as: 'Get Extension bundles here'
|
||||
paths:
|
||||
- zip-files/
|
||||
@@ -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
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "data/gnome-shell-sass"]
|
||||
path = data/gnome-shell-sass
|
||||
url = https://gitlab.gnome.org/GNOME/gnome-shell-sass.git
|
||||
url = https://git.gnome.org/browse/gnome-shell-sass/
|
||||
|
||||
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/master/doc/Style_Guide.md
|
||||
52
Makefile.am
Normal file
52
Makefile.am
Normal file
@@ -0,0 +1,52 @@
|
||||
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
|
||||
|
||||
DIST_SUBDIRS = data extensions po
|
||||
|
||||
SUBDIRS = extensions po
|
||||
|
||||
if CLASSIC_MODE
|
||||
SUBDIRS += data
|
||||
endif
|
||||
|
||||
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)"; \
|
||||
cp "$(srcdir)/NEWS" -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
|
||||
479
NEWS
479
NEWS
@@ -1,481 +1,6 @@
|
||||
40.beta
|
||||
=======
|
||||
* Add tooltips to workspace thumbnails [Florian; !155]
|
||||
* Drop arrows from top bar menus [Florian; !156]
|
||||
* drive-menu: Mark mounts that can be unmounted as removable [Michael; !152]
|
||||
* Remove horizontal-workspaces extension [Florian; !158]
|
||||
* Adjust to shell overview changes [Florian; !159, !160]
|
||||
* Fix crashes [Daniel; !157]
|
||||
* Misc. bug fixes and cleanups [Florian; !154, !161]
|
||||
|
||||
Contributors:
|
||||
Michael Lawton, Florian Müllner, Daniel van Vugt
|
||||
|
||||
Translators:
|
||||
Аляксей [be], A S Alam [pa]
|
||||
|
||||
40.alpha.1
|
||||
==========
|
||||
* Don't depend on sassc when building from tarball [Florian; !150]
|
||||
* Port extensions preferences to GTK4 [Florian; !148]
|
||||
* Misc. bug fixes and cleanups [Florian, Jonas; !149, !151, !153]
|
||||
|
||||
Contributors:
|
||||
Jonas Dreßler, Florian Müllner
|
||||
|
||||
40.alpha
|
||||
========
|
||||
* window-list: Honor changes in skip-taskbar property [Sergio; !130]
|
||||
* window-list, workspace-indicator: Adjust to 3.38 changes [Florian; !133]
|
||||
* window-list, workspace-indicator: Improve previews in workspace thumbs
|
||||
[Florian; #260, !142]
|
||||
* auto-move: Improve behavior on multi-monitor setups [Florian; !135]
|
||||
* windowNavigator: Adjust to 3.38 changes [Thun; #259]
|
||||
* Misc. bug fixes and cleanups [Florian, Jonas Å, Jordan, Ray; !131, !136,
|
||||
!137, !140, !141, !144, !146, !145]
|
||||
|
||||
Contributors:
|
||||
Sergio Costas, Florian Müllner, Jordan Petridis, Thun Pin, Ray Strode,
|
||||
Jonas Ådahl
|
||||
|
||||
Translators:
|
||||
Fabio Tomat [fur], Jordi Mas [ca]
|
||||
|
||||
3.38.1
|
||||
3.22.2
|
||||
======
|
||||
|
||||
Contributors:
|
||||
Yacine Bouklif, Florian Müllner
|
||||
|
||||
Translators:
|
||||
Yacine Bouklif [kab], Cheng-Chia Tseng [zh_TW], Stas Solovey [ru],
|
||||
Yosef Or Boczko [he]
|
||||
|
||||
3.38.0
|
||||
======
|
||||
|
||||
Translators:
|
||||
Balázs Meskó [hu], Alan Mortensen [da], Juliano Camargo [pt], Tim Sabsch [de],
|
||||
Milo Casagrande [it], Rūdolfs Mazurs [lv]
|
||||
|
||||
3.37.92
|
||||
=======
|
||||
|
||||
Translators:
|
||||
Nathan Follens [nl], Zander Brown [en_GB], Aurimas Černius [lt],
|
||||
Marek Černocký [cs], Changwoo Ryu [ko], Dušan Kazik [sk]
|
||||
|
||||
3.37.91
|
||||
=======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Fran Dieguez [gl], Akarshan Biswas [bn_IN], Kukuh Syafaat [id],
|
||||
Piotr Drąg [pl], Rafael Fontenelle [pt_BR], Jiri Grönroos [fi],
|
||||
Марко Костић [sr], Goran Vidović [hr]
|
||||
|
||||
3.37.90
|
||||
=======
|
||||
* Misc. bug fixes and cleanups [Florian, Piotr; !126, !128]
|
||||
|
||||
Contributors:
|
||||
Piotr Drąg, Florian Müllner
|
||||
|
||||
Translators:
|
||||
Fabio Tomat [fur], Efstathios Iosifidis [el], Anders Jonsson [sv],
|
||||
Asier Sarasua Garmendia [eu], Alexandre Franke [fr]
|
||||
|
||||
3.37.3
|
||||
======
|
||||
* window-list, native-window-placement: Adjust to shell changes [Florian; !124]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Jordi Mas [ca], sicklylife [ja], Boyuan Yang [zh_CN],
|
||||
Baurzhan Muftakhidinov [kk]
|
||||
|
||||
3.37.2
|
||||
======
|
||||
* window-list, auto-move: Modernize preference dialogs [Florian; !121]
|
||||
* Adjust to gnome-shell changes [Florian; !122]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Cheng-Chia Tseng [zh_TW], Yuri Chornoivan [uk], Daniel Mustieles [es],
|
||||
Emin Tufan Çetin [tr], Danial Behzadi [fa], Daniel Șerbănescu [ro],
|
||||
Matej Urbančič [sl]
|
||||
|
||||
3.37.1
|
||||
======
|
||||
* drive-menu: Emphasize eject buttons [Florian; #223]
|
||||
* user-theme: Add preference dialog [Florian; !117]
|
||||
* window-list: Fix inconsistent state in preference dialog [Milan; !119]
|
||||
* workspace-indicator: Overhaul preference dialog [Florian; !120]
|
||||
* user-theme: Support session mode styles [Florian; !118]
|
||||
* Misc. bug fixes and cleanups [Florian, Xiaoguang; !113, !106, !114, !116]
|
||||
|
||||
Contributors:
|
||||
Milan Crha, Florian Müllner, Xiaoguang Wang
|
||||
|
||||
Translators:
|
||||
Daniel Korostil [uk], Yosef Or Boczko [he], Kristjan SCHMIDT [eo],
|
||||
Dz Chen [zh_CN], Danial Behzadi [fa], Yuri Chornoivan [uk],
|
||||
Anders Jonsson [sv], Daniel Mustieles [es]
|
||||
|
||||
3.36.0
|
||||
======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.35.91
|
||||
=======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Zander Brown [en_GB]
|
||||
|
||||
3.35.90
|
||||
=======
|
||||
* Adjust to gnome-shell changes [Florian; !100, !101, !102]
|
||||
* Force single-line window titles in window list [Florian; #202]
|
||||
* Misc. bug fixes and cleanup [Florian; !104, !105]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
sicklylife [ja], Umarzuki Bin Mochlis Moktar [ms]
|
||||
|
||||
3.35.3
|
||||
======
|
||||
|
||||
Translators:
|
||||
Fran Dieguez [gl]
|
||||
|
||||
3.35.2
|
||||
======
|
||||
* Adjust to gnome-shell changes [Marco, Florian; !89, !95, !96]
|
||||
* window-list, workspace-indicator: Exclude DESKTOP windows from previews
|
||||
[Florian; !93]
|
||||
* screenshot-window-sizer: Fix cycling through all valid sizes [Willy; !97]
|
||||
|
||||
Contributors:
|
||||
Marco Trevisan (Treviño), Florian Müllner, Willy Stadnick
|
||||
|
||||
3.34.1
|
||||
======
|
||||
* Adjust to gnome-settings-daemon plugin removals [Xiaoguang; !94]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Xiaoguang Wang
|
||||
|
||||
Translators:
|
||||
Nathan Follens [nl], Dušan Kazik [sk], Ask Hjorth Larsen [da],
|
||||
Yi-Jyun Pan [zh_TW]
|
||||
|
||||
3.34.0
|
||||
======
|
||||
|
||||
Translators:
|
||||
Rafael Fontenelle [pt_BR], Efstathios Iosifidis [el], Milo Casagrande [it],
|
||||
Sabri Ünal [tr]
|
||||
|
||||
3.33.92
|
||||
=======
|
||||
|
||||
Translators:
|
||||
Марко Костић [sr], Tim Sabsch [de], Rūdolfs Mazurs [lv], Matej Urbančič [sl],
|
||||
Balázs Úr [hu], Claude Paroz [fr], Fran Dieguez [gl], Changwoo Ryu [ko],
|
||||
Ryuta Fujii [ja], Fabio Tomat [fur], Goran Vidović [hr]
|
||||
|
||||
3.33.91
|
||||
=======
|
||||
* Misc. bug fixes and cleanups [Florian; !88, !90, !91, !92]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Asier Sarasua Garmendia [eu], Anders Jonsson [sv], Marek Černocký [cs],
|
||||
Kukuh Syafaat [id], Jiri Grönroos [fi], Florentina Mușat [ro],
|
||||
Aurimas Černius [lt], Daniel Mustieles [es], Piotr Drąg [pl], Jordi Mas [ca],
|
||||
Danial Behzadi [fa]
|
||||
3.33.90
|
||||
=======
|
||||
* window-list: Support showing windows from all workspaces [Florian; #154]
|
||||
* Misc. bug fixes and cleanups [Florian; !86, !87]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Jor Teron [mjw]
|
||||
|
||||
3.33.4
|
||||
======
|
||||
* Make GNOME Classic more classic:
|
||||
- Disable GNOME 3 overview [Florian; !69]
|
||||
- Add window picker button to window list [Florian; !73, !80]
|
||||
- Style improvements and fixes [Jakub; #169, !82]
|
||||
- Support horizontal workspace layout in window list [Florian; !70]
|
||||
- Add draggable previews to window list workspace switcher [Florian; !74]
|
||||
- Arrange workspaces horizontally [Florian; !72]
|
||||
* workspace-indicator: Support horizontal workspace layout [Florian; !71]
|
||||
* workspace-indicator: Add draggable previews [Florian; !77]
|
||||
* Misc. bug fixes and cleanups [Florian; !75, !76, !79, !78, #168, !84]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Jakub Steiner, Jor Teron
|
||||
|
||||
Translators:
|
||||
Jor Teron [mjw]
|
||||
|
||||
3.33.3
|
||||
======
|
||||
* Misc. bug fixes [Florian, Marco; !67, !68]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Marco Trevisan (Treviño)
|
||||
|
||||
3.33.2
|
||||
======
|
||||
* Misc. bug fixes and cleanups [Florian; !66]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.33.1
|
||||
======
|
||||
* Misc. bug fixes [Florian; !64]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.32.1
|
||||
======
|
||||
* Fix windowsNavigator extension after ES6 port [Florian; #143]
|
||||
* screenshot-window-sizer: Add phone screenshot sizes [Adrien; !65]
|
||||
* Misc. bug fixes and cleanups [Fabian; !62]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Adrien Plazas, Fabian P. Schmidt
|
||||
|
||||
3.32.0
|
||||
======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translations:
|
||||
Victor Ibragimov [tg], Kristjan SCHMIDT [eo], Mart Raudsepp [et]
|
||||
|
||||
3.31.92
|
||||
=======
|
||||
* Misc. bug fixes and cleanups [Florian; !57, !58, !59, !60]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.31.91
|
||||
=======
|
||||
* apps-menu: Remove outdated legacy-tray handling [Florian; !53]
|
||||
* user-theme: Allow using XDG user data dir [Tomasz; !55]
|
||||
* Misc. bug fixes and cleanups [Florian; !52, !54, !56]
|
||||
|
||||
Contributors:
|
||||
Tomasz Gąsior, Florian Müllner
|
||||
|
||||
Translators:
|
||||
Matej Urbančič [sl], Gun Chleoc [gd]
|
||||
|
||||
3.31.90
|
||||
=======
|
||||
* Misc. bug fixes and cleanups [Florian; !49, !50, !51]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Ryuta Fujii [ja], Charles Monzat [fr], Pieter Schalk Schoeman [af]
|
||||
|
||||
3.31.2
|
||||
======
|
||||
* Remove obsolete alternate-tab extension [Florian; #786496]
|
||||
* Adjust to gnome-shell changes [Florian; #113]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.30.1
|
||||
======
|
||||
* apps-menu: Fix height on HiDPI systems [Florian; #102]
|
||||
* window-list: Only switch between windows on active workspace when scrolling
|
||||
[Florian; #78]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.30.0
|
||||
======
|
||||
* Bump version
|
||||
|
||||
3.29.91
|
||||
=======
|
||||
* Misc. bug fixes [Florian; #90]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.29.90
|
||||
=======
|
||||
* Misc. bug fixes [Florian; #786496]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.29.3
|
||||
======
|
||||
* Adjust to global.screen removal [Jonas; #759538]
|
||||
|
||||
Contributors:
|
||||
Jonas Ådahl, Florian Müllner
|
||||
|
||||
3.29.2
|
||||
======
|
||||
* Misc. bug fixes [Florian; #69]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
3.28.1
|
||||
======
|
||||
* Misc. bug fixes [Xiaoguang, Florian; #59, #62]
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Xiaoguang Wang
|
||||
|
||||
Translators:
|
||||
Dz Chen [zh_CN]
|
||||
|
||||
3.28.0
|
||||
======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner, Xiaoguang Wang
|
||||
|
||||
Translators:
|
||||
Aman Alam [pa], Bruce Cowan [en_GB]
|
||||
|
||||
3.27.92
|
||||
=======
|
||||
|
||||
Contributors:
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Piotr Drąg [es], GNOME Translation Robot [gd], Daniel Șerbănescu [ro]
|
||||
|
||||
3.27.91
|
||||
=======
|
||||
* places-menu: Support unmounting ejectable places [Rémy; #17]
|
||||
* apps-menu: Support separators and custom sort order [Florian; #27]
|
||||
* Port to meson [Florian; #31, #45]
|
||||
* window-list: Fix missing icons on wayland [Florian; #10]
|
||||
* places-menu: Fix terminating gnome-shell with recent gjs [Florian; #44]
|
||||
* auto-move: Make it work with wayland windows [Florian; #33]
|
||||
* Classic theme fixes [Florian, Jonas; #26, #41, #39, #40]
|
||||
* Require sassc for classic styling [Florian; !28]
|
||||
* Misc. bug fixes [Piotr, Florian; #772211, #32, #30]
|
||||
|
||||
Contributors:
|
||||
Jeremy Bicha, Piotr Drąg, Jonas Kümmerlin, Rémy Lefevre, Iñigo Martínez,
|
||||
Florian Müllner
|
||||
|
||||
Translators:
|
||||
Matej Urbančič [sl], Kjartan Maraas [nb]
|
||||
|
||||
3.27.1
|
||||
======
|
||||
* updated translations (ca@valencia)
|
||||
|
||||
3.26.1
|
||||
======
|
||||
* native-window-placement: Adjust to gnome-shell changes
|
||||
* updated translations: el, fa, ru, sv
|
||||
|
||||
3.26.0
|
||||
======
|
||||
* updated translations (be, bg, ca, da, eu, fi, is, it, ko, lv, ml,
|
||||
nl, pt_BR, vi, zh_TW)
|
||||
|
||||
3.25.91
|
||||
=======
|
||||
* updated translations (ca, fr, it, pl, pt_BR, sr, sr@latin, tr)
|
||||
|
||||
3.25.90
|
||||
=======
|
||||
* updated translations (es, gl, hr, hu, kk, sl, sv, sv)
|
||||
|
||||
3.25.4
|
||||
======
|
||||
* screenshot-window-sizer: Fix backward cycling
|
||||
* updated translations (ar, be, ca, cs, de, fur, id, lt, pl, sk)
|
||||
|
||||
3.25.3
|
||||
======
|
||||
* places-menu: Use mount operation if necessary
|
||||
* window-list: Respect MWM hints
|
||||
* updated translations (es, fur, kk)
|
||||
|
||||
3.25.2
|
||||
======
|
||||
* places-menu: Make URI launching asynchronous
|
||||
* updated translations (de, fur, hr, hu, id, sl)
|
||||
|
||||
3.25.1
|
||||
======
|
||||
* apps-menu: Mark copied launchers as trusted
|
||||
* places-menu: Make icon lookup asynchronous
|
||||
* updated translations (hr)
|
||||
|
||||
3.24.1
|
||||
======
|
||||
* apps-menu: Allow creating desktop launchers via DND
|
||||
* updated translations (el, vi)
|
||||
|
||||
3.24.0
|
||||
======
|
||||
* updated translations (lv, tr)
|
||||
|
||||
3.23.92
|
||||
=======
|
||||
* update classic theme
|
||||
* updated translations (be, ko, ca, da, cs, ru, lt)
|
||||
|
||||
3.23.91
|
||||
=======
|
||||
* updated translations (de, es, eu, fi, fr, fur, gl, hu, id, it, kk, nb, pl, pt_BR,
|
||||
sk, sr, sr@latin, sv, uk, zh_TW)
|
||||
|
||||
3.23.90
|
||||
=======
|
||||
* window-list: Improve styling
|
||||
* window-list: Hide workspace indicator when there's a single (static) workspace
|
||||
* new translation (be)
|
||||
|
||||
3.23.2
|
||||
======
|
||||
* alternateTab: Don't take over 'switch-group' shortcut
|
||||
* updated translations (zh_CN)
|
||||
* updated translations (zh_CN)
|
||||
|
||||
3.22.1
|
||||
======
|
||||
|
||||
74
README
Normal file
74
README
Normal file
@@ -0,0 +1,74 @@
|
||||
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.
|
||||
|
||||
For more information about GNOME Shell Extensions
|
||||
https://wiki.gnome.org/Projects/GnomeShell/Extensions
|
||||
|
||||
For general information about GNOME Shell
|
||||
https://wiki.gnome.org/Projects/GnomeShell
|
||||
|
||||
Bugs should be reported at https://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.
|
||||
|
||||
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.
|
||||
|
||||
user-theme
|
||||
|
||||
Loads a shell theme from ~/.themes/<name>/gnome-shell.
|
||||
|
||||
windowsNavigator
|
||||
|
||||
Allow keyboard selection of windows and workspaces in overlay mode.
|
||||
|
||||
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.
|
||||
|
||||
83
README.md
83
README.md
@@ -1,83 +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.
|
||||
|
||||
## 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/
|
||||
26
autogen.sh
Executable file
26
autogen.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# Run this to generate all the initial makefiles, etc.
|
||||
|
||||
srcdir=`dirname $0`
|
||||
test -z "$srcdir" && srcdir=.
|
||||
|
||||
test -f $srcdir/configure.ac || {
|
||||
echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
|
||||
echo " top-level gnome-shell-extensions directory"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Fetch submodules if needed
|
||||
if test ! -f data/gnome-shell-sass/COPYING;
|
||||
then
|
||||
echo "+ Setting up submodules"
|
||||
git submodule init
|
||||
fi
|
||||
git submodule update
|
||||
|
||||
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
|
||||
98
configure.ac
Normal file
98
configure.ac
Normal file
@@ -0,0 +1,98 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell-extensions],[3.22.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-xz 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.])
|
||||
|
||||
AM_GNU_GETTEXT_VERSION([0.19.6])
|
||||
AM_GNU_GETTEXT([external])
|
||||
|
||||
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
|
||||
CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list"
|
||||
DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator"
|
||||
ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows example native-window-placement user-theme"
|
||||
AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS])
|
||||
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 and distribute all extensions that can be installed in the home directory and have no external depedencies.
|
||||
Use "all" to enable all available extensions, or "classic-mode" to enable extensions that restore GNOME 2 functionality.])],
|
||||
[],
|
||||
[enable_extensions=$DEFAULT_EXTENSIONS]
|
||||
)
|
||||
|
||||
if test x"$enable_extensions" = xall; then
|
||||
enable_extensions="$ALL_EXTENSIONS"
|
||||
enable_classic_mode_default=yes
|
||||
elif test x"$enable_extensions" = xclassic-mode; then
|
||||
enable_extensions="$CLASSIC_EXTENSIONS"
|
||||
enable_classic_mode_default=yes
|
||||
else
|
||||
enable_classic_mode_default=no
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE([classic-mode],
|
||||
[AS_HELP_STRING([--enable-classic-mode],[Enable installing data files for classic mode.
|
||||
Defaults to yes if enabling all or classic-mode extensions, and no otherwise.])],
|
||||
[],
|
||||
[enable_classic_mode=$enable_classic_mode_default])
|
||||
AM_CONDITIONAL([CLASSIC_MODE], [test x"$enable_classic_mode" != xno])
|
||||
|
||||
ENABLED_EXTENSIONS=
|
||||
for e in $enable_extensions; do
|
||||
case $e in
|
||||
dnl keep this in alphabetic order
|
||||
alternate-tab|apps-menu|auto-move-windows|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|user-theme|window-list|windowsNavigator|workspace-indicator)
|
||||
ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR([invalid extension $e])
|
||||
esac
|
||||
done
|
||||
|
||||
AC_SUBST(ENABLED_EXTENSIONS, [$ENABLED_EXTENSIONS])
|
||||
|
||||
AC_PATH_PROG([SASS],[sass],[])
|
||||
|
||||
dnl Please keep this sorted alphabetically
|
||||
AC_CONFIG_FILES([
|
||||
data/Makefile
|
||||
extensions/alternate-tab/Makefile
|
||||
extensions/apps-menu/Makefile
|
||||
extensions/auto-move-windows/Makefile
|
||||
extensions/drive-menu/Makefile
|
||||
extensions/example/Makefile
|
||||
extensions/launch-new-instance/Makefile
|
||||
extensions/native-window-placement/Makefile
|
||||
extensions/places-menu/Makefile
|
||||
extensions/screenshot-window-sizer/Makefile
|
||||
extensions/user-theme/Makefile
|
||||
extensions/window-list/Makefile
|
||||
extensions/windowsNavigator/Makefile
|
||||
extensions/workspace-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 +1,6 @@
|
||||
To generate the css files, from the project directory:
|
||||
|
||||
sass --sourcemap=none --update .
|
||||
|
||||
To update the gnome-shell-sass submodule to latest upstream commit:
|
||||
git submodule update --rebase
|
||||
|
||||
73
data/Makefile.am
Normal file
73
data/Makefile.am
Normal file
@@ -0,0 +1,73 @@
|
||||
include $(top_srcdir)/include.mk
|
||||
|
||||
sessiondir = $(datadir)/gnome-session/sessions
|
||||
session_in_files = gnome-classic.session.desktop.in
|
||||
session_DATA = $(session_in_files:.session.desktop.in=.session)
|
||||
|
||||
xsessiondir = $(datadir)/xsessions
|
||||
xsession_in_files = gnome-classic.desktop.in
|
||||
xsession_DATA = $(xsession_in_files:.desktop.in=.desktop)
|
||||
|
||||
modedir = $(datadir)/gnome-shell/modes
|
||||
mode_in_files = classic.json.in
|
||||
mode_DATA = $(mode_in_files:.json.in=.json)
|
||||
|
||||
themedir = $(datadir)/gnome-shell/theme
|
||||
theme_DATA = \
|
||||
$(srcdir)/calendar-today.svg \
|
||||
$(srcdir)/classic-process-working.svg \
|
||||
$(srcdir)/classic-toggle-off-intl.svg \
|
||||
$(srcdir)/classic-toggle-off-us.svg \
|
||||
$(srcdir)/classic-toggle-on-intl.svg \
|
||||
$(srcdir)/classic-toggle-on-us.svg \
|
||||
$(srcdir)/gnome-classic.css \
|
||||
$(srcdir)/gnome-classic-high-contrast.css \
|
||||
$(NULL)
|
||||
|
||||
theme_sources = \
|
||||
$(srcdir)/gnome-classic.scss \
|
||||
$(srcdir)/gnome-shell-sass/_colors.scss \
|
||||
$(srcdir)/gnome-shell-sass/_common.scss \
|
||||
$(srcdir)/gnome-shell-sass/_drawing.scss \
|
||||
$(srcdir)/gnome-shell-sass/_high-contrast-colors.scss \
|
||||
$(NULL)
|
||||
|
||||
%.css: %.scss $(theme_sources)
|
||||
@if test -n "$(SASS)"; then \
|
||||
if $(AM_V_P); then PS4= set -x; else echo " GEN $@"; fi; \
|
||||
$(SASS) --sourcemap=none --update -f -q $<; \
|
||||
fi
|
||||
|
||||
gsettings_SCHEMAS = org.gnome.shell.extensions.classic-overrides.gschema.xml
|
||||
|
||||
@GSETTINGS_RULES@
|
||||
|
||||
%.desktop:%.desktop.in
|
||||
$(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@
|
||||
|
||||
%.session: %.session.desktop.in
|
||||
$(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@
|
||||
|
||||
comma:=,
|
||||
empty:=
|
||||
space:= $(empty) $(empty)
|
||||
extensions:=$(patsubst %,\"%$(extensionbase)\",$(CLASSIC_EXTENSIONS))
|
||||
extension_list:=$(subst $(space),$(comma),$(extensions))
|
||||
%.json:%.json.in
|
||||
$(AM_V_GEN) sed \
|
||||
-e "s|\@CLASSIC_EXTENSIONS\@|$(extension_list)|g" \
|
||||
$< > $@
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(session_in_files) \
|
||||
$(xsession_in_files) \
|
||||
$(mode_in_files) \
|
||||
$(gsettings_SCHEMAS) \
|
||||
$(theme_DATA) \
|
||||
$(NULL)
|
||||
|
||||
CLEANFILES = \
|
||||
$(session_DATA) \
|
||||
$(xsession_DATA) \
|
||||
$(mode_DATA) \
|
||||
$(NULL)
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"parentMode": "user",
|
||||
"stylesheetName": "gnome-classic.css",
|
||||
"hasOverview": false,
|
||||
"enabledExtensions": [@CLASSIC_EXTENSIONS@],
|
||||
"panel": { "left": ["appMenu"],
|
||||
"panel": { "left": ["activities", "appMenu"],
|
||||
"center": [],
|
||||
"right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"]
|
||||
}
|
||||
|
||||
1926
data/gnome-classic.css
Normal file
1926
data/gnome-classic.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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=env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Classic;GNOME;
|
||||
|
||||
@@ -4,11 +4,10 @@ $variant: 'light';
|
||||
@import "gnome-shell-sass/_colors"; //use gtk colors
|
||||
@import "gnome-shell-sass/_drawing";
|
||||
@import "gnome-shell-sass/_common";
|
||||
@import "gnome-shell-sass/_widgets";
|
||||
|
||||
/* Overrides */
|
||||
|
||||
#panel, #panel.solid {
|
||||
#panel {
|
||||
font-weight: normal;
|
||||
background-color: $bg_color;
|
||||
background-gradient-direction: vertical;
|
||||
@@ -32,38 +31,15 @@ $variant: 'light';
|
||||
-minimum-hpadding: 4px;
|
||||
font-weight: normal;
|
||||
color: $fg_color;
|
||||
text-shadow: none;
|
||||
transition-duration: 0ms;
|
||||
border: 0;
|
||||
border-radius: 0px;
|
||||
|
||||
&.clock-display {
|
||||
.clock {
|
||||
transition-duration: 0ms;
|
||||
border: 0;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: lighten($fg_color,10%);
|
||||
text-shadow: none;
|
||||
& .system-status-icon { icon-shadow: none; }
|
||||
}
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
// Trick due to St limitations. It needs a background to draw
|
||||
// a box-shadow
|
||||
background-color: $selected_bg_color;
|
||||
color: $selected_fg_color;
|
||||
background-color: $selected_bg_color !important;
|
||||
color: $selected_fg_color !important;
|
||||
box-shadow: none;
|
||||
& > .system-status-icon { icon-shadow: none; }
|
||||
}
|
||||
|
||||
.app-menu-icon { width: 0; height: 0; margin: 0; } // shell's display:none; :D
|
||||
|
||||
.system-status-icon {
|
||||
icon-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-corner,
|
||||
@@ -75,12 +51,13 @@ $variant: 'light';
|
||||
&.lock-screen,
|
||||
&.unlock-screen,
|
||||
&.login-screen {
|
||||
background-color: transparentize($bg_color, 0.5);
|
||||
background-gradient-start: transparentize($bg_color, 0.5);
|
||||
background-gradient-end: transparentize($bg_color, 0.5);
|
||||
background-color: transparentize($_bubble_bg_color, 0.5);
|
||||
background-gradient-start: transparentize($_bubble_bg_color, 0.5);
|
||||
background-gradient-end: transparentize($_bubble_bg_color, 0.5);
|
||||
border-bottom: none;
|
||||
.panel-button { color: $osd_fg_color; }
|
||||
}
|
||||
.popup-menu-arrow { width: 0; height: 0; } // shell's display: none;
|
||||
}
|
||||
|
||||
#appMenu {
|
||||
@@ -104,4 +81,3 @@ $variant: 'light';
|
||||
.calendar-day-with-events {
|
||||
background-image: url("calendar-today.svg");
|
||||
}
|
||||
|
||||
|
||||
3
data/gnome-classic.session.desktop.in
Normal file
3
data/gnome-classic.session.desktop.in
Normal file
@@ -0,0 +1,3 @@
|
||||
[GNOME Session]
|
||||
Name=GNOME Classic
|
||||
RequiredComponents=org.gnome.Shell;gnome-settings-daemon;nautilus-classic;
|
||||
Submodule data/gnome-shell-sass updated: 179ce628ca...b2190083c2
@@ -1,97 +0,0 @@
|
||||
session_desktop = 'gnome-classic.desktop'
|
||||
i18n.merge_file('',
|
||||
input: session_desktop + '.in',
|
||||
output: session_desktop,
|
||||
po_dir: '../po',
|
||||
install: true,
|
||||
install_dir: xsessiondir,
|
||||
type: 'desktop'
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
theme_sources = files(
|
||||
'gnome-shell-sass/_colors.scss',
|
||||
'gnome-shell-sass/_common.scss',
|
||||
'gnome-shell-sass/_drawing.scss',
|
||||
'gnome-shell-sass/_high-contrast-colors.scss',
|
||||
'gnome-shell-sass/_widgets.scss',
|
||||
'gnome-shell-sass/widgets/_a11y.scss',
|
||||
'gnome-shell-sass/widgets/_app-grid.scss',
|
||||
'gnome-shell-sass/widgets/_base.scss',
|
||||
'gnome-shell-sass/widgets/_buttons.scss',
|
||||
'gnome-shell-sass/widgets/_calendar.scss',
|
||||
'gnome-shell-sass/widgets/_check-box.scss',
|
||||
'gnome-shell-sass/widgets/_corner-ripple.scss',
|
||||
'gnome-shell-sass/widgets/_dash.scss',
|
||||
'gnome-shell-sass/widgets/_dialogs.scss',
|
||||
'gnome-shell-sass/widgets/_entries.scss',
|
||||
'gnome-shell-sass/widgets/_hotplug.scss',
|
||||
'gnome-shell-sass/widgets/_ibus-popup.scss',
|
||||
'gnome-shell-sass/widgets/_keyboard.scss',
|
||||
'gnome-shell-sass/widgets/_login-dialog.scss',
|
||||
'gnome-shell-sass/widgets/_looking-glass.scss',
|
||||
'gnome-shell-sass/widgets/_message-list.scss',
|
||||
'gnome-shell-sass/widgets/_misc.scss',
|
||||
'gnome-shell-sass/widgets/_network-dialog.scss',
|
||||
'gnome-shell-sass/widgets/_notifications.scss',
|
||||
'gnome-shell-sass/widgets/_osd.scss',
|
||||
'gnome-shell-sass/widgets/_overview.scss',
|
||||
'gnome-shell-sass/widgets/_panel.scss',
|
||||
'gnome-shell-sass/widgets/_popovers.scss',
|
||||
'gnome-shell-sass/widgets/_screen-shield.scss',
|
||||
'gnome-shell-sass/widgets/_scrollbars.scss',
|
||||
'gnome-shell-sass/widgets/_search-entry.scss',
|
||||
'gnome-shell-sass/widgets/_search-results.scss',
|
||||
'gnome-shell-sass/widgets/_slider.scss',
|
||||
'gnome-shell-sass/widgets/_switcher-popup.scss',
|
||||
'gnome-shell-sass/widgets/_switches.scss',
|
||||
'gnome-shell-sass/widgets/_tiled-previews.scss',
|
||||
'gnome-shell-sass/widgets/_window-picker.scss',
|
||||
'gnome-shell-sass/widgets/_workspace-switcher.scss',
|
||||
'gnome-shell-sass/widgets/_workspace-thumbnails.scss'
|
||||
)
|
||||
|
||||
theme_data = [
|
||||
'calendar-today.svg',
|
||||
'classic-process-working.svg',
|
||||
'classic-toggle-off-intl.svg',
|
||||
'classic-toggle-off-us.svg',
|
||||
'classic-toggle-on-intl.svg',
|
||||
'classic-toggle-on-us.svg',
|
||||
'gnome-classic-high-contrast.css'
|
||||
]
|
||||
|
||||
stylesheet = 'gnome-classic.css'
|
||||
if fs.exists(stylesheet)
|
||||
install_data(stylesheet, install_dir: themedir)
|
||||
else
|
||||
sassc = find_program('sassc', required: true)
|
||||
custom_target(stylesheet,
|
||||
input: fs.replace_suffix(stylesheet, '.scss'),
|
||||
output: stylesheet,
|
||||
depend_files: theme_sources,
|
||||
command: [sassc, '-a', '@INPUT@', '@OUTPUT@'],
|
||||
install: true,
|
||||
install_dir: themedir
|
||||
)
|
||||
endif
|
||||
|
||||
install_data(theme_data, install_dir: themedir)
|
||||
|
||||
classic_override = '00_org.gnome.shell.extensions.classic.gschema.override'
|
||||
install_data(classic_override, install_dir: schemadir)
|
||||
@@ -0,0 +1,46 @@
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell.extensions.classic-overrides"
|
||||
path="/org/gnome/shell/extensions/classic-overrides/"
|
||||
gettext-domain="gnome-shell-extensions">
|
||||
<key name="attach-modal-dialogs" type="b">
|
||||
<default>true</default>
|
||||
<summary>Attach modal dialog to the parent window</summary>
|
||||
<description>
|
||||
This key overrides the key in org.gnome.mutter when running
|
||||
GNOME Shell.
|
||||
</description>
|
||||
</key>
|
||||
|
||||
<key name="button-layout" type="s">
|
||||
<default>"appmenu:minimize,maximize,close"</default>
|
||||
<summary>Arrangement of buttons on the titlebar</summary>
|
||||
<description>
|
||||
This key overrides the key in org.gnome.desktop.wm.preferences when running GNOME Shell.
|
||||
</description>
|
||||
</key>
|
||||
|
||||
<key name="edge-tiling" type="b">
|
||||
<default>true</default>
|
||||
<summary>Enable edge tiling when dropping windows on screen edges</summary>
|
||||
<description>
|
||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||
</description>
|
||||
</key>
|
||||
|
||||
<key name="workspaces-only-on-primary" type="b">
|
||||
<default>true</default>
|
||||
<summary>Workspaces only on primary monitor</summary>
|
||||
<description>
|
||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||
</description>
|
||||
</key>
|
||||
|
||||
<key name="focus-change-on-pointer-rest" type="b">
|
||||
<default>true</default>
|
||||
<summary>Delay focus changes in mouse mode until the pointer stops moving</summary>
|
||||
<description>
|
||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
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
|
||||
ninja -C$builddir install
|
||||
|
||||
rm -rf $srcdir/zip-files
|
||||
mkdir $srcdir/zip-files
|
||||
|
||||
extensiondir=$installdir/share/gnome-shell/extensions
|
||||
schemadir=$installdir/share/glib-2.0/schemas
|
||||
localedir=$installdir/share/locale
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
5
extensions/alternate-tab/Makefile.am
Normal file
5
extensions/alternate-tab/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = alternate-tab
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
include ../../extension.mk
|
||||
65
extensions/alternate-tab/extension.js
Normal file
65
extensions/alternate-tab/extension.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const Main = imports.ui.main;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
|
||||
let injections = {};
|
||||
|
||||
function init(metadata) {
|
||||
}
|
||||
|
||||
function setKeybinding(name, func) {
|
||||
Main.wm.setCustomKeybindingHandler(name, Shell.ActionMode.NORMAL, func);
|
||||
}
|
||||
|
||||
function enable() {
|
||||
injections['_keyPressHandler'] = AltTab.WindowSwitcherPopup.prototype._keyPressHandler;
|
||||
AltTab.WindowSwitcherPopup.prototype._keyPressHandler = function(keysym, action) {
|
||||
switch(action) {
|
||||
case Meta.KeyBindingAction.SWITCH_APPLICATIONS:
|
||||
case Meta.KeyBindingAction.SWITCH_GROUP:
|
||||
action = Meta.KeyBindingAction.SWITCH_WINDOWS;
|
||||
break;
|
||||
case Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD:
|
||||
case Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD:
|
||||
action = Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD;
|
||||
break;
|
||||
}
|
||||
return injections['_keyPressHandler'].call(this, keysym, action);
|
||||
};
|
||||
|
||||
Main.wm._forcedWindowSwitcher = function(display, screen, window, binding) {
|
||||
/* prevent a corner case where both popups show up at once */
|
||||
if (this._workspaceSwitcherPopup != null)
|
||||
this._workspaceSwitcherPopup.destroy();
|
||||
|
||||
let tabPopup = new AltTab.WindowSwitcherPopup();
|
||||
|
||||
if (!tabPopup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()))
|
||||
tabPopup.destroy();
|
||||
};
|
||||
|
||||
setKeybinding('switch-applications', Lang.bind(Main.wm, Main.wm._forcedWindowSwitcher));
|
||||
setKeybinding('switch-group', Lang.bind(Main.wm, Main.wm._forcedWindowSwitcher));
|
||||
setKeybinding('switch-applications-backward', Lang.bind(Main.wm, Main.wm._forcedWindowSwitcher));
|
||||
setKeybinding('switch-group-backward', Lang.bind(Main.wm, Main.wm._forcedWindowSwitcher));
|
||||
}
|
||||
|
||||
function disable() {
|
||||
var prop;
|
||||
|
||||
setKeybinding('switch-applications', Lang.bind(Main.wm, Main.wm._startSwitcher));
|
||||
setKeybinding('switch-group', Lang.bind(Main.wm, Main.wm._startSwitcher));
|
||||
setKeybinding('switch-applications-backward', Lang.bind(Main.wm, Main.wm._startSwitcher));
|
||||
setKeybinding('switch-group-backward', Lang.bind(Main.wm, Main.wm._startSwitcher));
|
||||
|
||||
for (prop in injections)
|
||||
AltTab.WindowSwitcherPopup.prototype[prop] = injections[prop];
|
||||
delete Main.wm._forcedWindowSwitcher;
|
||||
}
|
||||
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": "Substitute Alt-Tab with a window based switcher that does not group by application.\nThis extension is part of Classic Mode and is officially supported by GNOME. Please do not report bugs using the form below, use GNOME Bugzilla instead.",
|
||||
"original-authors": [ "jw@bargsten.org", "thomas.bouffon@gmail.com" ],
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
85
extensions/alternate-tab/prefs.js
Normal file
85
extensions/alternate-tab/prefs.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
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 = 24;
|
||||
this.row_spacing = 6;
|
||||
this.orientation = Gtk.Orientation.VERTICAL;
|
||||
|
||||
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.window-switcher' });
|
||||
|
||||
let presentLabel = '<b>' + _("Present windows as") + '</b>';
|
||||
this.add(new Gtk.Label({ label: presentLabel, use_markup: true,
|
||||
halign: Gtk.Align.START }));
|
||||
|
||||
let align = new Gtk.Alignment({ left_padding: 12 });
|
||||
this.add(align);
|
||||
|
||||
let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
|
||||
row_spacing: 6,
|
||||
column_spacing: 6 });
|
||||
align.add(grid);
|
||||
|
||||
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);
|
||||
}));
|
||||
grid.add(radio);
|
||||
|
||||
if (mode == currentMode)
|
||||
radio.active = true;
|
||||
}
|
||||
|
||||
let check = new Gtk.CheckButton({ label: _("Show only windows in the current workspace"),
|
||||
margin_top: 6 });
|
||||
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 */
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
@@ -3,7 +3,7 @@
|
||||
"uuid": "@uuid@",
|
||||
"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.",
|
||||
"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 Bugzilla instead.",
|
||||
"original-authors": [ "e2002@bk.ru", "debarshir@gnome.org" ],
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
|
||||
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,134 +1,149 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Start apps on custom workspaces
|
||||
/* exported init enable disable */
|
||||
|
||||
const { Shell } = imports.gi;
|
||||
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;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
class WindowMover {
|
||||
constructor() {
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
this._appConfigs = new Map();
|
||||
this._appData = new Map();
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
this._appsChangedId =
|
||||
this._appSystem.connect('installed-changed',
|
||||
this._updateAppData.bind(this));
|
||||
const SETTINGS_KEY = 'application-list';
|
||||
|
||||
this._settings.connect('changed', this._updateAppConfigs.bind(this));
|
||||
this._updateAppConfigs();
|
||||
}
|
||||
let settings;
|
||||
|
||||
_updateAppConfigs() {
|
||||
this._appConfigs.clear();
|
||||
const WindowMover = new Lang.Class({
|
||||
Name: 'AutoMoveWindows.WindowMover',
|
||||
|
||||
this._settings.get_strv('application-list').forEach(v => {
|
||||
let [appId, num] = v.split(':');
|
||||
this._appConfigs.set(appId, parseInt(num) - 1);
|
||||
});
|
||||
_init: function() {
|
||||
this._settings = settings;
|
||||
this._windowTracker = Shell.WindowTracker.get_default();
|
||||
|
||||
this._updateAppData();
|
||||
}
|
||||
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));
|
||||
},
|
||||
|
||||
_updateAppData() {
|
||||
let ids = [...this._appConfigs.keys()];
|
||||
let removedApps = [...this._appData.keys()]
|
||||
.filter(a => !ids.includes(a.id));
|
||||
removedApps.forEach(app => {
|
||||
app.disconnect(this._appData.get(app).windowsChangedId);
|
||||
this._appData.delete(app);
|
||||
});
|
||||
|
||||
let addedApps = ids
|
||||
.map(id => this._appSystem.lookup_app(id))
|
||||
.filter(app => app && !this._appData.has(app));
|
||||
addedApps.forEach(app => {
|
||||
let data = {
|
||||
windowsChangedId: app.connect('windows-changed',
|
||||
this._appWindowsChanged.bind(this)),
|
||||
moveWindowsId: 0,
|
||||
windows: app.get_windows(),
|
||||
};
|
||||
this._appData.set(app, data);
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._appsChangedId) {
|
||||
this._appSystem.disconnect(this._appsChangedId);
|
||||
this._appsChangedId = 0;
|
||||
destroy: function() {
|
||||
if (this._windowCreatedId) {
|
||||
global.screen.get_display().disconnect(this._windowCreatedId);
|
||||
this._windowCreatedId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
if (this._settings) {
|
||||
this._settings.run_dispose();
|
||||
this._settings = null;
|
||||
_ensureAtLeastWorkspaces: function(num, window) {
|
||||
for (let j = global.screen.n_workspaces; j <= num; j++) {
|
||||
window.change_workspace_by_index(j-1, false);
|
||||
global.screen.append_new_workspace(false, 0);
|
||||
}
|
||||
},
|
||||
|
||||
this._appConfigs.clear();
|
||||
this._updateAppData();
|
||||
}
|
||||
|
||||
_moveWindow(window, workspaceNum) {
|
||||
if (window.skip_taskbar || window.is_on_all_workspaces())
|
||||
_findAndMove: function(display, window, noRecurse) {
|
||||
if (window.skip_taskbar)
|
||||
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);
|
||||
|
||||
window.change_workspace_by_index(workspace_num, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let prevCheckWorkspaces;
|
||||
let winMover;
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
settings = Convenience.getSettings();
|
||||
}
|
||||
|
||||
function myCheckWorkspaces() {
|
||||
let 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]);
|
||||
}
|
||||
let i;
|
||||
let emptyWorkspaces = new Array(this._workspaces.length);
|
||||
|
||||
if (!Meta.prefs_get_dynamic_workspaces()) {
|
||||
this._checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the original method only removes empty workspaces at the end
|
||||
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1));
|
||||
prevCheckWorkspaces.call(this);
|
||||
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId);
|
||||
for (i = 0; i < this._workspaces.length; i++) {
|
||||
let lastRemoved = this._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)) ||
|
||||
this._workspaces[i]._keepAliveId)
|
||||
emptyWorkspaces[i] = false;
|
||||
else
|
||||
emptyWorkspaces[i] = true;
|
||||
}
|
||||
|
||||
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
|
||||
for (i = 0; i < sequences.length; i++) {
|
||||
let index = sequences[i].get_workspace();
|
||||
if (index >= 0 && index <= global.screen.n_workspaces)
|
||||
emptyWorkspaces[index] = false;
|
||||
}
|
||||
|
||||
let windows = global.get_window_actors();
|
||||
for (i = 0; i < windows.length; i++) {
|
||||
let winActor = windows[i];
|
||||
let win = winActor.meta_window;
|
||||
if (win.is_on_all_workspaces())
|
||||
continue;
|
||||
|
||||
let workspaceIndex = win.get_workspace().index();
|
||||
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();
|
||||
emptyWorkspaces[activeWorkspaceIndex] = false;
|
||||
|
||||
// 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(this._workspaces[i], global.get_current_time());
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
this._checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
@@ -1,275 +1,274 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Start apps on custom workspaces
|
||||
/* exported init buildPrefsWidget */
|
||||
|
||||
const { Gio, GLib, GObject, Gtk, Pango } = imports.gi;
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
const AutoMoveSettingsWidget = GObject.registerClass(
|
||||
class AutoMoveSettingsWidget extends Gtk.ScrolledWindow {
|
||||
_init() {
|
||||
super._init({
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
});
|
||||
const Columns = {
|
||||
APPINFO: 0,
|
||||
DISPLAY_NAME: 1,
|
||||
ICON: 2,
|
||||
WORKSPACE: 3,
|
||||
ADJUSTMENT: 4
|
||||
};
|
||||
|
||||
const box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
halign: Gtk.Align.CENTER,
|
||||
spacing: 12,
|
||||
margin_top: 36,
|
||||
margin_bottom: 36,
|
||||
margin_start: 36,
|
||||
margin_end: 36,
|
||||
});
|
||||
this.set_child(box);
|
||||
const Widget = new GObject.Class({
|
||||
Name: 'AutoMoveWindows.Prefs.Widget',
|
||||
GTypeName: 'AutoMoveWindowsPrefsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
box.append(new Gtk.Label({
|
||||
label: '<b>%s</b>'.format(_('Workspace Rules')),
|
||||
use_markup: true,
|
||||
halign: Gtk.Align.START,
|
||||
}));
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this.set_orientation(Gtk.Orientation.VERTICAL);
|
||||
|
||||
this._list = new Gtk.ListBox({
|
||||
selection_mode: Gtk.SelectionMode.NONE,
|
||||
valign: Gtk.Align.START,
|
||||
show_separators: true,
|
||||
});
|
||||
box.append(this._list);
|
||||
this._settings = Convenience.getSettings();
|
||||
this._settings.connect('changed', Lang.bind(this, this._refresh));
|
||||
this._changedPermitted = false;
|
||||
|
||||
const context = this._list.get_style_context();
|
||||
const cssProvider = new Gtk.CssProvider();
|
||||
cssProvider.load_from_data(
|
||||
'list { min-width: 30em; }');
|
||||
this._store = new Gtk.ListStore();
|
||||
this._store.set_column_types([Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon, GObject.TYPE_INT,
|
||||
Gtk.Adjustment]);
|
||||
|
||||
context.add_provider(cssProvider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context.add_class('frame');
|
||||
let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN});
|
||||
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.add(scrolled);
|
||||
|
||||
this._list.append(new NewRuleRow());
|
||||
|
||||
this._actionGroup = new Gio.SimpleActionGroup();
|
||||
this._list.insert_action_group('rules', this._actionGroup);
|
||||
this._treeView = new Gtk.TreeView({ model: this._store,
|
||||
hexpand: true, vexpand: true });
|
||||
this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE);
|
||||
|
||||
let action;
|
||||
action = new Gio.SimpleAction({ name: 'add' });
|
||||
action.connect('activate', this._onAddActivated.bind(this));
|
||||
this._actionGroup.add_action(action);
|
||||
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);
|
||||
|
||||
action = new Gio.SimpleAction({
|
||||
name: 'remove',
|
||||
parameter_type: new GLib.VariantType('s'),
|
||||
});
|
||||
action.connect('activate', this._onRemoveActivated.bind(this));
|
||||
this._actionGroup.add_action(action);
|
||||
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);
|
||||
|
||||
action = new Gio.SimpleAction({ name: 'update' });
|
||||
action.connect('activate', () => {
|
||||
this._settings.set_strv(SETTINGS_KEY,
|
||||
this._getRuleRows().map(row => `${row.id}:${row.value}`));
|
||||
});
|
||||
this._actionGroup.add_action(action);
|
||||
this._updateAction = action;
|
||||
scrolled.add(this._treeView);
|
||||
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
this._changedId = this._settings.connect('changed',
|
||||
this._sync.bind(this));
|
||||
this._sync();
|
||||
let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR });
|
||||
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
this.add(toolbar);
|
||||
|
||||
this.connect('destroy', () => this._settings.run_dispose());
|
||||
}
|
||||
let newButton = new Gtk.ToolButton({ icon_name: 'bookmark-new-symbolic',
|
||||
label: _("Add Rule"),
|
||||
is_important: true });
|
||||
newButton.connect('clicked', Lang.bind(this, this._createNew));
|
||||
toolbar.add(newButton);
|
||||
|
||||
_onAddActivated() {
|
||||
const dialog = new NewRuleDialog(this.get_root());
|
||||
dialog.connect('response', (dlg, id) => {
|
||||
const appInfo = id === Gtk.ResponseType.OK
|
||||
? dialog.get_widget().get_app_info() : null;
|
||||
if (appInfo) {
|
||||
this._settings.set_strv(SETTINGS_KEY, [
|
||||
...this._settings.get_strv(SETTINGS_KEY),
|
||||
`${appInfo.get_id()}:1`,
|
||||
]);
|
||||
}
|
||||
dialog.destroy();
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
let delButton = new Gtk.ToolButton({ icon_name: 'edit-delete-symbolic' });
|
||||
delButton.connect('clicked', Lang.bind(this, this._deleteSelected));
|
||||
toolbar.add(delButton);
|
||||
|
||||
_onRemoveActivated(action, param) {
|
||||
const removed = param.deepUnpack();
|
||||
this._settings.set_strv(SETTINGS_KEY,
|
||||
this._settings.get_strv(SETTINGS_KEY).filter(entry => {
|
||||
const [id] = entry.split(':');
|
||||
return id !== removed;
|
||||
}));
|
||||
}
|
||||
let selection = this._treeView.get_selection();
|
||||
selection.connect('changed',
|
||||
function() {
|
||||
delButton.sensitive = selection.count_selected_rows() > 0;
|
||||
});
|
||||
delButton.sensitive = selection.count_selected_rows() > 0;
|
||||
|
||||
_getRuleRows() {
|
||||
return [...this._list].filter(row => !!row.id);
|
||||
}
|
||||
|
||||
_sync() {
|
||||
const oldRules = this._getRuleRows();
|
||||
const newRules = this._settings.get_strv(SETTINGS_KEY).map(entry => {
|
||||
const [id, value] = entry.split(':');
|
||||
return { id, value };
|
||||
});
|
||||
|
||||
this._settings.block_signal_handler(this._changedId);
|
||||
this._updateAction.enabled = false;
|
||||
|
||||
newRules.forEach(({ id, value }, index) => {
|
||||
const row = oldRules.find(r => r.id === id);
|
||||
const appInfo = row
|
||||
? null : Gio.DesktopAppInfo.new(id);
|
||||
|
||||
if (row)
|
||||
row.set({ value });
|
||||
else if (appInfo)
|
||||
this._list.insert(new RuleRow(appInfo, value), index);
|
||||
});
|
||||
|
||||
const removed = oldRules.filter(
|
||||
({ id }) => !newRules.find(r => r.id === id));
|
||||
removed.forEach(r => this._list.remove(r));
|
||||
|
||||
this._settings.unblock_signal_handler(this._changedId);
|
||||
this._updateAction.enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
const RuleRow = GObject.registerClass({
|
||||
Properties: {
|
||||
'id': GObject.ParamSpec.string(
|
||||
'id', 'id', 'id',
|
||||
GObject.ParamFlags.READABLE,
|
||||
''),
|
||||
'value': GObject.ParamSpec.uint(
|
||||
'value', 'value', 'value',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
1, WORKSPACE_MAX, 1),
|
||||
this._changedPermitted = true;
|
||||
this._refresh();
|
||||
},
|
||||
}, class RuleRow extends Gtk.ListBoxRow {
|
||||
_init(appInfo, value) {
|
||||
const box = new Gtk.Box({
|
||||
spacing: 6,
|
||||
margin_top: 6,
|
||||
margin_bottom: 6,
|
||||
margin_start: 6,
|
||||
margin_end: 6,
|
||||
});
|
||||
|
||||
super._init({
|
||||
activatable: false,
|
||||
value,
|
||||
child: box,
|
||||
});
|
||||
this._appInfo = appInfo;
|
||||
_createNew: function() {
|
||||
let dialog = new Gtk.Dialog({ title: _("Create new matching rule"),
|
||||
transient_for: this.get_toplevel(),
|
||||
use_header_bar: true,
|
||||
modal: true });
|
||||
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
|
||||
let addButton = dialog.add_button(_("Add"), Gtk.ResponseType.OK);
|
||||
dialog.set_default_response(Gtk.ResponseType.OK);
|
||||
|
||||
const icon = new Gtk.Image({
|
||||
gicon: appInfo.get_icon(),
|
||||
pixel_size: 32,
|
||||
});
|
||||
icon.get_style_context().add_class('icon-dropshadow');
|
||||
box.append(icon);
|
||||
let grid = new Gtk.Grid({ column_spacing: 10,
|
||||
row_spacing: 15,
|
||||
margin: 10 });
|
||||
dialog._appChooser = new Gtk.AppChooserWidget({ show_all: true });
|
||||
dialog._appChooser.connect('application-selected', Lang.bind(this,
|
||||
function(w, appInfo) {
|
||||
addButton.sensitive = appInfo &&
|
||||
this._checkId(appInfo.get_id());
|
||||
}));
|
||||
let appInfo = dialog._appChooser.get_app_info();
|
||||
addButton.sensitive = appInfo && this._checkId(appInfo.get_id());
|
||||
|
||||
const label = new Gtk.Label({
|
||||
label: appInfo.get_display_name(),
|
||||
halign: Gtk.Align.START,
|
||||
hexpand: true,
|
||||
max_width_chars: 20,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
});
|
||||
box.append(label);
|
||||
grid.attach(dialog._appChooser, 0, 0, 2, 1);
|
||||
grid.attach(new Gtk.Label({ label: _("Workspace"),
|
||||
halign: Gtk.Align.END }), 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);
|
||||
|
||||
const spinButton = new Gtk.SpinButton({
|
||||
adjustment: new Gtk.Adjustment({
|
||||
lower: 1,
|
||||
upper: WORKSPACE_MAX,
|
||||
step_increment: 1,
|
||||
}),
|
||||
snap_to_ticks: true,
|
||||
margin_end: 6,
|
||||
});
|
||||
this.bind_property('value',
|
||||
spinButton, 'value',
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
|
||||
box.append(spinButton);
|
||||
dialog.connect('response', Lang.bind(this, function(dialog, id) {
|
||||
if (id != Gtk.ResponseType.OK) {
|
||||
dialog.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const button = new Gtk.Button({
|
||||
action_name: 'rules.remove',
|
||||
action_target: new GLib.Variant('s', this.id),
|
||||
icon_name: 'edit-delete-symbolic',
|
||||
});
|
||||
box.append(button);
|
||||
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.connect('notify::value',
|
||||
() => this.activate_action('rules.update', null));
|
||||
}
|
||||
this._changedPermitted = false;
|
||||
this._appendItem(appInfo.get_id(), index);
|
||||
this._changedPermitted = true;
|
||||
|
||||
get id() {
|
||||
return this._appInfo.get_id();
|
||||
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]);
|
||||
|
||||
dialog.destroy();
|
||||
}));
|
||||
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._changedPermitted = true;
|
||||
this._store.remove(iter);
|
||||
}
|
||||
},
|
||||
|
||||
_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);
|
||||
},
|
||||
|
||||
_checkId: function(id) {
|
||||
let items = this._settings.get_strv(SETTINGS_KEY);
|
||||
return !items.some(function(i) { return i.startsWith(id + ':'); });
|
||||
},
|
||||
|
||||
_appendItem: function(id, workspace) {
|
||||
let currentItems = this._settings.get_strv(SETTINGS_KEY);
|
||||
currentItems.push(id + ':' + workspace);
|
||||
this._settings.set_strv(SETTINGS_KEY, currentItems);
|
||||
},
|
||||
|
||||
_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);
|
||||
}
|
||||
});
|
||||
|
||||
const NewRuleRow = GObject.registerClass(
|
||||
class NewRuleRow extends Gtk.ListBoxRow {
|
||||
_init() {
|
||||
super._init({
|
||||
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')]);
|
||||
}
|
||||
});
|
||||
|
||||
const NewRuleDialog = GObject.registerClass(
|
||||
class NewRuleDialog extends Gtk.AppChooserDialog {
|
||||
_init(parent) {
|
||||
super._init({
|
||||
transient_for: parent,
|
||||
modal: true,
|
||||
});
|
||||
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
|
||||
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())));
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
return new AutoMoveSettingsWidget();
|
||||
let widget = new Widget({ margin: 12 });
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
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,60 +1,56 @@
|
||||
/* exported init enable disable */
|
||||
// Drive menu extension
|
||||
const { Clutter, Gio, GObject, Shell, St } = imports.gi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
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;
|
||||
|
||||
var MountMenuItem = GObject.registerClass(
|
||||
class MountMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
_init(mount) {
|
||||
super._init({
|
||||
style_class: 'drive-menu-item',
|
||||
});
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
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;
|
||||
const MountMenuItem = new Lang.Class({
|
||||
Name: 'DriveMenu.MountMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
this.mount = mount;
|
||||
_init: function(mount) {
|
||||
this.parent();
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this.label = new St.Label({ text: mount.get_name() });
|
||||
this.actor.add(this.label, { expand: true });
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
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.mount = mount;
|
||||
|
||||
this._changedId = mount.connect('changed', this._syncVisibility.bind(this));
|
||||
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.actor.add(ejectButton);
|
||||
|
||||
this._changedId = mount.connect('changed', Lang.bind(this, this._syncVisibility));
|
||||
this._syncVisibility();
|
||||
}
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
destroy: function() {
|
||||
if (this._changedId) {
|
||||
this.mount.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_isInteresting() {
|
||||
_isInteresting: function() {
|
||||
if (!this.mount.can_eject() && !this.mount.can_unmount())
|
||||
return false;
|
||||
if (this.mount.is_shadowed())
|
||||
@@ -62,147 +58,148 @@ class MountMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
|
||||
let volume = this.mount.get_volume();
|
||||
|
||||
if (!volume) {
|
||||
if (volume == null) {
|
||||
// probably a GDaemonMount, could be network or
|
||||
// local, but we can't tell; assume it's local for now
|
||||
return true;
|
||||
}
|
||||
|
||||
return volume.get_identifier('class') !== 'network';
|
||||
}
|
||||
return volume.get_identifier('class') != 'network';
|
||||
},
|
||||
|
||||
_syncVisibility() {
|
||||
this.visible = this._isInteresting();
|
||||
}
|
||||
_syncVisibility: function() {
|
||||
this.actor.visible = this._isInteresting();
|
||||
},
|
||||
|
||||
_eject() {
|
||||
let unmountArgs = [
|
||||
Gio.MountUnmountFlags.NONE,
|
||||
new ShellMountOperation.ShellMountOperation(this.mount).mountOp,
|
||||
null, // Gio.Cancellable
|
||||
];
|
||||
_eject: function() {
|
||||
let mountOp = new ShellMountOperation.ShellMountOperation(this.mount);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
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(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch (e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
}
|
||||
_unmountFinish: function(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish(mount, result) {
|
||||
try {
|
||||
mount.eject_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(exception) {
|
||||
// TRANSLATORS: %s is the filesystem name
|
||||
let msg = _('Ejecting drive “%s” failed:').format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
}
|
||||
_reportFailure: function(exception) {
|
||||
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();
|
||||
activate: function(event) {
|
||||
let context = global.create_app_launch_context(event.get_time(), -1);
|
||||
Gio.AppInfo.launch_default_for_uri(uri, context);
|
||||
Gio.AppInfo.launch_default_for_uri(this.mount.get_root().get_uri(),
|
||||
context);
|
||||
|
||||
super.activate(event);
|
||||
this.parent(event);
|
||||
}
|
||||
});
|
||||
|
||||
let DriveMenu = GObject.registerClass(
|
||||
class DriveMenu extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(0.0, _('Removable devices'));
|
||||
const DriveMenu = new Lang.Class({
|
||||
Name: 'DriveMenu.DriveMenu',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
let icon = new St.Icon({
|
||||
icon_name: 'media-eject-symbolic',
|
||||
style_class: 'system-status-icon',
|
||||
});
|
||||
_init: function() {
|
||||
this.parent(0.0, _("Removable devices"));
|
||||
|
||||
this.add_child(icon);
|
||||
let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
let icon = new St.Icon({ icon_name: 'media-eject-symbolic',
|
||||
style_class: 'system-status-icon' });
|
||||
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
this._addedId = this._monitor.connect('mount-added', (monitor, mount) => {
|
||||
this._addMount(mount);
|
||||
this._updateMenuVisibility();
|
||||
});
|
||||
this._removedId = this._monitor.connect('mount-removed', (monitor, mount) => {
|
||||
this._removeMount(mount);
|
||||
this._updateMenuVisibility();
|
||||
});
|
||||
hbox.add_child(icon);
|
||||
hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
|
||||
this.actor.add_child(hbox);
|
||||
|
||||
this._mounts = [];
|
||||
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._monitor.get_mounts().forEach(this._addMount.bind(this));
|
||||
this._mounts = [ ];
|
||||
|
||||
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._monitor.get_mounts().forEach(Lang.bind(this, this._addMount));
|
||||
|
||||
this._updateMenuVisibility();
|
||||
}
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Open File"), function(event) {
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let app = appSystem.lookup_app('org.gnome.Nautilus.desktop');
|
||||
app.activate_full(-1, event.get_time());
|
||||
});
|
||||
|
||||
_updateMenuVisibility() {
|
||||
if (this._mounts.filter(i => i.visible).length > 0)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
}
|
||||
this._updateMenuVisibility();
|
||||
},
|
||||
|
||||
_addMount(mount) {
|
||||
let item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
}
|
||||
_updateMenuVisibility: function() {
|
||||
if (this._mounts.filter(function(i) i.actor.visible).length > 0)
|
||||
this.actor.show();
|
||||
else
|
||||
this.actor.hide();
|
||||
},
|
||||
|
||||
_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');
|
||||
}
|
||||
_addMount: function(mount) {
|
||||
let item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
if (this._addedId) {
|
||||
this._monitor.disconnect(this._addedId);
|
||||
this._monitor.disconnect(this._removedId);
|
||||
this._addedId = 0;
|
||||
this._removedId = 0;
|
||||
_removeMount: function(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');
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._connectedId) {
|
||||
this._monitor.disconnect(this._connectedId);
|
||||
this._monitor.disconnect(this._disconnectedId);
|
||||
this._connectedId = 0;
|
||||
this._disconnectedId = 0;
|
||||
}
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
let _indicator;
|
||||
|
||||
function enable() {
|
||||
_indicator = new DriveMenu();
|
||||
_indicator = new DriveMenu;
|
||||
Main.panel.addToStatusArea('drive-menu', _indicator);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
@@ -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>
|
||||
57
extensions/example/prefs.js
Normal file
57
extensions/example/prefs.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// -*- 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 = 12;
|
||||
this.row_spacing = this.column_spacing = 6;
|
||||
this.set_orientation(Gtk.Orientation.VERTICAL);
|
||||
|
||||
this.add(new Gtk.Label({ label: '<b>' + _("Message") + '</b>',
|
||||
use_markup: true,
|
||||
halign: Gtk.Align.START }));
|
||||
|
||||
let entry = new Gtk.Entry({ hexpand: true,
|
||||
margin_bottom: 12 });
|
||||
this.add(entry);
|
||||
|
||||
this._settings = Convenience.getSettings();
|
||||
this._settings.bind('hello-text', entry, 'text', Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
// 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.add(new Gtk.Label({ label: primaryText,
|
||||
wrap: true, xalign: 0 }));
|
||||
}
|
||||
});
|
||||
|
||||
function buildPrefsWidget() {
|
||||
let widget = new ExamplePrefsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
8
extensions/example/stylesheet.css
Normal file
8
extensions/example/stylesheet.css
Normal file
@@ -0,0 +1,8 @@
|
||||
/* Example stylesheet */
|
||||
.helloworld-label {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: rgba(10,10,10,0.7);
|
||||
border-radius: 5px;
|
||||
}
|
||||
3
extensions/launch-new-instance/Makefile.am
Normal file
3
extensions/launch-new-instance/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = launch-new-instance
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,15 +1,17 @@
|
||||
/* exported enable disable */
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
|
||||
let _activateOriginal = null;
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
function enable() {
|
||||
_activateOriginal = AppDisplay.AppIcon.prototype.activate;
|
||||
AppDisplay.AppIcon.prototype.activate = function () {
|
||||
_activateOriginal.call(this, 2);
|
||||
};
|
||||
_activateOriginal = AppDisplay.AppIcon.prototype.activate;
|
||||
AppDisplay.AppIcon.prototype.activate = function() {
|
||||
_activateOriginal.call(this, 2);
|
||||
};
|
||||
}
|
||||
|
||||
function disable() {
|
||||
AppDisplay.AppIcon.prototype.activate = _activateOriginal;
|
||||
AppDisplay.AppIcon.prototype.activate = _activateOriginal;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
@@ -4,7 +4,7 @@
|
||||
"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.",
|
||||
"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 Bugzilla instead.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
@@ -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 = files(e + '/stylesheet.css')
|
||||
|
||||
subdir(e)
|
||||
|
||||
install_data (extension_sources + extension_data,
|
||||
install_dir: join_paths(extensiondir, uuid)
|
||||
)
|
||||
endforeach
|
||||
|
||||
install_data (extension_schemas,
|
||||
install_dir: schemadir
|
||||
)
|
||||
@@ -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')
|
||||
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,93 +1,149 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
/* exported enable disable */
|
||||
const { Clutter } = imports.gi;
|
||||
// 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;
|
||||
|
||||
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;
|
||||
|
||||
const Workspace = imports.ui.workspace;
|
||||
const WindowPositionFlags = Workspace.WindowPositionFlags;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const { WindowPreview } = imports.ui.windowPreview;
|
||||
const Workspace = imports.ui.workspace;
|
||||
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.desktop.wm.preferences';
|
||||
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(settings) {
|
||||
super();
|
||||
this._settings = settings;
|
||||
}
|
||||
function enable() {
|
||||
resetState();
|
||||
|
||||
computeLayout(windows, layout) {
|
||||
layout.windows = windows;
|
||||
}
|
||||
let settings = Convenience.getSettings();
|
||||
let useMoreScreen = settings.get_boolean('use-more-screen');
|
||||
let windowCaptionsOnTop = settings.get_boolean('window-captions-on-top');
|
||||
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, area) {
|
||||
// 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.
|
||||
|
||||
let areaRect = new Rect(area.x, area.y, area.width, area.height);
|
||||
let bounds = areaRect.copy();
|
||||
let clones = layout.windows;
|
||||
let area_rect = new Rect(area.x, area.y, area.width, area.height);
|
||||
let bounds = area_rect.copy();
|
||||
|
||||
let direction = 0;
|
||||
let directions = [];
|
||||
@@ -101,36 +157,36 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
// 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_rect.height / area_rect.width )
|
||||
diff[0] *= 2;
|
||||
else
|
||||
diff[1] *= 2;
|
||||
@@ -145,7 +201,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
|
||||
@@ -159,33 +215,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]);
|
||||
@@ -198,72 +254,194 @@ 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_rect.width / bounds.width,
|
||||
area_rect.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_rect.width - bounds.width * scale) / 2;
|
||||
bounds.y = bounds.y - (area_rect.height - bounds.height * scale) / 2;
|
||||
bounds.width = area_rect.width / scale;
|
||||
bounds.height = area_rect.height / scale;
|
||||
|
||||
// Move all windows back onto the screen and set their scale
|
||||
for (let i = 0; i < rects.length; i++)
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
rects[i].translate(-bounds.x, -bounds.y);
|
||||
}
|
||||
|
||||
// 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 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;
|
||||
rects[i].x = rects[i].x * scale + area_rect.x;
|
||||
rects[i].y = rects[i].y * scale + area_rect.y;
|
||||
|
||||
slots.push([rects[i].x, rects[i].y, rects[i].width, rects[i].height, clones[i]]);
|
||||
slots.push([rects[i].x, rects[i].y, scale, clones[i]]);
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
workspaceInjections['_calculateWindowTransformationsNatural'] = undefined;
|
||||
|
||||
let winInjections, workspaceInjections;
|
||||
/**
|
||||
* _updateWindowPositions:
|
||||
* @flags:
|
||||
* INITIAL - this is the initial positioning of the windows.
|
||||
* ANIMATE - Indicates that we need animate changing position.
|
||||
*/
|
||||
workspaceInjections['_updateWindowPositions'] = Workspace.Workspace.prototype._updateWindowPositions;
|
||||
Workspace.Workspace.prototype._updateWindowPositions = function(flags) {
|
||||
if (this._currentLayout == null) {
|
||||
this._recalculateWindowPositions(flags);
|
||||
return;
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
winInjections = { };
|
||||
workspaceInjections = { };
|
||||
}
|
||||
let initialPositioning = flags & WindowPositionFlags.INITIAL;
|
||||
let animate = flags & WindowPositionFlags.ANIMATE;
|
||||
|
||||
function enable() {
|
||||
resetState();
|
||||
let layout = this._currentLayout;
|
||||
let strategy = layout.strategy;
|
||||
|
||||
let settings = ExtensionUtils.getSettings();
|
||||
let [, , padding] = this._getSpacingAndPadding();
|
||||
let area = Workspace.padArea(this._actualGeometry, padding);
|
||||
|
||||
workspaceInjections['_createBestLayout'] = Workspace.WorkspaceLayout.prototype._createBestLayout;
|
||||
Workspace.WorkspaceLayout.prototype._createBestLayout = function (area) {
|
||||
let strategy = new NaturalLayoutStrategy(settings);
|
||||
let layout = { area, strategy };
|
||||
strategy.computeLayout(this._sortedWindows, layout);
|
||||
/// EDIT replace this version by our own:
|
||||
//let slots = strategy.computeWindowSlots(layout, area);
|
||||
|
||||
return layout;
|
||||
};
|
||||
|
||||
// position window titles on top of windows in overlay
|
||||
winInjections['_init'] = WindowPreview.prototype._init;
|
||||
WindowPreview.prototype._init = function (metaWindow, workspace) {
|
||||
winInjections['_init'].call(this, metaWindow, workspace);
|
||||
/// EDIT copied from _realRecalculateWindowPositions:
|
||||
let clones = this._windows.slice();
|
||||
if (clones.length == 0)
|
||||
return;
|
||||
|
||||
const constraint = this._title.get_constraints().find(
|
||||
c => c.align_axis && c.align_axis === Clutter.AlignAxis.Y_AXIS);
|
||||
constraint.factor = settings.get_boolean('window-captions-on-top')
|
||||
? 0 : 1;
|
||||
};
|
||||
clones.sort(function(a, b) {
|
||||
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
|
||||
});
|
||||
|
||||
if (this._reservedSlot)
|
||||
clones.push(this._reservedSlot);
|
||||
|
||||
/// EDIT our own window placement function:
|
||||
let slots = this._calculateWindowTransformationsNatural(clones, area);
|
||||
|
||||
|
||||
let currentWorkspace = global.screen.get_active_workspace();
|
||||
let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace;
|
||||
|
||||
for (let i = 0; i < slots.length; i++) {
|
||||
let slot = slots[i];
|
||||
let [x, y, scale, clone] = slot;
|
||||
let metaWindow = clone.metaWindow;
|
||||
let overlay = clone.overlay;
|
||||
clone.slotId = i;
|
||||
|
||||
// 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;
|
||||
|
||||
let cloneWidth = clone.actor.width * scale;
|
||||
let cloneHeight = clone.actor.height * scale;
|
||||
clone.slot = [x, y, cloneWidth, cloneHeight];
|
||||
|
||||
if (overlay && (initialPositioning || !clone.positioned))
|
||||
overlay.hide();
|
||||
|
||||
if (!clone.positioned) {
|
||||
// This window appeared after the overview was already up
|
||||
// Grow the clone from the center of the slot
|
||||
clone.actor.x = x + cloneWidth / 2;
|
||||
clone.actor.y = y + cloneHeight / 2;
|
||||
clone.actor.scale_x = 0;
|
||||
clone.actor.scale_y = 0;
|
||||
clone.positioned = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Tweener.addTween(clone.actor,
|
||||
{ opacity: 255,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeInQuad'
|
||||
});
|
||||
}
|
||||
|
||||
this._animateClone(clone, overlay, x, y, scale, initialPositioning);
|
||||
} else {
|
||||
// cancel any active tweens (otherwise they might override our changes)
|
||||
Tweener.removeTweens(clone.actor);
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
clone.overlay.relayout(false);
|
||||
this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// position window titles on top of windows in overlay ////
|
||||
if (windowCaptionsOnTop) {
|
||||
|
||||
/// This is almost a direct copy of the original relayout function. Differences are marked.
|
||||
winInjections['relayout'] = Workspace.WindowOverlay.prototype.relayout;
|
||||
Workspace.WindowOverlay.prototype.relayout = function(animate) {
|
||||
winInjections['relayout'].call(this, animate);
|
||||
let title = this.title;
|
||||
let border = this.border;
|
||||
|
||||
this._parentActor.set_child_above_sibling(title, border);
|
||||
|
||||
Tweener.removeTweens(title);
|
||||
|
||||
let [cloneX, cloneY, cloneWidth, cloneHeight] = this._windowClone.slot;
|
||||
|
||||
// Clutter.Actor.get_preferred_width() will return the fixed width if one
|
||||
// is set, so we need to reset the width by calling set_width(-1), to forward
|
||||
// the call down to StLabel.
|
||||
// We also need to save and restore the current width, otherwise the animation
|
||||
// starts from the wrong point.
|
||||
let prevTitleWidth = title.width;
|
||||
title.set_width(-1);
|
||||
let [titleMinWidth, titleNatWidth] = title.get_preferred_width(-1);
|
||||
let titleWidth = Math.max(titleMinWidth, Math.min(titleNatWidth, cloneWidth));
|
||||
title.width = prevTitleWidth;
|
||||
|
||||
let titleX = cloneX + (cloneWidth - titleWidth) / 2;
|
||||
|
||||
/// this is the actual difference to original gnome-shell:
|
||||
//let titleY = cloneY + cloneHeight + title._spacing;
|
||||
let titleY = cloneY - title.height + title._spacing;
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function removeInjection(object, injection, name) {
|
||||
@@ -277,10 +455,17 @@ function disable() {
|
||||
var i;
|
||||
|
||||
for (i in workspaceInjections)
|
||||
removeInjection(Workspace.WorkspaceLayout.prototype, workspaceInjections, i);
|
||||
removeInjection(Workspace.Workspace.prototype, workspaceInjections, i);
|
||||
for (i in winInjections)
|
||||
removeInjection(WindowPreview.prototype, winInjections, i);
|
||||
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')
|
||||
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,150 +1,140 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/* exported init enable disable */
|
||||
|
||||
const { Clutter, GObject, St } = imports.gi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Panel = imports.ui.panel;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = x => x;
|
||||
const N_ = function(x) { return 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;
|
||||
|
||||
var PlaceMenuItem = GObject.registerClass(
|
||||
class PlaceMenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
_init(info) {
|
||||
super._init({
|
||||
style_class: 'place-menu-item',
|
||||
});
|
||||
this._info = info;
|
||||
const PlaceMenuItem = new Lang.Class({
|
||||
Name: 'PlaceMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
this._icon = new St.Icon({
|
||||
gicon: info.icon,
|
||||
icon_size: PLACE_ICON_SIZE,
|
||||
});
|
||||
this.add_child(this._icon);
|
||||
_init: function(info) {
|
||||
this.parent();
|
||||
this._info = info;
|
||||
|
||||
this._label = new St.Label({
|
||||
text: info.name,
|
||||
x_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_child(this._label);
|
||||
this._icon = new St.Icon({ gicon: info.icon,
|
||||
icon_size: PLACE_ICON_SIZE });
|
||||
this.actor.add_child(this._icon);
|
||||
|
||||
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._label = new St.Label({ text: info.name });
|
||||
this.actor.add_child(this._label);
|
||||
|
||||
this._changedId = info.connect('changed',
|
||||
this._propertiesChanged.bind(this));
|
||||
}
|
||||
Lang.bind(this, this._propertiesChanged));
|
||||
},
|
||||
|
||||
destroy() {
|
||||
destroy: function() {
|
||||
if (this._changedId) {
|
||||
this._info.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
|
||||
activate(event) {
|
||||
this._info.launch(event.get_time());
|
||||
activate: function(event) {
|
||||
this._info.launch(event.get_time());
|
||||
|
||||
super.activate(event);
|
||||
}
|
||||
this.parent(event);
|
||||
},
|
||||
|
||||
_propertiesChanged(info) {
|
||||
_propertiesChanged: function(info) {
|
||||
this._icon.gicon = info.icon;
|
||||
this._label.text = info.name;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const SECTIONS = [
|
||||
'special',
|
||||
'devices',
|
||||
'bookmarks',
|
||||
'network',
|
||||
];
|
||||
'network'
|
||||
]
|
||||
|
||||
let PlacesMenu = GObject.registerClass(
|
||||
class PlacesMenu extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(0.0, _('Places'));
|
||||
const PlacesMenu = new Lang.Class({
|
||||
Name: 'PlacesMenu.PlacesMenu',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
let label = new St.Label({
|
||||
text: _('Places'),
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.add_actor(label);
|
||||
_init: function() {
|
||||
this.parent(0.0, _("Places"));
|
||||
|
||||
let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
let label = new St.Label({ text: _("Places"),
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
hbox.add_child(label);
|
||||
hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
|
||||
this.actor.add_actor(hbox);
|
||||
|
||||
this.placesManager = new PlaceDisplay.PlacesManager();
|
||||
|
||||
this._sections = { };
|
||||
this._sections = { };
|
||||
|
||||
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);
|
||||
});
|
||||
for (let i=0; i < SECTIONS.length; i++) {
|
||||
let id = SECTIONS[i];
|
||||
this._sections[id] = new PopupMenu.PopupMenuSection();
|
||||
this.placesManager.connect(id + '-updated', Lang.bind(this, function() {
|
||||
this._redisplay(id);
|
||||
}));
|
||||
|
||||
this._create(id);
|
||||
this.menu.addMenuItem(this._sections[id]);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
}
|
||||
}
|
||||
this._create(id);
|
||||
this.menu.addMenuItem(this._sections[id]);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
}
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
this.placesManager.destroy();
|
||||
destroy: function() {
|
||||
this.placesManager.destroy();
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_redisplay(id) {
|
||||
this._sections[id].removeAll();
|
||||
_redisplay: function(id) {
|
||||
this._sections[id].removeAll();
|
||||
this._create(id);
|
||||
}
|
||||
},
|
||||
|
||||
_create(id) {
|
||||
_create: function(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].actor.visible = places.length > 0;
|
||||
this._sections[id].actor.visible = places.length > 0;
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
let _indicator;
|
||||
|
||||
function enable() {
|
||||
_indicator = new PlacesMenu();
|
||||
_indicator = new PlacesMenu;
|
||||
|
||||
let pos = Main.sessionMode.panel.left.indexOf('appMenu');
|
||||
let pos = 1;
|
||||
if ('apps-menu' in Main.panel.statusArea)
|
||||
pos++;
|
||||
pos = 2;
|
||||
Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
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 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 Bugzilla instead.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const { Gio, GLib, Shell } = imports.gi;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
const Params = imports.misc.params;
|
||||
const Search = imports.ui.search;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = x => x;
|
||||
const N_ = function(x) { return x; }
|
||||
|
||||
const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
|
||||
|
||||
@@ -19,247 +27,154 @@ const Hostname1Iface = '<node> \
|
||||
</node>';
|
||||
const Hostname1 = Gio.DBusProxy.makeProxyWrapper(Hostname1Iface);
|
||||
|
||||
class PlaceInfo {
|
||||
constructor(...params) {
|
||||
this._init(...params);
|
||||
}
|
||||
const PlaceInfo = new Lang.Class({
|
||||
Name: 'PlaceInfo',
|
||||
|
||||
_init(kind, file, name, icon) {
|
||||
_init: function(kind, file, name, icon) {
|
||||
this.kind = kind;
|
||||
this.file = file;
|
||||
this.name = name || this._getFileName();
|
||||
this.icon = icon ? new Gio.ThemedIcon({ name: icon }) : this.getIcon();
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
}
|
||||
destroy: function() {
|
||||
},
|
||||
|
||||
isRemovable() {
|
||||
isRemovable: function() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async _ensureMountAndLaunch(context, tryMount) {
|
||||
try {
|
||||
await this._launchDefaultForUri(this.file.get_uri(), context, null);
|
||||
} catch (err) {
|
||||
if (!err.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) {
|
||||
Main.notifyError(_('Failed to launch “%s”').format(this.name), err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
let source = {
|
||||
get_icon: () => this.icon,
|
||||
};
|
||||
let op = new ShellMountOperation.ShellMountOperation(source);
|
||||
try {
|
||||
await this._mountEnclosingVolume(0, op.mountOp, null);
|
||||
|
||||
if (tryMount)
|
||||
this._ensureMountAndLaunch(context, false);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED))
|
||||
Main.notifyError(_('Failed to mount volume for “%s”').format(this.name), e.message);
|
||||
} finally {
|
||||
op.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch(timestamp) {
|
||||
launch: function(timestamp) {
|
||||
let launchContext = global.create_app_launch_context(timestamp, -1);
|
||||
this._ensureMountAndLaunch(launchContext, true);
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
this.file.query_info_async('standard::symbolic-icon',
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
0,
|
||||
null,
|
||||
(file, result) => {
|
||||
try {
|
||||
let info = file.query_info_finish(result);
|
||||
this.icon = info.get_symbolic_icon();
|
||||
this.emit('changed');
|
||||
} catch (e) {
|
||||
if (e instanceof Gio.IOErrorEnum)
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
Gio.AppInfo.launch_default_for_uri(this.file.get_uri(),
|
||||
launchContext);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) {
|
||||
this.file.mount_enclosing_volume(0, null, null, function(file, result) {
|
||||
file.mount_enclosing_volume_finish(result);
|
||||
Gio.AppInfo.launch_default_for_uri(file.get_uri(), launchContext);
|
||||
});
|
||||
|
||||
// return a generic icon for this kind for now, until we have the
|
||||
// icon from the query info above
|
||||
switch (this.kind) {
|
||||
case 'network':
|
||||
return new Gio.ThemedIcon({ name: 'folder-remote-symbolic' });
|
||||
case 'devices':
|
||||
return new Gio.ThemedIcon({ name: 'drive-harddisk-symbolic' });
|
||||
case 'special':
|
||||
case 'bookmarks':
|
||||
default:
|
||||
if (!this.file.is_native())
|
||||
return new Gio.ThemedIcon({ name: 'folder-remote-symbolic' });
|
||||
else
|
||||
return new Gio.ThemedIcon({ name: 'folder-symbolic' });
|
||||
} catch(e) {
|
||||
Main.notifyError(_("Failed to launch \"%s\"").format(this.name), e.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getFileName() {
|
||||
getIcon: function() {
|
||||
try {
|
||||
let info = this.file.query_info('standard::symbolic-icon', 0, null);
|
||||
return info.get_symbolic_icon();
|
||||
} catch(e if e instanceof Gio.IOErrorEnum) {
|
||||
// return a generic icon for this kind
|
||||
switch (this.kind) {
|
||||
case 'network':
|
||||
return new Gio.ThemedIcon({ name: 'folder-remote-symbolic' });
|
||||
case 'devices':
|
||||
return new Gio.ThemedIcon({ name: 'drive-harddisk-symbolic' });
|
||||
case 'special':
|
||||
case 'bookmarks':
|
||||
default:
|
||||
if (!this.file.is_native())
|
||||
return new Gio.ThemedIcon({ name: 'folder-remote-symbolic' });
|
||||
else
|
||||
return new Gio.ThemedIcon({ name: 'folder-symbolic' });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getFileName: function() {
|
||||
try {
|
||||
let info = this.file.query_info('standard::display-name', 0, null);
|
||||
return info.get_display_name();
|
||||
} catch (e) {
|
||||
if (e instanceof Gio.IOErrorEnum)
|
||||
return this.file.get_basename();
|
||||
throw e;
|
||||
} catch(e if e instanceof Gio.IOErrorEnum) {
|
||||
return this.file.get_basename();
|
||||
}
|
||||
}
|
||||
|
||||
_launchDefaultForUri(uri, context, cancel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Gio.AppInfo.launch_default_for_uri_async(uri, context, cancel, (o, res) => {
|
||||
try {
|
||||
Gio.AppInfo.launch_default_for_uri_finish(res);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_mountEnclosingVolume(flags, mountOp, cancel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.file.mount_enclosing_volume(flags, mountOp, cancel, (o, res) => {
|
||||
try {
|
||||
this.file.mount_enclosing_volume_finish(res);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(PlaceInfo.prototype);
|
||||
|
||||
class RootInfo extends PlaceInfo {
|
||||
_init() {
|
||||
super._init('devices', Gio.File.new_for_path('/'), _('Computer'));
|
||||
const RootInfo = new Lang.Class({
|
||||
Name: 'RootInfo',
|
||||
Extends: PlaceInfo,
|
||||
|
||||
let busName = 'org.freedesktop.hostname1';
|
||||
let objPath = '/org/freedesktop/hostname1';
|
||||
new Hostname1(Gio.DBus.system, busName, objPath, (obj, error) => {
|
||||
if (error)
|
||||
return;
|
||||
_init: function() {
|
||||
this.parent('devices', Gio.File.new_for_path('/'), _("Computer"));
|
||||
|
||||
this._proxy = obj;
|
||||
this._proxy.connect('g-properties-changed',
|
||||
this._propertiesChanged.bind(this));
|
||||
this._propertiesChanged(obj);
|
||||
});
|
||||
}
|
||||
this._proxy = new Hostname1(Gio.DBus.system,
|
||||
'org.freedesktop.hostname1',
|
||||
'/org/freedesktop/hostname1',
|
||||
Lang.bind(this, function(obj, error) {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
getIcon() {
|
||||
this._proxy.connect('g-properties-changed',
|
||||
Lang.bind(this, this._propertiesChanged));
|
||||
this._propertiesChanged(obj);
|
||||
}));
|
||||
},
|
||||
|
||||
getIcon: function() {
|
||||
return new Gio.ThemedIcon({ name: 'drive-harddisk-symbolic' });
|
||||
}
|
||||
},
|
||||
|
||||
_propertiesChanged(proxy) {
|
||||
_propertiesChanged: function(proxy) {
|
||||
// GDBusProxy will emit a g-properties-changed when hostname1 goes down
|
||||
// ignore it
|
||||
if (proxy.g_name_owner) {
|
||||
this.name = proxy.PrettyHostname || _('Computer');
|
||||
this.name = proxy.PrettyHostname || _("Computer");
|
||||
this.emit('changed');
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._proxy.run_dispose();
|
||||
this.parent();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._proxy) {
|
||||
this._proxy.run_dispose();
|
||||
this._proxy = null;
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
class PlaceDeviceInfo extends PlaceInfo {
|
||||
_init(kind, mount) {
|
||||
const PlaceDeviceInfo = new Lang.Class({
|
||||
Name: 'PlaceDeviceInfo',
|
||||
Extends: PlaceInfo,
|
||||
|
||||
_init: function(kind, mount) {
|
||||
this._mount = mount;
|
||||
super._init(kind, mount.get_root(), mount.get_name());
|
||||
}
|
||||
this.parent(kind, mount.get_root(), mount.get_name());
|
||||
},
|
||||
|
||||
getIcon() {
|
||||
getIcon: function() {
|
||||
return this._mount.get_symbolic_icon();
|
||||
}
|
||||
});
|
||||
|
||||
isRemovable() {
|
||||
return this._mount.can_eject() || this._mount.can_unmount();
|
||||
}
|
||||
const PlaceVolumeInfo = new Lang.Class({
|
||||
Name: 'PlaceVolumeInfo',
|
||||
Extends: PlaceInfo,
|
||||
|
||||
eject() {
|
||||
let unmountArgs = [
|
||||
Gio.MountUnmountFlags.NONE,
|
||||
new ShellMountOperation.ShellMountOperation(this._mount).mountOp,
|
||||
null, // Gio.Cancellable
|
||||
];
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
_ejectFinish(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch (e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
_unmountFinish(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch (e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
_reportFailure(exception) {
|
||||
let msg = _('Ejecting drive “%s” failed:').format(this._mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceVolumeInfo extends PlaceInfo {
|
||||
_init(kind, volume) {
|
||||
_init: function(kind, volume) {
|
||||
this._volume = volume;
|
||||
super._init(kind, volume.get_activation_root(), volume.get_name());
|
||||
}
|
||||
this.parent(kind, volume.get_activation_root(), volume.get_name());
|
||||
},
|
||||
|
||||
launch(timestamp) {
|
||||
launch: function(timestamp) {
|
||||
if (this.file) {
|
||||
super.launch(timestamp);
|
||||
this.parent(timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
this._volume.mount(0, null, null, (volume, result) => {
|
||||
this._volume.mount(0, null, null, Lang.bind(this, function(volume, result) {
|
||||
volume.mount_finish(result);
|
||||
|
||||
let mount = volume.get_mount();
|
||||
this.file = mount.get_root();
|
||||
super.launch(timestamp);
|
||||
});
|
||||
}
|
||||
this.parent(timestamp);
|
||||
}));
|
||||
},
|
||||
|
||||
getIcon() {
|
||||
getIcon: function() {
|
||||
return this._volume.get_symbolic_icon();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const DEFAULT_DIRECTORIES = [
|
||||
GLib.UserDirectory.DIRECTORY_DOCUMENTS,
|
||||
@@ -269,8 +184,10 @@ const DEFAULT_DIRECTORIES = [
|
||||
GLib.UserDirectory.DIRECTORY_VIDEOS,
|
||||
];
|
||||
|
||||
var PlacesManager = class {
|
||||
constructor() {
|
||||
const PlacesManager = new Lang.Class({
|
||||
Name: 'PlacesManager',
|
||||
|
||||
_init: function() {
|
||||
this._places = {
|
||||
special: [],
|
||||
devices: [],
|
||||
@@ -279,8 +196,9 @@ var PlacesManager = class {
|
||||
};
|
||||
|
||||
this._settings = new Gio.Settings({ schema_id: BACKGROUND_SCHEMA });
|
||||
this._showDesktopIconsChangedId = this._settings.connect(
|
||||
'changed::show-desktop-icons', this._updateSpecials.bind(this));
|
||||
this._showDesktopIconsChangedId =
|
||||
this._settings.connect('changed::show-desktop-icons',
|
||||
Lang.bind(this, this._updateSpecials));
|
||||
this._updateSpecials();
|
||||
|
||||
/*
|
||||
@@ -290,50 +208,41 @@ var PlacesManager = class {
|
||||
this._connectVolumeMonitorSignals();
|
||||
this._updateMounts();
|
||||
|
||||
this._bookmarksFile = this._findBookmarksFile();
|
||||
this._bookmarksFile = this._findBookmarksFile()
|
||||
this._bookmarkTimeoutId = 0;
|
||||
this._monitor = null;
|
||||
|
||||
if (this._bookmarksFile) {
|
||||
this._monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
|
||||
this._monitor.connect('changed', () => {
|
||||
this._monitor.connect('changed', Lang.bind(this, function () {
|
||||
if (this._bookmarkTimeoutId > 0)
|
||||
return;
|
||||
/* Defensive event compression */
|
||||
this._bookmarkTimeoutId = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT, 100, () => {
|
||||
this._bookmarkTimeoutId = 0;
|
||||
this._reloadBookmarks();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
|
||||
this._bookmarkTimeoutId = 0;
|
||||
this._reloadBookmarks();
|
||||
return false;
|
||||
}));
|
||||
}));
|
||||
|
||||
this._reloadBookmarks();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_connectVolumeMonitorSignals() {
|
||||
const signals = [
|
||||
'volume-added',
|
||||
'volume-removed',
|
||||
'volume-changed',
|
||||
'mount-added',
|
||||
'mount-removed',
|
||||
'mount-changed',
|
||||
'drive-connected',
|
||||
'drive-disconnected',
|
||||
'drive-changed',
|
||||
];
|
||||
_connectVolumeMonitorSignals: function() {
|
||||
const signals = ['volume-added', 'volume-removed', 'volume-changed',
|
||||
'mount-added', 'mount-removed', 'mount-changed',
|
||||
'drive-connected', 'drive-disconnected', 'drive-changed'];
|
||||
|
||||
this._volumeMonitorSignals = [];
|
||||
let func = this._updateMounts.bind(this);
|
||||
let func = Lang.bind(this, this._updateMounts);
|
||||
for (let i = 0; i < signals.length; i++) {
|
||||
let id = this._volumeMonitor.connect(signals[i], func);
|
||||
this._volumeMonitorSignals.push(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
destroy: function() {
|
||||
if (this._settings)
|
||||
this._settings.disconnect(this._showDesktopIconsChangedId);
|
||||
this._settings = null;
|
||||
@@ -344,19 +253,18 @@ var PlacesManager = class {
|
||||
if (this._monitor)
|
||||
this._monitor.cancel();
|
||||
if (this._bookmarkTimeoutId)
|
||||
GLib.source_remove(this._bookmarkTimeoutId);
|
||||
}
|
||||
Mainloop.source_remove(this._bookmarkTimeoutId);
|
||||
},
|
||||
|
||||
_updateSpecials() {
|
||||
this._places.special.forEach(p => p.destroy());
|
||||
_updateSpecials: function() {
|
||||
this._places.special.forEach(function (p) { p.destroy(); });
|
||||
this._places.special = [];
|
||||
|
||||
let homePath = GLib.get_home_dir();
|
||||
|
||||
this._places.special.push(new PlaceInfo(
|
||||
'special',
|
||||
Gio.File.new_for_path(homePath),
|
||||
_('Home')));
|
||||
this._places.special.push(new PlaceInfo('special',
|
||||
Gio.File.new_for_path(homePath),
|
||||
_("Home")));
|
||||
|
||||
let specials = [];
|
||||
let dirs = DEFAULT_DIRECTORIES.slice();
|
||||
@@ -366,56 +274,55 @@ var PlacesManager = class {
|
||||
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
let specialPath = GLib.get_user_special_dir(dirs[i]);
|
||||
if (!specialPath || specialPath === homePath)
|
||||
if (specialPath == null || specialPath == homePath)
|
||||
continue;
|
||||
|
||||
let file = Gio.File.new_for_path(specialPath), info;
|
||||
try {
|
||||
info = new PlaceInfo('special', file);
|
||||
} catch (e) {
|
||||
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
continue;
|
||||
throw e;
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
specials.push(info);
|
||||
}
|
||||
|
||||
specials.sort((a, b) => GLib.utf8_collate(a.name, b.name));
|
||||
specials.sort(function(a, b) {
|
||||
return GLib.utf8_collate(a.name, b.name);
|
||||
});
|
||||
this._places.special = this._places.special.concat(specials);
|
||||
|
||||
this.emit('special-updated');
|
||||
}
|
||||
},
|
||||
|
||||
_updateMounts() {
|
||||
_updateMounts: function() {
|
||||
let networkMounts = [];
|
||||
let networkVolumes = [];
|
||||
|
||||
this._places.devices.forEach(p => p.destroy());
|
||||
this._places.devices.forEach(function (p) { p.destroy(); });
|
||||
this._places.devices = [];
|
||||
this._places.network.forEach(p => p.destroy());
|
||||
this._places.network.forEach(function (p) { p.destroy(); });
|
||||
this._places.network = [];
|
||||
|
||||
/* Add standard places */
|
||||
this._places.devices.push(new RootInfo());
|
||||
this._places.network.push(new PlaceInfo(
|
||||
'network',
|
||||
Gio.File.new_for_uri('network:///'),
|
||||
_('Browse Network'),
|
||||
'network-workgroup-symbolic'));
|
||||
this._places.network.push(new PlaceInfo('network',
|
||||
Gio.File.new_for_uri('network:///'),
|
||||
_("Browse Network"),
|
||||
'network-workgroup-symbolic'));
|
||||
|
||||
/* first go through all connected drives */
|
||||
let drives = this._volumeMonitor.get_connected_drives();
|
||||
for (let i = 0; i < drives.length; i++) {
|
||||
let volumes = drives[i].get_volumes();
|
||||
|
||||
for (let j = 0; j < volumes.length; j++) {
|
||||
for(let j = 0; j < volumes.length; j++) {
|
||||
let identifier = volumes[j].get_identifier('class');
|
||||
if (identifier && identifier.includes('network')) {
|
||||
if (identifier && identifier.indexOf('network') >= 0) {
|
||||
networkVolumes.push(volumes[j]);
|
||||
} else {
|
||||
let mount = volumes[j].get_mount();
|
||||
if (mount)
|
||||
if(mount != null)
|
||||
this._addMount('devices', mount);
|
||||
}
|
||||
}
|
||||
@@ -423,27 +330,27 @@ var PlacesManager = class {
|
||||
|
||||
/* add all volumes that is not associated with a drive */
|
||||
let volumes = this._volumeMonitor.get_volumes();
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
if (volumes[i].get_drive())
|
||||
for(let i = 0; i < volumes.length; i++) {
|
||||
if(volumes[i].get_drive() != null)
|
||||
continue;
|
||||
|
||||
let identifier = volumes[i].get_identifier('class');
|
||||
if (identifier && identifier.includes('network')) {
|
||||
if (identifier && identifier.indexOf('network') >= 0) {
|
||||
networkVolumes.push(volumes[i]);
|
||||
} else {
|
||||
let mount = volumes[i].get_mount();
|
||||
if (mount)
|
||||
if(mount != null)
|
||||
this._addMount('devices', mount);
|
||||
}
|
||||
}
|
||||
|
||||
/* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
|
||||
let mounts = this._volumeMonitor.get_mounts();
|
||||
for (let i = 0; i < mounts.length; i++) {
|
||||
if (mounts[i].is_shadowed())
|
||||
for(let i = 0; i < mounts.length; i++) {
|
||||
if(mounts[i].is_shadowed())
|
||||
continue;
|
||||
|
||||
if (mounts[i].get_volume())
|
||||
if(mounts[i].get_volume())
|
||||
continue;
|
||||
|
||||
let root = mounts[i].get_default_location();
|
||||
@@ -463,15 +370,15 @@ var PlacesManager = class {
|
||||
this._addVolume('network', networkVolumes[i]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < networkMounts.length; i++)
|
||||
for (let i = 0; i < networkMounts.length; i++) {
|
||||
this._addMount('network', networkMounts[i]);
|
||||
|
||||
}
|
||||
|
||||
this.emit('devices-updated');
|
||||
this.emit('network-updated');
|
||||
}
|
||||
},
|
||||
|
||||
_findBookmarksFile() {
|
||||
_findBookmarksFile: function() {
|
||||
let paths = [
|
||||
GLib.build_filenamev([GLib.get_user_config_dir(), 'gtk-3.0', 'bookmarks']),
|
||||
GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']),
|
||||
@@ -483,9 +390,10 @@ var PlacesManager = class {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
_reloadBookmarks: function() {
|
||||
|
||||
_reloadBookmarks() {
|
||||
this._bookmarks = [];
|
||||
|
||||
let content = Shell.get_file_contents_utf8_sync(this._bookmarksFile.get_path());
|
||||
@@ -495,7 +403,7 @@ var PlacesManager = class {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
let components = line.split(' ');
|
||||
let [bookmark] = components;
|
||||
let bookmark = components[0];
|
||||
|
||||
if (!bookmark)
|
||||
continue;
|
||||
@@ -505,16 +413,16 @@ var PlacesManager = class {
|
||||
continue;
|
||||
|
||||
let duplicate = false;
|
||||
for (let j = 0; j < this._places.special.length; j++) {
|
||||
if (file.equal(this._places.special[j].file)) {
|
||||
for (let i = 0; i < this._places.special.length; i++) {
|
||||
if (file.equal(this._places.special[i].file)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (duplicate)
|
||||
continue;
|
||||
for (let j = 0; j < bookmarks.length; j++) {
|
||||
if (file.equal(bookmarks[j].file)) {
|
||||
for (let i = 0; i < bookmarks.length; i++) {
|
||||
if (file.equal(bookmarks[i].file)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
@@ -532,38 +440,34 @@ var PlacesManager = class {
|
||||
this._places.bookmarks = bookmarks;
|
||||
|
||||
this.emit('bookmarks-updated');
|
||||
}
|
||||
},
|
||||
|
||||
_addMount(kind, mount) {
|
||||
_addMount: function(kind, mount) {
|
||||
let devItem;
|
||||
|
||||
try {
|
||||
devItem = new PlaceDeviceInfo(kind, mount);
|
||||
} catch (e) {
|
||||
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
return;
|
||||
throw e;
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._places[kind].push(devItem);
|
||||
}
|
||||
},
|
||||
|
||||
_addVolume(kind, volume) {
|
||||
_addVolume: function(kind, volume) {
|
||||
let volItem;
|
||||
|
||||
try {
|
||||
volItem = new PlaceVolumeInfo(kind, volume);
|
||||
} catch (e) {
|
||||
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
return;
|
||||
throw e;
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._places[kind].push(volItem);
|
||||
}
|
||||
},
|
||||
|
||||
get(kind) {
|
||||
get: function (kind) {
|
||||
return this._places[kind];
|
||||
}
|
||||
};
|
||||
});
|
||||
Signals.addSignalMethods(PlacesManager.prototype);
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
.place-menu-item .button {
|
||||
border-radius: 99px;
|
||||
padding: 3px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.place-menu-item .button:ltr { margin-left: 6px; }
|
||||
.place-menu-item .button:rtl { margin-right: 6px; }
|
||||
/* none used*/
|
||||
|
||||
4
extensions/screenshot-window-sizer/Makefile.am
Normal file
4
extensions/screenshot-window-sizer/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
EXTENSION_ID = screenshot-window-sizer
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
@@ -1,4 +1,3 @@
|
||||
/* exported enable disable */
|
||||
/* Screenshot Window Sizer for Gnome Shell
|
||||
*
|
||||
* Copyright (c) 2013 Owen Taylor <otaylor@redhat.com>
|
||||
@@ -19,14 +18,21 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
const { Clutter, Meta, Shell, St } = imports.gi;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Main = imports.ui.main;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const MESSAGE_FADE_TIME = 2000;
|
||||
const MESSAGE_FADE_TIME = 2;
|
||||
|
||||
let text;
|
||||
let text, button;
|
||||
|
||||
function hideMessage() {
|
||||
text.destroy();
|
||||
@@ -39,22 +45,20 @@ function flashMessage(message) {
|
||||
Main.uiGroup.add_actor(text);
|
||||
}
|
||||
|
||||
text.remove_all_transitions();
|
||||
Tweener.removeTweens(text);
|
||||
text.text = message;
|
||||
|
||||
text.opacity = 255;
|
||||
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
text.set_position(
|
||||
monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
|
||||
monitor.y + Math.floor(monitor.height / 2 - text.height / 2));
|
||||
text.set_position(monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
|
||||
monitor.y + Math.floor(monitor.height / 2 - text.height / 2));
|
||||
|
||||
text.ease({
|
||||
opacity: 0,
|
||||
duration: MESSAGE_FADE_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: hideMessage,
|
||||
});
|
||||
Tweener.addTween(text,
|
||||
{ opacity: 0,
|
||||
time: MESSAGE_FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: hideMessage });
|
||||
}
|
||||
|
||||
let SIZES = [
|
||||
@@ -62,28 +66,29 @@ let SIZES = [
|
||||
[800, 450],
|
||||
[1024, 576],
|
||||
[1200, 675],
|
||||
[1600, 900],
|
||||
[360, 654], // Phone portrait maximized
|
||||
[720, 360], // Phone landscape fullscreen
|
||||
[1600, 900]
|
||||
];
|
||||
|
||||
function cycleScreenshotSizes(display, window, binding) {
|
||||
function cycleScreenshotSizes(display, screen, window, binding) {
|
||||
// Probably this isn't useful with 5 sizes, but you can decrease instead
|
||||
// of increase by holding down shift.
|
||||
let modifiers = binding.get_modifiers();
|
||||
let backwards = (modifiers & Meta.VirtualModifier.SHIFT_MASK) !== 0;
|
||||
let backwards = (modifiers & Meta.VirtualModifier.SHIFT_MASK) != 0;
|
||||
|
||||
// Unmaximize first
|
||||
if (window.get_maximized() !== 0)
|
||||
window.unmaximize(Meta.MaximizeFlags.BOTH);
|
||||
if (window.maximized_horizontally || window.maximizedVertically)
|
||||
window.unmaximize(Meta.MaximizeFlags.HORIZONTAL | Meta.MaximizeFlags.VERTICAL);
|
||||
|
||||
let workArea = window.get_work_area_current_monitor();
|
||||
let outerRect = window.get_frame_rect();
|
||||
|
||||
// Double both axes if on a hidpi display
|
||||
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
let scaledSizes = SIZES.map(size => size.map(wh => wh * scaleFactor))
|
||||
.filter(([w, h]) => w <= workArea.width && h <= workArea.height);
|
||||
let scaledSizes = SIZES.map(function(size) {
|
||||
return size.map(function(wh) {
|
||||
return wh * scaleFactor;
|
||||
});
|
||||
});
|
||||
|
||||
// Find the nearest 16:9 size for the current window size
|
||||
let nearestIndex;
|
||||
@@ -98,7 +103,7 @@ function cycleScreenshotSizes(display, window, binding) {
|
||||
|
||||
// get the best initial window size
|
||||
let error = Math.abs(width - outerRect.width) + Math.abs(height - outerRect.height);
|
||||
if (nearestIndex === undefined || error < nearestError) {
|
||||
if (nearestIndex == null || error < nearestError) {
|
||||
nearestIndex = i;
|
||||
nearestError = error;
|
||||
}
|
||||
@@ -106,7 +111,10 @@ function cycleScreenshotSizes(display, window, binding) {
|
||||
|
||||
// get the next size up or down from ideal
|
||||
let newIndex = (nearestIndex + (backwards ? -1 : 1)) % scaledSizes.length;
|
||||
let [newWidth, newHeight] = scaledSizes[newIndex];
|
||||
let newWidth, newHeight;
|
||||
[newWidth, newHeight] = scaledSizes[newIndex];
|
||||
if (newWidth > workArea.width || newHeight > workArea.height)
|
||||
[newWidth, newHeight] = scaledSizes[0];
|
||||
|
||||
// Push the window onscreen if it would be resized offscreen
|
||||
let newX = outerRect.x;
|
||||
@@ -120,35 +128,30 @@ function cycleScreenshotSizes(display, window, binding) {
|
||||
|
||||
let newOuterRect = window.get_frame_rect();
|
||||
let message = '%d×%d'.format(
|
||||
newOuterRect.width / scaleFactor,
|
||||
newOuterRect.height / scaleFactor);
|
||||
(newOuterRect.width / scaleFactor),
|
||||
(newOuterRect.height / scaleFactor));
|
||||
|
||||
// The new size might have been constrained by geometry hints (e.g. for
|
||||
// a terminal) - in that case, include the actual ratio to the message
|
||||
// we flash
|
||||
let actualNumerator = 9 * newOuterRect.width / newOuterRect.height;
|
||||
let actualNumerator = (newOuterRect.width / newOuterRect.height) * 9;
|
||||
if (Math.abs(actualNumerator - 16) > 0.01)
|
||||
message += ' (%.2f:9)'.format(actualNumerator);
|
||||
|
||||
flashMessage(message);
|
||||
}
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
function enable() {
|
||||
Main.wm.addKeybinding(
|
||||
'cycle-screenshot-sizes',
|
||||
ExtensionUtils.getSettings(),
|
||||
Meta.KeyBindingFlags.PER_WINDOW,
|
||||
Shell.ActionMode.NORMAL,
|
||||
cycleScreenshotSizes);
|
||||
Main.wm.addKeybinding(
|
||||
'cycle-screenshot-sizes-backward',
|
||||
ExtensionUtils.getSettings(),
|
||||
Meta.KeyBindingFlags.PER_WINDOW | Meta.KeyBindingFlags.IS_REVERSED,
|
||||
Shell.ActionMode.NORMAL,
|
||||
cycleScreenshotSizes);
|
||||
Main.wm.addKeybinding('cycle-screenshot-sizes',
|
||||
Convenience.getSettings(),
|
||||
Meta.KeyBindingFlags.PER_WINDOW | Meta.KeyBindingFlags.REVERSES,
|
||||
Shell.ActionMode.NORMAL,
|
||||
cycleScreenshotSizes);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
Main.wm.removeKeybinding('cycle-screenshot-sizes');
|
||||
Main.wm.removeKeybinding('cycle-screenshot-sizes-backward');
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
@@ -1,14 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell.extensions.screenshot-window-sizer"
|
||||
path="/org/gnome/shell/extensions/screenshot-window-sizer/">
|
||||
<key type="as" name="cycle-screenshot-sizes">
|
||||
<default><![CDATA[['<Alt><Control>s']]]></default>
|
||||
<summary>Cycle Screenshot Sizes</summary>
|
||||
</key>
|
||||
<key type="as" name="cycle-screenshot-sizes-backward">
|
||||
<default><![CDATA[['<Shift><Alt><Control>s']]]></default>
|
||||
<summary>Cycle Screenshot Sizes Backward</summary>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
|
||||
5
extensions/user-theme/Makefile.am
Normal file
5
extensions/user-theme/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = user-theme
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Load shell theme from ~/.local/share/themes/name/gnome-shell
|
||||
/* exported init */
|
||||
// Load shell theme from ~/.themes/name/gnome-shell
|
||||
|
||||
const { Gio } = imports.gi;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Util = Me.imports.util;
|
||||
|
||||
const SETTINGS_KEY = 'name';
|
||||
|
||||
class ThemeManager {
|
||||
constructor() {
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
}
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
enable() {
|
||||
this._changedId = this._settings.connect(`changed::${SETTINGS_KEY}`, this._changeTheme.bind(this));
|
||||
const ThemeManager = new Lang.Class({
|
||||
Name: 'UserTheme.ThemeManager',
|
||||
|
||||
_init: function() {
|
||||
this._settings = Convenience.getSettings();
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._changedId = this._settings.connect('changed::'+SETTINGS_KEY, Lang.bind(this, this._changeTheme));
|
||||
this._changeTheme();
|
||||
}
|
||||
},
|
||||
|
||||
disable() {
|
||||
disable: function() {
|
||||
if (this._changedId) {
|
||||
this._settings.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
@@ -30,33 +32,39 @@ class ThemeManager {
|
||||
|
||||
Main.setThemeStylesheet(null);
|
||||
Main.loadTheme();
|
||||
}
|
||||
},
|
||||
|
||||
_changeTheme() {
|
||||
let stylesheet = null;
|
||||
let themeName = this._settings.get_string(SETTINGS_KEY);
|
||||
_changeTheme: function() {
|
||||
let _stylesheet = null;
|
||||
let _themeName = this._settings.get_string(SETTINGS_KEY);
|
||||
|
||||
if (themeName) {
|
||||
const stylesheetPaths = Util.getThemeDirs()
|
||||
.map(dir => `${dir}/${themeName}/gnome-shell/gnome-shell.css`);
|
||||
|
||||
stylesheetPaths.push(...Util.getModeThemeDirs()
|
||||
.map(dir => `${dir}/${themeName}.css`));
|
||||
|
||||
stylesheet = stylesheetPaths.find(path => {
|
||||
let file = Gio.file_new_for_path(path);
|
||||
return file.query_exists(null);
|
||||
});
|
||||
if (_themeName) {
|
||||
let _userCssStylesheet = GLib.get_home_dir() + '/.themes/' + _themeName + '/gnome-shell/gnome-shell.css';
|
||||
let file = Gio.file_new_for_path(_userCssStylesheet);
|
||||
if (file.query_exists(null))
|
||||
_stylesheet = _userCssStylesheet;
|
||||
else {
|
||||
let sysdirs = GLib.get_system_data_dirs();
|
||||
sysdirs.unshift(GLib.get_user_data_dir());
|
||||
for (let i = 0; i < sysdirs.length; i++) {
|
||||
_userCssStylesheet = sysdirs[i] + '/themes/' + _themeName + '/gnome-shell/gnome-shell.css';
|
||||
let file = Gio.file_new_for_path(_userCssStylesheet);
|
||||
if (file.query_exists(null)) {
|
||||
_stylesheet = _userCssStylesheet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stylesheet)
|
||||
global.log(`loading user theme: ${stylesheet}`);
|
||||
if (_stylesheet)
|
||||
global.log('loading user theme: ' + _stylesheet);
|
||||
else
|
||||
global.log('loading default theme (Adwaita)');
|
||||
Main.setThemeStylesheet(stylesheet);
|
||||
Main.setThemeStylesheet(_stylesheet);
|
||||
Main.loadTheme();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
return new ThemeManager();
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
extension_sources += files('prefs.js', 'util.js')
|
||||
extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
@@ -1,182 +0,0 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
/* exported init buildPrefsWidget */
|
||||
|
||||
// we use async/await here to not block the mainloop, not to parallelize
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
const { Gio, GLib, GObject, Gtk } = imports.gi;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Util = Me.imports.util;
|
||||
|
||||
Gio._promisify(Gio._LocalFilePrototype,
|
||||
'enumerate_children_async', 'enumerate_children_finish');
|
||||
Gio._promisify(Gio._LocalFilePrototype,
|
||||
'query_info_async', 'query_info_finish');
|
||||
Gio._promisify(Gio.FileEnumerator.prototype,
|
||||
'next_files_async', 'next_files_finish');
|
||||
|
||||
const UserThemePrefsWidget = GObject.registerClass(
|
||||
class UserThemePrefsWidget extends Gtk.ScrolledWindow {
|
||||
_init() {
|
||||
super._init({
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
});
|
||||
|
||||
const box = new Gtk.Box();
|
||||
this.set_child(box);
|
||||
|
||||
this._list = new Gtk.ListBox({
|
||||
selection_mode: Gtk.SelectionMode.NONE,
|
||||
show_separators: true,
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.START,
|
||||
hexpand: true,
|
||||
margin_start: 60,
|
||||
margin_end: 60,
|
||||
margin_top: 60,
|
||||
margin_bottom: 60,
|
||||
});
|
||||
this._list.get_style_context().add_class('frame');
|
||||
box.append(this._list);
|
||||
|
||||
this._actionGroup = new Gio.SimpleActionGroup();
|
||||
this._list.insert_action_group('theme', this._actionGroup);
|
||||
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
this._actionGroup.add_action(
|
||||
this._settings.create_action('name'));
|
||||
|
||||
this.connect('destroy', () => this._settings.run_dispose());
|
||||
|
||||
this._rows = new Map();
|
||||
this._addTheme(''); // default
|
||||
|
||||
this._collectThemes();
|
||||
}
|
||||
|
||||
async _collectThemes() {
|
||||
for (const dirName of Util.getThemeDirs()) {
|
||||
const dir = Gio.File.new_for_path(dirName);
|
||||
for (const name of await this._enumerateDir(dir)) {
|
||||
if (this._rows.has(name))
|
||||
continue;
|
||||
|
||||
const file = dir.resolve_relative_path(
|
||||
`${name}/gnome-shell/gnome-shell.css`);
|
||||
try {
|
||||
await file.query_info_async(
|
||||
Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT, null);
|
||||
this._addTheme(name);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dirName of Util.getModeThemeDirs()) {
|
||||
const dir = Gio.File.new_for_path(dirName);
|
||||
for (const filename of await this._enumerateDir(dir)) {
|
||||
if (!filename.endsWith('.css'))
|
||||
continue;
|
||||
|
||||
const name = filename.slice(0, -4);
|
||||
if (!this._rows.has(name))
|
||||
this._addTheme(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addTheme(name) {
|
||||
const row = new ThemeRow(name, this._settings);
|
||||
this._rows.set(name, row);
|
||||
|
||||
this._list.append(row);
|
||||
}
|
||||
|
||||
async _enumerateDir(dir) {
|
||||
const fileInfos = [];
|
||||
let fileEnum;
|
||||
|
||||
try {
|
||||
fileEnum = await dir.enumerate_children_async(
|
||||
Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT, null);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
logError(e);
|
||||
return [];
|
||||
}
|
||||
|
||||
let infos;
|
||||
do {
|
||||
infos = await fileEnum.next_files_async(100,
|
||||
GLib.PRIORITY_DEFAULT, null);
|
||||
fileInfos.push(...infos);
|
||||
} while (infos.length > 0);
|
||||
|
||||
return fileInfos.map(info => info.get_name());
|
||||
}
|
||||
});
|
||||
|
||||
const ThemeRow = GObject.registerClass(
|
||||
class ThemeRow extends Gtk.ListBoxRow {
|
||||
_init(name, settings) {
|
||||
this._name = name;
|
||||
this._settings = settings;
|
||||
|
||||
const box = new Gtk.Box({
|
||||
spacing: 12,
|
||||
margin_start: 12,
|
||||
margin_end: 12,
|
||||
margin_top: 12,
|
||||
margin_bottom: 12,
|
||||
});
|
||||
super._init({
|
||||
action_name: 'theme.name',
|
||||
action_target: new GLib.Variant('s', name),
|
||||
child: box,
|
||||
});
|
||||
|
||||
box.append(new Gtk.Label({
|
||||
label: name || 'Default',
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
max_width_chars: 25,
|
||||
width_chars: 25,
|
||||
}));
|
||||
|
||||
this._checkmark = new Gtk.Image({
|
||||
icon_name: 'emblem-ok-symbolic',
|
||||
pixel_size: 16,
|
||||
});
|
||||
box.append(this._checkmark);
|
||||
|
||||
const id = this._settings.connect('changed::name',
|
||||
this._syncCheckmark.bind(this));
|
||||
this._syncCheckmark();
|
||||
|
||||
this.connect('destroy', () => {
|
||||
this._settings.disconnect(id);
|
||||
this._settings = null;
|
||||
});
|
||||
}
|
||||
|
||||
_syncCheckmark() {
|
||||
const visible = this._name === this._settings.get_string('name');
|
||||
this._checkmark.opacity = visible ? 1. : 0;
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
return new UserThemePrefsWidget();
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/* exported getThemeDirs getModeThemeDirs */
|
||||
const { GLib } = imports.gi;
|
||||
|
||||
const fn = (...args) => GLib.build_filenamev(args);
|
||||
|
||||
function getThemeDirs() {
|
||||
return [
|
||||
fn(GLib.get_home_dir(), '.themes'),
|
||||
fn(GLib.get_user_data_dir(), 'themes'),
|
||||
...GLib.get_system_data_dirs().map(dir => fn(dir, 'themes')),
|
||||
];
|
||||
}
|
||||
|
||||
function getModeThemeDirs() {
|
||||
return GLib.get_system_data_dirs()
|
||||
.map(dir => fn(dir, 'gnome-shell', 'theme'));
|
||||
}
|
||||
10
extensions/window-list/Makefile.am
Normal file
10
extensions/window-list/Makefile.am
Normal file
@@ -0,0 +1,10 @@
|
||||
EXTENSION_ID = window-list
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
if CLASSIC_MODE
|
||||
EXTRA_MODULES += classic.css
|
||||
endif
|
||||
|
||||
include ../../extension.mk
|
||||
include ../../settings.mk
|
||||
@@ -4,39 +4,36 @@
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 0px;
|
||||
height: 2.25em ;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.bottom-panel .window-button > StWidget,
|
||||
.bottom-panel .window-picker-toggle > StWidget {
|
||||
color: #2e3436;
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
padding: 3px 6px 1px;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
border: 1px solid rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.bottom-panel .window-button > StWidget {
|
||||
background-gradient-drection: vertical;
|
||||
background-color: #fff;
|
||||
background-gradient-start: #fff;
|
||||
background-gradient-end: #eee;
|
||||
color: #000;
|
||||
-st-natural-width: 18.7em;
|
||||
max-width: 18.75em;
|
||||
color: #2e3436;
|
||||
background-color: #eee;
|
||||
border-radius: 2px;
|
||||
padding: 3px 6px 1px;
|
||||
box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5);
|
||||
text-shadow: 0 0 transparent;
|
||||
}
|
||||
|
||||
.bottom-panel .window-button:hover > StWidget,
|
||||
.bottom-panel .window-picker-toggle:hover > StWidget {
|
||||
.bottom-panel .window-button:hover > StWidget {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.bottom-panel .window-button:active > StWidget,
|
||||
.bottom-panel .window-button:focus > StWidget {
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
||||
box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.bottom-panel .window-button.focused > StWidget,
|
||||
.bottom-panel .window-picker-toggle:checked > StWidget {
|
||||
background-color: #ccc;
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
||||
.bottom-panel .window-button.focused > StWidget {
|
||||
background-color: #ddd;
|
||||
box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.bottom-panel .window-button.focused:hover > StWidget {
|
||||
@@ -45,24 +42,9 @@
|
||||
|
||||
.bottom-panel .window-button.minimized > StWidget {
|
||||
color: #888;
|
||||
box-shadow: none;
|
||||
box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* workspace switcher */
|
||||
.window-list-workspace-indicator .workspace {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.window-list-window-preview {
|
||||
background-color: #ededed;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.window-list-window-preview.active {
|
||||
background-color: #f6f5f4;
|
||||
border: 2px solid #888;
|
||||
}
|
||||
.bottom-panel .window-button.minimized > StWidget {
|
||||
box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js')
|
||||
extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
|
||||
|
||||
if classic_mode_enabled
|
||||
extension_data += files('classic.css')
|
||||
endif
|
||||
@@ -4,7 +4,7 @@
|
||||
"settings-schema": "@gschemaname@",
|
||||
"gettext-domain": "@gettext_domain@",
|
||||
"name": "Window List",
|
||||
"description": "Display a window list at the bottom of the screen.\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": "Display a window list at the bottom of the screen.\nThis extension is part of Classic Mode and is officially supported by GNOME. Please do not report bugs using the form below, use GNOME Bugzilla instead.",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"url": "@url@"
|
||||
}
|
||||
|
||||
@@ -12,14 +12,7 @@
|
||||
<summary>When to group windows</summary>
|
||||
<description>
|
||||
Decides when to group windows from the same application on the
|
||||
window list. Possible values are “never”, “auto” and “always”.
|
||||
</description>
|
||||
</key>
|
||||
<key name="display-all-workspaces" type="b">
|
||||
<default>false</default>
|
||||
<summary>Show windows from all workspaces</summary>
|
||||
<description>
|
||||
Whether to show windows from all workspaces or only the current one.
|
||||
window list. Possible values are "never", "auto" and "always".
|
||||
</description>
|
||||
</key>
|
||||
<key name="show-on-all-monitors" type="b">
|
||||
|
||||
@@ -1,109 +1,87 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
/* exported init buildPrefsWidget */
|
||||
|
||||
const { Gio, GObject, Gtk } = imports.gi;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
|
||||
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() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
const WindowListPrefsWidget = GObject.registerClass(
|
||||
class WindowListPrefsWidget extends Gtk.Box {
|
||||
_init() {
|
||||
super._init({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 6,
|
||||
margin_top: 36,
|
||||
margin_bottom: 36,
|
||||
margin_start: 36,
|
||||
margin_end: 36,
|
||||
halign: Gtk.Align.CENTER,
|
||||
});
|
||||
const WindowListPrefsWidget = new GObject.Class({
|
||||
Name: 'WindowList.Prefs.Widget',
|
||||
GTypeName: 'WindowListPrefsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
let groupingLabel = '<b>%s</b>'.format(_('Window Grouping'));
|
||||
this.append(new Gtk.Label({
|
||||
label: groupingLabel, use_markup: true,
|
||||
halign: Gtk.Align.START,
|
||||
}));
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
|
||||
const box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 12,
|
||||
margin_bottom: 12,
|
||||
});
|
||||
this.append(box);
|
||||
this.margin = 24;
|
||||
this.row_spacing = 6;
|
||||
this.orientation = Gtk.Orientation.VERTICAL;
|
||||
|
||||
const context = box.get_style_context();
|
||||
const cssProvider = new Gtk.CssProvider();
|
||||
cssProvider.load_from_data(
|
||||
'box { padding: 12px; }');
|
||||
let groupingLabel = '<b>' + _("Window Grouping") + '</b>';
|
||||
this.add(new Gtk.Label({ label: groupingLabel, use_markup: true,
|
||||
halign: Gtk.Align.START }));
|
||||
|
||||
context.add_provider(cssProvider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context.add_class('frame');
|
||||
context.add_class('view');
|
||||
let align = new Gtk.Alignment({ left_padding: 12 });
|
||||
this.add(align);
|
||||
|
||||
this._settings = ExtensionUtils.getSettings();
|
||||
let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
|
||||
row_spacing: 6,
|
||||
column_spacing: 6 });
|
||||
align.add(grid);
|
||||
|
||||
this._settings = Convenience.getSettings();
|
||||
let currentMode = this._settings.get_string('grouping-mode');
|
||||
let range = this._settings.get_range('grouping-mode');
|
||||
let modes = range.deep_unpack()[1].deep_unpack();
|
||||
|
||||
let modeLabels = {
|
||||
'never': _('Never group windows'),
|
||||
'auto': _('Group windows when space is limited'),
|
||||
'always': _('Always group windows'),
|
||||
'never': _("Never group windows"),
|
||||
'auto': _("Group windows when space is limited"),
|
||||
'always': _("Always group windows")
|
||||
};
|
||||
|
||||
let radio = null;
|
||||
let currentRadio = null;
|
||||
for (let i = 0; i < modes.length; i++) {
|
||||
let mode = modes[i];
|
||||
let label = modeLabels[mode];
|
||||
if (!label) {
|
||||
log('Unhandled option "%s" for grouping-mode'.format(mode));
|
||||
continue;
|
||||
log('Unhandled option "%s" for grouping-mode'.format(mode));
|
||||
continue;
|
||||
}
|
||||
|
||||
radio = new Gtk.CheckButton({
|
||||
active: !i,
|
||||
label,
|
||||
group: radio,
|
||||
margin_end: 12,
|
||||
});
|
||||
box.append(radio);
|
||||
radio = new Gtk.RadioButton({ active: currentMode == mode,
|
||||
label: label,
|
||||
group: radio });
|
||||
grid.add(radio);
|
||||
|
||||
if (currentMode === mode)
|
||||
currentRadio = radio;
|
||||
|
||||
radio.connect('toggled', button => {
|
||||
radio.connect('toggled', Lang.bind(this, function(button) {
|
||||
if (button.active)
|
||||
this._settings.set_string('grouping-mode', mode);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (currentRadio)
|
||||
currentRadio.active = true;
|
||||
|
||||
let check = new Gtk.CheckButton({
|
||||
label: _('Show on all monitors'),
|
||||
});
|
||||
let check = new Gtk.CheckButton({ label: _("Show on all monitors"),
|
||||
margin_top: 6 });
|
||||
this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
this.append(check);
|
||||
|
||||
check = new Gtk.CheckButton({
|
||||
label: _('Show windows from all workspaces'),
|
||||
});
|
||||
this._settings.bind('display-all-workspaces', check, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
this.append(check);
|
||||
this.add(check);
|
||||
}
|
||||
});
|
||||
|
||||
function buildPrefsWidget() {
|
||||
return new WindowListPrefsWidget();
|
||||
let widget = new WindowListPrefsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
.bottom-panel {
|
||||
/* .window-button-icon height +
|
||||
.window-button vertical padding +
|
||||
.window-button > StWidget vertical padding) */
|
||||
height: 2.25em;
|
||||
}
|
||||
|
||||
.window-list {
|
||||
spacing: 2px;
|
||||
font-size: 10pt;
|
||||
@@ -19,8 +26,9 @@
|
||||
spacing: 4px;
|
||||
}
|
||||
|
||||
.window-button > StWidget,
|
||||
.window-picker-toggle > StWidget {
|
||||
.window-button > StWidget {
|
||||
-st-natural-width: 18.75em;
|
||||
max-width: 18.75em;
|
||||
color: #bbb;
|
||||
background-color: black;
|
||||
border-radius: 4px;
|
||||
@@ -29,21 +37,7 @@
|
||||
text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
.window-picker-toggle {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.window-picker-toggle > StWidet {
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
.window-button > StWidget {
|
||||
-st-natural-width: 18.75em;
|
||||
max-width: 18.75em;
|
||||
}
|
||||
|
||||
.window-button:hover > StWidget,
|
||||
.window-picker-toggle:hover > StWidget {
|
||||
.window-button:hover > StWidget {
|
||||
color: white;
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
@@ -53,14 +47,12 @@
|
||||
box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.window-button.focused > StWidget,
|
||||
.window-picker-toggle:checked > StWidget {
|
||||
.window-button.focused > StWidget {
|
||||
color: white;
|
||||
box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
.window-button.focused:active > StWidget,
|
||||
.window-picker-toggle:checked:active > StWidget {
|
||||
.window-button.focused:active > StWidget {
|
||||
box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
@@ -78,50 +70,13 @@
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .status-label-bin {
|
||||
background-color: rgba(200, 200, 200, .3);
|
||||
border: 1px solid #cccccc;
|
||||
padding: 0 3px;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspaces-box {
|
||||
spacing: 3px;
|
||||
.window-list-workspace-indicator {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace {
|
||||
border: 1px solid #cccccc;
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace:first-child:last-child:ltr,
|
||||
.window-list-workspace-indicator .workspace:first-child:last-child:rtl {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace:first-child:ltr,
|
||||
.window-list-workspace-indicator .workspace:last-child:rtl {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace:first-child:rtl,
|
||||
.window-list-workspace-indicator .workspace:last-child:ltr {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.window-list-workspace-indicator .workspace.active {
|
||||
.window-list-workspace-indicator > StWidget {
|
||||
background-color: rgba(200, 200, 200, .3);
|
||||
}
|
||||
|
||||
.window-list-window-preview {
|
||||
background-color: #252525;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.window-list-window-preview.active {
|
||||
background-color: #353535;
|
||||
border: 2px solid #ccc;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.notification {
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
/* exported WindowPicker, WindowPickerToggle */
|
||||
const { Clutter, GObject, Shell, St } = imports.gi;
|
||||
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const { WorkspacesDisplay } = imports.ui.workspacesView;
|
||||
const Workspace = imports.ui.workspace;
|
||||
|
||||
const { VIGNETTE_BRIGHTNESS } = imports.ui.lightbox;
|
||||
const {
|
||||
SIDE_CONTROLS_ANIMATION_TIME,
|
||||
OverviewAdjustment,
|
||||
ControlsState,
|
||||
} = imports.ui.overviewControls;
|
||||
|
||||
let MyWorkspacesDisplay = GObject.registerClass(
|
||||
class MyWorkspacesDisplay extends WorkspacesDisplay {
|
||||
_init(controls, overviewAdjustment) {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._overviewAdjustment = overviewAdjustment;
|
||||
this._workspaceAdjustment = new St.Adjustment({
|
||||
value: workspaceManager.get_active_workspace_index(),
|
||||
lower: 0,
|
||||
page_increment: 1,
|
||||
page_size: 1,
|
||||
step_increment: 0,
|
||||
upper: workspaceManager.n_workspaces,
|
||||
});
|
||||
|
||||
this._nWorkspacesChangedId =
|
||||
workspaceManager.connect('notify::n-workspaces',
|
||||
this._updateAdjustment.bind(this));
|
||||
|
||||
super._init(controls, this._workspaceAdjustment, this._overviewAdjustment);
|
||||
|
||||
this._workspaceAdjustment.actor = this;
|
||||
|
||||
this.add_constraint(
|
||||
new Layout.MonitorConstraint({
|
||||
primary: true,
|
||||
work_area: true,
|
||||
}));
|
||||
}
|
||||
|
||||
prepareToEnterOverview(...args) {
|
||||
if (!this._scrollEventId) {
|
||||
this._scrollEventId = Main.windowPicker.connect('scroll-event',
|
||||
this._onScrollEvent.bind(this));
|
||||
}
|
||||
|
||||
super.prepareToEnterOverview(...args);
|
||||
}
|
||||
|
||||
vfunc_hide(...args) {
|
||||
if (this._scrollEventId > 0)
|
||||
Main.windowPicker.disconnect(this._scrollEventId);
|
||||
this._scrollEventId = 0;
|
||||
|
||||
super.vfunc_hide(...args);
|
||||
}
|
||||
|
||||
_updateAdjustment() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
this._workspaceAdjustment.set({
|
||||
upper: workspaceManager.n_workspaces,
|
||||
value: workspaceManager.get_active_workspace_index(),
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._nWorkspacesChangedId)
|
||||
global.workspace_manager.disconnect(this._nWorkspacesChangedId);
|
||||
this._nWorkspacesChangedId = 0;
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
});
|
||||
|
||||
const MyWorkspace = GObject.registerClass(
|
||||
class MyWorkspace extends Workspace.Workspace {
|
||||
_init(...args) {
|
||||
super._init(...args);
|
||||
|
||||
this._adjChangedId =
|
||||
this._overviewAdjustment.connect('notify::value', () => {
|
||||
const { value: progress } = this._overviewAdjustment;
|
||||
const brightness = 1 - (1 - VIGNETTE_BRIGHTNESS) * progress;
|
||||
for (const bg of this._background?._backgroundGroup ?? []) {
|
||||
bg.content.set({
|
||||
vignette: true,
|
||||
brightness,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
super._onDestroy();
|
||||
|
||||
if (this._adjChangedId)
|
||||
this._overviewAdjustment.disconnect(this._adjChangedId);
|
||||
this._adjChangedId = 0;
|
||||
}
|
||||
});
|
||||
|
||||
const MyWorkspaceBackground = GObject.registerClass(
|
||||
class MyWorkspaceBackground extends Workspace.WorkspaceBackground {
|
||||
_updateBorderRadius() {
|
||||
}
|
||||
|
||||
vfunc_allocate(box) {
|
||||
this.set_allocation(box);
|
||||
|
||||
const themeNode = this.get_theme_node();
|
||||
const contentBox = themeNode.get_content_box(box);
|
||||
|
||||
this._bin.allocate(contentBox);
|
||||
|
||||
const [contentWidth, contentHeight] = contentBox.get_size();
|
||||
const monitor = Main.layoutManager.monitors[this._monitorIndex];
|
||||
const xRatio = contentWidth / this._workarea.width;
|
||||
const yRatio = contentHeight / this._workarea.height;
|
||||
|
||||
const right = area => area.x + area.width;
|
||||
const bottom = area => area.y + area.height;
|
||||
|
||||
const offsets = {
|
||||
left: xRatio * (this._workarea.x - monitor.x),
|
||||
right: xRatio * (right(monitor) - right(this._workarea)),
|
||||
top: yRatio * (this._workarea.y - monitor.y),
|
||||
bottom: yRatio * (bottom(monitor) - bottom(this._workarea)),
|
||||
};
|
||||
|
||||
contentBox.set_origin(-offsets.left, -offsets.top);
|
||||
contentBox.set_size(
|
||||
offsets.left + contentWidth + offsets.right,
|
||||
offsets.top + contentHeight + offsets.bottom);
|
||||
this._backgroundGroup.allocate(contentBox);
|
||||
}
|
||||
});
|
||||
|
||||
var WindowPicker = GObject.registerClass({
|
||||
Signals: {
|
||||
'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
|
||||
},
|
||||
}, class extends Clutter.Actor {
|
||||
_init() {
|
||||
this._visible = false;
|
||||
this._modal = false;
|
||||
|
||||
this._overlayKeyId = 0;
|
||||
this._stageKeyPressId = 0;
|
||||
|
||||
super._init({ reactive: true });
|
||||
|
||||
this._adjustment = new OverviewAdjustment(this);
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
|
||||
global.bind_property('screen-width',
|
||||
this, 'width',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
global.bind_property('screen-height',
|
||||
this, 'height',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
this._workspacesDisplay = new MyWorkspacesDisplay(this, this._adjustment);
|
||||
this.add_child(this._workspacesDisplay);
|
||||
|
||||
Main.uiGroup.insert_child_below(this, global.window_group);
|
||||
|
||||
if (!Main.sessionMode.hasOverview) {
|
||||
this._injectBackgroundShade();
|
||||
|
||||
this._overlayKeyId = global.display.connect('overlay-key', () => {
|
||||
if (!this._visible)
|
||||
this.open();
|
||||
else
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_injectBackgroundShade() {
|
||||
this._origWorkspace = Workspace.Workspace;
|
||||
this._origWorkspaceBackground = Workspace.WorkspaceBackground;
|
||||
|
||||
Workspace.Workspace = MyWorkspace;
|
||||
Workspace.WorkspaceBackground = MyWorkspaceBackground;
|
||||
}
|
||||
|
||||
get visible() {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this._visible)
|
||||
return;
|
||||
|
||||
this._visible = true;
|
||||
|
||||
if (!this._syncGrab())
|
||||
return;
|
||||
|
||||
this._fakeOverviewVisible(true);
|
||||
this._workspacesDisplay.prepareToEnterOverview();
|
||||
Main.overview._animationInProgress = true;
|
||||
|
||||
this._adjustment.value = ControlsState.HIDDEN;
|
||||
this._adjustment.ease(ControlsState.WINDOW_PICKER, {
|
||||
duration: SIDE_CONTROLS_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => (Main.overview._animationInProgress = false),
|
||||
});
|
||||
|
||||
this._stageKeyPressId = global.stage.connect('key-press-event',
|
||||
(a, event) => {
|
||||
let sym = event.get_key_symbol();
|
||||
if (sym === Clutter.KEY_Escape) {
|
||||
this.close();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
});
|
||||
|
||||
this.emit('open-state-changed', this._visible);
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this._visible)
|
||||
return;
|
||||
|
||||
this._visible = false;
|
||||
|
||||
if (!this._syncGrab())
|
||||
return;
|
||||
|
||||
this._workspacesDisplay.prepareToLeaveOverview();
|
||||
|
||||
Main.overview._animationInProgress = true;
|
||||
this._adjustment.ease(ControlsState.HIDDEN, {
|
||||
duration: SIDE_CONTROLS_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => {
|
||||
Main.overview._animationInProgress = false;
|
||||
this._workspacesDisplay.hide();
|
||||
this._fakeOverviewVisible(false);
|
||||
},
|
||||
});
|
||||
|
||||
global.stage.disconnect(this._stageKeyPressId);
|
||||
this._stageKeyPressId = 0;
|
||||
|
||||
this.emit('open-state-changed', this._visible);
|
||||
}
|
||||
|
||||
getWorkspacesBoxForState() {
|
||||
return this.allocation;
|
||||
}
|
||||
|
||||
_fakeOverviewVisible(visible) {
|
||||
// Fake overview state for WorkspacesDisplay
|
||||
Main.overview._visible = visible;
|
||||
|
||||
// Hide real windows
|
||||
Main.layoutManager._inOverview = visible;
|
||||
Main.layoutManager._updateVisibility();
|
||||
}
|
||||
|
||||
_syncGrab() {
|
||||
if (this._visible) {
|
||||
if (this._modal)
|
||||
return true;
|
||||
|
||||
this._modal = Main.pushModal(this, {
|
||||
actionMode: Shell.ActionMode.OVERVIEW,
|
||||
});
|
||||
|
||||
if (!this._modal) {
|
||||
this.hide();
|
||||
return false;
|
||||
}
|
||||
} else if (this._modal) {
|
||||
Main.popModal(this);
|
||||
this._modal = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._origWorkspace)
|
||||
Workspace.Workspace = this._origWorkspace;
|
||||
|
||||
if (this._origWorkspaceBackground)
|
||||
Workspace.WorkspaceBackground = this._origWorkspaceBackground;
|
||||
|
||||
if (this._monitorsChangedId)
|
||||
Main.layoutManager.disconnect(this._monitorsChangedId);
|
||||
this._monitorsChangedId = 0;
|
||||
|
||||
if (this._overlayKeyId)
|
||||
global.display.disconnect(this._overlayKeyId);
|
||||
this._overlayKeyId = 0;
|
||||
|
||||
if (this._stageKeyPressId)
|
||||
global.stage.disconnect(this._stageKeyPressId);
|
||||
this._stageKeyPressId = 0;
|
||||
}
|
||||
});
|
||||
|
||||
var WindowPickerToggle = GObject.registerClass(
|
||||
class WindowPickerToggle extends St.Button {
|
||||
_init() {
|
||||
let iconBin = new St.Widget({
|
||||
layout_manager: new Clutter.BinLayout(),
|
||||
});
|
||||
iconBin.add_child(new St.Icon({
|
||||
icon_name: 'focus-windows-symbolic',
|
||||
icon_size: 16,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
}));
|
||||
super._init({
|
||||
style_class: 'window-picker-toggle',
|
||||
child: iconBin,
|
||||
visible: !Main.sessionMode.hasOverview,
|
||||
toggle_mode: true,
|
||||
});
|
||||
|
||||
this.connect('notify::checked', () => {
|
||||
if (this.checked)
|
||||
Main.windowPicker.open();
|
||||
else
|
||||
Main.windowPicker.close();
|
||||
});
|
||||
|
||||
Main.windowPicker.connect('open-state-changed', () => {
|
||||
this.checked = Main.windowPicker.visible;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,432 +0,0 @@
|
||||
/* exported WorkspaceIndicator */
|
||||
const { Clutter, Gio, GObject, Meta, St } = imports.gi;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const TOOLTIP_OFFSET = 6;
|
||||
const TOOLTIP_ANIMATION_TIME = 150;
|
||||
|
||||
let WindowPreview = GObject.registerClass(
|
||||
class WindowPreview extends St.Button {
|
||||
_init(window) {
|
||||
super._init({
|
||||
style_class: 'window-list-window-preview',
|
||||
});
|
||||
|
||||
this._delegate = this;
|
||||
DND.makeDraggable(this, { restoreOnSuccess: true });
|
||||
|
||||
this._window = window;
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
|
||||
this._sizeChangedId = this._window.connect('size-changed',
|
||||
() => this.queue_relayout());
|
||||
this._positionChangedId = this._window.connect('position-changed',
|
||||
() => {
|
||||
this._updateVisible();
|
||||
this.queue_relayout();
|
||||
});
|
||||
this._minimizedChangedId = this._window.connect('notify::minimized',
|
||||
this._updateVisible.bind(this));
|
||||
|
||||
this._focusChangedId = global.display.connect('notify::focus-window',
|
||||
this._onFocusChanged.bind(this));
|
||||
this._onFocusChanged();
|
||||
}
|
||||
|
||||
// needed for DND
|
||||
get metaWindow() {
|
||||
return this._window;
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
this._window.disconnect(this._sizeChangedId);
|
||||
this._window.disconnect(this._positionChangedId);
|
||||
this._window.disconnect(this._minimizedChangedId);
|
||||
global.display.disconnect(this._focusChangedId);
|
||||
}
|
||||
|
||||
_onFocusChanged() {
|
||||
if (global.display.focus_window === this._window)
|
||||
this.add_style_class_name('active');
|
||||
else
|
||||
this.remove_style_class_name('active');
|
||||
}
|
||||
|
||||
_updateVisible() {
|
||||
const monitor = Main.layoutManager.findIndexForActor(this);
|
||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
||||
this.visible = this._window.get_frame_rect().overlap(workArea) &&
|
||||
this._window.window_type !== Meta.WindowType.DESKTOP &&
|
||||
this._window.showing_on_its_workspace();
|
||||
}
|
||||
});
|
||||
|
||||
let WorkspaceLayout = GObject.registerClass(
|
||||
class WorkspaceLayout extends Clutter.LayoutManager {
|
||||
vfunc_get_preferred_width() {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height() {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
vfunc_allocate(container, box) {
|
||||
const monitor = Main.layoutManager.findIndexForActor(container);
|
||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
||||
const hscale = box.get_width() / workArea.width;
|
||||
const vscale = box.get_height() / workArea.height;
|
||||
|
||||
for (const child of container) {
|
||||
const childBox = new Clutter.ActorBox();
|
||||
const frameRect = child.metaWindow.get_frame_rect();
|
||||
childBox.set_size(
|
||||
Math.round(Math.min(frameRect.width, workArea.width) * hscale),
|
||||
Math.round(Math.min(frameRect.height, workArea.height) * vscale));
|
||||
childBox.set_origin(
|
||||
Math.round((frameRect.x - workArea.x) * hscale),
|
||||
Math.round((frameRect.y - workArea.y) * vscale));
|
||||
child.allocate(childBox);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let WorkspaceThumbnail = GObject.registerClass(
|
||||
class WorkspaceThumbnail extends St.Button {
|
||||
_init(index) {
|
||||
super._init({
|
||||
style_class: 'workspace',
|
||||
child: new Clutter.Actor({
|
||||
layout_manager: new WorkspaceLayout(),
|
||||
clip_to_allocation: true,
|
||||
}),
|
||||
});
|
||||
|
||||
this._tooltip = new St.Label({
|
||||
style_class: 'dash-label',
|
||||
visible: false,
|
||||
});
|
||||
Main.uiGroup.add_child(this._tooltip);
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this.connect('notify::hover', this._syncTooltip.bind(this));
|
||||
|
||||
this._index = index;
|
||||
this._delegate = this; // needed for DND
|
||||
|
||||
this._windowPreviews = new Map();
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
this._workspace = workspaceManager.get_workspace_by_index(index);
|
||||
|
||||
this._windowAddedId = this._workspace.connect('window-added',
|
||||
(ws, window) => {
|
||||
this._addWindow(window);
|
||||
});
|
||||
this._windowRemovedId = this._workspace.connect('window-removed',
|
||||
(ws, window) => {
|
||||
this._removeWindow(window);
|
||||
});
|
||||
this._restackedId = global.display.connect('restacked',
|
||||
this._onRestacked.bind(this));
|
||||
|
||||
this._workspace.list_windows().forEach(w => this._addWindow(w));
|
||||
this._onRestacked();
|
||||
}
|
||||
|
||||
acceptDrop(source) {
|
||||
if (!source.metaWindow)
|
||||
return false;
|
||||
|
||||
this._moveWindow(source.metaWindow);
|
||||
return true;
|
||||
}
|
||||
|
||||
handleDragOver(source) {
|
||||
if (source.metaWindow)
|
||||
return DND.DragMotionResult.MOVE_DROP;
|
||||
else
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
}
|
||||
|
||||
_addWindow(window) {
|
||||
if (this._windowPreviews.has(window))
|
||||
return;
|
||||
|
||||
let preview = new WindowPreview(window);
|
||||
preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
|
||||
this._windowPreviews.set(window, preview);
|
||||
this.child.add_child(preview);
|
||||
}
|
||||
|
||||
_removeWindow(window) {
|
||||
let preview = this._windowPreviews.get(window);
|
||||
if (!preview)
|
||||
return;
|
||||
|
||||
this._windowPreviews.delete(window);
|
||||
preview.destroy();
|
||||
}
|
||||
|
||||
_onRestacked() {
|
||||
let lastPreview = null;
|
||||
let windows = global.get_window_actors().map(a => a.meta_window);
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let preview = this._windowPreviews.get(windows[i]);
|
||||
if (!preview)
|
||||
continue;
|
||||
|
||||
this.child.set_child_above_sibling(preview, lastPreview);
|
||||
lastPreview = preview;
|
||||
}
|
||||
}
|
||||
|
||||
_moveWindow(window) {
|
||||
let monitorIndex = Main.layoutManager.findIndexForActor(this);
|
||||
if (monitorIndex !== window.get_monitor())
|
||||
window.move_to_monitor(monitorIndex);
|
||||
window.change_workspace_by_index(this._index, false);
|
||||
}
|
||||
|
||||
on_clicked() {
|
||||
let ws = global.workspace_manager.get_workspace_by_index(this._index);
|
||||
if (ws)
|
||||
ws.activate(global.get_current_time());
|
||||
}
|
||||
|
||||
_syncTooltip() {
|
||||
if (this.hover) {
|
||||
this._tooltip.set({
|
||||
text: Meta.prefs_get_workspace_name(this._index),
|
||||
visible: true,
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
const [stageX, stageY] = this.get_transformed_position();
|
||||
const thumbWidth = this.allocation.get_width();
|
||||
const tipWidth = this._tooltip.width;
|
||||
const tipHeight = this._tooltip.height;
|
||||
const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
|
||||
const monitor = Main.layoutManager.findMonitorForActor(this);
|
||||
const x = Math.clamp(
|
||||
stageX + xOffset,
|
||||
monitor.x,
|
||||
monitor.x + monitor.width - tipWidth);
|
||||
const y = stageY - tipHeight - TOOLTIP_OFFSET;
|
||||
this._tooltip.set_position(x, y);
|
||||
}
|
||||
|
||||
this._tooltip.ease({
|
||||
opacity: this.hover ? 255 : 0,
|
||||
duration: TOOLTIP_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => (this._tooltip.visible = this.hover),
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
this._tooltip.destroy();
|
||||
|
||||
this._workspace.disconnect(this._windowAddedId);
|
||||
this._workspace.disconnect(this._windowRemovedId);
|
||||
global.display.disconnect(this._restackedId);
|
||||
}
|
||||
});
|
||||
|
||||
var WorkspaceIndicator = GObject.registerClass(
|
||||
class WorkspaceIndicator extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(0.0, _('Workspace Indicator'), true);
|
||||
this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM));
|
||||
this.add_style_class_name('window-list-workspace-indicator');
|
||||
this.menu.actor.remove_style_class_name('panel-menu');
|
||||
|
||||
let container = new St.Widget({
|
||||
layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
});
|
||||
this.add_actor(container);
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
||||
this._statusLabel = new St.Label({ text: this._getStatusText() });
|
||||
|
||||
this._statusBin = new St.Bin({
|
||||
style_class: 'status-label-bin',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
child: this._statusLabel,
|
||||
});
|
||||
container.add_actor(this._statusBin);
|
||||
|
||||
this._thumbnailsBox = new St.BoxLayout({
|
||||
style_class: 'workspaces-box',
|
||||
y_expand: true,
|
||||
reactive: true,
|
||||
});
|
||||
this._thumbnailsBox.connect('scroll-event',
|
||||
this._onScrollEvent.bind(this));
|
||||
container.add_actor(this._thumbnailsBox);
|
||||
|
||||
this._workspacesItems = [];
|
||||
|
||||
this._workspaceManagerSignals = [
|
||||
workspaceManager.connect('notify::n-workspaces',
|
||||
this._nWorkspacesChanged.bind(this)),
|
||||
workspaceManager.connect_after('workspace-switched',
|
||||
this._onWorkspaceSwitched.bind(this)),
|
||||
workspaceManager.connect('notify::layout-rows',
|
||||
this._onWorkspaceOrientationChanged.bind(this)),
|
||||
];
|
||||
|
||||
this.connect('scroll-event', this._onScrollEvent.bind(this));
|
||||
this._updateMenu();
|
||||
this._updateThumbnails();
|
||||
this._onWorkspaceOrientationChanged();
|
||||
|
||||
this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
|
||||
this._settingsChangedId = this._settings.connect(
|
||||
'changed::workspace-names', this._updateMenuLabels.bind(this));
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
for (let i = 0; i < this._workspaceManagerSignals.length; i++)
|
||||
global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
|
||||
|
||||
if (this._settingsChangedId) {
|
||||
this._settings.disconnect(this._settingsChangedId);
|
||||
this._settingsChangedId = 0;
|
||||
}
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
|
||||
_onWorkspaceOrientationChanged() {
|
||||
let vertical = global.workspace_manager.layout_rows === -1;
|
||||
this.reactive = vertical;
|
||||
|
||||
this._statusBin.visible = vertical;
|
||||
this._thumbnailsBox.visible = !vertical;
|
||||
}
|
||||
|
||||
_onWorkspaceSwitched() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
||||
|
||||
this._updateMenuOrnament();
|
||||
this._updateActiveThumbnail();
|
||||
|
||||
this._statusLabel.set_text(this._getStatusText());
|
||||
}
|
||||
|
||||
_nWorkspacesChanged() {
|
||||
this._updateMenu();
|
||||
this._updateThumbnails();
|
||||
}
|
||||
|
||||
_updateMenuOrnament() {
|
||||
for (let i = 0; i < this._workspacesItems.length; i++) {
|
||||
this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
||||
? PopupMenu.Ornament.DOT
|
||||
: PopupMenu.Ornament.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
_updateActiveThumbnail() {
|
||||
let thumbs = this._thumbnailsBox.get_children();
|
||||
for (let i = 0; i < thumbs.length; i++) {
|
||||
if (i === this._currentWorkspace)
|
||||
thumbs[i].add_style_class_name('active');
|
||||
else
|
||||
thumbs[i].remove_style_class_name('active');
|
||||
}
|
||||
}
|
||||
|
||||
_getStatusText() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let current = workspaceManager.get_active_workspace_index();
|
||||
let total = workspaceManager.n_workspaces;
|
||||
|
||||
return '%d / %d'.format(current + 1, total);
|
||||
}
|
||||
|
||||
_updateMenuLabels() {
|
||||
for (let i = 0; i < this._workspacesItems.length; i++) {
|
||||
let item = this._workspacesItems[i];
|
||||
let name = Meta.prefs_get_workspace_name(i);
|
||||
item.label.text = name;
|
||||
}
|
||||
}
|
||||
|
||||
_updateMenu() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this.menu.removeAll();
|
||||
this._workspacesItems = [];
|
||||
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
||||
|
||||
for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
||||
let name = Meta.prefs_get_workspace_name(i);
|
||||
let item = new PopupMenu.PopupMenuItem(name);
|
||||
item.workspaceId = i;
|
||||
|
||||
item.connect('activate', () => {
|
||||
this._activate(item.workspaceId);
|
||||
});
|
||||
|
||||
if (i === this._currentWorkspace)
|
||||
item.setOrnament(PopupMenu.Ornament.DOT);
|
||||
|
||||
this.menu.addMenuItem(item);
|
||||
this._workspacesItems[i] = item;
|
||||
}
|
||||
|
||||
this._statusLabel.set_text(this._getStatusText());
|
||||
}
|
||||
|
||||
_updateThumbnails() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._thumbnailsBox.destroy_all_children();
|
||||
|
||||
for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
||||
let thumb = new WorkspaceThumbnail(i);
|
||||
this._thumbnailsBox.add_actor(thumb);
|
||||
}
|
||||
this._updateActiveThumbnail();
|
||||
}
|
||||
|
||||
_activate(index) {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
if (index >= 0 && index < workspaceManager.n_workspaces) {
|
||||
let metaWorkspace = workspaceManager.get_workspace_by_index(index);
|
||||
metaWorkspace.activate(global.get_current_time());
|
||||
}
|
||||
}
|
||||
|
||||
_onScrollEvent(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
let diff = 0;
|
||||
if (direction === Clutter.ScrollDirection.DOWN)
|
||||
diff = 1;
|
||||
else if (direction === Clutter.ScrollDirection.UP)
|
||||
diff = -1;
|
||||
else
|
||||
return;
|
||||
|
||||
let newIndex = this._currentWorkspace + diff;
|
||||
this._activate(newIndex);
|
||||
}
|
||||
});
|
||||
|
||||
3
extensions/windowsNavigator/Makefile.am
Normal file
3
extensions/windowsNavigator/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = windowsNavigator
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,184 +1,154 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/* exported init */
|
||||
const { Clutter, Graphene, GObject, St } = imports.gi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Workspace = imports.ui.workspace;
|
||||
const WorkspacesView = imports.ui.workspacesView;
|
||||
|
||||
const WINDOW_SLOT = 4;
|
||||
|
||||
var MyWorkspace = GObject.registerClass(
|
||||
class MyWorkspace extends Workspace.Workspace {
|
||||
_init(metaWorkspace, monitorIndex) {
|
||||
super._init(metaWorkspace, monitorIndex);
|
||||
|
||||
if (metaWorkspace && metaWorkspace.index() < 9) {
|
||||
this._tip = new St.Label({
|
||||
style_class: 'extension-windowsNavigator-window-tooltip',
|
||||
visible: false,
|
||||
});
|
||||
this.add_actor(this._tip);
|
||||
|
||||
this.connect('notify::scale-x', () => {
|
||||
this._tip.set_scale(1 / this.scale_x, 1 / this.scale_x);
|
||||
});
|
||||
} else {
|
||||
this._tip = null;
|
||||
}
|
||||
function injectToFunction(parent, name, func) {
|
||||
let origin = parent[name];
|
||||
parent[name] = function() {
|
||||
let ret;
|
||||
ret = origin.apply(this, arguments);
|
||||
if (ret === undefined)
|
||||
ret = func.apply(this, arguments);
|
||||
return ret;
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
vfunc_allocate(box) {
|
||||
super.vfunc_allocate(box);
|
||||
let winInjections, workspaceInjections, workViewInjections, createdActors, connectedSignals;
|
||||
|
||||
if (this._tip)
|
||||
this._tip.allocate_preferred_size(0, 0);
|
||||
function resetState() {
|
||||
winInjections = { };
|
||||
workspaceInjections = { };
|
||||
workViewInjections = { };
|
||||
createdActors = [ ];
|
||||
connectedSignals = [ ];
|
||||
}
|
||||
|
||||
function enable() {
|
||||
resetState();
|
||||
|
||||
Workspace.WindowOverlay.prototype.showTooltip = function() {
|
||||
this._text.raise_top();
|
||||
this._text.show();
|
||||
this._text.text = (this._windowClone.slotId + 1).toString();
|
||||
}
|
||||
winInjections['showTooltip'] = undefined;
|
||||
|
||||
showTooltip() {
|
||||
if (!this._tip)
|
||||
Workspace.WindowOverlay.prototype.hideTooltip = function() {
|
||||
if (this._text && this._text.visible)
|
||||
this._text.hide();
|
||||
}
|
||||
winInjections['hideTooltip'] = undefined;
|
||||
|
||||
Workspace.Workspace.prototype.showTooltip = function() {
|
||||
if (this._tip == null || this._actualGeometry == null)
|
||||
return;
|
||||
this._tip.text = (this.metaWorkspace.index() + 1).toString();
|
||||
|
||||
// Hand code this instead of using _getSpacingAndPadding
|
||||
// because that fails on empty workspaces
|
||||
let node = this.actor.get_theme_node();
|
||||
let padding = {
|
||||
left: node.get_padding(St.Side.LEFT),
|
||||
top: node.get_padding(St.Side.TOP),
|
||||
bottom: node.get_padding(St.Side.BOTTOM),
|
||||
right: node.get_padding(St.Side.RIGHT),
|
||||
};
|
||||
|
||||
let area = Workspace.padArea(this._actualGeometry, padding);
|
||||
this._tip.x = area.x;
|
||||
this._tip.y = area.y;
|
||||
this._tip.show();
|
||||
this.set_child_below_sibling(this._tip, null);
|
||||
this._tip.raise_top();
|
||||
}
|
||||
workspaceInjections['showTooltip'] = undefined;
|
||||
|
||||
hideTooltip() {
|
||||
if (this._tip)
|
||||
this._tip.hide();
|
||||
Workspace.Workspace.prototype.hideTooltip = function() {
|
||||
if (this._tip == null)
|
||||
return;
|
||||
if (!this._tip.get_parent())
|
||||
return;
|
||||
this._tip.hide();
|
||||
}
|
||||
workspaceInjections['hideTooltip'] = undefined;
|
||||
|
||||
getWindowWithTooltip(id) {
|
||||
const slot = this.layout_manager._windowSlots[id - 1];
|
||||
return slot ? slot[WINDOW_SLOT].metaWindow : null;
|
||||
Workspace.Workspace.prototype.getWindowWithTooltip = function(id) {
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
if ((this._windows[i].slotId + 1) == id)
|
||||
return this._windows[i].metaWindow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
workspaceInjections['getWindowWithTooltip'] = undefined;
|
||||
|
||||
showWindowsTooltips() {
|
||||
for (let i = 0; i < this.layout_manager._windowSlots.length; i++) {
|
||||
if (this.layout_manager._windowSlots[i])
|
||||
this.layout_manager._windowSlots[i][WINDOW_SLOT].showTooltip(`${i + 1}`);
|
||||
Workspace.Workspace.prototype.showWindowsTooltips = function() {
|
||||
for (let i in this._windowOverlays) {
|
||||
if (this._windowOverlays[i] != null)
|
||||
this._windowOverlays[i].showTooltip();
|
||||
}
|
||||
}
|
||||
workspaceInjections['showWindowsTooltips'] = undefined;
|
||||
|
||||
hideWindowsTooltips() {
|
||||
for (let i in this.layout_manager._windowSlots) {
|
||||
if (this.layout_manager._windowSlots[i])
|
||||
this.layout_manager._windowSlots[i][WINDOW_SLOT].hideTooltip();
|
||||
Workspace.Workspace.prototype.hideWindowsTooltips = function() {
|
||||
for (let i in this._windowOverlays) {
|
||||
if (this._windowOverlays[i] != null)
|
||||
this._windowOverlays[i].hideTooltip();
|
||||
}
|
||||
}
|
||||
workspaceInjections['hideWindowsTooltips'] = undefined;
|
||||
|
||||
// overriding _addWindowClone to apply the tooltip patch on the cloned
|
||||
// windowPreview
|
||||
_addWindowClone(metaWindow) {
|
||||
const clone = super._addWindowClone(metaWindow);
|
||||
|
||||
// appling the tooltip patch
|
||||
(function patchPreview() {
|
||||
this._text = new St.Label({
|
||||
style_class: 'extension-windowsNavigator-window-tooltip',
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this._text.add_constraint(new Clutter.BindConstraint({
|
||||
source: this._borderCenter,
|
||||
coordinate: Clutter.BindCoordinate.POSITION,
|
||||
}));
|
||||
this._text.add_constraint(new Clutter.AlignConstraint({
|
||||
source: this._borderCenter,
|
||||
align_axis: Clutter.AlignAxis.X_AXIS,
|
||||
pivot_point: new Graphene.Point({ x: 0.5, y: -1 }),
|
||||
factor: this._closeButtonSide === St.Side.LEFT ? 1 : 0,
|
||||
}));
|
||||
this._text.add_constraint(new Clutter.AlignConstraint({
|
||||
source: this._borderCenter,
|
||||
align_axis: Clutter.AlignAxis.Y_AXIS,
|
||||
pivot_point: new Graphene.Point({ x: -1, y: 0.5 }),
|
||||
factor: 0,
|
||||
}));
|
||||
|
||||
this.add_child(this._text);
|
||||
}).call(clone);
|
||||
|
||||
clone.showTooltip = function (text) {
|
||||
this._text.set({ text });
|
||||
this._text.show();
|
||||
};
|
||||
|
||||
clone.hideTooltip = function () {
|
||||
if (this._text && this._text.visible)
|
||||
this._text.hide();
|
||||
};
|
||||
|
||||
return clone;
|
||||
}
|
||||
});
|
||||
|
||||
var MyWorkspacesView = GObject.registerClass(
|
||||
class MyWorkspacesView extends WorkspacesView.WorkspacesView {
|
||||
_init(width, height, x, y, workspaces) {
|
||||
super._init(width, height, x, y, workspaces);
|
||||
|
||||
this._pickWorkspace = false;
|
||||
this._pickWindow = false;
|
||||
this._keyPressEventId =
|
||||
global.stage.connect('key-press-event', this._onKeyPress.bind(this));
|
||||
this._keyReleaseEventId =
|
||||
global.stage.connect('key-release-event', this._onKeyRelease.bind(this));
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
super._onDestroy();
|
||||
|
||||
global.stage.disconnect(this._keyPressEventId);
|
||||
global.stage.disconnect(this._keyReleaseEventId);
|
||||
}
|
||||
|
||||
_hideTooltips() {
|
||||
if (global.stage.get_key_focus() === global.stage)
|
||||
WorkspacesView.WorkspacesView.prototype._hideTooltips = function() {
|
||||
if (global.stage.get_key_focus() == global.stage)
|
||||
global.stage.set_key_focus(this._prevFocusActor);
|
||||
this._pickWindow = false;
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].hideWindowsTooltips();
|
||||
}
|
||||
workViewInjections['_hideTooltips'] = undefined;
|
||||
|
||||
_hideWorkspacesTooltips() {
|
||||
WorkspacesView.WorkspacesView.prototype._hideWorkspacesTooltips = function() {
|
||||
global.stage.set_key_focus(this._prevFocusActor);
|
||||
this._pickWorkspace = false;
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].hideTooltip();
|
||||
}
|
||||
workViewInjections['_hideWorkspacesTooltips'] = undefined;
|
||||
|
||||
_onKeyRelease(s, o) {
|
||||
WorkspacesView.WorkspacesView.prototype._onKeyRelease = function(s, o) {
|
||||
if (this._pickWindow &&
|
||||
(o.get_key_symbol() === Clutter.KEY_Alt_L ||
|
||||
o.get_key_symbol() === Clutter.KEY_Alt_R))
|
||||
(o.get_key_symbol() == Clutter.KEY_Alt_L ||
|
||||
o.get_key_symbol() == Clutter.KEY_Alt_R))
|
||||
this._hideTooltips();
|
||||
if (this._pickWorkspace &&
|
||||
(o.get_key_symbol() === Clutter.KEY_Control_L ||
|
||||
o.get_key_symbol() === Clutter.KEY_Control_R))
|
||||
(o.get_key_symbol() == Clutter.KEY_Control_L ||
|
||||
o.get_key_symbol() == Clutter.KEY_Control_R))
|
||||
this._hideWorkspacesTooltips();
|
||||
}
|
||||
workViewInjections['_onKeyRelease'] = undefined;
|
||||
|
||||
_onKeyPress(s, o) {
|
||||
let { viewSelector } = Main.overview;
|
||||
if (viewSelector._activePage !== viewSelector._workspacesPage)
|
||||
WorkspacesView.WorkspacesView.prototype._onKeyPress = function(s, o) {
|
||||
if(Main.overview.viewSelector._activePage != Main.overview.viewSelector._workspacesPage)
|
||||
return false;
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
if ((o.get_key_symbol() === Clutter.KEY_Alt_L ||
|
||||
o.get_key_symbol() === Clutter.KEY_Alt_R) &&
|
||||
!this._pickWorkspace) {
|
||||
if ((o.get_key_symbol() == Clutter.KEY_Alt_L ||
|
||||
o.get_key_symbol() == Clutter.KEY_Alt_R)
|
||||
&& !this._pickWorkspace) {
|
||||
this._prevFocusActor = global.stage.get_key_focus();
|
||||
global.stage.set_key_focus(null);
|
||||
this._active = workspaceManager.get_active_workspace_index();
|
||||
this._active = global.screen.get_active_workspace_index();
|
||||
this._pickWindow = true;
|
||||
this._workspaces[workspaceManager.get_active_workspace_index()].showWindowsTooltips();
|
||||
this._workspaces[global.screen.get_active_workspace_index()].showWindowsTooltips();
|
||||
return true;
|
||||
}
|
||||
if ((o.get_key_symbol() === Clutter.KEY_Control_L ||
|
||||
o.get_key_symbol() === Clutter.KEY_Control_R) &&
|
||||
!this._pickWindow) {
|
||||
if ((o.get_key_symbol() == Clutter.KEY_Control_L ||
|
||||
o.get_key_symbol() == Clutter.KEY_Control_R)
|
||||
&& !this._pickWindow) {
|
||||
this._prevFocusActor = global.stage.get_key_focus();
|
||||
global.stage.set_key_focus(null);
|
||||
this._pickWorkspace = true;
|
||||
@@ -187,17 +157,17 @@ class MyWorkspacesView extends WorkspacesView.WorkspacesView {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (global.stage.get_key_focus() !== global.stage)
|
||||
if (global.stage.get_key_focus() != global.stage)
|
||||
return false;
|
||||
|
||||
// ignore shift presses, they're required to get numerals in azerty keyboards
|
||||
if ((this._pickWindow || this._pickWorkspace) &&
|
||||
(o.get_key_symbol() === Clutter.KEY_Shift_L ||
|
||||
o.get_key_symbol() === Clutter.KEY_Shift_R))
|
||||
(o.get_key_symbol() == Clutter.KEY_Shift_L ||
|
||||
o.get_key_symbol() == Clutter.KEY_Shift_R))
|
||||
return true;
|
||||
|
||||
if (this._pickWindow) {
|
||||
if (this._active !== workspaceManager.get_active_workspace_index()) {
|
||||
if (this._active != global.screen.get_active_workspace_index()) {
|
||||
this._hideTooltips();
|
||||
return false;
|
||||
}
|
||||
@@ -239,25 +209,80 @@ class MyWorkspacesView extends WorkspacesView.WorkspacesView {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workViewInjections['_onKeyPress'] = undefined;
|
||||
|
||||
class Extension {
|
||||
constructor() {
|
||||
this._origWorkspace = Workspace.Workspace;
|
||||
this._origWorkspacesView = WorkspacesView.WorkspacesView;
|
||||
}
|
||||
winInjections['_init'] = injectToFunction(Workspace.WindowOverlay.prototype, '_init', function(windowClone, parentActor) {
|
||||
this._id = null;
|
||||
createdActors.push(this._text = new St.Label({ style_class: 'extension-windowsNavigator-window-tooltip' }));
|
||||
this._text.hide();
|
||||
parentActor.add_actor(this._text);
|
||||
});
|
||||
|
||||
enable() {
|
||||
Workspace.Workspace = MyWorkspace;
|
||||
WorkspacesView.WorkspacesView = MyWorkspacesView;
|
||||
}
|
||||
winInjections['relayout'] = injectToFunction(Workspace.WindowOverlay.prototype, 'relayout', function(animate) {
|
||||
let [cloneX, cloneY, cloneWidth, cloneHeight] = this._windowClone.slot;
|
||||
|
||||
disable() {
|
||||
Workspace.Workspace = this._origWorkspace;
|
||||
WorkspacesView.WorkspacesView = this._origWorkspacesView;
|
||||
}
|
||||
let textX = cloneX - 2;
|
||||
let textY = cloneY - 2;
|
||||
this._text.set_position(Math.floor(textX) + 5, Math.floor(textY) + 5);
|
||||
this._text.raise_top();
|
||||
});
|
||||
|
||||
workspaceInjections['_init'] = injectToFunction(Workspace.Workspace.prototype, '_init', function(metaWorkspace) {
|
||||
if (metaWorkspace && metaWorkspace.index() < 9) {
|
||||
createdActors.push(this._tip = new St.Label({ style_class: 'extension-windowsNavigator-window-tooltip',
|
||||
visible: false }));
|
||||
|
||||
this.actor.add_actor(this._tip);
|
||||
let signalId = this.actor.connect('notify::scale-x', Lang.bind(this, function() {
|
||||
this._tip.set_scale(1 / this.actor.scale_x, 1 / this.actor.scale_x);
|
||||
}));
|
||||
connectedSignals.push({ obj: this.actor, id: signalId });
|
||||
} else
|
||||
this._tip = null;
|
||||
});
|
||||
|
||||
workViewInjections['_init'] = injectToFunction(WorkspacesView.WorkspacesView.prototype, '_init', function(width, height, x, y, workspaces) {
|
||||
this._pickWorkspace = false;
|
||||
this._pickWindow = false;
|
||||
this._keyPressEventId = global.stage.connect('key-press-event', Lang.bind(this, this._onKeyPress));
|
||||
this._keyReleaseEventId = global.stage.connect('key-release-event', Lang.bind(this, this._onKeyRelease));
|
||||
connectedSignals.push({ obj: global.stage, id: this._keyPressEventId });
|
||||
connectedSignals.push({ obj: global.stage, id: this._keyReleaseEventId });
|
||||
});
|
||||
|
||||
workViewInjections['_onDestroy'] = injectToFunction(WorkspacesView.WorkspacesView.prototype, '_onDestroy', function() {
|
||||
global.stage.disconnect(this._keyPressEventId);
|
||||
global.stage.disconnect(this._keyReleaseEventId);
|
||||
connectedSignals = [ ];
|
||||
});
|
||||
}
|
||||
|
||||
function removeInjection(object, injection, name) {
|
||||
if (injection[name] === undefined)
|
||||
delete object[name];
|
||||
else
|
||||
object[name] = injection[name];
|
||||
}
|
||||
|
||||
function disable() {
|
||||
let i;
|
||||
|
||||
for (i in workspaceInjections)
|
||||
removeInjection(Workspace.Workspace.prototype, workspaceInjections, i);
|
||||
for (i in winInjections)
|
||||
removeInjection(Workspace.WindowOverlay.prototype, winInjections, i);
|
||||
for (i in workViewInjections)
|
||||
removeInjection(WorkspacesView.WorkspacesView.prototype, workViewInjections, i);
|
||||
|
||||
for each (i in connectedSignals)
|
||||
i.obj.disconnect(i.id);
|
||||
|
||||
for each (i in createdActors)
|
||||
i.destroy();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
function init() {
|
||||
return new Extension();
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
5
extensions/workspace-indicator/Makefile.am
Normal file
5
extensions/workspace-indicator/Makefile.am
Normal file
@@ -0,0 +1,5 @@
|
||||
EXTENSION_ID = workspace-indicator
|
||||
|
||||
EXTRA_MODULES = prefs.js
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -1,450 +1,141 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
/* exported init enable disable */
|
||||
|
||||
const { Clutter, Gio, GObject, Meta, St } = imports.gi;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Main = imports.ui.main;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Panel = imports.ui.panel;
|
||||
|
||||
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;
|
||||
|
||||
const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
||||
const WORKSPACE_KEY = 'workspace-names';
|
||||
|
||||
const TOOLTIP_OFFSET = 6;
|
||||
const TOOLTIP_ANIMATION_TIME = 150;
|
||||
const WorkspaceIndicator = new Lang.Class({
|
||||
Name: 'WorkspaceIndicator.WorkspaceIndicator',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
let WindowPreview = GObject.registerClass(
|
||||
class WindowPreview extends St.Button {
|
||||
_init(window) {
|
||||
super._init({
|
||||
style_class: 'workspace-indicator-window-preview',
|
||||
});
|
||||
_init: function(){
|
||||
this.parent(0.0, _("Workspace Indicator"));
|
||||
|
||||
this._delegate = this;
|
||||
DND.makeDraggable(this, { restoreOnSuccess: true });
|
||||
this._currentWorkspace = global.screen.get_active_workspace().index();
|
||||
this.statusLabel = new St.Label({ y_align: Clutter.ActorAlign.CENTER,
|
||||
text: this._labelText() });
|
||||
|
||||
this._window = window;
|
||||
this.actor.add_actor(this.statusLabel);
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this.workspacesItems = [];
|
||||
this._workspaceSection = new PopupMenu.PopupMenuSection();
|
||||
this.menu.addMenuItem(this._workspaceSection);
|
||||
|
||||
this._sizeChangedId = this._window.connect('size-changed',
|
||||
() => this.queue_relayout());
|
||||
this._positionChangedId = this._window.connect('position-changed',
|
||||
() => {
|
||||
this._updateVisible();
|
||||
this.queue_relayout();
|
||||
});
|
||||
this._minimizedChangedId = this._window.connect('notify::minimized',
|
||||
this._updateVisible.bind(this));
|
||||
this._screenSignals = [];
|
||||
this._screenSignals.push(global.screen.connect_after('workspace-added', Lang.bind(this,this._createWorkspacesSection)));
|
||||
this._screenSignals.push(global.screen.connect_after('workspace-removed', Lang.bind(this,this._createWorkspacesSection)));
|
||||
this._screenSignals.push(global.screen.connect_after('workspace-switched', Lang.bind(this,this._updateIndicator)));
|
||||
|
||||
this._focusChangedId = global.display.connect('notify::focus-window',
|
||||
this._onFocusChanged.bind(this));
|
||||
this._onFocusChanged();
|
||||
}
|
||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
this._createWorkspacesSection();
|
||||
|
||||
// needed for DND
|
||||
get metaWindow() {
|
||||
return this._window;
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
this._window.disconnect(this._sizeChangedId);
|
||||
this._window.disconnect(this._positionChangedId);
|
||||
this._window.disconnect(this._minimizedChangedId);
|
||||
global.display.disconnect(this._focusChangedId);
|
||||
}
|
||||
|
||||
_onFocusChanged() {
|
||||
if (global.display.focus_window === this._window)
|
||||
this.add_style_class_name('active');
|
||||
else
|
||||
this.remove_style_class_name('active');
|
||||
}
|
||||
|
||||
_updateVisible() {
|
||||
const monitor = Main.layoutManager.findIndexForActor(this);
|
||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
||||
this.visible = this._window.get_frame_rect().overlap(workArea) &&
|
||||
this._window.window_type !== Meta.WindowType.DESKTOP &&
|
||||
this._window.showing_on_its_workspace();
|
||||
}
|
||||
});
|
||||
|
||||
let WorkspaceLayout = GObject.registerClass(
|
||||
class WorkspaceLayout extends Clutter.LayoutManager {
|
||||
vfunc_get_preferred_width() {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height() {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
vfunc_allocate(container, box) {
|
||||
const monitor = Main.layoutManager.findIndexForActor(container);
|
||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
||||
const hscale = box.get_width() / workArea.width;
|
||||
const vscale = box.get_height() / workArea.height;
|
||||
|
||||
for (const child of container) {
|
||||
const childBox = new Clutter.ActorBox();
|
||||
const frameRect = child.metaWindow.get_frame_rect();
|
||||
childBox.set_size(
|
||||
Math.round(Math.min(frameRect.width, workArea.width) * hscale),
|
||||
Math.round(Math.min(frameRect.height, workArea.height) * vscale));
|
||||
childBox.set_origin(
|
||||
Math.round((frameRect.x - workArea.x) * hscale),
|
||||
Math.round((frameRect.y - workArea.y) * vscale));
|
||||
child.allocate(childBox);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let WorkspaceThumbnail = GObject.registerClass(
|
||||
class WorkspaceThumbnail extends St.Button {
|
||||
_init(index) {
|
||||
super._init({
|
||||
style_class: 'workspace',
|
||||
child: new Clutter.Actor({
|
||||
layout_manager: new WorkspaceLayout(),
|
||||
clip_to_allocation: true,
|
||||
}),
|
||||
});
|
||||
|
||||
this._tooltip = new St.Label({
|
||||
style_class: 'dash-label',
|
||||
visible: false,
|
||||
});
|
||||
Main.uiGroup.add_child(this._tooltip);
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this.connect('notify::hover', this._syncTooltip.bind(this));
|
||||
|
||||
this._index = index;
|
||||
this._delegate = this; // needed for DND
|
||||
|
||||
this._windowPreviews = new Map();
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
this._workspace = workspaceManager.get_workspace_by_index(index);
|
||||
|
||||
this._windowAddedId = this._workspace.connect('window-added',
|
||||
(ws, window) => {
|
||||
this._addWindow(window);
|
||||
});
|
||||
this._windowRemovedId = this._workspace.connect('window-removed',
|
||||
(ws, window) => {
|
||||
this._removeWindow(window);
|
||||
});
|
||||
this._restackedId = global.display.connect('restacked',
|
||||
this._onRestacked.bind(this));
|
||||
|
||||
this._workspace.list_windows().forEach(w => this._addWindow(w));
|
||||
this._onRestacked();
|
||||
}
|
||||
|
||||
acceptDrop(source) {
|
||||
if (!source.metaWindow)
|
||||
return false;
|
||||
|
||||
this._moveWindow(source.metaWindow);
|
||||
return true;
|
||||
}
|
||||
|
||||
handleDragOver(source) {
|
||||
if (source.metaWindow)
|
||||
return DND.DragMotionResult.MOVE_DROP;
|
||||
else
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
}
|
||||
|
||||
_addWindow(window) {
|
||||
if (this._windowPreviews.has(window))
|
||||
return;
|
||||
|
||||
let preview = new WindowPreview(window);
|
||||
preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
|
||||
this._windowPreviews.set(window, preview);
|
||||
this.child.add_child(preview);
|
||||
}
|
||||
|
||||
_removeWindow(window) {
|
||||
let preview = this._windowPreviews.get(window);
|
||||
if (!preview)
|
||||
return;
|
||||
|
||||
this._windowPreviews.delete(window);
|
||||
preview.destroy();
|
||||
}
|
||||
|
||||
_onRestacked() {
|
||||
let lastPreview = null;
|
||||
let windows = global.get_window_actors().map(a => a.meta_window);
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let preview = this._windowPreviews.get(windows[i]);
|
||||
if (!preview)
|
||||
continue;
|
||||
|
||||
this.child.set_child_above_sibling(preview, lastPreview);
|
||||
lastPreview = preview;
|
||||
}
|
||||
}
|
||||
|
||||
_moveWindow(window) {
|
||||
let monitorIndex = Main.layoutManager.findIndexForActor(this);
|
||||
if (monitorIndex !== window.get_monitor())
|
||||
window.move_to_monitor(monitorIndex);
|
||||
window.change_workspace_by_index(this._index, false);
|
||||
}
|
||||
|
||||
on_clicked() {
|
||||
let ws = global.workspace_manager.get_workspace_by_index(this._index);
|
||||
if (ws)
|
||||
ws.activate(global.get_current_time());
|
||||
}
|
||||
|
||||
_syncTooltip() {
|
||||
if (this.hover) {
|
||||
this._tooltip.set({
|
||||
text: Meta.prefs_get_workspace_name(this._index),
|
||||
visible: true,
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
const [stageX, stageY] = this.get_transformed_position();
|
||||
const thumbWidth = this.allocation.get_width();
|
||||
const thumbHeight = this.allocation.get_height();
|
||||
const tipWidth = this._tooltip.width;
|
||||
const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
|
||||
const monitor = Main.layoutManager.findMonitorForActor(this);
|
||||
const x = Math.clamp(
|
||||
stageX + xOffset,
|
||||
monitor.x,
|
||||
monitor.x + monitor.width - tipWidth);
|
||||
const y = stageY + thumbHeight + TOOLTIP_OFFSET;
|
||||
this._tooltip.set_position(x, y);
|
||||
}
|
||||
|
||||
this._tooltip.ease({
|
||||
opacity: this.hover ? 255 : 0,
|
||||
duration: TOOLTIP_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => (this._tooltip.visible = this.hover),
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
this._tooltip.destroy();
|
||||
|
||||
this._workspace.disconnect(this._windowAddedId);
|
||||
this._workspace.disconnect(this._windowRemovedId);
|
||||
global.display.disconnect(this._restackedId);
|
||||
}
|
||||
});
|
||||
|
||||
let WorkspaceIndicator = GObject.registerClass(
|
||||
class WorkspaceIndicator extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(0.0, _('Workspace Indicator'));
|
||||
|
||||
let container = new St.Widget({
|
||||
layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
});
|
||||
this.add_actor(container);
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
||||
this._statusLabel = new St.Label({
|
||||
style_class: 'panel-workspace-indicator',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
text: this._labelText(),
|
||||
});
|
||||
|
||||
container.add_actor(this._statusLabel);
|
||||
|
||||
this._thumbnailsBox = new St.BoxLayout({
|
||||
style_class: 'panel-workspace-indicator-box',
|
||||
y_expand: true,
|
||||
reactive: true,
|
||||
});
|
||||
|
||||
container.add_actor(this._thumbnailsBox);
|
||||
|
||||
this._workspacesItems = [];
|
||||
this._workspaceSection = new PopupMenu.PopupMenuSection();
|
||||
this.menu.addMenuItem(this._workspaceSection);
|
||||
|
||||
this._workspaceManagerSignals = [
|
||||
workspaceManager.connect_after('notify::n-workspaces',
|
||||
this._nWorkspacesChanged.bind(this)),
|
||||
workspaceManager.connect_after('workspace-switched',
|
||||
this._onWorkspaceSwitched.bind(this)),
|
||||
workspaceManager.connect('notify::layout-rows',
|
||||
this._onWorkspaceOrientationChanged.bind(this)),
|
||||
];
|
||||
|
||||
this.connect('scroll-event', this._onScrollEvent.bind(this));
|
||||
this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this));
|
||||
this._createWorkspacesSection();
|
||||
this._updateThumbnails();
|
||||
this._onWorkspaceOrientationChanged();
|
||||
//styling
|
||||
this.statusLabel.add_style_class_name('panel-workspace-indicator');
|
||||
|
||||
this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
|
||||
this._settingsChangedId = this._settings.connect(
|
||||
`changed::${WORKSPACE_KEY}`,
|
||||
this._updateMenuLabels.bind(this));
|
||||
}
|
||||
this._settingsChangedId = this._settings.connect('changed::' + WORKSPACE_KEY, Lang.bind(this, this._createWorkspacesSection));
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
for (let i = 0; i < this._workspaceManagerSignals.length; i++)
|
||||
global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
|
||||
destroy: function() {
|
||||
for (let i = 0; i < this._screenSignals.length; i++)
|
||||
global.screen.disconnect(this._screenSignals[i]);
|
||||
|
||||
if (this._settingsChangedId) {
|
||||
this._settings.disconnect(this._settingsChangedId);
|
||||
this._settingsChangedId = 0;
|
||||
}
|
||||
|
||||
Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
||||
this.parent();
|
||||
},
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
_updateIndicator: function() {
|
||||
this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
|
||||
this._currentWorkspace = global.screen.get_active_workspace().index();
|
||||
this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
|
||||
|
||||
_onWorkspaceOrientationChanged() {
|
||||
let vertical = global.workspace_manager.layout_rows === -1;
|
||||
this.reactive = vertical;
|
||||
this.statusLabel.set_text(this._labelText());
|
||||
},
|
||||
|
||||
this._statusLabel.visible = vertical;
|
||||
this._thumbnailsBox.visible = !vertical;
|
||||
_labelText : function(workspaceIndex) {
|
||||
if(workspaceIndex == undefined) {
|
||||
workspaceIndex = this._currentWorkspace;
|
||||
return (workspaceIndex + 1).toString();
|
||||
}
|
||||
return Meta.prefs_get_workspace_name(workspaceIndex);
|
||||
},
|
||||
|
||||
// Disable offscreen-redirect when showing the workspace switcher
|
||||
// so that clip-to-allocation works
|
||||
Main.panel.set_offscreen_redirect(vertical
|
||||
? Clutter.OffscreenRedirect.ALWAYS
|
||||
: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
|
||||
}
|
||||
_createWorkspacesSection : function() {
|
||||
this._workspaceSection.removeAll();
|
||||
this.workspacesItems = [];
|
||||
this._currentWorkspace = global.screen.get_active_workspace().index();
|
||||
|
||||
_onWorkspaceSwitched() {
|
||||
this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
|
||||
let i = 0;
|
||||
for(; i < global.screen.n_workspaces; i++) {
|
||||
this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
|
||||
this._workspaceSection.addMenuItem(this.workspacesItems[i]);
|
||||
this.workspacesItems[i].workspaceId = i;
|
||||
this.workspacesItems[i].label_actor = this.statusLabel;
|
||||
let self = this;
|
||||
this.workspacesItems[i].connect('activate', Lang.bind(this, function(actor, event) {
|
||||
this._activate(actor.workspaceId);
|
||||
}));
|
||||
|
||||
this._updateMenuOrnament();
|
||||
this._updateActiveThumbnail();
|
||||
if (i == this._currentWorkspace)
|
||||
this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT);
|
||||
}
|
||||
|
||||
this._statusLabel.set_text(this._labelText());
|
||||
}
|
||||
this.statusLabel.set_text(this._labelText());
|
||||
},
|
||||
|
||||
_nWorkspacesChanged() {
|
||||
this._createWorkspacesSection();
|
||||
this._updateThumbnails();
|
||||
}
|
||||
_activate : function (index) {
|
||||
if(index >= 0 && index < global.screen.n_workspaces) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(index);
|
||||
metaWorkspace.activate(global.get_current_time());
|
||||
}
|
||||
},
|
||||
|
||||
_updateMenuOrnament() {
|
||||
for (let i = 0; i < this._workspacesItems.length; i++) {
|
||||
this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
||||
? PopupMenu.Ornament.DOT
|
||||
: PopupMenu.Ornament.NONE);
|
||||
}
|
||||
}
|
||||
_onScrollEvent : function(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
let diff = 0;
|
||||
if (direction == Clutter.ScrollDirection.DOWN) {
|
||||
diff = 1;
|
||||
} else if (direction == Clutter.ScrollDirection.UP) {
|
||||
diff = -1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
_updateActiveThumbnail() {
|
||||
let thumbs = this._thumbnailsBox.get_children();
|
||||
for (let i = 0; i < thumbs.length; i++) {
|
||||
if (i === this._currentWorkspace)
|
||||
thumbs[i].add_style_class_name('active');
|
||||
else
|
||||
thumbs[i].remove_style_class_name('active');
|
||||
}
|
||||
}
|
||||
|
||||
_labelText(workspaceIndex) {
|
||||
if (workspaceIndex === undefined) {
|
||||
workspaceIndex = this._currentWorkspace;
|
||||
return (workspaceIndex + 1).toString();
|
||||
}
|
||||
return Meta.prefs_get_workspace_name(workspaceIndex);
|
||||
}
|
||||
|
||||
_updateMenuLabels() {
|
||||
for (let i = 0; i < this._workspacesItems.length; i++)
|
||||
this._workspacesItems[i].label.text = this._labelText(i);
|
||||
}
|
||||
|
||||
_createWorkspacesSection() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._workspaceSection.removeAll();
|
||||
this._workspacesItems = [];
|
||||
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
||||
|
||||
let i = 0;
|
||||
for (; i < workspaceManager.n_workspaces; i++) {
|
||||
this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
|
||||
this._workspaceSection.addMenuItem(this._workspacesItems[i]);
|
||||
this._workspacesItems[i].workspaceId = i;
|
||||
this._workspacesItems[i].label_actor = this._statusLabel;
|
||||
this._workspacesItems[i].connect('activate', (actor, _event) => {
|
||||
this._activate(actor.workspaceId);
|
||||
});
|
||||
|
||||
if (i === this._currentWorkspace)
|
||||
this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT);
|
||||
}
|
||||
|
||||
this._statusLabel.set_text(this._labelText());
|
||||
}
|
||||
|
||||
_updateThumbnails() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
this._thumbnailsBox.destroy_all_children();
|
||||
|
||||
for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
||||
let thumb = new WorkspaceThumbnail(i);
|
||||
this._thumbnailsBox.add_actor(thumb);
|
||||
}
|
||||
this._updateActiveThumbnail();
|
||||
}
|
||||
|
||||
_activate(index) {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
if (index >= 0 && index < workspaceManager.n_workspaces) {
|
||||
let metaWorkspace = workspaceManager.get_workspace_by_index(index);
|
||||
metaWorkspace.activate(global.get_current_time());
|
||||
}
|
||||
}
|
||||
|
||||
_onScrollEvent(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
let diff = 0;
|
||||
if (direction === Clutter.ScrollDirection.DOWN)
|
||||
diff = 1;
|
||||
else if (direction === Clutter.ScrollDirection.UP)
|
||||
diff = -1;
|
||||
else
|
||||
return;
|
||||
|
||||
|
||||
let newIndex = global.workspace_manager.get_active_workspace_index() + diff;
|
||||
this._activate(newIndex);
|
||||
}
|
||||
let newIndex = global.screen.get_active_workspace().index() + diff;
|
||||
this._activate(newIndex);
|
||||
},
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
function init(meta) {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
let _indicator;
|
||||
|
||||
function enable() {
|
||||
_indicator = new WorkspaceIndicator();
|
||||
_indicator = new WorkspaceIndicator;
|
||||
Main.panel.addToStatusArea('workspace-indicator', _indicator);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
extension_data += configure_file(
|
||||
input: metadata_name + '.in',
|
||||
output: metadata_name,
|
||||
configuration: metadata_conf
|
||||
)
|
||||
|
||||
extension_sources += files('prefs.js')
|
||||
@@ -1,218 +1,219 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
/* exported init buildPrefsWidget */
|
||||
|
||||
const { Gio, GLib, GObject, Gtk, Pango } = imports.gi;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
const N_ = e => e;
|
||||
const N_ = function(e) { return e };
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
||||
const WORKSPACE_KEY = 'workspace-names';
|
||||
|
||||
const WorkspaceSettingsWidget = GObject.registerClass(
|
||||
class WorkspaceSettingsWidget extends Gtk.ScrolledWindow {
|
||||
_init() {
|
||||
super._init({
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
});
|
||||
const WorkspaceNameModel = new GObject.Class({
|
||||
Name: 'WorkspaceIndicator.WorkspaceNameModel',
|
||||
GTypeName: 'WorkspaceNameModel',
|
||||
Extends: Gtk.ListStore,
|
||||
|
||||
const box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
halign: Gtk.Align.CENTER,
|
||||
spacing: 12,
|
||||
margin_top: 36,
|
||||
margin_bottom: 36,
|
||||
margin_start: 36,
|
||||
margin_end: 36,
|
||||
});
|
||||
this.set_child(box);
|
||||
Columns: {
|
||||
LABEL: 0,
|
||||
},
|
||||
|
||||
box.append(new Gtk.Label({
|
||||
label: '<b>%s</b>'.format(_('Workspace Names')),
|
||||
use_markup: true,
|
||||
halign: Gtk.Align.START,
|
||||
}));
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this.set_column_types([GObject.TYPE_STRING]);
|
||||
|
||||
this._list = new Gtk.ListBox({
|
||||
selection_mode: Gtk.SelectionMode.NONE,
|
||||
valign: Gtk.Align.START,
|
||||
show_separators: true,
|
||||
});
|
||||
this._list.connect('row-activated', (l, row) => row.edit());
|
||||
box.append(this._list);
|
||||
this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
|
||||
//this._settings.connect('changed::workspace-names', Lang.bind(this, this._reloadFromSettings));
|
||||
|
||||
const context = this._list.get_style_context();
|
||||
const cssProvider = new Gtk.CssProvider();
|
||||
cssProvider.load_from_data(
|
||||
'list { min-width: 25em; }');
|
||||
this._reloadFromSettings();
|
||||
|
||||
context.add_provider(cssProvider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context.add_class('frame');
|
||||
// overriding class closure doesn't work, because GtkTreeModel
|
||||
// plays tricks with marshallers and class closures
|
||||
this.connect('row-changed', Lang.bind(this, this._onRowChanged));
|
||||
this.connect('row-inserted', Lang.bind(this, this._onRowInserted));
|
||||
this.connect('row-deleted', Lang.bind(this, this._onRowDeleted));
|
||||
},
|
||||
|
||||
this._list.append(new NewWorkspaceRow());
|
||||
_reloadFromSettings: function() {
|
||||
if (this._preventChanges)
|
||||
return;
|
||||
this._preventChanges = true;
|
||||
|
||||
this._actionGroup = new Gio.SimpleActionGroup();
|
||||
this._list.insert_action_group('workspaces', this._actionGroup);
|
||||
let newNames = this._settings.get_strv(WORKSPACE_KEY);
|
||||
|
||||
let action;
|
||||
action = new Gio.SimpleAction({ name: 'add' });
|
||||
action.connect('activate', () => {
|
||||
const names = this._settings.get_strv(WORKSPACE_KEY);
|
||||
this._settings.set_strv(WORKSPACE_KEY, [
|
||||
...names,
|
||||
_('Workspace %d').format(names.length + 1),
|
||||
]);
|
||||
});
|
||||
this._actionGroup.add_action(action);
|
||||
let i = 0;
|
||||
let [ok, iter] = this.get_iter_first();
|
||||
while (ok && i < newNames.length) {
|
||||
this.set(iter, [this.Columns.LABEL], [newNames[i]]);
|
||||
|
||||
action = new Gio.SimpleAction({
|
||||
name: 'remove',
|
||||
parameter_type: new GLib.VariantType('s'),
|
||||
});
|
||||
action.connect('activate', (a, param) => {
|
||||
const removed = param.deepUnpack();
|
||||
this._settings.set_strv(WORKSPACE_KEY,
|
||||
this._settings.get_strv(WORKSPACE_KEY)
|
||||
.filter(name => name !== removed));
|
||||
});
|
||||
this._actionGroup.add_action(action);
|
||||
ok = this.iter_next(iter);
|
||||
i++;
|
||||
}
|
||||
|
||||
action = new Gio.SimpleAction({ name: 'update' });
|
||||
action.connect('activate', () => {
|
||||
const names = this._getWorkspaceRows().map(row => row.name);
|
||||
this._settings.set_strv(WORKSPACE_KEY, names);
|
||||
});
|
||||
this._actionGroup.add_action(action);
|
||||
while (ok)
|
||||
ok = this.remove(iter);
|
||||
|
||||
this._settings = new Gio.Settings({
|
||||
schema_id: WORKSPACE_SCHEMA,
|
||||
});
|
||||
this._settings.connect(`changed::${WORKSPACE_KEY}`,
|
||||
this._sync.bind(this));
|
||||
this._sync();
|
||||
}
|
||||
for ( ; i < newNames.length; i++) {
|
||||
iter = this.append();
|
||||
this.set(iter, [this.Columns.LABEL], [newNames[i]]);
|
||||
}
|
||||
|
||||
_getWorkspaceRows() {
|
||||
return [...this._list].filter(row => row.name);
|
||||
}
|
||||
this._preventChanges = false;
|
||||
},
|
||||
|
||||
_sync() {
|
||||
const rows = this._getWorkspaceRows();
|
||||
_onRowChanged: function(self, path, iter) {
|
||||
if (this._preventChanges)
|
||||
return;
|
||||
this._preventChanges = true;
|
||||
|
||||
const oldNames = rows.map(row => row.name);
|
||||
const newNames = this._settings.get_strv(WORKSPACE_KEY);
|
||||
let index = path.get_indices()[0];
|
||||
let names = this._settings.get_strv(WORKSPACE_KEY);
|
||||
|
||||
const removed = oldNames.filter(n => !newNames.includes(n));
|
||||
const added = newNames.filter(n => !oldNames.includes(n));
|
||||
if (index >= names.length) {
|
||||
// fill with blanks
|
||||
for (let i = names.length; i <= index; i++)
|
||||
names[i] = '';
|
||||
}
|
||||
|
||||
removed.forEach(n => this._list.remove(rows.find(r => r.name === n)));
|
||||
added.forEach(n => {
|
||||
this._list.insert(new WorkspaceRow(n), newNames.indexOf(n));
|
||||
});
|
||||
}
|
||||
names[index] = this.get_value(iter, this.Columns.LABEL);
|
||||
|
||||
this._settings.set_strv(WORKSPACE_KEY, names);
|
||||
|
||||
this._preventChanges = false;
|
||||
},
|
||||
|
||||
_onRowInserted: function(self, path, iter) {
|
||||
if (this._preventChanges)
|
||||
return;
|
||||
this._preventChanges = true;
|
||||
|
||||
let index = path.get_indices()[0];
|
||||
let names = this._settings.get_strv(WORKSPACE_KEY);
|
||||
let label = this.get_value(iter, this.Columns.LABEL) || '';
|
||||
names.splice(index, 0, label);
|
||||
|
||||
this._settings.set_strv(WORKSPACE_KEY, names);
|
||||
|
||||
this._preventChanges = false;
|
||||
},
|
||||
|
||||
_onRowDeleted: function(self, path) {
|
||||
if (this._preventChanges)
|
||||
return;
|
||||
this._preventChanges = true;
|
||||
|
||||
let index = path.get_indices()[0];
|
||||
let names = this._settings.get_strv(WORKSPACE_KEY);
|
||||
|
||||
if (index >= names.length)
|
||||
return;
|
||||
|
||||
names.splice(index, 1);
|
||||
|
||||
// compact the array
|
||||
for (let i = names.length -1; i >= 0 && !names[i]; i++)
|
||||
names.pop();
|
||||
|
||||
this._settings.set_strv(WORKSPACE_KEY, names);
|
||||
|
||||
this._preventChanges = false;
|
||||
},
|
||||
});
|
||||
|
||||
const WorkspaceRow = GObject.registerClass(
|
||||
class WorkspaceRow extends Gtk.ListBoxRow {
|
||||
_init(name) {
|
||||
super._init({ name });
|
||||
const WorkspaceSettingsWidget = new GObject.Class({
|
||||
Name: 'WorkspaceIndicator.WorkspaceSettingsWidget',
|
||||
GTypeName: 'WorkspaceSettingsWidget',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
const controller = new Gtk.ShortcutController();
|
||||
controller.add_shortcut(new Gtk.Shortcut({
|
||||
trigger: Gtk.ShortcutTrigger.parse_string('Escape'),
|
||||
action: Gtk.CallbackAction.new(this._stopEdit.bind(this)),
|
||||
}));
|
||||
this.add_controller(controller);
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this.margin = 12;
|
||||
this.orientation = Gtk.Orientation.VERTICAL;
|
||||
|
||||
const box = new Gtk.Box({
|
||||
spacing: 12,
|
||||
margin_top: 6,
|
||||
margin_bottom: 6,
|
||||
margin_start: 6,
|
||||
margin_end: 6,
|
||||
});
|
||||
this.add(new Gtk.Label({ label: '<b>' + _("Workspace Names") + '</b>',
|
||||
use_markup: true, margin_bottom: 6,
|
||||
hexpand: true, halign: Gtk.Align.START }));
|
||||
|
||||
const label = new Gtk.Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
max_width_chars: 25,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
});
|
||||
this.bind_property('name', label, 'label',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
box.append(label);
|
||||
let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN });
|
||||
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.add(scrolled);
|
||||
|
||||
const button = new Gtk.Button({
|
||||
action_name: 'workspaces.remove',
|
||||
action_target: new GLib.Variant('s', name),
|
||||
icon_name: 'edit-delete-symbolic',
|
||||
});
|
||||
box.append(button);
|
||||
this._store = new WorkspaceNameModel();
|
||||
this._treeView = new Gtk.TreeView({ model: this._store,
|
||||
headers_visible: false,
|
||||
reorderable: true,
|
||||
hexpand: true,
|
||||
vexpand: true
|
||||
});
|
||||
|
||||
this._entry = new Gtk.Entry({
|
||||
max_width_chars: 25,
|
||||
});
|
||||
let column = new Gtk.TreeViewColumn({ title: _("Name") });
|
||||
let renderer = new Gtk.CellRendererText({ editable: true });
|
||||
renderer.connect('edited', Lang.bind(this, this._cellEdited));
|
||||
column.pack_start(renderer, true);
|
||||
column.add_attribute(renderer, 'text', this._store.Columns.LABEL);
|
||||
this._treeView.append_column(column);
|
||||
|
||||
this._stack = new Gtk.Stack();
|
||||
this._stack.add_named(box, 'display');
|
||||
this._stack.add_named(this._entry, 'edit');
|
||||
this.child = this._stack;
|
||||
scrolled.add(this._treeView);
|
||||
|
||||
this._entry.connect('activate', () => {
|
||||
this.name = this._entry.text;
|
||||
this._stopEdit();
|
||||
});
|
||||
this._entry.connect('notify::has-focus', () => {
|
||||
if (this._entry.has_focus)
|
||||
return;
|
||||
this._stopEdit();
|
||||
});
|
||||
let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR });
|
||||
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
|
||||
this.connect('notify::name', () => {
|
||||
button.action_target = new GLib.Variant('s', this.name);
|
||||
this.activate_action('workspaces.update', null);
|
||||
});
|
||||
}
|
||||
let newButton = new Gtk.ToolButton({ icon_name: 'list-add-symbolic' });
|
||||
newButton.connect('clicked', Lang.bind(this, this._newClicked));
|
||||
toolbar.add(newButton);
|
||||
|
||||
edit() {
|
||||
this._entry.text = this.name;
|
||||
this._entry.grab_focus();
|
||||
this._stack.visible_child_name = 'edit';
|
||||
}
|
||||
let delButton = new Gtk.ToolButton({ icon_name: 'list-remove-symbolic' });
|
||||
delButton.connect('clicked', Lang.bind(this, this._delClicked));
|
||||
toolbar.add(delButton);
|
||||
|
||||
_stopEdit() {
|
||||
this.grab_focus();
|
||||
this._stack.visible_child_name = 'display';
|
||||
}
|
||||
});
|
||||
let selection = this._treeView.get_selection();
|
||||
selection.connect('changed',
|
||||
function() {
|
||||
delButton.sensitive = selection.count_selected_rows() > 0;
|
||||
});
|
||||
delButton.sensitive = selection.count_selected_rows() > 0;
|
||||
|
||||
const NewWorkspaceRow = GObject.registerClass(
|
||||
class NewWorkspaceRow extends Gtk.ListBoxRow {
|
||||
_init() {
|
||||
super._init({
|
||||
action_name: 'workspaces.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 Workspace')]);
|
||||
this.add(toolbar);
|
||||
},
|
||||
|
||||
_cellEdited: function(renderer, path, new_text) {
|
||||
let [ok, iter] = this._store.get_iter_from_string(path);
|
||||
|
||||
if (ok)
|
||||
this._store.set(iter, [this._store.Columns.LABEL], [new_text]);
|
||||
},
|
||||
|
||||
_newClicked: function() {
|
||||
let iter = this._store.append();
|
||||
let index = this._store.get_path(iter).get_indices()[0];
|
||||
|
||||
let label = _("Workspace %d").format(index + 1);
|
||||
this._store.set(iter, [this._store.Columns.LABEL], [label]);
|
||||
},
|
||||
|
||||
_delClicked: function() {
|
||||
let [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
|
||||
if (any)
|
||||
this._store.remove(iter);
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
return new WorkspaceSettingsWidget();
|
||||
let widget = new WorkspaceSettingsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
.panel-workspace-indicator {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.panel-workspace-indicator-box {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.panel-workspace-indicator-box .workspace {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.panel-workspace-indicator,
|
||||
.panel-workspace-indicator-box .workspace {
|
||||
background-color: rgba(200, 200, 200, .5);
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.panel-workspace-indicator,
|
||||
.panel-workspace-indicator-box .workspace.active {
|
||||
background-color: rgba(200, 200, 200, .5);
|
||||
}
|
||||
|
||||
.panel-workspace-indicator-box .workspace {
|
||||
background-color: rgba(200, 200, 200, .3);
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.panel-workspace-indicator-box .workspace:first-child {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.workspace-indicator-window-preview {
|
||||
background-color: #252525;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.workspace-indicator-window-preview.active {
|
||||
background-color: #353535;
|
||||
border: 2px solid #ccc;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ and will be picked automatically at next login.
|
||||
<homepage rdf:resource="https://wiki.gnome.org/Projects/GnomeShell/Extensions" />
|
||||
<mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/gnome-shell-list" />
|
||||
<download-page rdf:resource="http://download.gnome.org/sources/gnome-shell-extensions/" />
|
||||
<bug-database rdf:resource="https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues" />
|
||||
<bug-database rdf:resource="http://bugzilla.gnome.org/browse.cgi?product=gnome-shell&component=extensions" />
|
||||
|
||||
<category rdf:resource="http://api.gnome.org/doap-extensions#core" />
|
||||
<programming-language>JavaScript</programming-language>
|
||||
@@ -34,10 +34,4 @@ and will be picked automatically at next login.
|
||||
<gnome:userid>fmuellner</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Marge Bot</foaf:name>
|
||||
<gnome:userid>marge-bot</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
</Project>
|
||||
|
||||
12
include.mk
Normal file
12
include.mk
Normal file
@@ -0,0 +1,12 @@
|
||||
extensionurl = http://git.gnome.org/gnome-shell-extensions
|
||||
|
||||
# Change these to modify how installation is performed
|
||||
topextensiondir = $(datadir)/gnome-shell/extensions
|
||||
extensionbase = @gnome-shell-extensions.gcampax.github.com
|
||||
|
||||
gschemabase = org.gnome.shell.extensions
|
||||
|
||||
uuid = $(EXTENSION_ID)$(extensionbase)
|
||||
gschemaname = $(gschemabase).$(EXTENSION_ID)
|
||||
|
||||
extensiondir = $(topextensiondir)/$(uuid)
|
||||
93
lib/convenience.js
Normal file
93
lib/convenience.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the GNOME nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
/**
|
||||
* initTranslations:
|
||||
* @domain: (optional): the gettext domain to use
|
||||
*
|
||||
* Initialize Gettext to load translations from extensionsdir/locale.
|
||||
* If @domain is not provided, it will be taken from metadata['gettext-domain']
|
||||
*/
|
||||
function initTranslations(domain) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
domain = domain || extension.metadata['gettext-domain'];
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the locale files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell
|
||||
let localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null))
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettings:
|
||||
* @schema: (optional): the GSettings schema id
|
||||
*
|
||||
* Builds and return a GSettings schema for @schema, using schema files
|
||||
* in extensionsdir/schemas. If @schema is not provided, it is taken from
|
||||
* metadata['settings-schema'].
|
||||
*/
|
||||
function getSettings(schema) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
schema = schema || extension.metadata['settings-schema'];
|
||||
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null))
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
else
|
||||
schemaSource = GioSSS.get_default();
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension '
|
||||
+ extension.metadata.uuid + '. Please check your installation.');
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
---
|
||||
# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
|
||||
env:
|
||||
es6: true
|
||||
extends: 'eslint:recommended'
|
||||
rules:
|
||||
array-bracket-newline:
|
||||
- error
|
||||
- consistent
|
||||
array-bracket-spacing:
|
||||
- error
|
||||
- never
|
||||
array-callback-return: error
|
||||
arrow-parens:
|
||||
- error
|
||||
- as-needed
|
||||
arrow-spacing: error
|
||||
block-scoped-var: error
|
||||
block-spacing: error
|
||||
brace-style: error
|
||||
# Waiting for this to have matured a bit in eslint
|
||||
# camelcase:
|
||||
# - error
|
||||
# - properties: never
|
||||
# allow: [^vfunc_, ^on_, _instance_init]
|
||||
comma-dangle:
|
||||
- error
|
||||
- arrays: always-multiline
|
||||
objects: always-multiline
|
||||
functions: never
|
||||
comma-spacing:
|
||||
- error
|
||||
- before: false
|
||||
after: true
|
||||
comma-style:
|
||||
- error
|
||||
- last
|
||||
computed-property-spacing: error
|
||||
curly:
|
||||
- error
|
||||
- multi-or-nest
|
||||
- consistent
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
eol-last: error
|
||||
eqeqeq: error
|
||||
func-call-spacing: error
|
||||
func-name-matching: error
|
||||
func-style:
|
||||
- error
|
||||
- declaration
|
||||
- allowArrowFunctions: true
|
||||
indent:
|
||||
- error
|
||||
- 4
|
||||
- ignoredNodes:
|
||||
# Allow not indenting the body of GObject.registerClass, since in the
|
||||
# future it's intended to be a decorator
|
||||
- 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child'
|
||||
# Allow dedenting chained member expressions
|
||||
MemberExpression: 'off'
|
||||
key-spacing:
|
||||
- error
|
||||
- beforeColon: false
|
||||
afterColon: true
|
||||
keyword-spacing:
|
||||
- error
|
||||
- before: true
|
||||
after: true
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
lines-between-class-members: error
|
||||
max-nested-callbacks: error
|
||||
max-statements-per-line: error
|
||||
new-parens: error
|
||||
no-array-constructor: error
|
||||
no-await-in-loop: error
|
||||
no-caller: error
|
||||
no-constant-condition:
|
||||
- error
|
||||
- checkLoops: false
|
||||
no-div-regex: error
|
||||
no-empty:
|
||||
- error
|
||||
- allowEmptyCatch: true
|
||||
no-extra-bind: error
|
||||
no-extra-parens:
|
||||
- error
|
||||
- all
|
||||
- conditionalAssign: false
|
||||
nestedBinaryExpressions: false
|
||||
returnAssign: false
|
||||
no-implicit-coercion:
|
||||
- error
|
||||
- allow:
|
||||
- '!!'
|
||||
no-invalid-this: error
|
||||
no-iterator: error
|
||||
no-label-var: error
|
||||
no-lonely-if: error
|
||||
no-loop-func: error
|
||||
no-nested-ternary: error
|
||||
no-new-object: error
|
||||
no-new-wrappers: error
|
||||
no-octal-escape: error
|
||||
no-proto: error
|
||||
no-prototype-builtins: 'off'
|
||||
no-restricted-properties:
|
||||
- error
|
||||
- object: Lang
|
||||
property: copyProperties
|
||||
message: Use Object.assign()
|
||||
- object: Lang
|
||||
property: bind
|
||||
message: Use arrow notation or Function.prototype.bind()
|
||||
- object: Lang
|
||||
property: Class
|
||||
message: Use ES6 classes
|
||||
no-restricted-syntax:
|
||||
- error
|
||||
- selector: >-
|
||||
MethodDefinition[key.name="_init"] >
|
||||
FunctionExpression[params.length=1] >
|
||||
BlockStatement[body.length=1]
|
||||
CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] >
|
||||
Identifier:first-child
|
||||
message: _init() that only calls super._init() is unnecessary
|
||||
- selector: >-
|
||||
MethodDefinition[key.name="_init"] >
|
||||
FunctionExpression[params.length=0] >
|
||||
BlockStatement[body.length=1]
|
||||
CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"]
|
||||
message: _init() that only calls super._init() is unnecessary
|
||||
- selector: BinaryExpression[operator="instanceof"][right.name="Array"]
|
||||
message: Use Array.isArray()
|
||||
no-return-assign: error
|
||||
no-return-await: error
|
||||
no-self-compare: error
|
||||
no-shadow: error
|
||||
no-shadow-restricted-names: error
|
||||
no-spaced-func: error
|
||||
no-tabs: error
|
||||
no-template-curly-in-string: error
|
||||
no-throw-literal: error
|
||||
no-trailing-spaces: error
|
||||
no-undef-init: error
|
||||
no-unneeded-ternary: error
|
||||
no-unused-expressions: error
|
||||
no-unused-vars:
|
||||
- error
|
||||
# Vars use a suffix _ instead of a prefix because of file-scope private vars
|
||||
- varsIgnorePattern: (^unused|_$)
|
||||
argsIgnorePattern: ^(unused|_)
|
||||
no-useless-call: error
|
||||
no-useless-computed-key: error
|
||||
no-useless-concat: error
|
||||
no-useless-constructor: error
|
||||
no-useless-rename: error
|
||||
no-useless-return: error
|
||||
no-whitespace-before-property: error
|
||||
no-with: error
|
||||
nonblock-statement-body-position:
|
||||
- error
|
||||
- below
|
||||
object-curly-newline:
|
||||
- error
|
||||
- consistent: true
|
||||
object-curly-spacing: error
|
||||
object-shorthand: error
|
||||
operator-assignment: error
|
||||
operator-linebreak: error
|
||||
padded-blocks:
|
||||
- error
|
||||
- never
|
||||
# These may be a bit controversial, we can try them out and enable them later
|
||||
# prefer-const: error
|
||||
# prefer-destructuring: error
|
||||
prefer-numeric-literals: error
|
||||
prefer-promise-reject-errors: error
|
||||
prefer-rest-params: error
|
||||
prefer-spread: error
|
||||
prefer-template: error
|
||||
quotes:
|
||||
- error
|
||||
- single
|
||||
- avoidEscape: true
|
||||
require-await: error
|
||||
rest-spread-spacing: error
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
semi-spacing:
|
||||
- error
|
||||
- before: false
|
||||
after: true
|
||||
semi-style: error
|
||||
space-before-blocks: error
|
||||
space-before-function-paren:
|
||||
- error
|
||||
- named: never
|
||||
# for `function ()` and `async () =>`, preserve space around keywords
|
||||
anonymous: always
|
||||
asyncArrow: always
|
||||
space-in-parens: error
|
||||
space-infix-ops:
|
||||
- error
|
||||
- int32Hint: false
|
||||
space-unary-ops: error
|
||||
spaced-comment: error
|
||||
switch-colon-spacing: error
|
||||
symbol-description: error
|
||||
template-curly-spacing: error
|
||||
template-tag-spacing: error
|
||||
unicode-bom: error
|
||||
valid-jsdoc:
|
||||
- error
|
||||
- requireReturn: false
|
||||
wrap-iife:
|
||||
- error
|
||||
- inside
|
||||
yield-star-spacing: error
|
||||
yoda: error
|
||||
globals:
|
||||
ARGV: readonly
|
||||
Debugger: readonly
|
||||
GIRepositoryGType: readonly
|
||||
globalThis: readonly
|
||||
imports: readonly
|
||||
Intl: readonly
|
||||
log: readonly
|
||||
logError: readonly
|
||||
print: readonly
|
||||
printerr: readonly
|
||||
parserOptions:
|
||||
ecmaVersion: 2020
|
||||
@@ -1,11 +0,0 @@
|
||||
rules:
|
||||
camelcase:
|
||||
- error
|
||||
- properties: never
|
||||
allow: [^vfunc_, ^on_]
|
||||
object-curly-spacing:
|
||||
- error
|
||||
- always
|
||||
prefer-arrow-callback: error
|
||||
globals:
|
||||
global: readonly
|
||||
1
m4/.gitignore
vendored
Normal file
1
m4/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
intltool.m4
|
||||
95
meson.build
95
meson.build
@@ -1,95 +0,0 @@
|
||||
project('gnome-shell-extensions',
|
||||
version: '40.beta',
|
||||
meson_version: '>= 0.44.0',
|
||||
license: 'GPL2+'
|
||||
)
|
||||
|
||||
gettext_domain = meson.project_name()
|
||||
|
||||
fs = import('fs')
|
||||
gnome = import('gnome')
|
||||
i18n = import('i18n')
|
||||
|
||||
datadir = get_option('datadir')
|
||||
|
||||
shelldir = join_paths(datadir, 'gnome-shell')
|
||||
extensiondir = join_paths(shelldir, 'extensions')
|
||||
modedir = join_paths(shelldir, 'modes')
|
||||
themedir = join_paths(shelldir, 'theme')
|
||||
|
||||
schemadir = join_paths(datadir, 'glib-2.0', 'schemas')
|
||||
sessiondir = join_paths(datadir, 'gnome-session', 'sessions')
|
||||
xsessiondir = join_paths(datadir, 'xsessions')
|
||||
|
||||
ver_arr = meson.project_version().split('.')
|
||||
if ver_arr[1].version_compare('>=0')
|
||||
shell_version = ver_arr[0]
|
||||
else # pre-release (alpha, beta, rc)
|
||||
shell_version = '.'.join(ver_arr)
|
||||
endif
|
||||
|
||||
uuid_suffix = '@gnome-shell-extensions.gcampax.github.com'
|
||||
|
||||
classic_extensions = [
|
||||
'apps-menu',
|
||||
'places-menu',
|
||||
'launch-new-instance',
|
||||
'window-list'
|
||||
]
|
||||
|
||||
default_extensions = classic_extensions
|
||||
default_extensions += [
|
||||
'drive-menu',
|
||||
'screenshot-window-sizer',
|
||||
'windowsNavigator',
|
||||
'workspace-indicator'
|
||||
]
|
||||
|
||||
all_extensions = default_extensions
|
||||
all_extensions += [
|
||||
'auto-move-windows',
|
||||
'native-window-placement',
|
||||
'user-theme'
|
||||
]
|
||||
|
||||
enabled_extensions = get_option('enable_extensions')
|
||||
|
||||
if enabled_extensions.length() == 0
|
||||
set = get_option('extension_set')
|
||||
|
||||
if set == 'classic'
|
||||
enabled_extensions += classic_extensions
|
||||
elif set == 'default'
|
||||
enabled_extensions += default_extensions
|
||||
elif set == 'all'
|
||||
enabled_extensions += all_extensions
|
||||
endif
|
||||
endif
|
||||
|
||||
classic_mode_enabled = get_option('classic_mode')
|
||||
|
||||
if classic_mode_enabled
|
||||
# Sanity check: Make sure all classic extensions are enabled
|
||||
foreach e : classic_extensions
|
||||
if not enabled_extensions.contains(e)
|
||||
error('Classic mode is enabled, ' +
|
||||
'but the required extension @0@ is not.'.format(e))
|
||||
endif
|
||||
endforeach
|
||||
endif
|
||||
|
||||
# Sanity check: Make sure enabled extensions are valid
|
||||
foreach e : enabled_extensions
|
||||
if not all_extensions.contains(e)
|
||||
error('Invalid extension @0@.'.format(e))
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if classic_mode_enabled
|
||||
subdir('data')
|
||||
endif
|
||||
|
||||
subdir('extensions')
|
||||
subdir('po')
|
||||
|
||||
meson.add_dist_script('meson/generate-stylesheets.py')
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from pathlib import PurePath
|
||||
import subprocess
|
||||
|
||||
sourceroot = os.environ.get('MESON_SOURCE_ROOT')
|
||||
distroot = os.environ.get('MESON_DIST_ROOT')
|
||||
|
||||
stylesheet_path = PurePath('data/gnome-classic.css')
|
||||
src = PurePath(sourceroot, stylesheet_path.with_suffix('.scss'))
|
||||
dst = PurePath(distroot, stylesheet_path)
|
||||
subprocess.call(['sassc', '-a', src, dst])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user