Fixes in ebuild files, editing gnome shell settings, deleting extensions folder

master
nemof_san 1 year ago
parent 4502911fb9
commit 5de3dcdb0f

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

@ -26,6 +26,9 @@ BDEPEND=""
S="${WORKDIR}/no-overview-${PV}"
extension_uuid="no-overview@fthx"
# Not useful for us
src_compile() { :; }
src_install() {
einstalldocs
rm -f README.md LICENSE || die

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

@ -27,13 +27,15 @@ BDEPEND=""
S="${WORKDIR}/${MY_P}"
extension_uuid="RemoveAppMenu@Dragon8oy.com"
src_prepare() {
eapply "${FILESDIR}"/remove-app-menu-makefile-9.0.patch
eapply_user
}
# Not useful for us
src_compile() { :; }
src_install() {
default
einstalldocs
rm -f README.md clean-svgs.py Makefile || die
rm -rf docs || die
insinto /usr/share/gnome-shell/extensions/"${extension_uuid}"
doins -r *
}
pkg_preinst() {

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

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16">
<path d="M 6.6757812,0.10370312 V 4.6564375 H 9.3242188 V 0.10370312 Z M 4.2460938,1.8947188 c 0,0 -3.7336862,1.8715687 -3.75000005,6.4765625 -0.0163138,4.6049937 3.75000005,6.5253907 3.75000005,6.5253907 0,0 1.7791591,1.021306 3.78125,1.015625 2.0020912,-0.0057 3.7441402,-1.015625 3.7441402,-1.015625 0,0 3.724059,-2.124435 3.736328,-6.5253907 0.01227,-4.4009557 -3.732421,-6.4648438 -3.732421,-6.4648438 v 3.4003906 c 0,0 0.02378,0.0065 0.144531,0.1660157 0.120768,0.1595124 0.126953,0.1992187 0.126953,0.1992187 L 13.222656,4.9943281 13.544922,5.5412031 12.375,6.2189375 c 0,0 0.180927,0.4073893 0.324219,0.9003906 0.143291,0.4930013 0.173828,0.9414063 0.173828,0.9414063 l 1.337891,-0.00391 0.0059,0.6328126 -1.330078,-0.00781 c 0,0 -0.0089,0.3677374 -0.152344,0.9375 -0.143417,0.569763 -0.337891,0.919922 -0.337891,0.919922 l 1.142578,0.679688 -0.3125,0.541015 -1.152343,-0.671875 c 0,0 -0.178508,0.29967 -0.576172,0.708985 -0.397581,0.409316 -0.773438,0.640625 -0.773438,0.640625 l 0.66211,1.154297 -0.554688,0.324218 -0.658203,-1.160156 c 0,0 -0.3427972,0.186493 -0.9296874,0.34375 -0.5868902,0.157282 -0.9140625,0.148438 -0.9140625,0.148438 l -0.011719,1.335937 c -0.2043011,0.0017 -0.4070271,0 -0.6113282,0 l -0.011719,-1.335937 c 0,-10e-7 -0.3271723,0.0088 -0.9140625,-0.148438 C 6.1943598,12.94254 5.8515625,12.756047 5.8515625,12.756047 L 5.1933594,13.916203 4.6386719,13.591985 5.3007812,12.437688 c 0,0 -0.3757763,-0.231309 -0.7734374,-0.640625 -0.3976649,-0.409315 -0.578125,-0.708985 -0.578125,-0.708985 l -1.1503907,0.671875 -0.3125,-0.541015 1.1425781,-0.679688 c 1e-7,0 -0.1944733,-0.350159 -0.3378906,-0.9199219 -0.1434221,-0.5697626 -0.1523437,-0.9375 -0.1523437,-0.9375 l -1.3300781,0.00781 0.00586,-0.6328126 1.3378907,0.00391 c 0,0 0.030536,-0.448405 0.1738281,-0.9414063 0.143291,-0.4930024 0.324218,-0.9003917 0.324218,-0.9003917 L 2.4804688,5.5412031 2.8027344,4.9943281 3.9785156,5.6720625 c 0,0 0.00621,-0.039706 0.1269532,-0.1992187 C 4.2262215,5.3133328 4.25,5.3068281 4.25,5.3068281 Z M 6.6757812,5.0138594 V 7.0568281 H 9.3242188 V 5.0158125 Z M 6.671875,7.4259688 V 8.2443281 H 9.3183594 V 7.4279219 Z m 0,1.1855469 V 9.3693281 H 9.3203125 V 8.6134688 Z" style="fill:#2e3436;stroke:#000000;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"/>
</svg>

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 &apos;shutdown -P/-r&apos; 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 &amp;#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 &amp;#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 &amp;#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 &amp;#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));
};
}

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16" height="16.001" xmlns="http://www.w3.org/2000/svg">
<g fill="#474747">
<path d="M5 5v2h6V5z" overflow="visible"/>
<path d="M5.469 0c-.49 0-.797.216-1.032.456C4.202.696 4 1.012 4 1.486V2H2v14h12V2h-2v-.406l-.002-.028a1.616 1.616 0 0 0-.416-1.011c-.236-.28-.62-.585-1.2-.553L10.438 0zm.53 2h4v2h2v10H4V4h2z" color="#bebebe" font-family="sans-serif" font-weight="400" overflow="visible" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none" white-space="normal"/>
<path d="m5 8v2h6v-2zm0 3v2h6v-2z" overflow="visible"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 909 B

@ -1 +0,0 @@
<?xml version="1.0" ?><svg enable-background="new 0 0 52 52" id="Layer_1" version="1.1" viewBox="0 0 52 52" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M38.9799805,2H2v48h48V13.0200195L38.9799805,2z M25.0005493,6.0021362h3v2h-3V6.0021362z M25.0005493,10.0021362h3v2h-3 V10.0021362z M25.0005493,14.0021362h3v2h-3V14.0021362z M19.0005493,6.0021362h3v2h-3V6.0021362z M19.0005493,10.0021362h3v2h-3 V10.0021362z M19.0005493,14.0021362h3v2h-3V14.0021362z M10.0005493,46.0021362h-3v-2h3V46.0021362z M10.0005493,42.0021362h-3v-2 h3V42.0021362z M10.0005493,38.0021362h-3v-2h3V38.0021362z M10.0005493,34.0021362h-3v-2h3V34.0021362z M10.0005493,30.0021362h-3 v-2h3V30.0021362z M10.0005493,25.0021362h-3v-2h3V25.0021362z M10.0005493,21.0021362h-3v-2h3V21.0021362z M10.0005493,16.0021362 h-3v-2h3V16.0021362z M10.0005493,12.0021362h-3v-2h3V12.0021362z M10.0005493,8.0021362h-3v-2h3V8.0021362z M16.0005493,46.0021362 h-3v-2h3V46.0021362z M16.0005493,42.0021362h-3v-2h3V42.0021362z M16.0005493,38.0021362h-3v-2h3V38.0021362z M16.0005493,34.0021362h-3v-2h3V34.0021362z M16.0005493,30.0021362h-3v-2h3V30.0021362z M16.0005493,25.0021362h-3v-2h3 V25.0021362z M16.0005493,21.0021362h-3v-2h3V21.0021362z M16.0005493,16.0021362h-3v-2h3V16.0021362z M16.0005493,12.0021362h-3v-2 h3V12.0021362z M16.0005493,8.0021362h-3v-2h3V8.0021362z M19,19h14v14H19V19z M22.0005493,46.0021362h-3v-2h3V46.0021362z M22.0005493,42.0021362h-3v-2h3V42.0021362z M22.0005493,38.0021362h-3v-2h3V38.0021362z M28.0005493,46.0021362h-3v-2h3 V46.0021362z M28.0005493,42.0021362h-3v-2h3V42.0021362z M28.0005493,38.0021362h-3v-2h3V38.0021362z M34.0005493,46.0021362h-3v-2 h3V46.0021362z M34.0005493,42.0021362h-3v-2h3V42.0021362z M34.0005493,38.0021362h-3v-2h3V38.0021362z M34.0005493,16.0021362h-3 v-2h3V16.0021362z M34.0005493,12.0021362h-3v-2h3V12.0021362z M34.0005493,8.0021362h-3v-2h3V8.0021362z M40.0005493,46.0021362h-3 v-2h3V46.0021362z M40.0005493,42.0021362h-3v-2h3V42.0021362z M40.0005493,38.0021362h-3v-2h3V38.0021362z M40.0005493,34.0021362 h-3v-2h3V34.0021362z M40.0005493,30.0021362h-3v-2h3V30.0021362z M40.0005493,25.0021362h-3v-2h3V25.0021362z M40.0005493,21.0021362h-3v-2h3V21.0021362z M40.0005493,16.0021362h-3v-2h3V16.0021362z M40.0005493,12.0021362h-3v-2h3 V12.0021362z M46.0005493,46.0021362h-3v-2h3V46.0021362z M46.0005493,42.0021362h-3v-2h3V42.0021362z M46.0005493,38.0021362h-3v-2 h3V38.0021362z M46.0005493,34.0021362h-3v-2h3V34.0021362z M46.0005493,30.0021362h-3v-2h3V30.0021362z M46.0005493,25.0021362h-3 v-2h3V25.0021362z M46.0005493,21.0021362h-3v-2h3V21.0021362z M46.0005493,16.0021362h-3v-2h3V16.0021362z"/></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg7384"
version="1.1"
inkscape:version="0.48.3.1 r9886"
height="16"
sodipodi:docname="fan.svg"
width="16">
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Gnome Symbolic Icon Theme</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
inkscape:object-paths="true"
inkscape:cy="7.9523753"
inkscape:current-layer="svg7384"
inkscape:window-width="1680"
pagecolor="#555753"
showborder="false"
showguides="true"
inkscape:snap-nodes="false"
objecttolerance="10"
showgrid="true"
inkscape:object-nodes="true"
inkscape:pageshadow="2"
inkscape:guide-bbox="true"
inkscape:window-x="0"
inkscape:snap-bbox="true"
bordercolor="#666666"
id="namedview88"
inkscape:window-maximized="1"
inkscape:snap-global="false"
inkscape:window-y="27"
gridtolerance="10"
inkscape:zoom="45.254834"
inkscape:window-height="991"
borderopacity="1"
guidetolerance="10"
inkscape:snap-bbox-midpoints="false"
inkscape:cx="6.4701314"
inkscape:bbox-paths="false"
inkscape:snap-grids="true"
inkscape:pageopacity="1"
inkscape:snap-to-guides="true">
<inkscape:grid
visible="true"
spacingx="1px"
type="xygrid"
spacingy="1px"
id="grid4866"
empspacing="2"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<title
id="title9167">Gnome Symbolic Icon Theme</title>
<defs
id="defs7386" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer9"
inkscape:label="status"
style="display:inline" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer10"
inkscape:label="devices" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer11"
inkscape:label="apps" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer12"
inkscape:label="actions" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer13"
inkscape:label="places" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer14"
inkscape:label="mimetypes" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer15"
inkscape:label="emblems"
style="display:inline" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="g4953"
inkscape:label="categories"
style="display:inline" />
<path
style="fill:#bebebe;fill-opacity:1;stroke:none"
d="M 8 0 C 7.7662783 -3.1322393e-17 7.5411335 0.011610998 7.3125 0.03125 C 5.2102943 1.4256475 5.5987411 4.0108346 6.90625 6.34375 C 7.2209706 6.1372503 7.5954602 6 8 6 C 8.0316752 6 8.062435 5.9985435 8.09375 6 C 8.7335699 3.3508722 11.235427 1.3679906 11.96875 1.0625 C 10.798244 0.39154001 9.4458996 -7.7509341e-16 8 0 z M 4 1.09375 C 2.5986515 1.9071018 1.4765974 3.1207274 0.78125 4.59375 C 0.9182133 7.1283275 3.3250439 8.0908827 6 8.125 C 5.9974202 8.0834163 6 8.0422335 6 8 C 6 7.6007368 6.1109237 7.2184502 6.3125 6.90625 C 4.3514736 5.0277432 3.8996391 1.8856213 4 1.09375 z M 13.15625 3.0625 C 11.454675 3.0097524 9.9536158 4.4003424 8.875 6.21875 C 9.2647725 6.4100014 9.5848814 6.7069783 9.78125 7.09375 C 12.408696 6.3344077 15.40554 7.5273725 16 8 C 16 6.3023731 15.464331 4.7324062 14.5625 3.4375 C 14.086383 3.1993071 13.609414 3.0765477 13.15625 3.0625 z M 10 7.875 C 10.00258 7.9165837 10 7.9577665 10 8 C 10 8.3992632 9.8890763 8.7815498 9.6875 9.09375 C 11.648527 10.972257 12.100361 14.114379 12 14.90625 C 13.401349 14.092898 14.523403 12.879273 15.21875 11.40625 C 15.081787 8.8716722 12.674956 7.9091173 10 7.875 z M 0 8 C -4.5501758e-16 9.6976269 0.53566854 11.267594 1.4375 12.5625 C 3.7013779 13.695079 5.7591272 12.083936 7.125 9.78125 C 6.7352275 9.5899986 6.4151186 9.2930217 6.21875 8.90625 C 3.5913037 9.6655923 0.5944604 8.4726275 0 8 z M 9.09375 9.65625 C 8.7790294 9.8627497 8.4045398 10 8 10 C 7.9683248 10 7.937565 10.001457 7.90625 10 C 7.26643 12.649128 4.7645729 14.632009 4.03125 14.9375 C 5.2017564 15.60846 6.5541004 16 8 16 C 8.2337217 16 8.4588665 15.988389 8.6875 15.96875 C 10.789706 14.574352 10.401259 11.989165 9.09375 9.65625 z "
id="path3830" />
<path
sodipodi:type="arc"
style="fill:#bebebe;fill-opacity:1;stroke:none"
id="path3896"
sodipodi:cx="8.0433397"
sodipodi:cy="8.0008545"
sodipodi:rx="0.9722718"
sodipodi:ry="0.95017475"
d="m 9.0156115,8.0008545 a 0.9722718,0.95017475 0 1 1 -1.9445436,0 0.9722718,0.95017475 0 1 1 1.9445436,0 z"
transform="matrix(1.028519,0,0,1.052438,-0.27272757,-0.42040319)" />
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

@ -1 +0,0 @@
<?xml version="1.0" ?><svg enable-background="new 0 0 52 52" id="Layer_1" version="1.1" viewBox="0 0 52 52" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M50,41.0999756V11.4000244h-6.9978638V7.7766113C44.164978,7.364502,45,6.2706909,45,4.9799805 C45,3.3400269,43.6600342,2,42,2c-1.6499634,0-3,1.3400269-3,2.9799805c0,1.2908936,0.8413696,2.3848267,2.0021362,2.796814v3.62323 h-8V7.7766113C34.164978,7.364502,35,6.2706909,35,4.9799805C35,3.3400269,33.6600342,2,32,2c-1.6499634,0-3,1.3400269-3,2.9799805 c0,1.2908936,0.8413696,2.3848267,2.0021362,2.796814v3.62323h-8V7.7766113C24.164978,7.364502,25,6.2706909,25,4.9799805 C25,3.3400269,23.6600342,2,22,2c-1.6499634,0-3,1.3400269-3,2.9799805c0,1.2908936,0.8413696,2.3848267,2.0021362,2.796814v3.62323 h-8V7.7766113C14.164978,7.364502,15,6.2706909,15,4.9799805C15,3.3400269,13.6600342,2,12,2c-1.6499634,0-3,1.3400269-3,2.9799805 c0,1.2908936,0.8413696,2.3848267,2.0021362,2.796814v3.62323H2v29.6999512h9.0021362v3.1321411 C9.8413696,44.6417847,9,45.7312622,9,47.0299683C9,48.6699829,10.3500366,50,12,50c1.6600342,0,3-1.3300171,3-2.9700317 c0-1.298584-0.835022-2.3878784-1.9978638-2.7977295v-3.1322632h8v3.1321411C19.8413696,44.6417847,19,45.7312622,19,47.0299683 C19,48.6699829,20.3500366,50,22,50c1.6600342,0,3-1.3300171,3-2.9700317c0-1.298584-0.835022-2.3878784-1.9978638-2.7977295 v-3.1322632h8v3.1321411C29.8413696,44.6417847,29,45.7312622,29,47.0299683C29,48.6699829,30.3500366,50,32,50 c1.6600342,0,3-1.3300171,3-2.9700317c0-1.298584-0.835022-2.3878784-1.9978638-2.7977295v-3.1322632h8v3.1321411 C39.8413696,44.6417847,39,45.7312622,39,47.0299683C39,48.6699829,40.3500366,50,42,50c1.6600342,0,3-1.3300171,3-2.9700317 c0-1.298584-0.835022-2.3878784-1.9978638-2.7977295v-3.1322632H50z M7,16.3500366h38v19.7999878H7V16.3500366z"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@ -1 +0,0 @@
<?xml version="1.0" ?><svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><defs/><g fill="none" fill-rule="evenodd" id="Icons with numbers" stroke="none" stroke-width="1"><g fill="#000000" id="Group" transform="translate(-672.000000, -288.000000)"><path d="M679,293 L679,296 L677,296 L680,300 L683,296 L681,296 L681,293 Z M672,298 C672,296.651721 672.895887,295.507545 674.127761,295.131093 C674.500314,293.897932 675.645295,293 677,293 C677.174013,293 677.344566,293.014816 677.510466,293.043254 C678.195719,291.823839 679.50165,291 681,291 C683.209139,291 685,292.790861 685,295 C686.668415,295.005076 688,296.346276 688,298 C688,299.653483 686.652611,301 684.990522,301 L675.009478,301 C673.336631,301 672,299.656854 672,298 Z M672,298" id="Rectangle 169 copy 2"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 936 B

@ -1 +0,0 @@
<?xml version="1.0" ?><svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><defs/><g fill="none" fill-rule="evenodd" id="Icons with numbers" stroke="none" stroke-width="1"><g fill="#000000" id="Group" transform="translate(-576.000000, -288.000000)"><path d="M576,298 C576,296.651721 576.895887,295.507545 578.127761,295.131093 C578.500314,293.897932 579.645295,293 581,293 C581.174013,293 581.344566,293.014816 581.510466,293.043254 C582.195719,291.823839 583.50165,291 585,291 C587.209139,291 589,292.790861 589,295 C590.668415,295.005076 592,296.346276 592,298 C592,299.653483 590.652611,301 588.990522,301 L579.009478,301 C577.336631,301 576,299.656854 576,298 Z M576,298" id="Rectangle 169"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 864 B

@ -1 +0,0 @@
<?xml version="1.0" ?><svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><defs/><g fill="none" fill-rule="evenodd" id="Icons with numbers" stroke="none" stroke-width="1"><g fill="#000000" id="Group" transform="translate(-624.000000, -288.000000)"><path d="M631,297 L631,300 L633,300 L633,297 L635,297 L632,293 L629,297 Z M624,298 C624,296.651721 624.895887,295.507545 626.127761,295.131093 C626.500314,293.897932 627.645295,293 629,293 C629.174013,293 629.344566,293.014816 629.510466,293.043254 C630.195719,291.823839 631.50165,291 633,291 C635.209139,291 637,292.790861 637,295 C638.668415,295.005076 640,296.346276 640,298 C640,299.653483 638.652611,301 636.990522,301 L627.009478,301 C625.336631,301 624,299.656854 624,298 Z M624,298" id="Rectangle 169 copy"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 934 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<path style="fill:#bebebe" d="M 3,1 C 2,1 2,2 2,2 V 14 C 2,14 2,15 3,15 H 13 C 13,15 14,15 14,14 V 2 C 14,2 14,1 13,1 Z M 8,3 C 10.21,3 12,4.79 12,7 12,9.21 10.21,11 8,11 H 4 V 7 C 4,4.79 5.79,3 8,3 Z M 8,5 C 6.9,5 6,5.9 6,7 6,8.1 6.9,9 8,9 9.1,9 10,8.1 10,7 10,5.9 9.1,5 8,5 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<path style="fill:#bebebe" d="M 15,1 C 16,1 16,2 16,2 V 12 C 16,12 16,13 15,13 H 1 C 1,13 0,13 0,12 V 2 C 0,2 0,1 1,1 Z M 14,3 H 2 V 11 H 14 Z M 11,14 V 15 H 5 V 14 C 5,13 6,13 6,13 H 10 C 10,13 11,13 11,14 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 298 B

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new -0.2 0 18 30"
height="30"
version="1.1"
viewBox="-0.2 0 30 30"
width="30"
xml:space="preserve"
id="svg6"
sodipodi:docname="if_Vector-icons_49_1041647.svg"
inkscape:version="0.92.3 (3ce5693, 2018-03-11)"><metadata
id="metadata10"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1070"
inkscape:window-height="638"
id="namedview8"
showgrid="false"
inkscape:zoom="7.8666667"
inkscape:cx="5.2520864"
inkscape:cy="14.6"
inkscape:window-x="330"
inkscape:window-y="226"
inkscape:window-maximized="0"
inkscape:current-layer="svg6" /><defs
id="defs2" /><path
d="M 14.6,29.8 C 9.8,29.8 6,25.9 6,21.2 6,18.1 7.6,15.4 10,13.9 V 4.5 C 10,2 12.1,0 14.5,0 16.9,0 19,2 19,4.5 v 9.2 c 2.5,1.5 4.2,4.3 4.2,7.4 0.1,4.8 -3.8,8.7 -8.6,8.7 z M 17.3,15.4 17.1,5 C 17.1,3.8 15.7,2.9 14.5,2.9 13.3,2.9 12,3.8 12,5 v 10.5 c -2.1,1 -3.6,3.2 -3.6,5.7 0,3.5 2.8,6.3 6.3,6.3 3.5,0 6.3,-2.8 6.3,-6.3 0,-2.6 -1.5,-4.8 -3.7,-5.8 z m -2.7,10.5 c -2.6,0 -4.7,-2.1 -4.7,-4.7 0,-2 1.3,-3.7 3.1,-4.4 V 8 h 3 v 8.8 c 1.9,0.6 3.3,2.3 3.3,4.4 0,2.6 -2.1,4.7 -4.7,4.7 z"
id="path4"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#0d0d0d;fill-rule:evenodd" /></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg7384"
version="1.1"
inkscape:version="0.48.3.1 r9886"
height="16"
sodipodi:docname="voltage.svg"
width="16">
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Gnome Symbolic Icon Theme</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
inkscape:object-paths="true"
inkscape:cy="8.2822539"
inkscape:current-layer="svg7384"
inkscape:window-width="1680"
pagecolor="#555753"
showborder="false"
showguides="true"
inkscape:snap-nodes="false"
objecttolerance="10"
showgrid="true"
inkscape:object-nodes="true"
inkscape:pageshadow="2"
inkscape:guide-bbox="true"
inkscape:window-x="0"
inkscape:snap-bbox="true"
bordercolor="#666666"
id="namedview88"
inkscape:window-maximized="1"
inkscape:snap-global="false"
inkscape:window-y="27"
gridtolerance="10"
inkscape:zoom="68.593502"
inkscape:window-height="991"
borderopacity="1"
guidetolerance="10"
inkscape:snap-bbox-midpoints="false"
inkscape:cx="6.2925429"
inkscape:bbox-paths="false"
inkscape:snap-grids="true"
inkscape:pageopacity="1"
inkscape:snap-to-guides="true">
<inkscape:grid
visible="true"
spacingx="1px"
type="xygrid"
spacingy="1px"
id="grid4866"
empspacing="2"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<title
id="title9167">Gnome Symbolic Icon Theme</title>
<defs
id="defs7386" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer9"
inkscape:label="status"
style="display:inline" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer10"
inkscape:label="devices" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer11"
inkscape:label="apps" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer12"
inkscape:label="actions" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer13"
inkscape:label="places" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer14"
inkscape:label="mimetypes" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="layer15"
inkscape:label="emblems"
style="display:inline" />
<g
transform="translate(-261,-277)"
inkscape:groupmode="layer"
id="g4953"
inkscape:label="categories"
style="display:inline" />
<path
style="fill:#bebebe;fill-opacity:1;stroke:none"
d="m 11.40625,1 c -0.455108,0.038189 -0.61564,0.1791748 -1.25,0.625 L 2.84375,7.34375 C 1.9959525,8.0221961 2,8.0144269 2,9 l 0,1 4,0 -3.1875,3.1875 C 1.98932,14.004249 2.00091,13.998722 2,15 l 0,1 1,0.03125 c 0.986227,-0.01956 0.997937,-0.03056 1.84375,-0.625 L 12.15625,9.6875 C 13.004048,9.0090539 13,8.9855731 13,8 L 13,7 9,7 12.1875,3.8125 C 13.01068,2.9957516 12.99909,3.0012778 13,2 l 0,-1 -1,0 c -0.246557,0.00489 -0.442047,-0.0127296 -0.59375,0 z"
id="path3898"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sccsccccccccsccccccs" />
</svg>

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

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

Loading…
Cancel
Save