@ -0,0 +1,2 @@
|
||||
DIST gnome-shell-extension-blur-my-shell-44.tar.gz 97666 BLAKE2B 17605c9736e605399ae63ac0ead907ebdf4eb79699b553ac7e6d881213ea9200b705c19726d2613180651bc2ebaa59c82c631f8b8b2f8135a1e2eb710eb62460 SHA512 ea1695927f95a39500e03c96d7f1eb0d26af0651884e249cff1fa4b201a2eb1eb57b65b90a616e8f0d7a570fb17e57ab571bfa79174150198b7f8cdf5973f315
|
||||
EBUILD gnome-shell-extension-blur-my-shell-44.ebuild 1545 BLAKE2B 92692c8813fb57ea981d801752d5d6d3cbf7d2340b387fd36c54bf62ad247131de7c25d68f1a8c2df4e05791e1bb077e60015c2a232ed866b942a9135c85fce6 SHA512 575f92774fd9b2612d05f7e87407453c0638d781d7dcc5200dcc7f0490492a38e70eb0f9d20bdd3fbdca3485f08b8fe045c90fc3c1630b2d2ebac4a2b356fba9
|
@ -0,0 +1,64 @@
|
||||
# Copyright 1999-2022 Gentoo Authors
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=8
|
||||
inherit gnome2-utils
|
||||
|
||||
MY_PN="${PN/gnome-shell-extension-/}"
|
||||
MY_P="${MY_PN}-${PV}"
|
||||
|
||||
DESCRIPTION="A GNOME Shell extension that adds a blur look to different parts of the GNOME Shell, including the top panel, dash and overview."
|
||||
HOMEPAGE="https://github.com/aunetx/blur-my-shell"
|
||||
SRC_URI="https://github.com/aunetx/${MY_PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz"
|
||||
|
||||
LICENSE="GPL-3+"
|
||||
SLOT="0"
|
||||
KEYWORDS="amd64 x86"
|
||||
IUSE=""
|
||||
|
||||
COMMON_DEPEND="dev-libs/glib:2"
|
||||
RDEPEND="${COMMON_DEPEND}
|
||||
app-eselect/eselect-gnome-shell-extensions
|
||||
>=gnome-base/gnome-shell-3.36
|
||||
"
|
||||
DEPEND="${COMMON_DEPEND}"
|
||||
BDEPEND=""
|
||||
|
||||
S="${WORKDIR}/${MY_P}"
|
||||
extensions=.local/share/gnome-shell/extensions
|
||||
extension_uuid="blur-my-shell@aunetx"
|
||||
|
||||
# Not useful for us
|
||||
src_compile() { :; }
|
||||
|
||||
src_install() {
|
||||
default
|
||||
cd ${HOME}/${extensions}/"${extension_uuid}"
|
||||
insinto /usr/share/gnome-shell/extensions/"${extension_uuid}"
|
||||
doins -r *
|
||||
insinto /usr/share/glib-2.0/schemas
|
||||
doins schemas/*.xml
|
||||
insinto /usr/share/locale
|
||||
doins -r locale/*
|
||||
cd ${S}/
|
||||
rm -r ${HOME}/.local ${HOME}/.cache || die
|
||||
rm -r ${ED}/usr/share/gnome-shell/extensions/"${extension_uuid}"/locale || die
|
||||
rm -r ${ED}/usr/share/gnome-shell/extensions/"${extension_uuid}"/schemas || die
|
||||
}
|
||||
|
||||
pkg_preinst() {
|
||||
gnome2_schemas_savelist
|
||||
}
|
||||
|
||||
pkg_postinst() {
|
||||
gnome2_schemas_update
|
||||
ebegin "Updating list of installed extensions"
|
||||
eselect gnome-shell-extensions update
|
||||
eend $?
|
||||
}
|
||||
|
||||
pkg_postrm() {
|
||||
gnome2_schemas_update
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,2 @@
|
||||
AUX remove-app-menu-makefile-9.0.patch 928 BLAKE2B 466c172b90a799d19c01d023173e858b103b74c3edb0ecc661221c51a2b6422fb18e85d1238de83424ef636533a9c42e6671c60fbf0900e94652aac7b297946d SHA512 4d774264e3f35af75518c88a7a7a85df47d34e2a83f24b51f7372a0146c4c47ad16a6709984ca78d3f549a4d6620127131a335a7d67d69d2eaf0a8144a8a47e4
|
||||
DIST gnome-shell-extension-remove-app-menu-9.0.tar.gz 18273 BLAKE2B ad710144138a0fc38954db52214564ca2488ad627f91134871c73321961526300f524b0746ff995205ea120aa71031e95bcb8da27adcf0a53db5773792bec071 SHA512 f5dd97f2d0fe2422d595ed143619dd22d57e196fcf478122060c33d0d290258a5e1379cd32536a4672e6eb67b09e988b009da0efcee66fade25eda755df10069
|
||||
EBUILD gnome-shell-extension-remove-app-menu-9.0.ebuild 1080 BLAKE2B fda2fa31c0e30f7b8ac6c71f334f89f9fee933344fd4b65fae4b7f279ec30aa54d9bc1bd0106024041d3331c48b7d59e9d2f740ad397bbaa285c29b04d69ecef SHA512 fd443e3943a9e9a2fd541914ef19be709d72c93d49b3cc5a481e93f300c9921ea6fb07bd8b92c86c52dad2b554c912c83b5dc31051a4e06a3505de1adb486fbd
|
||||
EBUILD gnome-shell-extension-remove-app-menu-9.0.ebuild 1167 BLAKE2B a77c4fa141709dfe174c4770c98bfde32ef03f797cc689ae3101f9e5d8de1d18616e66101d4bcd9bd1b03e62b5ac80e81913d8240e5dd1fd4fa4f1b212acf968 SHA512 49438ef312c10f9c5e8c7b6bbb348dc576972fc8ecc2557ebc03e05f1a776221276b1255fc8ea357057d3c04da20bff9244f301d5f6c319b2c11cd06ac652c4b
|
||||
|
@ -1,38 +0,0 @@
|
||||
|
||||
diff --git a/Makefile b/Makefile
|
||||
|
||||
--- a/Makefile 2022-03-12 14:06:41.000000000 +0300
|
||||
+++ b/Makefile 2022-07-03 17:18:34.966377020 +0300
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
PNG_FILES=$(wildcard ./docs/*.png)
|
||||
|
||||
+ifeq ($(strip $(DESTDIR)), )
|
||||
+ INSTALL_DIR_TYPE = local
|
||||
+else
|
||||
+ INSTALL_DIR_TYPE = system
|
||||
+ SHARE_PREFIX = $(DESTDIR)/usr/share
|
||||
+endif
|
||||
+
|
||||
.PHONY: build check prune compress install uninstall clean $(PNG_FILES)
|
||||
|
||||
build:
|
||||
@@ -20,8 +27,17 @@
|
||||
$(MAKE) $(PNG_FILES)
|
||||
$(PNG_FILES):
|
||||
optipng "$(COMPRESSLEVEL)" -strip all "$@"
|
||||
-install:
|
||||
+install: install-local
|
||||
+
|
||||
+install-local:
|
||||
gnome-extensions install "$(UUID).shell-extension.zip" --force
|
||||
+
|
||||
+ifeq ($(INSTALL_DIR_TYPE), system)
|
||||
+ mkdir -p $(SHARE_PREFIX)/gnome-shell
|
||||
+ mkdir -p $(SHARE_PREFIX)/gnome-shell/extensions
|
||||
+ mv $(HOME)/.local/share/gnome-shell/extensions/$(UUID) $(SHARE_PREFIX)/gnome-shell/extensions/
|
||||
+endif
|
||||
+
|
||||
uninstall:
|
||||
gnome-extensions uninstall "$(UUID)"
|
||||
clean:
|
@ -0,0 +1,3 @@
|
||||
AUX sound-output-device-chooser-43.patch 1768 BLAKE2B 91bf6e4a0d2ae66d37d988d717f8c12fda7eab5c5b871d5a5a7e89ce5a4a11bc42683366b6a35ae2d0644ca83e8abb8caa7d12fff55e85d8936e9ee65bf7f323 SHA512 a0aa84c204aada969365ec865659ba154433d22b338593a37762a4c83f5d66a13d24a1634c1929a9fb44b1af42730cb7696bcfce4d3b7831aa08674b14eeb478
|
||||
DIST gnome-shell-extension-sound-output-device-chooser-43.tar.gz 64537 BLAKE2B 598b855489fdaac845dce71a79494e0a57ca8bcda94dd0a0832671863cccde293db25a8f0baa44270fca069c88cecf151ae131bb0cff2db716354e75108e9b63 SHA512 62b22291bbf17c027546096a120f79e47b8f1cd0e63da573ab598a84075c331a23ac68c4fbe231d3e329a7f9a0f5c6fc5cb3e11364ebd04e03533400b2ca2cc4
|
||||
EBUILD gnome-shell-extension-sound-output-device-chooser-43.ebuild 1176 BLAKE2B 4663d1258796cada476116a32c38ac3f6b4f16255e48b6ca2080f2d0d17f23011deb195c66f7d55bc55ed57eb9de5318e2e0a727b7f812a8060d24a8842ed987 SHA512 798aa47bb6b7c7401202a9749e179c2731920086cb08dc886360fe90c9d5bedd80171acc37f3fd397c3c2b7c21e92ed52d53308a717e8de465c61f632f4596f8
|
@ -0,0 +1,52 @@
|
||||
--- a/Makefile 2022-04-23 00:37:38.000000000 +0300
|
||||
+++ b/Makefile 2022-12-10 13:21:47.551930063 +0300
|
||||
@@ -1,17 +1,26 @@
|
||||
-INSTALL_DIR=~/.local/share/gnome-shell/extensions
|
||||
-SRC_DIR=sound-output-device-chooser@kgshank.net
|
||||
+UUID=sound-output-device-chooser@kgshank.net
|
||||
+SRC_DIR=./sound-output-device-chooser@kgshank.net
|
||||
LOCALE_SRC=$(shell find $(SRC_DIR) -regextype posix-extended -regex '.*\.(js|glade)' 2> /dev/null)
|
||||
LOCALE_DIR=$(SRC_DIR)/locale
|
||||
POT_FILE=$(SRC_DIR)/sound-output-device-chooser.pot
|
||||
PO_FILES=$(wildcard $(LOCALE_DIR)/*/LC_MESSAGES/*.po)
|
||||
MO_FILES=$(PO_FILES:.po=.mo)
|
||||
|
||||
+ifeq ($(strip $(DESTDIR)), )
|
||||
+ INSTALL_DIR_TYPE = local
|
||||
+ INSTALL_DIR_BASE = $(HOME)/.local/share/gnome-shell/extensions
|
||||
+else
|
||||
+ INSTALL_DIR_TYPE = system
|
||||
+ SHARE_PREFIX = $(DESTDIR)/usr/share
|
||||
+ INSTALL_DIR_BASE = $(SHARE_PREFIX)/gnome-shell/extensions
|
||||
+endif
|
||||
+
|
||||
.PHONY: all
|
||||
-all: build install
|
||||
+all: build
|
||||
|
||||
.PHONY: build
|
||||
build: $(MO_FILES)
|
||||
- glib-compile-schemas $(SRC_DIR)/schemas
|
||||
+ glib-compile-schemas $(SRC_DIR)/schemas/
|
||||
|
||||
.PHONY: potfile
|
||||
potfile:
|
||||
@@ -46,7 +55,14 @@
|
||||
msgfmt --check --output-file=$@ $<
|
||||
|
||||
.PHONY: install
|
||||
-install:
|
||||
- @echo "Installing extension files in $(INSTALL_DIR)/sound-output-device-chooser@kgshank.net"
|
||||
- mkdir -p $(INSTALL_DIR)
|
||||
- cp -r sound-output-device-chooser@kgshank.net $(INSTALL_DIR)
|
||||
+install: install-local
|
||||
+install-local:
|
||||
+ mkdir -p $(INSTALL_DIR_BASE)
|
||||
+ cp -r ./sound-output-device-chooser@kgshank.net $(INSTALL_DIR_BASE)/
|
||||
+
|
||||
+ifeq ($(INSTALL_DIR_TYPE), system)
|
||||
+ rm -r $(INSTALL_DIR_BASE)/$(UUID)/schemas $(INSTALL_DIR_BASE)/$(UUID)/locale
|
||||
+ mkdir -p $(SHARE_PREFIX)/glib-2.0/schemas $(SHARE_PREFIX)/locale
|
||||
+ cp -r $(SRC_DIR)/schemas/*gschema.* $(SHARE_PREFIX)/glib-2.0/schemas
|
||||
+ cp -r $(LOCALE_DIR)/* $(SHARE_PREFIX)/locale
|
||||
+endif
|
@ -0,0 +1,55 @@
|
||||
# Copyright 1999-2022 Gentoo Authors
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=8
|
||||
inherit gnome2-utils
|
||||
|
||||
MY_PN="${PN/gnome-shell-extension-/}"
|
||||
|
||||
COMMIT="9af403cf79c4d27ae3b1aadcda063eeb6b7ecb9f"
|
||||
|
||||
DESCRIPTION="A simple selector to enabled selection of sound source and sink based on Gnome Control Center"
|
||||
HOMEPAGE="https://github.com/kgshank/gse-sound-output-device-chooser"
|
||||
SRC_URI="https://github.com/kgshank/gse-${MY_PN}/archive/${COMMIT}.tar.gz -> ${P}.tar.gz"
|
||||
|
||||
LICENSE="GPL-3+"
|
||||
SLOT="0"
|
||||
KEYWORDS="amd64 x86"
|
||||
IUSE=""
|
||||
|
||||
COMMON_DEPEND="dev-libs/glib:2"
|
||||
RDEPEND="${COMMON_DEPEND}
|
||||
app-eselect/eselect-gnome-shell-extensions
|
||||
>=gnome-base/gnome-shell-3.32
|
||||
"
|
||||
DEPEND="${COMMON_DEPEND}"
|
||||
BDEPEND=""
|
||||
|
||||
S="${WORKDIR}/gse-${MY_PN}-${COMMIT}"
|
||||
extension_uuid="sound-output-device-chooser@kgshank.net"
|
||||
|
||||
src_prepare() {
|
||||
eapply "${FILESDIR}"/sound-output-device-chooser-43.patch
|
||||
eapply_user
|
||||
}
|
||||
|
||||
src_install() {
|
||||
default
|
||||
}
|
||||
|
||||
pkg_preinst() {
|
||||
gnome2_schemas_savelist
|
||||
}
|
||||
|
||||
pkg_postinst() {
|
||||
gnome2_schemas_update
|
||||
ebegin "Updating list of installed extensions"
|
||||
eselect gnome-shell-extensions update
|
||||
eend $?
|
||||
}
|
||||
|
||||
pkg_postrm() {
|
||||
gnome2_schemas_update
|
||||
}
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
# Calculate path=~/.local/share/gnome-shell
|
@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
Just Perfection GNOME Shell Desktop
|
||||
Copyright (C) 2021 Javad Rahmatzadeh
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
Just Perfection GNOME Shell Desktop Copyright (C) 2021 Javad Rahmatzadeh
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
@ -1,546 +0,0 @@
|
||||
/**
|
||||
* Shutdown Timer Extension for GNOME Shell
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @author D. Neumann <neumann89@gmail.com>
|
||||
* @copyright 2014-2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported init, enable, disable */
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const {
|
||||
ScheduleInfo,
|
||||
MenuItem,
|
||||
Textbox,
|
||||
RootMode,
|
||||
Timer,
|
||||
Convenience,
|
||||
EndSessionDialogAware,
|
||||
SessionModeAware,
|
||||
CheckCommand,
|
||||
} = Me.imports.lib;
|
||||
|
||||
const {
|
||||
guiIdle,
|
||||
throttleTimeout,
|
||||
disableGuiIdle,
|
||||
modeLabel,
|
||||
enableGuiIdle,
|
||||
longDurationString,
|
||||
logDebug,
|
||||
} = Convenience;
|
||||
|
||||
/* IMPORTS */
|
||||
const { GLib } = imports.gi;
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
|
||||
// screen and main functionality
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
// translations
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
const C_ = Gettext.pgettext;
|
||||
const _n = Gettext.ngettext;
|
||||
|
||||
/* GLOBAL VARIABLES */
|
||||
let shutdownTimerMenu, timer, separator, settings;
|
||||
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function refreshExternalInfo() {
|
||||
if (shutdownTimerMenu !== undefined) {
|
||||
shutdownTimerMenu.infoFetcher.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param textmsg
|
||||
*/
|
||||
function maybeShowTextbox(textmsg) {
|
||||
if (settings.get_boolean('show-textboxes-value')) {
|
||||
guiIdle(() => {
|
||||
Textbox.showTextbox(textmsg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param info
|
||||
* @param stopScheduled
|
||||
*/
|
||||
async function maybeStopRootModeProtection(info, stopScheduled = false) {
|
||||
if (
|
||||
(stopScheduled || !info.scheduled) &&
|
||||
settings.get_boolean('root-mode-value')
|
||||
) {
|
||||
logDebug(`Stop root mode protection for: ${info.mode}`);
|
||||
try {
|
||||
switch (info.mode) {
|
||||
case 'poweroff':
|
||||
case 'reboot':
|
||||
await RootMode.shutdownCancel();
|
||||
refreshExternalInfo();
|
||||
break;
|
||||
default:
|
||||
logDebug(`No root mode protection stopped for: ${info.mode}`);
|
||||
}
|
||||
} catch (err) {
|
||||
maybeShowTextbox(
|
||||
C_('Error', '%s\n%s').format(_('Root mode protection failed!'), err)
|
||||
);
|
||||
logError(err, 'DisableRootModeProtection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Insure that shutdown is executed even if the GLib timer fails by running
|
||||
* the `shutdown` command delayed by 1 minute. Suspend is not insured.
|
||||
*
|
||||
* @param info
|
||||
*/
|
||||
async function maybeStartRootModeProtection(info) {
|
||||
if (info.scheduled && settings.get_boolean('root-mode-value')) {
|
||||
logDebug(`Start root mode protection for: ${info.label}`);
|
||||
try {
|
||||
const minutes = Math.max(0, info.minutes) + 1;
|
||||
switch (info.mode) {
|
||||
case 'poweroff':
|
||||
await RootMode.shutdown(minutes);
|
||||
refreshExternalInfo();
|
||||
break;
|
||||
case 'reboot':
|
||||
await RootMode.shutdown(minutes, true);
|
||||
refreshExternalInfo();
|
||||
break;
|
||||
default:
|
||||
logDebug(`No root mode protection started for: ${info.mode}`);
|
||||
}
|
||||
} catch (err) {
|
||||
maybeShowTextbox(
|
||||
C_('Error', '%s\n%s').format(_('Root mode protection failed!'), err)
|
||||
);
|
||||
logError(err, 'EnableRootModeProtection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param wakeMinutes
|
||||
*/
|
||||
async function maybeStartWake(wakeMinutes) {
|
||||
if (settings.get_boolean('auto-wake-value')) {
|
||||
await wakeAction('wake', wakeMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function maybeStopWake() {
|
||||
if (settings.get_boolean('auto-wake-value')) {
|
||||
await wakeAction('no-wake');
|
||||
}
|
||||
}
|
||||
|
||||
// timer action (shutdown/reboot/suspend)
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
*/
|
||||
async function serveInernalSchedule(mode) {
|
||||
const checkCmd = maybeCheckCmdString();
|
||||
try {
|
||||
if (checkCmd !== '') {
|
||||
guiIdle(() => {
|
||||
shutdownTimerMenu.checkRunning = true;
|
||||
shutdownTimerMenu.updateShutdownInfo();
|
||||
});
|
||||
maybeShowTextbox(checkCmd);
|
||||
maybeShowTextbox(
|
||||
_('Waiting for %s confirmation').format(modeLabel(mode))
|
||||
);
|
||||
await CheckCommand.doCheck(
|
||||
checkCmd,
|
||||
line => {
|
||||
if (!line.startsWith('[')) {
|
||||
maybeShowTextbox(`'${line}'`);
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
// keep protection alive
|
||||
await maybeStartRootModeProtection(
|
||||
new ScheduleInfo.ScheduleInfo({ mode, deadline: 0 })
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
// check succeeded: do shutdown
|
||||
shutdown(mode);
|
||||
} catch (err) {
|
||||
logError(err, 'CheckError');
|
||||
// check failed: cancel shutdown
|
||||
// stop root protection
|
||||
await maybeStopRootModeProtection(
|
||||
new ScheduleInfo.ScheduleInfo({ mode, deadline: 0 }),
|
||||
true
|
||||
);
|
||||
try {
|
||||
const root = settings.get_boolean('root-mode-value');
|
||||
if (root) {
|
||||
await RootMode.shutdownCancel();
|
||||
}
|
||||
const wake = settings.get_boolean('auto-wake-value');
|
||||
if (wake) {
|
||||
await RootMode.wakeCancel();
|
||||
}
|
||||
if (root || wake) {
|
||||
refreshExternalInfo();
|
||||
}
|
||||
} catch (err2) {
|
||||
// error is most likely: script not installed
|
||||
logError(err2, 'CheckError');
|
||||
}
|
||||
// check failed: log failure
|
||||
let code = '?';
|
||||
if ('code' in err) {
|
||||
code = `${err.code}`;
|
||||
logDebug(`Check command aborted ${mode}. Code: ${code}`);
|
||||
}
|
||||
maybeShowTextbox(
|
||||
C_('CheckCommand', '%s aborted (Code: %s)').format(modeLabel(mode), code)
|
||||
);
|
||||
if (parseInt(code) === 19) {
|
||||
maybeShowTextbox(_('Confirmation canceled'));
|
||||
}
|
||||
} finally {
|
||||
// update shutdownTimerMenu
|
||||
guiIdle(() => {
|
||||
shutdownTimerMenu.checkRunning = false;
|
||||
shutdownTimerMenu.updateShutdownInfo();
|
||||
});
|
||||
// reset schedule timestamp
|
||||
settings.set_int('shutdown-timestamp-value', -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function foregroundActive() {
|
||||
// ubuntu22.04 uses 'ubuntu' as 'user' sessionMode
|
||||
return Main.sessionMode.currentMode !== 'unlock-dialog';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
*/
|
||||
function shutdown(mode) {
|
||||
if (foregroundActive()) {
|
||||
Main.overview.hide();
|
||||
Textbox.hideAll();
|
||||
}
|
||||
|
||||
if (['reboot', 'poweroff'].includes(mode)) {
|
||||
if (
|
||||
foregroundActive() &&
|
||||
settings.get_boolean('show-end-session-dialog-value')
|
||||
) {
|
||||
// show endSessionDialog
|
||||
// refresh root shutdown protection
|
||||
maybeStartRootModeProtection(
|
||||
new ScheduleInfo.ScheduleInfo({ mode, deadline: 0 })
|
||||
);
|
||||
EndSessionDialogAware.register();
|
||||
const session = new imports.misc.gnomeSession.SessionManager();
|
||||
if (mode === 'reboot') {
|
||||
session.RebootRemote(0);
|
||||
} else {
|
||||
session.ShutdownRemote(0);
|
||||
}
|
||||
} else {
|
||||
imports.misc.util.spawnCommandLine(
|
||||
mode === 'reboot' ? 'reboot' : 'poweroff'
|
||||
);
|
||||
}
|
||||
} else if (mode === 'suspend') {
|
||||
LoginManager.getLoginManager().suspend();
|
||||
} else {
|
||||
logError(new Error(`Unknown shutdown mode: ${mode}`));
|
||||
}
|
||||
}
|
||||
|
||||
/* ACTION FUNCTIONS */
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
* @param minutes
|
||||
*/
|
||||
async function wakeAction(mode, minutes) {
|
||||
try {
|
||||
switch (mode) {
|
||||
case 'wake':
|
||||
await RootMode.wake(minutes);
|
||||
refreshExternalInfo();
|
||||
return;
|
||||
case 'no-wake':
|
||||
await RootMode.wakeCancel();
|
||||
refreshExternalInfo();
|
||||
return;
|
||||
default:
|
||||
logError(new Error(`Unknown wake mode: ${mode}`));
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
maybeShowTextbox(
|
||||
C_('Error', '%s\n%s').format(_('Wake action failed!'), err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param stopProtection
|
||||
*/
|
||||
function stopSchedule(stopProtection = true) {
|
||||
EndSessionDialogAware.unregister();
|
||||
const canceled = CheckCommand.maybeCancel();
|
||||
if (!canceled && settings.get_int('shutdown-timestamp-value') > -1) {
|
||||
settings.set_int('shutdown-timestamp-value', -1);
|
||||
maybeShowTextbox(_('Shutdown Timer stopped'));
|
||||
}
|
||||
|
||||
if (stopProtection) {
|
||||
// stop root protection
|
||||
const info =
|
||||
timer !== undefined ? timer.info : new ScheduleInfo.ScheduleInfo();
|
||||
return Promise.all([maybeStopRootModeProtection(info), maybeStopWake()]);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param maxTimerMinutes
|
||||
* @param wakeMinutes
|
||||
*/
|
||||
async function startSchedule(maxTimerMinutes, wakeMinutes) {
|
||||
EndSessionDialogAware.unregister();
|
||||
CheckCommand.maybeCancel();
|
||||
|
||||
const seconds = maxTimerMinutes * 60;
|
||||
const info = new ScheduleInfo.ScheduleInfo({
|
||||
mode: settings.get_string('shutdown-mode-value'),
|
||||
deadline: GLib.DateTime.new_now_utc().to_unix() + Math.max(1, seconds),
|
||||
});
|
||||
settings.set_int('shutdown-timestamp-value', info.deadline);
|
||||
let startPopupText = C_('StartSchedulePopup', '%s in %s').format(
|
||||
modeLabel(info.mode),
|
||||
longDurationString(
|
||||
maxTimerMinutes,
|
||||
h => _n('%s hour', '%s hours', h),
|
||||
m => _n('%s minute', '%s minutes', m)
|
||||
)
|
||||
);
|
||||
const checkCmd = maybeCheckCmdString();
|
||||
if (checkCmd !== '') {
|
||||
maybeShowTextbox(checkCmd);
|
||||
}
|
||||
maybeShowTextbox(startPopupText);
|
||||
|
||||
// start root protection
|
||||
await Promise.all([
|
||||
maybeStartRootModeProtection(info),
|
||||
maybeStartWake(wakeMinutes),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function maybeCheckCmdString() {
|
||||
const cmd = settings
|
||||
.get_string('check-command-value')
|
||||
.split('\n')
|
||||
.filter(line => !line.trimLeft().startsWith('#') && line.trim())
|
||||
.join('\n');
|
||||
|
||||
return settings.get_boolean('enable-check-command-value') ? cmd : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param info
|
||||
*/
|
||||
function onShutdownScheduleChange(info) {
|
||||
if (timer !== undefined) {
|
||||
timer.adjustTo(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sessionMode
|
||||
*/
|
||||
function onSessionModeChange(sessionMode) {
|
||||
logDebug(`sessionMode: ${sessionMode}`);
|
||||
switch (sessionMode) {
|
||||
case 'unlock-dialog':
|
||||
disableForeground();
|
||||
break;
|
||||
case 'user':
|
||||
default:
|
||||
enableForeground();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function enableForeground() {
|
||||
enableGuiIdle();
|
||||
// add separator line and submenu in status area menu
|
||||
const statusMenu = Main.panel.statusArea['aggregateMenu'];
|
||||
if (separator === undefined) {
|
||||
separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||
statusMenu.menu.addMenuItem(separator);
|
||||
}
|
||||
if (shutdownTimerMenu === undefined) {
|
||||
shutdownTimerMenu = new MenuItem.ShutdownTimer();
|
||||
shutdownTimerMenu.checkRunning = CheckCommand.isChecking();
|
||||
timer.setTickCallback(
|
||||
shutdownTimerMenu.updateShutdownInfo.bind(shutdownTimerMenu)
|
||||
);
|
||||
statusMenu.menu.addMenuItem(shutdownTimerMenu);
|
||||
}
|
||||
// stop schedule if endSessionDialog cancel button is activated
|
||||
EndSessionDialogAware.load(stopSchedule);
|
||||
logDebug('Enabled foreground.');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function disableForeground() {
|
||||
disableGuiIdle();
|
||||
Textbox.hideAll();
|
||||
if (shutdownTimerMenu !== undefined) {
|
||||
shutdownTimerMenu.destroy();
|
||||
if (timer !== undefined) {
|
||||
timer.setTickCallback(null);
|
||||
// keep sleep process alive
|
||||
timer.stopForeground();
|
||||
}
|
||||
}
|
||||
shutdownTimerMenu = undefined;
|
||||
if (separator !== undefined) {
|
||||
separator.destroy();
|
||||
}
|
||||
separator = undefined;
|
||||
EndSessionDialogAware.unload();
|
||||
logDebug('Disabled foreground.');
|
||||
}
|
||||
|
||||
/* EXTENSION MAIN FUNCTIONS */
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function init() {
|
||||
// initialize translations
|
||||
ExtensionUtils.initTranslations();
|
||||
}
|
||||
|
||||
let throttleDisable = null;
|
||||
let throttleDisableCancel = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function enable() {
|
||||
if (!initialized) {
|
||||
[throttleDisable, throttleDisableCancel] = throttleTimeout(
|
||||
completeDisable,
|
||||
100
|
||||
);
|
||||
// initialize settings
|
||||
settings = ExtensionUtils.getSettings();
|
||||
// ensure that no shutdown is scheduled
|
||||
settings.set_int('shutdown-timestamp-value', -1);
|
||||
MenuItem.init(settings, {
|
||||
wakeAction,
|
||||
startSchedule,
|
||||
stopSchedule,
|
||||
maybeStopRootModeProtection,
|
||||
maybeStartRootModeProtection,
|
||||
onShutdownScheduleChange,
|
||||
});
|
||||
Textbox.init();
|
||||
|
||||
// check for shutdown may run in background and can be canceled by user
|
||||
// starts internal shutdown schedule if ready
|
||||
timer = new Timer.Timer(serveInernalSchedule);
|
||||
|
||||
SessionModeAware.load(onSessionModeChange);
|
||||
|
||||
initialized = true;
|
||||
} else {
|
||||
throttleDisableCancel();
|
||||
}
|
||||
if (foregroundActive()) {
|
||||
enableForeground();
|
||||
logDebug('Completly enabled.');
|
||||
} else {
|
||||
logDebug('Background enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function disable() {
|
||||
// unlock-dialog session-mode is required such that the timer action can trigger
|
||||
disableForeground();
|
||||
// DELAYED DISABLE:
|
||||
// Workaround for Gnome 42 weird behaviour (?unlock-dialog sessionMode bug?):
|
||||
// on first screensaver activation after login gnome-shell quickly enables/disables this extension
|
||||
// for each extension that is also enabled besides this extension
|
||||
if (initialized) {
|
||||
throttleDisable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function completeDisable() {
|
||||
if (initialized) {
|
||||
if (timer !== undefined) {
|
||||
timer.stopTimer();
|
||||
timer = undefined;
|
||||
}
|
||||
Textbox.uninit();
|
||||
MenuItem.uninit();
|
||||
// clear internal schedule and keep root protected schedule
|
||||
stopSchedule(false);
|
||||
SessionModeAware.unload();
|
||||
|
||||
throttleDisableCancel();
|
||||
throttleDisable = null;
|
||||
throttleDisableCancel = null;
|
||||
initialized = false;
|
||||
logDebug('Completly disabled.');
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,95 +0,0 @@
|
||||
/**
|
||||
* CheckCommand module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported doCheck, maybeCancel, isChecking */
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const { RootMode, Convenience } = Me.imports.lib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const logDebug = Convenience.logDebug;
|
||||
|
||||
let checkCancel;
|
||||
|
||||
/**
|
||||
* Wait for checkCmd to execute successfully.
|
||||
*
|
||||
* @param {string} checkCmd check command
|
||||
* @param {(line: string) => void} onLog
|
||||
* @param {async () => void} redoRootProtection
|
||||
*/
|
||||
function doCheck(checkCmd, onLog, redoRootProtection) {
|
||||
if (checkCancel !== undefined) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'Confirmation canceled: attempted to start a second check command!'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
checkCancel = new Gio.Cancellable();
|
||||
const checkWatchCancel = new Gio.Cancellable();
|
||||
return Promise.all([
|
||||
_doCheck(checkCmd, checkWatchCancel, onLog),
|
||||
continueRootProtectionDuringCheck(checkWatchCancel, redoRootProtection),
|
||||
]);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param checkCmd
|
||||
* @param checkWatchCancel
|
||||
* @param onLog
|
||||
*/
|
||||
async function _doCheck(checkCmd, checkWatchCancel, onLog) {
|
||||
try {
|
||||
await RootMode.execCheck(checkCmd, checkCancel, true, onLog);
|
||||
logDebug(`Check command "${checkCmd}" confirmed shutdown.`);
|
||||
} finally {
|
||||
checkCancel = undefined;
|
||||
checkWatchCancel.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cancellable
|
||||
* @param redoRootProtection
|
||||
*/
|
||||
async function continueRootProtectionDuringCheck(
|
||||
cancellable,
|
||||
redoRootProtection
|
||||
) {
|
||||
try {
|
||||
await RootMode.execCheck(['sleep', '30'], cancellable, false);
|
||||
} catch (err) {
|
||||
logDebug('RootProtection during check: Canceled');
|
||||
}
|
||||
if (checkCancel === undefined) {
|
||||
logDebug('RootProtection during check: Done');
|
||||
} else {
|
||||
await redoRootProtection();
|
||||
logDebug('RootProtection during check: Continue');
|
||||
await continueRootProtectionDuringCheck(cancellable, redoRootProtection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function isChecking() {
|
||||
return checkCancel !== undefined && !checkCancel.is_cancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function maybeCancel() {
|
||||
const doCancel = isChecking();
|
||||
if (doCancel) {
|
||||
checkCancel.cancel();
|
||||
}
|
||||
return doCancel;
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
/**
|
||||
* Convenience module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported MODES, WAKE_MODES, modeLabel, logDebug, proxyPromise, durationString, longDurationString, disableGuiIdle, enableGuiIdle, guiIdle, throttleTimeout */
|
||||
|
||||
const { GLib } = imports.gi;
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
const _n = Gettext.ngettext;
|
||||
|
||||
let debugMode = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {...any} args
|
||||
*/
|
||||
function logDebug(...args) {
|
||||
if (debugMode) {
|
||||
log(...args);
|
||||
}
|
||||
}
|
||||
|
||||
var MODES = ['suspend', 'poweroff', 'reboot'];
|
||||
var WAKE_MODES = ['wake', 'no-wake'];
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
*/
|
||||
function modeLabel(mode) {
|
||||
return {
|
||||
suspend: _('Suspend'),
|
||||
poweroff: _('Power Off'),
|
||||
reboot: _('Restart'),
|
||||
wake: _('Wake after'),
|
||||
'no-wake': _('No Wake'),
|
||||
}[mode];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ProxyType
|
||||
* @param session
|
||||
* @param dest
|
||||
* @param objectPath
|
||||
*/
|
||||
function proxyPromise(ProxyType, session, dest, objectPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
new ProxyType(session, dest, objectPath, (proxy, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(proxy);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param seconds
|
||||
*/
|
||||
function durationString(seconds) {
|
||||
const sign = Math.sign(seconds);
|
||||
const absSec = Math.floor(Math.abs(seconds));
|
||||
const minutes = Math.floor(absSec / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours >= 3) {
|
||||
return _n('%s hour', '%s hours', hours).format(sign * hours);
|
||||
} else if (minutes === 0) {
|
||||
return _n('%s sec', '%s secs', absSec).format(
|
||||
sign * (absSec > 5 ? 10 * Math.ceil(absSec / 10) : absSec)
|
||||
);
|
||||
}
|
||||
return _n('%s min', '%s mins', minutes).format(sign * minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param minutes
|
||||
* @param hrFmt
|
||||
* @param minFmt
|
||||
*/
|
||||
function longDurationString(minutes, hrFmt, minFmt) {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const residualMinutes = minutes % 60;
|
||||
let parts = [minFmt(residualMinutes).format(residualMinutes)];
|
||||
if (hours) {
|
||||
parts = [hrFmt(hours).format(hours)].concat(parts);
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
let idleSourceId = null;
|
||||
let idleCallbacks = [];
|
||||
let idleEnabled = false;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function enableGuiIdle() {
|
||||
idleEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function disableGuiIdle() {
|
||||
idleEnabled = false;
|
||||
idleCallbacks = [];
|
||||
if (idleSourceId) {
|
||||
GLib.Source.remove(idleSourceId);
|
||||
}
|
||||
idleSourceId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
function guiIdle(callback) {
|
||||
if (idleEnabled) {
|
||||
idleCallbacks.push(callback);
|
||||
if (!idleSourceId) {
|
||||
idleSourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||
for (const func of idleCallbacks) {
|
||||
func();
|
||||
}
|
||||
idleCallbacks = [];
|
||||
idleSourceId = null;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param timeoutFunc
|
||||
* @param delayMillis
|
||||
*/
|
||||
function throttleTimeout(timeoutFunc, delayMillis) {
|
||||
let current = null;
|
||||
return [
|
||||
() => {
|
||||
if (current === null) {
|
||||
current = setTimeout(() => {
|
||||
timeoutFunc();
|
||||
current = null;
|
||||
}, delayMillis);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if (current) {
|
||||
clearTimeout(current);
|
||||
current = null;
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* EndSessionDialogAware module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported register, unregister, load, unload */
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const { Convenience } = Me.imports.lib;
|
||||
const { Gio } = imports.gi;
|
||||
const { loadInterfaceXML } = imports.misc.fileUtils;
|
||||
const { proxyPromise, logDebug } = Convenience;
|
||||
|
||||
const EndSessionDialogInf = loadInterfaceXML(
|
||||
'org.gnome.SessionManager.EndSessionDialog'
|
||||
);
|
||||
const EndSessionDialogProxy =
|
||||
Gio.DBusProxy.makeProxyWrapper(EndSessionDialogInf);
|
||||
|
||||
let endSessionDialog = null;
|
||||
let onCancelAction = null;
|
||||
let registered = false;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unregister() {
|
||||
registered = false;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function register() {
|
||||
registered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cancelAction
|
||||
*/
|
||||
function load(cancelAction) {
|
||||
onCancelAction = cancelAction;
|
||||
_update();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unload() {
|
||||
onCancelAction = null;
|
||||
_update();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function _update() {
|
||||
try {
|
||||
if (onCancelAction && !endSessionDialog) {
|
||||
endSessionDialog = await proxyPromise(
|
||||
EndSessionDialogProxy,
|
||||
Gio.DBus.session,
|
||||
'org.gnome.Shell',
|
||||
'/org/gnome/SessionManager/EndSessionDialog'
|
||||
);
|
||||
}
|
||||
if (endSessionDialog) {
|
||||
if (onCancelAction === null) {
|
||||
_disconnect(endSessionDialog);
|
||||
endSessionDialog = null;
|
||||
} else {
|
||||
_connect(endSessionDialog, onCancelAction);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logError(err, 'EndSessionDialogProxyError');
|
||||
endSessionDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dialog
|
||||
* @param cancelAction
|
||||
*/
|
||||
function _connect(dialog, cancelAction) {
|
||||
if (!('_cancelSignalId' in dialog)) {
|
||||
logDebug('Connect cancel of endSessionDialog...');
|
||||
dialog['_cancelSignalId'] = dialog.connectSignal('Canceled', () => {
|
||||
logDebug(
|
||||
`endSessionDialog cancel triggered. propagate registered: ${registered}`
|
||||
);
|
||||
if (registered) {
|
||||
cancelAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dialog
|
||||
*/
|
||||
function _disconnect(dialog) {
|
||||
const signalId = dialog['_cancelSignalId'];
|
||||
if (signalId) {
|
||||
logDebug('Disconnect cancel of endSessionDialog...');
|
||||
dialog.disconnectSignal(signalId);
|
||||
delete dialog['_cancelSignalId'];
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/**
|
||||
* InfoFetcher module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported InfoFetcher */
|
||||
|
||||
const { Gio, GLib } = imports.gi;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { Convenience } = Me.imports.lib;
|
||||
const { logDebug, throttleTimeout } = Convenience;
|
||||
const ByteArray = imports.byteArray;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path
|
||||
*/
|
||||
function readFile(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const file = Gio.File.new_for_path(path);
|
||||
file.load_contents_async(null, (f, res) => {
|
||||
try {
|
||||
const [, contents] = f.load_contents_finish(res);
|
||||
resolve(ByteArray.toString(contents));
|
||||
GLib.free(contents);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function isWakeInfoLocal() {
|
||||
const content = await readFile('/etc/adjtime').catch(() => '');
|
||||
return content.trim().toLowerCase().endsWith('local');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rtc
|
||||
*/
|
||||
async function wakeInfo(rtc) {
|
||||
const content = await readFile(`/sys/class/rtc/${rtc}/wakealarm`).catch(
|
||||
() => ''
|
||||
);
|
||||
let timestamp = content !== '' ? parseInt(content) : -1;
|
||||
if (timestamp > -1 && (await isWakeInfoLocal())) {
|
||||
const dt = GLib.DateTime.new_from_unix_local(timestamp);
|
||||
timestamp = dt.to_unix() - dt.get_utc_offset() / 1000000;
|
||||
dt.unref();
|
||||
}
|
||||
return { deadline: timestamp };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function shutdownInfo() {
|
||||
try {
|
||||
const content = await readFile('/run/systemd/shutdown/scheduled');
|
||||
// content: schedule unix-timestamp (micro-seconds), warn-all, shutdown-mode
|
||||
const [usec, _, mode] = content.split('\n').map(l => l.split('=')[1]);
|
||||
return {
|
||||
mode,
|
||||
deadline: parseInt(usec) / 1000000,
|
||||
};
|
||||
} catch {
|
||||
return { deadline: -1 };
|
||||
}
|
||||
}
|
||||
|
||||
var InfoFetcher = class {
|
||||
constructor(onFetch) {
|
||||
this._infoTimerId = null;
|
||||
this._pending = false;
|
||||
this._rtc = 'rtc0';
|
||||
this._onFetch = onFetch;
|
||||
[this.refresh, this._cancelRefresh] = throttleTimeout(
|
||||
this._refresh.bind(this),
|
||||
300
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
logDebug('Extra info refresh...');
|
||||
this.stop();
|
||||
// restart loop
|
||||
this._infoTimerId = setInterval(this.tick.bind(this), 5000);
|
||||
this.tick();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._infoTimerId !== null) {
|
||||
clearInterval(this._infoTimerId);
|
||||
}
|
||||
this._cancelRefresh();
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (!this._pending) {
|
||||
this._pending = true;
|
||||
Promise.all([shutdownInfo(), wakeInfo(this._rtc)]).then(
|
||||
([schedule, wake]) => {
|
||||
this._onFetch(schedule, wake);
|
||||
this._pending = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Install module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported installAction, checkInstalled, reset, actionLabel */
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const { Convenience, RootMode } = Me.imports.lib;
|
||||
const logDebug = Convenience.logDebug;
|
||||
|
||||
const { Gio } = imports.gi;
|
||||
// translations
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
let installCancel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @param logInstall
|
||||
*/
|
||||
function installAction(action, logInstall) {
|
||||
if (installCancel !== undefined) {
|
||||
logDebug('Trigger cancel install.');
|
||||
installCancel.cancel();
|
||||
} else {
|
||||
logDebug(`Trigger ${action} action.`);
|
||||
installCancel = new Gio.Cancellable();
|
||||
_installAction(action, logInstall, installCancel).finally(() => {
|
||||
installCancel = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function checkInstalled() {
|
||||
const scriptPath = RootMode.installedScriptPath();
|
||||
const isInstalled = scriptPath !== null;
|
||||
if (isInstalled) {
|
||||
logDebug(`Existing installation at: ${scriptPath}`);
|
||||
}
|
||||
return isInstalled;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @param logInstall
|
||||
* @param cancel
|
||||
*/
|
||||
async function _installAction(action, logInstall, cancel) {
|
||||
logInstall(`[${_('START')} ${actionLabel(action)}]`);
|
||||
try {
|
||||
if (action === 'install') {
|
||||
await RootMode.installScript(cancel, logInstall);
|
||||
} else {
|
||||
await RootMode.uninstallScript(cancel, logInstall);
|
||||
}
|
||||
logInstall(`[${_('END')} ${actionLabel(action)}]`);
|
||||
} catch (err) {
|
||||
logInstall(`[${_('FAIL')} ${actionLabel(action)}]\n# ${err}`);
|
||||
logError(err, 'InstallError');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
*/
|
||||
function actionLabel(action) {
|
||||
return { install: _('install'), uninstall: _('uninstall') }[action];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function reset() {
|
||||
if (installCancel !== undefined) {
|
||||
installCancel.cancel();
|
||||
}
|
||||
installCancel = undefined;
|
||||
}
|
@ -1,444 +0,0 @@
|
||||
/**
|
||||
* MenuItem module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported ShutdownTimer, init, uninit, MODES */
|
||||
|
||||
const { GObject, St, Gio, Clutter } = imports.gi;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const { Convenience, InfoFetcher, ScheduleInfo } = Me.imports.lib;
|
||||
const {
|
||||
logDebug,
|
||||
modeLabel,
|
||||
MODES,
|
||||
WAKE_MODES,
|
||||
durationString,
|
||||
longDurationString,
|
||||
guiIdle,
|
||||
} = Convenience;
|
||||
|
||||
// menu items
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Slider = imports.ui.slider;
|
||||
|
||||
// translations
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
const _n = Gettext.ngettext;
|
||||
const C_ = Gettext.pgettext;
|
||||
|
||||
let ACTIONS;
|
||||
let settings;
|
||||
|
||||
var ShutdownTimer = GObject.registerClass(
|
||||
class ShutdownTimer extends PopupMenu.PopupSubMenuMenuItem {
|
||||
_init() {
|
||||
super._init('', true);
|
||||
// track external shutdown and wake schedule
|
||||
this.infoFetcher = new InfoFetcher.InfoFetcher(
|
||||
this._externalScheduleInfoTick.bind(this)
|
||||
);
|
||||
|
||||
this.checkRunning = false;
|
||||
this.externalScheduleInfo = new ScheduleInfo.ScheduleInfo({
|
||||
external: true,
|
||||
});
|
||||
this.externalWakeInfo = new ScheduleInfo.ScheduleInfo({
|
||||
external: false,
|
||||
mode: 'wake',
|
||||
});
|
||||
this.internalScheduleInfo = new ScheduleInfo.ScheduleInfo({
|
||||
external: false,
|
||||
deadline: settings.get_int('shutdown-timestamp-value'),
|
||||
mode: settings.get_string('shutdown-mode-value'),
|
||||
});
|
||||
|
||||
// submenu in status area menu with slider and toggle button
|
||||
this.sliderItems = {};
|
||||
this.sliders = {};
|
||||
['shutdown', 'wake'].forEach(prefix => {
|
||||
const [item, slider] = _createSliderItem(prefix);
|
||||
this.sliderItems[prefix] = item;
|
||||
this.sliders[prefix] = slider;
|
||||
this._onShowSliderChanged(prefix);
|
||||
});
|
||||
this.switcher = new PopupMenu.PopupSwitchMenuItem('', false);
|
||||
_connect(this.switcher, [['toggled', this._onToggle.bind(this)]]);
|
||||
this.switcherSettingsButton = new St.Button({
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: _('Settings'),
|
||||
style_class: 'system-menu-action settings-button',
|
||||
});
|
||||
this.switcherSettingsButton.child = new St.Icon({
|
||||
icon_name: 'emblem-system-symbolic',
|
||||
style_class: 'popup-menu-icon',
|
||||
});
|
||||
_connect(this.switcherSettingsButton, [
|
||||
[
|
||||
'clicked',
|
||||
async () => {
|
||||
try {
|
||||
const r = ExtensionUtils.openPrefs();
|
||||
if (r) {
|
||||
await r;
|
||||
}
|
||||
} catch {
|
||||
logDebug('failed to open preferences!');
|
||||
}
|
||||
},
|
||||
],
|
||||
]);
|
||||
this.switcher.add_child(this.switcherSettingsButton);
|
||||
|
||||
this._onShowSettingsButtonChanged();
|
||||
this._updateSwitchLabel();
|
||||
this.icon.icon_name = 'preferences-system-time-symbolic';
|
||||
this.menu.addMenuItem(this.switcher);
|
||||
// make switcher toggle without popup menu closing
|
||||
this.switcher.activate = __ => {
|
||||
if (this.switcher._switch.mapped) {
|
||||
this.switcher.toggle();
|
||||
}
|
||||
};
|
||||
this.menu.addMenuItem(this.sliderItems['shutdown']);
|
||||
|
||||
this.modeItems = MODES.map(mode => {
|
||||
const modeItem = new PopupMenu.PopupMenuItem(modeLabel(mode));
|
||||
_connect(modeItem, [
|
||||
[
|
||||
'activate',
|
||||
() => {
|
||||
this._startMode(mode);
|
||||
},
|
||||
],
|
||||
]);
|
||||
this.menu.addMenuItem(modeItem);
|
||||
return [mode, modeItem];
|
||||
});
|
||||
|
||||
this.wakeItems = [
|
||||
new PopupMenu.PopupSeparatorMenuItem(),
|
||||
this.sliderItems['wake'],
|
||||
...WAKE_MODES.map(mode => {
|
||||
const modeItem = new PopupMenu.PopupMenuItem(modeLabel(mode));
|
||||
if (mode === 'wake') {
|
||||
this.wakeModeItem = modeItem;
|
||||
}
|
||||
_connect(modeItem, [
|
||||
[
|
||||
'activate',
|
||||
() => ACTIONS.wakeAction(mode, _getSliderMinutes('wake')),
|
||||
],
|
||||
]);
|
||||
return modeItem;
|
||||
}),
|
||||
];
|
||||
this._updateWakeModeItem();
|
||||
this.wakeItems.forEach(item => {
|
||||
this.menu.addMenuItem(item);
|
||||
});
|
||||
this._updateShownWakeItems();
|
||||
this._updateShownModeItems();
|
||||
this._updateSelectedModeItems();
|
||||
this._onInternalShutdownTimestampChanged();
|
||||
|
||||
// handlers for changed values in settings
|
||||
this.settingsHandlerIds = [
|
||||
['shutdown-max-timer-value', this._updateSwitchLabel.bind(this)],
|
||||
['nonlinear-shutdown-slider-value', this._updateSwitchLabel.bind(this)],
|
||||
['wake-max-timer-value', this._updateWakeModeItem.bind(this)],
|
||||
['nonlinear-wake-slider-value', this._updateWakeModeItem.bind(this)],
|
||||
[
|
||||
'shutdown-slider-value',
|
||||
() => {
|
||||
this._updateSlider('shutdown');
|
||||
this._updateSwitchLabel();
|
||||
},
|
||||
],
|
||||
[
|
||||
'wake-slider-value',
|
||||
() => {
|
||||
this._updateSlider('wake');
|
||||
this._updateWakeModeItem();
|
||||
},
|
||||
],
|
||||
['root-mode-value', this._onRootModeChanged.bind(this)],
|
||||
['show-settings-value', this._onShowSettingsButtonChanged.bind(this)],
|
||||
[
|
||||
'show-shutdown-slider-value',
|
||||
() => this._onShowSliderChanged('shutdown'),
|
||||
],
|
||||
['show-wake-slider-value', () => this._onShowSliderChanged('wake')],
|
||||
['show-wake-items-value', this._updateShownWakeItems.bind(this)],
|
||||
['show-shutdown-mode-value', this._updateShownModeItems.bind(this)],
|
||||
['shutdown-mode-value', this._onModeChange.bind(this)],
|
||||
[
|
||||
'shutdown-timestamp-value',
|
||||
this._onInternalShutdownTimestampChanged.bind(this),
|
||||
],
|
||||
].map(([label, func]) => settings.connect(`changed::${label}`, func));
|
||||
}
|
||||
|
||||
_onRootModeChanged() {
|
||||
Promise.all([
|
||||
ACTIONS.maybeStopRootModeProtection(this.internalScheduleInfo),
|
||||
ACTIONS.maybeStartRootModeProtection(this.internalScheduleInfo),
|
||||
]).then(() => {
|
||||
this._updateSwitchLabel();
|
||||
});
|
||||
}
|
||||
|
||||
_onModeChange() {
|
||||
// redo Root-mode protection
|
||||
ACTIONS.maybeStopRootModeProtection(this.internalScheduleInfo, true)
|
||||
.then(() => {
|
||||
this._updateCurrentMode();
|
||||
logDebug(`Shutdown mode: ${this.internalScheduleInfo.mode}`);
|
||||
guiIdle(this._updateSelectedModeItems.bind(this));
|
||||
})
|
||||
.then(() =>
|
||||
ACTIONS.maybeStartRootModeProtection(this.internalScheduleInfo)
|
||||
);
|
||||
}
|
||||
|
||||
_updateCurrentMode() {
|
||||
this.internalScheduleInfo = this.internalScheduleInfo.copy({
|
||||
mode: settings.get_string('shutdown-mode-value'),
|
||||
});
|
||||
|
||||
ACTIONS.onShutdownScheduleChange(this.internalScheduleInfo);
|
||||
this.updateShutdownInfo();
|
||||
}
|
||||
|
||||
_onInternalShutdownTimestampChanged() {
|
||||
this.internalScheduleInfo = this.internalScheduleInfo.copy({
|
||||
deadline: settings.get_int('shutdown-timestamp-value'),
|
||||
});
|
||||
|
||||
ACTIONS.onShutdownScheduleChange(this.internalScheduleInfo);
|
||||
this.switcher.setToggleState(this.internalScheduleInfo.scheduled);
|
||||
this.updateShutdownInfo();
|
||||
}
|
||||
|
||||
/* Schedule Info updates */
|
||||
_externalScheduleInfoTick(info, wakeInfo) {
|
||||
this.externalScheduleInfo = this.externalScheduleInfo.copy({
|
||||
...info,
|
||||
});
|
||||
this.externalWakeInfo = this.externalWakeInfo.copy({ ...wakeInfo });
|
||||
guiIdle(this.updateShutdownInfo.bind(this));
|
||||
}
|
||||
|
||||
_updateShownModeItems() {
|
||||
const activeModes = settings
|
||||
.get_string('show-shutdown-mode-value')
|
||||
.split(',')
|
||||
.map(s => s.trim().toLowerCase())
|
||||
.filter(s => MODES.includes(s));
|
||||
this.modeItems.forEach(([mode, item]) => {
|
||||
const position = activeModes.indexOf(mode);
|
||||
if (position > -1) {
|
||||
this.menu.moveMenuItem(item, position + 2);
|
||||
}
|
||||
item.visible = position > -1;
|
||||
});
|
||||
}
|
||||
|
||||
updateShutdownInfo() {
|
||||
let shutdownLabel;
|
||||
if (this.internalScheduleInfo.scheduled && this.checkRunning) {
|
||||
const secPassed = Math.max(0, -this.internalScheduleInfo.secondsLeft);
|
||||
shutdownLabel = _('Check %s for %s').format(
|
||||
this.internalScheduleInfo.modeText,
|
||||
durationString(secPassed)
|
||||
);
|
||||
} else {
|
||||
const info = this.externalScheduleInfo.isMoreUrgendThan(
|
||||
this.internalScheduleInfo
|
||||
)
|
||||
? this.externalScheduleInfo
|
||||
: this.internalScheduleInfo;
|
||||
shutdownLabel = info.label;
|
||||
}
|
||||
this.label.text =
|
||||
[shutdownLabel, this.externalWakeInfo.label]
|
||||
.filter(v => !!v)
|
||||
.join('\n') || _('Shutdown Timer');
|
||||
}
|
||||
|
||||
_updateSelectedModeItems() {
|
||||
this.modeItems.forEach(([mode, item]) => {
|
||||
item.setOrnament(
|
||||
mode === this.internalScheduleInfo.mode
|
||||
? PopupMenu.Ornament.DOT
|
||||
: PopupMenu.Ornament.NONE
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// update timer value if slider has changed
|
||||
_updateSlider(prefix) {
|
||||
this.sliders[prefix].value =
|
||||
settings.get_double(`${prefix}-slider-value`) / 100.0;
|
||||
}
|
||||
|
||||
_updateSwitchLabel() {
|
||||
const minutes = Math.abs(_getSliderMinutes('shutdown'));
|
||||
const timeStr = longDurationString(
|
||||
minutes,
|
||||
h => _n('%s hr', '%s hrs', h),
|
||||
m => _n('%s min', '%s mins', m)
|
||||
);
|
||||
this.switcher.label.text = settings.get_boolean('root-mode-value')
|
||||
? _('%s (protect)').format(timeStr)
|
||||
: timeStr;
|
||||
}
|
||||
|
||||
_updateWakeModeItem() {
|
||||
const minutes = Math.abs(_getSliderMinutes('wake'));
|
||||
this.wakeModeItem.label.text = C_('WakeButtonText', '%s %s').format(
|
||||
modeLabel('wake'),
|
||||
longDurationString(
|
||||
minutes,
|
||||
h => _n('%s hour', '%s hours', h),
|
||||
m => _n('%s minute', '%s minutes', m)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_onShowSettingsButtonChanged() {
|
||||
this.switcherSettingsButton.visible = settings.get_boolean(
|
||||
'show-settings-value'
|
||||
);
|
||||
}
|
||||
|
||||
_updateShownWakeItems() {
|
||||
this.wakeItems.forEach(item => {
|
||||
item.visible = settings.get_boolean('show-wake-items-value');
|
||||
});
|
||||
this._onShowSliderChanged('wake');
|
||||
}
|
||||
|
||||
_onShowSliderChanged(settingsPrefix) {
|
||||
this.sliderItems[settingsPrefix].visible =
|
||||
(settingsPrefix !== 'wake' ||
|
||||
settings.get_boolean('show-wake-items-value')) &&
|
||||
settings.get_boolean(`show-${settingsPrefix}-slider-value`);
|
||||
}
|
||||
|
||||
_startMode(mode) {
|
||||
settings.set_string('shutdown-mode-value', mode);
|
||||
ACTIONS.startSchedule(
|
||||
_getSliderMinutes('shutdown'),
|
||||
_getSliderMinutes('wake')
|
||||
);
|
||||
}
|
||||
|
||||
// toggle button starts/stops shutdown timer
|
||||
_onToggle() {
|
||||
if (this.switcher.state) {
|
||||
// start shutdown timer
|
||||
ACTIONS.startSchedule(
|
||||
_getSliderMinutes('shutdown'),
|
||||
_getSliderMinutes('wake')
|
||||
);
|
||||
} else {
|
||||
// stop shutdown timer
|
||||
ACTIONS.stopSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.infoFetcher.stop();
|
||||
this.settingsHandlerIds.forEach(handlerId => {
|
||||
settings.disconnect(handlerId);
|
||||
});
|
||||
this.settingsHandlerIds = [];
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param settingsObj
|
||||
* @param actions
|
||||
*/
|
||||
function init(settingsObj, actions) {
|
||||
settings = settingsObj;
|
||||
ACTIONS = actions;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function uninit() {
|
||||
settings = null;
|
||||
ACTIONS = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param prefix
|
||||
*/
|
||||
function _getSliderMinutes(prefix) {
|
||||
let sliderValue = settings.get_double(`${prefix}-slider-value`) / 100.0;
|
||||
const rampUp = settings.get_double(`nonlinear-${prefix}-slider-value`);
|
||||
const ramp = x => Math.expm1(rampUp * x) / Math.expm1(rampUp);
|
||||
return Math.floor(
|
||||
(rampUp === 0 ? sliderValue : ramp(sliderValue)) *
|
||||
settings.get_int(`${prefix}-max-timer-value`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @param connections
|
||||
*/
|
||||
function _connect(item, connections) {
|
||||
const handlerIds = connections.map(([label, func]) =>
|
||||
item.connect(label, func)
|
||||
);
|
||||
const destroyId = item.connect('destroy', () => {
|
||||
handlerIds.concat(destroyId).forEach(handlerId => {
|
||||
item.disconnect(handlerId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param settingsPrefix
|
||||
*/
|
||||
function _createSliderItem(settingsPrefix) {
|
||||
const sliderValue =
|
||||
settings.get_double(`${settingsPrefix}-slider-value`) / 100.0;
|
||||
const item = new PopupMenu.PopupBaseMenuItem({ activate: false });
|
||||
const sliderIcon = new St.Icon({
|
||||
icon_name:
|
||||
settingsPrefix === 'wake' ? 'alarm-symbolic' : 'system-shutdown-symbolic',
|
||||
style_class: 'popup-menu-icon',
|
||||
});
|
||||
item.add(sliderIcon);
|
||||
const slider = new Slider.Slider(sliderValue);
|
||||
_connect(slider, [
|
||||
[
|
||||
'notify::value',
|
||||
() => {
|
||||
settings.set_double(
|
||||
`${settingsPrefix}-slider-value`,
|
||||
slider.value * 100
|
||||
);
|
||||
},
|
||||
],
|
||||
]);
|
||||
item.add_child(slider);
|
||||
return [item, slider];
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
/**
|
||||
* RootMode module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported shutdown, shutdownCancel, wake, wakeCancel, installScript, uninstallScript */
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const logDebug = Me.imports.lib.Convenience.logDebug;
|
||||
|
||||
const { Gio, GLib } = imports.gi;
|
||||
|
||||
// translations
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param stream
|
||||
* @param cancellable
|
||||
*/
|
||||
function readLine(stream, cancellable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.read_line_async(0, cancellable, (s, res) => {
|
||||
try {
|
||||
const line = s.read_line_finish_utf8(res)[0];
|
||||
|
||||
if (line !== null) {
|
||||
resolve(line);
|
||||
} else {
|
||||
reject(new Error('No line was read!'));
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
function quoteEscape(str) {
|
||||
return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command asynchronously and check the exit status.
|
||||
*
|
||||
* If given, @cancellable can be used to stop the process before it finishes.
|
||||
*
|
||||
* @param {string[] | string} argv - a list of string arguments or command line that will be parsed
|
||||
* @param {Gio.Cancellable} [cancellable] - optional cancellable object
|
||||
* @param {boolean} shell - run command as shell command
|
||||
* @param logFunc
|
||||
* @returns {Promise<void>} - The process success
|
||||
*/
|
||||
function execCheck(
|
||||
argv,
|
||||
cancellable = null,
|
||||
shell = true,
|
||||
logFunc = undefined
|
||||
) {
|
||||
if (!shell && typeof argv === 'string') {
|
||||
argv = GLib.shell_parse_argv(argv)[1];
|
||||
}
|
||||
|
||||
const isRootProc = argv[0] && argv[0].endsWith('pkexec');
|
||||
|
||||
if (shell && Array.isArray(argv)) {
|
||||
argv = argv.map(c => `"${quoteEscape(c)}"`).join(' ');
|
||||
}
|
||||
let cancelId = 0;
|
||||
let proc = new Gio.Subprocess({
|
||||
argv: (shell ? ['/bin/sh', '-c'] : []).concat(argv),
|
||||
flags:
|
||||
logFunc !== undefined
|
||||
? Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
||||
: Gio.SubprocessFlags.NONE,
|
||||
});
|
||||
proc.init(cancellable);
|
||||
|
||||
if (cancellable instanceof Gio.Cancellable) {
|
||||
cancelId = cancellable.connect(() => {
|
||||
if (logFunc !== undefined) {
|
||||
if (isRootProc) {
|
||||
logFunc(`# ${_('Can not cancel root process!')}`);
|
||||
} else {
|
||||
logFunc(`[${_('CANCEL')}]`);
|
||||
}
|
||||
}
|
||||
proc.force_exit();
|
||||
});
|
||||
}
|
||||
let stdoutStream = null;
|
||||
let stderrStream = null;
|
||||
let stdCancel = null;
|
||||
|
||||
if (logFunc !== undefined) {
|
||||
stdoutStream = new Gio.DataInputStream({
|
||||
base_stream: proc.get_stdout_pipe(),
|
||||
close_base_stream: true,
|
||||
});
|
||||
|
||||
stderrStream = new Gio.DataInputStream({
|
||||
base_stream: proc.get_stderr_pipe(),
|
||||
close_base_stream: true,
|
||||
});
|
||||
const readNextLine = async (stream, prefix) => {
|
||||
stdCancel = new Gio.Cancellable();
|
||||
const line = await readLine(stream, stdCancel);
|
||||
logFunc(prefix + line);
|
||||
logDebug(line);
|
||||
return readNextLine(stream, prefix);
|
||||
};
|
||||
// read stdout and stderr asynchronously
|
||||
readNextLine(stdoutStream, '').catch(() => {});
|
||||
readNextLine(stderrStream, '# ').catch(() => {});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
proc.wait_check_async(null, (p, res) => {
|
||||
try {
|
||||
const success = p.wait_check_finish(res);
|
||||
if (stdCancel !== null) {
|
||||
stdCancel.cancel();
|
||||
}
|
||||
if (!success) {
|
||||
let status = p.get_exit_status();
|
||||
|
||||
throw new Gio.IOErrorEnum({
|
||||
code: Gio.io_error_from_errno(status),
|
||||
message: GLib.strerror(status),
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
} finally {
|
||||
const maybeCloseStream = stream => {
|
||||
if (stream !== null && !stream.is_closed()) {
|
||||
stream.close_async(0, null, (s, sRes) => {
|
||||
try {
|
||||
s.close_finish(sRes);
|
||||
} catch (e) {
|
||||
logError(e, 'StreamCloseError');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
maybeCloseStream(stdoutStream);
|
||||
maybeCloseStream(stderrStream);
|
||||
if (cancelId > 0) {
|
||||
cancellable.disconnect(cancelId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function installedScriptPath() {
|
||||
for (const name of [
|
||||
'shutdowntimerctl',
|
||||
`shutdowntimerctl-${GLib.get_user_name()}`,
|
||||
]) {
|
||||
const standard = GLib.find_program_in_path(name);
|
||||
if (standard !== null) {
|
||||
return standard;
|
||||
}
|
||||
for (const bindir of ['/usr/local/bin/', '/usr/bin/']) {
|
||||
const path = bindir + name;
|
||||
logDebug(`Looking for: ${path}`);
|
||||
if (Gio.File.new_for_path(path).query_exists(null)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @param cancellable
|
||||
* @param logFunc
|
||||
*/
|
||||
function _runInstaller(action, cancellable, logFunc) {
|
||||
const user = GLib.get_user_name();
|
||||
logDebug(`? installer.sh --tool-user ${user} ${action}`);
|
||||
return execCheck(
|
||||
[
|
||||
'pkexec',
|
||||
Me.dir.get_child('tool').get_child('installer.sh').get_path(),
|
||||
'--tool-user',
|
||||
user,
|
||||
action,
|
||||
],
|
||||
cancellable,
|
||||
false,
|
||||
logFunc
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cancellable
|
||||
* @param logFunc
|
||||
*/
|
||||
async function installScript(cancellable, logFunc) {
|
||||
// install for user if installed in the /home directory
|
||||
await _runInstaller('install', cancellable, logFunc);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cancellable
|
||||
* @param logFunc
|
||||
*/
|
||||
async function uninstallScript(cancellable, logFunc) {
|
||||
await _runInstaller('uninstall', cancellable, logFunc);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
* @param noScriptArgs
|
||||
*/
|
||||
function runWithScript(args, noScriptArgs) {
|
||||
const installedScript = installedScriptPath();
|
||||
if (installedScript !== null) {
|
||||
return execCheck(['pkexec', installedScript].concat(args), null, false);
|
||||
}
|
||||
if (noScriptArgs === undefined) {
|
||||
throw new Error(_('Privileged script installation required!'));
|
||||
}
|
||||
return execCheck(noScriptArgs, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param minutes
|
||||
* @param reboot
|
||||
*/
|
||||
function shutdown(minutes, reboot = false) {
|
||||
return runWithScript(
|
||||
[reboot ? 'reboot' : 'shutdown', `${minutes}`],
|
||||
['shutdown', reboot ? '-r' : '-P', `${minutes}`]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function shutdownCancel() {
|
||||
return runWithScript(['shutdown-cancel'], ['shutdown', '-c']);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param minutes
|
||||
*/
|
||||
function wake(minutes) {
|
||||
return runWithScript(['wake', `${minutes}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function wakeCancel() {
|
||||
return runWithScript(['wake-cancel']);
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* ScheduleInfo module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported ScheduleInfo */
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { durationString } = Me.imports.lib.Convenience;
|
||||
|
||||
const { GLib } = imports.gi;
|
||||
|
||||
// translations
|
||||
const Gettext = imports.gettext.domain('ShutdownTimer');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
var ScheduleInfo = class {
|
||||
constructor({ mode = '?', deadline = -1, external = false }) {
|
||||
this._v = { mode, deadline, external };
|
||||
}
|
||||
|
||||
copy(vals) {
|
||||
return new ScheduleInfo({ ...this._v, ...vals });
|
||||
}
|
||||
|
||||
get deadline() {
|
||||
return this._v.deadline;
|
||||
}
|
||||
|
||||
get external() {
|
||||
return this._v.external;
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this._v.mode;
|
||||
}
|
||||
|
||||
get scheduled() {
|
||||
return this.deadline > -1;
|
||||
}
|
||||
|
||||
get secondsLeft() {
|
||||
return this.deadline - GLib.DateTime.new_now_utc().to_unix();
|
||||
}
|
||||
|
||||
get minutes() {
|
||||
return Math.floor(this.secondsLeft / 60);
|
||||
}
|
||||
|
||||
get modeText() {
|
||||
const texts = {
|
||||
suspend: _('suspend'),
|
||||
poweroff: _('shutdown'),
|
||||
reboot: _('reboot'),
|
||||
wake: _('wakeup'),
|
||||
};
|
||||
return this.mode in texts ? texts[this.mode] : texts['poweroff'];
|
||||
}
|
||||
|
||||
get label() {
|
||||
let label = '';
|
||||
if (this.scheduled) {
|
||||
label = _('%s until %s').format(
|
||||
durationString(this.secondsLeft),
|
||||
this.modeText
|
||||
);
|
||||
if (this.external) {
|
||||
label = _('%s (sys)').format(label);
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
isMoreUrgendThan(otherInfo) {
|
||||
return (
|
||||
!otherInfo.scheduled ||
|
||||
(this.scheduled &&
|
||||
// external deadline is instant, internal deadline has 1 min slack time
|
||||
(this.external ? this.deadline : this.deadline + 58) <
|
||||
otherInfo.deadline)
|
||||
);
|
||||
}
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* ScreenModeAware module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported load, unload */
|
||||
const Main = imports.ui.main;
|
||||
|
||||
let sessionId = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param onSessionModeChange
|
||||
*/
|
||||
function load(onSessionModeChange) {
|
||||
if (sessionId === null) {
|
||||
sessionId = Main.sessionMode.connect('updated', session =>
|
||||
onSessionModeChange(session.currentMode)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unload() {
|
||||
if (sessionId !== null) {
|
||||
Main.sessionMode.disconnect(sessionId);
|
||||
sessionId = null;
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Textbox module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported showTextbox, hideAll, init, uninit */
|
||||
const Main = imports.ui.main;
|
||||
const { St, Clutter } = imports.gi;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const { Convenience } = Me.imports.lib;
|
||||
const { logDebug, throttleTimeout } = Convenience;
|
||||
let textboxes = [];
|
||||
|
||||
let throttleUpdate = null;
|
||||
let throttleUpdateCancel = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function init() {
|
||||
[throttleUpdate, throttleUpdateCancel] = throttleTimeout(_update, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function uninit() {
|
||||
throttleUpdate = null;
|
||||
throttleUpdateCancel = null;
|
||||
}
|
||||
|
||||
// show textbox with message
|
||||
/**
|
||||
*
|
||||
* @param textmsg
|
||||
*/
|
||||
function showTextbox(textmsg) {
|
||||
for (const t of textboxes) {
|
||||
// replace old textbox if it has the same text
|
||||
if (t.text === textmsg) {
|
||||
t['_hidden'] = 1;
|
||||
}
|
||||
}
|
||||
logDebug(`show textbox: ${textmsg}`);
|
||||
const textbox = new St.Label({
|
||||
style_class: 'textbox-label',
|
||||
text: textmsg,
|
||||
opacity: 0,
|
||||
});
|
||||
Main.uiGroup.add_actor(textbox);
|
||||
textboxes.unshift(textbox);
|
||||
throttleUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function _update() {
|
||||
// remove hidden textboxes
|
||||
textboxes = textboxes.filter(t => {
|
||||
if (t['_hidden']) {
|
||||
const sid = t['_sourceId'];
|
||||
if (sid) {
|
||||
clearTimeout(sid);
|
||||
}
|
||||
delete t['_sourceId'];
|
||||
t.destroy();
|
||||
}
|
||||
return !t['_hidden'];
|
||||
});
|
||||
const monitor = Main.layoutManager.primaryMonitor;
|
||||
let heightOffset = 0;
|
||||
textboxes.forEach((textbox, i) => {
|
||||
if (i === 0) {
|
||||
heightOffset = -textbox.height / 2;
|
||||
}
|
||||
textbox.set_position(
|
||||
monitor.x + Math.floor(monitor.width / 2 - textbox.width / 2),
|
||||
monitor.y + Math.floor(monitor.height / 2 + heightOffset)
|
||||
);
|
||||
heightOffset += textbox.height + 10;
|
||||
if (!('_sourceId' in textbox)) {
|
||||
// start fadeout of textbox after 3 seconds
|
||||
textbox['_sourceId'] = setTimeout(() => {
|
||||
textbox['_sourceId'] = 0;
|
||||
textbox.ease({
|
||||
opacity: 0,
|
||||
duration: 1000,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => {
|
||||
textbox['_hidden'] = 1;
|
||||
throttleUpdate();
|
||||
},
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
if (textbox['_sourceId']) {
|
||||
// set opacity before fadeout starts
|
||||
textbox.opacity =
|
||||
i === 0
|
||||
? 255
|
||||
: Math.max(25, 25 + 230 * (1 - heightOffset / (monitor.height / 2)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function hideAll() {
|
||||
throttleUpdateCancel();
|
||||
for (const t of textboxes) {
|
||||
t['_hidden'] = 1;
|
||||
}
|
||||
_update();
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Timer module
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported Timer */
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { RootMode, ScheduleInfo } = Me.imports.lib;
|
||||
const logDebug = Me.imports.lib.Convenience.logDebug;
|
||||
|
||||
const { Gio } = imports.gi;
|
||||
|
||||
/* TIMER */
|
||||
var Timer = class {
|
||||
constructor(callbackAction, initialMode = 'shutdown') {
|
||||
this._timerMaxSeconds = 0;
|
||||
this._timerCancel = null;
|
||||
this._callbackAction = callbackAction;
|
||||
this._tick = null;
|
||||
this._timerId = null;
|
||||
this.info = new ScheduleInfo.ScheduleInfo({ mode: initialMode });
|
||||
}
|
||||
|
||||
setTickCallback(tick) {
|
||||
this._tick = tick;
|
||||
}
|
||||
|
||||
adjustTo(info) {
|
||||
const newDeadline = info.deadline !== this.info.deadline;
|
||||
this.info = info;
|
||||
if (info.scheduled) {
|
||||
if (newDeadline) {
|
||||
// update proc process for new deadline
|
||||
this.startProcTimer();
|
||||
}
|
||||
this.startForegroundTick();
|
||||
logDebug(
|
||||
`Started timer: ${this.info.minutes}min remaining (deadline: ${this.info.deadline})`
|
||||
);
|
||||
} else {
|
||||
this.stopTimer();
|
||||
logDebug(
|
||||
`Stopped timer: ${this.info.minutes}min remaining (deadline: ${this.info.deadline})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stopTimer() {
|
||||
this.stopProcTimer();
|
||||
this.stopForeground();
|
||||
}
|
||||
|
||||
_maybeRunTimerAction() {
|
||||
if (this._timerCancel !== null) {
|
||||
// ensure timer action is only run once
|
||||
logDebug(`Running '${this.info.mode}' timer action...`);
|
||||
this._callbackAction(this.info.mode);
|
||||
}
|
||||
}
|
||||
|
||||
async startProcTimer() {
|
||||
// secondary timer witch calls a sleep process as timer
|
||||
this.stopProcTimer();
|
||||
|
||||
const secs = this.info.secondsLeft;
|
||||
this._timerCancel = new Gio.Cancellable();
|
||||
try {
|
||||
if (secs > 0) {
|
||||
await RootMode.execCheck(
|
||||
['sleep', `${secs}`],
|
||||
this._timerCancel,
|
||||
false
|
||||
);
|
||||
}
|
||||
this._maybeRunTimerAction();
|
||||
} catch {
|
||||
} finally {
|
||||
this._timerCancel = null;
|
||||
}
|
||||
}
|
||||
|
||||
stopProcTimer() {
|
||||
if (this._timerCancel !== null) {
|
||||
this._timerCancel.cancel();
|
||||
}
|
||||
this._timerCancel = null;
|
||||
}
|
||||
|
||||
_maybeTick() {
|
||||
if (this._tick !== null) {
|
||||
this._tick();
|
||||
}
|
||||
}
|
||||
|
||||
startForegroundTick() {
|
||||
if (this._timerId === null) {
|
||||
// primary timer which updates ticks every second
|
||||
this._timerId = setInterval(() => {
|
||||
this._maybeTick();
|
||||
if (!this.info.scheduled || this.info.secondsLeft < 0) {
|
||||
// timer completed
|
||||
this._maybeRunTimerAction();
|
||||
this.stopTimer();
|
||||
}
|
||||
}, 1000);
|
||||
this._maybeTick();
|
||||
}
|
||||
}
|
||||
|
||||
stopForeground() {
|
||||
if (this._timerId) {
|
||||
clearInterval(this._timerId);
|
||||
}
|
||||
this._timerId = null;
|
||||
}
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "Shutdown/reboot/suspend the device after a specific time or wake with a rtc alarm.\n\nThe screen-saver will not interrupt the timer. A privileged control script may be installed to control shutdown and rtcwake as user. Additionally, a check command may be configured before shutdown.",
|
||||
"gettext-domain": "ShutdownTimer",
|
||||
"name": "Shutdown Timer",
|
||||
"session-modes": [
|
||||
"user",
|
||||
"unlock-dialog"
|
||||
],
|
||||
"settings-schema": "org.gnome.shell.extensions.shutdowntimer-deminder",
|
||||
"shell-version": [
|
||||
"42"
|
||||
],
|
||||
"url": "https://github.com/Deminder/ShutdownTimer",
|
||||
"uuid": "ShutdownTimer@deminder",
|
||||
"version": 31
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.freedesktop.policykit.exec" &&
|
||||
action.lookup("program") == "{{TOOL_OUT}}" &&
|
||||
subject.isInGroup("{{TOOL_USER}}"))
|
||||
{
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
polkit.addRule(function (action, subject) {
|
||||
var idx = action.id.lastIndexOf(".");
|
||||
var username_stripped = action.id.substring(0, idx);
|
||||
var username = action.id.substring(idx + 1);
|
||||
if (username_stripped === "{{RULE_BASE}}") {
|
||||
if (subject.user === username) {
|
||||
return polkit.Result.YES;
|
||||
} else {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<vendor>Shutdown Timer</vendor>
|
||||
<vendor_url>https://github.com/Deminder/ShutdownTimer</vendor_url>
|
||||
|
||||
<action id="{{ACTION_ID}}">
|
||||
<description>Control shutdown and rtc wake alarm schedule</description>
|
||||
<description xml:lang="de">Steuerung des Ausschalt-Planers und des RTC-Weck-Alarms</description>
|
||||
<message>No Authorization required to control shutdown or rtc wake alarm.</message>
|
||||
<message xml:lang="de">Keine Autorisierung zur Steuerung des Ausschalt-Planers oder RTC-Weck-Alarms notwendig.</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">{{PATH}}</annotate>
|
||||
<annotate key="{{ACTION_BASE}.polkit-rule.version">3.0.0</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
@ -1,229 +0,0 @@
|
||||
/**
|
||||
* Extension preferences GUI
|
||||
*
|
||||
* @author Deminder <tremminder@gmail.com>
|
||||
* @copyright 2021
|
||||
* @license GNU General Public License v3.0
|
||||
*/
|
||||
/* exported init, fillPreferencesWindow */
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
const { Install, Convenience } = Me.imports.lib;
|
||||
const { logDebug } = Convenience;
|
||||
const { enableGuiIdle, disableGuiIdle, guiIdle, modeLabel, MODES } =
|
||||
Convenience;
|
||||
|
||||
const { GLib, Gtk, Gio } = imports.gi;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
}
|
||||
|
||||
const templateComponents = {
|
||||
shutdown: {
|
||||
'shutdown-mode': 'combo',
|
||||
'root-mode': 'switch',
|
||||
'show-end-session-dialog': 'switch',
|
||||
'shutdown-max-timer': 'adjustment',
|
||||
'shutdown-slider': 'adjustment',
|
||||
'nonlinear-shutdown-slider': 'adjustment',
|
||||
},
|
||||
wake: {
|
||||
'auto-wake': 'switch',
|
||||
'wake-max-timer': 'adjustment',
|
||||
'wake-slider': 'adjustment',
|
||||
'nonlinear-wake-slider': 'adjustment',
|
||||
},
|
||||
display: {
|
||||
'show-settings': 'switch',
|
||||
'show-shutdown-mode': 'buffer',
|
||||
'show-shutdown-slider': 'switch',
|
||||
'show-textboxes': 'switch',
|
||||
'show-wake-slider': 'switch',
|
||||
'show-wake-items': 'switch',
|
||||
},
|
||||
check: {
|
||||
'check-command': 'textbuffer',
|
||||
'enable-check-command': 'switch',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pageId
|
||||
* @param builder
|
||||
* @param settings
|
||||
* @param handlers
|
||||
*/
|
||||
function initPage(pageId, builder, settings, handlers) {
|
||||
const page = builder.get_object(pageId);
|
||||
const pageName = pageId.split('_').at(-1);
|
||||
if (!page) {
|
||||
throw new Error(`${pageId} not found!`);
|
||||
}
|
||||
|
||||
const connectComp = (baseName, component) => {
|
||||
const baseId = baseName.replaceAll('-', '_');
|
||||
const settingsName = `${baseName}-value`;
|
||||
const compId = `${baseId}_${component}`;
|
||||
const comp = builder.get_object(compId);
|
||||
if (!comp) {
|
||||
throw new Error(`Component not found in template: ${compId}`);
|
||||
}
|
||||
if (compId === 'shutdown_mode_combo') {
|
||||
// replace combo box entries
|
||||
comp.remove_all();
|
||||
MODES.forEach(mode => {
|
||||
comp.append(mode, modeLabel(mode));
|
||||
});
|
||||
}
|
||||
|
||||
settings.bind(
|
||||
settingsName,
|
||||
comp,
|
||||
{
|
||||
adjustment: 'value',
|
||||
switch: 'active',
|
||||
textbuffer: 'text',
|
||||
buffer: 'text',
|
||||
combo: 'active-id',
|
||||
}[component],
|
||||
Gio.SettingsBindFlags.DEFAULT
|
||||
);
|
||||
|
||||
if (compId === 'show_shutdown_mode_buffer') {
|
||||
builder
|
||||
.get_object(`${baseId}_entry`)
|
||||
.set_placeholder_text(
|
||||
`${MODES.join(',')} (${MODES.map(modeLabel).join(', ')})`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (pageName in templateComponents) {
|
||||
for (const [k, v] of Object.entries(templateComponents[pageName])) {
|
||||
connectComp(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
const lineIter = (buffer, lineIndex) => {
|
||||
const [ok, iter] = buffer.get_iter_at_line(lineIndex);
|
||||
if (!ok) {
|
||||
throw new Error(`Line ${lineIndex} not found!`);
|
||||
}
|
||||
return iter;
|
||||
};
|
||||
|
||||
if (pageName === 'check') {
|
||||
// check command textbuffer
|
||||
const checkCommandBuffer = builder.get_object('check_command_textbuffer');
|
||||
const commentTag = new Gtk.TextTag({ foreground: 'grey' });
|
||||
checkCommandBuffer.get_tag_table().add(commentTag);
|
||||
const applyCommentTags = b => {
|
||||
b.remove_tag(commentTag, b.get_start_iter(), b.get_end_iter());
|
||||
const lc = b.get_line_count();
|
||||
for (let i = 0; i < lc; i++) {
|
||||
const startIter = lineIter(b, i);
|
||||
const endIter = lc === i + 1 ? b.get_end_iter() : lineIter(b, i + 1);
|
||||
const line = b.get_text(startIter, endIter, false);
|
||||
if (line.trimLeft().startsWith('#')) {
|
||||
b.apply_tag(commentTag, startIter, endIter);
|
||||
}
|
||||
}
|
||||
};
|
||||
applyCommentTags(checkCommandBuffer);
|
||||
handlers.push([
|
||||
checkCommandBuffer,
|
||||
checkCommandBuffer.connect('changed', applyCommentTags),
|
||||
]);
|
||||
} else if (pageName === 'install') {
|
||||
// install log textbuffer updates
|
||||
const logTextBuffer = builder.get_object('install_log_textbuffer');
|
||||
const scrollAdj = builder.get_object('installer_scrollbar_adjustment');
|
||||
const errorTag = new Gtk.TextTag({ foreground: 'red' });
|
||||
const successTag = new Gtk.TextTag({ foreground: 'green' });
|
||||
logTextBuffer.get_tag_table().add(errorTag);
|
||||
logTextBuffer.get_tag_table().add(successTag);
|
||||
const appendLogLine = line => {
|
||||
line = ['[', '#'].includes(line[0]) ? line : ` ${line}`;
|
||||
logTextBuffer.insert(logTextBuffer.get_end_iter(), `${line}\n`, -1);
|
||||
const lastLineIndex = logTextBuffer.get_line_count() - 1;
|
||||
const applyTag = tag => {
|
||||
logTextBuffer.apply_tag(
|
||||
tag,
|
||||
lineIter(logTextBuffer, lastLineIndex - 1),
|
||||
lineIter(logTextBuffer, lastLineIndex)
|
||||
);
|
||||
};
|
||||
if (line.startsWith('# ')) {
|
||||
applyTag(errorTag);
|
||||
} else if (line.endsWith('🟢')) {
|
||||
applyTag(successTag);
|
||||
}
|
||||
guiIdle(() => scrollAdj.set_value(1000000));
|
||||
};
|
||||
|
||||
const installSwitch = builder.get_object('install_policy_switch');
|
||||
installSwitch.set_active(Install.checkInstalled());
|
||||
const switchHandlerId = installSwitch.connect('notify::active', () =>
|
||||
Install.installAction(
|
||||
installSwitch.get_active() ? 'install' : 'uninstall',
|
||||
message => guiIdle(() => message.split('\n').forEach(appendLogLine))
|
||||
)
|
||||
);
|
||||
handlers.push([installSwitch, switchHandlerId]);
|
||||
|
||||
// clear log
|
||||
guiIdle(() => logTextBuffer.set_text('', -1));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param window
|
||||
*/
|
||||
function fillPreferencesWindow(window) {
|
||||
const builder = Gtk.Builder.new();
|
||||
builder.add_from_file(
|
||||
Me.dir.get_child('ui').get_child('prefs.ui').get_path()
|
||||
);
|
||||
|
||||
const settings = ExtensionUtils.getSettings();
|
||||
const handlers = [];
|
||||
const pageNames = ['install', 'shutdown', 'wake', 'display', 'check'].map(
|
||||
n => `shutdowntimer-prefs-${n}`
|
||||
);
|
||||
enableGuiIdle();
|
||||
for (const page of pageNames.map(name =>
|
||||
initPage(name.replaceAll('-', '_'), builder, settings, handlers)
|
||||
)) {
|
||||
window.add(page);
|
||||
}
|
||||
const selPageName =
|
||||
pageNames[settings.get_int('preferences-selected-page-value')];
|
||||
if (selPageName) {
|
||||
window.set_visible_page_name(selPageName);
|
||||
}
|
||||
const pageVisHandlerId = window.connect('notify::visible-page-name', () => {
|
||||
logDebug(window.get_visible_page_name());
|
||||
settings.set_int(
|
||||
'preferences-selected-page-value',
|
||||
pageNames.indexOf(window.get_visible_page_name())
|
||||
);
|
||||
});
|
||||
handlers.push([window, pageVisHandlerId]);
|
||||
// release all resources on destroy
|
||||
const destroyId = window.connect('destroy', () => {
|
||||
disableGuiIdle();
|
||||
handlers.forEach(([comp, handlerId]) => {
|
||||
comp.disconnect(handlerId);
|
||||
});
|
||||
Install.reset();
|
||||
window.disconnect(destroyId);
|
||||
});
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="ShutdownTimer">
|
||||
<schema path="/org/gnome/shell/extensions/shutdowntimer-deminder/" id="org.gnome.shell.extensions.shutdowntimer-deminder">
|
||||
|
||||
<key type="i" name="shutdown-max-timer-value">
|
||||
<default>180</default>
|
||||
<summary>Maximum shutdown time (in minutes)</summary>
|
||||
<description>Set maximum selectable shutdown time of the slider (in minutes). Use only values greater zero.</description>
|
||||
</key>
|
||||
|
||||
<key type="i" name="wake-max-timer-value">
|
||||
<default>1440</default>
|
||||
<summary>Maximum wake time (in minutes)</summary>
|
||||
<description>Set maximum selectable wake time of the slider (in minutes). Use only values greater zero.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="auto-wake-value">
|
||||
<default>false</default>
|
||||
<summary>Automatically start and stop wake on shutdown timer toggle</summary>
|
||||
<description>Enable/Disable the wake alarm when the shutdown timer is started/stopped.</description>
|
||||
</key>
|
||||
|
||||
<key type="i" name="shutdown-timestamp-value">
|
||||
<default>-1</default>
|
||||
<summary>Scheduled shutdown timestamp.</summary>
|
||||
<description>Unix time in seconds of scheduled shutdown or -1 if disabled.</description>
|
||||
</key>
|
||||
|
||||
<key type="d" name="wake-slider-value">
|
||||
<default>70</default>
|
||||
<summary>Wake slider position (in percent)</summary>
|
||||
<description>Set wake slider position as percent of the maximum time. Must be in range 0 and 100.</description>
|
||||
</key>
|
||||
|
||||
<key type="d" name="nonlinear-wake-slider-value">
|
||||
<default>1.5</default>
|
||||
<summary>Ramp-up of non-linear wake slider value</summary>
|
||||
<description>Exponential ramp-up for wake time slider</description>
|
||||
</key>
|
||||
|
||||
<key type="d" name="shutdown-slider-value">
|
||||
<default>70</default>
|
||||
<summary>Shutdown slider position (in percent)</summary>
|
||||
<description>Set shutdown slider position as percent of the maximum time. Must be in range 0 and 100.</description>
|
||||
</key>
|
||||
|
||||
<key type="d" name="nonlinear-shutdown-slider-value">
|
||||
<default>0</default>
|
||||
<summary>Ramp-up of non-linear shutdown slider value</summary>
|
||||
<description>Exponential ramp-up for shutdown time slider</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-settings-value">
|
||||
<default>true</default>
|
||||
<summary>Show settings button</summary>
|
||||
<description>Show/hide settings button in widget.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-shutdown-slider-value">
|
||||
<default>true</default>
|
||||
<summary>Show shutdown slider</summary>
|
||||
<description>Show/hide shutdown slider in widget.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-wake-slider-value">
|
||||
<default>true</default>
|
||||
<summary>Show wake slider</summary>
|
||||
<description>Show/hide wake slider in widget.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-wake-items-value">
|
||||
<default>false</default>
|
||||
<summary>Show all wake items</summary>
|
||||
<description>Show/hide all wake items in widget.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-textboxes-value">
|
||||
<default>true</default>
|
||||
<summary>Show notification text boxes</summary>
|
||||
<description>Show/hide notification text boxes on screen.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="root-mode-value">
|
||||
<default>false</default>
|
||||
<summary>Root mode</summary>
|
||||
<description>Set root mode on/off. In root mode powering off is done via 'pkexec' and 'shutdown' terminal command.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="show-end-session-dialog-value">
|
||||
<default>true</default>
|
||||
<summary>Show end-session dialog</summary>
|
||||
<description>Show the end-session dialog for reboot and shutdown if screensaver is inactive.</description>
|
||||
</key>
|
||||
|
||||
<key type="s" name="show-shutdown-mode-value">
|
||||
<default>"poweroff,suspend"</default>
|
||||
<summary>Shown shutdown modes</summary>
|
||||
<description>Comma-separated shutdown modes which are shown in the popup menu</description>
|
||||
</key>
|
||||
|
||||
<key type="s" name="shutdown-mode-value">
|
||||
<default>"poweroff"</default>
|
||||
<summary>Use mode</summary>
|
||||
<description>Mode to use for timer action</description>
|
||||
</key>
|
||||
|
||||
<key type="s" name="check-command-value">
|
||||
<default>"# Examples ...
|
||||
|
||||
# Run a pre-shutdown script (e.g. updates or backups)
|
||||
# ~/.local/scripts/pre-shutdown.sh
|
||||
|
||||
# Wait for a process to exit
|
||||
# tail -f --pid=$PID
|
||||
|
||||
# Activate the screen saver
|
||||
# xdg-screensaver activate"</default>
|
||||
<summary>Check command(s)</summary>
|
||||
<description>Run command(s) before shutdown command. Proceed with shutdown only if check command succeeds.</description>
|
||||
</key>
|
||||
|
||||
<key type="b" name="enable-check-command-value">
|
||||
<default>true</default>
|
||||
<summary>Enable check command</summary>
|
||||
<description>Check command is skipped if disabled.</description>
|
||||
</key>
|
||||
|
||||
<key type="i" name="preferences-selected-page-value">
|
||||
<default>0</default>
|
||||
<summary>Last selected page in the preferences.</summary>
|
||||
<description>Last selected page in the preferences.</description>
|
||||
</key>
|
||||
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
AUTHOR: Daniel Neumann
|
||||
**/
|
||||
|
||||
.textbox-label {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: rgba(10, 10, 10, 0.7);
|
||||
border-radius: 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
padding: 4px;
|
||||
border-radius: 32px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.settings-button:hover,
|
||||
.settings-button:focus {
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.settings-button > StIcon {
|
||||
icon-size: 15px;
|
||||
}
|
@ -1,261 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# installer.sh - This script installs a policykit rule for the Shutdown Timer gnome-shell extension.
|
||||
#
|
||||
# This file is part of the gnome-shell extension ShutdownTimer@Deminder.
|
||||
|
||||
# Authors: Martin Koppehel <psl.kontakt@gmail.com>, Fin Christensen <christensen.fin@gmail.com> (cpupower extension), Deminder <tremminder@gmail.com>
|
||||
|
||||
set -e
|
||||
|
||||
################################
|
||||
# EXTENSION SPECIFIC OPTIONS: #
|
||||
################################
|
||||
|
||||
EXTENSION_NAME="Shutdown Timer"
|
||||
ACTION_BASE="dem.shutdowntimer"
|
||||
RULE_BASE="$ACTION_BASE.settimers"
|
||||
CFC_BASE="shutdowntimerctl"
|
||||
POLKIT_DIR="polkit"
|
||||
VERSION=1
|
||||
|
||||
|
||||
EXIT_SUCCESS=0
|
||||
EXIT_INVALID_ARG=1
|
||||
EXIT_FAILED=2
|
||||
EXIT_NEEDS_UPDATE=3
|
||||
EXIT_NEEDS_SECURITY_UPDATE=4
|
||||
EXIT_NOT_INSTALLED=5
|
||||
EXIT_MUST_BE_ROOT=6
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #stackoverflow 59895
|
||||
|
||||
export TEXTDOMAINDIR="$DIR/../locale"
|
||||
export TEXTDOMAIN="ShutdownTimer"
|
||||
function gtxt() {
|
||||
gettext "$1"
|
||||
}
|
||||
|
||||
function recent_polkit() {
|
||||
printf -v versions '%s\n%s' "$(pkaction --version | cut -d' ' -f3)" "0.106"
|
||||
if [[ $versions != "$(sort -V <<< "$versions")" ]];then
|
||||
echo "available"
|
||||
else
|
||||
echo "unavailable"
|
||||
fi
|
||||
}
|
||||
|
||||
function check_support() {
|
||||
RECENT_STR=", stand-alone polkit rules $(recent_polkit)"
|
||||
if which rtcwake >/dev/null 2>&1
|
||||
then
|
||||
echo "rtcwake supported${RECENT_STR}"
|
||||
exit ${EXIT_SUCCESS}
|
||||
else
|
||||
echo "rtcwake unsupported${RECENT_STR}"
|
||||
exit ${EXIT_FAILED}
|
||||
fi
|
||||
}
|
||||
|
||||
function fail() {
|
||||
echo "$(gtxt "Failed")${1}" >&2 && exit ${EXIT_FAILED}
|
||||
}
|
||||
DEFAULT_SUCCESS_MSG=$(gtxt 'Success')
|
||||
|
||||
function success() {
|
||||
echo -n "${1:-$DEFAULT_SUCCESS_MSG}"
|
||||
echo -e "\U1F7E2"
|
||||
}
|
||||
|
||||
|
||||
|
||||
########################
|
||||
# GENERALIZED SCRIPT: #
|
||||
########################
|
||||
|
||||
function usage() {
|
||||
echo "Usage: installer.sh [options] {supported,install,check,update,uninstall}"
|
||||
echo
|
||||
echo "Available options:"
|
||||
echo " --tool-user USER Set the user of the tool (default: \$USER)"
|
||||
echo
|
||||
exit ${EXIT_INVALID_ARG}
|
||||
}
|
||||
|
||||
if [ $# -lt 1 ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
ACTION=""
|
||||
TOOL_USER="$USER"
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
key="$1"
|
||||
|
||||
# we have to use command line arguments here as pkexec does not support
|
||||
# setting environment variables
|
||||
case $key in
|
||||
--tool-user)
|
||||
TOOL_USER="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
supported|install|check|update|uninstall)
|
||||
if [ -z "$ACTION" ]
|
||||
then
|
||||
ACTION="$1"
|
||||
else
|
||||
echo "Too many actions specified. Please give at most 1."
|
||||
usage
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument $key"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
CFC_DIR="/usr/local/bin"
|
||||
RULE_DIR="/etc/polkit-1/rules.d"
|
||||
|
||||
RULE_IN="${DIR}/../${POLKIT_DIR}/10-$RULE_BASE.rules"
|
||||
if [[ "$(recent_polkit)" != "available" ]];then
|
||||
RULE_IN="${RULE_IN}.legacy"
|
||||
ACTION_IN="${DIR}/../${POLKIT_DIR}/${ACTION_BASE}.policy.in"
|
||||
fi
|
||||
TOOL_IN="${DIR}/$CFC_BASE"
|
||||
|
||||
TOOL_OUT="${CFC_DIR}/${CFC_BASE}-${TOOL_USER}"
|
||||
RULE_OUT="${RULE_DIR}/10-${RULE_BASE}-${TOOL_USER}.rules"
|
||||
ACTION_ID="${RULE_BASE}.${TOOL_USER}"
|
||||
ACTION_OUT="/usr/share/polkit-1/actions/${ACTION_ID}.policy"
|
||||
|
||||
function print_policy_xml() {
|
||||
sed -e "s:{{PATH}}:${TOOL_OUT}:g" \
|
||||
-e "s:{{ACTION_BASE}}:${ACTION_BASE}:g" \
|
||||
-e "s:{{ACTION_ID}}:${ACTION_ID}:g" "${ACTION_IN}"
|
||||
}
|
||||
|
||||
function print_rules_javascript() {
|
||||
if [[ "$RULE_IN" == *.legacy ]]; then
|
||||
sed -e "s:{{RULE_BASE}}:${RULE_BASE}:g" "${RULE_IN}"
|
||||
else
|
||||
sed -e "s:{{TOOL_OUT}}:${TOOL_OUT}:g" \
|
||||
-e "s:{{TOOL_USER}}:${TOOL_USER}:g" "${RULE_IN}"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
if [ "$ACTION" = "supported" ]
|
||||
then
|
||||
check_support
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "check" ]
|
||||
then
|
||||
if ! print_rules_javascript | cmp --silent "${RULE_OUT}"
|
||||
then
|
||||
if [ -f "${ACTION_OUT}" ]
|
||||
then
|
||||
echo "Your $EXTENSION_NAME installation needs updating!"
|
||||
exit ${EXIT_NEEDS_UPDATE}
|
||||
else
|
||||
echo "Not installed"
|
||||
exit ${EXIT_NOT_INSTALLED}
|
||||
fi
|
||||
fi
|
||||
echo "Installed"
|
||||
|
||||
exit ${EXIT_SUCCESS}
|
||||
fi
|
||||
|
||||
TOOL_NAME=$(basename ${TOOL_OUT})
|
||||
|
||||
if [ "$ACTION" = "install" ]
|
||||
then
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
echo "The install action must be run as root for security reasons!"
|
||||
echo "Please have a look at https://github.com/martin31821/cpupower/issues/102"
|
||||
echo "for further details."
|
||||
exit ${EXIT_MUST_BE_ROOT}
|
||||
fi
|
||||
|
||||
echo -n "$(gtxt 'Installing') ${TOOL_NAME} $(gtxt 'tool')... "
|
||||
mkdir -p "${CFC_DIR}"
|
||||
install "${TOOL_IN}" "${TOOL_OUT}" || fail
|
||||
success
|
||||
|
||||
if [ ! -z "$ACTION_IN" ];then
|
||||
echo "$(gtxt 'Using legacy policykit install')..."
|
||||
echo -n "$(gtxt 'Installing') $(gtxt 'policykit action')..."
|
||||
(print_policy_xml > "${ACTION_OUT}" 2>/dev/null && chmod 0644 "${ACTION_OUT}") || fail
|
||||
success
|
||||
fi
|
||||
|
||||
echo -n "$(gtxt 'Installing') $(gtxt 'policykit rule')..."
|
||||
mkdir -p "${RULE_DIR}"
|
||||
(print_rules_javascript > "${RULE_OUT}" 2>/dev/null && chmod 0644 "${RULE_OUT}") || fail
|
||||
success
|
||||
|
||||
exit ${EXIT_SUCCESS}
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "update" ]
|
||||
then
|
||||
"${BASH_SOURCE[0]}" --tool-user "${TOOL_USER}" uninstall || exit $?
|
||||
"${BASH_SOURCE[0]}" --tool-user "${TOOL_USER}" install || exit $?
|
||||
|
||||
exit ${EXIT_SUCCESS}
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "uninstall" ]
|
||||
then
|
||||
LEG_CFG_OUT="/usr/bin/shutdowntimerctl-$TOOL_USER"
|
||||
if [ -f "$LEG_CFG_OUT" ]
|
||||
then
|
||||
# remove legacy "tool" install
|
||||
echo -n "$(gtxt 'Uninstalling') $(gtxt 'tool')..."
|
||||
rm "${LEG_CFG_OUT}" || fail " - $(gtxt 'cannot remove') ${LEG_CFG_OUT}" && success
|
||||
fi
|
||||
|
||||
if [ -f "$ACTION_OUT" ]
|
||||
then
|
||||
# remove legacy "policykit action" install
|
||||
echo -n "$(gtxt 'Uninstalling') $(gtxt 'policykit action')..."
|
||||
rm "${ACTION_OUT}" || fail " - $(gtxt 'cannot remove') ${ACTION_OUT}" && success
|
||||
fi
|
||||
LEG_RULE_OUT="/usr/share/polkit-1/rules.d/10-dem.shutdowntimer.settimers.rules"
|
||||
if [ -f "$LEG_RULE_OUT" ]
|
||||
then
|
||||
# remove legacy "policykit action" install
|
||||
echo -n "$(gtxt 'Uninstalling') $(gtxt 'policykit rule')..."
|
||||
rm "${LEG_RULE_OUT}" || fail " - $(gtxt 'cannot remove') ${LEG_RULE_OUT}" && success
|
||||
fi
|
||||
|
||||
echo -n "$(gtxt 'Uninstalling') ${TOOL_NAME} $(gtxt 'tool')... "
|
||||
if [ -f "${TOOL_OUT}" ]
|
||||
then
|
||||
rm "${TOOL_OUT}" || fail " - $(gtxt 'cannot remove') ${TOOL_OUT}" && success
|
||||
else
|
||||
echo "$(gtxt 'tool') $(gtxt 'not installed at') ${TOOL_OUT}"
|
||||
fi
|
||||
|
||||
echo -n "$(gtxt 'Uninstalling') $(gtxt 'policykit rule')... "
|
||||
if [ -f "${RULE_OUT}" ]
|
||||
then
|
||||
rm "${RULE_OUT}" || fail " - $(gtxt 'cannot remove') ${RULE_OUT}" && success
|
||||
else
|
||||
echo "$(gtxt 'policy rule') $(gtxt 'not installed at') ${RULE_OUT}"
|
||||
fi
|
||||
|
||||
exit ${EXIT_SUCCESS}
|
||||
fi
|
||||
|
||||
echo "Unknown parameter."
|
||||
usage
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shutdowntimerctl - This script can configure the shutdown and rtc wake alarm schedule.
|
||||
#
|
||||
# This file is part of the gnome-shell extension ShutdownTimer@Deminder.
|
||||
|
||||
SHUTDOWN_BIN=/usr/sbin/shutdown
|
||||
RTCWAKE_BIN=/usr/sbin/rtcwake
|
||||
|
||||
SHUTDOWN_MODE="-P"
|
||||
if [ ! -z "$2" ] && [ "$2" -gt 0 ];then
|
||||
POSITIVE_VALUE="$2"
|
||||
fi
|
||||
|
||||
function print_help() {
|
||||
echo "[help] (show this help)" >&2
|
||||
echo "[shutdown|reboot] {MINUTES}" >&2
|
||||
echo "[wake|wake-cancel] {MINUTES} (default: 0)" >&2
|
||||
}
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
print_help
|
||||
exit
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
shutdown|reboot)
|
||||
if [[ "$1" = "reboot" ]]; then
|
||||
SHUTDOWN_MODE="-r"
|
||||
fi
|
||||
$SHUTDOWN_BIN "$SHUTDOWN_MODE" "$POSITIVE_VALUE"
|
||||
;;
|
||||
shutdown-cancel)
|
||||
$SHUTDOWN_BIN -c
|
||||
;;
|
||||
wake|wake-cancel)
|
||||
if [ -z "$POSITIVE_VALUE" ];then
|
||||
$RTCWAKE_BIN --mode disable
|
||||
else
|
||||
$RTCWAKE_BIN --date +${POSITIVE_VALUE}min --mode on &
|
||||
PID=$!
|
||||
sleep 0.2
|
||||
kill $PID
|
||||
fi
|
||||
;;
|
||||
-h|help)
|
||||
print_help
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $1" >&2
|
||||
print_help
|
||||
esac
|
||||
|
@ -1,456 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkTextBuffer" id="check_command_textbuffer" />
|
||||
<object class="GtkTextBuffer" id="install_log_textbuffer">
|
||||
<property name="text">START test
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
DONE test</property>
|
||||
</object>
|
||||
<object class="GtkEntryBuffer" id="install_policy_prefix_buffer" />
|
||||
<object class="GtkAdjustment" id="installer_scrollbar_adjustment">
|
||||
<property name="upper">1000000</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="nonlinear_shutdown_slider_adjustment">
|
||||
<property name="lower">-5</property>
|
||||
<property name="upper">5</property>
|
||||
<property name="step-increment">0.001</property>
|
||||
<property name="page-increment">0.10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="nonlinear_wake_slider_adjustment">
|
||||
<property name="lower">-5</property>
|
||||
<property name="upper">5</property>
|
||||
<property name="step-increment">0.001</property>
|
||||
<property name="page-increment">0.10</property>
|
||||
</object>
|
||||
<object class="GtkEntryBuffer" id="show_shutdown_mode_buffer" />
|
||||
<object class="GtkAdjustment" id="shutdown_max_timer_adjustment">
|
||||
<property name="upper">10000</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="shutdown_slider_adjustment">
|
||||
<property name="upper">100</property>
|
||||
<property name="step-increment">0.1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="wake_max_timer_adjustment">
|
||||
<property name="upper">10000</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="wake_slider_adjustment">
|
||||
<property name="upper">100</property>
|
||||
<property name="step-increment">0.1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="AdwPreferencesPage" id="shutdowntimer_prefs_install">
|
||||
<property name="name">shutdowntimer-prefs-install</property>
|
||||
<property name="title" translatable="yes">Install</property>
|
||||
<property name="icon-name">go-up-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Install/Uninstall privileges for this user</property>
|
||||
<property name="subtitle" translatable="yes">Setup a privileged script and give user access via polkit</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="install_policy_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesRow">
|
||||
<property name="title" translatable="yes">Install output</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="vadjustment">installer_scrollbar_adjustment</property>
|
||||
<property name="min-content-height">350</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="focusable">1</property>
|
||||
<property name="top-margin">5</property>
|
||||
<property name="editable">0</property>
|
||||
<property name="wrap-mode">char</property>
|
||||
<property name="cursor-visible">0</property>
|
||||
<property name="buffer">install_log_textbuffer</property>
|
||||
<property name="accepts-tab">0</property>
|
||||
<property name="monospace">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwPreferencesPage" id="shutdowntimer_prefs_shutdown">
|
||||
<property name="name">shutdowntimer-prefs-shutdown</property>
|
||||
<property name="title" translatable="yes">Shutdown</property>
|
||||
<property name="icon-name">preferences-system-time-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Timer Action</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Use mode</property>
|
||||
<property name="subtitle" translatable="yes">Mode to use for timer action</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="shutdown_mode_combo">
|
||||
<property name="valign">center</property>
|
||||
<property name="active">0</property>
|
||||
<items>
|
||||
<item id="poweroff">Shutdown</item>
|
||||
<item id="suspend">Suspend</item>
|
||||
<item id="reboot">Reboot</item>
|
||||
<item id="DO NOT EDIT">(replaced in code)</item>
|
||||
</items>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Show end-session dialog</property>
|
||||
<property name="subtitle" translatable="yes">Shown for reboot and shutdown if screensaver is inactive</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_end_session_dialog_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Toggle root shutdown with shutdown timer</property>
|
||||
<property name="subtitle" translatable="yes">Runs extra 'shutdown -P/-r' command for shutdown or reboot</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="root_mode_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Timer Input</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Shutdown slider position (in &#37;)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="shutdown_slider_spinbutton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">shutdown_slider_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesRow">
|
||||
<property name="title" translatable="yes">Shutdown slider position (in &#37;)</property>
|
||||
<child>
|
||||
<object class="GtkScale">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="draw-value">1</property>
|
||||
<property name="adjustment">shutdown_slider_adjustment</property>
|
||||
<property name="show-fill-level">1</property>
|
||||
<property name="round-digits">1</property>
|
||||
<property name="digits">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Non-linear scaling or 0 to disable</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">nonlinear_shutdown_slider_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="digits">3</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
<property name="value">120</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Maximum shutdown timer value (in minutes)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="shutdown_max_timer_spinbutton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">shutdown_max_timer_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
<property name="value">120</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwPreferencesPage" id="shutdowntimer_prefs_wake">
|
||||
<property name="name">shutdowntimer-prefs-wake</property>
|
||||
<property name="title" translatable="yes">Wake</property>
|
||||
<property name="icon-name">alarm-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Timer Action</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Toggle wake with timer action</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="auto_wake_switch">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Timer Input</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Wake slider position (in &#37;)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="wake_slider_spinbutton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">wake_slider_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesRow">
|
||||
<property name="title" translatable="yes">Wake slider position (in &#37;)</property>
|
||||
<child>
|
||||
<object class="GtkScale">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="draw-value">1</property>
|
||||
<property name="adjustment">wake_slider_adjustment</property>
|
||||
<property name="show-fill-level">1</property>
|
||||
<property name="round-digits">1</property>
|
||||
<property name="digits">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Non-linear scaling or 0 to disable</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">nonlinear_wake_slider_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="digits">3</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
<property name="value">120</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Maximum wake timer value (in minutes)</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="wake_max_timer_spinbutton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">wake_max_timer_adjustment</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="snap-to-ticks">1</property>
|
||||
<property name="numeric">1</property>
|
||||
<property name="update-policy">if-valid</property>
|
||||
<property name="value">120</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwPreferencesPage" id="shutdowntimer_prefs_display">
|
||||
<property name="name">shutdowntimer-prefs-display</property>
|
||||
<property name="title" translatable="yes">Display</property>
|
||||
<property name="icon-name">preferences-color-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">General</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Show settings button</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_settings_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Show notification text boxes</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_textboxes_switch">
|
||||
<property name="valign">center</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Shutdown</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Show shutdown items</property>
|
||||
<property name="subtitle" translatable="yes">Comma-separated shutdown modes which are shown in the popup menu</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesRow">
|
||||
<property name="title" translatable="yes">Show shutdown items</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="show_shutdown_mode_entry">
|
||||
<property name="valign">center</property>
|
||||
<property name="buffer">show_shutdown_mode_buffer</property>
|
||||
<property name="placeholder-text">poweroff, reboot, suspend</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Show shutdown slider</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_shutdown_slider_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Wake</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Show wake items</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_wake_items_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Show wake slider</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="show_wake_slider_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwPreferencesPage" id="shutdowntimer_prefs_check">
|
||||
<property name="name">shutdowntimer-prefs-check</property>
|
||||
<property name="title" translatable="yes">Check</property>
|
||||
<property name="icon-name">emblem-ok-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="title" translatable="yes">Enable check</property>
|
||||
<property name="subtitle" translatable="yes">Script is run in a bash user shell after timer activates. Timer action proceeds if check terminates successfully.</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="enable_check_command_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesRow">
|
||||
<property name="title" translatable="yes">Check script</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="min-content-height">350</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="focusable">1</property>
|
||||
<property name="top-margin">5</property>
|
||||
<property name="buffer">check_command_textbuffer</property>
|
||||
<property name="monospace">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
@ -1,339 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
@ -1,564 +0,0 @@
|
||||
const {Clutter, Gio, St, GObject} = imports.gi;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Main = imports.ui.main;
|
||||
const Util = imports.misc.util;
|
||||
const Mainloop = imports.mainloop;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Sensors = Me.imports.sensors;
|
||||
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
|
||||
const _ = Gettext.gettext;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const Values = Me.imports.values;
|
||||
const Config = imports.misc.config;
|
||||
const MenuItem = Me.imports.menuItem;
|
||||
|
||||
let vitalsMenu;
|
||||
|
||||
var VitalsMenuButton = GObject.registerClass({
|
||||
GTypeName: 'VitalsMenuButton',
|
||||
}, class VitalsMenuButton extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(St.Align.START);
|
||||
|
||||
this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.vitals');
|
||||
|
||||
this._sensorIcons = {
|
||||
'temperature' : { 'icon': 'temperature-symbolic.svg' },
|
||||
'voltage' : { 'icon': 'voltage-symbolic.svg' },
|
||||
'fan' : { 'icon': 'fan-symbolic.svg' },
|
||||
'memory' : { 'icon': 'memory-symbolic.svg' },
|
||||
'processor' : { 'icon': 'cpu-symbolic.svg' },
|
||||
'system' : { 'icon': 'system-symbolic.svg' },
|
||||
'network' : { 'icon': 'network-symbolic.svg',
|
||||
'icon-rx': 'network-download-symbolic.svg',
|
||||
'icon-tx': 'network-upload-symbolic.svg' },
|
||||
'storage' : { 'icon': 'storage-symbolic.svg' },
|
||||
'battery' : { 'icon': 'battery-symbolic.svg' }
|
||||
}
|
||||
|
||||
this._warnings = [];
|
||||
this._sensorMenuItems = {};
|
||||
this._hotLabels = {};
|
||||
this._hotIcons = {};
|
||||
this._groups = {};
|
||||
this._widths = {};
|
||||
this._last_query = new Date().getTime();
|
||||
|
||||
this._sensors = new Sensors.Sensors(this._settings, this._sensorIcons);
|
||||
this._values = new Values.Values(this._settings, this._sensorIcons);
|
||||
this._menuLayout = new St.BoxLayout({
|
||||
vertical: false,
|
||||
clip_to_allocation: true,
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
reactive: true,
|
||||
x_expand: true,
|
||||
pack_start: false
|
||||
});
|
||||
|
||||
this._drawMenu();
|
||||
this.add_actor(this._menuLayout);
|
||||
this._settingChangedSignals = [];
|
||||
this._refreshTimeoutId = null;
|
||||
|
||||
this._addSettingChangedSignal('update-time', this._updateTimeChanged.bind(this));
|
||||
this._addSettingChangedSignal('position-in-panel', this._positionInPanelChanged.bind(this));
|
||||
|
||||
let settings = [ 'use-higher-precision', 'alphabetize', 'hide-zeros', 'fixed-widths', 'hide-icons', 'unit', 'memory-measurement', 'include-public-ip', 'network-speed-format', 'storage-measurement' ];
|
||||
for (let setting of Object.values(settings))
|
||||
this._addSettingChangedSignal(setting, this._redrawMenu.bind(this));
|
||||
|
||||
// add signals for show- preference based categories
|
||||
for (let sensor in this._sensorIcons)
|
||||
this._addSettingChangedSignal('show-' + sensor, this._showHideSensorsChanged.bind(this));
|
||||
|
||||
this._initializeMenu();
|
||||
|
||||
// start off with fresh sensors
|
||||
this._querySensors();
|
||||
|
||||
// start monitoring sensors
|
||||
this._initializeTimer();
|
||||
}
|
||||
|
||||
_initializeMenu() {
|
||||
// display sensor categories
|
||||
for (let sensor in this._sensorIcons) {
|
||||
// groups associated sensors under accordion menu
|
||||
if (sensor in this._groups) continue;
|
||||
|
||||
this._groups[sensor] = new PopupMenu.PopupSubMenuMenuItem(_(this._ucFirst(sensor)), true);
|
||||
this._groups[sensor].icon.gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons[sensor]['icon']);
|
||||
|
||||
// hide menu items that user has requested to not include
|
||||
if (!this._settings.get_boolean('show-' + sensor))
|
||||
this._groups[sensor].actor.hide();
|
||||
|
||||
if (!this._groups[sensor].status) {
|
||||
this._groups[sensor].status = this._defaultLabel();
|
||||
this._groups[sensor].actor.insert_child_at_index(this._groups[sensor].status, 4);
|
||||
this._groups[sensor].status.text = 'No Data';
|
||||
}
|
||||
|
||||
this.menu.addMenuItem(this._groups[sensor]);
|
||||
}
|
||||
|
||||
// add separator
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
let item = new PopupMenu.PopupBaseMenuItem({
|
||||
reactive: false,
|
||||
style_class: 'vitals-menu-button-container'
|
||||
});
|
||||
|
||||
let customButtonBox = new St.BoxLayout({
|
||||
style_class: 'vitals-button-box',
|
||||
vertical: false,
|
||||
clip_to_allocation: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
reactive: true,
|
||||
x_expand: true,
|
||||
pack_start: false
|
||||
});
|
||||
|
||||
// custom round refresh button
|
||||
let refreshButton = this._createRoundButton('view-refresh-symbolic', _('Refresh'));
|
||||
refreshButton.connect('clicked', (self) => {
|
||||
// force refresh by clearing history
|
||||
this._sensors.resetHistory();
|
||||
this._values.resetHistory();
|
||||
|
||||
// make sure timer fires at next full interval
|
||||
this._updateTimeChanged();
|
||||
|
||||
// refresh sensors now
|
||||
this._querySensors();
|
||||
});
|
||||
customButtonBox.add_actor(refreshButton);
|
||||
|
||||
// custom round monitor button
|
||||
let monitorButton = this._createRoundButton('org.gnome.SystemMonitor-symbolic', _('System Monitor'));
|
||||
monitorButton.connect('clicked', (self) => {
|
||||
this.menu._getTopMenu().close();
|
||||
Util.spawn([this._settings.get_string('monitor-cmd')]);
|
||||
});
|
||||
customButtonBox.add_actor(monitorButton);
|
||||
|
||||
// custom round preferences button
|
||||
let prefsButton = this._createRoundButton('preferences-system-symbolic', _('Preferences'));
|
||||
prefsButton.connect('clicked', (self) => {
|
||||
this.menu._getTopMenu().close();
|
||||
ExtensionUtils.openPrefs();
|
||||
});
|
||||
customButtonBox.add_actor(prefsButton);
|
||||
|
||||
// now add the buttons to the top bar
|
||||
item.actor.add_actor(customButtonBox);
|
||||
|
||||
// add buttons
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
// query sensors on menu open
|
||||
this._menuStateChangeId = this.menu.connect('open-state-changed', (self, isMenuOpen) => {
|
||||
if (isMenuOpen) {
|
||||
// make sure timer fires at next full interval
|
||||
this._updateTimeChanged();
|
||||
|
||||
// refresh sensors now
|
||||
this._querySensors();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_createRoundButton(iconName) {
|
||||
let button = new St.Button({
|
||||
style_class: 'message-list-clear-button button vitals-button-action'
|
||||
});
|
||||
|
||||
button.child = new St.Icon({
|
||||
icon_name: iconName
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
_removeMissingHotSensors(hotSensors) {
|
||||
for (let i = hotSensors.length - 1; i >= 0; i--) {
|
||||
let sensor = hotSensors[i];
|
||||
|
||||
// make sure default icon (if any) stays visible
|
||||
if (sensor == '_default_icon_') continue;
|
||||
|
||||
// removes sensors that are no longer available
|
||||
if (!this._sensorMenuItems[sensor]) {
|
||||
hotSensors.splice(i, 1);
|
||||
this._removeHotLabel(sensor);
|
||||
this._removeHotIcon(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
return hotSensors;
|
||||
}
|
||||
|
||||
_saveHotSensors(hotSensors) {
|
||||
// removes any sensors that may not currently be available
|
||||
hotSensors = this._removeMissingHotSensors(hotSensors);
|
||||
|
||||
this._settings.set_strv('hot-sensors', hotSensors.filter(
|
||||
function(item, pos) {
|
||||
return hotSensors.indexOf(item) == pos;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
_initializeTimer() {
|
||||
// used to query sensors and update display
|
||||
let update_time = this._settings.get_int('update-time');
|
||||
this._refreshTimeoutId = Mainloop.timeout_add_seconds(update_time, (self) => {
|
||||
// only update menu if we have hot sensors
|
||||
if (Object.values(this._hotLabels).length > 0)
|
||||
this._querySensors();
|
||||
|
||||
// keep the timer running
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
_createHotItem(key, value) {
|
||||
let icon = this._defaultIcon(key);
|
||||
this._hotIcons[key] = icon;
|
||||
this._menuLayout.add_actor(icon)
|
||||
|
||||
// don't add a label when no sensors are in the panel
|
||||
if (key == '_default_icon_') return;
|
||||
|
||||
let label = new St.Label({
|
||||
style_class: 'vitals-panel-label',
|
||||
text: (value)?value:'\u2026', // ...
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.START
|
||||
});
|
||||
|
||||
// attempt to prevent ellipsizes
|
||||
label.get_clutter_text().ellipsize = 0;
|
||||
|
||||
this._hotLabels[key] = label;
|
||||
|
||||
// support for fixed widths #55, save label (text) width
|
||||
this._widths[key] = label.width;
|
||||
|
||||
this._menuLayout.add_actor(label);
|
||||
}
|
||||
|
||||
_showHideSensorsChanged(self, sensor) {
|
||||
this._sensors.resetHistory();
|
||||
this._groups[sensor.substr(5)].visible = this._settings.get_boolean(sensor);
|
||||
}
|
||||
|
||||
_positionInPanelChanged() {
|
||||
this.container.get_parent().remove_actor(this.container);
|
||||
let position = this._positionInPanel();
|
||||
|
||||
// allows easily addressable boxes
|
||||
let boxes = {
|
||||
left: Main.panel._leftBox,
|
||||
center: Main.panel._centerBox,
|
||||
right: Main.panel._rightBox
|
||||
};
|
||||
|
||||
// update position when changed from preferences
|
||||
boxes[position[0]].insert_child_at_index(this.container, position[1]);
|
||||
}
|
||||
|
||||
_removeHotLabel(key) {
|
||||
if (key in this._hotLabels) {
|
||||
let label = this._hotLabels[key];
|
||||
delete this._hotLabels[key];
|
||||
// make sure set_label is not called on non existant actor
|
||||
label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_removeHotLabels() {
|
||||
for (let key in this._hotLabels)
|
||||
this._removeHotLabel(key);
|
||||
}
|
||||
|
||||
_removeHotIcon(key) {
|
||||
if (key in this._hotIcons) {
|
||||
this._hotIcons[key].destroy();
|
||||
delete this._hotIcons[key];
|
||||
}
|
||||
}
|
||||
|
||||
_removeHotIcons() {
|
||||
for (let key in this._hotIcons)
|
||||
this._removeHotIcon(key);
|
||||
}
|
||||
|
||||
_redrawMenu() {
|
||||
this._removeHotIcons();
|
||||
this._removeHotLabels();
|
||||
|
||||
for (let key in this._sensorMenuItems) {
|
||||
if (key.includes('-group')) continue;
|
||||
this._sensorMenuItems[key].destroy();
|
||||
delete this._sensorMenuItems[key];
|
||||
}
|
||||
|
||||
this._drawMenu();
|
||||
this._sensors.resetHistory();
|
||||
this._values.resetHistory();
|
||||
this._querySensors();
|
||||
}
|
||||
|
||||
_drawMenu() {
|
||||
// grab list of selected menubar icons
|
||||
let hotSensors = this._settings.get_strv('hot-sensors');
|
||||
for (let key of Object.values(hotSensors)) {
|
||||
// fixes issue #225 which started when _max_ was moved to the end
|
||||
if (key == '__max_network-download__') key = '__network-rx_max__';
|
||||
if (key == '__max_network-upload__') key = '__network-tx_max__';
|
||||
|
||||
this._createHotItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
_destroyTimer() {
|
||||
// invalidate and reinitialize timer
|
||||
if (this._refreshTimeoutId != null) {
|
||||
Mainloop.source_remove(this._refreshTimeoutId);
|
||||
this._refreshTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
_updateTimeChanged() {
|
||||
this._destroyTimer();
|
||||
this._initializeTimer();
|
||||
}
|
||||
|
||||
_addSettingChangedSignal(key, callback) {
|
||||
this._settingChangedSignals.push(this._settings.connect('changed::' + key, callback));
|
||||
}
|
||||
|
||||
_updateDisplay(label, value, type, key) {
|
||||
// update sensor value in menubar
|
||||
if (this._hotLabels[key]) {
|
||||
this._hotLabels[key].set_text(value);
|
||||
|
||||
// support for fixed widths #55
|
||||
if (this._settings.get_boolean('fixed-widths')) {
|
||||
// grab text box width and see if new text is wider than old text
|
||||
let width2 = this._hotLabels[key].get_clutter_text().width;
|
||||
if (width2 > this._widths[key]) {
|
||||
this._hotLabels[key].set_width(width2);
|
||||
this._widths[key] = width2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// have we added this sensor before?
|
||||
let item = this._sensorMenuItems[key];
|
||||
if (item) {
|
||||
// update sensor value in the group
|
||||
item.value = value;
|
||||
} else if (type.includes('-group')) {
|
||||
// update text next to group header
|
||||
let group = type.split('-')[0];
|
||||
if (this._groups[group]) {
|
||||
this._groups[group].status.text = value;
|
||||
this._sensorMenuItems[type] = this._groups[group];
|
||||
}
|
||||
} else {
|
||||
// add item to group for the first time
|
||||
let sensor = { 'label': label, 'value': value, 'type': type }
|
||||
this._appendMenuItem(sensor, key);
|
||||
}
|
||||
}
|
||||
|
||||
_appendMenuItem(sensor, key) {
|
||||
let split = sensor.type.split('-');
|
||||
let type = split[0];
|
||||
let icon = (split.length == 2)?'icon-' + split[1]:'icon';
|
||||
let gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons[type][icon]);
|
||||
|
||||
let item = new MenuItem.MenuItem(gicon, key, sensor.label, sensor.value, this._hotLabels[key]);
|
||||
item.connect('toggle', (self) => {
|
||||
let hotSensors = this._settings.get_strv('hot-sensors');
|
||||
|
||||
if (self.checked) {
|
||||
// add selected sensor to panel
|
||||
hotSensors.push(self.key);
|
||||
this._createHotItem(self.key, self.value);
|
||||
} else {
|
||||
// remove selected sensor from panel
|
||||
hotSensors.splice(hotSensors.indexOf(self.key), 1);
|
||||
this._removeHotLabel(self.key);
|
||||
this._removeHotIcon(self.key);
|
||||
}
|
||||
|
||||
if (hotSensors.length <= 0) {
|
||||
// add generic icon to panel when no sensors are selected
|
||||
hotSensors.push('_default_icon_');
|
||||
this._createHotItem('_default_icon_');
|
||||
} else {
|
||||
let defIconPos = hotSensors.indexOf('_default_icon_');
|
||||
if (defIconPos >= 0) {
|
||||
// remove generic icon from panel when sensors are selected
|
||||
hotSensors.splice(defIconPos, 1);
|
||||
this._removeHotIcon('_default_icon_');
|
||||
}
|
||||
}
|
||||
|
||||
// this code is called asynchronously - make sure to save it for next round
|
||||
this._saveHotSensors(hotSensors);
|
||||
});
|
||||
|
||||
this._sensorMenuItems[key] = item;
|
||||
let i = Object.keys(this._sensorMenuItems[key]).length;
|
||||
|
||||
// alphabetize the sensors for these categories
|
||||
if (this._settings.get_boolean('alphabetize')) {
|
||||
let menuItems = this._groups[type].menu._getMenuItems();
|
||||
for (i = 0; i < menuItems.length; i++)
|
||||
// use natural sort order for system load, etc
|
||||
if (menuItems[i].label.localeCompare(item.label, undefined, { numeric: true, sensitivity: 'base' }) > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
this._groups[type].menu.addMenuItem(item, i);
|
||||
}
|
||||
|
||||
_defaultLabel() {
|
||||
return new St.Label({
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER
|
||||
});
|
||||
}
|
||||
|
||||
_defaultIcon(key) {
|
||||
let split = key.replaceAll('_', ' ').trim().split(' ')[0].split('-');
|
||||
let type = split[0];
|
||||
|
||||
let icon = new St.Icon({
|
||||
style_class: 'system-status-icon vitals-panel-icon-' + type,
|
||||
reactive: true
|
||||
});
|
||||
|
||||
// second condition prevents crash due to issue #225, which started when _max_ was moved to the end
|
||||
if (type == 'default' || !(type in this._sensorIcons)) {
|
||||
icon.gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons['system']['icon']);
|
||||
} else if (!this._settings.get_boolean('hide-icons')) { // support for hide icons #80
|
||||
let iconObj = (split.length == 2)?'icon-' + split[1]:'icon';
|
||||
icon.gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons[type][iconObj]);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
_ucFirst(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
_positionInPanel() {
|
||||
let alignment = '';
|
||||
let gravity = 0;
|
||||
let arrow_pos = 0;
|
||||
|
||||
switch (this._settings.get_int('position-in-panel')) {
|
||||
case 0: // left
|
||||
alignment = 'left';
|
||||
gravity = -1;
|
||||
arrow_pos = 1;
|
||||
break;
|
||||
case 1: // center
|
||||
alignment = 'center';
|
||||
gravity = -1;
|
||||
arrow_pos = 0.5;
|
||||
break;
|
||||
case 2: // right
|
||||
alignment = 'right';
|
||||
gravity = 0;
|
||||
arrow_pos = 0;
|
||||
break;
|
||||
case 3: // far left
|
||||
alignment = 'left';
|
||||
gravity = 0;
|
||||
arrow_pos = 1;
|
||||
break;
|
||||
case 4: // far right
|
||||
alignment = 'right';
|
||||
gravity = -1;
|
||||
arrow_pos = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// set arrow position when initializing and moving vitals
|
||||
this.menu._arrowAlignment = arrow_pos;
|
||||
|
||||
return [alignment, gravity];
|
||||
}
|
||||
|
||||
_querySensors() {
|
||||
// figure out last run time
|
||||
let now = new Date().getTime();
|
||||
let dwell = (now - this._last_query) / 1000;
|
||||
this._last_query = now;
|
||||
|
||||
this._sensors.query((label, value, type, format) => {
|
||||
let key = '_' + type.replace('-group', '') + '_' + label.replace(' ', '_').toLowerCase() + '_';
|
||||
|
||||
// if a sensor is disabled, gray it out
|
||||
if (key in this._sensorMenuItems) {
|
||||
this._sensorMenuItems[key].setSensitive((value!='disabled'));
|
||||
|
||||
// don't continue below, last known value is shown
|
||||
if (value == 'disabled') return;
|
||||
}
|
||||
|
||||
let items = this._values.returnIfDifferent(dwell, label, value, type, format, key);
|
||||
for (let item of Object.values(items))
|
||||
this._updateDisplay(_(item[0]), item[1], item[2], item[3]);
|
||||
}, dwell);
|
||||
|
||||
if (this._warnings.length > 0) {
|
||||
this._notify('Vitals', this._warnings.join("\n"), 'folder-symbolic');
|
||||
this._warnings = [];
|
||||
}
|
||||
}
|
||||
|
||||
_notify(msg, details, icon) {
|
||||
let source = new MessageTray.Source('MyApp Information', icon);
|
||||
Main.messageTray.add(source);
|
||||
let notification = new MessageTray.Notification(source, msg, details);
|
||||
notification.setTransient(true);
|
||||
source.notify(notification);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._destroyTimer();
|
||||
|
||||
for (let signal of Object.values(this._settingChangedSignals))
|
||||
this._settings.disconnect(signal);
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations('vitals');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
vitalsMenu = new VitalsMenuButton();
|
||||
let position = vitalsMenu._positionInPanel();
|
||||
Main.panel.addToStatusArea('vitalsMenu', vitalsMenu, position[1], position[0]);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
vitalsMenu.destroy();
|
||||
vitalsMenu = null;
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
Me.imports.helpers.polyfills;
|
||||
const ByteArray = imports.byteArray;
|
||||
|
||||
function getcontents(filename) {
|
||||
let handle = Gio.File.new_for_path(filename);
|
||||
let contents = handle.load_contents(null)[1];
|
||||
return ByteArray.toString(contents).trim();
|
||||
}
|
||||
|
||||
function File(path) {
|
||||
if (path.indexOf('https://') == -1)
|
||||
this.file = Gio.File.new_for_path(path);
|
||||
else
|
||||
this.file = Gio.File.new_for_uri(path);
|
||||
}
|
||||
|
||||
File.prototype.read = function(delimiter = '', strip_header = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.file.load_contents_async(null, function(file, res) {
|
||||
try {
|
||||
// grab contents of file or website
|
||||
let contents = file.load_contents_finish(res)[1];
|
||||
|
||||
// convert contents to string
|
||||
contents = ByteArray.toString(contents).trim();
|
||||
|
||||
// split contents by delimiter if passed in
|
||||
if (delimiter) contents = contents.split(delimiter);
|
||||
|
||||
// optionally strip header when converting to a list
|
||||
if (strip_header) contents.shift();
|
||||
|
||||
// return results
|
||||
resolve(contents);
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
File.prototype.list = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let max_items = 125, results = [];
|
||||
|
||||
try {
|
||||
this.file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_LOW, null, function(file, res) {
|
||||
try {
|
||||
let enumerator = file.enumerate_children_finish(res);
|
||||
|
||||
let callback = function(enumerator, res) {
|
||||
try {
|
||||
let files = enumerator.next_files_finish(res);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
results.push(files[i].get_attribute_as_string(Gio.FILE_ATTRIBUTE_STANDARD_NAME));
|
||||
}
|
||||
|
||||
if (files.length == 0) {
|
||||
enumerator.close_async(GLib.PRIORITY_LOW, null, function(){});
|
||||
resolve(results);
|
||||
} else {
|
||||
enumerator.next_files_async(max_items, GLib.PRIORITY_LOW, null, callback);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
enumerator.next_files_async(max_items, GLib.PRIORITY_LOW, null, callback);
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
});
|
||||
};
|
@ -1,184 +0,0 @@
|
||||
if (!String.prototype.includes) {
|
||||
String.prototype.includes = function(search, start) {
|
||||
'use strict';
|
||||
|
||||
if (typeof start !== 'number')
|
||||
start = 0;
|
||||
|
||||
if (start + search.length > this.length)
|
||||
return false;
|
||||
else
|
||||
return this.indexOf(search, start) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
// in parts of the system you may think that we can use Object.values
|
||||
// instead of "key in" statements. Gnome 3.18 - 3.22 doesn't like doing that.
|
||||
if (!Object.values)
|
||||
Object.values = obj => Object.keys(obj).map(key => obj[key]);
|
||||
|
||||
if (!Math.getMaxOfArray) {
|
||||
Math.getMaxOfArray = function(numArray) {
|
||||
return Math.max.apply(null, numArray);
|
||||
}
|
||||
}
|
||||
|
||||
// newer verisons of Gnome have Promises built in
|
||||
// Credit goes to https://github.com/satya164/gjs-helpers
|
||||
if (typeof Promise === 'undefined') {
|
||||
const GLib = imports.gi.GLib;
|
||||
|
||||
const PENDING = 0,
|
||||
FULFILLED = 1,
|
||||
REJECTED = 2;
|
||||
|
||||
Promise = function(executor) {
|
||||
if (false === (this instanceof Promise)) {
|
||||
throw new TypeError("Promises must be constructed via new");
|
||||
}
|
||||
|
||||
if (typeof executor !== "function") {
|
||||
throw new TypeError("Promise resolver " + executor + " is not a function");
|
||||
}
|
||||
|
||||
// Create an array to add handlers
|
||||
this._deferreds = [];
|
||||
|
||||
// Set the promise status
|
||||
this._state = PENDING;
|
||||
this._caught = false;
|
||||
|
||||
this._handle = deferred => {
|
||||
if (this._state === PENDING) {
|
||||
this._deferreds.push(deferred);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
let cb = this._state === FULFILLED ? deferred.onFulfilled : deferred.onRejected;
|
||||
|
||||
if (cb === null) {
|
||||
(this._state === FULFILLED ? deferred.resolve : deferred.reject)(this._value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof cb !== "function") {
|
||||
deferred.reject(this._value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
||||
try {
|
||||
ret = cb(this._value);
|
||||
} catch (e) {
|
||||
deferred.reject(e);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
deferred.resolve(ret);
|
||||
|
||||
return false; // Don't repeat
|
||||
});
|
||||
};
|
||||
|
||||
let doresolve = (fn, onFulfilled, onRejected) => {
|
||||
let done = false;
|
||||
|
||||
try {
|
||||
fn(value => {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
onFulfilled(value);
|
||||
}, function(reason) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
onRejected(reason);
|
||||
});
|
||||
} catch (e) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
onRejected(e);
|
||||
}
|
||||
};
|
||||
|
||||
let finale = () => {
|
||||
for (var i = 0, len = this._deferreds.length; i < len; i++) {
|
||||
this._handle.call(this, this._deferreds[i]);
|
||||
}
|
||||
|
||||
this._deferreds = null;
|
||||
};
|
||||
|
||||
let resolve = value => {
|
||||
// Call all fulfillment handlers one by one
|
||||
try {
|
||||
if (value === this) {
|
||||
throw new TypeError("A promise cannot be resolved with itself");
|
||||
}
|
||||
|
||||
if (value && (typeof value === "object" || typeof value === "function")) {
|
||||
// If returned value is a thenable, treat is as a promise
|
||||
if (typeof value.then === "function") {
|
||||
doresolve(value.then.bind(value), resolve.bind(this), reject.bind(this));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Promise is fulfilled
|
||||
this._state = FULFILLED;
|
||||
this._value = value;
|
||||
|
||||
finale.call(this);
|
||||
} catch (e) {
|
||||
reject.call(this, e);
|
||||
}
|
||||
};
|
||||
|
||||
let reject = reason => {
|
||||
// Promise is rejected
|
||||
this._state = REJECTED;
|
||||
this._value = reason;
|
||||
|
||||
finale.call(this);
|
||||
};
|
||||
|
||||
doresolve(executor, resolve.bind(this), reject.bind(this));
|
||||
};
|
||||
|
||||
// Appends fulfillment and rejection handlers to the promise
|
||||
Promise.prototype.then = function(onFulfilled, onRejected) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._handle.call(this, {
|
||||
resolve: resolve,
|
||||
reject: reject,
|
||||
onFulfilled: onFulfilled,
|
||||
onRejected: onRejected
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Appends a rejection handler callback to the promise
|
||||
Promise.prototype.catch = function(onRejected) {
|
||||
return this.then(null, onRejected);
|
||||
};
|
||||
|
||||
// Returns a Promise object that is rejected with the given reason
|
||||
Promise.reject = function(reason) {
|
||||
return new Promise((resolve, reject) => reject(reason));
|
||||
};
|
||||
|
||||
// Returns a Promise object that is resolved with the given value
|
||||
Promise.resolve = function(value) {
|
||||
return new Promise(resolve => resolve(value));
|
||||
};
|
||||
}
|
Before Width: | Height: | Size: 909 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 936 B |
Before Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 934 B |
Before Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.8 KiB |
@ -1,77 +0,0 @@
|
||||
const St = imports.gi.St;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
|
||||
var MenuItem = GObject.registerClass({
|
||||
|
||||
Signals: {
|
||||
'toggle': { param_types: [Clutter.Event.$gtype] },
|
||||
},
|
||||
|
||||
}, class MenuItem extends PopupMenu.PopupBaseMenuItem {
|
||||
|
||||
_init(icon, key, label, value, checked) {
|
||||
super._init({ reactive: true });
|
||||
|
||||
this._checked = checked;
|
||||
this._updateOrnament();
|
||||
|
||||
this._key = key;
|
||||
this._gIcon = icon;
|
||||
|
||||
// add icon
|
||||
this.add(new St.Icon({ style_class: 'popup-menu-icon', gicon : this._gIcon }));
|
||||
|
||||
// add label
|
||||
this._labelActor = new St.Label({ text: label });
|
||||
this.add(this._labelActor);
|
||||
|
||||
// add value
|
||||
this._valueLabel = new St.Label({ text: value });
|
||||
this._valueLabel.set_x_align(Clutter.ActorAlign.END);
|
||||
this._valueLabel.set_x_expand(true);
|
||||
this._valueLabel.set_y_expand(true);
|
||||
this.add(this._valueLabel);
|
||||
|
||||
this.actor._delegate = this;
|
||||
}
|
||||
|
||||
get checked() {
|
||||
return this._checked;
|
||||
}
|
||||
|
||||
get key() {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
get gicon() {
|
||||
return this._gIcon;
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
this._valueLabel.text = value;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._valueLabel.text;
|
||||
}
|
||||
|
||||
// prevents menu from being closed
|
||||
activate(event) {
|
||||
this._checked = !this._checked;
|
||||
this._updateOrnament();
|
||||
this.emit('toggle', event);
|
||||
}
|
||||
|
||||
_updateOrnament() {
|
||||
if (this._checked)
|
||||
this.setOrnament(PopupMenu.Ornament.CHECK);
|
||||
else
|
||||
this.setOrnament(PopupMenu.Ornament.NONE);
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this._labelActor.text;
|
||||
}
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "A glimpse into your computer's temperature, voltage, fan speed, memory usage, processor load, system resources, network speed and storage stats. This is a one stop shop to monitor all of your vital sensors. Uses asynchronous polling to provide a smooth user experience. Feature requests or bugs? Please use GitHub.",
|
||||
"gettext-domain": "vitals",
|
||||
"name": "Vitals",
|
||||
"settings-schema": "org.gnome.shell.extensions.vitals",
|
||||
"shell-version": [
|
||||
"3.38",
|
||||
"40",
|
||||
"41",
|
||||
"42",
|
||||
"43"
|
||||
],
|
||||
"url": "https://github.com/corecoding/Vitals",
|
||||
"uuid": "Vitals@CoreCoding.com",
|
||||
"version": 57
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
// https://gjs.guide/extensions/upgrading/gnome-shell-40.html#contents
|
||||
const Config = imports.misc.config;
|
||||
const [major] = Config.PACKAGE_VERSION.split('.');
|
||||
const shellVersion = Number.parseInt(major);
|
||||
const {Gio, Gtk, GObject} = imports.gi;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
/*
|
||||
if (sensor == 'show-storage' && this._settings.get_boolean(sensor)) {
|
||||
|
||||
let val = true;
|
||||
|
||||
try {
|
||||
let GTop = imports.gi.GTop;
|
||||
} catch (e) {
|
||||
val = false;
|
||||
}
|
||||
|
||||
let now = new Date().getTime();
|
||||
this._notify("Vitals", "Please run sudo apt install gir1.2-gtop-2.0", 'folder-symbolic');
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
const Settings = new GObject.Class({
|
||||
Name: 'Vitals.Settings',
|
||||
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
|
||||
this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.vitals');
|
||||
|
||||
this.builder = new Gtk.Builder();
|
||||
this.builder.set_translation_domain(Me.metadata['gettext-domain']);
|
||||
|
||||
if (shellVersion < 40)
|
||||
this.builder.add_from_file(Me.path + '/prefs.legacy.ui');
|
||||
else
|
||||
this.builder.add_from_file(Me.path + '/prefs.ui');
|
||||
|
||||
this.widget = this.builder.get_object('prefs-container');
|
||||
|
||||
this._bind_settings();
|
||||
},
|
||||
|
||||
// Bind the gtk window to the schema settings
|
||||
_bind_settings: function() {
|
||||
let widget;
|
||||
|
||||
// process sensor toggles
|
||||
let sensors = [ 'show-temperature', 'show-voltage', 'show-fan',
|
||||
'show-memory', 'show-processor', 'show-system',
|
||||
'show-network', 'show-storage', 'use-higher-precision',
|
||||
'alphabetize', 'hide-zeros', 'include-public-ip',
|
||||
'show-battery', 'fixed-widths', 'hide-icons' ];
|
||||
|
||||
for (let key in sensors) {
|
||||
let sensor = sensors[key];
|
||||
|
||||
widget = this.builder.get_object(sensor);
|
||||
widget.set_active(this._settings.get_boolean(sensor));
|
||||
widget.connect('state-set', (_, val) => {
|
||||
this._settings.set_boolean(sensor, val);
|
||||
});
|
||||
}
|
||||
|
||||
// process individual drop down sensor preferences
|
||||
sensors = [ 'position-in-panel', 'unit', 'network-speed-format', 'memory-measurement', 'storage-measurement', 'battery-slot' ];
|
||||
for (let key in sensors) {
|
||||
let sensor = sensors[key];
|
||||
|
||||
widget = this.builder.get_object(sensor);
|
||||
widget.set_active(this._settings.get_int(sensor));
|
||||
widget.connect('changed', (widget) => {
|
||||
this._settings.set_int(sensor, widget.get_active());
|
||||
});
|
||||
}
|
||||
|
||||
this._settings.bind('update-time', this.builder.get_object('update-time'), 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
// process individual text entry sensor preferences
|
||||
sensors = [ 'storage-path', 'monitor-cmd' ];
|
||||
for (let key in sensors) {
|
||||
let sensor = sensors[key];
|
||||
|
||||
widget = this.builder.get_object(sensor);
|
||||
widget.set_text(this._settings.get_string(sensor));
|
||||
|
||||
widget.connect('changed', (widget) => {
|
||||
let text = widget.get_text();
|
||||
if (!text) text = widget.get_placeholder_text();
|
||||
this._settings.set_string(sensor, text);
|
||||
});
|
||||
}
|
||||
|
||||
// makes individual sensor preference boxes appear
|
||||
sensors = [ 'temperature', 'network', 'storage', 'memory', 'battery', 'system' ];
|
||||
for (let key in sensors) {
|
||||
let sensor = sensors[key];
|
||||
|
||||
// create dialog for intelligent autohide advanced settings
|
||||
this.builder.get_object(sensor + '-prefs').connect('clicked', () => {
|
||||
let transientObj;
|
||||
if (shellVersion < 40)
|
||||
transientObj = this.widget.get_toplevel();
|
||||
else
|
||||
transientObj = this.widget.get_root();
|
||||
|
||||
let title = sensor.charAt(0).toUpperCase() + sensor.slice(1);
|
||||
let dialog = new Gtk.Dialog({ title: _(title + ' Preferences'),
|
||||
transient_for: transientObj,
|
||||
use_header_bar: false,
|
||||
modal: true });
|
||||
|
||||
let box = this.builder.get_object(sensor + '_prefs');
|
||||
|
||||
if (shellVersion < 40)
|
||||
dialog.get_content_area().add(box);
|
||||
else
|
||||
dialog.get_content_area().append(box);
|
||||
|
||||
dialog.connect('response', (dialog, id) => {
|
||||
// remove the settings box so it doesn't get destroyed;
|
||||
dialog.get_content_area().remove(box);
|
||||
dialog.destroy();
|
||||
return;
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
ExtensionUtils.initTranslations();
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
let settings = new Settings();
|
||||
let widget = settings.widget;
|
||||
|
||||
widget.show();
|
||||
return widget;
|
||||
}
|
@ -1,629 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2018, Chris Monahan <chris@corecoding.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 GObject = imports.gi.GObject;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const FileModule = Me.imports.helpers.file;
|
||||
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
|
||||
const _ = Gettext.gettext;
|
||||
const NM = imports.gi.NM;
|
||||
|
||||
let GTop, hasGTop = true;
|
||||
try {
|
||||
GTop = imports.gi.GTop;
|
||||
} catch (e) {
|
||||
global.log(e);
|
||||
hasGTop = false;
|
||||
}
|
||||
|
||||
var Sensors = GObject.registerClass({
|
||||
GTypeName: 'Sensors',
|
||||
}, class Sensors extends GObject.Object {
|
||||
_init(settings, sensorIcons) {
|
||||
this._settings = settings;
|
||||
this._sensorIcons = sensorIcons;
|
||||
|
||||
this.resetHistory();
|
||||
|
||||
this._last_processor = { 'core': {}, 'speed': [] };
|
||||
|
||||
if (hasGTop) {
|
||||
this.storage = new GTop.glibtop_fsusage();
|
||||
this._storageDevice = '';
|
||||
this._findStorageDevice();
|
||||
|
||||
this._lastRead = 0;
|
||||
this._lastWrite = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_refreshIPAddress(callback) {
|
||||
// check IP address
|
||||
new FileModule.File('https://corecoding.com/vitals.php').read().then(contents => {
|
||||
let obj = JSON.parse(contents);
|
||||
this._returnValue(callback, 'Public IP', obj['IPv4'], 'network', 'string');
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_findStorageDevice() {
|
||||
new FileModule.File('/proc/mounts').read("\n").then(lines => {
|
||||
for (let line of Object.values(lines)) {
|
||||
let loadArray = line.trim().split(/\s+/);
|
||||
if (loadArray[1] == this._settings.get_string('storage-path')) {
|
||||
this._storageDevice = loadArray[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
query(callback, dwell) {
|
||||
if (!this._hardware_detected) {
|
||||
// we could set _hardware_detected in discoverHardwareMonitors, but by
|
||||
// doing it here, we guarantee avoidance of race conditions
|
||||
this._hardware_detected = true;
|
||||
this._discoverHardwareMonitors(callback);
|
||||
}
|
||||
|
||||
for (let sensor in this._sensorIcons) {
|
||||
if (this._settings.get_boolean('show-' + sensor)) {
|
||||
if (sensor == 'temperature' || sensor == 'voltage' || sensor == 'fan') {
|
||||
// for temp, volt, fan, we have a shared handler
|
||||
this._queryTempVoltFan(callback, sensor);
|
||||
} else {
|
||||
// directly call queryFunction below
|
||||
let method = '_query' + sensor[0].toUpperCase() + sensor.slice(1);
|
||||
this[method](callback, dwell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_queryTempVoltFan(callback, type) {
|
||||
for (let label in this._tempVoltFanSensors[type]) {
|
||||
let sensor = this._tempVoltFanSensors[type][label];
|
||||
|
||||
new FileModule.File(sensor['path']).read().then(value => {
|
||||
this._returnValue(callback, label, value, type, sensor['format']);
|
||||
}).catch(err => {
|
||||
this._returnValue(callback, label, 'disabled', type, sensor['format']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_queryMemory(callback) {
|
||||
// check memory info
|
||||
new FileModule.File('/proc/meminfo').read().then(lines => {
|
||||
let total = 0, avail = 0, swapTotal = 0, swapFree = 0;
|
||||
|
||||
let values = lines.match(/MemTotal:(\s+)(\d+) kB/);
|
||||
if (values) total = values[2];
|
||||
|
||||
values = lines.match(/MemAvailable:(\s+)(\d+) kB/);
|
||||
if (values) avail = values[2];
|
||||
|
||||
values = lines.match(/SwapTotal:(\s+)(\d+) kB/);
|
||||
if (values) swapTotal = values[2];
|
||||
|
||||
values = lines.match(/SwapFree:(\s+)(\d+) kB/);
|
||||
if (values) swapFree = values[2];
|
||||
|
||||
let used = total - avail
|
||||
let utilized = used / total;
|
||||
|
||||
this._returnValue(callback, 'Usage', utilized, 'memory', 'percent');
|
||||
this._returnValue(callback, 'memory', utilized, 'memory-group', 'percent');
|
||||
this._returnValue(callback, 'Physical', total, 'memory', 'memory');
|
||||
this._returnValue(callback, 'Available', avail, 'memory', 'memory');
|
||||
this._returnValue(callback, 'Allocated', used, 'memory', 'memory');
|
||||
this._returnValue(callback, 'Swap', swapTotal - swapFree, 'memory', 'memory');
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_queryProcessor(callback, dwell) {
|
||||
let columns = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq', 'steal', 'guest', 'guest_nice'];
|
||||
|
||||
// check processor usage
|
||||
new FileModule.File('/proc/stat').read("\n").then(lines => {
|
||||
let statistics = {};
|
||||
|
||||
for (let line of Object.values(lines)) {
|
||||
let reverse_data = line.match(/^(cpu\d*\s)(.+)/);
|
||||
if (reverse_data) {
|
||||
let cpu = reverse_data[1].trim();
|
||||
|
||||
if (!(cpu in statistics))
|
||||
statistics[cpu] = {};
|
||||
|
||||
if (!(cpu in this._last_processor['core']))
|
||||
this._last_processor['core'][cpu] = 0;
|
||||
|
||||
let stats = reverse_data[2].trim().split(' ').reverse();
|
||||
for (let column of columns)
|
||||
statistics[cpu][column] = parseInt(stats.pop());
|
||||
}
|
||||
}
|
||||
|
||||
let cores = Object.keys(statistics).length - 1;
|
||||
|
||||
for (let cpu in statistics) {
|
||||
let total = statistics[cpu]['user'] + statistics[cpu]['nice'] + statistics[cpu]['system'];
|
||||
|
||||
// make sure we have data to report
|
||||
if (this._last_processor['core'][cpu] > 0) {
|
||||
let delta = (total - this._last_processor['core'][cpu]) / dwell;
|
||||
|
||||
// /proc/stat provides overall usage for us under the 'cpu' heading
|
||||
if (cpu == 'cpu') {
|
||||
delta = delta / cores;
|
||||
this._returnValue(callback, 'processor', delta / 100, 'processor-group', 'percent');
|
||||
this._returnValue(callback, 'Usage', delta / 100, 'processor', 'percent');
|
||||
} else {
|
||||
this._returnValue(callback, _('Core %d').format(cpu.substr(3)), delta / 100, 'processor', 'percent');
|
||||
}
|
||||
}
|
||||
|
||||
this._last_processor['core'][cpu] = total;
|
||||
}
|
||||
|
||||
// if frequency scaling is enabled, gather cpu-freq values
|
||||
if (!this._processor_uses_cpu_info) {
|
||||
for (let core = 0; core <= cores; core++) {
|
||||
new FileModule.File('/sys/devices/system/cpu/cpu' + core + '/cpufreq/scaling_cur_freq').read().then(value => {
|
||||
this._last_processor['speed'][core] = parseInt(value);
|
||||
}).catch(err => { });
|
||||
}
|
||||
}
|
||||
}).catch(err => { });
|
||||
|
||||
// if frequency scaling is disabled, use cpuinfo for speed
|
||||
if (this._processor_uses_cpu_info) {
|
||||
// grab CPU frequency
|
||||
new FileModule.File('/proc/cpuinfo').read("\n").then(lines => {
|
||||
let freqs = [];
|
||||
for (let line of Object.values(lines)) {
|
||||
// grab megahertz
|
||||
let value = line.match(/^cpu MHz(\s+): ([+-]?\d+(\.\d+)?)/);
|
||||
if (value) freqs.push(parseFloat(value[2]));
|
||||
}
|
||||
|
||||
let sum = freqs.reduce((a, b) => a + b);
|
||||
let hertz = (sum / freqs.length) * 1000 * 1000;
|
||||
this._returnValue(callback, 'Frequency', hertz, 'processor', 'hertz');
|
||||
|
||||
//let max_hertz = Math.getMaxOfArray(freqs) * 1000 * 1000;
|
||||
//this._returnValue(callback, 'Boost', max_hertz, 'processor', 'hertz');
|
||||
}).catch(err => { });
|
||||
// if frequency scaling is enabled, cpu-freq reports
|
||||
} else if (Object.values(this._last_processor['speed']).length > 0) {
|
||||
let sum = this._last_processor['speed'].reduce((a, b) => a + b);
|
||||
let hertz = (sum / this._last_processor['speed'].length) * 1000;
|
||||
this._returnValue(callback, 'Frequency', hertz, 'processor', 'hertz');
|
||||
//let max_hertz = Math.getMaxOfArray(this._last_processor['speed']) * 1000;
|
||||
//this._returnValue(callback, 'Boost', max_hertz, 'processor', 'hertz');
|
||||
}
|
||||
}
|
||||
|
||||
_querySystem(callback) {
|
||||
// check load average
|
||||
new FileModule.File('/proc/sys/fs/file-nr').read("\t").then(loadArray => {
|
||||
this._returnValue(callback, 'Open Files', loadArray[0], 'system', 'string');
|
||||
}).catch(err => { });
|
||||
|
||||
// check load average
|
||||
new FileModule.File('/proc/loadavg').read(' ').then(loadArray => {
|
||||
let proc = loadArray[3].split('/');
|
||||
|
||||
this._returnValue(callback, 'Load 1m', loadArray[0], 'system', 'load');
|
||||
this._returnValue(callback, 'system', loadArray[0], 'system-group', 'load');
|
||||
this._returnValue(callback, 'Load 5m', loadArray[1], 'system', 'load');
|
||||
this._returnValue(callback, 'Load 15m', loadArray[2], 'system', 'load');
|
||||
this._returnValue(callback, 'Threads Active', proc[0], 'system', 'string');
|
||||
this._returnValue(callback, 'Threads Total', proc[1], 'system', 'string');
|
||||
}).catch(err => { });
|
||||
|
||||
// check uptime
|
||||
new FileModule.File('/proc/uptime').read(' ').then(upArray => {
|
||||
this._returnValue(callback, 'Uptime', upArray[0], 'system', 'duration');
|
||||
|
||||
let cores = Object.keys(this._last_processor['core']).length - 1;
|
||||
if (cores > 0)
|
||||
this._returnValue(callback, 'Process Time', upArray[0] - upArray[1] / cores, 'processor', 'duration');
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_queryNetwork(callback, dwell) {
|
||||
// check network speed
|
||||
let directions = ['tx', 'rx'];
|
||||
let netbase = '/sys/class/net/';
|
||||
|
||||
new FileModule.File(netbase).list().then(interfaces => {
|
||||
for (let iface of interfaces) {
|
||||
for (let direction of directions) {
|
||||
// lo tx and rx are the same
|
||||
if (iface == 'lo' && direction == 'rx') continue;
|
||||
|
||||
new FileModule.File(netbase + iface + '/statistics/' + direction + '_bytes').read().then(value => {
|
||||
// issue #217 - don't include 'lo' traffic in Maximum calculations in values.js
|
||||
// by not using network-rx or network-tx
|
||||
let name = iface + ((iface == 'lo')?'':' ' + direction);
|
||||
|
||||
let type = 'network' + ((iface=='lo')?'':'-' + direction);
|
||||
this._returnValue(callback, name, value, type, 'storage');
|
||||
}).catch(err => { });
|
||||
}
|
||||
}
|
||||
}).catch(err => { });
|
||||
|
||||
// some may not want public ip checking
|
||||
if (this._settings.get_boolean('include-public-ip')) {
|
||||
// check the public ip every hour or when waking from sleep
|
||||
if (this._next_public_ip_check <= 0) {
|
||||
this._next_public_ip_check = 3600;
|
||||
|
||||
this._refreshIPAddress(callback);
|
||||
}
|
||||
|
||||
this._next_public_ip_check -= dwell;
|
||||
}
|
||||
|
||||
// wireless interface statistics
|
||||
new FileModule.File('/proc/net/wireless').read("\n", true).then(lines => {
|
||||
for (let line of Object.values(lines)) {
|
||||
let netArray = line.trim().split(/\s+/);
|
||||
let quality_pct = netArray[2].substr(0, netArray[2].length-1) / 70;
|
||||
let signal = netArray[3].substr(0, netArray[3].length-1);
|
||||
|
||||
this._returnValue(callback, 'WiFi Link Quality', quality_pct, 'network', 'percent');
|
||||
this._returnValue(callback, 'WiFi Signal Level', signal, 'network', 'string');
|
||||
}
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_queryStorage(callback, dwell) {
|
||||
// display zfs arc status, if available
|
||||
new FileModule.File('/proc/spl/kstat/zfs/arcstats').read().then(lines => {
|
||||
let target = 0, maximum = 0, current = 0;
|
||||
|
||||
let values = lines.match(/c(\s+)(\d+)(\s+)(\d+)/);
|
||||
if (values) target = values[4];
|
||||
|
||||
values = lines.match(/c_max(\s+)(\d+)(\s+)(\d+)/);
|
||||
if (values) maximum = values[4];
|
||||
|
||||
values = lines.match(/size(\s+)(\d+)(\s+)(\d+)/);
|
||||
if (values) current = values[4];
|
||||
|
||||
// ZFS statistics
|
||||
this._returnValue(callback, 'ARC Target', target, 'storage', 'storage');
|
||||
this._returnValue(callback, 'ARC Maximum', maximum, 'storage', 'storage');
|
||||
this._returnValue(callback, 'ARC Current', current, 'storage', 'storage');
|
||||
}).catch(err => { });
|
||||
|
||||
// check disk performance stats
|
||||
new FileModule.File('/proc/diskstats').read("\n").then(lines => {
|
||||
for (let line of Object.values(lines)) {
|
||||
let loadArray = line.trim().split(/\s+/);
|
||||
if ('/dev/' + loadArray[2] == this._storageDevice) {
|
||||
var read = (loadArray[5] * 512);
|
||||
var write = (loadArray[9] * 512);
|
||||
this._returnValue(callback, 'Read total', read, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Write total', write, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Read rate', (read - this._lastRead) / dwell, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Write rate', (write - this._lastWrite) / dwell, 'storage', 'storage');
|
||||
this._lastRead = read;
|
||||
this._lastWrite = write;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).catch(err => { });
|
||||
|
||||
// skip rest of stats if gtop not available
|
||||
if (!hasGTop) return;
|
||||
|
||||
GTop.glibtop_get_fsusage(this.storage, this._settings.get_string('storage-path'));
|
||||
|
||||
let total = this.storage.blocks * this.storage.block_size;
|
||||
let avail = this.storage.bavail * this.storage.block_size;
|
||||
let free = this.storage.bfree * this.storage.block_size;
|
||||
let used = total - free;
|
||||
let reserved = (total - avail) - used;
|
||||
|
||||
this._returnValue(callback, 'Total', total, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Used', used, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Reserved', reserved, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Free', avail, 'storage', 'storage');
|
||||
this._returnValue(callback, 'storage', avail, 'storage-group', 'storage');
|
||||
}
|
||||
|
||||
_queryBattery(callback) {
|
||||
let battery_slot = this._settings.get_int('battery-slot');
|
||||
|
||||
// addresses issue #161
|
||||
let batt_key = 'BAT';
|
||||
if (battery_slot == 3) {
|
||||
batt_key = 'CMB';
|
||||
battery_slot = 0;
|
||||
}
|
||||
|
||||
let battery_path = '/sys/class/power_supply/' + batt_key + battery_slot + '/';
|
||||
|
||||
new FileModule.File(battery_path + 'status').read().then(value => {
|
||||
this._returnValue(callback, 'State', value, 'battery', '');
|
||||
}).catch(err => { });
|
||||
|
||||
new FileModule.File(battery_path + 'cycle_count').read().then(value => {
|
||||
if (value > 0 || (value == 0 && !this._settings.get_boolean('hide-zeros')))
|
||||
this._returnValue(callback, 'Cycles', value, 'battery', '');
|
||||
}).catch(err => { });
|
||||
|
||||
new FileModule.File(battery_path + 'charge_full').read().then(charge_full => {
|
||||
new FileModule.File(battery_path + 'voltage_min_design').read().then(voltage_min_design => {
|
||||
this._returnValue(callback, 'Energy (full)', charge_full * voltage_min_design, 'battery', 'watt-hour');
|
||||
new FileModule.File(battery_path + 'charge_full_design').read().then(charge_full_design => {
|
||||
this._returnValue(callback, 'Capacity', (charge_full / charge_full_design), 'battery', 'percent');
|
||||
this._returnValue(callback, 'Energy (design)', charge_full_design * voltage_min_design, 'battery', 'watt-hour');
|
||||
}).catch(err => { });
|
||||
|
||||
new FileModule.File(battery_path + 'voltage_now').read().then(voltage_now => {
|
||||
this._returnValue(callback, 'Voltage', voltage_now / 1000, 'battery', 'in');
|
||||
|
||||
new FileModule.File(battery_path + 'current_now').read().then(current_now => {
|
||||
let watt = current_now * voltage_now;
|
||||
this._returnValue(callback, 'Rate', watt, 'battery', 'watt');
|
||||
this._returnValue(callback, 'battery', watt, 'battery-group', 'watt');
|
||||
|
||||
new FileModule.File(battery_path + 'charge_now').read().then(charge_now => {
|
||||
let rest_pwr = voltage_min_design * charge_now;
|
||||
this._returnValue(callback, 'Energy (now)', rest_pwr, 'battery', 'watt-hour');
|
||||
|
||||
//let time_left_h = rest_pwr / last_pwr;
|
||||
//this._returnValue(callback, 'time_left_h', time_left_h, 'battery', '');
|
||||
|
||||
let level = charge_now / charge_full;
|
||||
this._returnValue(callback, 'Percentage', level, 'battery', 'percent');
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => {
|
||||
new FileModule.File(battery_path + 'energy_full').read().then(energy_full => {
|
||||
new FileModule.File(battery_path + 'voltage_min_design').read().then(voltage_min_design => {
|
||||
this._returnValue(callback, 'Energy (full)', energy_full * 1000000, 'battery', 'watt-hour');
|
||||
new FileModule.File(battery_path + 'energy_full_design').read().then(energy_full_design => {
|
||||
this._returnValue(callback, 'Capacity', (energy_full / energy_full_design), 'battery', 'percent');
|
||||
this._returnValue(callback, 'Energy (design)', energy_full_design * 1000000, 'battery', 'watt-hour');
|
||||
}).catch(err => { });
|
||||
|
||||
new FileModule.File(battery_path + 'voltage_now').read().then(voltage_now => {
|
||||
this._returnValue(callback, 'Voltage', voltage_now / 1000, 'battery', 'in');
|
||||
|
||||
new FileModule.File(battery_path + 'power_now').read().then(power_now => {
|
||||
this._returnValue(callback, 'Rate', power_now * 1000000, 'battery', 'watt');
|
||||
this._returnValue(callback, 'battery', power_now * 1000000, 'battery-group', 'watt');
|
||||
|
||||
new FileModule.File(battery_path + 'energy_now').read().then(energy_now => {
|
||||
this._returnValue(callback, 'Energy (now)', energy_now * 1000000, 'battery', 'watt-hour');
|
||||
|
||||
//let time_left_h = energy_now / last_pwr;
|
||||
//this._returnValue(callback, 'time_left_h', time_left_h, 'battery', '');
|
||||
|
||||
let level = energy_now / energy_full;
|
||||
this._returnValue(callback, 'Percentage', level, 'battery', 'percent');
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
}).catch(err => { });
|
||||
});
|
||||
}
|
||||
|
||||
_returnValue(callback, label, value, type, format) {
|
||||
callback(label, value, type, format);
|
||||
}
|
||||
|
||||
_discoverHardwareMonitors(callback) {
|
||||
this._tempVoltFanSensors = { 'temperature': {}, 'voltage': {}, 'fan': {} };
|
||||
|
||||
let hwbase = '/sys/class/hwmon/';
|
||||
|
||||
// process sensor_types now so it is not called multiple times below
|
||||
let sensor_types = {};
|
||||
|
||||
if (this._settings.get_boolean('show-temperature'))
|
||||
sensor_types['temp'] = 'temperature';
|
||||
|
||||
if (this._settings.get_boolean('show-voltage'))
|
||||
sensor_types['in'] = 'voltage';
|
||||
|
||||
if (this._settings.get_boolean('show-fan'))
|
||||
sensor_types['fan'] = 'fan';
|
||||
|
||||
// a little informal, but this code has zero I/O block
|
||||
new FileModule.File(hwbase).list().then(files => {
|
||||
for (let file of files) {
|
||||
// grab name of sensor
|
||||
new FileModule.File(hwbase + file + '/name').read().then(name => {
|
||||
// are we dealing with a CPU?
|
||||
if (name == 'coretemp') {
|
||||
// determine which processor (socket) we are dealing with
|
||||
new FileModule.File(hwbase + file + '/temp1_label').read().then(prefix => {
|
||||
this._processTempVoltFan(callback, sensor_types, prefix, hwbase + file, file);
|
||||
}).catch(err => {
|
||||
// this shouldn't be necessary, but just in case temp1_label doesn't exist
|
||||
// attempt to fix #266
|
||||
this._processTempVoltFan(callback, sensor_types, name, hwbase + file, file);
|
||||
});
|
||||
} else {
|
||||
// not a CPU, process all other sensors
|
||||
this._processTempVoltFan(callback, sensor_types, name, hwbase + file, file);
|
||||
}
|
||||
}).catch(err => {
|
||||
new FileModule.File(hwbase + file + '/device/name').read().then(name => {
|
||||
this._processTempVoltFan(callback, sensor_types, name, hwbase + file + '/device', file);
|
||||
}).catch(err => { });
|
||||
});
|
||||
}
|
||||
}).catch(err => { });
|
||||
|
||||
// does this system support cpu scaling? if so we will use it to grab Frequency and Boost below
|
||||
new FileModule.File('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq').read().then(value => {
|
||||
this._processor_uses_cpu_info = false;
|
||||
}).catch(err => { });
|
||||
|
||||
// grab static CPU information
|
||||
new FileModule.File('/proc/cpuinfo').read("\n").then(lines => {
|
||||
let vendor_id = '';
|
||||
let bogomips = '';
|
||||
let sockets = {};
|
||||
let cache = '';
|
||||
|
||||
for (let line of Object.values(lines)) {
|
||||
// grab cpu vendor
|
||||
let value = line.match(/^vendor_id(\s+): (\w+.*)/);
|
||||
if (value) vendor_id = value[2];
|
||||
|
||||
// grab bogomips
|
||||
value = line.match(/^bogomips(\s+): (\d*\.?\d*)$/);
|
||||
if (value) bogomips = value[2];
|
||||
|
||||
// grab processor count
|
||||
value = line.match(/^physical id(\s+): (\d+)$/);
|
||||
if (value) sockets[value[2]] = 1;
|
||||
|
||||
// grab cache
|
||||
value = line.match(/^cache size(\s+): (\d+) KB$/);
|
||||
if (value) cache = value[2];
|
||||
}
|
||||
|
||||
this._returnValue(callback, 'Vendor', vendor_id, 'processor', 'string');
|
||||
this._returnValue(callback, 'Bogomips', bogomips, 'processor', 'string');
|
||||
this._returnValue(callback, 'Sockets', Object.keys(sockets).length, 'processor', 'string');
|
||||
this._returnValue(callback, 'Cache', cache, 'processor', 'memory');
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_processTempVoltFan(callback, sensor_types, name, path, file) {
|
||||
let sensor_files = [ 'input', 'label' ];
|
||||
|
||||
// grab files from directory
|
||||
new FileModule.File(path).list().then(files2 => {
|
||||
let trisensors = {};
|
||||
|
||||
// loop over files from directory
|
||||
for (let file2 of Object.values(files2)) {
|
||||
// simple way of processing input and label (from above)
|
||||
for (let key of Object.values(sensor_files)) {
|
||||
// process toggled on sensors from extension preferences
|
||||
for (let sensor_type in sensor_types) {
|
||||
if (file2.substr(0, sensor_type.length) == sensor_type && file2.substr(-(key.length+1)) == '_' + key) {
|
||||
let key2 = file + file2.substr(0, file2.indexOf('_'));
|
||||
|
||||
if (!(key2 in trisensors)) {
|
||||
trisensors[key2] = {
|
||||
'type': sensor_types[sensor_type],
|
||||
'format': sensor_type,
|
||||
'label': path + '/name'
|
||||
};
|
||||
}
|
||||
|
||||
trisensors[key2][key] = path + '/' + file2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let obj of Object.values(trisensors)) {
|
||||
if (!('input' in obj))
|
||||
continue;
|
||||
|
||||
new FileModule.File(obj['input']).read().then(value => {
|
||||
let extra = (obj['label'].indexOf('_label')==-1) ? ' ' + obj['input'].substr(obj['input'].lastIndexOf('/')+1).split('_')[0] : '';
|
||||
|
||||
if (value > 0 || !this._settings.get_boolean('hide-zeros') || obj['type'] == 'fan') {
|
||||
new FileModule.File(obj['label']).read().then(label => {
|
||||
this._addTempVoltFan(callback, obj, name, label, extra, value);
|
||||
}).catch(err => {
|
||||
let tmpFile = obj['label'].substr(0, obj['label'].lastIndexOf('/')) + '/name';
|
||||
new FileModule.File(tmpFile).read().then(label => {
|
||||
this._addTempVoltFan(callback, obj, name, label, extra, value);
|
||||
}).catch(err => { });
|
||||
});
|
||||
}
|
||||
}).catch(err => { });
|
||||
}
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_addTempVoltFan(callback, obj, name, label, extra, value) {
|
||||
// prepend module that provided sensor data
|
||||
if (name != label) label = name + ' ' + label;
|
||||
|
||||
//if (label == 'nvme Composite') label = 'NVMe';
|
||||
//if (label == 'nouveau') label = 'Nvidia';
|
||||
|
||||
label = label + extra;
|
||||
|
||||
// in the future we will read /etc/sensors3.conf
|
||||
if (label == 'acpitz temp1') label = 'ACPI Thermal Zone';
|
||||
if (label == 'pch_cannonlake temp1') label = 'Platform Controller Hub';
|
||||
if (label == 'iwlwifi_1 temp1') label = 'Wireless Adapter';
|
||||
if (label == 'Package id 0') label = 'Processor 0';
|
||||
if (label == 'Package id 1') label = 'Processor 1';
|
||||
label = label.replace('Package id', 'CPU');
|
||||
|
||||
let types = [ 'temperature', 'voltage', 'fan' ];
|
||||
for (let type of types) {
|
||||
// check if this label already exists
|
||||
if (label in this._tempVoltFanSensors[type]) {
|
||||
for (let i = 2; i <= 9; i++) {
|
||||
// append an incremented number to end
|
||||
let new_label = label + ' ' + i;
|
||||
|
||||
// if new label is available, use it
|
||||
if (!(new_label in this._tempVoltFanSensors[type])) {
|
||||
label = new_label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update screen on initial build to prevent delay on update
|
||||
this._returnValue(callback, label, value, obj['type'], obj['format']);
|
||||
|
||||
this._tempVoltFanSensors[obj['type']][label] = {
|
||||
'format': obj['format'],
|
||||
'path': obj['input']
|
||||
};
|
||||
}
|
||||
|
||||
resetHistory() {
|
||||
this._next_public_ip_check = 0;
|
||||
this._hardware_detected = false;
|
||||
this._processor_uses_cpu_info = true;
|
||||
}
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
.vitals-icon { icon-size: 16px; }
|
||||
.vitals-menu-button-container {}
|
||||
.vitals-panel-icon-temperature { margin: 0 1px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-voltage { margin: 0 0 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-fan { margin: 0 4px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-memory { margin: 0 2px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-processor { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-system { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-network { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-storage { margin: 0 2px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-battery { margin: 0 4px 0 8px; padding: 0; }
|
||||
.vitals-panel-label { margin: 0 3px 0 0; padding: 0; }
|
||||
.vitals-button-action { -st-icon-style: symbolic; border-radius: 32px; margin: 0px; min-height: 22px; min-width: 22px; padding: 10px; font-size: 100%; border: 1px solid transparent; }
|
||||
.vitals-button-action:hover, .vitals-button-action:focus { border-color: #777; }
|
||||
.vitals-button-action > StIcon { icon-size: 16px; }
|
||||
.vitals-button-box { padding: 0px; spacing: 22px; }
|
@ -1,337 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2018, Chris Monahan <chris@corecoding.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 GObject = imports.gi.GObject;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
|
||||
const cbFun = (d, c) => {
|
||||
let bb = d[1] % c[0],
|
||||
aa = (d[1] - bb) / c[0];
|
||||
aa = aa > 0 ? aa + c[1] : '';
|
||||
|
||||
return [d[0] + aa, bb];
|
||||
};
|
||||
|
||||
var Values = GObject.registerClass({
|
||||
GTypeName: 'Values',
|
||||
}, class Values extends GObject.Object {
|
||||
|
||||
_init(settings, sensorIcons) {
|
||||
this._settings = settings;
|
||||
this._sensorIcons = sensorIcons;
|
||||
|
||||
this._networkSpeedOffset = {};
|
||||
this._networkSpeeds = {};
|
||||
|
||||
this._history = {};
|
||||
//this._history2 = {};
|
||||
this.resetHistory();
|
||||
}
|
||||
|
||||
_legible(value, sensorClass) {
|
||||
let unit = 1000;
|
||||
if (value === null) return 'N/A';
|
||||
let use_higher_precision = this._settings.get_boolean('use-higher-precision');
|
||||
let memory_measurement = this._settings.get_int('memory-measurement')
|
||||
let storage_measurement = this._settings.get_int('storage-measurement')
|
||||
let use_bps = (this._settings.get_int('network-speed-format') == 1);
|
||||
|
||||
let format = '';
|
||||
let ending = '';
|
||||
let exp = 0;
|
||||
|
||||
var decimal = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
|
||||
var binary = [ 'B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB' ];
|
||||
var hertz = [ 'Hz', 'KHz', 'MHz', 'GHz', 'THz', 'PHz', 'EHz', 'ZHz' ];
|
||||
|
||||
switch (sensorClass) {
|
||||
case 'percent':
|
||||
format = (use_higher_precision)?'%.1f%s':'%d%s';
|
||||
value = value * 100;
|
||||
if (value > 100) value = 100;
|
||||
ending = '%';
|
||||
break;
|
||||
case 'temp':
|
||||
value = value / 1000;
|
||||
ending = '°C';
|
||||
|
||||
// are we converting to fahrenheit?
|
||||
if (this._settings.get_int('unit') == 1) {
|
||||
value = ((9 / 5) * value + 32);
|
||||
ending = '°F';
|
||||
}
|
||||
|
||||
format = (use_higher_precision)?'%.1f%s':'%d%s';
|
||||
break;
|
||||
case 'fan':
|
||||
format = '%d %s';
|
||||
ending = 'RPM';
|
||||
break;
|
||||
case 'in': // voltage
|
||||
value = value / 1000;
|
||||
format = ((value >= 0) ? '+' : '-') + ((use_higher_precision)?'%.2f %s':'%.1f %s');
|
||||
ending = 'V';
|
||||
break;
|
||||
case 'hertz':
|
||||
if (value > 0) {
|
||||
exp = Math.floor(Math.log(value) / Math.log(unit));
|
||||
if (value >= Math.pow(unit, exp) * (unit - 0.05)) exp++;
|
||||
value = parseFloat((value / Math.pow(unit, exp)));
|
||||
}
|
||||
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
ending = hertz[exp];
|
||||
break;
|
||||
case 'memory':
|
||||
unit = (memory_measurement)?1000:1024;
|
||||
|
||||
if (value > 0) {
|
||||
value *= unit;
|
||||
exp = Math.floor(Math.log(value) / Math.log(unit));
|
||||
if (value >= Math.pow(unit, exp) * (unit - 0.05)) exp++;
|
||||
value = parseFloat((value / Math.pow(unit, exp)));
|
||||
}
|
||||
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
|
||||
if (memory_measurement)
|
||||
ending = decimal[exp];
|
||||
else
|
||||
ending = binary[exp];
|
||||
|
||||
break;
|
||||
case 'storage':
|
||||
unit = (storage_measurement)?1000:1024;
|
||||
|
||||
if (value > 0) {
|
||||
exp = Math.floor(Math.log(value) / Math.log(unit));
|
||||
if (value >= Math.pow(unit, exp) * (unit - 0.05)) exp++;
|
||||
value = parseFloat((value / Math.pow(unit, exp)));
|
||||
}
|
||||
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
|
||||
if (storage_measurement)
|
||||
ending = decimal[exp];
|
||||
else
|
||||
ending = binary[exp];
|
||||
|
||||
break;
|
||||
case 'speed':
|
||||
if (value > 0) {
|
||||
if (use_bps) value *= 8;
|
||||
exp = Math.floor(Math.log(value) / Math.log(unit));
|
||||
if (value >= Math.pow(unit, exp) * (unit - 0.05)) exp++;
|
||||
value = parseFloat((value / Math.pow(unit, exp)));
|
||||
}
|
||||
|
||||
format = (use_higher_precision)?'%.1f %s':'%.0f %s';
|
||||
|
||||
if (use_bps) {
|
||||
ending = decimal[exp].replace('B', 'bps');
|
||||
} else {
|
||||
ending = decimal[exp] + '/s';
|
||||
}
|
||||
|
||||
break;
|
||||
case 'duration':
|
||||
let scale = [24, 60, 60];
|
||||
let units = ['d ', 'h ', 'm '];
|
||||
|
||||
// show seconds on higher precision or if value under a minute
|
||||
if (use_higher_precision || value < 60) {
|
||||
scale.push(1);
|
||||
units.push('s ');
|
||||
}
|
||||
|
||||
let rslt = scale.map((d, i, a) => a.slice(i).reduce((d, c) => d * c))
|
||||
.map((d, i) => ([d, units[i]]))
|
||||
.reduce(cbFun, ['', value]);
|
||||
|
||||
value = rslt[0].trim();
|
||||
|
||||
format = '%s';
|
||||
break;
|
||||
case 'milliamp':
|
||||
format = (use_higher_precision)?'%.1f %s':'%d %s';
|
||||
value = value / 1000;
|
||||
ending = 'mA';
|
||||
break;
|
||||
case 'milliamp-hour':
|
||||
format = (use_higher_precision)?'%.1f %s':'%d %s';
|
||||
value = value / 1000;
|
||||
ending = 'mAh';
|
||||
break;
|
||||
case 'watt':
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
value = value / 1000000000000;
|
||||
ending = 'W';
|
||||
break;
|
||||
case 'watt-hour':
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
value = value / 1000000000000;
|
||||
ending = 'Wh';
|
||||
break;
|
||||
case 'load':
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
break;
|
||||
default:
|
||||
format = '%s';
|
||||
break;
|
||||
}
|
||||
|
||||
return format.format(value, ending);
|
||||
}
|
||||
|
||||
returnIfDifferent(dwell, label, value, type, format, key) {
|
||||
let output = [];
|
||||
|
||||
// make sure the keys exist
|
||||
if (!(type in this._history)) this._history[type] = {};
|
||||
|
||||
// no sense in continuing when the raw value has not changed
|
||||
if (type != 'network-rx' && type != 'network-tx' &&
|
||||
key in this._history[type] && this._history[type][key][1] == value)
|
||||
return output;
|
||||
|
||||
// is the value different from last time?
|
||||
let legible = this._legible(value, format);
|
||||
|
||||
// don't return early when dealing with network traffic
|
||||
if (type != 'network-rx' && type != 'network-tx') {
|
||||
// only update when we are coming through for the first time, or if a value has changed
|
||||
if (key in this._history[type] && this._history[type][key][0] == legible)
|
||||
return output;
|
||||
|
||||
// add label as it was sent from sensors class
|
||||
output.push([label, legible, type, key]);
|
||||
}
|
||||
|
||||
// save previous values to update screen on chnages only
|
||||
let previousValue = this._history[type][key];
|
||||
this._history[type][key] = [legible, value];
|
||||
|
||||
// process average, min and max values
|
||||
if (type == 'temperature' || type == 'voltage' || type == 'fan') {
|
||||
let vals = Object.values(this._history[type]).map(x => parseFloat(x[1]));
|
||||
|
||||
// show value in group even if there is one value present
|
||||
let sum = vals.reduce((a, b) => a + b);
|
||||
let avg = this._legible(sum / vals.length, format);
|
||||
output.push([type, avg, type + '-group', '']);
|
||||
|
||||
// If only one value is present, don't display avg, min and max
|
||||
if (vals.length > 1) {
|
||||
output.push(['Average', avg, type, '__' + type + '_avg__']);
|
||||
|
||||
// calculate Minimum value
|
||||
let min = Math.min(...vals);
|
||||
min = this._legible(min, format);
|
||||
output.push(['Minimum', min, type, '__' + type + '_min__']);
|
||||
|
||||
// calculate Maximum value
|
||||
let max = Math.max(...vals);
|
||||
max = this._legible(max, format);
|
||||
output.push(['Maximum', max, type, '__' + type + '_max__']);
|
||||
}
|
||||
} else if (type == 'network-rx' || type == 'network-tx') {
|
||||
let direction = type.split('-')[1];
|
||||
|
||||
// appends total upload and download for all interfaces for #216
|
||||
let vals = Object.values(this._history[type]).map(x => parseFloat(x[1]));
|
||||
let sum = vals.reduce((partialSum, a) => partialSum + a, 0);
|
||||
output.push(['Boot ' + direction, this._legible(sum, format), type, '__' + type + '_boot__']);
|
||||
|
||||
// keeps track of session start point
|
||||
if (!(key in this._networkSpeedOffset) || this._networkSpeedOffset[key] <= 0)
|
||||
this._networkSpeedOffset[key] = sum;
|
||||
|
||||
// outputs session upload and download for all interfaces for #234
|
||||
output.push(['Session ' + direction, this._legible(sum - this._networkSpeedOffset[key], format), type, '__' + type + '_ses__']);
|
||||
|
||||
// calculate speed for this interface
|
||||
let speed = (value - previousValue[1]) / dwell;
|
||||
output.push([label, this._legible(speed, 'speed'), type, key]);
|
||||
|
||||
// store speed for Device report
|
||||
if (!(direction in this._networkSpeeds)) this._networkSpeeds[direction] = {};
|
||||
if (!(label in this._networkSpeeds[direction])) this._networkSpeeds[direction][label] = 0;
|
||||
|
||||
// store value for next go around
|
||||
if (value > 0 || (value == 0 && !this._settings.get_boolean('hide-zeros')))
|
||||
this._networkSpeeds[direction][label] = speed;
|
||||
|
||||
// calculate total upload and download device speed
|
||||
for (let direction in this._networkSpeeds) {
|
||||
let sum = 0;
|
||||
for (let iface in this._networkSpeeds[direction])
|
||||
sum += parseFloat(this._networkSpeeds[direction][iface]);
|
||||
|
||||
sum = this._legible(sum, 'speed');
|
||||
output.push(['Device ' + direction, sum, 'network-' + direction, '__network-' + direction + '_max__']);
|
||||
// append download speed to group itself
|
||||
if (direction == 'rx') output.push([type, sum, type + '-group', '']);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
global.log('before', JSON.stringify(output));
|
||||
for (let i = output.length - 1; i >= 0; i--) {
|
||||
let sensor = output[i];
|
||||
// sensor[0]=label, sensor[1]=value, sensor[2]=type, sensor[3]=key)
|
||||
|
||||
//["CPU Core 5","46°C","temperature","_temperature_hwmon8temp7_"]
|
||||
|
||||
// make sure the keys exist
|
||||
if (!(sensor[2] in this._history2)) this._history2[sensor[2]] = {};
|
||||
|
||||
if (sensor[3] in this._history2[sensor[2]]) {
|
||||
if (this._history2[sensor[2]][sensor[3]] == sensor[1]) {
|
||||
output.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._history2[sensor[2]][sensor[3]] = sensor[1];
|
||||
}
|
||||
|
||||
global.log(' after', JSON.stringify(output));
|
||||
global.log('***************************');
|
||||
*/
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
resetHistory() {
|
||||
// don't call this._history = {}, as we want to keep network-rx and network-tx
|
||||
// otherwise network history statistics will start over
|
||||
for (let sensor in this._sensorIcons) {
|
||||
this._history[sensor] = {};
|
||||
this._history[sensor + '-group'] = {};
|
||||
//this._history2[sensor] = {};
|
||||
//this._history2[sensor + '-group'] = {};
|
||||
}
|
||||
}
|
||||
});
|
@ -1,265 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Shell, GLib, Clutter } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { PaintSignals } = Me.imports.effects.paint_signals;
|
||||
const Tweener = imports.tweener.tweener;
|
||||
|
||||
const transparent = Clutter.Color.from_pixel(0x00000000);
|
||||
const FOLDER_DIALOG_ANIMATION_TIME = 200;
|
||||
const FRAME_UPDATE_PERIOD = 16;
|
||||
|
||||
const DIALOGS_STYLES = [
|
||||
"",
|
||||
"appfolder-dialogs-transparent",
|
||||
"appfolder-dialogs-light",
|
||||
"appfolder-dialogs-dark"
|
||||
];
|
||||
|
||||
let original_zoomAndFadeIn = null;
|
||||
let original_zoomAndFadeOut = null;
|
||||
let sigma;
|
||||
let brightness;
|
||||
|
||||
let _zoomAndFadeIn = function () {
|
||||
let [sourceX, sourceY] =
|
||||
this._source.get_transformed_position();
|
||||
let [dialogX, dialogY] =
|
||||
this.child.get_transformed_position();
|
||||
|
||||
this.child.set({
|
||||
translation_x: sourceX - dialogX,
|
||||
translation_y: sourceY - dialogY,
|
||||
scale_x: this._source.width / this.child.width,
|
||||
scale_y: this._source.height / this.child.height,
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
this.set_background_color(transparent);
|
||||
|
||||
let blur_effect = this.get_effect("appfolder-blur");
|
||||
|
||||
blur_effect.sigma = 0;
|
||||
blur_effect.brightness = 1.0;
|
||||
Tweener.addTween(blur_effect,
|
||||
{
|
||||
sigma: sigma,
|
||||
brightness: brightness,
|
||||
time: FOLDER_DIALOG_ANIMATION_TIME / 1000,
|
||||
transition: 'easeOutQuad'
|
||||
}
|
||||
);
|
||||
|
||||
this.child.ease({
|
||||
translation_x: 0,
|
||||
translation_y: 0,
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
opacity: 255,
|
||||
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
|
||||
this._needsZoomAndFade = false;
|
||||
|
||||
if (this._sourceMappedId === 0) {
|
||||
this._sourceMappedId = this._source.connect(
|
||||
'notify::mapped', this._zoomAndFadeOut.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
let _zoomAndFadeOut = function () {
|
||||
if (!this._isOpen)
|
||||
return;
|
||||
|
||||
if (!this._source.mapped) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let [sourceX, sourceY] =
|
||||
this._source.get_transformed_position();
|
||||
let [dialogX, dialogY] =
|
||||
this.child.get_transformed_position();
|
||||
|
||||
this.set_background_color(transparent);
|
||||
|
||||
let blur_effect = this.get_effect("appfolder-blur");
|
||||
Tweener.addTween(blur_effect,
|
||||
{
|
||||
sigma: 0,
|
||||
brightness: 1.0,
|
||||
time: FOLDER_DIALOG_ANIMATION_TIME / 1000,
|
||||
transition: 'easeInQuad'
|
||||
}
|
||||
);
|
||||
|
||||
this.child.ease({
|
||||
translation_x: sourceX - dialogX,
|
||||
translation_y: sourceY - dialogY,
|
||||
scale_x: this._source.width / this.child.width,
|
||||
scale_y: this._source.height / this.child.height,
|
||||
opacity: 0,
|
||||
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => {
|
||||
this.child.set({
|
||||
translation_x: 0,
|
||||
translation_y: 0,
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
opacity: 255,
|
||||
});
|
||||
this.hide();
|
||||
|
||||
this._popdownCallbacks.forEach(func => func());
|
||||
this._popdownCallbacks = [];
|
||||
},
|
||||
});
|
||||
|
||||
this._needsZoomAndFade = false;
|
||||
};
|
||||
|
||||
|
||||
var AppFoldersBlur = class AppFoldersBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.paint_signals = new PaintSignals(connections);
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring appfolders");
|
||||
|
||||
brightness = this.prefs.appfolder.CUSTOMIZE
|
||||
? this.prefs.appfolder.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS;
|
||||
sigma = this.prefs.appfolder.CUSTOMIZE
|
||||
? this.prefs.appfolder.SIGMA
|
||||
: this.prefs.SIGMA;
|
||||
|
||||
let appDisplay = Main.overview._overview.controls._appDisplay;
|
||||
|
||||
if (appDisplay._folderIcons.length > 0) {
|
||||
this.blur_appfolders();
|
||||
}
|
||||
|
||||
this.connections.connect(
|
||||
appDisplay, 'view-loaded', this.blur_appfolders.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
blur_appfolders() {
|
||||
let appDisplay = Main.overview._overview.controls._appDisplay;
|
||||
|
||||
if (this.prefs.HACKS_LEVEL === 1 || this.prefs.HACKS_LEVEL === 2)
|
||||
this._log(`appfolders hack level ${this.prefs.HACKS_LEVEL}`);
|
||||
|
||||
appDisplay._folderIcons.forEach(icon => {
|
||||
icon._ensureFolderDialog();
|
||||
|
||||
if (original_zoomAndFadeIn == null) {
|
||||
original_zoomAndFadeIn = icon._dialog._zoomAndFadeIn;
|
||||
}
|
||||
if (original_zoomAndFadeOut == null) {
|
||||
original_zoomAndFadeOut = icon._dialog._zoomAndFadeOut;
|
||||
}
|
||||
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
name: "appfolder-blur",
|
||||
sigma: sigma,
|
||||
brightness: brightness,
|
||||
mode: Shell.BlurMode.BACKGROUND
|
||||
});
|
||||
|
||||
icon._dialog.remove_effect_by_name("appfolder-blur");
|
||||
icon._dialog.add_effect(blur_effect);
|
||||
|
||||
DIALOGS_STYLES.forEach(
|
||||
style => icon._dialog._viewBox.remove_style_class_name(style)
|
||||
);
|
||||
|
||||
icon._dialog._viewBox.add_style_class_name(
|
||||
DIALOGS_STYLES[this.prefs.appfolder.STYLE_DIALOGS]
|
||||
);
|
||||
|
||||
// finally override the builtin functions
|
||||
|
||||
icon._dialog._zoomAndFadeIn = _zoomAndFadeIn;
|
||||
icon._dialog._zoomAndFadeOut = _zoomAndFadeOut;
|
||||
|
||||
|
||||
// HACK
|
||||
//
|
||||
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
|
||||
//
|
||||
// This does not entirely fix this bug (shadows caused by windows
|
||||
// still cause artifacts), but it prevents the shadows of the panel
|
||||
// buttons to cause artifacts on the panel itself
|
||||
//
|
||||
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
|
||||
|
||||
if (this.prefs.HACKS_LEVEL === 1 || this.prefs.HACKS_LEVEL === 2) {
|
||||
this.paint_signals.disconnect_all_for_actor(icon._dialog);
|
||||
this.paint_signals.connect(icon._dialog, blur_effect);
|
||||
} else {
|
||||
this.paint_signals.disconnect_all();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
set_sigma(s) {
|
||||
sigma = s;
|
||||
if (this.prefs.appfolder.BLUR)
|
||||
this.blur_appfolders();
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
brightness = b;
|
||||
if (this.prefs.appfolder.BLUR)
|
||||
this.blur_appfolders();
|
||||
}
|
||||
|
||||
// not implemented for dynamic blur
|
||||
set_color(c) { }
|
||||
set_noise_amount(n) { }
|
||||
set_noise_lightness(l) { }
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from appfolders");
|
||||
|
||||
let appDisplay = Main.overview._overview.controls._appDisplay;
|
||||
|
||||
if (original_zoomAndFadeIn != null) {
|
||||
appDisplay._folderIcons.forEach(icon => {
|
||||
if (icon._dialog)
|
||||
icon._dialog._zoomAndFadeIn = original_zoomAndFadeIn;
|
||||
});
|
||||
}
|
||||
|
||||
if (original_zoomAndFadeOut != null) {
|
||||
appDisplay._folderIcons.forEach(icon => {
|
||||
if (icon._dialog)
|
||||
icon._dialog._zoomAndFadeOut = original_zoomAndFadeOut;
|
||||
});
|
||||
}
|
||||
|
||||
appDisplay._folderIcons.forEach(icon => {
|
||||
if (icon._dialog) {
|
||||
icon._dialog.remove_effect_by_name("appfolder-blur");
|
||||
DIALOGS_STYLES.forEach(
|
||||
s => icon._dialog._viewBox.remove_style_class_name(s)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.connections.disconnect_all();
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > appfolders] ${str}`);
|
||||
}
|
||||
};
|
@ -1,541 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Shell, Clutter, Meta, GLib } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { PaintSignals } = Me.imports.effects.paint_signals;
|
||||
const { ApplicationsService } = Me.imports.dbus.services;
|
||||
|
||||
var ApplicationsBlur = class ApplicationsBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.prefs = prefs;
|
||||
this.paint_signals = new PaintSignals(connections);
|
||||
|
||||
// stores every blurred window
|
||||
this.window_map = new Map();
|
||||
// stores every blur actor
|
||||
this.blur_actor_map = new Map();
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring applications...");
|
||||
|
||||
// export dbus service for preferences
|
||||
this.service = new ApplicationsService;
|
||||
this.service.export();
|
||||
|
||||
// blur already existing windows
|
||||
this.update_all_windows();
|
||||
|
||||
// blur every new window
|
||||
this.connections.connect(
|
||||
global.display,
|
||||
'window-created',
|
||||
(_meta_display, meta_window) => {
|
||||
this._log("window created");
|
||||
|
||||
if (meta_window) {
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
this.track_new(window_actor, meta_window);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.connect_to_overview();
|
||||
}
|
||||
|
||||
/// Connect to the overview being opened/closed to force the blur being
|
||||
/// shown on every window of the workspaces viewer.
|
||||
connect_to_overview() {
|
||||
this.connections.disconnect_all_for(Main.overview);
|
||||
|
||||
if (this.prefs.applications.BLUR_ON_OVERVIEW) {
|
||||
// when the overview is opened, show every window actors (which
|
||||
// allows the blur to be shown too)
|
||||
this.connections.connect(
|
||||
Main.overview, 'showing',
|
||||
_ => this.window_map.forEach((meta_window, _pid) => {
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
window_actor.show();
|
||||
})
|
||||
);
|
||||
|
||||
// when the overview is closed, hide every actor that is not on the
|
||||
// current workspace (to mimic the original behaviour)
|
||||
this.connections.connect(
|
||||
Main.overview, 'hidden',
|
||||
_ => {
|
||||
let active_workspace =
|
||||
global.workspace_manager.get_active_workspace();
|
||||
|
||||
this.window_map.forEach((meta_window, _pid) => {
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
|
||||
if (
|
||||
meta_window.get_workspace() !== active_workspace
|
||||
)
|
||||
window_actor.hide();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate through all existing windows and add blur as needed.
|
||||
update_all_windows() {
|
||||
// remove all previously blurred windows, in the case where the
|
||||
// whitelist was changed
|
||||
this.window_map.forEach(((_meta_window, pid) => {
|
||||
this.remove_blur(pid);
|
||||
}));
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < global.workspace_manager.get_n_workspaces();
|
||||
++i
|
||||
) {
|
||||
let workspace = global.workspace_manager.get_workspace_by_index(i);
|
||||
let windows = workspace.list_windows();
|
||||
|
||||
windows.forEach(meta_window => {
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
|
||||
// disconnect previous signals
|
||||
this.connections.disconnect_all_for(window_actor);
|
||||
|
||||
this.track_new(window_actor, meta_window);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the needed signals to every new tracked window, and adds blur if
|
||||
/// needed.
|
||||
track_new(window_actor, meta_window) {
|
||||
let pid = ("" + Math.random()).slice(2, 16);
|
||||
|
||||
window_actor['blur_provider_pid'] = pid;
|
||||
meta_window['blur_provider_pid'] = pid;
|
||||
|
||||
// remove the blur when the window is destroyed
|
||||
this.connections.connect(window_actor, 'destroy', window_actor => {
|
||||
let pid = window_actor.blur_provider_pid;
|
||||
if (this.blur_actor_map.has(pid)) {
|
||||
this.remove_blur(pid);
|
||||
}
|
||||
this.window_map.delete(pid);
|
||||
});
|
||||
|
||||
// update the blur when mutter-hint or wm-class is changed
|
||||
for (const prop of ['mutter-hints', 'wm-class']) {
|
||||
this.connections.connect(
|
||||
meta_window,
|
||||
`notify::${prop}`,
|
||||
_ => {
|
||||
let pid = meta_window.blur_provider_pid;
|
||||
this._log(`${prop} changed for pid ${pid}`);
|
||||
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
this.check_blur(pid, window_actor, meta_window);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// update the position and size when the window size changes
|
||||
this.connections.connect(meta_window, 'size-changed', () => {
|
||||
if (this.blur_actor_map.has(pid)) {
|
||||
let allocation = this.compute_allocation(meta_window);
|
||||
let blur_actor = this.blur_actor_map.get(pid);
|
||||
blur_actor.x = allocation.x;
|
||||
blur_actor.y = allocation.y;
|
||||
blur_actor.width = allocation.width;
|
||||
blur_actor.height = allocation.height;
|
||||
}
|
||||
});
|
||||
|
||||
this.check_blur(pid, window_actor, meta_window);
|
||||
}
|
||||
|
||||
/// Checks if the given actor needs to be blurred.
|
||||
///
|
||||
/// In order to be blurred, a window either:
|
||||
/// - is whitelisted in the user preferences if not enable-all
|
||||
/// - is not blacklisted if enable-all
|
||||
/// - has a correct mutter hint, set to `blur-provider=sigma_value`
|
||||
check_blur(pid, window_actor, meta_window) {
|
||||
let mutter_hint = meta_window.get_mutter_hints();
|
||||
let window_wm_class = meta_window.get_wm_class();
|
||||
|
||||
let enable_all = this.prefs.applications.ENABLE_ALL;
|
||||
let whitelist = this.prefs.applications.WHITELIST;
|
||||
let blacklist = this.prefs.applications.BLACKLIST;
|
||||
|
||||
this._log(`checking blur for ${pid}`);
|
||||
|
||||
// either the window is included in whitelist
|
||||
if (window_wm_class !== ""
|
||||
&& ((enable_all && !blacklist.includes(window_wm_class))
|
||||
|| (!enable_all && whitelist.includes(window_wm_class))
|
||||
)
|
||||
&& [
|
||||
Meta.FrameType.NORMAL,
|
||||
Meta.FrameType.DIALOG,
|
||||
Meta.FrameType.MODAL_DIALOG
|
||||
].includes(meta_window.get_frame_type())
|
||||
) {
|
||||
this._log(`application ${pid} listed, blurring it`);
|
||||
|
||||
// get blur effect parameters
|
||||
|
||||
let brightness, sigma;
|
||||
|
||||
if (this.prefs.applications.CUSTOMIZE) {
|
||||
brightness = this.prefs.applications.BRIGHTNESS;
|
||||
sigma = this.prefs.applications.SIGMA;
|
||||
} else {
|
||||
brightness = this.prefs.BRIGHTNESS;
|
||||
sigma = this.prefs.SIGMA;
|
||||
}
|
||||
|
||||
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
|
||||
}
|
||||
|
||||
// or blur is asked by window itself
|
||||
else if (
|
||||
mutter_hint != null &&
|
||||
mutter_hint.includes("blur-provider")
|
||||
) {
|
||||
this._log(`application ${pid} has hint ${mutter_hint}, parsing`);
|
||||
|
||||
// get blur effect parameters
|
||||
let [brightness, sigma] = this.parse_xprop(mutter_hint);
|
||||
|
||||
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
|
||||
}
|
||||
|
||||
// remove blur if the mutter hint is no longer valid, and the window
|
||||
// is not explicitly whitelisted or un-blacklisted
|
||||
else if (this.blur_actor_map.has(pid)) {
|
||||
this.remove_blur(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/// When given the xprop property, returns the brightness and sigma values
|
||||
/// matching. If one of the two values is invalid, or missing, then it uses
|
||||
/// default values.
|
||||
///
|
||||
/// An xprop property is valid if it is in one of the following formats:
|
||||
///
|
||||
/// blur-provider=sigma:60,brightness:0.9
|
||||
/// blur-provider=s:10,brightness:0.492
|
||||
/// blur-provider=b:1.0,s:16
|
||||
///
|
||||
/// Brightness is a floating-point between 0.0 and 1.0 included.
|
||||
/// Sigma is an integer between 0 and 999 included.
|
||||
///
|
||||
/// If sigma is set to 0, then the blur is removed.
|
||||
/// Setting "default" instead of the two values will make the
|
||||
/// extension use its default value.
|
||||
///
|
||||
/// Note that no space can be inserted.
|
||||
///
|
||||
parse_xprop(property) {
|
||||
// set brightness and sigma to default values
|
||||
let brightness, sigma;
|
||||
if (this.prefs.applications.CUSTOMIZE) {
|
||||
brightness = this.prefs.applications.BRIGHTNESS;
|
||||
sigma = this.prefs.applications.SIGMA;
|
||||
} else {
|
||||
brightness = this.prefs.BRIGHTNESS;
|
||||
sigma = this.prefs.SIGMA;
|
||||
}
|
||||
|
||||
// get the argument of the property
|
||||
let arg = property.match("blur-provider=(.*)");
|
||||
this._log(`argument = ${arg}`);
|
||||
|
||||
// if argument is valid, parse it
|
||||
if (arg != null) {
|
||||
// verify if there is only one value: in this case, this is sigma
|
||||
let maybe_sigma = parseInt(arg[1]);
|
||||
|
||||
if (
|
||||
!isNaN(maybe_sigma) &&
|
||||
maybe_sigma >= 0 &&
|
||||
maybe_sigma <= 999
|
||||
) {
|
||||
sigma = maybe_sigma;
|
||||
} else {
|
||||
// perform pattern matching
|
||||
let res_b = arg[1].match("(brightness|b):(default|0?1?\.[0-9]*)");
|
||||
let res_s = arg[1].match("(sigma|s):(default|\\d{1,3})");
|
||||
|
||||
// if values are valid and not default, change them to the xprop one
|
||||
if (
|
||||
res_b != null && res_b[2] !== 'default'
|
||||
) {
|
||||
brightness = parseFloat(res_b[2]);
|
||||
}
|
||||
|
||||
if (
|
||||
res_s != null && res_s[2] !== 'default'
|
||||
) {
|
||||
sigma = parseInt(res_s[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._log(`brightness = ${brightness}, sigma = ${sigma}`);
|
||||
|
||||
return [brightness, sigma];
|
||||
}
|
||||
|
||||
/// Updates the blur on a window which needs to be blurred.
|
||||
update_blur(pid, window_actor, meta_window, brightness, sigma) {
|
||||
// the window is already blurred, update its blur effect
|
||||
if (this.blur_actor_map.has(pid)) {
|
||||
// window is already blurred, but sigma is null: remove the blur
|
||||
if (sigma === 0) {
|
||||
this.remove_blur(pid);
|
||||
}
|
||||
// window is already blurred and sigma is non-null: update it
|
||||
else {
|
||||
this.update_blur_effect(
|
||||
this.blur_actor_map.get(pid),
|
||||
brightness,
|
||||
sigma
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// the window is not blurred, and sigma is a non-null value: blur it
|
||||
else if (sigma !== 0) {
|
||||
// window is not blurred, blur it
|
||||
this.create_blur_effect(
|
||||
pid,
|
||||
window_actor,
|
||||
meta_window,
|
||||
brightness,
|
||||
sigma
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the blur effect to the window.
|
||||
create_blur_effect(pid, window_actor, meta_window, brightness, sigma) {
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
sigma: sigma,
|
||||
brightness: brightness,
|
||||
mode: Shell.BlurMode.BACKGROUND
|
||||
});
|
||||
|
||||
let blur_actor = this.create_blur_actor(
|
||||
meta_window,
|
||||
window_actor,
|
||||
blur_effect
|
||||
);
|
||||
|
||||
// if hacks are selected, force to repaint the window
|
||||
if (this.prefs.HACKS_LEVEL === 1 || this.prefs.HACKS_LEVEL === 2) {
|
||||
this._log("applications hack level 1 or 2");
|
||||
|
||||
this.paint_signals.disconnect_all();
|
||||
this.paint_signals.connect(blur_actor, blur_effect);
|
||||
} else {
|
||||
this.paint_signals.disconnect_all();
|
||||
}
|
||||
|
||||
// insert the blurred widget
|
||||
window_actor.insert_child_at_index(blur_actor, 0);
|
||||
|
||||
// make sure window is blurred in overview
|
||||
if (this.prefs.applications.BLUR_ON_OVERVIEW)
|
||||
this.enforce_window_visibility_on_overview_for(window_actor);
|
||||
|
||||
// set the window actor's opacity
|
||||
this.set_window_opacity(window_actor, this.prefs.applications.OPACITY);
|
||||
|
||||
// register the blur actor/effect
|
||||
blur_actor['blur_provider_pid'] = pid;
|
||||
this.blur_actor_map.set(pid, blur_actor);
|
||||
this.window_map.set(pid, meta_window);
|
||||
|
||||
// hide the blur if window is invisible
|
||||
if (!window_actor.visible) {
|
||||
blur_actor.hide();
|
||||
}
|
||||
|
||||
// hide the blur if window becomes invisible
|
||||
this.connections.connect(
|
||||
window_actor,
|
||||
'notify::visible',
|
||||
window_actor => {
|
||||
let pid = window_actor.blur_provider_pid;
|
||||
if (window_actor.visible) {
|
||||
this.blur_actor_map.get(pid).show();
|
||||
} else {
|
||||
this.blur_actor_map.get(pid).hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Makes sure that, when the overview is visible, the window actor will
|
||||
/// stay visible no matter what.
|
||||
/// We can instead hide the last child of the window actor, which will
|
||||
/// improve performances without hiding the blur effect.
|
||||
enforce_window_visibility_on_overview_for(window_actor) {
|
||||
this.connections.connect(window_actor, 'notify::visible',
|
||||
_ => {
|
||||
if (this.prefs.applications.BLUR_ON_OVERVIEW) {
|
||||
if (
|
||||
!window_actor.visible
|
||||
&& Main.overview.visible
|
||||
) {
|
||||
window_actor.show();
|
||||
window_actor.get_last_child().hide();
|
||||
}
|
||||
else if (
|
||||
window_actor.visible
|
||||
)
|
||||
window_actor.get_last_child().show();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Set the opacity of the window actor that sits on top of the blur effect.
|
||||
set_window_opacity(window_actor, opacity) {
|
||||
window_actor.get_children().forEach(child => {
|
||||
if (child.name !== "blur-actor")
|
||||
child.opacity = opacity;
|
||||
});
|
||||
}
|
||||
|
||||
/// Compute the size and position for a blur actor.
|
||||
/// On wayland, it seems like we need to divide by the scale to get the
|
||||
/// correct result.
|
||||
compute_allocation(meta_window) {
|
||||
const is_wayland = Meta.is_wayland_compositor();
|
||||
const monitor_index = meta_window.get_monitor();
|
||||
const scale = is_wayland
|
||||
? Main.layoutManager.monitors[monitor_index].geometry_scale
|
||||
: 1;
|
||||
|
||||
let frame = meta_window.get_frame_rect();
|
||||
let buffer = meta_window.get_buffer_rect();
|
||||
|
||||
return {
|
||||
x: (frame.x - buffer.x) / scale,
|
||||
y: (frame.y - buffer.y) / scale,
|
||||
width: frame.width / scale,
|
||||
height: frame.height / scale
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns a new already blurred widget, configured to follow the size and
|
||||
/// position of its target window.
|
||||
create_blur_actor(meta_window, window_actor, blur_effect) {
|
||||
// compute the size and position
|
||||
let allocation = this.compute_allocation(meta_window);
|
||||
|
||||
// create the actor
|
||||
let blur_actor = new Clutter.Actor({
|
||||
x: allocation.x,
|
||||
y: allocation.y,
|
||||
width: allocation.width,
|
||||
height: allocation.height
|
||||
});
|
||||
|
||||
// add the effect
|
||||
blur_actor.add_effect_with_name('blur-effect', blur_effect);
|
||||
|
||||
return blur_actor;
|
||||
}
|
||||
|
||||
/// Updates the blur effect by overwriting its sigma and brightness values.
|
||||
update_blur_effect(blur_actor, brightness, sigma) {
|
||||
let effect = blur_actor.get_effect('blur-effect');
|
||||
effect.sigma = sigma;
|
||||
effect.brightness = brightness;
|
||||
}
|
||||
|
||||
/// Removes the blur actor from the shell and unregister it.
|
||||
remove_blur(pid) {
|
||||
this._log(`removing blur for pid ${pid}`);
|
||||
|
||||
let meta_window = this.window_map.get(pid);
|
||||
// disconnect needed signals and untrack window
|
||||
if (meta_window) {
|
||||
this.window_map.delete(pid);
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
|
||||
let blur_actor = this.blur_actor_map.get(pid);
|
||||
if (blur_actor) {
|
||||
this.blur_actor_map.delete(pid);
|
||||
|
||||
if (window_actor) {
|
||||
// reset the opacity
|
||||
this.set_window_opacity(window_actor, 255);
|
||||
|
||||
// remove the blurred actor
|
||||
window_actor.remove_child(blur_actor);
|
||||
|
||||
// disconnect the signals about overview animation etc
|
||||
this.connections.disconnect_all_for(window_actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from applications...");
|
||||
|
||||
this.service?.unexport();
|
||||
|
||||
this.blur_actor_map.forEach(((_blur_actor, pid) => {
|
||||
this.remove_blur(pid);
|
||||
}));
|
||||
|
||||
this.connections.disconnect_all();
|
||||
this.paint_signals.disconnect_all();
|
||||
}
|
||||
|
||||
/// Update the opacity of all window actors.
|
||||
set_opacity() {
|
||||
let opacity = this.prefs.applications.OPACITY;
|
||||
|
||||
this.window_map.forEach(((meta_window, _pid) => {
|
||||
let window_actor = meta_window.get_compositor_private();
|
||||
this.set_window_opacity(window_actor, opacity);
|
||||
}));
|
||||
}
|
||||
|
||||
/// Updates each blur effect to use new sigma value
|
||||
// FIXME set_sigma and set_brightness are called when the extension is
|
||||
// loaded and when sigma is changed, and do not respect the per-app
|
||||
// xprop behaviour
|
||||
set_sigma(s) {
|
||||
this.blur_actor_map.forEach((actor, _) => {
|
||||
actor.get_effect('blur-effect').set_sigma(s);
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates each blur effect to use new brightness value
|
||||
set_brightness(b) {
|
||||
this.blur_actor_map.forEach((actor, _) => {
|
||||
actor.get_effect('blur-effect').set_brightness(b);
|
||||
});
|
||||
}
|
||||
|
||||
// not implemented for dynamic blur
|
||||
set_color(c) { }
|
||||
set_noise_amount(n) { }
|
||||
set_noise_lightness(l) { }
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > applications] ${str}`);
|
||||
}
|
||||
};
|
@ -1,320 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { St, Shell, GLib } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { PaintSignals } = Me.imports.effects.paint_signals;
|
||||
|
||||
const DASH_STYLES = [
|
||||
"transparent-dash",
|
||||
"light-dash",
|
||||
"dark-dash"
|
||||
];
|
||||
|
||||
|
||||
/// This type of object is created for every dash found, and talks to the main
|
||||
/// DashBlur thanks to signals.
|
||||
///
|
||||
/// This allows to dynamically track the created dashes for each screen.
|
||||
class DashInfos {
|
||||
constructor(dash_blur, dash, background_parent, effect, prefs) {
|
||||
// the parent DashBlur object, to communicate
|
||||
this.dash_blur = dash_blur;
|
||||
// the blurred dash
|
||||
this.dash = dash;
|
||||
this.background_parent = background_parent;
|
||||
this.effect = effect;
|
||||
this.prefs = prefs;
|
||||
this.old_style = this.dash._background.style;
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'remove-dashes', () => {
|
||||
this._log("removing blur from dash");
|
||||
this.dash.get_parent().remove_child(this.background_parent);
|
||||
this.dash._background.style = this.old_style;
|
||||
|
||||
DASH_STYLES.forEach(
|
||||
style => this.dash.remove_style_class_name(style)
|
||||
);
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'update-sigma', () => {
|
||||
this.effect.sigma = this.dash_blur.sigma;
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'update-brightness', () => {
|
||||
this.effect.brightness = this.dash_blur.brightness;
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'override-background', () => {
|
||||
this.dash._background.style = null;
|
||||
|
||||
DASH_STYLES.forEach(
|
||||
style => this.dash.remove_style_class_name(style)
|
||||
);
|
||||
|
||||
this.dash.set_style_class_name(
|
||||
DASH_STYLES[this.prefs.dash_to_dock.STYLE_DASH_TO_DOCK]
|
||||
);
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'reset-background', () => {
|
||||
this.dash._background.style = this.old_style;
|
||||
|
||||
DASH_STYLES.forEach(
|
||||
style => this.dash.remove_style_class_name(style)
|
||||
);
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'show', () => {
|
||||
this.effect.sigma = this.dash_blur.sigma;
|
||||
});
|
||||
|
||||
dash_blur.connections.connect(dash_blur, 'hide', () => {
|
||||
this.effect.sigma = 0;
|
||||
});
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > dash] ${str}`);
|
||||
}
|
||||
}
|
||||
|
||||
var DashBlur = class DashBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.dashes = [];
|
||||
this.connections = connections;
|
||||
this.prefs = prefs;
|
||||
this.paint_signals = new PaintSignals(connections);
|
||||
this.sigma = this.prefs.dash_to_dock.CUSTOMIZE
|
||||
? this.prefs.dash_to_dock.SIGMA
|
||||
: this.prefs.SIGMA;
|
||||
this.brightness = this.prefs.dash_to_dock.CUSTOMIZE
|
||||
? this.prefs.dash_to_dock.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.connections.connect(Main.uiGroup, 'actor-added', (_, actor) => {
|
||||
if (
|
||||
(actor.get_name() === "dashtodockContainer") &&
|
||||
(actor.constructor.name === 'DashToDock')
|
||||
)
|
||||
this.try_blur(actor);
|
||||
});
|
||||
|
||||
this.blur_existing_dashes();
|
||||
this.connect_to_overview();
|
||||
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
// Finds all existing dashes on every monitor, and call `try_blur` on them
|
||||
// We cannot only blur `Main.overview.dash`, as there could be several
|
||||
blur_existing_dashes() {
|
||||
this._log("searching for dash");
|
||||
|
||||
// blur every dash found, filtered by name
|
||||
Main.uiGroup.get_children().filter((child) => {
|
||||
return (child.get_name() === "dashtodockContainer") &&
|
||||
(child.constructor.name === 'DashToDock');
|
||||
}).forEach(this.try_blur.bind(this));
|
||||
}
|
||||
|
||||
// Tries to blur the dash contained in the given actor
|
||||
try_blur(dash_container) {
|
||||
let dash_box = dash_container._slider.get_child();
|
||||
|
||||
// verify that we did not already blur that dash
|
||||
if (!dash_box.get_children().some((child) => {
|
||||
return child.get_name() === "dash-blurred-background-parent";
|
||||
})) {
|
||||
this._log("dash to dock found, blurring it");
|
||||
|
||||
// finally blur the dash
|
||||
let dash = dash_box.get_children().find(child => {
|
||||
return child.get_name() === 'dash';
|
||||
});
|
||||
|
||||
this.dashes.push(this.blur_dash_from(dash, dash_container));
|
||||
}
|
||||
}
|
||||
|
||||
// Blurs the dash and returns a `DashInfos` containing its information
|
||||
blur_dash_from(dash, dash_container) {
|
||||
// the effect to be applied
|
||||
let effect = new Shell.BlurEffect({
|
||||
brightness: this.brightness,
|
||||
sigma: this.sigma,
|
||||
mode: Shell.BlurMode.BACKGROUND
|
||||
});
|
||||
|
||||
// dash background parent, not visible
|
||||
let background_parent = new St.Widget({
|
||||
name: 'dash-blurred-background-parent',
|
||||
style_class: 'dash-blurred-background-parent',
|
||||
width: 0,
|
||||
height: 0
|
||||
});
|
||||
|
||||
// dash background widget
|
||||
let background = new St.Widget({
|
||||
name: 'dash-blurred-background',
|
||||
style_class: 'dash-blurred-background',
|
||||
x: 0,
|
||||
y: dash_container._slider.y,
|
||||
width: dash.width,
|
||||
height: dash.height,
|
||||
});
|
||||
|
||||
// updates size and position on change
|
||||
this.connections.connect(dash_container._slider, 'notify::y', _ => {
|
||||
background.y = dash_container._slider.y;
|
||||
});
|
||||
this.connections.connect(dash, 'notify::width', _ => {
|
||||
background.width = dash.width;
|
||||
});
|
||||
this.connections.connect(dash, 'notify::height', _ => {
|
||||
background.height = dash.height;
|
||||
});
|
||||
|
||||
// add the widget to the dash
|
||||
background.add_effect(effect);
|
||||
background_parent.add_child(background);
|
||||
dash.get_parent().insert_child_at_index(background_parent, 0);
|
||||
|
||||
// HACK
|
||||
//
|
||||
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
|
||||
//
|
||||
// This does not entirely fix this bug (shadows caused by windows
|
||||
// still cause artifacts), but it prevents the shadows of the panel
|
||||
// buttons to cause artifacts on the panel itself
|
||||
//
|
||||
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
|
||||
|
||||
if (this.prefs.HACKS_LEVEL === 1) {
|
||||
this._log("dash hack level 1");
|
||||
this.paint_signals.disconnect_all();
|
||||
|
||||
let rp = () => {
|
||||
effect.queue_repaint();
|
||||
};
|
||||
|
||||
dash._box.get_children().forEach((icon) => {
|
||||
try {
|
||||
let zone = icon.get_child_at_index(0);
|
||||
|
||||
this.connections.connect(zone, [
|
||||
'enter-event', 'leave-event', 'button-press-event'
|
||||
], rp);
|
||||
} catch (e) {
|
||||
this._log(`${e}, continuing`);
|
||||
}
|
||||
});
|
||||
|
||||
this.connections.connect(dash._box, 'actor-added', (_, actor) => {
|
||||
try {
|
||||
let zone = actor.get_child_at_index(0);
|
||||
|
||||
this.connections.connect(zone, [
|
||||
'enter-event', 'leave-event', 'button-press-event'
|
||||
], rp);
|
||||
} catch (e) {
|
||||
this._log(`${e}, continuing`);
|
||||
}
|
||||
});
|
||||
|
||||
let show_apps = dash._showAppsIcon;
|
||||
|
||||
this.connections.connect(show_apps, [
|
||||
'enter-event', 'leave-event', 'button-press-event'
|
||||
], rp);
|
||||
|
||||
this.connections.connect(dash, 'leave-event', rp);
|
||||
} else if (this.prefs.HACKS_LEVEL === 2) {
|
||||
this._log("dash hack level 2");
|
||||
|
||||
this.paint_signals.connect(background, effect);
|
||||
} else {
|
||||
this.paint_signals.disconnect_all();
|
||||
}
|
||||
|
||||
// create infos
|
||||
let infos = new DashInfos(
|
||||
this, dash, background_parent, effect, this.prefs
|
||||
);
|
||||
|
||||
// update the background
|
||||
this.update_background();
|
||||
|
||||
// returns infos
|
||||
return infos;
|
||||
}
|
||||
|
||||
/// Connect when overview if opened/closed to hide/show the blur accordingly
|
||||
connect_to_overview() {
|
||||
this.connections.disconnect_all_for(Main.overview);
|
||||
|
||||
if (this.prefs.dash_to_dock.UNBLUR_IN_OVERVIEW) {
|
||||
this.connections.connect(
|
||||
Main.overview, 'showing', this.hide.bind(this)
|
||||
);
|
||||
this.connections.connect(
|
||||
Main.overview, 'hidden', this.show.bind(this)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/// Updates the background to either remove it or not, according to the
|
||||
/// user preferences.
|
||||
update_background() {
|
||||
if (this.prefs.dash_to_dock.OVERRIDE_BACKGROUND)
|
||||
this.emit('override-background', true);
|
||||
else
|
||||
this.emit('reset-background', true);
|
||||
}
|
||||
|
||||
set_sigma(sigma) {
|
||||
this.sigma = sigma;
|
||||
this.emit('update-sigma', true);
|
||||
}
|
||||
|
||||
set_brightness(brightness) {
|
||||
this.brightness = brightness;
|
||||
this.emit('update-brightness', true);
|
||||
}
|
||||
|
||||
// not implemented for dynamic blur
|
||||
set_color(c) { }
|
||||
set_noise_amount(n) { }
|
||||
set_noise_lightness(l) { }
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from dashes");
|
||||
|
||||
this.emit('remove-dashes', true);
|
||||
|
||||
this.dashes = [];
|
||||
this.connections.disconnect_all();
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.emit('show', true);
|
||||
}
|
||||
hide() {
|
||||
this.emit('hide', true);
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > dash manager] ${str}`);
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(DashBlur.prototype);
|
@ -1,171 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { St, Shell } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
const Background = imports.ui.background;
|
||||
const UnlockDialog = imports.ui.unlockDialog.UnlockDialog;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const ColorEffect = Me.imports.effects.color_effect.ColorEffect;
|
||||
const NoiseEffect = Me.imports.effects.noise_effect.NoiseEffect;
|
||||
|
||||
let sigma;
|
||||
let brightness;
|
||||
let color;
|
||||
let noise;
|
||||
let lightness;
|
||||
|
||||
const original_createBackground =
|
||||
UnlockDialog.prototype._createBackground;
|
||||
const original_updateBackgroundEffects =
|
||||
UnlockDialog.prototype._updateBackgroundEffects;
|
||||
|
||||
|
||||
var LockscreenBlur = class LockscreenBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring lockscreen");
|
||||
|
||||
brightness = this.prefs.lockscreen.CUSTOMIZE
|
||||
? this.prefs.lockscreen.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS;
|
||||
sigma = this.prefs.lockscreen.CUSTOMIZE
|
||||
? this.prefs.lockscreen.SIGMA
|
||||
: this.prefs.SIGMA;
|
||||
color = this.prefs.lockscreen.CUSTOMIZE
|
||||
? this.prefs.lockscreen.COLOR
|
||||
: this.prefs.COLOR;
|
||||
noise = this.prefs.lockscreen.CUSTOMIZE
|
||||
? this.prefs.lockscreen.NOISE_AMOUNT
|
||||
: this.prefs.NOISE_AMOUNT;
|
||||
lightness = this.prefs.lockscreen.CUSTOMIZE
|
||||
? this.prefs.lockscreen.NOISE_LIGHTNESS
|
||||
: this.prefs.NOISE_LIGHTNESS;
|
||||
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
update_lockscreen() {
|
||||
UnlockDialog.prototype._createBackground =
|
||||
this._createBackground;
|
||||
UnlockDialog.prototype._updateBackgroundEffects =
|
||||
this._updateBackgroundEffects;
|
||||
}
|
||||
|
||||
_createBackground(monitorIndex) {
|
||||
let monitor = Main.layoutManager.monitors[monitorIndex];
|
||||
let widget = new St.Widget({
|
||||
style_class: "screen-shield-background",
|
||||
x: monitor.x,
|
||||
y: monitor.y,
|
||||
width: monitor.width,
|
||||
height: monitor.height,
|
||||
});
|
||||
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
name: 'blur',
|
||||
sigma: sigma,
|
||||
brightness: brightness
|
||||
});
|
||||
|
||||
// store the scale in the effect in order to retrieve later
|
||||
blur_effect.scale = monitor.geometry_scale;
|
||||
|
||||
let color_effect = new ColorEffect({
|
||||
name: 'color',
|
||||
color: color
|
||||
});
|
||||
|
||||
let noise_effect = new NoiseEffect({
|
||||
name: 'noise',
|
||||
noise: noise,
|
||||
lightness: lightness
|
||||
});
|
||||
|
||||
widget.add_effect(color_effect);
|
||||
widget.add_effect(noise_effect);
|
||||
widget.add_effect(blur_effect);
|
||||
|
||||
let bgManager = new Background.BackgroundManager({
|
||||
container: widget,
|
||||
monitorIndex,
|
||||
controlPosition: false,
|
||||
});
|
||||
|
||||
this._bgManagers.push(bgManager);
|
||||
|
||||
this._backgroundGroup.add_child(widget);
|
||||
}
|
||||
|
||||
_updateBackgroundEffects() {
|
||||
for (const widget of this._backgroundGroup) {
|
||||
const color_effect = widget.get_effect('blur');
|
||||
const noise_effect = widget.get_effect('blur');
|
||||
const blur_effect = widget.get_effect('blur');
|
||||
|
||||
if (color_effect)
|
||||
color_effect.set({
|
||||
color: color
|
||||
});
|
||||
|
||||
if (noise_effect) {
|
||||
noise_effect.set({
|
||||
noise: noise,
|
||||
lightness: lightness,
|
||||
});
|
||||
}
|
||||
|
||||
if (blur_effect) {
|
||||
blur_effect.set({
|
||||
brightness: brightness,
|
||||
sigma: sigma * blur_effect.scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_sigma(s) {
|
||||
sigma = s;
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
brightness = b;
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
set_color(c) {
|
||||
color = c;
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
set_noise_amount(n) {
|
||||
noise = n;
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
set_noise_lightness(l) {
|
||||
lightness = l;
|
||||
this.update_lockscreen();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from lockscreen");
|
||||
|
||||
UnlockDialog.prototype._createBackground =
|
||||
original_createBackground;
|
||||
UnlockDialog.prototype._updateBackgroundEffects =
|
||||
original_updateBackgroundEffects;
|
||||
|
||||
this.connections.disconnect_all();
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > lockscreen] ${str}`);
|
||||
}
|
||||
};
|
@ -1,294 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Shell, Gio, Meta } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const { WorkspaceAnimationController } = imports.ui.workspaceAnimation;
|
||||
const wac_proto = WorkspaceAnimationController.prototype;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const ColorEffect = Me.imports.effects.color_effect.ColorEffect;
|
||||
const NoiseEffect = Me.imports.effects.noise_effect.NoiseEffect;
|
||||
|
||||
const OVERVIEW_COMPONENTS_STYLE = [
|
||||
"",
|
||||
"overview-components-light",
|
||||
"overview-components-dark",
|
||||
"overview-components-transparent"
|
||||
];
|
||||
|
||||
|
||||
var OverviewBlur = class OverviewBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.effects = [];
|
||||
this.prefs = prefs;
|
||||
this._workspace_switch_bg_actors = [];
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring overview");
|
||||
|
||||
// connect to every background change (even without changing image)
|
||||
// FIXME this signal is fired very often, so we should find another one
|
||||
// fired only when necessary (but that still catches all cases)
|
||||
this.connections.connect(
|
||||
Main.layoutManager._backgroundGroup,
|
||||
'notify',
|
||||
_ => {
|
||||
this._log("updated background");
|
||||
this.update_backgrounds();
|
||||
}
|
||||
);
|
||||
|
||||
// connect to monitors change
|
||||
this.connections.connect(
|
||||
Main.layoutManager,
|
||||
'monitors-changed',
|
||||
_ => {
|
||||
if (Main.screenShield && !Main.screenShield.locked) {
|
||||
this._log("changed monitors");
|
||||
this.update_backgrounds();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// add css class name for workspace-switch background
|
||||
Main.uiGroup.add_style_class_name("blurred-overview");
|
||||
|
||||
// add css class name to make components semi-transparent if wanted
|
||||
this.update_components_classname();
|
||||
|
||||
// update backgrounds when the component is enabled
|
||||
this.update_backgrounds();
|
||||
|
||||
|
||||
// part for the workspace animation switch
|
||||
|
||||
// make sure not to do this part if the extension was enabled prior, as
|
||||
// the functions would call themselves and cause infinite recursion
|
||||
if (!this.enabled) {
|
||||
// store original workspace switching methods for restoring them on
|
||||
// disable()
|
||||
this._original_PrepareSwitch = wac_proto._prepareWorkspaceSwitch;
|
||||
this._original_FinishSwitch = wac_proto._finishWorkspaceSwitch;
|
||||
|
||||
const w_m = global.workspace_manager;
|
||||
const outer_this = this;
|
||||
|
||||
// create a blurred background actor for each monitor during a workspace
|
||||
// switch
|
||||
wac_proto._prepareWorkspaceSwitch = function (...params) {
|
||||
outer_this._log("prepare workspace switch");
|
||||
outer_this._original_PrepareSwitch.apply(this, params);
|
||||
|
||||
// this permits to show the blur behind windows that are on
|
||||
// workspaces on the left and right
|
||||
if (
|
||||
outer_this.prefs.applications.BLUR
|
||||
) {
|
||||
let ws_index = w_m.get_active_workspace_index();
|
||||
[ws_index - 1, ws_index + 1].forEach(
|
||||
i => w_m.get_workspace_by_index(i)?.list_windows().forEach(
|
||||
window => window.get_compositor_private().show()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Main.layoutManager.monitors.forEach(monitor => {
|
||||
if (
|
||||
!(
|
||||
Meta.prefs_get_workspaces_only_on_primary() &&
|
||||
(monitor !== Main.layoutManager.primaryMonitor)
|
||||
)
|
||||
) {
|
||||
const bg_actor = outer_this.create_background_actor(
|
||||
monitor
|
||||
);
|
||||
|
||||
Main.uiGroup.insert_child_above(
|
||||
bg_actor,
|
||||
global.window_group
|
||||
);
|
||||
|
||||
// store the actors so that we can delete them later
|
||||
outer_this._workspace_switch_bg_actors.push(bg_actor);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// remove the workspace-switch actors when the switch is done
|
||||
wac_proto._finishWorkspaceSwitch = function (...params) {
|
||||
outer_this._log("finish workspace switch");
|
||||
outer_this._original_FinishSwitch.apply(this, params);
|
||||
|
||||
// this hides windows that are not on the current workspace
|
||||
if (
|
||||
outer_this.prefs.applications.BLUR
|
||||
)
|
||||
for (let i = 0; i < w_m.get_n_workspaces(); i++) {
|
||||
if (i != w_m.get_active_workspace_index())
|
||||
w_m.get_workspace_by_index(i)?.list_windows().forEach(
|
||||
window => window.get_compositor_private().hide()
|
||||
);
|
||||
}
|
||||
|
||||
outer_this._workspace_switch_bg_actors.forEach(actor => {
|
||||
actor.destroy();
|
||||
});
|
||||
outer_this._workspace_switch_bg_actors = [];
|
||||
};
|
||||
}
|
||||
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
update_backgrounds() {
|
||||
// remove every old background
|
||||
Main.layoutManager.overviewGroup.get_children().forEach(actor => {
|
||||
if (actor.constructor.name === 'Meta_BackgroundActor') {
|
||||
Main.layoutManager.overviewGroup.remove_child(actor);
|
||||
actor.destroy();
|
||||
}
|
||||
});
|
||||
this.effects = [];
|
||||
|
||||
// add new backgrounds
|
||||
Main.layoutManager.monitors.forEach(monitor => {
|
||||
const bg_actor = this.create_background_actor(monitor);
|
||||
|
||||
Main.layoutManager.overviewGroup.insert_child_at_index(
|
||||
bg_actor,
|
||||
monitor.index
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
create_background_actor(monitor) {
|
||||
let bg_actor = new Meta.BackgroundActor;
|
||||
let background = Main.layoutManager._backgroundGroup.get_child_at_index(
|
||||
Main.layoutManager.monitors.length - monitor.index - 1
|
||||
);
|
||||
|
||||
if (!background) {
|
||||
this._log("could not get background for overview");
|
||||
return bg_actor;
|
||||
}
|
||||
|
||||
bg_actor.set_content(background.get_content());
|
||||
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
brightness: this.prefs.overview.CUSTOMIZE
|
||||
? this.prefs.overview.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS,
|
||||
sigma: this.prefs.overview.CUSTOMIZE
|
||||
? this.prefs.overview.SIGMA
|
||||
: this.prefs.SIGMA
|
||||
* monitor.geometry_scale,
|
||||
mode: Shell.BlurMode.ACTOR
|
||||
});
|
||||
|
||||
// store the scale in the effect in order to retrieve it in set_sigma
|
||||
blur_effect.scale = monitor.geometry_scale;
|
||||
|
||||
let color_effect = new ColorEffect({
|
||||
color: this.prefs.overview.CUSTOMIZE
|
||||
? this.prefs.overview.COLOR
|
||||
: this.prefs.COLOR
|
||||
});
|
||||
|
||||
let noise_effect = new NoiseEffect({
|
||||
noise: this.prefs.overview.CUSTOMIZE
|
||||
? this.prefs.overview.NOISE_AMOUNT
|
||||
: this.prefs.NOISE_AMOUNT,
|
||||
lightness: this.prefs.overview.CUSTOMIZE
|
||||
? this.prefs.overview.NOISE_LIGHTNESS
|
||||
: this.prefs.NOISE_LIGHTNESS
|
||||
});
|
||||
|
||||
bg_actor.add_effect(color_effect);
|
||||
bg_actor.add_effect(noise_effect);
|
||||
bg_actor.add_effect(blur_effect);
|
||||
this.effects.push({ blur_effect, color_effect, noise_effect });
|
||||
|
||||
bg_actor.set_x(monitor.x);
|
||||
bg_actor.set_y(monitor.y);
|
||||
|
||||
return bg_actor;
|
||||
}
|
||||
|
||||
/// Updates the classname to style overview components with semi-transparent
|
||||
/// backgrounds.
|
||||
update_components_classname() {
|
||||
OVERVIEW_COMPONENTS_STYLE.forEach(
|
||||
style => Main.uiGroup.remove_style_class_name(style)
|
||||
);
|
||||
|
||||
Main.uiGroup.add_style_class_name(
|
||||
OVERVIEW_COMPONENTS_STYLE[this.prefs.overview.STYLE_COMPONENTS]
|
||||
);
|
||||
}
|
||||
|
||||
set_sigma(s) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.sigma = s * effect.blur_effect.scale;
|
||||
});
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.brightness = b;
|
||||
});
|
||||
}
|
||||
|
||||
set_color(c) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.color_effect.color = c;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_amount(n) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.noise_effect.noise = n;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_lightness(l) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.noise_effect.lightness = l;
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from overview");
|
||||
Main.layoutManager.overviewGroup.get_children().forEach(actor => {
|
||||
if (actor.constructor.name === 'Meta_BackgroundActor') {
|
||||
Main.layoutManager.overviewGroup.remove_child(actor);
|
||||
}
|
||||
});
|
||||
Main.uiGroup.remove_style_class_name("blurred-overview");
|
||||
OVERVIEW_COMPONENTS_STYLE.forEach(
|
||||
style => Main.uiGroup.remove_style_class_name(style)
|
||||
);
|
||||
|
||||
// make sure to absolutely not do this if the component was not enabled
|
||||
// prior, as this would cause infinite recursion
|
||||
if (this.enabled) {
|
||||
// restore original behavior
|
||||
if (this._original_PrepareSwitch)
|
||||
wac_proto._prepareWorkspaceSwitch = this._original_PrepareSwitch;
|
||||
if (this._original_FinishSwitch)
|
||||
wac_proto._finishWorkspaceSwitch = this._original_FinishSwitch;
|
||||
}
|
||||
|
||||
this.effects = [];
|
||||
this.connections.disconnect_all();
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > overview] ${str}`);
|
||||
}
|
||||
};
|
@ -1,660 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { St, Shell, Meta, Gio, GLib } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { PaintSignals } = Me.imports.effects.paint_signals;
|
||||
const ColorEffect = Me.imports.effects.color_effect.ColorEffect;
|
||||
const NoiseEffect = Me.imports.effects.noise_effect.NoiseEffect;
|
||||
|
||||
const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com';
|
||||
|
||||
const PANEL_STYLES = [
|
||||
"transparent-panel",
|
||||
"light-panel",
|
||||
"dark-panel"
|
||||
];
|
||||
|
||||
|
||||
var PanelBlur = class PanelBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.window_signal_ids = new Map();
|
||||
this.prefs = prefs;
|
||||
this.actors_list = [];
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring top panel");
|
||||
|
||||
// check for panels when Dash to Panel is activated
|
||||
this.connections.connect(
|
||||
Main.extensionManager,
|
||||
'extension-state-changed',
|
||||
(_, extension) => {
|
||||
if (extension.uuid === DASH_TO_PANEL_UUID
|
||||
&& extension.state === 1
|
||||
) {
|
||||
this.connections.connect(
|
||||
global.dashToPanel,
|
||||
'panels-created',
|
||||
_ => this.blur_dtp_panels()
|
||||
);
|
||||
|
||||
this.blur_existing_panels();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.blur_existing_panels();
|
||||
|
||||
// connect to overview being opened/closed, and dynamically show or not
|
||||
// the blur when a window is near a panel
|
||||
this.connect_to_windows_and_overview();
|
||||
|
||||
// connect to every background change (even without changing image)
|
||||
// FIXME this signal is fired very often, so we should find another one
|
||||
// fired only when necessary (but that still catches all cases)
|
||||
this.connections.connect(
|
||||
Main.layoutManager._backgroundGroup,
|
||||
'notify',
|
||||
_ => this.actors_list.forEach(actors =>
|
||||
this.update_wallpaper(actors)
|
||||
)
|
||||
);
|
||||
|
||||
// connect to monitors change
|
||||
this.connections.connect(
|
||||
Main.layoutManager,
|
||||
'monitors-changed',
|
||||
_ => {
|
||||
if (Main.screenShield && !Main.screenShield.locked) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._log("resetting...");
|
||||
|
||||
this.disable();
|
||||
setTimeout(_ => this.enable(), 1);
|
||||
}
|
||||
|
||||
/// Check for already existing panels and blur them if they are not already
|
||||
blur_existing_panels() {
|
||||
// check if dash-to-panel is present
|
||||
if (global.dashToPanel) {
|
||||
// blur already existing ones
|
||||
if (global.dashToPanel.panels)
|
||||
this.blur_dtp_panels();
|
||||
} else {
|
||||
// if no dash-to-panel, blur the main and only panel
|
||||
this.maybe_blur_panel(Main.panel);
|
||||
}
|
||||
}
|
||||
|
||||
blur_dtp_panels() {
|
||||
// FIXME when Dash to Panel changes its size, it seems it creates new
|
||||
// panels; but I can't get to delete old widgets
|
||||
|
||||
// blur every panel found
|
||||
global.dashToPanel.panels.forEach(p => {
|
||||
this.maybe_blur_panel(p.panel);
|
||||
});
|
||||
|
||||
// if main panel is not included in the previous panels, blur it
|
||||
if (
|
||||
!global.dashToPanel.panels
|
||||
.map(p => p.panel)
|
||||
.includes(Main.panel)
|
||||
)
|
||||
this.maybe_blur_panel(Main.panel);
|
||||
};
|
||||
|
||||
/// Blur a panel only if it is not already blurred (contained in the list)
|
||||
maybe_blur_panel(panel) {
|
||||
// check if the panel is contained in the list
|
||||
let actors = this.actors_list.find(
|
||||
actors => actors.widgets.panel == panel
|
||||
);
|
||||
|
||||
if (!actors)
|
||||
// if the actors is not blurred, blur it
|
||||
this.blur_panel(panel);
|
||||
else
|
||||
// if it is blurred, update the blur anyway
|
||||
this.change_blur_type(actors);
|
||||
}
|
||||
|
||||
/// Blur a panel
|
||||
blur_panel(panel) {
|
||||
let panel_box = panel.get_parent();
|
||||
let is_dtp_panel = false;
|
||||
if (!panel_box.name) {
|
||||
is_dtp_panel = true;
|
||||
panel_box = panel_box.get_parent();
|
||||
}
|
||||
|
||||
let monitor = this.find_monitor_for(panel);
|
||||
if (!monitor)
|
||||
return;
|
||||
|
||||
let background_parent = new St.Widget({
|
||||
name: 'topbar-blurred-background-parent',
|
||||
x: 0, y: 0, width: 0, height: 0
|
||||
});
|
||||
|
||||
let background = this.prefs.panel.STATIC_BLUR
|
||||
? new Meta.BackgroundActor
|
||||
: new St.Widget;
|
||||
|
||||
background_parent.add_child(background);
|
||||
|
||||
// insert background parent
|
||||
panel_box.insert_child_at_index(background_parent, 0);
|
||||
|
||||
let blur = new Shell.BlurEffect({
|
||||
brightness: this.prefs.panel.CUSTOMIZE
|
||||
? this.prefs.panel.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS,
|
||||
sigma: this.prefs.panel.CUSTOMIZE
|
||||
? this.prefs.panel.SIGMA
|
||||
: this.prefs.SIGMA
|
||||
* monitor.geometry_scale,
|
||||
mode: this.prefs.panel.STATIC_BLUR
|
||||
? Shell.BlurMode.ACTOR
|
||||
: Shell.BlurMode.BACKGROUND
|
||||
});
|
||||
|
||||
// store the scale in the effect in order to retrieve it in set_sigma
|
||||
blur.scale = monitor.geometry_scale;
|
||||
|
||||
let color = new ColorEffect({
|
||||
color: this.prefs.panel.CUSTOMIZE
|
||||
? this.prefs.panel.COLOR
|
||||
: this.prefs.COLOR
|
||||
});
|
||||
|
||||
let noise = new NoiseEffect({
|
||||
noise: this.prefs.panel.CUSTOMIZE
|
||||
? this.prefs.panel.NOISE_AMOUNT
|
||||
: this.prefs.NOISE_AMOUNT,
|
||||
lightness: this.prefs.panel.CUSTOMIZE
|
||||
? this.prefs.panel.NOISE_LIGHTNESS
|
||||
: this.prefs.NOISE_LIGHTNESS
|
||||
});
|
||||
|
||||
let paint_signals = new PaintSignals(this.connections);
|
||||
|
||||
let actors = {
|
||||
widgets: { panel, panel_box, background, background_parent },
|
||||
effects: { blur, color, noise },
|
||||
paint_signals,
|
||||
monitor,
|
||||
is_dtp_panel
|
||||
};
|
||||
|
||||
this.actors_list.push(actors);
|
||||
|
||||
// perform updates
|
||||
this.change_blur_type(actors);
|
||||
|
||||
// connect to panel, panel_box and its parent position or size change
|
||||
// this should fire update_size every time one of its params change
|
||||
this.connections.connect(
|
||||
panel,
|
||||
'notify::position',
|
||||
_ => this.update_size(actors)
|
||||
);
|
||||
this.connections.connect(
|
||||
panel_box,
|
||||
['notify::size', 'notify::position'],
|
||||
_ => this.update_size(actors)
|
||||
);
|
||||
this.connections.connect(
|
||||
panel_box.get_parent(),
|
||||
'notify::position',
|
||||
_ => this.update_size(actors)
|
||||
);
|
||||
}
|
||||
|
||||
update_all_blur_type() {
|
||||
this.actors_list.forEach(actors => this.change_blur_type(actors));
|
||||
}
|
||||
|
||||
change_blur_type(actors) {
|
||||
let is_static = this.prefs.panel.STATIC_BLUR;
|
||||
|
||||
// reset widgets to right state
|
||||
actors.widgets.background_parent.remove_child(actors.widgets.background);
|
||||
actors.widgets.background.remove_effect(actors.effects.blur);
|
||||
actors.widgets.background.remove_effect(actors.effects.color);
|
||||
actors.widgets.background.remove_effect(actors.effects.noise);
|
||||
|
||||
// create new background actor
|
||||
actors.widgets.background = is_static
|
||||
? new Meta.BackgroundActor
|
||||
: new St.Widget;
|
||||
|
||||
// change blur mode
|
||||
actors.effects.blur.set_mode(is_static ? 0 : 1);
|
||||
|
||||
// disable other effects if the blur is dynamic, as they makes it opaque
|
||||
actors.effects.color._static = is_static;
|
||||
actors.effects.noise._static = is_static;
|
||||
actors.effects.color.update_enabled();
|
||||
actors.effects.noise.update_enabled();
|
||||
|
||||
// add the effects in order
|
||||
actors.widgets.background.add_effect(actors.effects.color);
|
||||
actors.widgets.background.add_effect(actors.effects.noise);
|
||||
actors.widgets.background.add_effect(actors.effects.blur);
|
||||
|
||||
// add the background actor behing the panel
|
||||
actors.widgets.background_parent.add_child(actors.widgets.background);
|
||||
|
||||
// perform updates
|
||||
this.update_wallpaper(actors);
|
||||
this.update_size(actors);
|
||||
|
||||
|
||||
// HACK
|
||||
//
|
||||
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
|
||||
//
|
||||
// This does not entirely fix this bug (shadows caused by windows
|
||||
// still cause artifacts), but it prevents the shadows of the panel
|
||||
// buttons to cause artifacts on the panel itself
|
||||
//
|
||||
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
|
||||
|
||||
if (!is_static) {
|
||||
if (this.prefs.HACKS_LEVEL === 1) {
|
||||
this._log("panel hack level 1");
|
||||
actors.paint_signals.disconnect_all();
|
||||
|
||||
let rp = () => { actors.effects.blur.queue_repaint(); };
|
||||
|
||||
this.connections.connect(actors.widgets.panel, [
|
||||
'enter-event', 'leave-event', 'button-press-event'
|
||||
], rp);
|
||||
|
||||
actors.widgets.panel.get_children().forEach(child => {
|
||||
this.connections.connect(child, [
|
||||
'enter-event', 'leave-event', 'button-press-event'
|
||||
], rp);
|
||||
});
|
||||
} else if (this.prefs.HACKS_LEVEL === 2) {
|
||||
this._log("panel hack level 2");
|
||||
actors.paint_signals.disconnect_all();
|
||||
|
||||
actors.paint_signals.connect(
|
||||
actors.widgets.background, actors.effects.blur
|
||||
);
|
||||
} else {
|
||||
actors.paint_signals.disconnect_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_wallpaper(actors) {
|
||||
// if static blur, get right wallpaper and update blur with it
|
||||
if (this.prefs.panel.STATIC_BLUR) {
|
||||
let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
|
||||
Main.layoutManager.monitors.length
|
||||
- this.find_monitor_for(actors.widgets.panel).index - 1
|
||||
);
|
||||
if (bg)
|
||||
actors.widgets.background.set_content(bg.get_content());
|
||||
else
|
||||
this._log("could not get background for panel");
|
||||
}
|
||||
}
|
||||
|
||||
update_size(actors) {
|
||||
let panel = actors.widgets.panel;
|
||||
let panel_box = actors.widgets.panel_box;
|
||||
let background = actors.widgets.background;
|
||||
let monitor = this.find_monitor_for(panel);
|
||||
if (!monitor)
|
||||
return;
|
||||
|
||||
let [width, height] = panel_box.get_size();
|
||||
background.width = width;
|
||||
background.height = height;
|
||||
|
||||
// if static blur, need to clip the background
|
||||
if (this.prefs.panel.STATIC_BLUR) {
|
||||
// an alternative to panel.get_transformed_position, because it
|
||||
// sometimes yields NaN (probably when the actor is not fully
|
||||
// positionned yet)
|
||||
let [p_x, p_y] = panel_box.get_position();
|
||||
let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
|
||||
let x = p_x + p_p_x - monitor.x;
|
||||
let y = p_y + p_p_y - monitor.y;
|
||||
|
||||
background.set_clip(x, y, width, height);
|
||||
background.x = -x;
|
||||
background.y = -y;
|
||||
|
||||
// fixes a bug where the blur is washed away when changing the sigma
|
||||
this.invalidate_blur(actors);
|
||||
} else {
|
||||
background.x = panel.x;
|
||||
background.y = panel.y;
|
||||
}
|
||||
|
||||
// update the monitor panel is on
|
||||
actors.monitor = this.find_monitor_for(panel);
|
||||
}
|
||||
|
||||
/// An helper function to find the monitor in which an actor is situated,
|
||||
/// there might be a pre-existing function in GLib already
|
||||
find_monitor_for(actor) {
|
||||
let extents = actor.get_transformed_extents();
|
||||
let rect = new Meta.Rectangle({
|
||||
x: extents.get_x(),
|
||||
y: extents.get_y(),
|
||||
width: extents.get_width(),
|
||||
height: extents.get_height(),
|
||||
});
|
||||
|
||||
let index = global.display.get_monitor_index_for_rect(rect);
|
||||
|
||||
return Main.layoutManager.monitors[index];
|
||||
}
|
||||
|
||||
/// Connect when overview if opened/closed to hide/show the blur accordingly
|
||||
///
|
||||
/// If HIDETOPBAR is set, we need just to hide the blur when showing appgrid
|
||||
/// (so no shadow is cropped)
|
||||
connect_to_overview() {
|
||||
// may be called when panel blur is disabled, if hidetopbar
|
||||
// compatibility is toggled on/off
|
||||
// if this is the case, do nothing as only the panel blur interfers with
|
||||
// hidetopbar
|
||||
if (
|
||||
this.prefs.panel.BLUR &&
|
||||
this.prefs.panel.UNBLUR_IN_OVERVIEW
|
||||
) {
|
||||
if (!this.prefs.hidetopbar.COMPATIBILITY) {
|
||||
this.connections.connect(
|
||||
Main.overview, 'showing', this.hide.bind(this)
|
||||
);
|
||||
this.connections.connect(
|
||||
Main.overview, 'hidden', this.show.bind(this)
|
||||
);
|
||||
} else {
|
||||
let appDisplay = Main.overview._overview._controls._appDisplay;
|
||||
|
||||
this.connections.connect(
|
||||
appDisplay, 'show', this.hide.bind(this)
|
||||
);
|
||||
this.connections.connect(
|
||||
appDisplay, 'hide', this.show.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to windows disable transparency when a window is too close
|
||||
connect_to_windows() {
|
||||
if (
|
||||
this.prefs.panel.OVERRIDE_BACKGROUND_DYNAMICALLY
|
||||
) {
|
||||
// connect to overview opening/closing
|
||||
this.connections.connect(Main.overview, ['showing', 'hiding'],
|
||||
this.update_visibility.bind(this)
|
||||
);
|
||||
|
||||
// connect to session mode update
|
||||
this.connections.connect(Main.sessionMode, 'updated',
|
||||
this.update_visibility.bind(this)
|
||||
);
|
||||
|
||||
// manage already-existing windows
|
||||
for (const meta_window_actor of global.get_window_actors()) {
|
||||
this.on_window_actor_added(
|
||||
meta_window_actor.get_parent(), meta_window_actor
|
||||
);
|
||||
}
|
||||
|
||||
// manage windows at their creation/removal
|
||||
this.connections.connect(global.window_group, 'actor-added',
|
||||
this.on_window_actor_added.bind(this)
|
||||
);
|
||||
this.connections.connect(global.window_group, 'actor-removed',
|
||||
this.on_window_actor_removed.bind(this)
|
||||
);
|
||||
|
||||
// connect to a workspace change
|
||||
this.connections.connect(global.window_manager, 'switch-workspace',
|
||||
this.update_visibility.bind(this)
|
||||
);
|
||||
|
||||
// perform early update
|
||||
this.update_visibility();
|
||||
} else {
|
||||
// reset transparency for every panels
|
||||
this.actors_list.forEach(
|
||||
actors => this.set_should_override_panel(actors, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An helper to connect to both the windows and overview signals.
|
||||
/// This is the only function that should be directly called, to prevent
|
||||
/// inconsistencies with signals not being disconnected.
|
||||
connect_to_windows_and_overview() {
|
||||
this.disconnect_from_windows_and_overview();
|
||||
this.connect_to_overview();
|
||||
this.connect_to_windows();
|
||||
}
|
||||
|
||||
/// Disconnect all the connections created by connect_to_windows
|
||||
disconnect_from_windows_and_overview() {
|
||||
// disconnect the connections to actors
|
||||
for (const actor of [
|
||||
Main.overview, Main.sessionMode,
|
||||
global.window_group, global.window_manager,
|
||||
Main.overview._overview._controls._appDisplay
|
||||
]) {
|
||||
this.connections.disconnect_all_for(actor);
|
||||
}
|
||||
|
||||
// disconnect the connections from windows
|
||||
for (const [actor, ids] of this.window_signal_ids) {
|
||||
for (const id of ids) {
|
||||
actor.disconnect(id);
|
||||
}
|
||||
}
|
||||
this.window_signal_ids = new Map();
|
||||
}
|
||||
|
||||
/// Callback when a new window is added
|
||||
on_window_actor_added(container, meta_window_actor) {
|
||||
this.window_signal_ids.set(meta_window_actor, [
|
||||
meta_window_actor.connect('notify::allocation',
|
||||
_ => this.update_visibility()
|
||||
),
|
||||
meta_window_actor.connect('notify::visible',
|
||||
_ => this.update_visibility()
|
||||
)
|
||||
]);
|
||||
this.update_visibility();
|
||||
}
|
||||
|
||||
/// Callback when a window is removed
|
||||
on_window_actor_removed(container, meta_window_actor) {
|
||||
for (const signalId of this.window_signal_ids.get(meta_window_actor)) {
|
||||
meta_window_actor.disconnect(signalId);
|
||||
}
|
||||
this.window_signal_ids.delete(meta_window_actor);
|
||||
this.update_visibility();
|
||||
}
|
||||
|
||||
/// Update the visibility of the blur effect
|
||||
update_visibility() {
|
||||
if (
|
||||
Main.panel.has_style_pseudo_class('overview')
|
||||
|| !Main.sessionMode.hasWindows
|
||||
) {
|
||||
this.actors_list.forEach(
|
||||
actors => this.set_should_override_panel(actors, true)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Main.layoutManager.primaryMonitor)
|
||||
return;
|
||||
|
||||
// get all the windows in the active workspace that are visible
|
||||
const workspace = global.workspace_manager.get_active_workspace();
|
||||
const windows = workspace.list_windows().filter(meta_window =>
|
||||
meta_window.showing_on_its_workspace()
|
||||
&& !meta_window.is_hidden()
|
||||
&& meta_window.get_window_type() !== Meta.WindowType.DESKTOP
|
||||
// exclude Desktop Icons NG
|
||||
&& meta_window.get_gtk_application_id() !== "com.rastersoft.ding"
|
||||
);
|
||||
|
||||
// check if at least one window is near enough to each panel and act
|
||||
// accordingly
|
||||
const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
this.actors_list
|
||||
// do not apply for dtp panels, as it would only cause bugs and it
|
||||
// can be done from its preferences anyway
|
||||
.filter(actors => !actors.is_dtp_panel)
|
||||
.forEach(actors => {
|
||||
let panel = actors.widgets.panel;
|
||||
let panel_top = panel.get_transformed_position()[1];
|
||||
let panel_bottom = panel_top + panel.get_height();
|
||||
|
||||
// check if at least a window is near enough the panel
|
||||
let window_overlap_panel = false;
|
||||
windows.forEach(meta_window => {
|
||||
let window_monitor_i = meta_window.get_monitor();
|
||||
let same_monitor = actors.monitor.index == window_monitor_i;
|
||||
|
||||
let window_vertical_pos = meta_window.get_frame_rect().y;
|
||||
|
||||
// if so, and if in the same monitor, then it overlaps
|
||||
if (same_monitor
|
||||
&&
|
||||
window_vertical_pos < panel_bottom + 5 * scale
|
||||
)
|
||||
window_overlap_panel = true;
|
||||
});
|
||||
|
||||
// if no window overlaps, then the panel is transparent
|
||||
this.set_should_override_panel(
|
||||
actors, !window_overlap_panel
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Choose wether or not the panel background should be overriden, in
|
||||
/// respect to its argument and the `override-background` setting.
|
||||
set_should_override_panel(actors, should_override) {
|
||||
let panel = actors.widgets.panel;
|
||||
|
||||
PANEL_STYLES.forEach(style => panel.remove_style_class_name(style));
|
||||
|
||||
if (
|
||||
this.prefs.panel.OVERRIDE_BACKGROUND
|
||||
&&
|
||||
should_override
|
||||
)
|
||||
panel.add_style_class_name(
|
||||
PANEL_STYLES[this.prefs.panel.STYLE_PANEL]
|
||||
);
|
||||
}
|
||||
|
||||
/// Fixes a bug where the blur is washed away when changing the sigma, or
|
||||
/// enabling/disabling other effects.
|
||||
invalidate_blur(actors) {
|
||||
if (this.prefs.panel.STATIC_BLUR && actors.widgets.background)
|
||||
actors.widgets.background.get_content().invalidate();
|
||||
}
|
||||
|
||||
invalidate_all_blur() {
|
||||
this.actors_list.forEach(actors => this.invalidate_blur(actors));
|
||||
}
|
||||
|
||||
set_sigma(s) {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.effects.blur.sigma = s * actors.effects.blur.scale;
|
||||
this.invalidate_blur(actors);
|
||||
});
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.effects.blur.brightness = b;
|
||||
});
|
||||
}
|
||||
|
||||
set_color(c) {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.effects.color.color = c;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_amount(n) {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.effects.noise.noise = n;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_lightness(l) {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.effects.noise.lightness = l;
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.widgets.background_parent.show();
|
||||
});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.actors_list.forEach(actors => {
|
||||
actors.widgets.background_parent.hide();
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from top panel");
|
||||
|
||||
this.disconnect_from_windows_and_overview();
|
||||
|
||||
this.actors_list.forEach(actors => {
|
||||
this.set_should_override_panel(actors, false);
|
||||
try {
|
||||
actors.widgets.panel_box.remove_child(
|
||||
actors.widgets.background_parent
|
||||
);
|
||||
} catch (e) { }
|
||||
|
||||
});
|
||||
|
||||
this.actors_list = [];
|
||||
|
||||
this.connections.disconnect_all();
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > panel] ${str}`);
|
||||
}
|
||||
};
|
@ -1,166 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Shell, Gio, Meta } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const ColorEffect = Me.imports.effects.color_effect.ColorEffect;
|
||||
const NoiseEffect = Me.imports.effects.noise_effect.NoiseEffect;
|
||||
|
||||
|
||||
var ScreenshotBlur = class ScreenshotBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.effects = [];
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring screenshot's window selector");
|
||||
|
||||
// connect to every background change (even without changing image)
|
||||
// FIXME this signal is fired very often, so we should find another one
|
||||
// fired only when necessary (but that still catches all cases)
|
||||
this.connections.connect(
|
||||
Main.layoutManager._backgroundGroup,
|
||||
'notify',
|
||||
_ => {
|
||||
this._log("updated background for screenshot's window selector");
|
||||
this.update_backgrounds();
|
||||
}
|
||||
);
|
||||
|
||||
// connect to monitors change
|
||||
this.connections.connect(
|
||||
Main.layoutManager,
|
||||
'monitors-changed',
|
||||
_ => {
|
||||
if (Main.screenShield && !Main.screenShield.locked) {
|
||||
this._log("changed monitors for screenshot's window selector");
|
||||
this.update_backgrounds();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// update backgrounds when the component is enabled
|
||||
this.update_backgrounds();
|
||||
}
|
||||
|
||||
update_backgrounds() {
|
||||
// remove every old background
|
||||
this.remove();
|
||||
|
||||
// add new backgrounds
|
||||
for (let i = 0; i < Main.screenshotUI._windowSelectors.length; i++) {
|
||||
const actor = Main.screenshotUI._windowSelectors[i];
|
||||
const monitor = Main.layoutManager.monitors[i];
|
||||
|
||||
if (!monitor)
|
||||
continue;
|
||||
|
||||
const bg_actor = this.create_background_actor(monitor);
|
||||
actor.insert_child_at_index(bg_actor, 0);
|
||||
actor._blur_actor = bg_actor;
|
||||
}
|
||||
}
|
||||
|
||||
create_background_actor(monitor) {
|
||||
let bg_actor = new Meta.BackgroundActor;
|
||||
let background = Main.layoutManager._backgroundGroup.get_child_at_index(
|
||||
Main.layoutManager.monitors.length - monitor.index - 1
|
||||
);
|
||||
|
||||
if (!background) {
|
||||
this._log("could not get background for screenshot's window selector");
|
||||
return bg_actor;
|
||||
}
|
||||
|
||||
bg_actor.set_content(background.get_content());
|
||||
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
brightness: this.prefs.screenshot.CUSTOMIZE
|
||||
? this.prefs.screenshot.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS,
|
||||
sigma: this.prefs.screenshot.CUSTOMIZE
|
||||
? this.prefs.screenshot.SIGMA
|
||||
: this.prefs.SIGMA
|
||||
* monitor.geometry_scale,
|
||||
mode: Shell.BlurMode.ACTOR
|
||||
});
|
||||
|
||||
// store the scale in the effect in order to retrieve it in set_sigma
|
||||
blur_effect.scale = monitor.geometry_scale;
|
||||
|
||||
let color_effect = new ColorEffect({
|
||||
color: this.prefs.screenshot.CUSTOMIZE
|
||||
? this.prefs.screenshot.COLOR
|
||||
: this.prefs.COLOR
|
||||
});
|
||||
|
||||
let noise_effect = new NoiseEffect({
|
||||
noise: this.prefs.screenshot.CUSTOMIZE
|
||||
? this.prefs.screenshot.NOISE_AMOUNT
|
||||
: this.prefs.NOISE_AMOUNT,
|
||||
lightness: this.prefs.screenshot.CUSTOMIZE
|
||||
? this.prefs.screenshot.NOISE_LIGHTNESS
|
||||
: this.prefs.NOISE_LIGHTNESS
|
||||
});
|
||||
|
||||
bg_actor.add_effect(color_effect);
|
||||
bg_actor.add_effect(noise_effect);
|
||||
bg_actor.add_effect(blur_effect);
|
||||
this.effects.push({ blur_effect, color_effect, noise_effect });
|
||||
|
||||
return bg_actor;
|
||||
}
|
||||
|
||||
set_sigma(s) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.sigma = s * effect.blur_effect;
|
||||
});
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.brightness = b;
|
||||
});
|
||||
}
|
||||
|
||||
set_color(c) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.color_effect.color = c;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_amount(n) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.noise_effect.noise = n;
|
||||
});
|
||||
}
|
||||
|
||||
set_noise_lightness(l) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.noise_effect.lightness = l;
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
Main.screenshotUI._windowSelectors.forEach(actor => {
|
||||
if (actor._blur_actor)
|
||||
actor.remove_child(actor._blur_actor);
|
||||
});
|
||||
this.effects = [];
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from screenshot's window selector");
|
||||
|
||||
this.remove();
|
||||
this.connections.disconnect_all();
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > screenshot] ${str}`);
|
||||
}
|
||||
};
|
@ -1,165 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { St, Shell, Meta, Gio } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const { PaintSignals } = Me.imports.effects.paint_signals;
|
||||
|
||||
|
||||
var WindowListBlur = class WindowListBlur {
|
||||
constructor(connections, prefs) {
|
||||
this.connections = connections;
|
||||
this.prefs = prefs;
|
||||
this.paint_signals = new PaintSignals(connections);
|
||||
this.effects = [];
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._log("blurring window list");
|
||||
|
||||
// blur if window-list is found
|
||||
Main.layoutManager.uiGroup.get_children().forEach(
|
||||
child => this.try_blur(child)
|
||||
);
|
||||
|
||||
// listen to new actors in `Main.layoutManager.uiGroup` and blur it if
|
||||
// if is window-list
|
||||
this.connections.connect(
|
||||
Main.layoutManager.uiGroup,
|
||||
'actor-added',
|
||||
(_, child) => this.try_blur(child)
|
||||
);
|
||||
|
||||
// connect to overview
|
||||
this.connections.connect(Main.overview, 'showing', _ => {
|
||||
this.hide();
|
||||
});
|
||||
this.connections.connect(Main.overview, 'hidden', _ => {
|
||||
this.show();
|
||||
});
|
||||
}
|
||||
|
||||
try_blur(child) {
|
||||
if (
|
||||
child.constructor.name === "WindowList" &&
|
||||
child.style !== "background:transparent;"
|
||||
) {
|
||||
this._log("found window list to blur");
|
||||
|
||||
let blur_effect = new Shell.BlurEffect({
|
||||
name: 'window-list-blur',
|
||||
sigma: this.prefs.window_list.CUSTOMIZE
|
||||
? this.prefs.window_list.SIGMA
|
||||
: this.prefs.SIGMA,
|
||||
brightness: this.prefs.window_list.CUSTOMIZE
|
||||
? this.prefs.window_list.BRIGHTNESS
|
||||
: this.prefs.BRIGHTNESS,
|
||||
mode: Shell.BlurMode.BACKGROUND
|
||||
});
|
||||
|
||||
child.set_style("background:transparent;");
|
||||
child.add_effect(blur_effect);
|
||||
this.effects.push({ blur_effect });
|
||||
|
||||
child._windowList.get_children().forEach(
|
||||
window => this.blur_window_button(window)
|
||||
);
|
||||
|
||||
this.connections.connect(
|
||||
child._windowList,
|
||||
'actor-added',
|
||||
(_, window) => this.blur_window_button(window)
|
||||
);
|
||||
|
||||
|
||||
// HACK
|
||||
//
|
||||
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
|
||||
//
|
||||
// This does not entirely fix this bug (shadows caused by windows
|
||||
// still cause artifacts), but it prevents the shadows of the panel
|
||||
// buttons to cause artifacts on the panel itself
|
||||
//
|
||||
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
|
||||
|
||||
if (this.prefs.HACKS_LEVEL === 1) {
|
||||
this._log("window list hack level 1");
|
||||
|
||||
this.paint_signals.connect(child, blur_effect);
|
||||
|
||||
} else if (this.prefs.HACKS_LEVEL === 2) {
|
||||
this._log("window list hack level 2");
|
||||
|
||||
this.paint_signals.connect(child, blur_effect);
|
||||
} else {
|
||||
this.paint_signals.disconnect_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blur_window_button(window) {
|
||||
window.get_child_at_index(0).set_style(
|
||||
"box-shadow:none; background-color:rgba(0,0,0,0.2); border-radius:5px;"
|
||||
);
|
||||
}
|
||||
|
||||
try_remove_blur(child) {
|
||||
if (
|
||||
child.constructor.name === "WindowList" &&
|
||||
child.style === "background:transparent;"
|
||||
) {
|
||||
child.style = null;
|
||||
child.remove_effect_by_name('window-list-blur');
|
||||
|
||||
child._windowList.get_children().forEach(
|
||||
child => child.get_child_at_index(0).set_style(null)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set_sigma(s) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.sigma = s;
|
||||
});
|
||||
}
|
||||
|
||||
set_brightness(b) {
|
||||
this.effects.forEach(effect => {
|
||||
effect.blur_effect.brightness = b;
|
||||
});
|
||||
}
|
||||
|
||||
// not implemented for dynamic blur
|
||||
set_color(c) { }
|
||||
set_noise_amount(n) { }
|
||||
set_noise_lightness(l) { }
|
||||
|
||||
hide() {
|
||||
this.set_sigma(0);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.set_sigma(
|
||||
this.prefs.window_list.CUSTOMIZE
|
||||
? this.prefs.window_list.SIGMA
|
||||
: this.prefs.SIGMA
|
||||
);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._log("removing blur from window list");
|
||||
|
||||
Main.layoutManager.uiGroup.get_children().forEach(
|
||||
child => this.try_remove_blur(child)
|
||||
);
|
||||
|
||||
this.effects = [];
|
||||
this.connections.disconnect_all();
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
if (this.prefs.DEBUG)
|
||||
log(`[Blur my Shell > window list] ${str}`);
|
||||
}
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const GObject = imports.gi.GObject;
|
||||
|
||||
/// An object to easily manage signals.
|
||||
var Connections = class Connections {
|
||||
constructor() {
|
||||
this.buffer = [];
|
||||
}
|
||||
|
||||
/// Adds a connection.
|
||||
///
|
||||
/// Takes as arguments:
|
||||
/// - an actor, which fires the signal
|
||||
/// - signal(s) (string or array of strings), which are watched for
|
||||
/// - a callback, which is called when the signal is fired
|
||||
connect(actor, signals, handler) {
|
||||
if (signals instanceof Array) {
|
||||
signals.forEach(signal => {
|
||||
let id = actor.connect(signal, handler);
|
||||
this.process_connection(actor, id);
|
||||
});
|
||||
} else {
|
||||
let id = actor.connect(signals, handler);
|
||||
this.process_connection(actor, id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Process the given actor and id.
|
||||
///
|
||||
/// This makes sure that the signal is disconnected when the actor is
|
||||
/// destroyed, and that the signal can be managed through other Connections
|
||||
/// methods.
|
||||
process_connection(actor, id) {
|
||||
let infos = {
|
||||
actor: actor,
|
||||
id: id
|
||||
};
|
||||
|
||||
// remove the signal when the actor is destroyed
|
||||
if (
|
||||
actor.connect &&
|
||||
(
|
||||
!(actor instanceof GObject.Object) ||
|
||||
GObject.signal_lookup('destroy', actor)
|
||||
)
|
||||
) {
|
||||
let destroy_id = actor.connect('destroy', () => {
|
||||
actor.disconnect(id);
|
||||
actor.disconnect(destroy_id);
|
||||
|
||||
let index = this.buffer.indexOf(infos);
|
||||
if (index >= 0) {
|
||||
this.buffer.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.buffer.push(infos);
|
||||
}
|
||||
|
||||
/// Disconnects every connection found for an actor.
|
||||
disconnect_all_for(actor) {
|
||||
// get every connection stored for the actor
|
||||
let actor_connections = this.buffer.filter(
|
||||
infos => infos.actor === actor
|
||||
);
|
||||
|
||||
// remove each of them
|
||||
actor_connections.forEach((connection) => {
|
||||
// disconnect
|
||||
try {
|
||||
connection.actor.disconnect(connection.id);
|
||||
} catch (e) {
|
||||
this._log(`error removing connection: ${e}; continuing`);
|
||||
}
|
||||
|
||||
// remove from buffer
|
||||
let index = this.buffer.indexOf(connection);
|
||||
this.buffer.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
/// Disconnect every connection for each actor.
|
||||
disconnect_all() {
|
||||
this.buffer.forEach((connection) => {
|
||||
// disconnect
|
||||
try {
|
||||
connection.actor.disconnect(connection.id);
|
||||
} catch (e) {
|
||||
this._log(`error removing connection: ${e}; continuing`);
|
||||
}
|
||||
});
|
||||
|
||||
// reset buffer
|
||||
this.buffer = [];
|
||||
}
|
||||
|
||||
_log(str) {
|
||||
// no need to check if DEBUG here as this._log is only used on error
|
||||
log(`[Blur my Shell > connections] ${str}`);
|
||||
}
|
||||
};
|
@ -1,131 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
const { Type } = Me.imports.conveniences.settings;
|
||||
|
||||
// This lists the preferences keys
|
||||
var Keys = [
|
||||
{
|
||||
component: "general", schemas: [
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.B, name: "color-and-noise" },
|
||||
{ type: Type.I, name: "hacks-level" },
|
||||
{ type: Type.B, name: "debug" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "overview", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.I, name: "style-components" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "appfolder", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.I, name: "style-dialogs" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "panel", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.B, name: "static-blur" },
|
||||
{ type: Type.B, name: "unblur-in-overview" },
|
||||
{ type: Type.B, name: "override-background" },
|
||||
{ type: Type.I, name: "style-panel" },
|
||||
{ type: Type.B, name: "override-background-dynamically" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "dash-to-dock", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.B, name: "static-blur" },
|
||||
{ type: Type.B, name: "unblur-in-overview" },
|
||||
{ type: Type.B, name: "override-background" },
|
||||
{ type: Type.I, name: "style-dash-to-dock" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "applications", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
{ type: Type.I, name: "opacity" },
|
||||
{ type: Type.B, name: "blur-on-overview" },
|
||||
{ type: Type.B, name: "enable-all" },
|
||||
{ type: Type.AS, name: "whitelist" },
|
||||
{ type: Type.AS, name: "blacklist" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "lockscreen", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "window-list", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "screenshot", schemas: [
|
||||
{ type: Type.B, name: "blur" },
|
||||
{ type: Type.B, name: "customize" },
|
||||
{ type: Type.I, name: "sigma" },
|
||||
{ type: Type.D, name: "brightness" },
|
||||
{ type: Type.C, name: "color" },
|
||||
{ type: Type.D, name: "noise-amount" },
|
||||
{ type: Type.D, name: "noise-lightness" },
|
||||
]
|
||||
},
|
||||
{
|
||||
component: "hidetopbar", schemas: [
|
||||
{ type: Type.B, name: "compatibility" },
|
||||
]
|
||||
},
|
||||
];
|
@ -1,185 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Gio, GLib } = imports.gi;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
/// An enum non-extensively describing the type of gsettings key.
|
||||
var Type = {
|
||||
B: 'Boolean',
|
||||
I: 'Integer',
|
||||
D: 'Double',
|
||||
S: 'String',
|
||||
C: 'Color',
|
||||
AS: 'StringArray'
|
||||
};
|
||||
|
||||
/// An object to get and manage the gsettings preferences.
|
||||
///
|
||||
/// Should be initialized with an array of keys, for example:
|
||||
///
|
||||
/// let prefs = new Prefs([
|
||||
/// { type: Type.I, name: "panel-corner-radius" },
|
||||
/// { type: Type.B, name: "debug" }
|
||||
/// ]);
|
||||
///
|
||||
/// Each {type, name} object represents a gsettings key, which must be created
|
||||
/// in the gschemas.xml file of the extension.
|
||||
var Prefs = class Prefs {
|
||||
constructor(keys) {
|
||||
let settings = this.settings = ExtensionUtils.getSettings();
|
||||
this.keys = keys;
|
||||
|
||||
this.keys.forEach(bundle => {
|
||||
let component = this;
|
||||
let component_settings = settings;
|
||||
if (bundle.component !== "general") {
|
||||
let bundle_component = bundle.component.replaceAll('-', '_');
|
||||
this[bundle_component] = {
|
||||
settings: this.settings.get_child(bundle.component)
|
||||
};
|
||||
component = this[bundle_component];
|
||||
component_settings = settings.get_child(bundle.component);
|
||||
}
|
||||
|
||||
|
||||
bundle.schemas.forEach(key => {
|
||||
let property_name = this.get_property_name(key.name);
|
||||
|
||||
switch (key.type) {
|
||||
case Type.B:
|
||||
Object.defineProperty(component, property_name, {
|
||||
get() {
|
||||
return component_settings.get_boolean(key.name);
|
||||
},
|
||||
set(v) {
|
||||
component_settings.set_boolean(key.name, v);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Type.I:
|
||||
Object.defineProperty(component, property_name, {
|
||||
get() {
|
||||
return component_settings.get_int(key.name);
|
||||
},
|
||||
set(v) {
|
||||
component_settings.set_int(key.name, v);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Type.D:
|
||||
Object.defineProperty(component, property_name, {
|
||||
get() {
|
||||
return component_settings.get_double(key.name);
|
||||
},
|
||||
set(v) {
|
||||
component_settings.set_double(key.name, v);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Type.S:
|
||||
Object.defineProperty(component, property_name, {
|
||||
get() {
|
||||
return component_settings.get_string(key.name);
|
||||
},
|
||||
set(v) {
|
||||
component_settings.set_string(key.name, v);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Type.C:
|
||||
Object.defineProperty(component, property_name, {
|
||||
// returns the array [red, blue, green, alpha] with
|
||||
// values between 0 and 1
|
||||
get() {
|
||||
let val = component_settings.get_value(key.name);
|
||||
return val.deep_unpack();
|
||||
},
|
||||
// takes an array [red, blue, green, alpha] with
|
||||
// values between 0 and 1
|
||||
set(v) {
|
||||
let val = new GLib.Variant("(dddd)", v);
|
||||
component_settings.set_value(key.name, val);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Type.AS:
|
||||
Object.defineProperty(component, property_name, {
|
||||
get() {
|
||||
let val = component_settings.get_value(key.name);
|
||||
return val.deep_unpack();
|
||||
},
|
||||
set(v) {
|
||||
let val = new GLib.Variant("as", v);
|
||||
component_settings.set_value(key.name, val);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
component[property_name + '_reset'] = function () {
|
||||
return component_settings.reset(key.name);
|
||||
};
|
||||
|
||||
component[property_name + '_changed'] = function (cb) {
|
||||
return component_settings.connect('changed::' + key.name, cb);
|
||||
};
|
||||
|
||||
component[property_name + '_disconnect'] = function () {
|
||||
return component_settings.disconnect.apply(
|
||||
component_settings, arguments
|
||||
);
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/// Reset the preferences.
|
||||
reset() {
|
||||
this.keys.forEach(bundle => {
|
||||
let component = this;
|
||||
if (bundle.component !== "general") {
|
||||
let bundle_component = bundle.component.replaceAll('-', '_');
|
||||
component = this[bundle_component];
|
||||
}
|
||||
|
||||
bundle.schemas.forEach(key => {
|
||||
let property_name = this.get_property_name(key.name);
|
||||
component[property_name + '_reset']();
|
||||
});
|
||||
});
|
||||
|
||||
this.emit('reset', true);
|
||||
}
|
||||
|
||||
/// From the gschema name, returns the name of the associated property on
|
||||
/// the Prefs object.
|
||||
get_property_name(name) {
|
||||
return name.replaceAll('-', '_').toUpperCase();
|
||||
}
|
||||
|
||||
/// Remove all connections managed by the Prefs object, i.e. created with
|
||||
/// `prefs.PROPERTY_changed(callback)`.
|
||||
disconnect_all_settings() {
|
||||
this.keys.forEach(bundle => {
|
||||
let component = this;
|
||||
if (bundle.component !== "general") {
|
||||
let bundle_component = bundle.component.replaceAll('-', '_');
|
||||
component = this[bundle_component];
|
||||
}
|
||||
|
||||
bundle.schemas.forEach(key => {
|
||||
let property_name = this.get_property_name(key.name);
|
||||
component[property_name + '_disconnect']();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(Prefs.prototype);
|
@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const bus_name = 'org.gnome.Shell';
|
||||
const iface_name = 'dev.aunetx.BlurMyShell';
|
||||
const obj_path = '/dev/aunetx/BlurMyShell';
|
||||
|
||||
|
||||
/// Call pick() from the DBus service, it will open the Inspector from
|
||||
/// gnome-shell to pick an actor on stage.
|
||||
function pick() {
|
||||
Gio.DBus.session.call(
|
||||
bus_name,
|
||||
obj_path,
|
||||
iface_name,
|
||||
'pick',
|
||||
null,
|
||||
null,
|
||||
Gio.DBusCallFlags.NO_AUTO_START,
|
||||
-1,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/// Connect to DBus 'picking' signal, which will be emitted when the inspector
|
||||
/// is picking a window.
|
||||
function on_picking(cb) {
|
||||
const id = Gio.DBus.session.signal_subscribe(
|
||||
bus_name,
|
||||
iface_name,
|
||||
'picking',
|
||||
obj_path,
|
||||
null,
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
_ => {
|
||||
cb();
|
||||
Gio.DBus.session.signal_unsubscribe(id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Connect to DBus 'picked' signal, which will be emitted when a window is
|
||||
/// picked.
|
||||
function on_picked(cb) {
|
||||
const id = Gio.DBus.session.signal_subscribe(
|
||||
bus_name,
|
||||
iface_name,
|
||||
'picked',
|
||||
obj_path,
|
||||
null,
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
(conn, sender, obj_path, iface, signal, params) => {
|
||||
const val = params.get_child_value(0);
|
||||
cb(val.get_string()[0]);
|
||||
Gio.DBus.session.signal_unsubscribe(id);
|
||||
}
|
||||
);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<node>
|
||||
<interface name="dev.aunetx.BlurMyShell">
|
||||
<!-- This method is called in preferences to pick a window -->
|
||||
<method name="pick" />
|
||||
<!-- When window is picking, send a signal to preferences -->
|
||||
<signal name="picking"></signal>
|
||||
<!-- If window is picked, send a signal to preferences -->
|
||||
<signal name="picked">
|
||||
<arg name="window" type="s" />
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Gio, GLib } = imports.gi;
|
||||
const Main = imports.ui.main;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
|
||||
const load_file = path => {
|
||||
const [, buffer] = GLib.file_get_contents(path);
|
||||
const contents = imports.byteArray.toString(buffer);
|
||||
GLib.free(buffer);
|
||||
return contents;
|
||||
};
|
||||
|
||||
const iface = load_file(Me.dir.get_path() + '/dbus/iface.xml');
|
||||
|
||||
var ApplicationsService = class ApplicationsService {
|
||||
constructor() {
|
||||
this.DBusImpl = Gio.DBusExportedObject.wrapJSObject(iface, this);
|
||||
}
|
||||
|
||||
/// Pick Window for Preferences Page, exported to DBus client.
|
||||
pick() {
|
||||
// emit `picking` signal to know we are listening
|
||||
const send_picking_signal = _ =>
|
||||
this.DBusImpl.emit_signal(
|
||||
'picking',
|
||||
null
|
||||
);
|
||||
|
||||
// emit `picked` signal to send wm_class
|
||||
const send_picked_signal = wm_class =>
|
||||
this.DBusImpl.emit_signal(
|
||||
'picked',
|
||||
new GLib.Variant('(s)', [wm_class])
|
||||
);
|
||||
|
||||
// notify the preferences that we are listening
|
||||
send_picking_signal();
|
||||
|
||||
// A very interesting way to pick a window:
|
||||
// 1. Open LookingGlass to mask all event handles of window
|
||||
// 2. Use inspector to pick window, thats is also lookingGlass do
|
||||
// 3. Close LookingGlass when done
|
||||
// It will restore event handles of window
|
||||
|
||||
// open then hide LookingGlass
|
||||
const looking_class = Main.createLookingGlass();
|
||||
looking_class.open();
|
||||
looking_class.hide();
|
||||
|
||||
// inspect window now
|
||||
const inspector = new LookingGlass.Inspector(Main.createLookingGlass());
|
||||
inspector.connect('target', (me, target, x, y) => {
|
||||
// remove border effect when window is picked.
|
||||
const effect_name = 'lookingGlass_RedBorderEffect';
|
||||
target
|
||||
.get_effects()
|
||||
.filter(e => e.toString().includes(effect_name))
|
||||
.forEach(e => target.remove_effect(e));
|
||||
|
||||
// get wm_class_instance property of window, then pass it to DBus
|
||||
// client
|
||||
const type_str = target.toString();
|
||||
|
||||
let actor = target;
|
||||
if (type_str.includes('MetaSurfaceActor'))
|
||||
actor = target.get_parent();
|
||||
|
||||
if (!actor.toString().includes('WindowActor'))
|
||||
return send_picked_signal('window-not-found');
|
||||
|
||||
send_picked_signal(
|
||||
actor.meta_window.get_wm_class() ?? 'window-not-found'
|
||||
);
|
||||
});
|
||||
|
||||
// close LookingGlass when we're done
|
||||
inspector.connect('closed', _ => looking_class.close());
|
||||
}
|
||||
|
||||
export() {
|
||||
this.DBusImpl.export(
|
||||
Gio.DBus.session,
|
||||
'/dev/aunetx/BlurMyShell'
|
||||
);
|
||||
}
|
||||
|
||||
unexport() {
|
||||
this.DBusImpl.unexport();
|
||||
}
|
||||
};
|