[gnome] Fix extension locations
@ -0,0 +1,339 @@
|
||||
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.
|
||||
@ -0,0 +1,577 @@
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import St from 'gi://St'
|
||||
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js';
|
||||
|
||||
import * as Sensors from './sensors.js';
|
||||
|
||||
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
|
||||
import * as Values from './values.js';
|
||||
import * as Config from 'resource:///org/gnome/shell/misc/config.js';
|
||||
import * as MenuItem from './menuItem.js';
|
||||
|
||||
let vitalsMenu;
|
||||
|
||||
var VitalsMenuButton = GObject.registerClass({
|
||||
GTypeName: 'VitalsMenuButton',
|
||||
}, class VitalsMenuButton extends PanelMenu.Button {
|
||||
_init(extensionObject) {
|
||||
super._init(Clutter.ActorAlign.FILL);
|
||||
|
||||
this._extensionObject = extensionObject;
|
||||
this._settings = extensionObject.getSettings();
|
||||
|
||||
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));
|
||||
this._addSettingChangedSignal('menu-centered', 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', 'include-static-info' ];
|
||||
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(this._extensionObject.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').split(" "));
|
||||
});
|
||||
customButtonBox.add_actor(monitorButton);
|
||||
|
||||
// custom round preferences button
|
||||
let prefsButton = this._createRoundButton('preferences-system-symbolic', _('Preferences'));
|
||||
prefsButton.connect('clicked', (self) => {
|
||||
this.menu._getTopMenu().close();
|
||||
this._extensionObject.openPreferences();
|
||||
});
|
||||
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 = GLib.timeout_add_seconds(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
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 GLib.SOURCE_CONTINUE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_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;
|
||||
|
||||
// keep track of label for removal later
|
||||
this._hotLabels[key] = label;
|
||||
|
||||
// prevent "called on the widget" "which is not in the stage" errors by adding before width below
|
||||
this._menuLayout.add_actor(label);
|
||||
|
||||
// support for fixed widths #55, save label (text) width
|
||||
this._widths[key] = label.width;
|
||||
}
|
||||
|
||||
_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 existent 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) {
|
||||
GLib.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(this._extensionObject.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(this._extensionObject.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(this._extensionObject.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;
|
||||
}
|
||||
|
||||
let centered = this._settings.get_boolean('menu-centered')
|
||||
|
||||
if (centered) arrow_pos = 0.5;
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
|
||||
export default class VitalsExtension extends Extension {
|
||||
enable() {
|
||||
vitalsMenu = new VitalsMenuButton(this);
|
||||
let position = vitalsMenu._positionInPanel();
|
||||
Main.panel.addToStatusArea('vitalsMenu', vitalsMenu, position[1], position[0]);
|
||||
}
|
||||
|
||||
disable() {
|
||||
vitalsMenu.destroy();
|
||||
vitalsMenu = null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib'
|
||||
|
||||
// convert Uint8Array into a literal string
|
||||
function convertUint8ArrayToString(contents) {
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
return decoder.decode(contents).trim();
|
||||
}
|
||||
|
||||
export 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 = convertUint8ArrayToString(contents);
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 909 B |
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,133 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 936 B |
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 864 B |
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 934 B |
@ -0,0 +1,3 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 367 B |
@ -0,0 +1,3 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 298 B |
@ -0,0 +1,45 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,125 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,77 @@
|
||||
import Clutter from 'gi://Clutter';
|
||||
import GObject from 'gi://GObject';
|
||||
import St from 'gi://St'
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
|
||||
export const 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;
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
{
|
||||
"_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": [
|
||||
"45"
|
||||
],
|
||||
"url": "https://github.com/corecoding/Vitals",
|
||||
"uuid": "Vitals@CoreCoding.com",
|
||||
"version": 63
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
import Adw from 'gi://Adw';
|
||||
import Gio from 'gi://Gio';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk';
|
||||
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
|
||||
/*
|
||||
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(extensionObject, params) {
|
||||
this._extensionObject = extensionObject
|
||||
this.parent(params);
|
||||
|
||||
this._settings = extensionObject.getSettings();
|
||||
|
||||
this.builder = new Gtk.Builder();
|
||||
this.builder.set_translation_domain(this._extensionObject.metadata['gettext-domain']);
|
||||
this.builder.add_from_file(this._extensionObject.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',
|
||||
'menu-centered', 'include-static-info' ];
|
||||
|
||||
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', 'processor' ];
|
||||
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 = 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');
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default class VitalsPrefs extends ExtensionPreferences {
|
||||
fillPreferencesWindow(window) {
|
||||
window._settings = this.getSettings();
|
||||
|
||||
let settings = new Settings(this);
|
||||
let widget = settings.widget;
|
||||
|
||||
const page = new Adw.PreferencesPage();
|
||||
const group = new Adw.PreferencesGroup({});
|
||||
group.add(widget);
|
||||
page.add(group);
|
||||
window.add(page);
|
||||
window.set_default_size(widget.width, widget.height);
|
||||
widget.show();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="gnome-shell-extensions">
|
||||
<schema id="org.gnome.shell.extensions.vitals" path="/org/gnome/shell/extensions/vitals/">
|
||||
<key name="hot-sensors" type="as">
|
||||
<default>['_memory_usage_', '_system_load_1m_', '__network-rx_max__']</default>
|
||||
<summary>Sensors to show in panel</summary>
|
||||
<description>List of sensors to be shown in the panel</description>
|
||||
</key>
|
||||
<key type="i" name="update-time">
|
||||
<default>5</default>
|
||||
<summary>Seconds between updates</summary>
|
||||
<description>Delay between sensor polling</description>
|
||||
</key>
|
||||
<key type="i" name="position-in-panel">
|
||||
<default>2</default>
|
||||
<summary>Position in panel</summary>
|
||||
<description>Position in Panel ('left', 'center', 'right')</description>
|
||||
</key>
|
||||
<key type="b" name="use-higher-precision">
|
||||
<default>false</default>
|
||||
<summary>Use higher precision</summary>
|
||||
<description>Show one extra digit after decimal</description>
|
||||
</key>
|
||||
<key type="b" name="alphabetize">
|
||||
<default>true</default>
|
||||
<summary>Alphabetize sensors</summary>
|
||||
<description>Display sensors in alphabetical order</description>
|
||||
</key>
|
||||
<key type="b" name="hide-zeros">
|
||||
<default>false</default>
|
||||
<summary>Hide zero values</summary>
|
||||
<description>Hide data from sensors that are invalid</description>
|
||||
</key>
|
||||
<key type="b" name="show-temperature">
|
||||
<default>true</default>
|
||||
<summary>Monitor temperature</summary>
|
||||
<description>Display temperature of various components</description>
|
||||
</key>
|
||||
<key type="i" name="unit">
|
||||
<default>0</default>
|
||||
<summary>Temperature unit</summary>
|
||||
<description>The unit ('centigrade' or 'fahrenheit') the extension should display the temperature in</description>
|
||||
</key>
|
||||
<key type="b" name="show-voltage">
|
||||
<default>true</default>
|
||||
<summary>Monitor voltage</summary>
|
||||
<description>Display voltage of various components</description>
|
||||
</key>
|
||||
<key type="b" name="show-fan">
|
||||
<default>true</default>
|
||||
<summary>Monitor fan</summary>
|
||||
<description>Display fan rotation per minute</description>
|
||||
</key>
|
||||
<key type="b" name="show-memory">
|
||||
<default>true</default>
|
||||
<summary>Monitor memory</summary>
|
||||
<description>Display memory information</description>
|
||||
</key>
|
||||
<key type="b" name="show-processor">
|
||||
<default>true</default>
|
||||
<summary>Monitor processor</summary>
|
||||
<description>Display processor information</description>
|
||||
</key>
|
||||
<key type="b" name="show-system">
|
||||
<default>true</default>
|
||||
<summary>Monitor system</summary>
|
||||
<description>Display system information</description>
|
||||
</key>
|
||||
<key type="b" name="show-storage">
|
||||
<default>true</default>
|
||||
<summary>Monitor storage</summary>
|
||||
<description>Display storage information</description>
|
||||
</key>
|
||||
<key type="b" name="show-network">
|
||||
<default>true</default>
|
||||
<summary>Monitor network</summary>
|
||||
<description>Display network information</description>
|
||||
</key>
|
||||
<key type="b" name="include-public-ip">
|
||||
<default>true</default>
|
||||
<summary>Include public IP address</summary>
|
||||
<description>Display public IP address of internet connection</description>
|
||||
</key>
|
||||
<key type="i" name="network-speed-format">
|
||||
<default>0</default>
|
||||
<summary>Network speed format</summary>
|
||||
<description>Should speed display in bits or bytes?</description>
|
||||
</key>
|
||||
<key type="s" name="storage-path">
|
||||
<default>"/"</default>
|
||||
<summary>Storage path</summary>
|
||||
<description>Storage path for monitoring</description>
|
||||
</key>
|
||||
<key type="b" name="show-battery">
|
||||
<default>false</default>
|
||||
<summary>Monitor battery</summary>
|
||||
<description>Monitor battery health</description>
|
||||
</key>
|
||||
<key type="i" name="memory-measurement">
|
||||
<default>1</default>
|
||||
<summary>Memory measurement</summary>
|
||||
<description>Can use gigabyte or gibibyte for memory</description>
|
||||
</key>
|
||||
<key type="i" name="storage-measurement">
|
||||
<default>1</default>
|
||||
<summary>Storage measurement</summary>
|
||||
<description>Can use gigabyte or gibibyte for storage</description>
|
||||
</key>
|
||||
<key type="i" name="battery-slot">
|
||||
<default>0</default>
|
||||
<summary>Battery slot to monitor</summary>
|
||||
<description>Which numerical battery slot should vitals monitor</description>
|
||||
</key>
|
||||
<key type="b" name="fixed-widths">
|
||||
<default>true</default>
|
||||
<summary>Use fixed widths in top bar</summary>
|
||||
<description>Keep sensors in top bar from jumping around</description>
|
||||
</key>
|
||||
<key type="b" name="hide-icons">
|
||||
<default>false</default>
|
||||
<summary>Hide icons in top bar</summary>
|
||||
<description>Keep top bar clean by only showing sensor values</description>
|
||||
</key>
|
||||
<key type="b" name="menu-centered">
|
||||
<default>false</default>
|
||||
<summary>Make the menu centered</summary>
|
||||
<description>Center the menu to the icon regardless of the position in the panel</description>
|
||||
</key>
|
||||
<key type="s" name="monitor-cmd">
|
||||
<default>"gnome-system-monitor"</default>
|
||||
<summary>System Monitor command</summary>
|
||||
<description>The command run when system monitor button is clicked</description>
|
||||
</key>
|
||||
<key type="b" name="include-static-info">
|
||||
<default>false</default>
|
||||
<summary>Include processor static information</summary>
|
||||
<description>Display processor static information that doesn't change</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
@ -0,0 +1,671 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import GObject from 'gi://GObject';
|
||||
import * as FileModule from './helpers/file.js';
|
||||
import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
import NM from 'gi://NM';
|
||||
|
||||
let GTop, hasGTop = true;
|
||||
try {
|
||||
({default: GTop} = await import('gi://GTop'));
|
||||
} catch (err) {
|
||||
log(err);
|
||||
hasGTop = false;
|
||||
};
|
||||
|
||||
export const 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 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 values = '', total = 0, avail = 0, swapTotal = 0, swapFree = 0, cached = 0, memFree = 0;
|
||||
|
||||
if (values = lines.match(/MemTotal:(\s+)(\d+) kB/)) total = values[2];
|
||||
if (values = lines.match(/MemAvailable:(\s+)(\d+) kB/)) avail = values[2];
|
||||
if (values = lines.match(/SwapTotal:(\s+)(\d+) kB/)) swapTotal = values[2];
|
||||
if (values = lines.match(/SwapFree:(\s+)(\d+) kB/)) swapFree = values[2];
|
||||
if (values = lines.match(/Cached:(\s+)(\d+) kB/)) cached = values[2];
|
||||
if (values = lines.match(/MemFree:(\s+)(\d+) kB/)) memFree = 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, 'Cached', cached, 'memory', 'memory');
|
||||
this._returnValue(callback, 'Free', memFree, '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 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 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', 'uptime');
|
||||
|
||||
let cores = Object.keys(this._last_processor['core']).length - 1;
|
||||
if (cores > 0)
|
||||
this._returnValue(callback, 'Process Time', upArray[0] - upArray[1] / cores, 'processor', 'uptime');
|
||||
}).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 => {
|
||||
// wireless has two headers - first is stripped in helper function
|
||||
lines.shift();
|
||||
|
||||
// if multiple wireless device, we use the last one
|
||||
for (let line of 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 values = '', target = 0, maximum = 0, current = 0;
|
||||
|
||||
if (values = lines.match(/c(\s+)(\d+)(\s+)(\d+)/)) target = values[4];
|
||||
if (values = lines.match(/c_max(\s+)(\d+)(\s+)(\d+)/)) maximum = values[4];
|
||||
if (values = lines.match(/size(\s+)(\d+)(\s+)(\d+)/)) 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 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 battery_key = 'BAT'; // BAT0, BAT1 and BAT2
|
||||
if (battery_slot == 3) {
|
||||
battery_key = 'CMB'; // CMB0
|
||||
battery_slot = 0;
|
||||
} else if (battery_slot == 4) {
|
||||
battery_key = 'macsmc-battery'; // supports Asahi linux
|
||||
battery_slot = '';
|
||||
}
|
||||
|
||||
// uevent has all necessary fields, no need to read individual files
|
||||
let battery_path = '/sys/class/power_supply/' + battery_key + battery_slot + '/uevent';
|
||||
new FileModule.File(battery_path).read("\n").then(lines => {
|
||||
let output = {};
|
||||
for (let line of lines) {
|
||||
let split = line.split('=');
|
||||
output[split[0].replace('POWER_SUPPLY_', '')] = split[1];
|
||||
}
|
||||
|
||||
if ('STATUS' in output) {
|
||||
this._returnValue(callback, 'State', output['STATUS'], 'battery', '');
|
||||
}
|
||||
|
||||
if ('CYCLE_COUNT' in output) {
|
||||
this._returnValue(callback, 'Cycles', output['CYCLE_COUNT'], 'battery', '');
|
||||
}
|
||||
|
||||
if ('VOLTAGE_NOW' in output) {
|
||||
this._returnValue(callback, 'Voltage', output['VOLTAGE_NOW'] / 1000, 'battery', 'in');
|
||||
}
|
||||
|
||||
if ('CAPACITY_LEVEL' in output) {
|
||||
this._returnValue(callback, 'Level', output['CAPACITY_LEVEL'], 'battery', '');
|
||||
}
|
||||
|
||||
if ('CAPACITY' in output) {
|
||||
this._returnValue(callback, 'Percentage', output['CAPACITY'] / 100, 'battery', 'percent');
|
||||
}
|
||||
|
||||
if ('VOLTAGE_NOW' in output && 'CURRENT_NOW' in output && (!('POWER_NOW' in output))) {
|
||||
output['POWER_NOW'] = (output['VOLTAGE_NOW'] * output['CURRENT_NOW']) / 1000000;
|
||||
}
|
||||
|
||||
if ('POWER_NOW' in output) {
|
||||
this._returnValue(callback, 'Rate', output['POWER_NOW'], 'battery', 'watt');
|
||||
this._returnValue(callback, 'battery', output['POWER_NOW'], 'battery-group', 'watt');
|
||||
}
|
||||
|
||||
if ('CHARGE_FULL' in output && 'VOLTAGE_MIN_DESIGN' in output && (!('ENERGY_FULL' in output))) {
|
||||
output['ENERGY_FULL'] = (output['CHARGE_FULL'] * output['VOLTAGE_MIN_DESIGN']) / 1000000;
|
||||
}
|
||||
|
||||
if ('ENERGY_FULL' in output) {
|
||||
this._returnValue(callback, 'Energy (full)', output['ENERGY_FULL'], 'battery', 'watt-hour');
|
||||
}
|
||||
|
||||
if ('CHARGE_FULL_DESIGN' in output && 'VOLTAGE_MIN_DESIGN' in output && (!('ENERGY_FULL_DESIGN' in output))) {
|
||||
output['ENERGY_FULL_DESIGN'] = (output['CHARGE_FULL_DESIGN'] * output['VOLTAGE_MIN_DESIGN']) / 1000000;
|
||||
}
|
||||
|
||||
if ('ENERGY_FULL_DESIGN' in output) {
|
||||
this._returnValue(callback, 'Energy (design)', output['ENERGY_FULL_DESIGN'], 'battery', 'watt-hour');
|
||||
|
||||
if ('ENERGY_FULL' in output) {
|
||||
this._returnValue(callback, 'Capacity', (output['ENERGY_FULL'] / output['ENERGY_FULL_DESIGN']), 'battery', 'percent');
|
||||
}
|
||||
}
|
||||
|
||||
if ('VOLTAGE_MIN_DESIGN' in output && 'CHARGE_NOW' in output && (!('ENERGY_NOW' in output))) {
|
||||
output['ENERGY_NOW'] = (output['VOLTAGE_MIN_DESIGN'] * output['CHARGE_NOW']) / 1000000;
|
||||
}
|
||||
|
||||
if ('ENERGY_NOW' in output) {
|
||||
this._returnValue(callback, 'Energy (now)', output['ENERGY_NOW'], 'battery', 'watt-hour');
|
||||
}
|
||||
|
||||
if ('ENERGY_FULL' in output && 'ENERGY_NOW' in output && 'POWER_NOW' in output && output['POWER_NOW'] > 0 && 'STATUS' in output && (output['STATUS'] == 'Charging' || output['STATUS'] == 'Discharging')) {
|
||||
|
||||
let timeLeft = 0;
|
||||
|
||||
// two different formulas depending on if we are charging or discharging
|
||||
if (output['STATUS'] == 'Charging') {
|
||||
timeLeft = ((output['ENERGY_FULL'] - output['ENERGY_NOW']) / output['POWER_NOW']);
|
||||
} else {
|
||||
timeLeft = (output['ENERGY_NOW'] / output['POWER_NOW']);
|
||||
}
|
||||
|
||||
// don't process Infinity values
|
||||
if (timeLeft !== Infinity) {
|
||||
if (this._battery_charge_status != output['STATUS']) {
|
||||
// clears history due to state change
|
||||
this._battery_time_left_history = [];
|
||||
|
||||
// clear time left history when laptop goes in and out of charging
|
||||
this._battery_charge_status = output['STATUS'];
|
||||
}
|
||||
|
||||
// add latest time left estimate to our history
|
||||
this._battery_time_left_history.push(parseInt(timeLeft * 3600));
|
||||
|
||||
// keep track of last 15 time left estimates by erasing the first
|
||||
if (this._battery_time_left_history.length > 10)
|
||||
this._battery_time_left_history.shift();
|
||||
|
||||
// sum up and create average of our time left history
|
||||
let sum = this._battery_time_left_history.reduce((a, b) => a + b);
|
||||
let avg = sum / this._battery_time_left_history.length;
|
||||
|
||||
// use time left history to update screen
|
||||
this._returnValue(callback, 'Time left', parseInt(avg), 'battery', 'runtime');
|
||||
}
|
||||
} else {
|
||||
this._returnValue(callback, 'Time left', output['STATUS'], 'battery', '');
|
||||
}
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_returnValue(callback, label, value, type, format) {
|
||||
// don't return if value is not a number - will revisit later
|
||||
//if (isNaN(value)) return;
|
||||
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 => { });
|
||||
|
||||
// is static CPU information enabled?
|
||||
if (this._settings.get_boolean('include-static-info')) {
|
||||
// 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 lines) {
|
||||
let value = '';
|
||||
|
||||
// grab cpu vendor
|
||||
if (value = line.match(/^vendor_id(\s+): (\w+.*)/)) vendor_id = value[2];
|
||||
|
||||
// grab bogomips
|
||||
if (value = line.match(/^bogomips(\s+): (\d*\.?\d*)$/)) bogomips = value[2];
|
||||
|
||||
// grab processor count
|
||||
if (value = line.match(/^physical id(\s+): (\d+)$/)) sockets[value[2]] = 1;
|
||||
|
||||
// grab cache
|
||||
if (value = line.match(/^cache size(\s+): (\d+) KB$/)) 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 => { });
|
||||
|
||||
// grab static CPU information
|
||||
new FileModule.File('/proc/version').read(' ').then(kernelArray => {
|
||||
this._returnValue(callback, 'Kernel', kernelArray[2], 'system', 'string');
|
||||
}).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;
|
||||
this._battery_time_left_history = [];
|
||||
this._battery_charge_status = '';
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
.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; }
|
||||
@ -0,0 +1,337 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
export const 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 'runtime':
|
||||
case 'uptime':
|
||||
let scale = [24, 60, 60];
|
||||
let units = ['d ', 'h ', 'm '];
|
||||
|
||||
// show seconds on higher precision or if value under a minute
|
||||
if (sensorClass != 'runtime' && (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 / 1000000;
|
||||
ending = 'W';
|
||||
break;
|
||||
case 'watt-hour':
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
value = value / 1000000;
|
||||
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 changes 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'] = {};
|
||||
}
|
||||
}
|
||||
});
|
||||