feat: bury offline mode, ship antergos-layan-theme
remember when i thought an offline installer was a good idea? a pre-built squashfs that still needed internet to install DEs? "offline" in name only. it was a lie. a beautiful, naive lie. so i killed it. ripped out the mode picker, the connectivity check, the whole AskMode() circus. calamares-next now just shows a notice saying "yeah that wasnt actually offline" and launches directly into online mode. no more pretending. also fixed the QML nav button because having a greyed out "Next" on the last page is terrible UX. now it says "Done" and quits. youre welcome. and since we nuked the offline mode, might as well make the online mode look fucking spectacular. enter antergos-layan-theme: - kvantum theme because your apps deserve better than qt dreck - sddm theme (AntergosDark-Layan) for a sexy login screen - every plasma plasmoid from the rice (panel colorizer, netspeed, shutdown switch, applets, separators, music toolbar, the works) - yakuake arc-dark skin - antergos logos in /usr/share/logos and /usr/share/pixmaps - konsole profile, desktop appletsrc, all that jazz - kde_settings.conf now points to AntergosDark-Layan sddm theme also replaced the old antergos-grub-theme with the Layan grub theme because the old one was a git-sourced abomination. 75 distro icons, terminal box, selection widgets, antergos wallpaper. ships to /usr/share/grub/themes/antergos/ so calamares finds it. oh and a dozen audit fixes: - kscreenlockerrc wallpaper path fixed - kdialogrc no longer says some other distro - 7 stale knsregistry files deleted (dev machine was leaking) - systemd-inhibit guarded with command -v (dinit doesnt have it) - plasma restart uses killall not systemctl --user - "XeroLinux Toolkit" -> "Antergos Toolkit" in appletsrc - KDE_SESSION_VERSION=5 -> =6 (its 2026) - mirrorlist script says Artix not Arch - SDDM Messages.sh removed from build artifacts
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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,46 @@
|
||||
# Plasma News Widget
|
||||
|
||||
## Overview
|
||||
The Plasma News Widget is a lightweight plasmoid that fetches and displays news articles from the internet using RSS feeds. While designed primarily for news, it supports any valid RSS feed.
|
||||
|
||||
## Features
|
||||
- Fetches news articles from specified RSS feeds.
|
||||
- View the full article by clicking the Read More button
|
||||
- Easily switch between news sources and navigate between articles
|
||||
|
||||
## Project Structure
|
||||
```
|
||||
plasma-news-widget
|
||||
├── contents
|
||||
│ ├── config
|
||||
│ │ ├── config.qml # Configuration settings
|
||||
│ │ ├── main.xml # Main configuration file
|
||||
│ ├── ui
|
||||
│ │ ├── configGeneral.qml # General settings UI
|
||||
│ │ ├── main.qml # Main UI layout
|
||||
│ ├── utils
|
||||
│ │ ├── newsFetcher.js # Handles fetching and processing RSS feeds
|
||||
├── LICENSE # License information
|
||||
├── metadata.json # Widget metadata
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
## Setup Instructions
|
||||
1. **Clone the repository:**
|
||||
```
|
||||
git clone https://github.com/yourusername/plasma-news-widget.git
|
||||
cd plasma-news-widget
|
||||
```
|
||||
Requires: Plasma >= 6.0
|
||||
|
||||
2. **Testing:**
|
||||
Refer to: https://develop.kde.org/docs/plasma/widget/testing/
|
||||
|
||||
3. **Installing:**
|
||||
You can install it easily from the KDE Widgets store, or alternative you can execute `kpackagetool6 -i .` in the root directory of the applet.
|
||||
|
||||
## Contributing
|
||||
Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements.
|
||||
|
||||
## License
|
||||
This project is licensed under the GNU General Public License version 2. See LICENSE for details
|
||||
@@ -0,0 +1,10 @@
|
||||
import QtQuick 2.1
|
||||
import org.kde.plasma.configuration 2.0
|
||||
|
||||
ConfigModel {
|
||||
ConfigCategory {
|
||||
name: i18nc("@title", "General")
|
||||
icon: "configure"
|
||||
source: "configGeneral.qml"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||
<kcfgfile name=""/>
|
||||
<group name="General">
|
||||
<entry name="feeds" type="String">
|
||||
<default>
|
||||
https://rss.nytimes.com/services/xml/rss/nyt/Technology.xml
|
||||
https://feeds.bbci.co.uk/news/technology/rss.xml
|
||||
</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
@@ -0,0 +1,24 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.5 as QQC2
|
||||
import org.kde.kirigami 2.4 as Kirigami
|
||||
import QtQuick.Layouts 1.1 as QtLayouts
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: configPage
|
||||
|
||||
// use string list
|
||||
property alias cfg_feeds: feeds.text
|
||||
|
||||
QQC2.Label {
|
||||
QtLayouts.Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignBottom
|
||||
text: i18n("List of feeds")
|
||||
}
|
||||
|
||||
QQC2.TextArea {
|
||||
id: feeds
|
||||
placeholderText: i18n("Enter a list of feeds, separated by new lines")
|
||||
text: cfg_feeds // Load stored feeds as newline-separated text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import "../utils/newsFetcher.js" as NewsFetcher
|
||||
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
PlasmoidItem {
|
||||
id: mainWindow
|
||||
|
||||
readonly property int implicitWidth: Kirigami.Units.gridUnit * 40
|
||||
readonly property int implicitHeight: Kirigami.Units.gridUnit * 18.5
|
||||
Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground
|
||||
|
||||
Plasmoid.icon: "news"
|
||||
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
readonly property int minimumWidth: Kirigami.Units.gridUnit * 8
|
||||
readonly property int minimumHeight: Kirigami.Units.gridUnit * 8
|
||||
|
||||
property var feedList: []
|
||||
property string errors: ""
|
||||
property var newsItems: [];
|
||||
property int currentIndex: 0;
|
||||
|
||||
function fetchFeeds() {
|
||||
var feedUrls = plasmoid.configuration.feeds.split("\n"); // Read feed URLs from configuration
|
||||
NewsFetcher.fetchAllFeeds(feedUrls, function(allFeeds, errorMessage) {
|
||||
errors = errorMessage;
|
||||
feedList = allFeeds.sort(function(a, b) { // sort title alphabetically
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
|
||||
if(feedList.length > 0) {
|
||||
fetchCurrentFeed(currentIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to load a specific feed's news items
|
||||
function fetchCurrentFeed(index) {
|
||||
if (index < feedList.length) {
|
||||
// sort by date - oldest first
|
||||
newsItems = feedList[index].items.sort(function(a, b) {
|
||||
return new Date(a.pubDate) - new Date(b.pubDate);
|
||||
});
|
||||
currentIndex = newsItems.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: fetchFeeds();
|
||||
Plasmoid.onUserConfiguringChanged: fetchFeeds();
|
||||
|
||||
Row { // Tab Container
|
||||
id: tabContainer
|
||||
width: parent.width
|
||||
height: Math.max(feedTabs.implicitHeight, refreshButton.implicitHeight)
|
||||
anchors.top: parent.top
|
||||
spacing: 5
|
||||
|
||||
Flickable {
|
||||
id: tabBarFlickable
|
||||
width: parent.width - refreshButton.width - 10
|
||||
height: feedTabs.implicitHeight
|
||||
clip: true
|
||||
contentWidth: feedTabs.width
|
||||
interactive: feedTabs.width > width
|
||||
|
||||
TabBar {
|
||||
id: feedTabs
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Repeater {
|
||||
model: feedList
|
||||
TabButton {
|
||||
text: feedList[model.index].title
|
||||
onClicked: fetchCurrentFeed(model.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh Button
|
||||
PlasmaComponents.Button {
|
||||
id: refreshButton
|
||||
anchors.right: parent.right
|
||||
icon.name: "view-refresh"
|
||||
height: implicitHeight
|
||||
onClicked: fetchFeeds()
|
||||
z: 5
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Error Display
|
||||
id: errorDisplay
|
||||
visible: errors !== ""
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
// slgihtly red background
|
||||
color: "#FFCCCC"
|
||||
border.color: "#990000"
|
||||
border.width: 2
|
||||
radius: 8
|
||||
z: -1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
width: parent.width
|
||||
|
||||
Kirigami.Icon {
|
||||
source: "data-error"
|
||||
width: 50
|
||||
height: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
text: errors
|
||||
color: "#990000"
|
||||
font.bold: true
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column { // News Container
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: feedTabs.height
|
||||
visible: errors === ""
|
||||
spacing: 10
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: feedTabs.height / 3
|
||||
spacing: 10
|
||||
|
||||
// News Image
|
||||
Image {
|
||||
source: newsItems.length > 0 && newsItems[currentIndex].imageUrl !== ""
|
||||
? newsItems[currentIndex].imageUrl : ""
|
||||
width: parent.width * 0.32
|
||||
fillMode: Image.PreserveAspectFit
|
||||
height: parent.height * 0.8
|
||||
visible: newsItems.length > 0 && newsItems[currentIndex].imageUrl !== ""
|
||||
}
|
||||
|
||||
// News Text Content
|
||||
Column {
|
||||
id: newsContent
|
||||
width: newsItems.length > 0 && newsItems[currentIndex].imageUrl !== ""
|
||||
? parent.width * 0.68 : parent.width + 10
|
||||
height: parent.height
|
||||
spacing: 5
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: newsTitle
|
||||
text: newsItems.length > 0 ? newsItems[currentIndex].title : ""
|
||||
font.bold: true
|
||||
font.pointSize: 15.4
|
||||
wrapMode: Text.Wrap
|
||||
width: parent.width - 20
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: newsScrollView
|
||||
width: newsContent.width
|
||||
// Set height to the smaller of content height or available space
|
||||
height: Math.min(contentLabel.implicitHeight, parent.height - newsTitle.height - pubDateLabel.implicitHeight - navigationRow.height - 20)
|
||||
visible: newsItems.length > 0 && newsItems[currentIndex].description !== ""
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: contentLabel
|
||||
text: newsItems.length > 0 ? newsItems[currentIndex].description : ""
|
||||
wrapMode: Text.Wrap
|
||||
font.pointSize: 12
|
||||
width: newsScrollView.width - 20
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: pubDateLabel
|
||||
text: newsItems.length > 0 ? newsItems[currentIndex].pubDate : ""
|
||||
font.italic: true
|
||||
font.pointSize: 10
|
||||
color: "gray"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row { // Navigation Buttons
|
||||
id: navigationRow
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
height: parent.height * 0.1
|
||||
anchors.bottom: parent.bottom
|
||||
visible: errors === ""
|
||||
|
||||
PlasmaComponents.Button {
|
||||
text: "Read More"
|
||||
enabled: newsItems.length > 0
|
||||
onClicked: {
|
||||
if (newsItems.length > 0) {
|
||||
Qt.openUrlExternally(newsItems[currentIndex].link);
|
||||
}
|
||||
}
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true // Fills space between buttons
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 10
|
||||
anchors.right: parent.right
|
||||
PlasmaComponents.Button {
|
||||
icon.name: "arrow-left-double"
|
||||
enabled: currentIndex > 0
|
||||
onClicked: {
|
||||
if (currentIndex > 0) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Button {
|
||||
icon.name: "arrow-left"
|
||||
enabled: currentIndex > 0
|
||||
onClicked: {
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Button {
|
||||
icon.name: "arrow-right"
|
||||
enabled: currentIndex < newsItems.length - 1
|
||||
onClicked: {
|
||||
if (currentIndex < newsItems.length - 1) {
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Button {
|
||||
icon.name: "arrow-right-double"
|
||||
enabled: currentIndex < newsItems.length - 1
|
||||
onClicked: {
|
||||
if (currentIndex < newsItems.length - 1) {
|
||||
currentIndex = newsItems.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
function getAttributeValue(node, attributeName) {
|
||||
if (node.attributes) {
|
||||
for (let i = 0; i < node.attributes.length; i++) {
|
||||
if (node.attributes[i].name === attributeName) {
|
||||
return node.attributes[i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getTextContent(node) {
|
||||
return node && node.firstChild ? node.firstChild.nodeValue : "";
|
||||
}
|
||||
|
||||
// Removes inline tags that could break layout but don't have content
|
||||
function cleanDescription(html) {
|
||||
if (!html) return "";
|
||||
|
||||
var allowedTags = ["b", "strong", "i", "em", "u", "a", "p", "br", "ul", "ol", "li", "span", "strike", "s", "del", "sub", "sup", "code", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"];
|
||||
|
||||
// Remove HTML comments
|
||||
html = html.replace(/<!--[\s\S]*?-->/g, "");
|
||||
|
||||
// Regex to remove disallowed tags but keep content
|
||||
html = html.replace(/<\/?([a-zA-Z0-9]+)(?:\s[^>]*)?>/g, function (match, tagName) {
|
||||
tagName = tagName.toLowerCase();
|
||||
return allowedTags.includes(tagName) ? match : "";
|
||||
});
|
||||
|
||||
return html.trim();
|
||||
}
|
||||
|
||||
function fetchNews(rssUrl, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "document";
|
||||
console.log("Fetching news from " + rssUrl);
|
||||
xhr.open("GET", rssUrl);
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
var xmlDoc = xhr.responseXML;
|
||||
|
||||
if (!xmlDoc || !xmlDoc.documentElement) {
|
||||
callback([], "Failed to parse XML document");
|
||||
return;
|
||||
}
|
||||
|
||||
var newsArray = [];
|
||||
var feedTitle = "Unknown Feed";
|
||||
var root = xmlDoc.documentElement;
|
||||
|
||||
if (root.nodeName === "rss") {
|
||||
// --- RSS Feed ---
|
||||
var channel = null;
|
||||
var rootChildren = root.childNodes;
|
||||
for (let i = 0; i < rootChildren.length; i++) {
|
||||
if (rootChildren[i].nodeName === "channel") {
|
||||
channel = rootChildren[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
callback([], "No <channel> element found in RSS feed");
|
||||
return;
|
||||
}
|
||||
|
||||
var titleNode = channel.firstChild;
|
||||
while (titleNode) {
|
||||
if (titleNode.nodeName === "title") {
|
||||
feedTitle = getTextContent(titleNode);
|
||||
break;
|
||||
}
|
||||
titleNode = titleNode.nextSibling;
|
||||
}
|
||||
|
||||
var itemNode = channel.firstChild;
|
||||
while (itemNode) {
|
||||
if (itemNode.nodeName === "item") {
|
||||
newsArray.push(parseRssItem(itemNode));
|
||||
}
|
||||
itemNode = itemNode.nextSibling;
|
||||
}
|
||||
|
||||
} else if (root.nodeName === "feed") {
|
||||
// --- Atom Feed ---
|
||||
var titleNode = root.firstChild;
|
||||
while (titleNode) {
|
||||
if (titleNode.nodeName === "title") {
|
||||
feedTitle = getTextContent(titleNode);
|
||||
break;
|
||||
}
|
||||
titleNode = titleNode.nextSibling;
|
||||
}
|
||||
|
||||
var entryNode = root.firstChild;
|
||||
while (entryNode) {
|
||||
if (entryNode.nodeName === "entry") {
|
||||
newsArray.push(parseAtomEntry(entryNode));
|
||||
}
|
||||
entryNode = entryNode.nextSibling;
|
||||
}
|
||||
} else {
|
||||
callback([], "Unsupported feed format: " + root.nodeName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newsArray.length === 0) {
|
||||
callback([], "No news articles found in the feed.");
|
||||
} else {
|
||||
callback({ url: rssUrl, title: feedTitle, items: newsArray }, "");
|
||||
}
|
||||
|
||||
} else {
|
||||
callback([], "Failed to load RSS feed (HTTP Status " + xhr.status + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function parseRssItem(itemNode) {
|
||||
var title = "No title";
|
||||
var link = "#";
|
||||
var description = "No description";
|
||||
var pubDate = "No date";
|
||||
var imageUrl = "";
|
||||
|
||||
var children = itemNode.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
switch (child.nodeName) {
|
||||
case "title":
|
||||
title = getTextContent(child);
|
||||
break;
|
||||
case "link":
|
||||
if (link === "#") link = getTextContent(child);
|
||||
break;
|
||||
case "description":
|
||||
description = cleanDescription(getTextContent(child));
|
||||
break;
|
||||
case "pubDate":
|
||||
pubDate = getTextContent(child);
|
||||
break;
|
||||
case "thumbnail":
|
||||
case "enclosure":
|
||||
case "content":
|
||||
imageUrl = getAttributeValue(child, "url");
|
||||
break;
|
||||
case "group":
|
||||
var mediaChild = child.firstChild;
|
||||
while (mediaChild) {
|
||||
if (mediaChild.nodeName === "content") {
|
||||
imageUrl = getAttributeValue(mediaChild, "url");
|
||||
break;
|
||||
}
|
||||
mediaChild = mediaChild.nextSibling;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { title, link, description, pubDate, imageUrl };
|
||||
}
|
||||
|
||||
function parseAtomEntry(entryNode) {
|
||||
var title = "No title";
|
||||
var link = "#";
|
||||
var description = "No description";
|
||||
var pubDate = "No date";
|
||||
var imageUrl = "";
|
||||
|
||||
var children = entryNode.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
switch (child.nodeName) {
|
||||
case "title":
|
||||
title = getTextContent(child);
|
||||
break;
|
||||
case "link":
|
||||
var rel = getAttributeValue(child, "rel");
|
||||
if (rel !== "self" && getAttributeValue(child, "href")) {
|
||||
link = getAttributeValue(child, "href");
|
||||
}
|
||||
break;
|
||||
case "summary":
|
||||
case "content":
|
||||
description = cleanDescription(getTextContent(child));
|
||||
break;
|
||||
case "updated":
|
||||
case "published":
|
||||
pubDate = getTextContent(child);
|
||||
break;
|
||||
case "thumbnail":
|
||||
case "content":
|
||||
imageUrl = getAttributeValue(child, "url");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { title, link, description, pubDate, imageUrl };
|
||||
}
|
||||
|
||||
function fetchAllFeeds(feedUrls, callback) {
|
||||
var allFeeds = [];
|
||||
var errors = [];
|
||||
var remaining = feedUrls.length;
|
||||
|
||||
feedUrls.forEach(rssUrl => {
|
||||
fetchNews(rssUrl, (feedData, errorMessage) => {
|
||||
if (errorMessage) errors.push(`Error fetching ${rssUrl}: ${errorMessage}`);
|
||||
else allFeeds.push(feedData);
|
||||
|
||||
if (--remaining === 0) callback(allFeeds, errors.length ? errors.join("\n") : "");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"KPlugin": {
|
||||
"Authors": [
|
||||
{
|
||||
"Email": "info@fenriris.net",
|
||||
"Name": "Fenriris"
|
||||
}
|
||||
],
|
||||
"Category": "Internet",
|
||||
"Description": "View news articles from the Internet",
|
||||
"EnabledByDefault": true,
|
||||
"Icon": "news-subscribe",
|
||||
"Id": "com.SrS2225a.feeds",
|
||||
"License": "GPL",
|
||||
"Name": "News",
|
||||
"KPackageStructure": "Plasma/Applet",
|
||||
"Version": "1.2",
|
||||
"Website": "https://fenriris.net/"
|
||||
},
|
||||
"X-Plasma-API": "declarativeappletscript",
|
||||
"X-Plasma-MainScript": "ui/main.qml",
|
||||
"X-Plasma-StandAloneApp": true,
|
||||
"X-Plasma-API-Minimum-Version": "6.0",
|
||||
"KPackageStructure": "Plasma/Applet"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import org.kde.plasma.configuration
|
||||
|
||||
ConfigModel {
|
||||
ConfigCategory {
|
||||
name: i18n("General")
|
||||
icon: "preferences-desktop"
|
||||
source: "configuration/General.qml"
|
||||
}
|
||||
|
||||
ConfigCategory {
|
||||
name: i18n("Upgrade & Management")
|
||||
icon: "preferences-system-startup"
|
||||
source: "configuration/Upgrade.qml"
|
||||
}
|
||||
|
||||
ConfigCategory {
|
||||
name: i18n("Appearance")
|
||||
icon: "preferences-desktop-display-color"
|
||||
source: "configuration/Appearance.qml"
|
||||
}
|
||||
|
||||
ConfigCategory {
|
||||
name: i18n("Rules")
|
||||
icon: "preferences-system-windows-behavior"
|
||||
source: "configuration/Rules.qml"
|
||||
}
|
||||
|
||||
ConfigCategory {
|
||||
name: i18n("Support me")
|
||||
icon: "donate"
|
||||
source: "configuration/Support.qml"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||
|
||||
<kcfgfile name="apdatifier"/>
|
||||
<group name="General">
|
||||
<entry name="checkMode" type="string"><default>interval</default></entry>
|
||||
<entry name="intervalMinutes" type="int"><default>120</default></entry>
|
||||
<entry name="dailyHour" type="int"><default>20</default></entry>
|
||||
<entry name="dailyMinute" type="int"><default>30</default></entry>
|
||||
<entry name="weeklyDay" type="int"><default>0</default></entry>
|
||||
<entry name="weeklyHour" type="int"><default>20</default></entry>
|
||||
<entry name="weeklyMinute" type="int"><default>30</default></entry>
|
||||
<entry name="arch" type="bool"><default>true</default></entry>
|
||||
<entry name="aur" type="bool"><default>false</default></entry>
|
||||
<entry name="flatpak" type="bool"><default>false</default></entry>
|
||||
<entry name="widgets" type="bool"><default>false</default></entry>
|
||||
<entry name="fwupd" type="bool"><default>false</default></entry>
|
||||
<entry name="feedsEnabled" type="bool"><default>false</default></entry>
|
||||
<entry name="feedsFetchCount" type="int"><default>3</default></entry>
|
||||
<entry name="feeds" type="string"><default>https://archlinux.org/feeds/news/</default></entry>
|
||||
<entry name="middleAction" type="string"><default>upgradeSystem</default></entry>
|
||||
<entry name="rightAction" type="string"><default></default></entry>
|
||||
<entry name="scrollUpAction" type="string"><default>switchScheduler</default></entry>
|
||||
<entry name="scrollDownAction" type="string"><default>management</default></entry>
|
||||
<entry name="notifyUpdates" type="bool"><default>true</default></entry>
|
||||
<entry name="notifyUpdatesAction" type="bool"><default>false</default></entry>
|
||||
<entry name="notifyEveryBump" type="bool"><default>true</default></entry>
|
||||
<entry name="notifyNews" type="bool"><default>true</default></entry>
|
||||
<entry name="notifyNewsAction" type="bool"><default>false</default></entry>
|
||||
<entry name="notifyErrors" type="bool"><default>true</default></entry>
|
||||
<entry name="notifySound" type="bool"><default>true</default></entry>
|
||||
<entry name="notifyPersistent" type="bool"><default>false</default></entry>
|
||||
<entry name="checkConn" type="bool"><default>false</default></entry>
|
||||
<entry name="respectMeteredConn" type="bool"><default>false</default></entry>
|
||||
</group>
|
||||
<group name="Upgrade">
|
||||
<entry name="wrapper" type="string"><default></default></entry>
|
||||
<entry name="archFlags" type="string"><default></default></entry>
|
||||
<entry name="sudoBin" type="string"><default>sudo</default></entry>
|
||||
<entry name="rebootSystem" type="bool"><default>false</default></entry>
|
||||
<entry name="mirrors" type="string"><default>false</default></entry>
|
||||
<entry name="mirrorsAge" type="int"><default>7</default></entry>
|
||||
<entry name="mirrorCount" type="int"><default>5</default></entry>
|
||||
<entry name="mirrorProgress" type="bool"><default>false</default></entry>
|
||||
<entry name="dynamicUrl" type="string"><default>null</default></entry>
|
||||
<entry name="flatpakRemoveUnused" type="bool"><default>false</default></entry>
|
||||
<entry name="flatpakFlags" type="string"><default></default></entry>
|
||||
<entry name="widgetConfirmation" type="bool"><default>true</default></entry>
|
||||
<entry name="restartShell" type="bool"><default>true</default></entry>
|
||||
<entry name="restartCommand" type="string"><default>killall plasmashell</default></entry>
|
||||
<entry name="trayEnabledByDefault" type="bool"><default>false</default></entry>
|
||||
<entry name="preExec" type="string"><default></default></entry>
|
||||
<entry name="postExec" type="string"><default></default></entry>
|
||||
<entry name="terminal" type="string"><default></default></entry>
|
||||
<entry name="idleInhibit" type="bool"><default>false</default></entry>
|
||||
<entry name="tmuxSession" type="bool"><default>false</default></entry>
|
||||
<entry name="termFont" type="bool"><default>false</default></entry>
|
||||
<entry name="mngHideFlatpak" type="bool"><default>false</default></entry>
|
||||
<entry name="mngHideHeaders" type="bool"><default>false</default></entry>
|
||||
<entry name="mngHideHelp" type="bool"><default>false</default></entry>
|
||||
<entry name="mngHideNums" type="bool"><default>false</default></entry>
|
||||
<entry name="fzfOptions" type="string"><default></default></entry>
|
||||
</group>
|
||||
<group name="Appearance">
|
||||
<entry name="hideIconPolicy" type="int"><default>0</default></entry>
|
||||
<entry name="selectedIcon" type="string"><default>apdatifier-plasmoid</default></entry>
|
||||
<entry name="busyIndicator" type="string"><default>spinner</default></entry>
|
||||
<entry name="counterMode" type="string"><default>side</default></entry>
|
||||
<entry name="counterBadgePosition" type="string"><default>topRight</default></entry>
|
||||
<entry name="counterSidePosition" type="string"><default>left</default></entry>
|
||||
<entry name="counterGapsInner" type="int"><default>0</default></entry>
|
||||
<entry name="counterGapsOuter" type="int"><default>0</default></entry>
|
||||
<entry name="counterOffsetX" type="int"><default>0</default></entry>
|
||||
<entry name="counterOffsetY" type="int"><default>0</default></entry>
|
||||
<entry name="counterColor" type="string"><default></default></entry>
|
||||
<entry name="counterRadius" type="int"><default>3</default></entry>
|
||||
<entry name="counterOpacity" type="int"><default>8</default></entry>
|
||||
<entry name="counterFontFamily" type="string"><default></default></entry>
|
||||
<entry name="counterFontSize" type="int"><default>4</default></entry>
|
||||
<entry name="counterFontBold" type="bool"><default>true</default></entry>
|
||||
<entry name="pauseBadgePosition" type="string"><default>topLeft</default></entry>
|
||||
<entry name="updatedBadgePosition" type="string"><default>topRight</default></entry>
|
||||
<entry name="badgeOffsetX" type="int"><default>0</default></entry>
|
||||
<entry name="badgeOffsetY" type="int"><default>0</default></entry>
|
||||
<entry name="ownIconsUI" type="bool"><default>true</default></entry>
|
||||
<entry name="autoHideScrollBar" type="bool"><default>true</default></entry>
|
||||
<entry name="defaultTab" type="int"><default>0</default></entry>
|
||||
<entry name="switchDefaultTab" type="bool"><default>false</default></entry>
|
||||
<entry name="sorting" type="bool"><default>true</default></entry>
|
||||
<entry name="showStatusText" type="bool"><default>true</default></entry>
|
||||
<entry name="showToolBar" type="bool"><default>true</default></entry>
|
||||
<entry name="searchButton" type="bool"><default>true</default></entry>
|
||||
<entry name="intervalButton" type="bool"><default>true</default></entry>
|
||||
<entry name="sortButton" type="bool"><default>true</default></entry>
|
||||
<entry name="viewButton" type="bool"><default>true</default></entry>
|
||||
<entry name="managementButton" type="bool"><default>true</default></entry>
|
||||
<entry name="upgradeButton" type="bool"><default>true</default></entry>
|
||||
<entry name="checkButton" type="bool"><default>true</default></entry>
|
||||
<entry name="pinButton" type="bool"><default>true</default></entry>
|
||||
<entry name="settingsButton" type="bool"><default>true</default></entry>
|
||||
<entry name="compactInfo" type="string"><default>repository</default></entry>
|
||||
<entry name="extendedShowRepository" type="bool"><default>true</default></entry>
|
||||
<entry name="extendedShowInstallReason" type="bool"><default>true</default></entry>
|
||||
<entry name="highlightVersionDiff" type="bool"><default>false</default></entry>
|
||||
<entry name="colorizeInstallReason" type="bool"><default>false</default></entry>
|
||||
<entry name="tabBarVisible" type="bool"><default>true</default></entry>
|
||||
<entry name="tabBarTexts" type="bool"><default>true</default></entry>
|
||||
</group>
|
||||
<group name="Miscellaneous">
|
||||
<entry name="version" type="string"><default>v0</default></entry>
|
||||
<entry name="configMsg" type="bool"><default>true</default></entry>
|
||||
<entry name="dbPath" type="string"><default>${TMPDIR:-/tmp}/checkup-db-$(id -u)</default></entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
scriptDir="$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")"
|
||||
appletDir="${scriptDir%/contents/*}"
|
||||
configDir="${APDATIFIER_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/apdatifier}"
|
||||
cacheDir="${APDATIFIER_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/apdatifier}"
|
||||
localDir="${XDG_LOCAL_HOME:-$HOME/.local/share}"
|
||||
iconsDir="$localDir/icons/breeze/status/22"
|
||||
notifDir="$localDir/knotifications6"
|
||||
config="$configDir/config.conf"
|
||||
notif="apdatifier.notifyrc"
|
||||
icons=(apdatifier-plasmoid.svg
|
||||
apdatifier-packages.svg
|
||||
apdatifier-package.svg
|
||||
apdatifier-flatpak.svg)
|
||||
|
||||
test -d "$configDir" || mkdir -p "$configDir"
|
||||
test -f "$config" || touch "$config"
|
||||
|
||||
test -d "$cacheDir" || mkdir -p "$cacheDir"
|
||||
# Migrate cache files from old location
|
||||
[[ -f "$configDir/updates.json" && ! -f "$cacheDir/updates.json" ]] && mv "$configDir/updates.json" "$cacheDir/updates.json"
|
||||
[[ -f "$configDir/news.json" && ! -f "$cacheDir/news.json" ]] && mv "$configDir/news.json" "$cacheDir/news.json"
|
||||
|
||||
for dir in $iconsDir $notifDir; do
|
||||
test -d "$dir" || mkdir -p "$dir"
|
||||
done
|
||||
|
||||
for icon in "${icons[@]}"; do
|
||||
test -f "$iconsDir/$icon" || cp "$appletDir/contents/ui/assets/icons/$icon" "$iconsDir"
|
||||
done
|
||||
|
||||
test -d "$notifDir" && cat > "$notifDir/$notif" << EOF
|
||||
[Global]
|
||||
IconName=apdatifier-plasmoid
|
||||
Comment=Apdatifier
|
||||
|
||||
[Event/updates]
|
||||
Name=New updates
|
||||
Comment=Event when updates notification enabled without sound
|
||||
Action=Popup
|
||||
|
||||
[Event/updatesSound]
|
||||
Name=New updates (with sound)
|
||||
Comment=Event when updates notification enabled with sound
|
||||
Action=Popup|Sound
|
||||
Sound=service-login
|
||||
|
||||
[Event/error]
|
||||
Name=Error
|
||||
Comment=Event when error notification enabled without sound
|
||||
Action=Popup
|
||||
|
||||
[Event/errorSound]
|
||||
Name=Error (with sound)
|
||||
Comment=Event when errors notification enabled with sound
|
||||
Action=Popup|Sound
|
||||
Sound=dialog-error-serious
|
||||
|
||||
[Event/news]
|
||||
Name=News
|
||||
Comment=Event when news notification without sound
|
||||
Action=Popup
|
||||
|
||||
[Event/newsSound]
|
||||
Name=News (with sound)
|
||||
Comment=Event when news notification with sound
|
||||
Action=Popup|Sound
|
||||
Sound=dialog-information
|
||||
EOF
|
||||
@@ -0,0 +1,606 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/utils"
|
||||
|
||||
[ "$1" ] && selected="$1" || selected=0
|
||||
|
||||
if [[ $aur != true || -z $wrapper ]]; then
|
||||
wrapper="pacman"
|
||||
wrapper_sudo="$sudoBin $wrapper"
|
||||
else
|
||||
wrapper="${wrapper##*/}"
|
||||
wrapper_sudo=$wrapper
|
||||
fi
|
||||
|
||||
if [[ $termFont = "true" ]]; then
|
||||
ICO_MNG_01=""
|
||||
ICO_MNG_02=""
|
||||
ICO_MNG_03=""
|
||||
ICO_MNG_04=""
|
||||
ICO_MNG_05=""
|
||||
ICO_MNG_06=""
|
||||
ICO_MNG_07=""
|
||||
ICO_MNG_08=""
|
||||
ICO_MNG_09=""
|
||||
ICO_MNG_10=""
|
||||
ICO_MNG_11=""
|
||||
ICO_MNG_12=""
|
||||
ICO_MNG_13=""
|
||||
fi
|
||||
|
||||
fzf_settings="--preview-window=right:70%,border-none \
|
||||
--height=100% \
|
||||
--margin=0 \
|
||||
--padding=0 \
|
||||
--layout=reverse \
|
||||
--style minimal \
|
||||
--scrollbar \
|
||||
--color dark \
|
||||
--multi \
|
||||
--track \
|
||||
--exact \
|
||||
--cycle \
|
||||
--no-separator \
|
||||
--info=right \
|
||||
--marker=•"
|
||||
|
||||
[[ -n "$fzfOptions" ]] && fzf_settings+=" $fzfOptions"
|
||||
|
||||
show_help=false
|
||||
|
||||
|
||||
showMenu() {
|
||||
menu_type=$1
|
||||
opts=()
|
||||
|
||||
case $menu_type in
|
||||
main)
|
||||
opts+=("$ICO_MNG_01 $MNG_BROWSE")
|
||||
opts+=("$ICO_MNG_02 $MNG_INST")
|
||||
opts+=("$ICO_MNG_03 $MNG_EXPL")
|
||||
opts+=("$ICO_MNG_04 $MNG_ORPH")
|
||||
opts+=("$ICO_MNG_05 $MNG_DOWNGR")
|
||||
opts+=("$ICO_MNG_06 $MNG_CACHE")
|
||||
opts+=("$ICO_MNG_05 $MNG_REBUILD")
|
||||
opts+=("$ICO_MNG_08 $MNG_MIRROR")
|
||||
opts+=("$ICO_MNG_10 $MNG_RECENT")
|
||||
redraw
|
||||
case $selected in
|
||||
0) [[ $mngHideFlatpak = true ]] && fzfExec Slq || showMenu browse;;
|
||||
1) [[ $mngHideFlatpak = true ]] && fzfExec Qq || showMenu installed;;
|
||||
2) showMenu explicit;;
|
||||
3) showMenu orphans;;
|
||||
4) downgrade;;
|
||||
5) showMenu cache;;
|
||||
6) showMenu rebuild;;
|
||||
7) tui_exit; exec "$scriptDir/mirrorlist" menu "$selected";;
|
||||
8) recentlyInstalled;;
|
||||
9) showMenu main;;
|
||||
esac
|
||||
;;
|
||||
browse)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_11 pacman")
|
||||
opts+=("$ICO_MNG_11 flathub")
|
||||
redraw
|
||||
case $selected in
|
||||
0) fzfExec Slq;;
|
||||
1) flatpakFlathub;;
|
||||
2) selected=0; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
installed)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_11 pacman")
|
||||
opts+=("$ICO_MNG_11 flatpak")
|
||||
redraw
|
||||
case $selected in
|
||||
0) fzfExec Qq;;
|
||||
1) flatpakInstalled;;
|
||||
2) selected=1; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
explicit)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_03 $MNG_EXPL_L")
|
||||
opts+=("$ICO_MNG_04 $MNG_EXPL_U")
|
||||
redraw
|
||||
case $selected in
|
||||
0) fzfExec Qqe;;
|
||||
1) fzfExec Qqet;;
|
||||
2) selected=2; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
orphans)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_06 $MNG_ORPH_L")
|
||||
opts+=("$ICO_MNG_07 $MNG_ORPH_R")
|
||||
redraw
|
||||
case $selected in
|
||||
0) fzfExec Qqtd;;
|
||||
1) uninstallOrphans;;
|
||||
2) selected=3; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
cache)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_07 $MNG_CACHE_A")
|
||||
opts+=("$ICO_MNG_06 $MNG_CACHE_U")
|
||||
redraw
|
||||
case $selected in
|
||||
0) cleanCache Scc;;
|
||||
1) cleanCache Sc;;
|
||||
2) selected=5; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
rebuild)
|
||||
selected=0
|
||||
opts+=("$ICO_MNG_05 $MNG_REBUILD_A")
|
||||
opts+=("$ICO_MNG_09 $MNG_REBUILD_P")
|
||||
redraw
|
||||
case $selected in
|
||||
0) fzfExec Qqm;;
|
||||
1) rebuildPython;;
|
||||
2) selected=6; showMenu main;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
draw_menu() {
|
||||
printf '\e[H'
|
||||
|
||||
local min_height=$((${#opts[@]} + 4))
|
||||
if ((LINES < min_height)); then
|
||||
printf '\e[2J'
|
||||
echo "Terminal too small (need ${min_height} lines)"
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
if [[ $mngHideHeaders == false ]]; then
|
||||
local header
|
||||
case $menu_type in
|
||||
main) header="$ICO_MNG_13 $MNG_HEADER";;
|
||||
browse) header="$ICO_MNG_12 $MNG_BROWSE_H";;
|
||||
installed) header="$ICO_MNG_11 $MNG_INST_H";;
|
||||
explicit) header="$ICO_MNG_03 $MNG_EXPL_H";;
|
||||
orphans) header="$ICO_MNG_04 $MNG_ORPH_H";;
|
||||
cache) header="$ICO_MNG_06 $MNG_CACHE_H";;
|
||||
rebuild) header="$ICO_MNG_05 $MNG_REBUILD_H";;
|
||||
esac
|
||||
|
||||
echo -e "$(colorize gray bold $header)\n"
|
||||
fi
|
||||
|
||||
|
||||
for i in "${!opts[@]}"; do
|
||||
|
||||
local number=$((i + 1))
|
||||
if [[ $mngHideNums == false ]]; then
|
||||
# if [[ $termFont = "true" ]]; then
|
||||
# case $number in
|
||||
# 1) number=;;
|
||||
# 2) number=;;
|
||||
# 3) number=;;
|
||||
# 4) number=;;
|
||||
# 5) number=;;
|
||||
# 6) number=;;
|
||||
# 7) number=;;
|
||||
# 8) number=;;
|
||||
# 9) number=;;
|
||||
# esac
|
||||
# number="$(colorize gray $number) "
|
||||
# else
|
||||
number="$(colorize gray $number.) "
|
||||
# fi
|
||||
else
|
||||
number=""
|
||||
fi
|
||||
|
||||
if [[ $i -eq $selected ]]; then
|
||||
echo "$number$(colorize green bold ${opts[$i]})"
|
||||
else
|
||||
echo "$number${opts[$i]# }"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $mngHideHelp == false ]]; then
|
||||
tput cup $((LINES - 2)) 0
|
||||
|
||||
local footer
|
||||
if [[ $show_help == true ]]; then
|
||||
footer='→ l Enter 1-9 · select ↑ k · up ↓ j · down ← h · back q · quit'
|
||||
else
|
||||
footer='? · help'
|
||||
fi
|
||||
printf "$(colorize gray '%s')%*s\n" "$footer" "$((COLUMNS - ${#footer}))" ""
|
||||
fi
|
||||
}
|
||||
|
||||
redraw() {
|
||||
clear_screen
|
||||
draw_menu
|
||||
handleInput
|
||||
}
|
||||
|
||||
handleInput() {
|
||||
while true; do
|
||||
read -rsN1 "${read_flags[@]}" input
|
||||
[[ -z $input ]] && { draw_menu; continue; }
|
||||
|
||||
if [[ $input == $'\e' ]]; then
|
||||
read -rsN2 -t 0.02 rest || rest=""
|
||||
input+="$rest"
|
||||
fi
|
||||
|
||||
case "$input" in
|
||||
k|$'\e[A') ((selected--)) ;;
|
||||
j|$'\e[B') ((selected++)) ;;
|
||||
l|$'\e[C'|$'\n'|$'\r') break ;;
|
||||
h|$'\e[D')
|
||||
if [[ $menu_type != main ]]; then
|
||||
selected=${#opts[@]}
|
||||
break
|
||||
fi
|
||||
;;
|
||||
$'\e')
|
||||
if [[ $menu_type != main ]]; then
|
||||
selected=${#opts[@]}
|
||||
break
|
||||
else
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
[1-9]) (( input>0 && input<=${#opts[@]} )) && selected=$((input-1)) && break ;;
|
||||
'?') [[ $show_help == true ]] && show_help=false || show_help=true ;;
|
||||
q|Q) exit ;;
|
||||
esac
|
||||
|
||||
(( selected < 0 )) && selected=$((${#opts[@]} - 1))
|
||||
(( selected >= ${#opts[@]} )) && selected=0
|
||||
|
||||
draw_menu
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fzfExec() {
|
||||
tui_exit
|
||||
dependency pacman fzf || return
|
||||
|
||||
argList="-$1"
|
||||
case $1 in
|
||||
Slq) argPreview="-Si"; argAction="-Syu";;
|
||||
Qqm) argPreview="-Qi"; argAction="-S --rebuild";;
|
||||
*) argPreview="-Qil"; argAction="-Rsn";;
|
||||
esac
|
||||
|
||||
if [[ $aur == true && $1 == "Slq" && $wrapper == "paru" ]]; then
|
||||
packages=$( ( pacman $argList; curl -fsSL https://aur.archlinux.org/packages.gz | gzip -dc ) | LC_ALL=C sort -u | fzf $fzf_settings --preview "$wrapper $argPreview {}" )
|
||||
else
|
||||
packages=$($wrapper $argList | LC_ALL=C sort -u | fzf $fzf_settings --preview "$wrapper $argPreview {}")
|
||||
fi
|
||||
|
||||
if [[ -n "$packages" ]]; then
|
||||
packages=$(echo "$packages" | oneLine)
|
||||
printExec "$wrapper_sudo $argAction $packages"
|
||||
$wrapper_sudo $argAction $packages
|
||||
pressReturn
|
||||
fi
|
||||
|
||||
tui_restore
|
||||
}
|
||||
|
||||
uninstallOrphans() {
|
||||
tui_exit
|
||||
dependency pacman || return
|
||||
|
||||
orphans=$($wrapper -Qqtd | oneLine)
|
||||
if [[ -n "$orphans" ]]; then
|
||||
printExec "$wrapper_sudo -Rsn $orphans"
|
||||
printImportant "$MNG_WARN"; echo
|
||||
$wrapper_sudo -Rsn $orphans
|
||||
else
|
||||
printDone "$MNG_DONE"
|
||||
fi
|
||||
|
||||
pressReturn
|
||||
tui_restore
|
||||
}
|
||||
|
||||
downgrade() {
|
||||
tui_exit
|
||||
dependency pacman fzf || return
|
||||
|
||||
pacman_cache_dir="$(pacman-conf CacheDir)"
|
||||
wrapper_cache_dir="$HOME/.cache/$wrapper"
|
||||
|
||||
files=$(find "$pacman_cache_dir" "$wrapper_cache_dir" -type f -name "*.pkg.tar.zst" -printf "%f\n" 2>/dev/null | fzf $fzf_settings)
|
||||
|
||||
if [[ -n "$files" ]]; then
|
||||
file_paths=()
|
||||
|
||||
for file in $files; do
|
||||
if [ -f "${pacman_cache_dir%/}/${file}" ]; then
|
||||
file_paths+=("${pacman_cache_dir%/}/${file}")
|
||||
else
|
||||
path=$(find "$wrapper_cache_dir" -type f -name "$file" 2>/dev/null)
|
||||
[[ -n "$path" ]] && file_paths+=("$path")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#file_paths[@]} -gt 0 ]; then
|
||||
cache="${file_paths[*]}"
|
||||
printExec "$wrapper_sudo -U $cache"
|
||||
$wrapper_sudo -U $cache
|
||||
fi
|
||||
|
||||
pressReturn
|
||||
fi
|
||||
|
||||
tui_restore
|
||||
}
|
||||
|
||||
cleanCache() {
|
||||
tui_exit
|
||||
dependency pacman || return
|
||||
|
||||
pacman_cache_dir="$(pacman-conf CacheDir | head -n1)"
|
||||
mapfile -t dirs < <(find "$pacman_cache_dir" -mindepth 1 -maxdepth 1 -type d)
|
||||
if ((${#dirs[@]})); then
|
||||
printImportant "$MNG_CACHE_DIR"
|
||||
printf ' %q\n' "${dirs[@]}"
|
||||
echo
|
||||
|
||||
while true; do
|
||||
printQuestion "$MNG_CACHE_DIR_RM"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*)
|
||||
[ -w "$pacman_cache_dir" ] && prefix="" || prefix="$sudoBin "
|
||||
cmd="${prefix}rm -rf"
|
||||
for dir in "${dirs[@]}"; do
|
||||
printf -v escaped '%q' "$dir"
|
||||
cmd+=" $escaped"
|
||||
done
|
||||
echo
|
||||
printExec "$cmd"
|
||||
if [ -n "$prefix" ]; then
|
||||
"$sudoBin" rm -rf "${dirs[@]}"
|
||||
else
|
||||
rm -rf "${dirs[@]}"
|
||||
fi
|
||||
break
|
||||
;;
|
||||
[Nn]*|"") break ;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo
|
||||
fi
|
||||
|
||||
printExec "$wrapper_sudo -$1"
|
||||
$wrapper_sudo -$1
|
||||
pressReturn
|
||||
tui_restore
|
||||
}
|
||||
|
||||
rebuildPython() {
|
||||
tui_exit
|
||||
|
||||
if [[ $wrapper = "pacman" ]]; then
|
||||
found=0
|
||||
for h in yay paru pikaur; do
|
||||
if command -v "$h" >/dev/null 2>&1; then
|
||||
wrapper_sudo="$h"
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$found" -eq 0 ]; then
|
||||
printError "$CMD_ERR: yay/paru/pikaur"
|
||||
pressReturn
|
||||
tui_restore
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
rebuild_dir=$(find /usr/lib -maxdepth 1 -type d -name "python*.*" | sort -V | tail -1)
|
||||
|
||||
rebuild_packages=$( { pacman -Qqo "$rebuild_dir" | pacman -Qqm - | oneLine; } 2>/dev/null)
|
||||
if [[ -z "$rebuild_packages" ]]; then
|
||||
printDone "$MNG_DONE"
|
||||
else
|
||||
printExec "$wrapper_sudo -S --rebuild" "$rebuild_packages"
|
||||
while true; do
|
||||
printQuestion "$MNG_RESUME"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) echo; break;;
|
||||
[Nn]*|"") echo; pressReturn;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
pacman -Qqo "$rebuild_dir" | pacman -Qqm - | $wrapper -S --rebuild -
|
||||
fi
|
||||
|
||||
pressReturn
|
||||
tui_restore
|
||||
}
|
||||
|
||||
recentlyInstalled() {
|
||||
tui_exit
|
||||
|
||||
echo "$MNG_RECENT_SCAN"
|
||||
|
||||
local log="/var/log/pacman.log"
|
||||
if [[ ! -f "$log" ]]; then
|
||||
printError "Pacman log file not found."
|
||||
pressReturn
|
||||
tui_restore
|
||||
return
|
||||
fi
|
||||
|
||||
local explicit_pkgs orphan_pkgs installed_pkgs
|
||||
installed_pkgs=$(pacman -Qq)
|
||||
explicit_pkgs=$(pacman -Qqe)
|
||||
orphan_pkgs=$(pacman -Qqtd 2>/dev/null)
|
||||
|
||||
declare -A pkg_date
|
||||
while IFS= read -r line; do
|
||||
[[ $line =~ ^\[([0-9-]+)T([0-9:]+)[^\]]*\]\ \[ALPM\]\ installed\ ([^\ ]+) ]] || continue
|
||||
pkg_date["${BASH_REMATCH[3]}"]="${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"
|
||||
done < "$log"
|
||||
|
||||
local output=""
|
||||
for pkg in "${!pkg_date[@]}"; do
|
||||
echo "$installed_pkgs" | grep -qx "$pkg" || continue
|
||||
|
||||
local status status_color
|
||||
if echo "$orphan_pkgs" | grep -qx "$pkg"; then
|
||||
status="$MNG_RECENT_ORPHAN"; status_color="31"
|
||||
elif echo "$explicit_pkgs" | grep -qx "$pkg"; then
|
||||
status="$MNG_RECENT_EXPLICIT"; status_color="32"
|
||||
else
|
||||
status="$MNG_RECENT_DEPEND"; status_color="33"
|
||||
fi
|
||||
|
||||
output+="${pkg_date[$pkg]}|${pkg}|${status}|${status_color}\n"
|
||||
done
|
||||
|
||||
printf '\e[H\e[2J\e[3J'
|
||||
{
|
||||
printf "%s\t%s\t%s\n" "$(colorize gray bold DATE)" "$(colorize gray bold PACKAGE)" "$(colorize gray bold TYPE)"
|
||||
echo -e "$output" | sort -t'|' -k1 | tail -100 | while IFS='|' read -r date pkg status color; do
|
||||
[[ -z "$pkg" ]] && continue
|
||||
printf "\e[90m%s\e[0m\t%s\t\e[%sm%s\e[0m\n" "$date" "$pkg" "$color" "$status"
|
||||
done
|
||||
} | column -t -s$'\t'
|
||||
|
||||
pressReturn
|
||||
tui_restore
|
||||
}
|
||||
|
||||
flatpakInstalled() {
|
||||
tui_exit
|
||||
dependency flatpak fzf || return
|
||||
|
||||
local apps=$(
|
||||
flatpak list --app --columns=application,name \
|
||||
| sort -t$'\t' -k2,2f -k1,1 \
|
||||
| fzf $fzf_settings -d$'\t' --with-nth=2 --preview 'flatpak info {1}'
|
||||
)
|
||||
|
||||
if [[ -n "$apps" ]]; then
|
||||
local ids=$(echo "$apps" | cut -f1 | oneLine)
|
||||
printExec "flatpak uninstall $ids"
|
||||
flatpak uninstall $ids
|
||||
|
||||
if [ "$flatpakRemoveUnused" = true ]; then
|
||||
echo
|
||||
printExec "flatpak uninstall --unused $flatpakFlags"
|
||||
flatpak uninstall --unused $flatpakFlags
|
||||
fi
|
||||
|
||||
pressReturn
|
||||
fi
|
||||
|
||||
tui_restore
|
||||
}
|
||||
|
||||
flatpakFlathub() {
|
||||
tui_exit
|
||||
dependency flatpak fzf curl jq || return
|
||||
|
||||
local apps=$(curl -s https://flathub.org/api/v2/appstream | jq -r '.[]' | fzf $fzf_settings --preview "flatpak remote-info flathub {}")
|
||||
|
||||
if [[ -n "$apps" ]]; then
|
||||
apps=$(echo "$apps" | oneLine)
|
||||
printExec "flatpak install $apps"
|
||||
flatpak install $apps
|
||||
pressReturn
|
||||
fi
|
||||
|
||||
tui_restore
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
pressReturn() {
|
||||
printf '\e[?25l'
|
||||
echo -e "\n$(colorize blue bold $ICO_RETURN $MNG_RETURN)"
|
||||
IFS= read -rs
|
||||
}
|
||||
|
||||
tui_exit() {
|
||||
reset_terminal
|
||||
clear_screen
|
||||
}
|
||||
|
||||
tui_restore() {
|
||||
clear_screen
|
||||
setup_terminal
|
||||
showMenu $menu_type
|
||||
}
|
||||
|
||||
dependency() {
|
||||
for cmd in "$@"; do
|
||||
if ! command -v "$cmd" >/dev/null; then
|
||||
printError "${CMD_ERR} ${cmd}"
|
||||
pressReturn
|
||||
tui_restore
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
get_term_size() {
|
||||
read -r LINES COLUMNS < <(stty size)
|
||||
}
|
||||
|
||||
setup_terminal() {
|
||||
printf '\e[?1049h\e[?7l\e[?25l\e[2J\e[H'
|
||||
stty -echo
|
||||
}
|
||||
|
||||
reset_terminal() {
|
||||
printf '\e[?7h\e[?25h\e[2J\e[;r\e[?1049l'
|
||||
stty echo
|
||||
}
|
||||
|
||||
clear_screen() {
|
||||
printf '\e[H\e[2J'
|
||||
}
|
||||
|
||||
main() {
|
||||
((BASH_VERSINFO[0] > 3)) && read_flags=(-t 0.05)
|
||||
|
||||
trap '' SIGINT
|
||||
trap 'reset_terminal' EXIT
|
||||
trap 'get_term_size; clear_screen; draw_menu' WINCH
|
||||
|
||||
get_term_size
|
||||
setup_terminal
|
||||
showMenu main
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
|
||||
|
||||
# \e[?1049h Alternative screen buffer
|
||||
# \e[?1049l Restore main screen buffer
|
||||
# \e[?7l Disable line wrapping
|
||||
# \e[?7h Enable line wrapping
|
||||
# \e[?25l Hide cursor
|
||||
# \e[?25h Show cursor
|
||||
# \e[2J Clear terminal
|
||||
# \e[H Move cursor to top-left
|
||||
# \e[;r Set the scroll region to its default value
|
||||
# stty -echo Hide user input
|
||||
# stty echo Show user input
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
UPGRADE_PROGRESS = i18n("Upgrade in progress")
|
||||
UPGRADE_FULL_ARCH = i18n("Full system upgrade")
|
||||
UPGRADE_FULL_FLATPAK = i18n("Flatpak Upgrade")
|
||||
UPGRADE_FULL_WIDGETS = i18n("Plasma Widgets Upgrade")
|
||||
UPGRADE_PACKAGE = i18n("Upgrade")
|
||||
UPGRADE_EXECTIME = i18n("Total execution time:")
|
||||
UPGRADE_CRITICAL = i18n("Critical package(s) updated, reboot may be required:")
|
||||
UPGRADE_REBOOT = i18n("Do you want to reboot now?")
|
||||
UPGRADE_REBOOTING = i18n("The system is going to reboot now...")
|
||||
UPGRADE_ENTER = i18n("Press Enter to close")
|
||||
UPGRADE_SKIPPED = i18n("Skipped...")
|
||||
UPGRADE_COMPLETE = i18n("Upgrade complete, close the terminal")
|
||||
|
||||
MIRRORS_FETCH = i18n("Fetching the latest filtered mirror list")
|
||||
MIRRORS_RANK = i18n("Ranking mirrors by their connection and opening speed")
|
||||
MIRRORS_ERR = i18n("Check your mirrorlist generator settings...")
|
||||
MIRRORS_UPD = i18n("was updated with the following servers:")
|
||||
MIRRORS_SUDO = i18n("To write to the mirrorlist file, sudo privileges are required")
|
||||
MIRROR_TIME = i18n("Your current mirrorlist:")
|
||||
MIRROR_REFRESH = i18n("Refresh mirror list?")
|
||||
|
||||
WIDGETS_WARN = i18n("For some widgets you may need to Log Out or restart plasmashell after upgrade")
|
||||
WIDGETS_CHECK = i18n("Checking widgets for updates")
|
||||
WIDGETS_FETCH = i18n("Fetching data from the API")
|
||||
WIDGETS_LINK = i18n("Getting the download link")
|
||||
WIDGETS_DOWNLOADING = i18n("Downloading package")
|
||||
WIDGETS_PROCEED = i18n("Proceed with upgrade?")
|
||||
WIDGETS_RESTART = i18n("Restart plasmashell now?")
|
||||
WIDGETS_NODESC = i18n("No description")
|
||||
WIDGETS_ERR = i18n("Unable check widgets: ")
|
||||
WIDGETS_ERR_API = i18n("Too many API requests in the last 15 minutes from your IP address, please try again later")
|
||||
WIDGETS_ERR_API_FAIL = i18n("Failed to retrieve data from the API")
|
||||
WIDGETS_ERR_UNKNOWN = i18n("Unkwnown error")
|
||||
WIDGETS_ERR_NO_JSON = i18n("File metadata.json not found")
|
||||
WIDGETS_ERR_JSON = i18n("Errors in metadata.json file")
|
||||
WIDGETS_ERR_EXT = i18n("Unsupported file format")
|
||||
WIDGETS_ERR_NOFILES = i18n("No files for download")
|
||||
WIDGETS_ERR_NOTAGGED = i18n("No file tagged with version")
|
||||
WIDGETS_ERR_TAGGED = i18n("Multiple files are tagged with version")
|
||||
WIDGETS_ERR_PACKAGE_FAIL = i18n("Failed to download the package")
|
||||
|
||||
MNG_HEADER = i18n("Management")
|
||||
MNG_BROWSE = i18n("Browse available")
|
||||
MNG_BROWSE_H = i18n("Search all available packages and select what to install");
|
||||
MNG_INST = i18n("Manage installed")
|
||||
MNG_INST_H = i18n("View installed packages and select what to remove")
|
||||
MNG_EXPL = i18n("Explicit installs")
|
||||
MNG_EXPL_H = i18n("Show packages you installed directly");
|
||||
MNG_EXPL_L = i18n("All explicit installs")
|
||||
MNG_EXPL_U = i18n("Explicit installs not required by anything")
|
||||
MNG_ORPH = i18n("Orphaned dependencies")
|
||||
MNG_ORPH_H = i18n("Dependencies that are no longer needed by anything installed");
|
||||
MNG_ORPH_L = i18n("List orphans")
|
||||
MNG_ORPH_R = i18n("Remove orphans")
|
||||
MNG_DOWNGR = i18n("Downgrade from cache")
|
||||
MNG_CACHE = i18n("Clean cache")
|
||||
MNG_CACHE_H = i18n("Remove cached files to free disk space");
|
||||
MNG_CACHE_A = i18n("Remove all cache")
|
||||
MNG_CACHE_U = i18n("Remove unused cache")
|
||||
MNG_CACHE_DIR = i18n("Found directories in the pacman cache:")
|
||||
MNG_CACHE_DIR_RM = i18n("Remove these directories?")
|
||||
MNG_REBUILD = i18n("Rebuild packages")
|
||||
MNG_REBUILD_H = i18n("Select packages that need reinstall with rebuild")
|
||||
MNG_REBUILD_A = i18n("Rebuild AUR packages")
|
||||
MNG_REBUILD_P = i18n("Detect Python packages to rebuild")
|
||||
MNG_MIRROR = i18n("Refresh mirrors")
|
||||
MNG_WARN = i18n("Review dependency warnings before removing")
|
||||
MNG_RESUME = i18n("Resume?")
|
||||
MNG_RETURN = i18n("Press Enter to return menu")
|
||||
MNG_RECENT = i18n("Recent installs")
|
||||
MNG_RECENT_SCAN = i18n("Scanning pacman log...")
|
||||
MNG_RECENT_EXPLICIT = i18n("Explicitly installed")
|
||||
MNG_RECENT_DEPEND = i18n("Installed as a dependency")
|
||||
MNG_RECENT_ORPHAN = i18n("Orphaned dependency")
|
||||
|
||||
MNG_EXEC = i18n("Executed:")
|
||||
MNG_DONE = i18n("Nothing to do")
|
||||
CMD_ERR = i18n("Required installed")
|
||||
NEWS_ERR = i18n("Cannot fetch news ")
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/utils"
|
||||
|
||||
[[ "$1" == "menu" ]] && MENU=1 || MENU=0
|
||||
selected=$2
|
||||
|
||||
returnMenu() {
|
||||
printf '\e[?25l'
|
||||
echo -e "\n$(colorize blue bold $ICO_RETURN $MNG_RETURN)"
|
||||
IFS= read -rs
|
||||
exec "$scriptDir/management" "$selected"
|
||||
}
|
||||
|
||||
rank_mirrors() {
|
||||
while IFS= read -r line <&3; do
|
||||
[[ "$line" =~ ^[[:space:]]*#?[[:space:]]*Server[[:space:]]*=[[:space:]]*(.+) ]] || continue
|
||||
|
||||
local server="${BASH_REMATCH[1]}"
|
||||
server="${server%%#*}"
|
||||
server="${server%"${server##*[![:space:]]}"}"
|
||||
|
||||
local domain="$server"
|
||||
if [[ "$server" =~ ^([^/]+//[^/]+) ]]; then
|
||||
domain="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
local url="${server%/}"
|
||||
url="${url//'$arch'/$(uname -m)}"
|
||||
url="${url//'$repo'/core}"
|
||||
url="${url}/core.db"
|
||||
|
||||
local response=$(curl -s -m 9 -w "%{time_total} %{http_code}" "$url" -o /dev/null 2>/dev/null)
|
||||
local exit_code=$?
|
||||
local time status
|
||||
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
status="Unreachable"
|
||||
else
|
||||
time="${response%% *}"
|
||||
local code="${response##* }"
|
||||
[[ -z "$code" || $code -lt 200 || $code -gt 299 ]] && status="Unreachable"
|
||||
fi
|
||||
|
||||
if [[ -n "$status" ]]; then
|
||||
if [ "$mirrorProgress" = true ]; then
|
||||
echo "$(colorize red bold $status) $domain" >&2
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$mirrorProgress" = true ]; then
|
||||
echo "$(colorize gray bold "$time" sec) $domain" >&2
|
||||
fi
|
||||
echo "$time $server" >> "$rankmirrors_time"
|
||||
done 3< "$rankmirrors_mirrorlist"
|
||||
|
||||
echo "## Artix Linux repository mirrorlist" > "$rankmirrors_mirrorlist"
|
||||
echo "## Generated by Antergos Apdatifier on $(date '+%Y-%m-%d %H:%M:%S')" >> "$rankmirrors_mirrorlist"
|
||||
|
||||
sort -n -o "$rankmirrors_time" "$rankmirrors_time"
|
||||
local i=0
|
||||
while IFS=' ' read -r time server; do
|
||||
[[ -n "$mirrorCount" && "$mirrorCount" -gt 0 && $i -ge "$mirrorCount" ]] && break
|
||||
echo "Server = $server" >> "$rankmirrors_mirrorlist"
|
||||
((i++))
|
||||
done < "$rankmirrors_time"
|
||||
}
|
||||
|
||||
validate() {
|
||||
! grep -qE '^[[:space:]]*#?[[:space:]]*Server[[:space:]]*=[[:space:]]*' "$1" && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
mirrorfile="/etc/pacman.d/mirrorlist"
|
||||
OUTDATED=false
|
||||
[[ ! -f "$mirrorfile" ]] && {
|
||||
NORM_TIME="(no file)"
|
||||
OUTDATED=true
|
||||
} || {
|
||||
FILE_TIME=$(date -r "$mirrorfile" +%s)
|
||||
NORM_TIME=$(date -d @"$FILE_TIME" +"%d %b %H:%M:%S")
|
||||
CURR_TIME=$(date +%s)
|
||||
[[ "$mirrors" == "age" && $MENU -eq 0 ]] && {
|
||||
(( CURR_TIME - FILE_TIME >= mirrorsAge * 86400 )) && OUTDATED=true || exit
|
||||
}
|
||||
}
|
||||
|
||||
[[ "$mirrors" != "force" || $MENU -eq 1 ]] && {
|
||||
printImportant "$MIRROR_TIME" "$NORM_TIME"
|
||||
while true; do
|
||||
printQuestion "$MIRROR_REFRESH"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) echo; break;;
|
||||
[Nn]*|"") (( MENU )) && { returnMenu; } || { echo; exit; };;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
rankmirrors_mirrorlist="$(mktemp)"
|
||||
rankmirrors_time="$(mktemp)"
|
||||
|
||||
trap 'rm -f "$rankmirrors_mirrorlist" "$rankmirrors_time"' INT TERM EXIT
|
||||
|
||||
curl -s -m 10 -o "$rankmirrors_mirrorlist" "$dynamicUrl" 2>/dev/null & printWhile $! "$MIRRORS_FETCH"
|
||||
if ! validate "$rankmirrors_mirrorlist"; then
|
||||
printError "$MIRRORS_ERR"
|
||||
printError "No server entries found."
|
||||
(( MENU )) && returnMenu || exit
|
||||
fi
|
||||
|
||||
if [ "$mirrorProgress" = true ]; then
|
||||
echo -e "$(colorize blue bold "$ICO_MIRRORS $MIRRORS_RANK")"
|
||||
rank_mirrors
|
||||
else
|
||||
rank_mirrors & printWhile $! "$MIRRORS_RANK"
|
||||
fi
|
||||
|
||||
if ! validate "$rankmirrors_mirrorlist"; then
|
||||
(( MENU )) && returnMenu || exit
|
||||
fi
|
||||
|
||||
${sudoBin} -n true 2>/dev/null || { echo; printImportant "$MIRRORS_SUDO"; }
|
||||
cat "$rankmirrors_mirrorlist" | ${sudoBin} tee "$mirrorfile" > /dev/null
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
printDone "$mirrorfile $MIRRORS_UPD"
|
||||
echo -e "$y$(tail -n +3 "$mirrorfile" | sed 's/Server = //g')$c\n"
|
||||
(( MENU )) && returnMenu
|
||||
else
|
||||
printError "$MIRRORS_SUDO"
|
||||
(( MENU )) && returnMenu || exit
|
||||
fi
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/utils"
|
||||
|
||||
declare -A termArg=( ["gnome-terminal"]="--" ["ptyxis"]="--" ["terminator"]="-x" )
|
||||
name="${1^}"
|
||||
sess="Apdatifier"
|
||||
term="$(basename "$terminal")"
|
||||
termArg="${termArg[$term]:-"-e"}"
|
||||
scr="$(dirname "$0")/$1"
|
||||
arg="${@:2}"
|
||||
|
||||
_attach="tmux attach-session -t $sess \; select-window -t $name"
|
||||
_script="$scr $arg"
|
||||
|
||||
[[ $tmuxSession = "true" && -x $(command -v tmux) ]] && _TMUX=1
|
||||
|
||||
run_terminal() {
|
||||
"$term" "$termArg" bash -c "$1"
|
||||
}
|
||||
run_yakuake() {
|
||||
qdbusCMD sessions runCommandInTerminal "$session" "bash -c '$1'"
|
||||
}
|
||||
create_tmux_session() {
|
||||
! tmux has-session -t "$sess" 2>/dev/null && {
|
||||
tmux new-session -d -s "$sess" -n "$name" "bash -c '$scr $arg'"
|
||||
} || {
|
||||
! tmux list-windows -t "$sess" | grep -wq "$name" && {
|
||||
tmux new-window -t "$sess:" -n "$name" "bash -c '$scr $arg'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if [[ $term = "yakuake" ]]; then
|
||||
session=$(qdbusCMD sessions addSession)
|
||||
visible=$(qdbusCMD MainWindow_1 org.qtproject.Qt.QWidget.visible)
|
||||
qdbusCMD tabs org.kde.yakuake.setTabTitle "$session" "$name"
|
||||
if (( _TMUX )); then
|
||||
create_tmux_session
|
||||
run_yakuake "$_attach"
|
||||
else
|
||||
run_yakuake "tput sc; clear; $_script"
|
||||
fi
|
||||
[[ $visible = "false" ]] && qdbusCMD window org.kde.yakuake.toggleWindowState
|
||||
elif (( _TMUX )); then
|
||||
create_tmux_session
|
||||
run_terminal "$_attach"
|
||||
else
|
||||
run_terminal "$_script"
|
||||
fi
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/utils"
|
||||
|
||||
trap 'echo -e "\n\n$(colorize red bold $ICO_WARN "CTRL+C. $UPGRADE_SKIPPED")"' SIGINT
|
||||
|
||||
run() {
|
||||
[[ -z $2 ]] && printExec "$1"
|
||||
|
||||
if [ "$idleInhibit" = true ] && command -v systemd-inhibit >/dev/null 2>&1; then
|
||||
read -ra cmd <<< "$1"
|
||||
systemd-inhibit --what=idle:sleep:shutdown --who="Apdatifier" --why="$UPGRADE_PROGRESS" "${cmd[@]}"
|
||||
else
|
||||
eval ${1}
|
||||
fi
|
||||
}
|
||||
|
||||
printClose() {
|
||||
tput civis
|
||||
printMsg "$UPGRADE_ENTER"
|
||||
|
||||
while ! read -t 30 -r; do
|
||||
if command -v gdbus >/dev/null; then
|
||||
gdbus call --session \
|
||||
--dest org.kde.plasmashell \
|
||||
--object-path /org/kde/osdService \
|
||||
--method org.kde.osdService.showText \
|
||||
apdatifier-plasmoid "$UPGRADE_COMPLETE" >/dev/null 2>&1
|
||||
fi
|
||||
done
|
||||
|
||||
tput cnorm
|
||||
[[ $(basename "$terminal") = "yakuake" ]] && qdbusCMD sessions removeSession "$(qdbusCMD sessions activeSessionId)"
|
||||
}
|
||||
|
||||
bin="$sudoBin pacman"; [ "$aur" = true ] && bin=$wrapper
|
||||
|
||||
fullSystemUpgrade() {
|
||||
startTime=$(date +%s)
|
||||
critical_updated=()
|
||||
critical_installed=()
|
||||
critical_packages=(
|
||||
"amd-ucode"
|
||||
"intel-ucode"
|
||||
"cryptsetup"
|
||||
"linux"
|
||||
"linux-hardened"
|
||||
"linux-lts"
|
||||
"linux-zen"
|
||||
"linux-rt"
|
||||
"linux-rt-lts"
|
||||
"linux-firmware*"
|
||||
"linux-cachyos*"
|
||||
"linux-cacule*"
|
||||
"nvidia"
|
||||
"nvidia-dkms"
|
||||
"nvidia-*xx-dkms"
|
||||
"nvidia-*xx"
|
||||
"nvidia-*lts-dkms"
|
||||
"nvidia*-lts"
|
||||
"mkinitcpio*"
|
||||
"booster*"
|
||||
"mesa"
|
||||
"systemd*"
|
||||
"wayland"
|
||||
"virtualbox-guest-utils"
|
||||
"virtualbox-host-dkms"
|
||||
"virtualbox-host-modules-arch"
|
||||
"egl-wayland"
|
||||
"xf86-video-*"
|
||||
"xorg-server*"
|
||||
"xorg-fonts*"
|
||||
"winesync-dkms"
|
||||
)
|
||||
|
||||
if [ "$arch" = true ]; then
|
||||
printMsg "$UPGRADE_FULL_ARCH"
|
||||
echo
|
||||
|
||||
[ "$mirrors" != "false" ] && "$scriptDir"/mirrorlist
|
||||
|
||||
if [ -n "$preExec" ]; then
|
||||
run "$preExec"
|
||||
printf '\n\033[32m'; printf '%.0s:' {1..48}; printf '\033[0m\n\n'
|
||||
fi
|
||||
|
||||
if [ "$rebootSystem" = true ]; then
|
||||
for package in "${critical_packages[@]}"; do
|
||||
matches=$(pacman -Qq | grep -E "^${package//\*/.*}$")
|
||||
[ -n "$matches" ] && critical_installed+=($matches)
|
||||
done
|
||||
|
||||
declare -A beforeVersions
|
||||
for pkg in "${critical_installed[@]}"; do
|
||||
beforeVersions[$pkg]=$(pacman -Q $pkg | awk '{print $2}')
|
||||
done
|
||||
fi
|
||||
|
||||
test -f "$configDir/env.sh" && source "$configDir/env.sh"
|
||||
|
||||
ignorePkgs="$@"
|
||||
run "$bin -Syu $archFlags $ignorePkgs"
|
||||
|
||||
for pkg in "${critical_installed[@]}"; do
|
||||
afterVersion=$(pacman -Q $pkg | awk '{print $2}')
|
||||
if [ "${beforeVersions[$pkg]}" != "$afterVersion" ]; then
|
||||
critical_updated+=("$pkg")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$flatpak" = true ]; then
|
||||
printMsg "$UPGRADE_FULL_FLATPAK"
|
||||
echo
|
||||
|
||||
run "flatpak update $flatpakFlags"
|
||||
|
||||
if [ "$flatpakRemoveUnused" = true ]; then
|
||||
echo
|
||||
run "flatpak uninstall --unused $flatpakFlags"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$widgets" = true ]; then
|
||||
printMsg "$UPGRADE_FULL_WIDGETS"
|
||||
echo
|
||||
run "$scriptDir/widgets upgradeAll" noPrint
|
||||
fi
|
||||
|
||||
if [ "$fwupd" = true ]; then
|
||||
printMsg "fwupdmgr"
|
||||
echo
|
||||
run "$sudoBin fwupdmgr update"
|
||||
fi
|
||||
|
||||
if [ -n "$postExec" ]; then
|
||||
printf '\n\033[32m'; printf '%.0s:' {1..48}; printf '\033[0m\n\n'
|
||||
run "$postExec"
|
||||
fi
|
||||
|
||||
trap ' ' SIGINT
|
||||
|
||||
endTime=$(date +%s)
|
||||
runTime=$((endTime-startTime))
|
||||
echo; echo
|
||||
printImportant "$UPGRADE_EXECTIME" "$(printf "%02dh:%02dm:%02ds" $((runTime / 3600)) $(( (runTime % 3600) / 60 )) $((runTime % 60)))"
|
||||
|
||||
if [ ${#critical_updated[@]} -gt 0 ]; then
|
||||
echo
|
||||
printImportant "$UPGRADE_CRITICAL" "${critical_updated[*]}"
|
||||
while true; do
|
||||
printQuestion "$UPGRADE_REBOOT"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) sys_reboot; exit;;
|
||||
[Nn]*|"") break;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
printClose
|
||||
}
|
||||
|
||||
sys_reboot() {
|
||||
tput civis
|
||||
printMsg "$UPGRADE_REBOOTING"
|
||||
|
||||
read -s -t 2
|
||||
|
||||
qdbus6 org.kde.Shutdown /Shutdown logoutAndReboot
|
||||
}
|
||||
|
||||
flatpak_package() {
|
||||
printMsg "$UPGRADE_PACKAGE: $2"
|
||||
echo
|
||||
|
||||
printExec "flatpak update" "$1 $flatpakFlags"
|
||||
|
||||
flatpak update $1 $flatpakFlags
|
||||
|
||||
printClose
|
||||
}
|
||||
|
||||
widget_package() {
|
||||
printMsg "$UPGRADE_PACKAGE: $2"
|
||||
echo
|
||||
|
||||
"$scriptDir"/widgets upgrade "$1" "$2"
|
||||
|
||||
printClose
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
"full") shift; fullSystemUpgrade $@ ;;
|
||||
"flatpak") shift; flatpak_package $1 $2;;
|
||||
"widget") shift; widget_package $1 $2;;
|
||||
*) exit;;
|
||||
esac
|
||||
@@ -0,0 +1,524 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/vars"
|
||||
|
||||
cleanup() {
|
||||
[ -d "$tempDir" ] && rm -rf "$tempDir"
|
||||
}
|
||||
|
||||
makeTempDir() {
|
||||
cleanup
|
||||
mkdir "$tempDir"
|
||||
}
|
||||
|
||||
colorize() {
|
||||
local red="\033[31m"
|
||||
local green="\033[32m"
|
||||
local blue="\033[34m"
|
||||
local yellow="\033[33m"
|
||||
local white="\033[37m"
|
||||
local gray="\e[90m"
|
||||
local clear="\033[0m"
|
||||
local bold="\033[1m"
|
||||
|
||||
local color="$1"; shift
|
||||
local style=""
|
||||
|
||||
if [[ "$1" == "bold" ]]; then
|
||||
style="${!1}"
|
||||
shift
|
||||
fi
|
||||
|
||||
echo -ne "${!color}${style}$*${clear}"
|
||||
}
|
||||
|
||||
printDone() {
|
||||
echo -e "$(colorize green bold "$ICO_DONE $1")"
|
||||
}
|
||||
|
||||
printError() {
|
||||
echo -e "$(colorize red bold "$ICO_ERR $1")"
|
||||
}
|
||||
|
||||
printImportant() {
|
||||
echo -e "$(colorize yellow bold "$ICO_WARN $1") $2"
|
||||
}
|
||||
|
||||
printQuestion() {
|
||||
echo -en "$(colorize yellow bold "$ICO_QUESTION $1") [y/N]: "
|
||||
}
|
||||
|
||||
printExec() {
|
||||
echo -e "$(colorize blue bold "$ICO_EXEC $MNG_EXEC") $(colorize white $1) \n"
|
||||
}
|
||||
|
||||
printMsg() {
|
||||
local text="$1"
|
||||
local padding=$(( (48 - ${#text} - 2) / 2 ))
|
||||
local l=$(printf ":%.0s" {1..48})
|
||||
local s=$(printf ":%.0s" $(seq 1 $padding))
|
||||
local p=${s}$( (( ${#text} % 2 )) && echo ":" )
|
||||
|
||||
echo
|
||||
echo -e "$(colorize green bold $l)"
|
||||
echo -e "$(colorize green bold $s) $(colorize white bold $text) $(colorize green bold $p)"
|
||||
echo -e "$(colorize green bold $l)"
|
||||
}
|
||||
|
||||
printClose() {
|
||||
tput civis
|
||||
printMsg "$UPGRADE_ENTER"
|
||||
read -r
|
||||
tput cnorm
|
||||
[[ $(basename "$terminal") = "yakuake" ]] && qdbusCMD sessions removeSession "$(qdbusCMD sessions activeSessionId)"
|
||||
}
|
||||
|
||||
printWhile() {
|
||||
tput sc
|
||||
pid=$1
|
||||
spinner $pid "$2"
|
||||
wait $pid
|
||||
exitCode=$?
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
[[ $exitCode -eq 0 ]] && printDone "$2" || printError "$2"
|
||||
|
||||
if [[ "$2" = "$WIDGETS_FETCH" ]]; then
|
||||
case $exitCode in
|
||||
1) printError "$WIDGETS_ERR_API_FAIL" ;;
|
||||
2) printError "$WIDGETS_ERR_API" ;;
|
||||
3) printError "$WIDGETS_ERR_UNKNOWN" ;;
|
||||
esac
|
||||
elif [[ "$2" = "$WIDGETS_LINK" ]]; then
|
||||
case $exitCode in
|
||||
1) printError "$WIDGETS_ERR_NOFILES. $UPGRADE_SKIPPED" ;;
|
||||
2) printError "$WIDGETS_ERR_NOTAGGED $latestVer. $UPGRADE_SKIPPED" ;;
|
||||
3) printError "$WIDGETS_ERR_TAGGED $latestVer. $UPGRADE_SKIPPED" ;;
|
||||
esac
|
||||
elif [[ "$2" = "$WIDGETS_DOWNLOADING" ]]; then
|
||||
case $exitCode in
|
||||
1) printError "$WIDGETS_ERR_PACKAGE_FAIL" ;;
|
||||
2) printError "$WIDGETS_ERR_EXT" ;;
|
||||
3) printError "$WIDGETS_ERR_NO_JSON" ;;
|
||||
4) printError "$WIDGETS_ERR_JSON" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
qdbusCMD() {
|
||||
qdbus6 org.kde.yakuake /yakuake/$1 "${@:2}"
|
||||
}
|
||||
|
||||
dependencies() {
|
||||
for cmd in ${1}; do
|
||||
if ! command -v "$cmd" >/dev/null; then
|
||||
printError "${CMD_ERR} ${cmd}"
|
||||
[ $2 ] && returnMenu || exit
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
oneLine() {
|
||||
tr '\n' ' ' | sed 's/ $//'
|
||||
}
|
||||
|
||||
spinner() {
|
||||
local spin="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
||||
while kill -0 $1 2>/dev/null; do
|
||||
i=$(( (i+1) %10 ))
|
||||
printf "\r$(colorize red ${spin:$i:1}) $(colorize blue bold "$2...")"
|
||||
sleep .2
|
||||
done
|
||||
}
|
||||
|
||||
clearVer() {
|
||||
local ver="${1}"
|
||||
ver="${ver//[^0-9.]/.}"
|
||||
while [[ "$ver" == *".."* ]]; do
|
||||
ver="${ver//../.}"
|
||||
done
|
||||
ver="${ver#.}"
|
||||
ver="${ver%.}"
|
||||
echo "${ver}"
|
||||
}
|
||||
|
||||
compareVer() {
|
||||
[[ $1 == $2 ]] && return 0
|
||||
local IFS=.; local i ver1=($1) ver2=($2)
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do ver1[i]=0; done
|
||||
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||
[[ -z ${ver2[i]} ]] && ver2[i]=0
|
||||
((10#${ver1[i]} > 10#${ver2[i]})) && return 1
|
||||
((10#${ver1[i]} < 10#${ver2[i]})) && return 2
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
getId() {
|
||||
[[ $1 =~ ^[0-9]+$ ]] && f=2 || f=1
|
||||
grep "$1" "$(dirname "$0")/widgets-id" | cut -d' ' -f$f
|
||||
}
|
||||
|
||||
restartPlasmashell() {
|
||||
if [[ "$restartShell" = true ]]; then
|
||||
sleep 1
|
||||
while true; do
|
||||
printQuestion "$WIDGETS_RESTART"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) break;;
|
||||
[Nn]*|"") exit;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
eval ${restartCommand}
|
||||
else
|
||||
printImportant "$WIDGETS_WARN"
|
||||
fi
|
||||
}
|
||||
|
||||
combineFiles() {
|
||||
sleep 1
|
||||
[ -f "$1" ] && rm "$1"
|
||||
for cache in $(find "$cacheDir" -name "updates.json_*" 2>/dev/null | sort); do
|
||||
cat "$cache" >> "$1"; rm "$cache"
|
||||
done
|
||||
}
|
||||
|
||||
getIcons() {
|
||||
while read -r pkg file; do
|
||||
[[ "$processed" == *"$pkg"* ]] && continue
|
||||
icon=$(awk -F= '/^Icon=/ {print $2; exit}' "$file" 2>/dev/null || true) && [ -n "$icon" ] || continue
|
||||
processed="$processed $pkg"
|
||||
echo "$pkg $icon"
|
||||
done < <(pacman -Ql "$@" | grep '/usr/share/applications/.*\.desktop$')
|
||||
}
|
||||
|
||||
updateJson() {
|
||||
cat <<< "$2" > "$1.tmp" && mv "$1.tmp" "$1"
|
||||
}
|
||||
|
||||
rss() {
|
||||
command -v jq >/dev/null || { echo "${CMD_ERR} jq" >&2; return 127; }
|
||||
|
||||
# Wait for DNS to be ready (up to 30s) — avoids failures at boot/resume
|
||||
local host=$(echo "$1" | sed -e 's|^[^/]*//||' -e 's|/.*$||')
|
||||
for i in $(seq 1 30); do
|
||||
getent hosts "$host" >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
makeTempDir
|
||||
trap cleanup EXIT
|
||||
|
||||
local newsFile="$cacheDir/news.json"
|
||||
local errors=""
|
||||
|
||||
if [[ ! -s "$newsFile" ]] \
|
||||
|| ! jq -e '.' "$newsFile" >/dev/null 2>&1 \
|
||||
|| ! jq -e 'all(.[]; .timestamp | numbers)' "$newsFile" >/dev/null 2>&1; then
|
||||
echo '[]' > "$newsFile"
|
||||
fi
|
||||
|
||||
# Build per-URL output file list for parallel download
|
||||
local -a urls=("$@")
|
||||
local -a rssFiles=()
|
||||
local -a curlArgs=()
|
||||
for url in "${urls[@]}"; do
|
||||
local rssFile=$(mktemp "$tempDir/XXXXXX.xml")
|
||||
rssFiles+=("$rssFile")
|
||||
curlArgs+=(-o "$rssFile" --url "$url")
|
||||
done
|
||||
|
||||
# Fetch all feeds simultaneously
|
||||
curl -s --parallel --parallel-immediate \
|
||||
--connect-timeout 5 --retry 3 --retry-delay 1 --retry-all-errors --max-time 15 \
|
||||
"${curlArgs[@]}" 2>/dev/null
|
||||
|
||||
# Process each downloaded file
|
||||
for i in "${!urls[@]}"; do
|
||||
local url="${urls[$i]}"
|
||||
local rssFile="${rssFiles[$i]}"
|
||||
|
||||
# Strip default XML namespace to avoid XPath 1.0 namespace issues
|
||||
local cleanFile=$(mktemp "$tempDir/XXXXXX.xml")
|
||||
sed 's/ xmlns="[^"]*"//g' "$rssFile" > "$cleanFile"
|
||||
|
||||
xmllint --noout "$cleanFile" 2>/dev/null || {
|
||||
errors+="$url "
|
||||
continue
|
||||
}
|
||||
|
||||
# Detect RSS 2.0 vs Atom 1.0 by root element name
|
||||
local root=$(xmllint --xpath "name(/*)" "$cleanFile" 2>/dev/null)
|
||||
|
||||
# Count available items in feed (capped at feedsFetchCount)
|
||||
local count title
|
||||
if [[ "$root" == "feed" ]]; then
|
||||
count=$(xmllint --xpath "count(/feed/entry)" "$cleanFile" 2>/dev/null || echo 0)
|
||||
title=$(xmllint --xpath "string(/feed/title)" "$cleanFile")
|
||||
else
|
||||
count=$(xmllint --xpath "count(/rss/channel/item)" "$cleanFile" 2>/dev/null || echo 0)
|
||||
title=$(xmllint --xpath "string(/rss/channel/title)" "$cleanFile")
|
||||
fi
|
||||
|
||||
[[ -z "$title" ]] && { errors+="$url "; continue; }
|
||||
|
||||
local max=${feedsFetchCount:-3}
|
||||
(( count > max )) && count=$max
|
||||
|
||||
local n
|
||||
for (( n=1; n<=count; n++ )); do
|
||||
local article timestamp link
|
||||
if [[ "$root" == "feed" ]]; then
|
||||
article=$(xmllint --xpath "string(/feed/entry[$n]/title)" "$cleanFile")
|
||||
timestamp=$(date -d "$(xmllint --xpath "string(/feed/entry[$n]/updated)" "$cleanFile")" +%s 2>/dev/null)
|
||||
link=$(xmllint --xpath "string(/feed/entry[$n]/link/@href)" "$cleanFile")
|
||||
else
|
||||
article=$(xmllint --xpath "string(/rss/channel/item[$n]/title)" "$cleanFile")
|
||||
timestamp=$(date -d "$(xmllint --xpath "string(/rss/channel/item[$n]/pubDate)" "$cleanFile")" +%s 2>/dev/null)
|
||||
link=$(xmllint --xpath "string(/rss/channel/item[$n]/link)" "$cleanFile")
|
||||
fi
|
||||
|
||||
[[ -z "$article" || -z "$timestamp" || -z "$link" ]] && continue
|
||||
|
||||
if ! jq -e --arg l "$link" '.[] | select(.link == $l)' "$newsFile" > /dev/null; then
|
||||
local item=$(jq -n --arg t "$title" --arg a "$article" --arg l "$link" --argjson ts "$timestamp" \
|
||||
'{title: $t, article: $a, timestamp: $ts, link: $l, removed: false}')
|
||||
updateJson "$newsFile" "$(jq --argjson item "$item" '[$item] + .' "$newsFile")"
|
||||
fi
|
||||
done
|
||||
|
||||
# Prune oldest entries for this feed down to feedsFetchCount
|
||||
while (( $(jq --arg t "$title" '[.[] | select(.title == $t)] | length' "$newsFile") > max )); do
|
||||
index=$(jq --arg t "$title" 'to_entries | reverse | map(select(.value.title == $t)) | .[0].key' "$newsFile")
|
||||
updateJson "$newsFile" "$(jq --argjson index "$index" 'del(.[$index])' "$newsFile")"
|
||||
done
|
||||
done
|
||||
|
||||
updateJson "$newsFile" "$(jq 'sort_by(.timestamp) | reverse' "$newsFile")"
|
||||
|
||||
jq . $newsFile
|
||||
|
||||
if [[ -n "$errors" ]]; then
|
||||
errors="$NEWS_ERR: ${errors// /, }"
|
||||
errors="${errors%, }"
|
||||
echo "$errors" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_dbs() {
|
||||
! type -p fakeroot >/dev/null && {
|
||||
echo "fakeroot not found" >&2
|
||||
exit 127
|
||||
}
|
||||
|
||||
! mkdir -p "$dbPath" && {
|
||||
echo "Failed to create directory $dbPath" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
db_conf="$(pacman-conf DBPath)"
|
||||
db_conf="${db_conf%/}"
|
||||
! ln -sf "${db_conf}/local" "$dbPath" &> /dev/null && {
|
||||
echo "Failed to create symlink to $db_conf/local" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
test -f "$dbPath/db.lck" && rm -f "$dbPath/db.lck"
|
||||
|
||||
! fakeroot -- pacman -Sy --disable-sandbox-filesystem --dbpath "$dbPath" --logfile /dev/null 2>/dev/null && {
|
||||
echo "Failed to synchronize pacman databases" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
checkCurrentVersions() {
|
||||
local list
|
||||
|
||||
if [ "$arch" = true ]; then
|
||||
list+=$(pacman -Q | awk '{print "{\"NM\": \"" $1 "\", \"VO\": \"" $2 "\"},"}')
|
||||
fi
|
||||
|
||||
if [ "$flatpak" = true ]; then
|
||||
list+=$(flatpak list --app --columns=name,version,active | awk -F'\t' '{print "{\"NM\": \"" $1 "\", \"VO\": \"" $2 $3 "\"},"}')
|
||||
fi
|
||||
|
||||
if [ "$widgets" = true ]; then
|
||||
list+=$("$scriptDir"/widgets list)
|
||||
fi
|
||||
|
||||
if [ "$fwupd" = true ]; then
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
list+=$(fwupdmgr get-devices --json | jq -cr '.Devices | map({NM: ((.Vendor + " " + .Name)), VO: (.Version // "")}) | .[] | @json + ","')
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[${list%,}]"
|
||||
}
|
||||
|
||||
checkSetup() {
|
||||
if (( systemWide )); then
|
||||
printError "Operation not permitted - the widget is installed system-wide"
|
||||
printClose
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
install_devel() {
|
||||
checkSetup
|
||||
|
||||
printMsg "Downloading devel"
|
||||
echo
|
||||
|
||||
required="git jq"
|
||||
for cmd in ${required}; do command -v "$cmd" >/dev/null || { printError "Required installed ${cmd}"; printClose; exit; }; done;
|
||||
|
||||
commit=$(curl -s https://api.github.com/repos/exequtic/apdatifier/commits/main | jq -r '.sha' | cut -c1-7)
|
||||
|
||||
if [[ "github commit: $commit" = "$(jq -r '.KPlugin.Description' "$appletDir/metadata.json")" ]]; then
|
||||
printDone "Up to date"
|
||||
printClose
|
||||
exit
|
||||
fi
|
||||
|
||||
savedir=$(pwd)
|
||||
clonedir=$(mktemp -d)
|
||||
cd "$clonedir"
|
||||
|
||||
{ git clone -n --depth=10 --filter=tree:0 -b main https://github.com/exequtic/apdatifier 2>/dev/null
|
||||
} & printWhile $! "Clone main branch"
|
||||
[[ $exitCode -ne 0 ]] && { printClose; exit; }
|
||||
|
||||
cd apdatifier
|
||||
|
||||
{ git sparse-checkout set --no-cone package 2>/dev/null && git checkout 2>/dev/null
|
||||
} & printWhile $! "Checkout package directory"
|
||||
[[ $exitCode -ne 0 ]] && { printClose; exit; }
|
||||
|
||||
if command -v less &>/dev/null; then
|
||||
echo
|
||||
printImportant "Last 10 commits:"
|
||||
git log --oneline
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo
|
||||
printMsg "Upgrading Apdatifier"
|
||||
echo
|
||||
cd package || exit 1
|
||||
updateJson metadata.json "$(jq --arg new_value "github commit: $commit" '.KPlugin.Description = $new_value' metadata.json)"
|
||||
[[ $trayEnabledByDefault == "true" ]] && updateJson $metadata "$(jq '.KPlugin.EnabledByDefault = true' $metadata)"
|
||||
while true; do
|
||||
printQuestion "Do you want build translations?"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) cd translate; bash build; cd ..; break;;
|
||||
[Nn]*|"") break;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
echo
|
||||
tar --exclude=./apdatifier.tar -cf apdatifier.tar .
|
||||
kpackagetool6 -t Plasma/Applet -u apdatifier.tar 2>/dev/null
|
||||
echo
|
||||
|
||||
cd "$savedir"
|
||||
[ ! -d "$clonedir" ] || rm -rf "$clonedir"
|
||||
|
||||
restartShell=true
|
||||
restartPlasmashell
|
||||
|
||||
printClose
|
||||
exit
|
||||
}
|
||||
|
||||
install_stable() {
|
||||
checkSetup
|
||||
|
||||
if [[ "$(jq -r '.KPlugin.Description' "$appletDir/metadata.json")" = "Arch Update Notifier" ]]; then
|
||||
while true; do
|
||||
printImportant "It looks like you already have the stable version."
|
||||
printQuestion "Do you still want to proceed?"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) break;;
|
||||
[Nn]*|"") printClose; exit;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
$scriptDir/upgrade widget "2135796" "apdatifier"
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
checkSetup
|
||||
|
||||
iconsDir="$localDir/icons/breeze/status/22"
|
||||
notifDir="$localDir/knotifications6"
|
||||
notif="apdatifier.notifyrc"
|
||||
icons=(apdatifier-plasmoid.svg
|
||||
apdatifier-packages.svg
|
||||
apdatifier-package.svg
|
||||
apdatifier-flatpak.svg)
|
||||
|
||||
printMsg "Uninstall Apdatifier"
|
||||
echo
|
||||
|
||||
while true; do
|
||||
printQuestion "Continue?"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) break;;
|
||||
[Nn]*|"") printClose; exit;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
showCommand() {
|
||||
echo -e "$(colorize yellow bold "$ICO_EXEC Command:") $1"
|
||||
}
|
||||
|
||||
printImportant "Remove icons"
|
||||
for icon in "${icons[@]}"; do
|
||||
showCommand "rm $iconsDir/$icon"
|
||||
test -f "$iconsDir/$icon" && rm "$iconsDir/$icon"
|
||||
done
|
||||
showCommand "rmdir -p --ignore-fail-on-non-empty $iconsDir"
|
||||
test -d "$iconsDir" && rmdir -p --ignore-fail-on-non-empty "$iconsDir"
|
||||
sleep 1; echo
|
||||
|
||||
printImportant "Remove notification"
|
||||
showCommand "rm $notifDir/$notif"
|
||||
test -f "$notifDir/$notif" && rm "$notifDir/$notif"
|
||||
showCommand "rmdir -p --ignore-fail-on-non-empty $notifDir"
|
||||
test -d "$notifDir" && rmdir -p --ignore-fail-on-non-empty "$notifDir"
|
||||
sleep 1; echo
|
||||
|
||||
printImportant "Remove configuration"
|
||||
showCommand "rm -rf $configDir"
|
||||
test -d "$configDir" && rm -rf "$configDir"
|
||||
sleep 1; echo
|
||||
|
||||
printImportant "Uninstall plasmoid"
|
||||
showCommand "kpackagetool6 -t Plasma/Applet -r com.github.exequtic.apdatifier"
|
||||
kpackagetool6 -t Plasma/Applet -r com.github.exequtic.apdatifier 2>/dev/null
|
||||
sleep 1; echo
|
||||
|
||||
echo "Bye :("; sleep 1
|
||||
|
||||
printClose
|
||||
exit
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
"syncdb") sync_dbs ;;
|
||||
"rss") shift; rss "$@" ;;
|
||||
"combineFiles") shift; combineFiles $1 ;;
|
||||
"currentVersions") checkCurrentVersions ;;
|
||||
"getIcons") shift; getIcons "$@" ;;
|
||||
"installDev") install_devel ;;
|
||||
"installStable") install_stable ;;
|
||||
"uninstall") uninstall ;;
|
||||
esac
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
scriptDir="$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")"
|
||||
appletDir="${scriptDir%/contents/*}"
|
||||
[[ "$appletDir" == /home/*/.local* ]]; systemWide=$?
|
||||
|
||||
configDir="${APDATIFIER_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/apdatifier}"
|
||||
cacheDir="${APDATIFIER_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/apdatifier}"
|
||||
localDir="${XDG_LOCAL_HOME:-$HOME/.local/share}"
|
||||
tempDir="${TMPDIR:-/tmp}/apdatifier"
|
||||
config="$configDir/config.conf"
|
||||
|
||||
export TEXTDOMAINDIR="$scriptDir/../../locale"
|
||||
export TEXTDOMAIN="plasma_applet_com.github.exequtic.apdatifier"
|
||||
|
||||
source "$config"
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
key="${line%%=*}"
|
||||
key="${key%% *}"
|
||||
value="${line#*=}"
|
||||
text=$(echo "$value" | grep -oP '(?<=\().*(?=\))' | sed 's/"//g')
|
||||
[[ -n "$key" && -n "$text" ]] && eval "$key=\"\$(gettext \"$text\")\""
|
||||
done < "$scriptDir/messages"
|
||||
|
||||
if [[ $termFont = "true" ]]; then
|
||||
ICO_ERR=""
|
||||
ICO_DONE=""
|
||||
ICO_WARN=""
|
||||
ICO_QUESTION=""
|
||||
ICO_EXEC=""
|
||||
ICO_MIRRORS=""
|
||||
ICO_RETURN=""
|
||||
ICO_SELECT=""
|
||||
else
|
||||
ICO_ERR="✘"
|
||||
ICO_DONE="✔"
|
||||
ICO_WARN="::"
|
||||
ICO_QUESTION="::"
|
||||
ICO_EXEC="::"
|
||||
ICO_MIRRORS="::"
|
||||
ICO_RETURN="<<"
|
||||
ICO_SELECT=">"
|
||||
fi
|
||||
@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/utils"
|
||||
|
||||
fetch_api_data() {
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?><data>' > "$XML"
|
||||
|
||||
local ps=100 url="https://api.kde-look.org/ocs/v1/content/data"
|
||||
local q="categories=705x715x719x720&sort=new&pagesize=$ps"
|
||||
|
||||
local p0=$(mktemp "$tempDir/XXXXXX.xml")
|
||||
curl -s -o "$p0" --connect-timeout 5 --retry 2 --max-time 60 --request GET --url "$url?$q&page=0"
|
||||
! xmllint --noout "$p0" 2>/dev/null && { echo '</data>' >> "$XML"; return 1; }
|
||||
case $(xmllint --xpath "string(//ocs/meta/statuscode)" "$p0") in
|
||||
""|200) echo '</data>' >> "$XML"; return 2;;
|
||||
[!100]*) echo '</data>' >> "$XML"; return 3;;
|
||||
esac
|
||||
|
||||
local total=$(xmllint --xpath "string(//ocs/meta/totalitems)" "$p0")
|
||||
xmllint --xpath "//content[@details='summary']" "$p0" 2>/dev/null >> "$XML"
|
||||
[[ $total =~ ^[0-9]+$ ]] || total=0
|
||||
(( total <= ps )) && { echo '</data>' >> "$XML"; return 0; }
|
||||
|
||||
local pages=$(( (total - 1) / ps ))
|
||||
local -a files=() args=()
|
||||
for ((p=1; p<=pages; p++)); do
|
||||
f=$(mktemp "$tempDir/XXXXXX.xml"); files+=("$f")
|
||||
args+=(-o "$f" --url "$url?$q&page=$p")
|
||||
done
|
||||
curl -s --parallel --parallel-immediate --connect-timeout 5 --retry 2 --max-time 60 "${args[@]}"
|
||||
|
||||
for f in "${files[@]}"; do
|
||||
xmllint --noout "$f" 2>/dev/null || { echo '</data>' >> "$XML"; return 2; }
|
||||
case $(xmllint --xpath "string(//ocs/meta/statuscode)" "$f") in
|
||||
100) xmllint --xpath "//content[@details='summary']" "$f" 2>/dev/null >> "$XML";;
|
||||
*) echo '</data>' >> "$XML"; return 2;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo '</data>' >> "$XML"
|
||||
}
|
||||
|
||||
get_packages() {
|
||||
packages=($(find \
|
||||
"$localDir/kwin/effects" \
|
||||
"$localDir/kwin/scripts" \
|
||||
"$localDir/plasma/plasmoids" \
|
||||
"$localDir/plasma/wallpapers" \
|
||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null))
|
||||
[ ${#packages[@]} -eq 0 ] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
get_package_type() {
|
||||
case $(xmllint --xpath "string(//id[text()='$contentId']/../typeid)" "$XML") in
|
||||
"715") echo "Plasma/Wallpaper";;
|
||||
"719") echo "KWin/Effect";;
|
||||
"720") echo "KWin/Script";;
|
||||
*) echo "Plasma/Applet";;
|
||||
esac
|
||||
}
|
||||
|
||||
get_package_info() {
|
||||
local id=$(basename "$package")
|
||||
|
||||
local metadata="$package/metadata.json"
|
||||
[[ -s "$metadata" ]] || return 1
|
||||
jq . "$metadata" >/dev/null 2>&1 || return 2
|
||||
|
||||
local originName=$(jq -r '.KPlugin.Name' "$metadata")
|
||||
name=$(echo "$originName" | sed 's/ /-/g; s/.*/\L&/')
|
||||
|
||||
local quotedOriginName='"'$(echo "$originName" | sed 's/"/\\"/g')'"'
|
||||
contentId=$(xmllint --xpath "string(//name[text()=$quotedOriginName]/../id)" "$XML")
|
||||
if [[ -z "$contentId" ]]; then
|
||||
knsregistry=("plasmoids" "kwinscripts" "kwineffect" "wallpaperplugin")
|
||||
for kns in "${knsregistry[@]}"; do
|
||||
kns="$HOME/.local/share/knewstuff3/$kns.knsregistry"
|
||||
[[ -s "$kns" ]] && contentId=$(xmllint --xpath "string(//installedfile[contains(text(), '/$id')]/../id)" "$kns")
|
||||
[[ -n "$contentId" ]] && break
|
||||
done
|
||||
fi
|
||||
[[ -z "$contentId" ]] && contentId="$(getId "$id" | head -n 1)"
|
||||
[[ -z "$contentId" ]] && return 3
|
||||
|
||||
currentVer=$(clearVer "$(jq -r '.KPlugin.Version' "$metadata")")
|
||||
latestVer=$(clearVer "$(xmllint --xpath "string(//id[text()='$contentId']/../version)" $XML)")
|
||||
[ -z "$currentVer" ] || [ -z "$latestVer" ] && return 4
|
||||
compareVer "$currentVer" "$latestVer"
|
||||
[[ $? != 2 ]] && return 5
|
||||
|
||||
description=$(jq -r '.KPlugin.Description' "$metadata" | sed 's/"/\\"/g' | tr -d '\n')
|
||||
[ -z "$description" ] || [ "$description" = "null" ] && description="$WIDGETS_NODESC"
|
||||
|
||||
author=$(jq -r '.KPlugin.Authors[].Name' "$metadata" | paste -sd "," - | sed 's/,/, /g')
|
||||
[ -z "$author" ] || [ "$author" = "null" ] && author="?"
|
||||
|
||||
icon=$(jq -r '.KPlugin.Icon' "$metadata")
|
||||
local fallbackIcon="start-here-kde-plasma-symbolic"
|
||||
if [ -z "$icon" ]; then
|
||||
icon=$fallbackIcon
|
||||
else
|
||||
local iconTheme=$(kreadconfig6 --file kdeglobals --group Icons --key Theme)
|
||||
local themeDir1="/usr/share/icons/$iconTheme/"
|
||||
local themeDir2="$HOME/.local/share/icons/$iconTheme/"
|
||||
local themeDir3="$HOME/.icons/$iconTheme/"
|
||||
! find -L "$themeDir1" "$themeDir2" "$themeDir3" \
|
||||
-type f -name "$icon.svg" -print -quit 2>/dev/null | grep -q . \
|
||||
&& icon=$fallbackIcon
|
||||
fi
|
||||
|
||||
url="https://store.kde.org/p/$contentId"
|
||||
repo="kde-store"
|
||||
type=$(get_package_type)
|
||||
|
||||
if ! jq -e ".KPackageStructure == \"$type\"" "$metadata" >/dev/null 2>&1; then
|
||||
updateJson "$metadata" "$(jq ". + { \"KPackageStructure\": \"$type\" }" "$metadata")"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_download_link() {
|
||||
local signed=()
|
||||
local files=0
|
||||
|
||||
while read -r download_version; do
|
||||
((files++))
|
||||
if [[ "$latestVer" == "$(clearVer "$(xmllint --xpath "string(//id[text()='$contentId']/../$download_version)" "$XML")")" ]]; then
|
||||
signed+=("${download_version#download_version}")
|
||||
fi
|
||||
done < <(xmllint --xpath "//content[id='$contentId']/*" "$XML" | grep -o 'download_version[0-9]\+' | uniq)
|
||||
|
||||
if [[ $files -eq 1 || ${#signed[@]} -eq 1 ]]; then
|
||||
echo $(xmllint --xpath "string(//id[text()='$contentId']/../downloadlink${signed[0]:-1})" "$XML") > "$tempDir/link"
|
||||
return 0
|
||||
else
|
||||
[[ $files -eq 0 ]] && return 1
|
||||
[[ ${#signed[@]} -eq 0 ]] && return 2
|
||||
(( ${#signed[@]} > 1 )) && return 3
|
||||
fi
|
||||
}
|
||||
|
||||
make_updates_list() {
|
||||
local tmp="$tempDir/update.list.lines.json"
|
||||
: > "$tmp"
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
get_package_info
|
||||
[[ $? -ne 0 ]] && continue
|
||||
|
||||
jq -cn \
|
||||
--arg NM "$name" \
|
||||
--arg RE "$repo" \
|
||||
--arg TP "$type" \
|
||||
--arg CN "$contentId" \
|
||||
--arg IN "$icon" \
|
||||
--arg DE "$description" \
|
||||
--arg AU "$author" \
|
||||
--arg VO "$currentVer" \
|
||||
--arg VN "$latestVer" \
|
||||
--arg LN "$url" \
|
||||
'{NM:$NM,RE:$RE,TP:$TP,CN:$CN,IN:$IN,DE:$DE,AU:$AU,VO:$VO,VN:$VN,LN:$LN}' >> "$tmp"
|
||||
done
|
||||
|
||||
jq -s '.' "$tmp" > "$updatesList"
|
||||
}
|
||||
|
||||
check_packages_updates() {
|
||||
command -v jq >/dev/null || {
|
||||
echo "$WIDGETS_ERR $CMD_ERR jq" >&2
|
||||
exit 127
|
||||
}
|
||||
|
||||
declare -a packages
|
||||
! get_packages && {
|
||||
echo "[]"
|
||||
exit
|
||||
}
|
||||
|
||||
makeTempDir
|
||||
XML="$tempDir/api.kde-look.xml"
|
||||
updatesList="$tempDir/update.list.json"
|
||||
trap cleanup EXIT
|
||||
|
||||
fetch_api_data
|
||||
case $? in
|
||||
1) echo "$WIDGETS_ERR $WIDGETS_ERR_API_FAIL" >&2; exit 1 ;;
|
||||
2) echo "$WIDGETS_ERR $WIDGETS_ERR_API" >&2; exit 1 ;;
|
||||
3) echo "$WIDGETS_ERR $WIDGETS_ERR_UNKNOWN" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
make_updates_list
|
||||
jq . $updatesList
|
||||
}
|
||||
|
||||
download_package() {
|
||||
mkdir -p "$tempDir/$name/unpacked"
|
||||
link=$(cat "$tempDir/link")
|
||||
package="$tempDir/$name/$(basename "${link}")"
|
||||
|
||||
curl -s -o "$package" --connect-timeout 5 --retry 2 --max-time 120 --request GET --location --url "$link" 2>/dev/null
|
||||
|
||||
[ ! -s "$package" ] && return 1
|
||||
|
||||
bsdtar -xf "$package" -C "$tempDir/$name/unpacked" || return 2
|
||||
|
||||
metadata=$(find "$tempDir/$name/unpacked" -name metadata.json)
|
||||
[ -z "metadata" ] && return 3
|
||||
|
||||
jq . "$metadata" >/dev/null 2>&1 || return 4
|
||||
|
||||
if ! jq -e ".KPackageStructure == \"$type\"" "$metadata" >/dev/null 2>&1; then
|
||||
updateJson "$metadata" "$(jq ". + { \"KPackageStructure\": \"$type\" }" "$metadata")"
|
||||
fi
|
||||
|
||||
updateJson "$metadata" "$(jq --arg new_value "$latestVer" '.KPlugin.Version = $new_value' "$metadata")"
|
||||
|
||||
if [[ $name == "apdatifier" && $trayEnabledByDefault == "true" ]]; then
|
||||
if jq -e '.KPlugin.EnabledByDefault == false' "$metadata" > /dev/null; then
|
||||
updateJson "$metadata" "$(jq '.KPlugin.EnabledByDefault = true' "$metadata")"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
upgrade_all_packages() {
|
||||
dependencies "jq"
|
||||
|
||||
declare -a packages
|
||||
! get_packages && {
|
||||
printDone "$MNG_DONE"
|
||||
exit
|
||||
}
|
||||
|
||||
makeTempDir
|
||||
XML="$tempDir/api.kde-look.xml"
|
||||
updatesList="$tempDir/update.list.json"
|
||||
trap cleanup EXIT
|
||||
trap 'kill "$bg_pid" 2>/dev/null; cleanup' INT TERM
|
||||
|
||||
fetch_api_data & bg_pid=$!
|
||||
printWhile "$bg_pid" "$WIDGETS_FETCH"
|
||||
[[ $exitCode -ne 0 ]] && exit
|
||||
|
||||
make_updates_list & bg_pid=$!
|
||||
printWhile "$bg_pid" "$WIDGETS_CHECK"
|
||||
|
||||
if [[ -s "$updatesList" ]] && jq -e '(. | length) > 0' "$updatesList" > /dev/null 2>&1; then
|
||||
echo
|
||||
jq -c 'sort_by(.NM)[]' "$updatesList" | while read -r info; do
|
||||
echo -e "$(colorize white bold $(echo "$info" | jq -r '.NM')) \t \
|
||||
$(colorize yellow bold $(echo "$info" | jq -r '.TP')) \t \
|
||||
$(colorize red bold $(echo "$info" | jq -r '.VO')) \t \
|
||||
$(colorize white "->") \t \
|
||||
$(colorize green bold $(echo "$info" | jq -r '.VN'))"
|
||||
done | column -t
|
||||
echo
|
||||
|
||||
if [[ $widgetConfirmation = true ]]; then
|
||||
while true; do
|
||||
printQuestion "$WIDGETS_PROCEED"; read -r answer
|
||||
case "$answer" in
|
||||
[Yy]*) echo; break;;
|
||||
[Nn]*|"") return 0;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
updated=false
|
||||
while read -r info; do
|
||||
name=$(echo "$info" | jq -r '.NM')
|
||||
contentId=$(echo "$info" | jq -r '.CN')
|
||||
latestVer=$(echo "$info" | jq -r '.VN')
|
||||
type=$(echo "$info" | jq -r '.TP')
|
||||
|
||||
echo "$(colorize blue bold "$ICO_EXEC $name ($latestVer)")"
|
||||
|
||||
get_download_link & printWhile $! "$WIDGETS_LINK"
|
||||
[[ $exitCode -ne 0 ]] && { echo; continue; }
|
||||
|
||||
download_package & printWhile $! "$WIDGETS_DOWNLOADING"
|
||||
[[ $exitCode -ne 0 ]] && { echo; continue; }
|
||||
|
||||
kpackagetool6 -t "$type" -u "$(dirname "$(find "$tempDir/$name" -name "metadata.json")")" 2>/dev/null
|
||||
updated=true
|
||||
sleep 1
|
||||
echo
|
||||
done < <(jq -c 'sort_by(.NM)[]' "$updatesList")
|
||||
|
||||
[[ $updated = true ]] && restartPlasmashell
|
||||
else
|
||||
printDone "$MNG_DONE"
|
||||
fi
|
||||
}
|
||||
|
||||
upgrade_package() {
|
||||
[ -n "$1" ] && contentId="$1" || exit
|
||||
[ -n "$2" ] && name="$2" || exit
|
||||
|
||||
dependencies "jq"
|
||||
|
||||
mkdir -p "$tempDir/$name/unpacked"
|
||||
XML="$tempDir/$name/api.kde-look.xml"
|
||||
|
||||
trap 'kill "$bg_pid" 2>/dev/null; cleanup' INT TERM EXIT
|
||||
|
||||
{
|
||||
curl -s -o "$XML" --connect-timeout 5 --retry 2 --max-time 60 --request GET --url "https://api.kde-look.org/ocs/v1/content/data/$contentId" 2>/dev/null
|
||||
if xmllint --noout "$XML" 2>/dev/null; then
|
||||
statuscode=$(xmllint --xpath "string(//ocs/meta/statuscode)" "$XML")
|
||||
case $statuscode in
|
||||
100) return 0 ;;
|
||||
200) return 2 ;;
|
||||
*) return 3 ;;
|
||||
esac
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
} & bg_pid=$!
|
||||
printWhile "$bg_pid" "$WIDGETS_FETCH"
|
||||
[[ $exitCode -ne 0 ]] && exit
|
||||
|
||||
latestVer=$(clearVer "$(xmllint --xpath "string(//id[text()='$contentId']/../version)" "$XML")")
|
||||
type=$(get_package_type)
|
||||
|
||||
get_download_link & bg_pid=$!
|
||||
printWhile "$bg_pid" "$WIDGETS_LINK"
|
||||
[[ $exitCode -ne 0 ]] && exit
|
||||
|
||||
download_package & bg_pid=$!
|
||||
printWhile "$bg_pid" "$WIDGETS_DOWNLOADING"
|
||||
[[ $exitCode -ne 0 ]] && exit
|
||||
|
||||
kpackagetool6 -t "$type" -u "$(dirname "$(find "$tempDir/$name" -name "metadata.json")")" 2>/dev/null
|
||||
sleep 1
|
||||
echo
|
||||
|
||||
restartPlasmashell
|
||||
}
|
||||
|
||||
list_current_packages() {
|
||||
declare -a packages
|
||||
! get_packages && return
|
||||
|
||||
local out=""
|
||||
for package in "${packages[@]}"; do
|
||||
local json="$package/metadata.json"; [ -s "$json" ] || continue
|
||||
local name="$(jq -r '.KPlugin.Name' "$json")"; [ -z "$name" ] && continue
|
||||
local currentVer=$(clearVer "$(jq -r '.KPlugin.Version' "$json")"); [ -z "$currentVer" ] && continue
|
||||
out+="{\"NM\": \"${name}\","
|
||||
out+="\"VO\": \"${currentVer}\"},\n"
|
||||
done
|
||||
echo -e $out
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
"list") list_current_packages;;
|
||||
"check") check_packages_updates;;
|
||||
"upgrade") shift; upgrade_package $1 $2;;
|
||||
"upgradeAll") upgrade_all_packages;;
|
||||
*) exit;;
|
||||
esac
|
||||
@@ -0,0 +1,189 @@
|
||||
998890 com.bxabi.bumblebee-indicator
|
||||
998913 org.kde.plasma.awesomewidget
|
||||
1155946 com.dschopf.plasma.qalculate
|
||||
1288430 org.kde.plasma.shutdownorswitch
|
||||
1377704 org.kde.mediabar
|
||||
1384066 org.kde.workraveApplet
|
||||
1396415 com.github.solant.plasmataker
|
||||
1627256 com.github.xyz32.timekeeper
|
||||
1804745 com.github.heqro.day-night-switcher
|
||||
1898708 org.nielsvm.plasma.menupager
|
||||
2079446 dev.vili.sahkoporssi
|
||||
2100417 org.kde.mcwsremote
|
||||
2100418 org.kde.olib.thermalmonitor
|
||||
2107649 org.kde.panel.transparency.toggle
|
||||
2112443 com.github.tilorenz.compact_pager
|
||||
2113872 com.github.korapp.cloudflare-warp
|
||||
2114471 de.davidhi.ddcci-brightness
|
||||
2115883 org.kde.plasma.simplekickoff
|
||||
2117117 com.dv.fokus
|
||||
2118132 com.github.stepan-zubkov.days-to-new-year
|
||||
2118492 com.github.korapp.nordvpn
|
||||
2126775 com.github.tilorenz.timeprogressbar
|
||||
2128047 luisbocanegra.panelspacer.extended
|
||||
2128143 plasmusic-toolbar
|
||||
2128477 luisbocanegra.intel.gpu.monitor
|
||||
2129423 org.kde.windowtitle
|
||||
2130222 luisbocanegra.panel.modes.switcher
|
||||
2130541 org.kde.archupdatechecker
|
||||
2130967 luisbocanegra.panel.colorizer
|
||||
2131364 com.github.korapp.homeassistant
|
||||
2131462 org.kde.plasma.plasm6desktopindicator
|
||||
2132405 com.github.dhruv8sh.year-progress-mod
|
||||
2132554 com.himdek.kde.plasma.overview
|
||||
2132555 com.himdek.kde.plasma.runcommand
|
||||
2134470 a2n.archupdate.plasmoid
|
||||
2135509 com.github.antroids.application-title-bar
|
||||
2135511 org.kde.placesWidget
|
||||
2135552 org.kde.plasma.yesplaymusic-lyrics
|
||||
2135642 com.github.prayag2.minimalistclock
|
||||
2135653 com.github.prayag2.modernclock
|
||||
2135796 com.github.exequtic.apdatifier
|
||||
2135799 com.github.k-donn.plasmoid-wunderground
|
||||
2135898 com.dv.uswitcher
|
||||
2136288 org.kde.Big.Clock
|
||||
2136291 zayron.chaac.weather
|
||||
2136295 Clock.Asitoki.Color
|
||||
2136299 CircleClock
|
||||
2136302 zayron.almanac
|
||||
2136307 Minimal.chaac.weather
|
||||
2136321 com.Petik.clock
|
||||
2136329 weather.bicolor.widget
|
||||
2136505 org.kde.netspeedWidget
|
||||
2136546 com.nemmayan.clock
|
||||
2136631 com.github.scriptinator
|
||||
2136636 com.github.zren.commandoutput
|
||||
2136852 org.kde.latte.separator
|
||||
2136860 com.github.zren.alphablackcontrol
|
||||
2136933 org.kde.plasma.advancedradio
|
||||
2136963 luisbocanegra.kdematerialyou.colors
|
||||
2137016 org.kde.plasma.Beclock
|
||||
2137185 com.github.zren.dailyforecast
|
||||
2137197 com.github.zren.condensedweather
|
||||
2137217 org.kde.plasma.scpmk
|
||||
2137231 com.github.eatsu.spaceraspager
|
||||
2137418 zayron.simple.separator
|
||||
2137431 com.github.zren.simpleweather
|
||||
2137675 com.gitlab.scias.advancedreboot
|
||||
2137726 org.zayronxio.vector.clock
|
||||
2137844 org.kde.plasma.catwalk
|
||||
2138251 org.kpple.kppleMenu
|
||||
2138283 ink.chyk.minimumMediaController
|
||||
2138365 optimus-gpu-switcher
|
||||
2138473 org.kde.plasma.videocard
|
||||
2138476 lenovo-conservation-mode-switcher
|
||||
2138485 com.github.boraerciyas.controlcentre
|
||||
2138746 org.kde.plasma.pminhibition
|
||||
2138853 org.kde.Date.Bubble.P6
|
||||
2138907 org.kde.latte.spacer
|
||||
2139337 split-clock
|
||||
2139541 com.github.configurable_button
|
||||
2139890 Plasma.Control.Hub
|
||||
2140275 com.github.davide-sd.ip_address
|
||||
2140856 d4rkwzd.colorpicker-tray
|
||||
2141133 org.kde.MinimalMusic.P6
|
||||
2142681 Audio.Wave.Widget
|
||||
2142716 com.github.zren.tiledmenu
|
||||
2143899 org.kde.plasma.resources-monitor
|
||||
2144212 AndromedaLauncher
|
||||
2144426 org.previewqt.previewqt.plasmoidpreviewqt
|
||||
2144969 SoloDay.P6
|
||||
2145065 com.github.liujed.rssfeeds
|
||||
2145280 com.github.DenysMb.Kicker-AppsOnly
|
||||
2145723 luisbocanegra.desktop.wallpaper.effects
|
||||
2146553 org.kde.plasma.ginti
|
||||
2147850 zayron.almanac.V2
|
||||
2147871 org.kde.plasma.clearclock
|
||||
2147882 org.kde.windowtitle.Fork
|
||||
2148373 lyrics-on-panel-plasma6
|
||||
2148469 thot.observer.ram
|
||||
2148472 thot.observer.cpu
|
||||
2150544 org.kde.plasma.composeEmail
|
||||
2150610 com.gitlab.scias.plasmavantage
|
||||
2150916 org.kde.paneltransparencybutton
|
||||
2151247 org.kde.plasma.win7showdesktop
|
||||
2151576 ppk.plasma6.hydrobot
|
||||
2151585 com.samirgaire10.Brave-plasma6
|
||||
2151600 com.samirgaire10.chatgpt-plasma6
|
||||
2151622 com.samirgaire10.Deepl-plasma6
|
||||
2151683 com.samirgaire10.google_gemini-plasma6
|
||||
2151687 com.samirgaire10.Google-plasma6
|
||||
2151694 com.samirgaire10.GoogleTranslater-plasma6
|
||||
2151931 org.kde.windowbuttons
|
||||
2151962 com.samirgaire10.perplexityAi-plasma6
|
||||
2152642 com.samirgaire10.blackbox.AI_plasma6
|
||||
2153869 org.kde.plasma.private.BusyTasksPlasmoid
|
||||
2154331 com.softtechok.systemmonitorplasmoid
|
||||
2154894 org.51n7.kMenu
|
||||
2157390 org.latgardi.darwinmenu
|
||||
2158349 Almanac.Asimetric
|
||||
2158358 org.kde.losungen6
|
||||
2159592 com.samirgaire10.Ollama-plasma6
|
||||
2160638 com.softtechok.processmonitor
|
||||
2161461 Weather.IntiSol.kde
|
||||
2162586 org.kde.dwardor.fanspeedmonitor
|
||||
2163165 OnlyText.Date.Kde
|
||||
2163199 com.samirgaire10.DuckDuckGo-plasma6
|
||||
2163340 ChatAI-Plasmoid
|
||||
2163368 Circle.Music.Widget
|
||||
2164459 org.kde.redshiftControl6
|
||||
2164590 org.kde.plasma.mediacontroller.panel
|
||||
2164679 com.github.chrtall.kppleMenu
|
||||
2166656 Aligned.Music.Widget
|
||||
2166934 adhe.runcommand
|
||||
2167594 adhe.dittomenu
|
||||
2167836 com.github.jonmagon.plasma-screendimmer
|
||||
2168131 Date.Urban.kde
|
||||
2168466 Rates.Mercurius.Widget
|
||||
2169597 com.github.danielwjchen.folding-at-home-plasma-widget
|
||||
2170656 com.samirgaire10.Github-plasma6
|
||||
2171750 adhe.menu.11
|
||||
2172470 org.magpie.sonomatic.separator
|
||||
2173278 org.kde.plasma.powerusage
|
||||
2174238 adhe.launchpadPlasma
|
||||
2174265 Chaac.Complete.Weather
|
||||
2175475 Redmi.Clock
|
||||
2179264 org.magpie.nightglow.separator
|
||||
2179317 Start.Next.Menu
|
||||
2179829 com.kevinbburns.grammarly
|
||||
2179833 Small.Weather.Plasma6
|
||||
2180314 Soniq.Widget
|
||||
2180887 DeepinMenu.Classic
|
||||
2182964 adhe.menu.11 #Ignored, not a unique ID
|
||||
2183258 Compact.Menu
|
||||
2183752 luisbocanegra.cursor.eyes
|
||||
2184730 OnlyText.Date.Kde.v2
|
||||
2184747 ChatQT-Plasmoid
|
||||
2185174 Seeua.Weather
|
||||
2185549 org.kde.plasma.pipewiresettings
|
||||
2185626 org.kde.plasma.dockio
|
||||
2185931 asus-battery-health-switcher
|
||||
2185962 Start.11.Simple
|
||||
2186442 Music.Waves
|
||||
2187344 MusicMini
|
||||
2187962 MechanicalClock
|
||||
2188275 com.junongx.kde.currentlyplaying
|
||||
2188581 Lyndo.Kde
|
||||
2189544 Plain.Circle.Weather
|
||||
2189637 Solid.Plain.Music
|
||||
2193670 qinvvv.qtodo.widget
|
||||
2194992 org.kde.osymUpdates
|
||||
2195583 org.ruiny.NowPlaying
|
||||
2196105 KdeControlStation
|
||||
2196149 ff.qtodo.widget
|
||||
2196368 com.github.imoize.ollamacontrol
|
||||
2199942 Bix.Plasma.Player
|
||||
2200890 org.dhruv8sh.kara
|
||||
2201084 Emulator.Spectrum.Audio
|
||||
2202249 com.espaker.simpleradioplayer-plasma6
|
||||
2204865 org.dr42.analogclock
|
||||
2206628 runcommand.fork
|
||||
2211264 com.ekaaty.vinyl-launcher
|
||||
2216429 org.kde.plasma.ipaddress
|
||||
2216432 org.kde.plasma.shamsi-calendar
|
||||
2217171 Concepto.menu.12
|
||||
2217301 hermes.observer.ram.P6
|
||||
2217313 hermes.observer.cpu.P6
|
||||
2224768 org.kde.plasma.dayprogress
|
||||
2225123 org.kde.olib.pinpanel
|
||||
2225378 material.clock
|
||||
@@ -0,0 +1,983 @@
|
||||
const scriptDir = Qt.resolvedUrl("sh/").toString().replace("file://", "");
|
||||
const configDir = "${APDATIFIER_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/apdatifier}/"
|
||||
const cacheDir = "${APDATIFIER_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/apdatifier}/"
|
||||
const configFile = configDir + "config.conf"
|
||||
const cacheFile = cacheDir + "updates.json"
|
||||
const rulesFile = configDir + "rules.json"
|
||||
const newsFile = cacheDir + "news.json"
|
||||
const timestampFile = cacheDir + "lastCheck"
|
||||
|
||||
const procOpt = { stoppable: true }
|
||||
|
||||
function execute(command, callback, opt) {
|
||||
const component = Qt.createComponent("../ui/components/Shell.qml")
|
||||
if (component.status === Component.Ready) {
|
||||
const componentObject = component.createObject(root)
|
||||
if (componentObject) {
|
||||
if (opt && opt.stoppable) sts.proc = componentObject
|
||||
componentObject.exec(command, callback)
|
||||
} else {
|
||||
Error(1, "Failed to create executable DataSource object")
|
||||
}
|
||||
} else {
|
||||
Error(1, "Executable DataSource component not ready")
|
||||
}
|
||||
}
|
||||
|
||||
const readFile = (file) => `[ -f "${file}" ] && cat "${file}"`
|
||||
const writeFile = (data, redir, file) => `echo '${data}' ${redir} "${file}"`
|
||||
const saveTimestamp = () => execute(writeFile(Math.round(sts.lastCheck).toString(), '>', timestampFile))
|
||||
|
||||
const bash = (script, ...args) => scriptDir + script + ' ' + args.join(' ')
|
||||
const runInTerminal = (script, ...args) => {
|
||||
const cmd = bash('terminal', script, ...args)
|
||||
execute(`kstart -- bash -c '${cmd}'`)
|
||||
if (script === "upgrade") {
|
||||
runLater(5000, () => upgradeTimer.start())
|
||||
scheduler.stop()
|
||||
sts.busy = sts.upgrading = true
|
||||
sts.statusMsg = i18n("Upgrade in progress") + "..."
|
||||
sts.statusIco = cfg.ownIconsUI ? "toolbar_upgrade" : "akonadiconsole"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function runLater(ms, fn) {
|
||||
const timer = Qt.createQmlObject('import QtQuick 2.0; Timer { repeat: false }', parent)
|
||||
timer.interval = ms
|
||||
timer.triggered.connect(() => {
|
||||
fn()
|
||||
timer.destroy()
|
||||
})
|
||||
timer.start()
|
||||
}
|
||||
|
||||
const debug = true
|
||||
function log(message) {
|
||||
if (debug) console.log("[" + new Date().getTime().toString() + "] "+ "APDATIFIER: " + message)
|
||||
}
|
||||
|
||||
function Error(code, err) {
|
||||
if (err) {
|
||||
sts.errors = sts.errors.concat([{code: code, message: err.trim(), type: ""}])
|
||||
cfg.notifyErrors && notify.send("error", i18n("Exit code: ") + code, err.trim())
|
||||
setStatusBar()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function handleError(code, err, type, onError) {
|
||||
if (code && err) {
|
||||
sts.errors = sts.errors.concat([{code: code, message: err.trim(), type: type}])
|
||||
onError()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function init() {
|
||||
execute(bash('init'), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
loadConfig()
|
||||
})
|
||||
|
||||
function loadConfig() {
|
||||
execute(readFile(configFile), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
if (out) {
|
||||
const config = out.trim().split("\n")
|
||||
const convert = value => {
|
||||
if (!isNaN(parseFloat(value))) return parseFloat(value)
|
||||
if (value === "true" || value === "false") return value === 'true'
|
||||
return value
|
||||
}
|
||||
config.forEach(line => {
|
||||
const match = line.match(/(\w+)="([^"]*)"/)
|
||||
if (match) plasmoid.configuration[match[1]] = convert(match[2])
|
||||
})
|
||||
}
|
||||
|
||||
execute(readFile(timestampFile), (cmd, out, err, code) => {
|
||||
if (out) sts.lastCheck = parseInt(out.trim()) || 0
|
||||
loadCache()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function loadCache() {
|
||||
execute(readFile(cacheFile), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
if (out && validJSON(out, cacheFile)) cache = keys(JSON.parse(out.trim()))
|
||||
loadRules()
|
||||
})
|
||||
}
|
||||
|
||||
function loadRules() {
|
||||
execute(readFile(rulesFile), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
if (out && validJSON(out, rulesFile)) plasmoid.configuration.rules = out
|
||||
loadNews()
|
||||
})
|
||||
}
|
||||
|
||||
function loadNews() {
|
||||
execute(readFile(newsFile), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
if (out && validJSON(out, newsFile)) {
|
||||
const news = JSON.parse(out.trim())
|
||||
let migrate = false //
|
||||
for (const article of news) {
|
||||
// todo: remove later
|
||||
if (article.timestamp === undefined && article.date) {
|
||||
migrate = true
|
||||
const [d, t] = article.date.split(" | ")
|
||||
const [day, month, year] = d.split(".").map(Number)
|
||||
const [hour, minute] = t.split(":").map(Number)
|
||||
article.timestamp = Math.floor(new Date(year, month - 1, day, hour, minute).getTime() / 1000)
|
||||
delete article.date
|
||||
}//
|
||||
|
||||
addNewsItem(article)
|
||||
}
|
||||
|
||||
if (migrate) saveNews() //
|
||||
}
|
||||
|
||||
onStartup()
|
||||
})
|
||||
}
|
||||
|
||||
function onStartup() {
|
||||
sts.init = true
|
||||
checkDependencies()
|
||||
refreshListModel()
|
||||
upgradingState()
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
if (saveTimer.running) return
|
||||
let config = ""
|
||||
Object.keys(cfg).forEach(key => {
|
||||
if (key.endsWith("Default")) {
|
||||
let name = key.slice(0, -7)
|
||||
config += `${name}="${cfg[name]}"\n`
|
||||
}
|
||||
})
|
||||
execute(writeFile(config, ">", configFile))
|
||||
}
|
||||
|
||||
function checkDependencies() {
|
||||
const pkgs = "pacman flatpak fwupdmgr paru pikaur yay jq tmux alacritty foot ghostty gnome-terminal kitty konsole lxterminal ptyxis terminator tilix wezterm xterm yakuake"
|
||||
const checkPkg = (pkgs) => `for pkg in ${pkgs}; do command -v $pkg || echo; done`
|
||||
const populate = (data) => data.map(item => ({ "name": item.split("/").pop(), "value": item }))
|
||||
|
||||
execute(checkPkg(pkgs), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
|
||||
const output = out.split("\n")
|
||||
|
||||
const [pacman, flatpak, fwupdmgr, paru, pikaur, yay, jq, tmux ] = output.map(Boolean)
|
||||
cfg.packages = { pacman, flatpak, fwupdmgr, paru, pikaur, yay, jq, tmux }
|
||||
if (!cfg.wrapper) cfg.wrapper = paru ? "paru" : yay ? "yay" : pikaur ? "pikaur" : ""
|
||||
|
||||
const terminals = populate(output.slice(8).filter(Boolean))
|
||||
cfg.terminals = terminals.length > 0 ? terminals : null
|
||||
if (!cfg.terminal) cfg.terminal = cfg.terminals.length > 0 ? cfg.terminals[0].value : ""
|
||||
|
||||
if (!pacman) plasmoid.configuration.arch = false
|
||||
if (!pacman || (!yay && !paru && !pikaur)) plasmoid.configuration.aur = false
|
||||
if (!flatpak) plasmoid.configuration.flatpak = false
|
||||
if (!fwupdmgr) plasmoid.configuration.fwupd = false
|
||||
if (!tmux) plasmoid.configuration.tmuxSession = false
|
||||
if (!jq) {
|
||||
plasmoid.configuration.widgets = false
|
||||
plasmoid.configuration.feedsEnabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function upgradePackage(name, appID, contentID) {
|
||||
if (sts.upgrading) return
|
||||
|
||||
if (appID) {
|
||||
runInTerminal("upgrade", "flatpak", appID, name)
|
||||
} else if (contentID) {
|
||||
runInTerminal("upgrade", "widget", contentID, name)
|
||||
}
|
||||
}
|
||||
|
||||
function management() {
|
||||
runInTerminal("management")
|
||||
}
|
||||
|
||||
|
||||
function upgradingState() {
|
||||
const checkProc = `pgrep -f "apdatifier.*upgrade*"`
|
||||
execute(checkProc, (cmd, out, err, code) => {
|
||||
if (!out) {
|
||||
sts.busy = sts.upgrading = false
|
||||
upgradeTimer.stop()
|
||||
execute(bash('utils', "currentVersions"), (cmd, out, err, code) => {
|
||||
if (Error(code, err)) return
|
||||
if (!out || !validJSON(out)) return
|
||||
const currentVersions = JSON.parse(out)
|
||||
const newList = cache.filter(cached => {
|
||||
const current = currentVersions.find(pkg => pkg.NM.replace(/ /g, "-").toLowerCase() === cached.NM)
|
||||
return current && current.VO === cached.VO + cached.AC
|
||||
})
|
||||
if (JSON.stringify(cache) !== JSON.stringify(newList)) {
|
||||
cache = newList
|
||||
refreshListModel()
|
||||
saveCache(cache)
|
||||
}
|
||||
setStatusBar()
|
||||
resumeScheduler()
|
||||
})
|
||||
} else {
|
||||
scheduler.stop()
|
||||
upgradeTimer.start()
|
||||
sts.busy = sts.upgrading = true
|
||||
sts.statusMsg = i18n("Upgrade in progress") + "..."
|
||||
sts.statusIco = cfg.ownIconsUI ? "toolbar_upgrade" : "akonadiconsole"
|
||||
}
|
||||
updatePlasmoidStatus()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function upgradeSystem() {
|
||||
if (sts.upgrading && !cfg.tmuxSession) return
|
||||
const ignorePkgs = buildIgnoreString()
|
||||
runInTerminal("upgrade", "full", `${ignorePkgs}`)
|
||||
}
|
||||
|
||||
|
||||
function stopCheck() {
|
||||
sts.errors = []
|
||||
sts.busy = false
|
||||
sts.proc?.cleanup()
|
||||
setStatusBar()
|
||||
resumeScheduler()
|
||||
|
||||
if (isOnline) {
|
||||
sts.lastCheck = Date.now()
|
||||
saveTimestamp()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function loadIgnorePkgs() {
|
||||
return new Promise(resolve => {
|
||||
if (!cfg.packages || !cfg.packages.pacman) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
|
||||
execute("pacman-conf IgnorePkg", (cmd, out, err, code) => {
|
||||
if (code || !out) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
|
||||
const tokens = out.trim().split(/\s+/).filter(Boolean)
|
||||
const escapeRe = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
|
||||
const patterns = tokens.map(token => {
|
||||
let pat = token.split(/[<>=]/)[0].trim()
|
||||
if (!pat) return null
|
||||
let re = escapeRe(pat)
|
||||
re = re.replace(/\\\*/g, ".*").replace(/\\\?/g, ".")
|
||||
return new RegExp("^" + re + "$")
|
||||
}).filter(Boolean)
|
||||
|
||||
resolve(patterns)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function buildIgnoreString() {
|
||||
const rules = !cfg.rules ? [] : JSON.parse(cfg.rules)
|
||||
if (!Array.isArray(rules)) return ''
|
||||
|
||||
const ignoreItems = rules.filter(rule =>
|
||||
rule && rule.ignore === true
|
||||
)
|
||||
|
||||
if (ignoreItems.length === 0) return ''
|
||||
|
||||
const values = ignoreItems.map(rule => rule.value)
|
||||
return `--ignore ${values.join(',')}`
|
||||
}
|
||||
|
||||
|
||||
function checkUpdates() {
|
||||
if (sts.upgrading) return
|
||||
if (!isOnline) return
|
||||
|
||||
sts.errors = []
|
||||
scheduler.stop()
|
||||
sts.busy = true
|
||||
|
||||
let archRepos = [], archAur = [], flatpak = [], widgets = [], firmwares = []
|
||||
|
||||
const feeds = cfg.feedsEnabled ? parseCustomFeeds(cfg.feeds).map(u => `'${u}'`).join(' ') : ""
|
||||
const news = feeds && cfg.packages?.jq
|
||||
|
||||
news ? checkNews() :
|
||||
cfg.arch ? checkRepos() :
|
||||
cfg.aur ? checkAur() :
|
||||
cfg.flatpak ? checkFlatpak() :
|
||||
cfg.widgets ? checkWidgets() :
|
||||
cfg.fwupd ? checkFirmwares() :
|
||||
merge()
|
||||
|
||||
function checkNews() {
|
||||
const next = () => cfg.arch ? checkRepos() : cfg.aur ? checkAur() : cfg.flatpak ? checkFlatpak() :
|
||||
cfg.widgets ? checkWidgets() : cfg.fwupd ? checkFirmwares() : merge()
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_news" : "news-subscribe"
|
||||
sts.statusMsg = i18n("Checking latest news...")
|
||||
execute(bash('utils', 'rss', feeds), (cmd, out, err, code) => {
|
||||
if (out) {
|
||||
const news = JSON.parse(out.trim())
|
||||
if (cfg.notifyNews) {
|
||||
const currentLinks = new Set(Array.from(Array(newsModel.count), (_, i) => newsModel.get(i).link))
|
||||
const newItems = news.filter(item => !currentLinks.has(item.link))
|
||||
if (newItems.length > 0) {
|
||||
const title = i18np("%1 new article", "%1 new articles", newItems.length)
|
||||
const body = newItems.map(item => `<b>${item.title}</b>: ${item.article}`).join("\n")
|
||||
notify.send("news", title, body, newItems[0].link)
|
||||
}
|
||||
}
|
||||
|
||||
if (newsModel.count === 0) {
|
||||
news.forEach(item => addNewsItem(item))
|
||||
} else {
|
||||
let newLinks = news.map(item => item.link)
|
||||
let prevLinks = modelToArray(newsModel).map(item => item.link)
|
||||
|
||||
news.forEach(item => {
|
||||
if (!prevLinks.includes(item.link)) addNewsItem(item)
|
||||
})
|
||||
|
||||
for (let i = newsModel.count - 1; i >= 0; --i) {
|
||||
let modelItem = newsModel.get(i)
|
||||
if (!newLinks.includes(modelItem.link)) newsModel.remove(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handleError(code, err, "news", next)) return
|
||||
next()
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function checkRepos() {
|
||||
const next = () => cfg.aur ? checkAur() : cfg.flatpak ? checkFlatpak() :
|
||||
cfg.widgets ? checkWidgets() : cfg.fwupd ? checkFirmwares() : merge()
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_package" : "apdatifier-package"
|
||||
sts.statusMsg = i18n("Synchronizing pacman databases...")
|
||||
execute(bash('utils', 'syncdb'), (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "repositories", next)) return
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_package" : "apdatifier-package"
|
||||
sts.statusMsg = i18n("Checking system updates...")
|
||||
execute(`pacman -Qu --dbpath "${cfg.dbPath}" 2>&1`, (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "repositories", next)) return
|
||||
const updates = out ? out.trim().split("\n") : []
|
||||
makeArchList(updates, "repositories").then(result => {
|
||||
archRepos = result
|
||||
next()
|
||||
})
|
||||
}, procOpt)
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function checkAur() {
|
||||
const next = () => cfg.flatpak ? checkFlatpak() : cfg.widgets ? checkWidgets() : cfg.fwupd ? checkFirmwares() : merge()
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_package" : "apdatifier-package"
|
||||
sts.statusMsg = i18n("Checking AUR updates...")
|
||||
const cmdArg = cfg.wrapper === "pikaur" ? " -Qua --noconfirm" : " -Qua"
|
||||
execute(cfg.wrapper + cmdArg, (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "aur", next)) return
|
||||
const updates = out ? out.trim().split("\n") : []
|
||||
makeArchList(updates, "aur").then(result => {
|
||||
archAur = result
|
||||
next()
|
||||
})
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function checkFlatpak() {
|
||||
const next = () => cfg.widgets ? checkWidgets() : cfg.fwupd ? checkFirmwares() : merge()
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_flatpak" : "apdatifier-flatpak"
|
||||
sts.statusMsg = i18n("Synchronizing flatpak appstream...")
|
||||
execute("flatpak update --appstream >/dev/null 2>&1", (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "flatpak", next)) return
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_flatpak" : "apdatifier-flatpak"
|
||||
sts.statusMsg = i18n("Checking flatpak updates...")
|
||||
execute("flatpak remote-ls --app --updates --show-details", (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "flatpak", next)) return
|
||||
const updates = out.trim()
|
||||
makeFlatpakList(updates, "flatpak").then(result => {
|
||||
flatpak = result
|
||||
next()
|
||||
})
|
||||
}, procOpt)
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function checkWidgets() {
|
||||
const next = () => cfg.fwupd ? checkFirmwares() : merge()
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_widgets" : "start-here-kde-plasma-symbolic"
|
||||
sts.statusMsg = i18n("Checking widgets updates...")
|
||||
execute(bash('widgets', 'check'), (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "widgets", next)) return
|
||||
widgets = JSON.parse(out.trim())
|
||||
next()
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function checkFirmwares() {
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_package" : "apdatifier-package"
|
||||
sts.statusMsg = i18n("Checking firmware updates...")
|
||||
execute("fwupdmgr refresh --force", (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "fwupdmgr", merge)) return
|
||||
execute("fwupdmgr get-updates --json", (cmd, out, err, code) => {
|
||||
if (handleError(code, err, "fwupdmgr", merge)) return
|
||||
firmwares = JSON.parse(out).Devices.map(device => ({
|
||||
NM: `${device.Vendor}-${device.Name}`.replace(/ /g, "-").toLowerCase(),
|
||||
RE: "fwupd",
|
||||
DE: device.Releases?.[0]?.Summary || "",
|
||||
LN: device.Releases?.[0]?.Homepage || "",
|
||||
IN: device.Icons?.[0] || "",
|
||||
VO: device.Version || "",
|
||||
VN: device.Releases?.[0]?.Version || ""
|
||||
}))
|
||||
|
||||
merge()
|
||||
}, procOpt)
|
||||
}, procOpt)
|
||||
}
|
||||
|
||||
function merge() {
|
||||
const list = keys(archRepos.concat(archAur, flatpak, widgets, firmwares))
|
||||
|
||||
const finish = (finalList) => {
|
||||
sts.errors.length > 0
|
||||
? runLater(3000, () => isOnline ? finalize(finalList) : stopCheck())
|
||||
: finalize(finalList)
|
||||
}
|
||||
|
||||
loadIgnorePkgs().then(ignorePatterns => {
|
||||
if (!ignorePatterns || ignorePatterns.length === 0) {
|
||||
finish(list)
|
||||
} else {
|
||||
const filtered = list.filter(pkg => !ignorePatterns.some(re => re.test(pkg.NM)))
|
||||
finish(filtered)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function makeArchList(updates, source) {
|
||||
return new Promise((resolve) => {
|
||||
if (updates.length === 0) {
|
||||
resolve([])
|
||||
} else {
|
||||
const updateRe = /^([A-Za-z0-9@._+-]+)\s+(\S+)\s+->\s+(\S+)/
|
||||
const parsed = updates.map(l => l.trim()).map(l => l.match(updateRe)).filter(Boolean)
|
||||
if (parsed.length === 0) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
|
||||
const pkgs = parsed.map(m => m[1]).join(' ')
|
||||
execute(`LC_ALL=C.UTF-8 pacman -Sl --dbpath "${cfg.dbPath}" | awk 'index($0,"[installed:")>0'`, (cmd, out, err, code) => {
|
||||
if (code && handleError(code, err, source, () => resolve([]))) return
|
||||
const repositories = out.trim().split('\n')
|
||||
|
||||
execute("LC_ALL=C.UTF-8 pacman -Qi " + pkgs, (cmd, out, err, code) => {
|
||||
if (code && handleError(code, err, source, () => resolve([]))) return
|
||||
const descriptions = out.trim().split('\n\n')
|
||||
|
||||
execute(bash('utils', 'getIcons', pkgs), (cmd, out, err, code) => {
|
||||
if (code && handleError(code, err, source, () => resolve([]))) return
|
||||
const icons = out.split('\n')
|
||||
|
||||
const iconsMap = new Map()
|
||||
icons.map(l => l.split(' ', 2))
|
||||
.filter(([name, icon]) => name && icon)
|
||||
.forEach(([name, icon]) => iconsMap.set(name, icon))
|
||||
|
||||
const versionsMap = new Map()
|
||||
parsed.forEach(m => versionsMap.set(m[1], { currentVer: m[2], newVer: m[3] }))
|
||||
|
||||
const repositoriesMap = new Map()
|
||||
repositories.forEach(line => {
|
||||
const [repo, name] = line.trim().split(/\s+/)
|
||||
if (!repositoriesMap.has(name)) {
|
||||
repositoriesMap.set(name, repo)
|
||||
}
|
||||
})
|
||||
|
||||
const keyMap = {
|
||||
'Name': 'NM',
|
||||
'Description': 'DE',
|
||||
'URL': 'LN',
|
||||
'Groups': 'GR',
|
||||
'Provides': 'PR',
|
||||
'Depends On': 'DP',
|
||||
'Required By': 'RQ',
|
||||
'Optional For': 'OF',
|
||||
'Conflicts With': 'CF',
|
||||
'Replaces': 'RP',
|
||||
'Installed Size': 'IS',
|
||||
'Install Date': 'DT',
|
||||
'Install Reason': 'RN'
|
||||
}
|
||||
|
||||
const packagesData = descriptions.map(description => {
|
||||
const pkg = {}
|
||||
const lines = description.split('\n')
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(\w+(?:\s+\w+)*)\s+:\s*(.*)/)
|
||||
if (match) {
|
||||
const key = match[1].trim()
|
||||
const value = match[2].trim()
|
||||
if (value === "None") continue
|
||||
const mappedKey = keyMap[key]
|
||||
if (mappedKey) pkg[mappedKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (iconsMap.has(pkg.NM)) pkg.IN = iconsMap.get(pkg.NM)
|
||||
|
||||
const versions = versionsMap.get(pkg.NM) || {}
|
||||
pkg.VO = versions.currentVer || ""
|
||||
pkg.VN = (versions.newVer === "latest-commit") ? i18n("latest commit") : (versions.newVer || "")
|
||||
pkg.RE = repositoriesMap.get(pkg.NM) || (pkg.NM.endsWith("-git") || pkg.VN === i18n("latest commit") ? "devel" : "aur")
|
||||
|
||||
if (pkg.RN.includes("Explicitly")) {
|
||||
pkg.RN = "explicit"
|
||||
} else if (!pkg.RQ && !pkg.OF) {
|
||||
pkg.RN = "orphan"
|
||||
} else {
|
||||
pkg.RN = "dependency"
|
||||
}
|
||||
|
||||
delete pkg.OF
|
||||
|
||||
return pkg
|
||||
})
|
||||
|
||||
resolve([...new Map(packagesData.map(item => [item.NM, item])).values()])
|
||||
}, procOpt)
|
||||
}, procOpt)
|
||||
}, procOpt)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function makeFlatpakList(updates) {
|
||||
return new Promise((resolve) => {
|
||||
if (!updates) {
|
||||
resolve([])
|
||||
} else {
|
||||
execute("flatpak list --app --columns=application,version,active", (cmd, out, err, code) => {
|
||||
if (code && handleError(code, err, "flatpak", () => resolve([]))) return
|
||||
const description = out.trim().split("\n").reduce((obj, line) => {
|
||||
const [ID, VO, AC] = line.split("\t").map(entry => entry.trim())
|
||||
obj[ID] = { VO, AC }
|
||||
return obj
|
||||
}, {})
|
||||
const extendedList = updates.split("\n").map(line => {
|
||||
const [NM, DE, ID, VN, BR, , RE, , CM, RT, IS, DS] = line.split("\t").map(entry => entry.trim())
|
||||
const { VO, AC } = description[ID]
|
||||
return {
|
||||
NM: NM.replace(/ /g, "-").toLowerCase(),
|
||||
DE, LN: "https://flathub.org/apps/" + ID,
|
||||
ID, BR, RE, AC, CM, RT, IS, DS, VO,
|
||||
VN: VO === VN ? i18n("latest commit") : VN
|
||||
}
|
||||
})
|
||||
resolve(extendedList)
|
||||
}, procOpt)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function sortList(list, byName) {
|
||||
if (!list) return
|
||||
|
||||
return list.sort((a, b) => {
|
||||
const name = a.NM.localeCompare(b.NM)
|
||||
const repo = a.RE.localeCompare(b.RE)
|
||||
if (byName || !cfg.sorting) return name
|
||||
|
||||
if (a.IM !== b.IM) return a.IM ? -1 : 1
|
||||
|
||||
const develA = a.RE.includes("devel")
|
||||
const develB = b.RE.includes("devel")
|
||||
if (develA !== develB) return develA ? -1 : 1
|
||||
|
||||
const aurA = a.RE.includes("aur")
|
||||
const aurB = b.RE.includes("aur")
|
||||
if (aurA !== aurB) return aurA ? -1 : 1
|
||||
|
||||
return repo || name
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function refreshListModel(list) {
|
||||
list = sortList(applyRules(list || cache)) || []
|
||||
sts.count = list.length || 0
|
||||
setStatusBar()
|
||||
|
||||
if (!list) return
|
||||
|
||||
listModel.clear()
|
||||
list.forEach(item => listModel.append(item))
|
||||
}
|
||||
|
||||
|
||||
function finalize(list) {
|
||||
sts.busy = false
|
||||
resumeScheduler()
|
||||
|
||||
sts.lastCheck = Date.now()
|
||||
saveTimestamp()
|
||||
|
||||
if (cfg.notifyErrors && sts.error) {
|
||||
const notifyMsg = sts.errors.map(err => `<b>${err.type}</b> => ${err.message} (Exit code ${err.code})`).join('\n\n')
|
||||
notify.send("error", i18np("%1 error occurred", "%1 errors occurred", sts.errors.length), notifyMsg)
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
listModel.clear()
|
||||
execute(writeFile("[]", '>', cacheFile))
|
||||
cache = []
|
||||
sts.count = 0
|
||||
setStatusBar()
|
||||
return
|
||||
}
|
||||
|
||||
if (sts.error && cache.length > 0) {
|
||||
var errorTypes = {}
|
||||
for (var i = 0; i < sts.errors.length; i++) errorTypes[sts.errors[i].type] = true
|
||||
|
||||
var currentNames = {}
|
||||
for (var j = 0; j < list.length; j++) currentNames[list[j].NM] = true
|
||||
|
||||
var cachedPackages = []
|
||||
for (var k = 0; k < cache.length; k++) {
|
||||
var cached = cache[k]
|
||||
if (currentNames[cached.NM]) continue
|
||||
|
||||
if (errorTypes.repositories && cached.RE && cached.RE !== "aur" && cached.RE !== "devel") {
|
||||
cachedPackages.push(cached)
|
||||
} else if (errorTypes.aur && (cached.RE === "aur" || cached.RE === "devel")) {
|
||||
cachedPackages.push(cached)
|
||||
} else if (errorTypes.flatpak && cached.ID) {
|
||||
cachedPackages.push(cached)
|
||||
} else if (errorTypes.widgets && cached.CN) {
|
||||
cachedPackages.push(cached)
|
||||
}
|
||||
}
|
||||
|
||||
list = list.concat(cachedPackages)
|
||||
}
|
||||
|
||||
refreshListModel(list)
|
||||
|
||||
if (cfg.notifyUpdates) {
|
||||
const cached = new Map(cache.map(el => [el.NM, el.VN]))
|
||||
const newList = applyRules(list).filter(el => !cached.has(el.NM) || (cfg.notifyEveryBump && cached.get(el.NM) !== el.VN))
|
||||
|
||||
if (newList.length > 0) {
|
||||
const title = i18np("+%1 new update", "+%1 new updates", newList.length)
|
||||
const body = newList.map(pkg => `${pkg.NM} → ${pkg.VN}`).join("\n")
|
||||
notify.send("updates", title, body)
|
||||
}
|
||||
}
|
||||
|
||||
cache = list
|
||||
saveCache(cache)
|
||||
}
|
||||
|
||||
function saveCache(list) {
|
||||
if (JSON.stringify(list).length > 130000) {
|
||||
let start = 0
|
||||
const chunkSize = 200
|
||||
const json = JSON.stringify(keys(sortList(JSON.parse(JSON.stringify(list)), true))).replace(/},/g, "},\n").replace(/'/g, "")
|
||||
const lines = json.split("\n")
|
||||
while (start < lines.length) {
|
||||
const chunk = lines.slice(start, start + chunkSize).join("\n")
|
||||
const redir = start === 0 ? ">" : ">>"
|
||||
execute(writeFile(chunk, redir, `${cacheFile}_${Math.ceil(start / chunkSize)}`))
|
||||
start += chunkSize
|
||||
}
|
||||
execute(bash('utils', 'combineFiles', cacheFile))
|
||||
} else {
|
||||
const json = toFileFormat(keys(sortList(JSON.parse(JSON.stringify(list)), true)))
|
||||
execute(writeFile(json, '>', cacheFile))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setStatusBar() {
|
||||
if (sts.count > 0) {
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_pending" : "accept_time_event"
|
||||
sts.statusMsg = sts.count + " " + i18np("update is pending", "updates are pending", sts.count)
|
||||
} else {
|
||||
sts.statusIco = cfg.ownIconsUI ? "status_blank" : ""
|
||||
sts.statusMsg = ""
|
||||
}
|
||||
}
|
||||
|
||||
function resumeScheduler() {
|
||||
if (cfg.checkMode !== "manual") {
|
||||
scheduler.start()
|
||||
}
|
||||
}
|
||||
|
||||
function searchScheduler(options) {
|
||||
if (!isOnline) return
|
||||
if (isMetered) return
|
||||
|
||||
const mode = cfg.checkMode
|
||||
if (mode === "manual") {
|
||||
scheduler.stop()
|
||||
return
|
||||
}
|
||||
|
||||
const currTime = new Date().getTime()
|
||||
const lastCheck = sts.lastCheck || 0
|
||||
let nextCheck = null
|
||||
|
||||
if (mode === "interval") {
|
||||
const interval = parseFloat(cfg.intervalMinutes)
|
||||
const intervalMs = interval * 60 * 1000
|
||||
nextCheck = lastCheck ? (lastCheck + intervalMs) : (currTime + intervalMs)
|
||||
} else if (mode === "daily") {
|
||||
const scheduled = new Date(currTime)
|
||||
scheduled.setHours(cfg.dailyHour, cfg.dailyMinute, 0, 0)
|
||||
if (scheduled.getTime() <= currTime) scheduled.setDate(scheduled.getDate() + 1)
|
||||
nextCheck = scheduled.getTime()
|
||||
} else if (mode === "weekly") {
|
||||
const scheduled = new Date(currTime)
|
||||
const targetDay = Number(cfg.weeklyDay)
|
||||
const daysAhead = (targetDay - scheduled.getDay() + 7) % 7
|
||||
scheduled.setDate(scheduled.getDate() + daysAhead)
|
||||
scheduled.setHours(cfg.weeklyHour, cfg.weeklyMinute, 0, 0)
|
||||
if (scheduled.getTime() <= currTime) scheduled.setDate(scheduled.getDate() + 7)
|
||||
nextCheck = scheduled.getTime()
|
||||
}
|
||||
|
||||
options = options || {}
|
||||
if (options.simulate) return nextCheck
|
||||
|
||||
if (nextCheck && nextCheck <= (currTime + 10000) && nextCheck > lastCheck) {
|
||||
checkUpdates()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
function getCheckTime() {
|
||||
const currTime = new Date().getTime()
|
||||
const lastCheck = sts.lastCheck || currTime
|
||||
|
||||
const formatDelta = (ms1, ms2) => {
|
||||
const diff = Math.max(0, Math.floor((ms1 - ms2) / 1000))
|
||||
const days = Math.floor(diff / 86400)
|
||||
const hours = Math.floor((diff % 86400) / 3600)
|
||||
const minutes = Math.floor((diff % 3600) / 60)
|
||||
const seconds = diff % 60
|
||||
const parts = []
|
||||
if (days > 0) parts.push([days, i18np("%1 day", "%1 days", days)])
|
||||
if (hours > 0) parts.push([hours, i18np("%1 hour", "%1 hours", hours)])
|
||||
if (minutes > 0) parts.push([minutes, i18np("%1 minute", "%1 minutes", minutes)])
|
||||
if (parts.length === 0 && seconds > 0) return i18n("less than a minute")
|
||||
if (seconds > 0) parts.push([seconds, i18np("%1 second", "%1 seconds", seconds)])
|
||||
const take = parts.slice(0, 2).map(p => p[1])
|
||||
return take.join(' ')
|
||||
}
|
||||
|
||||
const lastCheckStr = (currTime > lastCheck) ? `${i18n("Last check:")} ${formatDelta(currTime, lastCheck)} ${i18n("ago")}` : ""
|
||||
|
||||
if (!scheduler.running) return lastCheckStr
|
||||
|
||||
const nextCheck = searchScheduler({ simulate: true })
|
||||
if (!nextCheck) return lastCheckStr
|
||||
|
||||
const nextCheckStr = (currTime < nextCheck) ? `${i18n("Next check in:")} ${formatDelta(nextCheck, currTime)}` : ""
|
||||
|
||||
if (lastCheckStr && nextCheckStr) return `${lastCheckStr}\n${nextCheckStr}`
|
||||
if (lastCheckStr && !nextCheckStr) return lastCheckStr
|
||||
if (!lastCheckStr && nextCheckStr) return nextCheckStr
|
||||
}
|
||||
|
||||
|
||||
function setIndex(value, arr) {
|
||||
let index = 0
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]["value"] == value) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
const defaultIcon = "apdatifier-plasmoid"
|
||||
function setIcon(icon) {
|
||||
return icon === "" ? defaultIcon : icon
|
||||
}
|
||||
|
||||
|
||||
function applyRules(list) {
|
||||
const rules = !cfg.rules ? [] : JSON.parse(cfg.rules)
|
||||
|
||||
list.forEach(el => {
|
||||
el.IC = el.IN ? el.IN : el.ID ? el.ID : "apdatifier-package"
|
||||
el.EX = false
|
||||
el.IM = false
|
||||
})
|
||||
|
||||
function applyRule(el, rule) {
|
||||
const types = {
|
||||
'name' : () => el.NM === rule.value,
|
||||
'regex' : () => { try { return new RegExp(rule.value).test(el.NM) } catch(e) { return false } }
|
||||
}
|
||||
|
||||
if (types[rule.type] && types[rule.type]()) {
|
||||
el.IC = rule.icon
|
||||
el.EX = rule.excluded
|
||||
el.IM = rule.important
|
||||
}
|
||||
}
|
||||
|
||||
rules.forEach(rule => list.forEach(el => applyRule(el, rule)))
|
||||
return list.filter(el => !el.EX)
|
||||
}
|
||||
|
||||
|
||||
function keys(list) {
|
||||
const keysList = ["GR", "PR", "DP", "RQ", "CF", "RP", "IS", "DT", "RN", "ID", "BR", "AC", "CM", "RT", "DS", "CN", "AU"]
|
||||
|
||||
list.forEach(el => {
|
||||
keysList.forEach(key => {
|
||||
if (!el.hasOwnProperty(key)) el[key] = ""
|
||||
else if (el[key] === "") delete el[key]
|
||||
})
|
||||
|
||||
if (el.hasOwnProperty("IC")) delete el["IC"]
|
||||
if (el.hasOwnProperty("EX")) delete el["EX"]
|
||||
if (el.hasOwnProperty("IM")) delete el["IM"]
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
function switchScheduler() {
|
||||
if (cfg.checkMode === "manual" || sts.busy) return
|
||||
scheduler.running ? scheduler.stop() : scheduler.start()
|
||||
}
|
||||
|
||||
function toFileFormat(obj) {
|
||||
const jsonStringWithSpace = JSON.stringify(obj, null, 2)
|
||||
const writebleJsonStrings = jsonStringWithSpace.replace(/'/g, "")
|
||||
return writebleJsonStrings
|
||||
}
|
||||
|
||||
function validJSON(string, file) {
|
||||
try {
|
||||
const json = JSON.parse(string)
|
||||
if (json && typeof json === "object") return json
|
||||
}
|
||||
catch (e) {
|
||||
file ? Error(1, `JSON data at ${file} is corrupted or broken and cannot be processed`)
|
||||
: Error(1, "JSON data is broken and cannot be processed")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function isValidFeedUrl(value) {
|
||||
return /^https?:\/\/\S+$/i.test((value || "").trim())
|
||||
}
|
||||
|
||||
function parseCustomFeeds(value) {
|
||||
return String(value || "")
|
||||
.split(/[\n|]/)
|
||||
.map(feed => feed.trim())
|
||||
.filter(isValidFeedUrl)
|
||||
}
|
||||
|
||||
function serializeCustomFeeds(value) {
|
||||
return (Array.isArray(value) ? value : parseCustomFeeds(value))
|
||||
.map(feed => feed.trim())
|
||||
.filter((feed, index, feeds) => isValidFeedUrl(feed) && feeds.indexOf(feed) === index)
|
||||
.join("|")
|
||||
}
|
||||
|
||||
|
||||
|
||||
function modelToArray(model) {
|
||||
const array = new Array(model.count)
|
||||
for (let i = 0; i < model.count; i++) {
|
||||
array[i] = model.get(i)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
function findInsertIndex(news) {
|
||||
const n = newsModel.count
|
||||
let firstRemoved = n
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (newsModel.get(i).removed) {
|
||||
firstRemoved = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const start = news.removed ? firstRemoved : 0
|
||||
const end = news.removed ? n : firstRemoved
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
if (newsModel.get(i).timestamp < news.timestamp)
|
||||
return i
|
||||
}
|
||||
|
||||
return end
|
||||
}
|
||||
|
||||
function saveNews() {
|
||||
execute(writeFile(toFileFormat(modelToArray(newsModel)), '>', newsFile))
|
||||
}
|
||||
|
||||
function addNewsItem(news) {
|
||||
newsModel.insert(findInsertIndex(news), news)
|
||||
}
|
||||
|
||||
function removeNewsItem(index) {
|
||||
const item = newsModel.get(index)
|
||||
if (!item || item.removed) return
|
||||
|
||||
let to = findInsertIndex({ timestamp: item.timestamp, removed: true })
|
||||
if (to > index) to--
|
||||
|
||||
newsModel.move(index, to, 1)
|
||||
newsModel.setProperty(to, "removed", true)
|
||||
|
||||
saveNews()
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 23 23">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #232629; }
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="m22.43,12.56s-.05-.04-.07-.07c-.04-.04-.07-.08-.12-.11,0,0,0,0,0,0,0,0,0,0,0,0l-4.97-2.6v-5.2c0-.09-.03-.17-.07-.24-.02-.03-.05-.04-.07-.07-.04-.04-.07-.08-.12-.11,0,0,0,0,0,0,0,0,0,0,0,0l-4.75-2.48c-.47-.24-1.04-.24-1.5,0l-4.75,2.48s0,0,0,0c0,0,0,0,0,0-.05.03-.08.07-.12.11-.02.02-.06.04-.07.07-.05.07-.07.15-.07.24v5.2L.76,12.38s0,0,0,0c0,0,0,0,0,0-.06.03-.09.08-.13.12-.02.02-.04.03-.06.05-.05.07-.07.15-.07.24v5.48c0,.17.1.33.26.42l5.24,2.74c.08.04.17.06.26.06s.18-.02.26-.06l4.98-2.6,4.98,2.6c.08.04.17.06.26.06s.18-.02.26-.06l5.24-2.74c.16-.09.26-.24.26-.42v-5.48c0-.09-.03-.17-.07-.24Zm-16.9,2.05l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm5.44,2.75c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86Zm-.2-10.97l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm1.25,4.71v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44Zm3.99,3.51l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm5.44,2.75c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 23 23">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #232629; }
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="m21.5,6.21c0-.06-.04-.11-.06-.18-.02-.09-.04-.19-.09-.27-.04-.06-.11-.1-.16-.15-.06-.06-.11-.13-.19-.17,0,0-.02,0-.03,0,0,0-.01-.01-.02-.02l-7.71-3.55c-1.1-.51-2.39-.51-3.49,0l-7.71,3.55s-.01.01-.02.02c0,0-.02,0-.03,0-.08.04-.12.11-.19.17-.05.05-.12.09-.16.15-.05.08-.07.17-.09.27-.02.06-.06.11-.06.18,0,0,0,0,0,.01,0,0,0,0,0,.01v9.57c0,.32.19.62.49.78l9.01,4.79c.15.08.32.12.49.12s.34-.04.49-.12l8.5-4.51c.62-.33,1.01-.94,1.01-1.6V6.24s0,0,0-.01c0,0,0,0,0-.01Zm-16.07-.32l5.2-2.4c.55-.25,1.19-.25,1.73,0l5.2,2.4c.37.17.39.66.03.85l-5.13,2.72c-.6.32-1.33.32-1.93,0l-5.13-2.72c-.36-.19-.35-.68.03-.85Zm14.1,8.5c0,.55-.32,1.06-.84,1.33l-5.44,2.88c-.34.18-.77-.05-.77-.41v-5.77c0-.55.32-1.05.83-1.33l3.24-1.72,2.2-1.16c.34-.18.77.05.77.41v5.77Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #232629; }
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor"
|
||||
d="m9.6094 13.32c-.68385 0-1.25.56615-1.25 1.25v.12891c0 .68385.56615 1.25 1.25 1.25h4.791c.68385 0 1.25-.56615 1.25-1.25v-.12891c0-.68385-.56615-1.25-1.25-1.25zm0 1h4.791c.14715 0 .25.10285.25.25v.12891c0 .14715-.10285.25-.25.25h-4.791c-.14715 0-.25-.10285-.25-.25v-.12891c0-.14715.10285-.25.25-.25z"/>
|
||||
<g stroke-linecap="round" stroke-linejoin="round" class="ColorScheme-Text" style="fill:currentColor">
|
||||
<path d="m4.9707 10.02a.75008.75008 0 00-.75.75v9.0996c0 1.0232.84592 1.8711 1.8691 1.8711h11.91c1.0232 0 1.8691-.84787 1.8691-1.8711v-9.0996a.75008.75008 0 00-.75-.75zm.75 1.5h12.648v8.3496c0 .21677-.15237.37109-.36914.37109h-11.91c-.21677 0-.36914-.15432-.36914-.37109z"/>
|
||||
<path d="m7.5098 7.1602a.75008.75008 0 00-.56055.25195l-2.5391 2.8594a.75008.75008 0 00.56055 1.248h14.148a.75008.75008 0 00.5625-1.248l-2.541-2.8594a.75008.75008 0 00-.56055-.25195zm.33594 1.5h8.3965l1.207 1.3594h-10.811z"/>
|
||||
<path d="m15.746 3.6992-7.4453.041016c-.57783 0-1.1067.42379-1.209 1.0117a.75008.75008 0 00-.00781.046875l-.20703 1.9082-2.3945-1.375c-.37855-.23296-.83629-.23296-1.2148 0a.75008.75008 0 00-.00781.00391l-2.7324 1.7246c-.00054767.000337.00054683.00162 0 .00195-.71708.44295-.71521 1.5381.005859 1.9707a.75008.75008 0 00.003906.00195l4.0313 2.3906a.75.75 0 001.0273-.26367.75.75 0 00-.26367-1.0273l-3.5391-2.0996 2.0898-1.3184 3.2441 1.8633a.75008.75008 0 001.1191-.57031l.30078-2.7715 6.9668-.037109.31055 2.8105a.75008.75008 0 001.127.56445l3.1641-1.8613 2.0918 1.3145-3.4746 2.0996a.75.75 0 00-.25391 1.0293.75.75 0 001.0293.25391l3.9336-2.377c.72984-.4325.72984-1.5675 0-2l-2.7031-1.6992a.75008.75008 0 00-.0059-.00391c-.35475-.21833-.84467-.24519-1.2382.0078162l-2.3047 1.3555-.21484-1.9277a.75008.75008 0 00-.0059-.046875c-.10095-.5807-.61862-1.0458-1.25-1.0195z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #232629; }
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="m 19.854756,20.481011 c -1.262396,-0.531251 -2.820748,-1.029352 -4.628787,-1.322602 -0.496751,-0.08075 -0.849454,-0.542301 -0.763897,-1.025102 0.05151,-0.28815 0.0777,-0.5899 0.0777,-0.905251 0,-1.996653 -1.097395,-3.618455 -2.445348,-3.618455 -1.357556,0 -2.4549513,1.621802 -2.4549513,3.618455 0,0.30005 0.025317,0.594151 0.074207,0.875501 0.082937,0.485351 -0.2697652,0.948602 -0.7691364,1.025102 -1.8831212,0.28815 -3.5008395,0.811751 -4.8121254,1.361702 -0.7988193,0.1122 -1.4439859,-0.777751 -0.9725516,-1.490052 0.1152394,-0.169151 0.231352,-0.346801 0.3369882,-0.525301 0.2793686,-0.430951 0.5587371,-0.862751 0.8276293,-1.312402 1.2327135,-1.977953 2.464554,-4.125056 3.6588544,-6.431109 0.028809,-0.0561 0.05762,-0.122401 0.086429,-0.178501 C 8.3203287,10.075296 8.560411,9.5967448 8.7917631,9.1190441 8.9070025,8.8937938 9.0231151,8.6591935 9.1287513,8.4339431 9.3120869,8.5461433 9.4945495,8.6770435 9.706695,8.8087937 10.669644,9.3901945 11.574972,9.718295 12.229743,9.8865952 11.748705,9.5304447 11.218778,9.1088441 10.679247,8.5835434 10.274162,8.1899928 9.9179674,7.7964422 9.6097889,7.4122417 10.062017,6.4372903 10.495912,5.4810389 10.880916,4.5528376 c 0.06722,-0.1496002 0.134447,-0.3000505 0.192939,-0.4403007 0.336988,-0.8151511 1.502479,-0.8151511 1.84907,-0.00935 0.163257,0.3842006 0.327385,0.7777511 0.501118,1.1721517 0.144049,0.3468005 0.308178,0.693601 0.471434,1.0497515 0.105636,0.2431004 0.221748,0.4964007 0.336989,0.7497011 0.231352,0.5066007 0.471434,1.0123514 0.721992,1.5283022 0.231352,0.4972507 0.481038,0.9936514 0.7412,1.4994026 0,0.0187 0.0096,0.02805 0.01921,0.04675 0.616356,1.228251 1.25192,2.399553 1.897086,3.534305 0.461831,0.833851 0.934139,1.640502 1.415176,2.419103 0.164128,0.290701 0.336989,0.572051 0.520325,0.853402 0.124842,0.1972 0.240954,0.39355 0.365797,0.5814 h -0.01048 c -0.336988,-0.17765 -0.702787,-0.3468 -1.097395,-0.49725 -0.683579,-0.262651 -1.319143,-0.421601 -1.868276,-0.515951 0.750802,0.365501 1.617718,0.844051 2.523047,1.481552 0.558736,0.402901 1.05898,0.796451 1.482399,1.190852 0.250558,0.674901 -0.356196,1.405902 -1.087792,1.284352 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23"><path d=""/></svg>
|
||||
|
After Width: | Height: | Size: 92 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m11.5,1.63C5.99,1.63,1.5,6.11,1.5,11.63s4.49,10,10,10,10-4.49,10-10S17.01,1.63,11.5,1.63Zm-7.26,10c0-4,3.26-7.26,7.26-7.26,1.56,0,3,.5,4.19,1.34L5.56,15.78c-.83-1.18-1.32-2.61-1.32-4.16Zm7.26,7.26c-1.46,0-2.83-.44-3.97-1.19l10.06-10c.74,1.13,1.17,2.48,1.17,3.93,0,4-3.26,7.26-7.26,7.26Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m22.43,12.56s-.05-.04-.07-.07c-.04-.04-.07-.08-.12-.11,0,0,0,0,0,0,0,0,0,0,0,0l-4.97-2.6v-5.2c0-.09-.03-.17-.07-.24-.02-.03-.05-.04-.07-.07-.04-.04-.07-.08-.12-.11,0,0,0,0,0,0,0,0,0,0,0,0l-4.75-2.48c-.47-.24-1.04-.24-1.5,0l-4.75,2.48s0,0,0,0c0,0,0,0,0,0-.05.03-.08.07-.12.11-.02.02-.06.04-.07.07-.05.07-.07.15-.07.24v5.2L.76,12.38s0,0,0,0c0,0,0,0,0,0-.06.03-.09.08-.13.12-.02.02-.04.03-.06.05-.05.07-.07.15-.07.24v5.48c0,.17.1.33.26.42l5.24,2.74c.08.04.17.06.26.06s.18-.02.26-.06l4.98-2.6,4.98,2.6c.08.04.17.06.26.06s.18-.02.26-.06l5.24-2.74c.16-.09.26-.24.26-.42v-5.48c0-.09-.03-.17-.07-.24Zm-16.9,2.05l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm5.44,2.75c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86Zm-.2-10.97l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm1.25,4.71v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44Zm3.99,3.51l-2.7-1.41c-.34-.18-.34-.62,0-.79l2.7-1.41c.45-.24,1-.24,1.45,0l2.7,1.41c.34.18.34.62,0,.79l-2.7,1.41c-.45.24-1,.24-1.45,0Zm5.44,2.75c0,.4-.23.76-.61.96l-2.75,1.44c-.37.19-.83-.05-.83-.44v-2.86c0-.4.23-.76.61-.96l2.75-1.44c.37-.19.83.05.83.44v2.86Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m18.18,3.3h-3.48c-.43-1.1-1.53-1.88-2.81-1.88s-2.37.78-2.81,1.88h-3.36c-1.53,0-2.77,1.2-2.77,2.68v12.75c0,1.48,1.24,2.68,2.77,2.68h12.46c1.53,0,2.77-1.2,2.77-2.68V5.98c0-1.48-1.24-2.68-2.77-2.68Zm-6.29-.35c.59,0,1.07.47,1.07,1.04s-.48,1.04-1.07,1.04-1.07-.47-1.07-1.04.48-1.04,1.07-1.04Zm-.27,14.89h-4.95c-.54,0-.98-.43-.98-.95s.44-.95.98-.95h4.95c.54,0,.98.43.98.95s-.44.95-.98.95Zm3.22-4.36H6.68c-.54,0-.98-.43-.98-.95s.44-.95.98-.95h8.17c.54,0,.98.43.98.95s-.44.95-.98.95Zm2.56-4.36H6.68c-.54,0-.98-.43-.98-.95s.44-.95.98-.95h10.73c.54,0,.98.43.98.95s-.44.95-.98.95Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 667 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m21.5,6.21c0-.06-.04-.11-.06-.18-.02-.09-.04-.19-.09-.27-.04-.06-.11-.1-.16-.15-.06-.06-.11-.13-.19-.17,0,0-.02,0-.03,0,0,0-.01-.01-.02-.02l-7.71-3.55c-1.1-.51-2.39-.51-3.49,0l-7.71,3.55s-.01.01-.02.02c0,0-.02,0-.03,0-.08.04-.12.11-.19.17-.05.05-.12.09-.16.15-.05.08-.07.17-.09.27-.02.06-.06.11-.06.18,0,0,0,0,0,.01,0,0,0,0,0,.01v9.57c0,.32.19.62.49.78l9.01,4.79c.15.08.32.12.49.12s.34-.04.49-.12l8.5-4.51c.62-.33,1.01-.94,1.01-1.6V6.24s0,0,0-.01c0,0,0,0,0-.01Zm-16.07-.32l5.2-2.4c.55-.25,1.19-.25,1.73,0l5.2,2.4c.37.17.39.66.03.85l-5.13,2.72c-.6.32-1.33.32-1.93,0l-5.13-2.72c-.36-.19-.35-.68.03-.85Zm14.1,8.5c0,.55-.32,1.06-.84,1.33l-5.44,2.88c-.34.18-.77-.05-.77-.41v-5.77c0-.55.32-1.05.83-1.33l3.24-1.72,2.2-1.16c.34-.18.77.05.77.41v5.77Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 839 B |
@@ -0,0 +1,4 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<polyline fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.13px" points="15.35 15.15 11.68 12.23 11.68 7.15"/>
|
||||
<path d="m22.5,11.61c0-.33-.02-.65-.05-.97,0-.01,0-.02,0-.03-.03-.31-.08-.61-.14-.92,0-.03,0-.05-.01-.08-.06-.27-.13-.54-.21-.8-.02-.06-.03-.11-.05-.17-.08-.24-.17-.48-.26-.72-.03-.07-.05-.13-.08-.2-.02-.04-.03-.08-.05-.12-.1-.23-.22-.46-.34-.68,0-.02-.02-.03-.02-.05-1.06-1.97-2.72-3.5-4.78-4.4-.05-.02-.09-.04-.14-.06-.07-.03-.14-.07-.22-.1-.19-.07-.37-.13-.56-.19-.04-.01-.08-.03-.13-.04-3.14-.98-6.44-.33-8.92,1.57-.12.09-.23.19-.34.29-.11.09-.22.17-.32.27-1.69,1.53-2.84,3.65-3.17,6.08h-1.34c-.78,0-1.17.96-.62,1.51l2.85,2.85c.33.33.87.33,1.21,0l2.83-2.86c.55-.56.16-1.51-.62-1.5h-1.54c.12-.64.32-1.25.6-1.82,0-.02.02-.03.03-.04.41-.83.96-1.55,1.61-2.15.15-.14.31-.27.47-.4,0,0,0,0,0,0,1.21-.92,2.73-1.47,4.37-1.47.74,0,1.46.11,2.13.32.01,0,.03,0,.04.01.21.07.41.14.61.22.03.01.05.02.07.03,1.49.65,2.71,1.78,3.48,3.2,0,0,0,0,0,.01.11.2.2.4.29.61.02.04.03.09.05.13.07.18.14.35.2.54.01.04.02.08.03.11.06.19.11.39.15.59,0,.02,0,.04,0,.06.04.22.08.44.1.67,0,0,0,.02,0,.03.02.23.03.46.04.7,0,.03,0,.05,0,.08,0,.21-.01.42-.03.62,0,.07-.02.14-.03.21-.02.16-.04.31-.07.47-.02.11-.05.23-.08.34-.03.11-.05.22-.08.33-.04.14-.09.28-.14.41-.03.08-.06.17-.09.25-.06.15-.12.29-.19.43-.04.08-.07.16-.12.24-.06.12-.13.24-.2.35-.05.09-.11.18-.17.27-.06.1-.14.19-.2.29-.07.1-.14.19-.21.29-.07.09-.15.18-.23.26-.08.08-.15.17-.23.25-.1.11-.21.2-.32.3-.06.05-.11.11-.17.16-.16.14-.33.27-.5.39,0,0-.02.01-.03.02-1.13.78-2.46,1.23-3.84,1.29-.03,0-.05,0-.08,0-.19,0-.38,0-.58-.01-.05,0-.11,0-.16,0-.2-.02-.39-.04-.59-.07-.04,0-.07-.01-.11-.02-.21-.04-.42-.08-.63-.14-.02,0-.04-.01-.06-.02-.22-.06-.43-.13-.65-.22-.02,0-.03-.01-.05-.02-.96-.38-1.81-.94-2.52-1.68-.53-.54-1.39-.55-1.93-.02-.54.53-.55,1.4-.02,1.94.87.89,1.88,1.59,3.01,2.1v.02c.13.06.28.11.42.17.03.01.06.03.09.04.03.01.06.02.09.03.24.09.48.17.72.24.07.02.13.04.2.06.24.06.48.12.72.17.08.01.16.02.23.04.23.04.47.08.71.1.06,0,.11,0,.17,0,.25.02.5.04.75.04.16,0,.31-.02.47-.02.11,0,.21,0,.32-.02.2-.02.39-.04.58-.07.07,0,.14-.02.21-.03.93-.15,1.81-.43,2.63-.82.05-.02.1-.05.14-.07.19-.09.37-.19.55-.29.06-.03.11-.07.17-.1.25-.15.49-.31.73-.48.14-.1.29-.21.42-.32.08-.07.16-.13.24-.2.16-.14.31-.28.46-.42.06-.05.11-.11.17-.17.19-.2.38-.4.55-.61,0-.01.02-.02.03-.03.2-.24.38-.49.56-.75.01-.02.02-.04.03-.05.15-.22.29-.46.42-.69.04-.08.08-.15.12-.23.09-.18.18-.37.27-.56.04-.1.09-.2.13-.3.02-.05.04-.1.06-.15.04-.1.06-.2.1-.3.06-.17.12-.33.17-.5.05-.17.09-.34.13-.51.03-.14.07-.27.09-.41.04-.23.08-.45.1-.68.01-.09.02-.17.03-.26.03-.29.04-.59.04-.88,0-.02,0-.05,0-.07,0,0,0-.01,0-.02Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m11.5,1.5C5.98,1.5,1.5,5.98,1.5,11.5s4.48,10,10,10,10-4.48,10-10S17.02,1.5,11.5,1.5Zm6.45,6.42l-6.94,8.96c-.34.44-.86.73-1.42.77-.06,0-.11,0-.17,0-.5,0-.99-.19-1.36-.53l-2.84-2.61c-.58-.53-.62-1.43-.09-2,.53-.58,1.43-.61,2-.09l2.19,2.01,6.39-8.25c.48-.62,1.37-.73,1.99-.25.62.48.73,1.37.25,1.99Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 393 B |
@@ -0,0 +1,6 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m14.49,15.49c-.46,0-.92-.2-1.24-.6-.58-.72-.5-1.8.18-2.41l3.92-3.5-3.93-3.55c-.68-.62-.76-1.7-.17-2.41.59-.71,1.62-.8,2.3-.18l5.38,4.85c.36.33.57.8.57,1.3,0,.5-.21.97-.57,1.3l-5.38,4.8c-.31.27-.68.41-1.06.41Z"/>
|
||||
<ellipse cx="7.09" cy="4.35" rx="1.51" ry="1.58"/>
|
||||
<ellipse cx="3.68" cy="11.77" rx="2.18" ry="2.28"/>
|
||||
<ellipse cx="8.48" cy="19.22" rx="3.04" ry="3.19"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 473 B |
@@ -0,0 +1,15 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 { fill: none; stroke: #000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2.13px; }
|
||||
</style>
|
||||
</defs>
|
||||
<ellipse cx="2.48" cy="5.61" rx="1.53" ry="1.5"/>
|
||||
<line class="cls-1" x1="8" y1="5.61" x2="20.94" y2="5.61"/>
|
||||
<ellipse cx="2.48" cy="9.61" rx="1.53" ry="1.5"/>
|
||||
<line class="cls-1" x1="8" y1="9.61" x2="20.94" y2="9.61"/>
|
||||
<ellipse cx="2.48" cy="13.61" rx="1.53" ry="1.5"/>
|
||||
<line class="cls-1" x1="8" y1="13.61" x2="20.94" y2="13.61"/>
|
||||
<ellipse cx="2.48" cy="17.61" rx="1.53" ry="1.5"/>
|
||||
<line class="cls-1" x1="8" y1="17.61" x2="20.94" y2="17.61"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 733 B |
@@ -0,0 +1,5 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m1.5,2.42v3.68c0,.45.32.82.71.82h18.58c.39,0,.71-.36.71-.82v-3.68c0-.45-.32-.82-.71-.82H2.21c-.39,0-.71.36-.71.82Zm2.14,3.07c-.63,0-1.14-.64-1.14-1.42s.51-1.42,1.14-1.42,1.14.64,1.14,1.42-.51,1.42-1.14,1.42Zm16.11-.43H7.53c-.39,0-.71-.36-.71-.82v-.34c0-.45.32-.82.71-.82h12.21c.39,0,.71.36.71.82v.34c0,.45-.32.82-.71.82Z"/>
|
||||
<path d="m1.5,9.77v3.68c0,.45.32.82.71.82h18.58c.39,0,.71-.36.71-.82v-3.68c0-.45-.32-.82-.71-.82H2.21c-.39,0-.71.36-.71.82Zm2.14,3.07c-.63,0-1.14-.64-1.14-1.42s.51-1.42,1.14-1.42,1.14.64,1.14,1.42-.51,1.42-1.14,1.42Zm16.11-.43H7.53c-.39,0-.71-.36-.71-.82v-.34c0-.45.32-.82.71-.82h12.21c.39,0,.71.36.71.82v.34c0,.45-.32.82-.71.82Z"/>
|
||||
<path d="m1.5,17.11v3.68c0,.45.32.82.71.82h18.58c.39,0,.71-.36.71-.82v-3.68c0-.45-.32-.82-.71-.82H2.21c-.39,0-.71.36-.71.82Zm2.14,3.07c-.63,0-1.14-.64-1.14-1.42s.51-1.42,1.14-1.42,1.14.64,1.14,1.42-.51,1.42-1.14,1.42Zm16.11-.43H7.53c-.39,0-.71-.36-.71-.82v-.34c0-.45.32-.82.71-.82h12.21c.39,0,.71.36.71.82v.34c0,.45-.32.82-.71.82Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m20.02,13.39c-.59,0-.93.29-1.07.68-.95,2.82-3.56,4.52-6.64,4.52-1.67,0-3.26-.58-4.52-1.64-.19-.16-.19-.44-.01-.61l1.5-1.46c.53-.52.16-1.42-.58-1.42l-5.62.1c-.55.01-1,.46-1,1.01v5.37c.18.8,1.1.88,1.54.45l1.39-1.36c.16-.15.4-.15.56,0,1.86,1.66,4.23,2.58,6.74,2.58,5.25,0,8.46-3.36,9.71-7.7.08-.26-.13-.53-.41-.53h-1.6Z"/>
|
||||
<path d="m4.07,9.82c.59,0,.93-.29,1.07-.68.95-2.82,3.56-4.52,6.64-4.52,1.68,0,3.26.58,4.53,1.64.19.15.19.44.01.61l-1.5,1.46c-.53.52-.16,1.42.58,1.42l5.62-.1c.55-.01.99-.46.99-1.01V3.28c-.18-.8-1.1-.88-1.54-.45l-1.39,1.36c-.16.15-.4.15-.56,0-1.86-1.67-4.23-2.58-6.75-2.58-5.25,0-8.46,3.37-9.71,7.71-.08.26.13.53.41.53h1.6Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1,5 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m9.72,11.51h13.28v-3.68c0-1.31-1.04-2.37-2.33-2.37h-4.1v-2.09c0-1.09-.88-1.98-1.95-1.98h-6.72c-1.08,0-1.95.89-1.95,1.98v2.09h-3.6c-1.29,0-2.33,1.06-2.33,2.37v3.68h9.72Zm-1.66-7.97h6.37v1.91h-6.37v-1.91Z"/>
|
||||
<path d="m9.72,12.93h3.71c.16,0,.29.13.29.29v.67c0,.16-.13.29-.29.29h-3.71c-.16,0-.29-.13-.29-.29v-.67c0-.16.13-.29.29-.29Z"/>
|
||||
<path d="m15.12,12.93c.02.1.03.19.03.29v.67c0,.94-.77,1.71-1.71,1.71h-3.71c-.94,0-1.71-.77-1.71-1.71v-.67c0-.1.01-.2.03-.29H0v6.08c0,1.31,1.04,2.37,2.33,2.37h18.33c1.29,0,2.33-1.06,2.33-2.37v-6.08h-7.88Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 642 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m11.5,1.51C5.98,1.51,1.5,5.99,1.5,11.51s4.48,10,10,10,10-4.48,10-10S17.02,1.51,11.5,1.51Zm-1.48,13.45c0,.88-.62,1.59-1.4,1.59s-1.4-.71-1.4-1.59v-6.91c0-.88.62-1.59,1.4-1.59s1.4.71,1.4,1.59v6.91Zm5.75,0c0,.88-.62,1.59-1.4,1.59s-1.4-.71-1.4-1.59v-6.91c0-.88.62-1.59,1.4-1.59s1.4.71,1.4,1.59v6.91Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
@@ -0,0 +1,12 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #000000; }
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="m 4.0214839,5.02e-5 a 0.9947634,1.0001 0 1 0 0,2 h 0.978516 v 3 c 0,1.328 -1.695715,2 -2.9999998,2 v 2 H 6.9999999 V 15.00005 l 0.976562,1 1.023438,-1 V 9.0000502 H 14 v -2 c -1.304286,0 -3.055749,-0.673171 -3,-2 v -3 h 0.978516 a 0.9947634,1.0001 0 1 0 0,-2 z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 567 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m20.88,17.92l-3.29-3.28c.85-1.32,1.35-2.88,1.35-4.56,0-4.73-3.91-8.57-8.72-8.57S1.5,5.35,1.5,10.08s3.91,8.57,8.72,8.57c1.55,0,3-.4,4.26-1.1.01.01.02.03.03.04l3.31,3.3c.41.4.95.63,1.53.63.55,0,1.07-.2,1.47-.57.43-.39.67-.92.68-1.5.01-.57-.21-1.11-.62-1.52ZM3.98,10.08c0-3.36,2.8-6.09,6.24-6.09s6.24,2.73,6.24,6.09-2.8,6.09-6.24,6.09-6.24-2.73-6.24-6.09Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,4 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="2 2 18 18">
|
||||
<path d="m9.2502 3.0001c-0.51888 1.549e-4 -0.95146 0.39711-0.99609 0.91406l-0.11719 1.3398c-0.23504 0.11806-0.46067 0.24888-0.67969 0.39453l-1.2207-0.57422c-0.47103-0.22177-1.0334-0.045069-1.293 0.40625l-1.75 3.0293c-0.25943 0.44992-0.13129 1.0235 0.29492 1.3203l1.0488 0.73242c-0.010017 0.14609-0.036985 0.29094-0.037109 0.4375 1.241e-4 0.14591 0.02718 0.2901 0.037109 0.43555l-1.0449 0.7207c-0.42981 0.29586-0.55992 0.87245-0.29883 1.3242l1.75 3.0293c0.2603 0.45166 0.82388 0.62761 1.2949 0.4043l1.2109-0.57422c0.22158 0.14673 0.44964 0.27991 0.6875 0.39844l0.11719 1.3477c0.044639 0.51695 0.47722 0.91391 0.99609 0.91406h3.5c0.52095-4.1e-4 0.95433-0.40065 0.99609-0.91992l0.10938-1.3438c0.23718-0.11834 0.46457-0.25009 0.68555-0.39648l1.2227 0.57422c0.47054 0.2221 1.0328 0.0463 1.293-0.4043l1.75-3.0293c0.26026-0.45085 0.13114-1.0259-0.29688-1.3223l-1.0508-0.72852c0.01082-0.13896 0.03915-0.27653 0.04102-0.41602 3.1e-5 -0.0046 3.1e-5 -0.0091 0-0.01367 1.45e-4 -0.14944-0.02697-0.29635-0.03711-0.44531l1.0449-0.72266c0.4287-0.29572 0.5587-0.87093 0.29883-1.3223l-1.75-3.0293c-0.25955-0.45132-0.82194-0.62802-1.293-0.40625l-1.2227 0.57617c-0.22098-0.14639-0.44836-0.27814-0.68555-0.39648l-0.10938-1.3359c-0.04273-0.5185-0.47584-0.91764-0.99609-0.91797zm0.91602 2h1.6621l0.07422 0.92188c0.03216 0.3873 0.28579 0.72091 0.65039 0.85547 0.4791 0.17704 0.92563 0.43516 1.3184 0.76172 0.29825 0.24821 0.71316 0.3015 1.0645 0.13672l0.84766-0.40039 0.83008 1.4355-0.76172 0.52539c-0.32136 0.22144-0.48455 0.60959-0.41797 0.99414 0.04336 0.25146 0.06621 0.50656 0.06641 0.76172-0.0037 0.25058-0.02895 0.50156-0.07422 0.74805-0.07068 0.38631 0.09129 0.77824 0.41406 1.002l0.77344 0.53516-0.83008 1.4375-0.84766-0.40039c-0.3513-0.16478-0.7662-0.11149-1.0645 0.13672-0.39273 0.32656-0.83926 0.58468-1.3184 0.76172-0.3646 0.13456-0.61823 0.46816-0.65039 0.85547l-0.07422 0.93164h-1.6602l-0.08203-0.93555c-0.03328-0.38536-0.2858-0.71699-0.64844-0.85156-0.4791-0.17704-0.92563-0.43516-1.3184-0.76172-0.29906-0.24836-0.71496-0.30092-1.0664-0.13477l-0.83789 0.39648-0.82812-1.4336 0.76172-0.52734c0.32064-0.22112 0.48374-0.60829 0.41797-0.99219-0.043342-0.25132-0.06619-0.50473-0.066407-0.75977 2.169e-4 -0.25503 0.023066-0.50844 0.066406-0.75977 0.065792-0.38246-0.095608-0.76845-0.41406-0.99023l-0.76758-0.53516 0.83203-1.4395 0.84766 0.40039c0.35219 0.16508 0.76815 0.11099 1.0664-0.13867 0.39026-0.32576 0.83238-0.58189 1.3086-0.75977 0.36187-0.13518 0.61353-0.46668 0.64648-0.85156z"/>
|
||||
<circle cx="11" cy="11" r="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m9.15,17.22h-2.11V2.27c0-.42-.38-.77-.86-.77h-1.15c-.47,0-.86.34-.86.77v14.96h-2.11c-.49,0-.74.52-.4.84l3.55,3.29c.22.2.58.2.8,0l3.55-3.29c.34-.31.09-.84-.4-.84Z"/>
|
||||
<path d="m19.62,9.26l-.42-1.08h-3.54l-.42,1.1c-.16.43-.3.72-.42.87s-.3.23-.56.23c-.22,0-.42-.08-.59-.24s-.26-.34-.26-.54c0-.12.02-.24.06-.36s.1-.3.2-.52l2.23-5.58c.06-.16.14-.35.23-.58s.18-.41.28-.56.23-.27.4-.36.37-.14.61-.14.45.05.61.14.3.21.4.35.19.3.26.47.16.39.26.67l2.27,5.54c.18.42.27.73.27.92s-.08.38-.25.55-.37.25-.61.25c-.14,0-.26-.02-.36-.07s-.18-.12-.25-.2-.14-.21-.22-.39-.14-.33-.2-.46Zm-3.49-2.38h2.6l-1.31-3.54-1.29,3.54Z"/>
|
||||
<path d="m14.27,19.5l4.47-5.28h-3.84c-.27,0-.47-.06-.61-.18s-.2-.27-.2-.46.07-.36.2-.48.34-.18.61-.18h4.95c.64,0,.96.29.96.88,0,.28-.05.51-.16.67s-.32.44-.65.81l-4.18,4.91h4.61c.27,0,.48.06.61.17s.2.27.2.47-.07.37-.2.49-.34.18-.61.18h-5.65c-.38,0-.67-.08-.87-.25s-.29-.4-.29-.68c0-.1.02-.19.05-.27s.07-.16.13-.23.13-.16.22-.28.18-.21.25-.29Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m11.5,1.51C5.98,1.51,1.5,5.99,1.5,11.51s4.48,10,10,10,10-4.48,10-10S17.02,1.51,11.5,1.51Zm5.17,10.81l-5.87,3.76c-1.03.66-2.37-.08-2.37-1.3v-6.54c0-1.22,1.35-1.95,2.37-1.3l5.87,3.76c.59.38.59,1.25,0,1.63Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 301 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m11.76,1.5C6.23,1.5,1.76,5.98,1.76,11.5s4.48,10,10,10,10-4.48,10-10S17.28,1.5,11.76,1.5Zm3.85,12.29c0,.86-.7,1.56-1.56,1.56h-4.58c-.86,0-1.56-.7-1.56-1.56v-4.58c0-.86.7-1.56,1.56-1.56h4.58c.86,0,1.56.7,1.56,1.56v4.58Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1,15 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text { color: #000000; }
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="M 4.0214844,0 C 3.7305148,4.096652e-4 3.4543107,0.12887668 3.265625,0.3515625 L 11.914062,9 H 14 V 7 C 12.695714,7 10.944251,6.326829 11,5 V 2 h 0.978516 c 1.345107,0.019125 1.345107,-2.01912521 0,-2 z M 3.71875,6.6328125 C 3.1820953,6.8759067 2.5561157,7 2,7 V 9 H 6.0859375 Z M 7,9.9140625 V 15 L 7.9765625,16 9,15 v -3.085938 z"/>
|
||||
<path
|
||||
class="ColorScheme-Text"
|
||||
style="fill:currentColor"
|
||||
d="M -1e-7,9.9999999e-8 V 1.5000001 L 14.5,16 H 16 V 14.5 L 1.4999999,9.9999999e-8 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 805 B |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
|
||||
<path d="m20.33,1.42H2.89C1.63,1.42.61,2.47.61,3.77v15.31c0,1.29,1.02,2.34,2.28,2.34h17.44c1.26,0,2.28-1.05,2.28-2.34V3.77c0-1.29-1.02-2.34-2.28-2.34Zm-9.99,10.67l-3.1,3.29c-.21.22-.48.33-.76.33-.26,0-.53-.1-.73-.3-.42-.42-.43-1.1-.02-1.53l2.38-2.53-2.35-2.45c-.41-.43-.4-1.11.01-1.53.41-.42,1.08-.41,1.49.01l3.07,3.2c.4.42.4,1.09,0,1.51Zm6.5,3.62h-4.07c-.58,0-1.05-.48-1.05-1.08s.47-1.08,1.05-1.08h4.07c.58,0,1.05.48,1.05,1.08s-.47,1.08-1.05,1.08Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 537 B |
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Rectangle {
|
||||
id: badge
|
||||
|
||||
property var iconName: ""
|
||||
property var iconColor: ""
|
||||
property var position: ""
|
||||
|
||||
width: Math.min(parent.width, parent.height) / 3
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: cfg.counterColor ? cfg.counterColor : Kirigami.Theme.backgroundColor
|
||||
|
||||
anchors {
|
||||
topMargin: state.includes("top") ? cfg.badgeOffsetX : 0
|
||||
leftMargin: state.includes("Left") ? cfg.badgeOffsetY : 0
|
||||
rightMargin: state.includes("Right") ? cfg.badgeOffsetY : 0
|
||||
bottomMargin: state.includes("bottom") ? cfg.badgeOffsetX : 0
|
||||
}
|
||||
|
||||
state: position || "topRight"
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "topLeft"
|
||||
AnchorChanges { target: badge; anchors.top: parent.top; anchors.left: parent.left }
|
||||
},
|
||||
State {
|
||||
name: "topRight"
|
||||
AnchorChanges { target: badge; anchors.top: parent.top; anchors.right: parent.right }
|
||||
},
|
||||
State {
|
||||
name: "bottomLeft"
|
||||
AnchorChanges { target: badge; anchors.bottom: parent.bottom; anchors.left: parent.left }
|
||||
},
|
||||
State {
|
||||
name: "bottomRight"
|
||||
AnchorChanges { target: badge; anchors.bottom: parent.bottom; anchors.right: parent.right }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition { AnchorAnimation { duration: 120 } }
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
source: cfg.ownIconsUI ? Qt.resolvedUrl("../assets/icons/" + iconName + ".svg") : iconName
|
||||
color: iconColor
|
||||
isMask: cfg.ownIconsUI
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
RowLayout {
|
||||
property alias labelText: label.text
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
implicitWidth: 250
|
||||
textRole: "name"
|
||||
model: [
|
||||
{"name": i18n("None"), "value": ""},
|
||||
{"name": i18n("Check updates"), "value": "checkUpdates"},
|
||||
{"name": i18n("Upgrade system"), "value": "upgradeSystem"},
|
||||
{"name": i18n("Pause auto check"), "value": "switchScheduler"},
|
||||
{"name": i18n("Management"), "value": "management"}
|
||||
]
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
var action = model[currentIndex]["value"]
|
||||
switch (type) {
|
||||
case "middle": cfg_middleAction = action; break;
|
||||
case "right": cfg_rightAction = action; break;
|
||||
case "scrollUp": cfg_scrollUpAction = action; break;
|
||||
case "scrollDown": cfg_scrollDownAction = action; break;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = JS.setIndex(plasmoid.configuration[type + "Action"], model)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
text: ""
|
||||
}
|
||||
|
||||
property string type: ""
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import QtQuick
|
||||
import org.kde.notification
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
Item {
|
||||
Component {
|
||||
id: notifyComponent
|
||||
Notification {
|
||||
componentName: "apdatifier"
|
||||
flags: cfg.notifyPersistent ? Notification.Persistent : Notification.CloseOnTimeout
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: upgradeAction
|
||||
NotificationAction {
|
||||
label: i18n("Upgrade system")
|
||||
onActivated: JS.upgradeSystem()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openArticleAction
|
||||
NotificationAction {
|
||||
property string link
|
||||
label: i18n("Read article")
|
||||
onActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
|
||||
function send(event, title, body, link) {
|
||||
const params = {
|
||||
updates: {
|
||||
icon: "apdatifier-packages",
|
||||
urgency: "DefaultUrgency"
|
||||
},
|
||||
news: {
|
||||
icon: "news-subscribe",
|
||||
urgency: "HighUrgency"
|
||||
},
|
||||
error: {
|
||||
icon: "error",
|
||||
urgency: "CriticalUrgency"
|
||||
}
|
||||
}
|
||||
|
||||
const { icon, urgency } = params[event]
|
||||
|
||||
const action = (event === "updates" && cfg.notifyUpdatesAction) ? upgradeAction.createObject(root)
|
||||
: (event === "news" && cfg.notifyNewsAction) ? openArticleAction.createObject(root, { link: link })
|
||||
: []
|
||||
|
||||
if (cfg.notifySound) event += "Sound"
|
||||
|
||||
notifyComponent.createObject(root, {
|
||||
eventId: event,
|
||||
iconName: icon,
|
||||
title: title,
|
||||
text: body,
|
||||
actions: action,
|
||||
urgency: urgency
|
||||
})?.sendEvent()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item target
|
||||
required property bool running
|
||||
|
||||
property real scaleTo: 1.20
|
||||
property real opacityTo: 0.60
|
||||
property int duration: 400
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running && target) {
|
||||
target.scale = 1
|
||||
target.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: scaleAnimation
|
||||
running: root.running
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.target
|
||||
property: "scale"
|
||||
to: root.scaleTo
|
||||
duration: root.duration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root.target
|
||||
property: "scale"
|
||||
to: 1.00
|
||||
duration: root.duration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: opacityAnimation
|
||||
running: root.running
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.target
|
||||
property: "opacity"
|
||||
to: root.opacityTo
|
||||
duration: root.duration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root.target
|
||||
property: "opacity"
|
||||
to: 1.00
|
||||
duration: root.duration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
ScrollBar {
|
||||
id: root
|
||||
|
||||
property bool showByTimer: false
|
||||
|
||||
visible: root.visualSize < 1.0
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 1200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: root.showByTimer = false
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
showByTimer = true
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onHoveredChanged: scroll.opacity = hovered ? 1 : 0.5
|
||||
}
|
||||
|
||||
opacity: (!cfg.autoHideScrollBar || root.active || root.hovered || root.showByTimer) ? 0.8 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 300 } }
|
||||
|
||||
contentItem: Rectangle {
|
||||
id: scroll
|
||||
implicitWidth: 5
|
||||
radius: width / 2
|
||||
color: Kirigami.Theme.highlightColor
|
||||
opacity: 0.5
|
||||
border.width: 1
|
||||
border.color: Qt.darker(Qt.rgba(color.r, color.g, color.b, 0.8), 2)
|
||||
Behavior on opacity { NumberAnimation { duration: 300 } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import QtQuick
|
||||
import org.kde.plasma.plasma5support as Plasma5Support
|
||||
|
||||
Plasma5Support.DataSource {
|
||||
id: executable
|
||||
engine: "executable"
|
||||
connectedSources: []
|
||||
onNewData: (sourceName, data) => {
|
||||
var cmd = sourceName
|
||||
var out = data["stdout"]
|
||||
var err = data["stderr"]
|
||||
var code = data["exit code"]
|
||||
|
||||
exited(cmd, out, err, code)
|
||||
|
||||
const cb = listeners[cmd]
|
||||
if (cb) cb(cmd, out, err, code)
|
||||
}
|
||||
|
||||
signal exited(string cmd, string out, string err, int code)
|
||||
|
||||
property var listeners: ({})
|
||||
|
||||
function exec(cmd, callback) {
|
||||
listeners[cmd] = execCallback.bind(executable, callback)
|
||||
connectSource(cmd)
|
||||
}
|
||||
|
||||
function execCallback(callback, cmd, out, err, code) {
|
||||
cleanup()
|
||||
if (callback) callback(cmd, out, err, code)
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
for (var cmd in listeners) {
|
||||
delete listeners[cmd]
|
||||
disconnectSource(cmd)
|
||||
}
|
||||
|
||||
if (typeof sts !== 'undefined' && sts?.proc === executable) sts.proc = null
|
||||
|
||||
executable.destroy()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.components
|
||||
|
||||
ToolButton {
|
||||
property string tooltipText: ""
|
||||
property string iconSource: ""
|
||||
property color iconColor: Kirigami.Theme.colorSet
|
||||
property alias buttonTooltip: tooltip
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
hoverEnabled: enabled
|
||||
highlighted: enabled
|
||||
|
||||
ToolTip {
|
||||
id: tooltip
|
||||
text: tooltipText
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
height: parent.height
|
||||
width: parent.height
|
||||
anchors.centerIn: parent
|
||||
source: iconSource
|
||||
color: iconColor
|
||||
scale: cfg.ownIconsUI ? 0.7 : 0.9
|
||||
isMask: cfg.ownIconsUI
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,819 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.ksvg
|
||||
import org.kde.kcmutils
|
||||
import org.kde.iconthemes
|
||||
import org.kde.kquickcontrolsaddons
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
SimpleKCM {
|
||||
property alias cfg_hideIconPolicy: hideIconPolicy.value
|
||||
property string cfg_selectedIcon: plasmoid.configuration.selectedIcon
|
||||
property string cfg_busyIndicator: plasmoid.configuration.busyIndicator
|
||||
|
||||
property string cfg_pauseBadgePosition: plasmoid.configuration.pauseBadgePosition
|
||||
property string cfg_updatedBadgePosition: plasmoid.configuration.updatedBadgePosition
|
||||
property alias cfg_badgeOffsetX: badgeOffsetX.value
|
||||
property alias cfg_badgeOffsetY: badgeOffsetY.value
|
||||
|
||||
property string cfg_counterMode: plasmoid.configuration.counterMode
|
||||
property string cfg_counterSidePosition: plasmoid.configuration.counterSidePosition
|
||||
property string cfg_counterBadgePosition: plasmoid.configuration.counterBadgePosition
|
||||
property alias cfg_counterOffsetX: counterOffsetX.value
|
||||
property alias cfg_counterOffsetY: counterOffsetY.value
|
||||
property string cfg_counterColor: plasmoid.configuration.counterColor
|
||||
property alias cfg_counterRadius: counterRadius.value
|
||||
property alias cfg_counterOpacity: counterOpacity.value
|
||||
property string cfg_counterFontFamily: plasmoid.configuration.counterFontFamily
|
||||
property alias cfg_counterFontBold: counterFontBold.checked
|
||||
property alias cfg_counterFontSize: counterFontSize.value
|
||||
property alias cfg_counterGapsInner: counterGapsInner.value
|
||||
property alias cfg_counterGapsOuter: counterGapsOuter.value
|
||||
|
||||
property alias cfg_ownIconsUI: ownIconsUI.checked
|
||||
property alias cfg_autoHideScrollBar: autoHideScrollBar.checked
|
||||
property int cfg_defaultTab: plasmoid.configuration.defaultTab
|
||||
property alias cfg_switchDefaultTab: switchDefaultTab.checked
|
||||
property alias cfg_sorting: sorting.checked
|
||||
property alias cfg_showStatusText: showStatusText.checked
|
||||
property alias cfg_showToolBar: showToolBar.checked
|
||||
property alias cfg_searchButton: searchButton.checked
|
||||
property alias cfg_intervalButton: intervalButton.checked
|
||||
property alias cfg_sortButton: sortButton.checked
|
||||
property alias cfg_viewButton: viewButton.checked
|
||||
property alias cfg_managementButton: managementButton.checked
|
||||
property alias cfg_upgradeButton: upgradeButton.checked
|
||||
property alias cfg_checkButton: checkButton.checked
|
||||
property alias cfg_pinButton: pinButton.checked
|
||||
property alias cfg_settingsButton: settingsButton.checked
|
||||
property alias cfg_tabBarVisible: tabBarVisible.checked
|
||||
property alias cfg_tabBarTexts: tabBarTexts.checked
|
||||
property string cfg_compactInfo: plasmoid.configuration.compactInfo || "repository"
|
||||
property alias cfg_extendedShowRepository: extendedShowRepository.checked
|
||||
property alias cfg_extendedShowInstallReason: extendedShowInstallReason.checked
|
||||
property alias cfg_highlightVersionDiff: highlightVersionDiff.checked
|
||||
property alias cfg_colorizeInstallReason: colorizeInstallReason.checked
|
||||
|
||||
readonly property bool inTray: (plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentDrawsPlasmoidHeading)
|
||||
readonly property bool onDesktop: plasmoid.location === PlasmaCore.Types.Floating
|
||||
readonly property bool horizontal: plasmoid.location === PlasmaCore.Types.TopEdge || plasmoid.location === PlasmaCore.Types.BottomEdge
|
||||
|
||||
readonly property bool allowOnlyCounterBadge: inTray || !horizontal
|
||||
|
||||
readonly property bool counterEnabled: cfg_counterMode !== "disabled"
|
||||
readonly property bool sideMode: cfg_counterMode === "side"
|
||||
readonly property bool badgeMode: cfg_counterMode === "badge"
|
||||
|
||||
readonly property var positions: [
|
||||
{ name: i18n("Disabled"), value: "disabled" },
|
||||
{ name: i18n("Top-Left"), value: "topLeft" },
|
||||
{ name: i18n("Top-Right"), value: "topRight" },
|
||||
{ name: i18n("Bottom-Left"), value: "bottomLeft" },
|
||||
{ name: i18n("Bottom-Right"), value: "bottomRight" }
|
||||
]
|
||||
|
||||
property int currentTab
|
||||
signal tabChanged(currentTab: int)
|
||||
onCurrentTabChanged: tabChanged(currentTab)
|
||||
|
||||
header: Kirigami.NavigationTabBar {
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "view-list-icons"
|
||||
text: i18n("Panel Icon")
|
||||
checked: currentTab === 0
|
||||
onTriggered: currentTab = 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "view-split-left-right"
|
||||
text: i18n("Full View")
|
||||
checked: currentTab === 1
|
||||
onTriggered: currentTab = 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: iconViewTab
|
||||
visible: currentTab === 0
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Icon")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
Button {
|
||||
id: iconButton
|
||||
|
||||
implicitWidth: iconFrame.width + Kirigami.Units.smallSpacing
|
||||
implicitHeight: implicitWidth
|
||||
hoverEnabled: true
|
||||
|
||||
FrameSvgItem {
|
||||
id: iconFrame
|
||||
anchors.centerIn: parent
|
||||
width: Kirigami.Units.iconSizes.large + fixedMargins.left + fixedMargins.right
|
||||
height: width
|
||||
imagePath: "widgets/background"
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.centerIn: parent
|
||||
width: Kirigami.Units.iconSizes.large
|
||||
height: width
|
||||
source: JS.setIcon(cfg_selectedIcon)
|
||||
}
|
||||
}
|
||||
|
||||
IconDialog {
|
||||
id: iconDialog
|
||||
onIconNameChanged: cfg_selectedIcon = iconName || JS.defaultIcon
|
||||
}
|
||||
|
||||
onClicked: menu.opened ? menu.close() : menu.open()
|
||||
|
||||
Menu {
|
||||
id: menu
|
||||
y: +parent.height
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Default") + " 1"
|
||||
icon.name: "apdatifier-plasmoid"
|
||||
enabled: cfg_selectedIcon !== JS.defaultIcon
|
||||
onClicked: cfg_selectedIcon = JS.defaultIcon
|
||||
}
|
||||
MenuItem {
|
||||
text: i18n("Default") + " 2"
|
||||
icon.name: "apdatifier-packages"
|
||||
enabled: cfg_selectedIcon !== icon.name
|
||||
onClicked: cfg_selectedIcon = icon.name
|
||||
}
|
||||
MenuItem {
|
||||
text: i18n("Default") + " 3"
|
||||
icon.name: "apdatifier-package"
|
||||
enabled: cfg_selectedIcon !== icon.name
|
||||
onClicked: cfg_selectedIcon = icon.name
|
||||
}
|
||||
MenuItem {
|
||||
text: i18n("Default") + " 4"
|
||||
icon.name: "apdatifier-flatpak"
|
||||
enabled: cfg_selectedIcon !== icon.name
|
||||
onClicked: cfg_selectedIcon = icon.name
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Select...")
|
||||
icon.name: "document-open-folder"
|
||||
onClicked: iconDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: cfg_selectedIcon === JS.defaultIcon ? i18n("Default icon") : cfg_selectedIcon
|
||||
delay: Kirigami.Units.toolTipDelay
|
||||
visible: iconButton.hovered
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Hide") + ":"
|
||||
|
||||
Label {
|
||||
text: i18n("when less than")
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
id: hideIconPolicy
|
||||
from: 0
|
||||
to: 999
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: i18np("update", "updates", hideIconPolicy.value)
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("If the widget is on a panel, you can access the hidden icon in Panel Configuration mode.")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Busy indicator") + ":"
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
ButtonGroup {
|
||||
id: busyIndicatorGroup
|
||||
onCheckedButtonChanged: {
|
||||
if (checkedButton) {
|
||||
cfg_busyIndicator = checkedButton.modeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
text: i18n("Spinner")
|
||||
property string modeValue: "spinner"
|
||||
ButtonGroup.group: busyIndicatorGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
text: i18n("Pulse")
|
||||
property string modeValue: "pulse"
|
||||
ButtonGroup.group: busyIndicatorGroup
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const current = plasmoid.configuration.busyIndicator || "spinner"
|
||||
for (let i = 0; i < busyIndicatorGroup.buttons.length; ++i) {
|
||||
const b = busyIndicatorGroup.buttons[i]
|
||||
if (b.modeValue === current) {
|
||||
b.checked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Spinner uses the standard Plasmoid busy indicator. Pulse animates the panel icon. On the desktop, the busy indicator is always off.")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Counter")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Mode") + ":"
|
||||
|
||||
RowLayout {
|
||||
id: counterMode
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
ButtonGroup {
|
||||
id: counterModeGroup
|
||||
onCheckedButtonChanged: {
|
||||
if (checkedButton) {
|
||||
cfg_counterMode = checkedButton.modeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
RadioButton {
|
||||
text: i18n("Off")
|
||||
property string modeValue: "disabled"
|
||||
ButtonGroup.group: counterModeGroup
|
||||
}
|
||||
RadioButton {
|
||||
text: i18n("Side")
|
||||
property string modeValue: "side"
|
||||
ButtonGroup.group: counterModeGroup
|
||||
enabled: !allowOnlyCounterBadge
|
||||
}
|
||||
RadioButton {
|
||||
text: i18n("Badge")
|
||||
property string modeValue: "badge"
|
||||
ButtonGroup.group: counterModeGroup
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (allowOnlyCounterBadge && plasmoid.configuration.counterMode === "side") {
|
||||
plasmoid.configuration.counterMode = "badge"
|
||||
}
|
||||
|
||||
for (let i = 0; i < counterModeGroup.buttons.length; ++i) {
|
||||
const b = counterModeGroup.buttons[i]
|
||||
if (b.modeValue === plasmoid.configuration.counterMode) {
|
||||
b.checked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Position") + ":"
|
||||
ComboBox {
|
||||
id: counterBadgePosition
|
||||
enabled: badgeMode
|
||||
textRole: "name"
|
||||
model: positions.slice(1).concat([{ name: i18n("Center"), value: "center" }])
|
||||
onCurrentIndexChanged: cfg_counterBadgePosition = model[currentIndex].value
|
||||
Component.onCompleted: currentIndex = JS.setIndex(plasmoid.configuration.counterBadgePosition, model)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Side") + ":"
|
||||
enabled: sideMode
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
RadioButton {
|
||||
text: i18n("Left")
|
||||
checked: cfg_counterSidePosition === "left"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_counterSidePosition = "left"
|
||||
}
|
||||
}
|
||||
RadioButton {
|
||||
text: i18n("Right")
|
||||
checked: cfg_counterSidePosition === "right"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_counterSidePosition = "right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Gaps") + ":"
|
||||
enabled: sideMode
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
Label { text: i18n("Inner") + ":" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
enabled: sideMode
|
||||
id: counterGapsInner
|
||||
from: 0
|
||||
to: 99
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label { text: i18n("Outer") + ":" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
enabled: sideMode
|
||||
id: counterGapsOuter
|
||||
from: 0
|
||||
to: 99
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Offset") + ":"
|
||||
enabled: badgeMode
|
||||
Label { text: "X:" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
id: counterOffsetX
|
||||
from: -5
|
||||
to: 5
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label { text: "Y:" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
id: counterOffsetY
|
||||
from: -5
|
||||
to: 5
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Kirigami.FormData.label: i18n("Font family") + ":"
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
enabled: counterEnabled
|
||||
editable: true
|
||||
textRole: "name"
|
||||
model: {
|
||||
let fonts = Qt.fontFamilies()
|
||||
let arr = []
|
||||
arr.push({"name": i18n("Default system font"), "value": ""})
|
||||
for (let i = 0; i < fonts.length; i++) {
|
||||
arr.push({"name": fonts[i], "value": fonts[i]})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: cfg_counterFontFamily = model[currentIndex]["value"]
|
||||
Component.onCompleted: currentIndex = JS.setIndex(plasmoid.configuration.counterFontFamily, model)
|
||||
}
|
||||
|
||||
Slider {
|
||||
Kirigami.FormData.label: i18n("Font size") + ":"
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
|
||||
enabled: counterEnabled
|
||||
id: counterFontSize
|
||||
from: 2
|
||||
to: 8
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Font bold") + ":"
|
||||
enabled: counterEnabled
|
||||
id: counterFontBold
|
||||
text: i18n("Enable")
|
||||
onCheckedChanged: cfg_counterFontBold = counterFontBold.checked
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Kirigami.FormData.label: i18n("Badge color") + ":"
|
||||
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||
enabled: badgeMode
|
||||
|
||||
id: counterColor
|
||||
implicitWidth: Kirigami.Units.gridUnit * 4
|
||||
implicitHeight: Kirigami.Units.gridUnit
|
||||
|
||||
background: Item {
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
radius: counterRadius.value
|
||||
border.width: 1
|
||||
border.color: "black"
|
||||
color: cfg_counterColor ? cfg_counterColor : Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: bg.color.toString().toUpperCase()
|
||||
font.bold: true
|
||||
color: Kirigami.ColorUtils.brightnessForColor(bg.color) === Kirigami.ColorUtils.Dark ? "white" : "black"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onPressed: menuColor.opened ? menuColor.close() : menuColor.open()
|
||||
|
||||
Menu {
|
||||
id: menuColor
|
||||
y: +parent.height
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Default color")
|
||||
icon.name: "edit-clear"
|
||||
enabled: cfg_counterColor && cfg_counterColor !== Kirigami.Theme.backgroundColor
|
||||
onClicked: cfg_counterColor = ""
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Select...")
|
||||
icon.name: "document-open-folder"
|
||||
onClicked: colorDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
ColorDialog {
|
||||
id: colorDialog
|
||||
visible: false
|
||||
title: i18n("Select counter background color")
|
||||
selectedColor: cfg_counterColor
|
||||
|
||||
onAccepted: {
|
||||
cfg_counterColor = selectedColor
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: cfg_counterColor ? cfg_counterColor : i18n("Default background color from current theme")
|
||||
delay: Kirigami.Units.toolTipDelay
|
||||
visible: !cfg_counterColor && counterColor.hovered
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Badge radius") + ":"
|
||||
enabled: badgeMode
|
||||
|
||||
Slider {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
|
||||
id: counterRadius
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: counterRadius.value
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Badge opacity") + ":"
|
||||
enabled: badgeMode
|
||||
|
||||
Slider {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
|
||||
id: counterOpacity
|
||||
from: 0
|
||||
to: 10
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: counterOpacity.value / 10
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Icon Badges")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Paused") + ":"
|
||||
ComboBox {
|
||||
id: pauseBadgePosition
|
||||
textRole: "name"
|
||||
model: positions
|
||||
onCurrentIndexChanged: cfg_pauseBadgePosition = model[currentIndex].value
|
||||
Component.onCompleted: currentIndex = JS.setIndex(plasmoid.configuration.pauseBadgePosition, model)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Updated") + ":"
|
||||
ComboBox {
|
||||
id: updatedBadgePosition
|
||||
textRole: "name"
|
||||
model: positions
|
||||
onCurrentIndexChanged: cfg_updatedBadgePosition = model[currentIndex].value
|
||||
Component.onCompleted: currentIndex = JS.setIndex(plasmoid.configuration.updatedBadgePosition, model)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Offset") + ":"
|
||||
Label { text: "X:" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
id: badgeOffsetX
|
||||
from: -5
|
||||
to: 5
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label { text: "Y:" }
|
||||
SpinBox {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
id: badgeOffsetY
|
||||
from: -5
|
||||
to: 5
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: listViewTab
|
||||
visible: currentTab === 1
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: "UI:"
|
||||
CheckBox {
|
||||
id: ownIconsUI
|
||||
text: i18n("Use built-in icons")
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Override custom icon theme and use default Apdatifier icons instead.")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Scrollbar") + ":"
|
||||
id: autoHideScrollBar
|
||||
text: i18n("Auto-hide scrollbar")
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: viewGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
Kirigami.FormData.label: i18n("Default view") + ":"
|
||||
id: extendedView
|
||||
ButtonGroup.group: viewGroup
|
||||
text: i18n("Extended")
|
||||
Component.onCompleted: checked = !plasmoid.configuration.defaultTab
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: viewGroup
|
||||
text: i18n("Compact")
|
||||
onCheckedChanged: cfg_defaultTab = checked
|
||||
Component.onCompleted: checked = plasmoid.configuration.defaultTab
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Behavior") + ":"
|
||||
id: switchDefaultTab
|
||||
text: i18n("Always switch to default view")
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: sortGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: sorting
|
||||
Kirigami.FormData.label: i18n("Sorting") + ":"
|
||||
text: i18n("By repository")
|
||||
checked: true
|
||||
Component.onCompleted: checked = plasmoid.configuration.sorting
|
||||
ButtonGroup.group: sortGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
text: i18n("By name")
|
||||
Component.onCompleted: checked = !plasmoid.configuration.sorting
|
||||
ButtonGroup.group: sortGroup
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Toolbar")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showStatusText
|
||||
Kirigami.FormData.label: i18n("Header") + ":"
|
||||
text: i18n("Show status")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showToolBar
|
||||
text: i18n("Show tool bar")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
enabled: showToolBar.checked
|
||||
ToolButton {
|
||||
id: searchButton
|
||||
icon.name: "search"
|
||||
checkable: true
|
||||
}
|
||||
ToolButton {
|
||||
id: intervalButton
|
||||
icon.name: "media-playback-paused"
|
||||
checkable: true
|
||||
}
|
||||
ToolButton {
|
||||
id: sortButton
|
||||
icon.name: "sort-name"
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
enabled: showToolBar.checked
|
||||
ToolButton {
|
||||
id: viewButton
|
||||
icon.name: "view-split-top-bottom"
|
||||
checkable: true
|
||||
}
|
||||
ToolButton {
|
||||
id: managementButton
|
||||
icon.name: "tools"
|
||||
checkable: true
|
||||
}
|
||||
ToolButton {
|
||||
id: upgradeButton
|
||||
icon.name: "akonadiconsole"
|
||||
checkable: true
|
||||
}
|
||||
|
||||
}
|
||||
RowLayout {
|
||||
enabled: showToolBar.checked
|
||||
ToolButton {
|
||||
id: checkButton
|
||||
icon.name: "view-refresh"
|
||||
checkable: true
|
||||
}
|
||||
ToolButton {
|
||||
id: settingsButton
|
||||
icon.name: "settings-configure"
|
||||
checkable: true
|
||||
enabled: !inTray
|
||||
}
|
||||
ToolButton {
|
||||
id: pinButton
|
||||
icon.name: "pin"
|
||||
checkable: true
|
||||
enabled: !inTray && !onDesktop
|
||||
ToolTip {
|
||||
text: !pinButton.enabled ? "Pinning button is not available in the system tray and on the desktop" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Additional Package Info")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Kirigami.FormData.label: i18n("Compact view") + ":"
|
||||
id: compactInfo
|
||||
textRole: "name"
|
||||
model: [
|
||||
{ name: i18n("Repository"), value: "repository" },
|
||||
{ name: i18n("Install reason"), value: "installReason" },
|
||||
{ name: i18n("Versions"), value: "versionDiff" }
|
||||
]
|
||||
onCurrentIndexChanged: cfg_compactInfo = model[currentIndex].value
|
||||
Component.onCompleted: currentIndex = JS.setIndex(plasmoid.configuration.compactInfo || "repository", model)
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Extended view") + ":"
|
||||
id: extendedShowRepository
|
||||
text: i18n("Repository")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: extendedShowInstallReason
|
||||
text: i18n("Install reason")
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: highlightVersionDiff
|
||||
text: i18n("Highlight version differences")
|
||||
}
|
||||
CheckBox {
|
||||
id: colorizeInstallReason
|
||||
text: i18n("Colorize install reason")
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Tab Bar")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Footer") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: tabBarVisible
|
||||
text: i18n("Show tab bar")
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: tabBarTexts
|
||||
text: i18n("Show tab texts")
|
||||
enabled: tabBarVisible.checked
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import org.kde.kcmutils
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../components" as QQC
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
SimpleKCM {
|
||||
property string cfg_checkMode: plasmoid.configuration.checkMode
|
||||
property alias cfg_intervalMinutes: intervalMinutes.value
|
||||
property alias cfg_dailyHour: dailyHour.value
|
||||
property alias cfg_dailyMinute: dailyMinute.value
|
||||
property alias cfg_weeklyDay: weeklyDay.currentIndex
|
||||
property alias cfg_weeklyHour: weeklyHour.value
|
||||
property alias cfg_weeklyMinute: weeklyMinute.value
|
||||
|
||||
property alias cfg_arch: arch.checked
|
||||
property alias cfg_aur: aur.checked
|
||||
property alias cfg_flatpak: flatpak.checked
|
||||
property alias cfg_fwupd: fwupd.checked
|
||||
property alias cfg_widgets: widgets.checked
|
||||
|
||||
property bool cfg_feedsEnabled: plasmoid.configuration.feedsEnabled
|
||||
property alias cfg_feedsFetchCount: feedsFetchCount.value
|
||||
property string cfg_feeds: plasmoid.configuration.feeds
|
||||
|
||||
property string cfg_middleAction: plasmoid.configuration.middleAction
|
||||
property string cfg_rightAction: plasmoid.configuration.rightAction
|
||||
property string cfg_scrollUpAction: plasmoid.configuration.scrollUpAction
|
||||
property string cfg_scrollDownAction: plasmoid.configuration.scrollDownAction
|
||||
|
||||
property alias cfg_notifyUpdates: notifyUpdates.checked
|
||||
property alias cfg_notifyUpdatesAction: notifyUpdatesAction.checked
|
||||
property alias cfg_notifyEveryBump: notifyEveryBump.checked
|
||||
property alias cfg_notifyNews: notifyNews.checked
|
||||
property alias cfg_notifyNewsAction: notifyNewsAction.checked
|
||||
property alias cfg_notifyErrors: notifyErrors.checked
|
||||
property alias cfg_notifySound: notifySound.checked
|
||||
property alias cfg_notifyPersistent: notifyPersistent.checked
|
||||
|
||||
property alias cfg_checkConn: checkConn.checked
|
||||
property alias cfg_respectMeteredConn: respectMeteredConn.checked
|
||||
|
||||
property var cfg: plasmoid.configuration
|
||||
property var pkg: plasmoid.configuration.packages
|
||||
property var terminals: plasmoid.configuration.terminals
|
||||
property var feeds: []
|
||||
property alias cfg_dbPath: dbPath.text
|
||||
|
||||
property bool systemWide: !/^\/home\/[^/]+\/\.local/.test(JS.scriptDir)
|
||||
|
||||
property int installButton
|
||||
property var dialogTitles: {
|
||||
"0": i18n("Install Development version"),
|
||||
"1": i18n("Install Stable version"),
|
||||
"2": i18n("Uninstall widget")
|
||||
}
|
||||
property var dialogSubtitles: {
|
||||
"0": i18n("Note: version with the latest commits may be unstable."),
|
||||
"1": i18n("Note: if you haven't installed the Devel version before, there's no need to install the Stable version."),
|
||||
"2": i18n("Removal of the widget and all related files, including the directory with its configuration.")
|
||||
}
|
||||
|
||||
property int currentTab
|
||||
signal tabChanged(currentTab: int)
|
||||
onCurrentTabChanged: tabChanged(currentTab)
|
||||
|
||||
Component.onCompleted: {
|
||||
feeds = JS.parseCustomFeeds(cfg_feeds)
|
||||
cfg_feeds = JS.serializeCustomFeeds(feeds)
|
||||
feedsUrl.syncToModel()
|
||||
JS.checkDependencies()
|
||||
}
|
||||
|
||||
header: Kirigami.NavigationTabBar {
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "search"
|
||||
text: i18n("Search")
|
||||
checked: currentTab === 0
|
||||
onTriggered: currentTab = 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "notification-active"
|
||||
text: i18n("Notifications")
|
||||
checked: currentTab === 1
|
||||
onTriggered: currentTab = 1
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "followmouse-symbolic"
|
||||
text: i18n("Mouse actions")
|
||||
checked: currentTab === 2
|
||||
onTriggered: currentTab = 2
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "documentinfo"
|
||||
text: i18n("Misc")
|
||||
checked: currentTab === 3
|
||||
onTriggered: currentTab = 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.InlineMessage {
|
||||
id: configMsg
|
||||
Layout.fillWidth: true
|
||||
icon.source: "document-save"
|
||||
text: "<b>" + i18n("Configuration is automatically saved in a config file and loaded at every startup, ensuring you never lose your settings. The config file is stored in ") + "~/.config/apdatifier" + "</b>"
|
||||
type: Kirigami.MessageType.Positive
|
||||
visible: plasmoid.configuration.configMsg
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: "OK"
|
||||
icon.name: "checkmark"
|
||||
onTriggered: plasmoid.configuration.configMsg = false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: searchTab
|
||||
visible: currentTab === 0
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Schedule")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Mode") + ":"
|
||||
|
||||
ComboBox {
|
||||
id: checkMode
|
||||
textRole: "name"
|
||||
model: [
|
||||
{ name: i18n("Manual"), value: "manual" },
|
||||
{ name: i18n("Interval"), value: "interval" },
|
||||
{ name: i18n("Daily"), value: "daily" },
|
||||
{ name: i18n("Weekly"), value: "weekly" }]
|
||||
currentIndex: JS.setIndex(cfg_checkMode, model)
|
||||
onCurrentIndexChanged: cfg_checkMode = model[currentIndex].value
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: cfg_checkMode === "interval"
|
||||
Kirigami.FormData.label: i18n("Interval") + ":"
|
||||
|
||||
SpinBox {
|
||||
id: intervalMinutes
|
||||
from: 30
|
||||
to: 43200
|
||||
stepSize: 5
|
||||
value: intervalMinutes.value
|
||||
onValueChanged: cfg_intervalMinutes = value
|
||||
}
|
||||
|
||||
Label { text: i18n("minutes") }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: cfg_checkMode === "daily"
|
||||
Kirigami.FormData.label: i18n("Daily time") + ":"
|
||||
|
||||
SpinBox {
|
||||
id: dailyHour
|
||||
from: 0
|
||||
to: 23
|
||||
stepSize: 1
|
||||
value: dailyHour.value
|
||||
onValueChanged: cfg_dailyHour = value
|
||||
Layout.preferredWidth: 50
|
||||
}
|
||||
|
||||
Label { text: ":" }
|
||||
|
||||
SpinBox {
|
||||
id: dailyMinute
|
||||
from: 0
|
||||
to: 59
|
||||
stepSize: 1
|
||||
value: dailyMinute.value
|
||||
onValueChanged: cfg_dailyMinute = value
|
||||
Layout.preferredWidth: 50
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: cfg_checkMode === "weekly"
|
||||
Kirigami.FormData.label: i18n("Schedule") + ":"
|
||||
|
||||
ComboBox {
|
||||
id: weeklyDay
|
||||
textRole: "name"
|
||||
model: [
|
||||
{ name: i18n("Sunday"), value: 0 },
|
||||
{ name: i18n("Monday"), value: 1 },
|
||||
{ name: i18n("Tuesday"), value: 2 },
|
||||
{ name: i18n("Wednesday"), value: 3 },
|
||||
{ name: i18n("Thursday"), value: 4 },
|
||||
{ name: i18n("Friday"), value: 5 },
|
||||
{ name: i18n("Saturday"), value: 6 }
|
||||
]
|
||||
currentIndex: JS.setIndex(plasmoid.configuration.weeklyDay, model)
|
||||
onCurrentIndexChanged: plasmoid.configuration.weeklyDay = model[currentIndex].value
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
SpinBox {
|
||||
id: weeklyHour
|
||||
from: 0
|
||||
to: 23
|
||||
stepSize: 1
|
||||
value: weeklyHour.value
|
||||
onValueChanged: cfg_weeklyHour = value
|
||||
Layout.preferredWidth: 50
|
||||
}
|
||||
|
||||
Label {
|
||||
text: ":"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: weeklyMinute
|
||||
from: 0
|
||||
to: 59
|
||||
stepSize: 1
|
||||
value: weeklyMinute.value
|
||||
onValueChanged: cfg_weeklyMinute = value
|
||||
Layout.preferredWidth: 50
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Updates")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: arch
|
||||
text: i18n("Arch Official Repositories")
|
||||
enabled: pkg.pacman
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.gridUnit
|
||||
visible: pkg.pacman
|
||||
|
||||
CheckBox {
|
||||
id: aur
|
||||
text: i18n("Arch User Repository") + " (AUR)"
|
||||
enabled: pkg.paru || pkg.yay || pkg.pikaur
|
||||
}
|
||||
|
||||
Kirigami.UrlButton {
|
||||
url: "https://github.com/exequtic/apdatifier#supported-pacman-wrappers"
|
||||
text: instTip.text
|
||||
font.pointSize: instTip.font.pointSize
|
||||
color: instTip.color
|
||||
visible: !aur.enabled
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.gridUnit
|
||||
|
||||
CheckBox {
|
||||
id: flatpak
|
||||
text: i18n("Flatpak applications")
|
||||
enabled: pkg.flatpak
|
||||
}
|
||||
|
||||
Kirigami.UrlButton {
|
||||
id: instTip
|
||||
url: "https://flathub.org/setup"
|
||||
text: i18n("Not installed")
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
color: Kirigami.Theme.neutralTextColor
|
||||
visible: !pkg.flatpak
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: widgets
|
||||
text: i18n("Plasma Widgets")
|
||||
enabled: pkg.jq
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.gridUnit
|
||||
|
||||
CheckBox {
|
||||
id: fwupd
|
||||
text: i18n("Firmware")
|
||||
enabled: pkg.fwupdmgr
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: currentTab === 0
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
ColumnLayout {
|
||||
id: feedsUrl
|
||||
Layout.fillWidth: true
|
||||
enabled: pkg.jq
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
function syncFromModel() {
|
||||
var items = []
|
||||
for (var i = 0; i < feedsModel.count; i++) {
|
||||
items.push(feedsModel.get(i).url)
|
||||
}
|
||||
feeds = items
|
||||
cfg_feeds = JS.serializeCustomFeeds(feeds)
|
||||
}
|
||||
function syncToModel() {
|
||||
feedsModel.clear()
|
||||
for (var i = 0; i < feeds.length; i++) {
|
||||
feedsModel.append({url: feeds[i]})
|
||||
}
|
||||
}
|
||||
function reset() {
|
||||
feedsModel.clear()
|
||||
var defaults = JS.parseCustomFeeds(plasmoid.configuration.feedsDefault)
|
||||
for (var i = 0; i < defaults.length; i++) {
|
||||
feedsModel.append({url: defaults[i]})
|
||||
}
|
||||
syncFromModel()
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: feedsModel
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: feedsList
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: contentHeight
|
||||
model: feedsModel
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
header: Kirigami.InlineViewHeader {
|
||||
width: feedsList.width
|
||||
text: i18n("RSS Feeds")
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "rating"
|
||||
enabled: cfg_feedsEnabled && pkg.jq
|
||||
onTriggered: recommendedFeedsDialog.open()
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: cfg_feedsEnabled ? "news-subscribe" : "news-unsubscribe"
|
||||
text: cfg_feedsEnabled ? i18n("Enabled") : i18n("Disabled")
|
||||
onTriggered: cfg_feedsEnabled = !cfg_feedsEnabled
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-reset"
|
||||
text: i18n("Reset")
|
||||
enabled: cfg_feedsEnabled && pkg.jq
|
||||
onTriggered: feedsUrl.reset()
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add-symbolic"
|
||||
text: i18n("Add")
|
||||
enabled: cfg_feedsEnabled && pkg.jq
|
||||
onTriggered: {
|
||||
feedsModel.insert(0, {url: ""})
|
||||
feedsUrl.syncFromModel()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
required property string url
|
||||
required property int index
|
||||
width: ListView.view.width
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
text: url
|
||||
placeholderText: "https://..."
|
||||
font.family: "monospace"
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
selectByMouse: true
|
||||
implicitHeight: Kirigami.Units.gridUnit * 1.8
|
||||
enabled: cfg_feedsEnabled
|
||||
onTextChanged: {
|
||||
feedsModel.setProperty(index, "url", text)
|
||||
feedsUrl.syncFromModel()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
icon.name: "delete-symbolic"
|
||||
implicitWidth: Kirigami.Units.gridUnit * 2
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
ToolTip {
|
||||
text: i18n("Remove")
|
||||
delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
onClicked: {
|
||||
feedsModel.remove(index)
|
||||
feedsUrl.syncFromModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition { NumberAnimation { property: "x"; from: 80; to: 0; duration: 320; easing.type: Easing.OutCubic } }
|
||||
moveDisplaced: Transition { NumberAnimation { properties: "x,y"; duration: 300; easing.type: Easing.OutCubic } }
|
||||
move: Transition { NumberAnimation { properties: "x,y"; duration: 300; easing.type: Easing.OutCubic } }
|
||||
removeDisplaced: Transition { NumberAnimation { properties: "x,y"; duration: 280; easing.type: Easing.OutCubic } }
|
||||
remove: Transition { ParallelAnimation {
|
||||
NumberAnimation { property: "opacity"; to: 0; duration: 220; easing.type: Easing.InQuad }
|
||||
NumberAnimation { property: "x"; to: 80; duration: 300; easing.type: Easing.InCubic } } }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
enabled: pkg.jq && cfg_feedsEnabled && cfg_feeds !== ""
|
||||
|
||||
SpinBox {
|
||||
id: feedsFetchCount
|
||||
from: 1
|
||||
to: 10
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: i18np(
|
||||
"latest article from each feed",
|
||||
"latest articles from each feed",
|
||||
feedsFetchCount.value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: recommendedFeedsDialog
|
||||
title: i18n("Recommended RSS Feeds")
|
||||
preferredWidth: Kirigami.Units.gridUnit * 25
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ name: "This week in Plasma", url: "https://blogs.kde.org/categories/this-week-in-plasma/index.xml" },
|
||||
{ name: "This week in KDE Apps", url: "https://blogs.kde.org/categories/this-week-in-kde-apps/index.xml" },
|
||||
{ name: "KDE Community", url: "https://kde.org/index.xml" },
|
||||
{ name: "CachyOS's Blog", url: "https://cachyos.org/rss.xml" },
|
||||
{ name: "Apdatifier Release Notes", url: "https://github.com/exequtic/apdatifier/releases.atom" }
|
||||
]
|
||||
|
||||
delegate: Frame {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: index > 0 ? 2 : 0
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Label {
|
||||
text: modelData.name
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
icon.name: "list-add"
|
||||
text: i18n("Add")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
|
||||
onClicked: {
|
||||
if (!feeds.includes(modelData.url)) {
|
||||
feedsModel.insert(0, {url: modelData.url})
|
||||
feedsUrl.syncFromModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: notificationsTab
|
||||
visible: currentTab === 1
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Notifications") + ":"
|
||||
id: notifyUpdates
|
||||
text: i18n("For new updates")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: notifyUpdatesAction
|
||||
text: i18n("Action button")
|
||||
enabled: notifyUpdates.checked
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: notifyEveryBump
|
||||
text: i18n("For every version bump")
|
||||
enabled: notifyUpdates.checked
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("If the option is <b>enabled</b>, notifications will be sent when a new version of the package is bumped, even if the package is already on the list. <b>More notifications.</b> <br><br>If the option is <b>disabled</b>, notifications will only be sent for packages that are not yet on the list. <b>Less notifications.</b>")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
CheckBox {
|
||||
id: notifyNews
|
||||
text: i18n("For news")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: notifyNewsAction
|
||||
text: i18n("Action button")
|
||||
enabled: notifyNews.checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: notifyErrors
|
||||
text: i18n("For errors")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: notifySound
|
||||
text: i18n("With sound")
|
||||
enabled: notifyUpdates.checked || notifyNews.checked || notifyErrors.checked
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: notifyPersistent
|
||||
text: i18n("Persistent")
|
||||
enabled: notifyUpdates.checked || notifyNews.checked || notifyErrors.checked
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: notifyTip
|
||||
|
||||
Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.maximumWidth: 250
|
||||
font.pointSize: instTip.font.pointSize
|
||||
text: i18n("To further configure, click the button below -> Application Settings -> Apdatifier")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: notifyTip.horizontalCenter
|
||||
enabled: notifyUpdates.checked || notifyNews.checked || notifyErrors.checked
|
||||
icon.name: "settings-configure"
|
||||
text: i18n("Configure...")
|
||||
onClicked: KCMLauncher.openSystemSettings("kcm_notifications")
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: mouseActionsTab
|
||||
visible: currentTab === 2
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
QQC.ComboBox {
|
||||
Kirigami.FormData.label: i18n("Middle click") + ":"
|
||||
type: "middle"
|
||||
}
|
||||
|
||||
QQC.ComboBox {
|
||||
Kirigami.FormData.label: i18n("Right click") + ":"
|
||||
type: "right"
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Do not enable this option if the widget is not used in the system tray; otherwise, you will not be able to open the settings by right-clicking.")
|
||||
}
|
||||
}
|
||||
|
||||
QQC.ComboBox {
|
||||
Kirigami.FormData.label: i18n("Scroll up") + ":"
|
||||
type: "scrollUp"
|
||||
}
|
||||
|
||||
QQC.ComboBox {
|
||||
Kirigami.FormData.label: i18n("Scroll down") + ":"
|
||||
type: "scrollDown"
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: miscTab
|
||||
visible: currentTab === 3
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.preferredWidth: miscTab.width - Kirigami.Units.largeSpacing * 10
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 500
|
||||
icon.name: "backup"
|
||||
text: i18n("Restore hidden tooltips")
|
||||
onClicked: {
|
||||
plasmoid.configuration.configMsg = true
|
||||
plasmoid.configuration.newsMsg = true
|
||||
plasmoid.configuration.version = "v0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.preferredWidth: miscTab.width - Kirigami.Units.largeSpacing * 10
|
||||
Button {
|
||||
enabled: !systemWide
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 500
|
||||
icon.name: "folder-git-symbolic"
|
||||
text: i18n("Install Development version")
|
||||
onClicked: {
|
||||
installButton = 0
|
||||
installDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.preferredWidth: miscTab.width - Kirigami.Units.largeSpacing * 10
|
||||
Button {
|
||||
enabled: !systemWide
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 500
|
||||
icon.name: "run-build"
|
||||
text: i18n("Install Stable version")
|
||||
onClicked: {
|
||||
installButton = 1
|
||||
installDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.preferredWidth: miscTab.width - Kirigami.Units.largeSpacing * 10
|
||||
Button {
|
||||
enabled: !systemWide
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 500
|
||||
icon.name: "delete"
|
||||
text: i18n("Uninstall widget")
|
||||
onClicked: {
|
||||
installButton = 2
|
||||
installDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.preferredWidth: miscTab.width - Kirigami.Units.largeSpacing * 10
|
||||
Label {
|
||||
text: "pacman DBPath:"
|
||||
}
|
||||
TextArea {
|
||||
id: dbPath
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 320
|
||||
readOnly: true
|
||||
Component.onCompleted: dbPath.text = plasmoid.configuration.dbPath
|
||||
}
|
||||
Button {
|
||||
icon.name: "document-open"
|
||||
onClicked: folderDialog.open()
|
||||
}
|
||||
Button {
|
||||
icon.name: "edit-reset"
|
||||
ToolTip.delay: 0
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: i18n("Reset to default. By default, the path is the same as for checkupdates.")
|
||||
onClicked: {
|
||||
dbPath.text = plasmoid.configuration.dbPathDefault
|
||||
pathError.visible = false
|
||||
}
|
||||
}
|
||||
FolderDialog {
|
||||
id: folderDialog
|
||||
onAccepted: {
|
||||
const path = selectedFolder.toString().substring(7)
|
||||
if (path.includes(' ')) {
|
||||
pathError.visible = true
|
||||
} else {
|
||||
dbPath.text = path
|
||||
pathError.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: pathError
|
||||
text: i18n("Path must not contain spaces")
|
||||
color: Kirigami.Theme.negativeTextColor
|
||||
visible: false
|
||||
}
|
||||
|
||||
Kirigami.PromptDialog {
|
||||
id: installDialog
|
||||
title: dialogTitles[installButton]
|
||||
subtitle: dialogSubtitles[installButton]
|
||||
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
onAccepted: {
|
||||
if (installButton === 0) {
|
||||
JS.execute(JS.runInTerminal("utils", "installDev"))
|
||||
} else if (installButton === 1) {
|
||||
JS.execute(JS.runInTerminal("utils", "installStable"))
|
||||
} else {
|
||||
JS.execute(JS.runInTerminal("utils", "uninstall"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Network")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkConn
|
||||
text: i18n("Connectivity check")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: respectMeteredConn
|
||||
text: i18n("Respect metered connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.iconthemes
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
ListModel {
|
||||
id: rulesModel
|
||||
Component.onCompleted: {
|
||||
JS.execute(JS.readFile(JS.rulesFile), (cmd, out, err, code) => {
|
||||
if (JS.Error(code, err)) return
|
||||
if (out && JS.validJSON(out, JS.rulesFile)) {
|
||||
JSON.parse(out).forEach(el => {
|
||||
if (el.type !== "name" && el.type !== "regex") return
|
||||
rulesModel.append({
|
||||
type: el.type,
|
||||
value: el.value,
|
||||
icon: el.icon,
|
||||
excluded: el.excluded,
|
||||
ignore: ('ignore' in el) ? el.ignore : false,
|
||||
important: ('important' in el) ? el.important : false
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: typesModel
|
||||
Component.onCompleted: {
|
||||
let types = [
|
||||
{name: i18n("Name"), type: "name", tip: i18n("Exact package name")},
|
||||
{name: i18n("Regex"), type: "regex", tip: i18n("See Help")}
|
||||
]
|
||||
|
||||
for (var i = 0; i < types.length; ++i) {
|
||||
typesModel.append({name: types[i].name, type: types[i].type, tip: types[i].tip})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rule
|
||||
Item {
|
||||
id: ruleItem
|
||||
required property int index
|
||||
required property var model
|
||||
width: rulesList.width - Kirigami.Units.largeSpacing * 2
|
||||
height: swipeListItem.height
|
||||
|
||||
Kirigami.SwipeListItem {
|
||||
id: swipeListItem
|
||||
width: ruleItem.width
|
||||
down: false
|
||||
hoverEnabled: true
|
||||
separatorVisible: true
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.ListItemDragHandle {
|
||||
listItem: swipeListItem
|
||||
listView: rulesList
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.minimumWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.maximumWidth: Kirigami.Units.iconSizes.small
|
||||
onMoveRequested: {
|
||||
var to = Math.max(0, Math.min(newIndex, rulesModel.count - 1))
|
||||
rulesModel.move(oldIndex, to, 1)
|
||||
}
|
||||
}
|
||||
ComboBox {
|
||||
implicitWidth: 120
|
||||
id: type
|
||||
model: typesModel
|
||||
textRole: "name"
|
||||
currentIndex: -1
|
||||
property bool ready: false
|
||||
onCurrentIndexChanged: {
|
||||
if (!ready) return
|
||||
rulesList.model.set(index, {
|
||||
"type": model.get(currentIndex).type,
|
||||
"value": "",
|
||||
"ignore": false
|
||||
})
|
||||
}
|
||||
Component.onCompleted: {
|
||||
var currentType = rulesList.model.get(index).type
|
||||
for (var i = 0; i < model.count; ++i) {
|
||||
if (model.get(i).type === currentType) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
ready = true
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: valueField
|
||||
Layout.fillWidth: true
|
||||
text: model.value
|
||||
placeholderText: type.model.get(type.currentIndex).tip
|
||||
enabled: true
|
||||
onTextChanged: {
|
||||
if (type.model.get(type.currentIndex).type === "name") {
|
||||
var filtered = valueField.text.replace(/[^a-z0-9_\-+.]/g, "")
|
||||
if (valueField.text !== filtered) {
|
||||
valueField.text = filtered
|
||||
return
|
||||
}
|
||||
}
|
||||
if (model.value !== valueField.text) {
|
||||
model.value = valueField.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolTip { text: model.icon }
|
||||
icon.name: model.icon
|
||||
onClicked: iconDialog.open()
|
||||
|
||||
IconDialog {
|
||||
id: iconDialog
|
||||
onIconNameChanged: model.icon = iconName
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolTip { text: i18n("Mark as important") }
|
||||
icon.name: model.important ? "flag-red" : "flag"
|
||||
checkable: true
|
||||
checked: model.important
|
||||
onClicked: model.important = !model.important
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolTip { text: model.excluded ? i18n("Show in the list") : i18n("Exclude from the list") }
|
||||
icon.name: model.excluded ? "hint" : "view-visible"
|
||||
checkable: true
|
||||
checked: model.excluded
|
||||
onClicked: model.excluded = !model.excluded
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolTip {
|
||||
text: i18n("Ignore a package upgrade. <b>Be careful in skipping packages, since partial upgrades are unsupported.</b>")
|
||||
}
|
||||
icon.name: model.ignore ? "process-stop" : "software-updates-updates"
|
||||
enabled: type.model.get(type.currentIndex).type === "name"
|
||||
checkable: true
|
||||
checked: model.ignore
|
||||
opacity: enabled ? 1 : 0.5
|
||||
onClicked: model.ignore = !model.ignore
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolTip { text: i18n("Remove") }
|
||||
icon.name: 'delete'
|
||||
onClicked: rulesList.model.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.InlineViewHeader {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Rules")
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "help-about-symbolic"
|
||||
text: i18n("Help")
|
||||
checkable: true
|
||||
onTriggered: rulesMsg.visible = checked
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add-symbolic"
|
||||
text: i18n("Add")
|
||||
onTriggered: rulesModel.append({
|
||||
type: "name", value: "",
|
||||
icon: plasmoid.configuration.ownIconsUI ? "apdatifier-package" : "server-database",
|
||||
excluded: false, important: false, ignore: false
|
||||
})
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "dialog-ok-apply"
|
||||
text: i18n("Apply")
|
||||
onTriggered: {
|
||||
var array = []
|
||||
for (var i = 0; i < rulesModel.count; i++) {
|
||||
var item = rulesModel.get(i)
|
||||
if (item.value.trim() !== "")
|
||||
array.push({
|
||||
type: item.type, value: item.value, icon: item.icon,
|
||||
excluded: item.excluded, important: item.important, ignore: item.ignore
|
||||
})
|
||||
}
|
||||
var rules = JS.toFileFormat(array)
|
||||
plasmoid.configuration.rules = rules
|
||||
JS.execute(JS.writeFile(rules, '>', JS.rulesFile))
|
||||
rulesModel.clear()
|
||||
for (var i = 0; i < array.length; i++)
|
||||
rulesModel.append(array[i])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
id: rulesMsg
|
||||
Layout.fillWidth: true
|
||||
icon.source: "showinfo"
|
||||
text: i18n("Rules apply top-to-bottom: later rules override earlier ones.<br><br>Regex examples:<br><b>^nvidia</b> — names starting with nvidia<br><b>wayland$</b> — names ending with wayland<br><b>.*qt.*</b> — names containing qt<br><b>(ttf|font|otf)</b> — any of ttf, font, or otf<br><b>linux(-zen|-lts)?$</b> — linux, linux-zen or linux-lts")
|
||||
visible: false
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
id: rulesList
|
||||
model: rulesModel
|
||||
delegate: rule
|
||||
clip: true
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
reuseItems: true
|
||||
moveDisplaced: Transition {
|
||||
YAnimator { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
ScrollBar.vertical: ScrollBar { active: true }
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
visible: rulesModel.count === 0
|
||||
text: i18n("No rules yet")
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3
|
||||
opacity: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
id: supportPage
|
||||
|
||||
leftPadding: Kirigami.Units.gridUnit
|
||||
rightPadding: Kirigami.Units.gridUnit
|
||||
|
||||
header: Item {
|
||||
height: layout.implicitHeight + (Kirigami.Units.gridUnit * 2)
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: parent.width - (Kirigami.Units.gridUnit * 2)
|
||||
anchors.centerIn: parent
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: i18n("Thanks for using my widget! If you appreciate my work, you can support me by starring the GitHub repository or buying me a coffee ;)")
|
||||
font.bold: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: menu
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
MenuItem {
|
||||
text: "buymeacoffee.com"
|
||||
icon.name: "internet-web-browser-symbolic"
|
||||
onTriggered: Qt.openUrlExternally("https://buymeacoffee.com/evgk")
|
||||
}
|
||||
MenuItem {
|
||||
text: "nowpayments.io (crypto)"
|
||||
icon.name: "internet-web-browser-symbolic"
|
||||
onTriggered: Qt.openUrlExternally("https://nowpayments.io/donation/exequtic")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
source: "../assets/art/apdatifier-donate.png"
|
||||
sourceSize.width: supportPage.width / 2
|
||||
sourceSize.height: supportPage.height
|
||||
|
||||
HoverHandler {
|
||||
id: handlerDonate
|
||||
cursorShape: menu.opened ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: (event) => menu.opened ? menu.close() : menu.popup(event.position.x, event.position.y)
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "Donate"
|
||||
font.bold: true
|
||||
visible: handlerDonate.hovered && !menu.opened
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
source: "../assets/art/apdatifier-githubstar.png"
|
||||
sourceSize.width: supportPage.width / 2
|
||||
sourceSize.height: supportPage.height
|
||||
|
||||
HoverHandler {
|
||||
id: handlerGithub
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: Qt.openUrlExternally("https://github.com/exequtic/apdatifier")
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.bold: true
|
||||
visible: handlerGithub.hovered
|
||||
text: "https://github.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import org.kde.kcmutils
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
SimpleKCM {
|
||||
property string cfg_terminal: plasmoid.configuration.terminal
|
||||
property alias cfg_tmuxSession: tmuxSession.checked
|
||||
property alias cfg_idleInhibit: idleInhibit.checked
|
||||
property alias cfg_termFont: termFont.checked
|
||||
property alias cfg_mngHideFlatpak: mngHideFlatpak.checked
|
||||
property alias cfg_mngHideHeaders: mngHideHeaders.checked
|
||||
property alias cfg_mngHideHelp: mngHideHelp.checked
|
||||
property alias cfg_mngHideNums: mngHideNums.checked
|
||||
property alias cfg_fzfOptions: fzfOptions.text
|
||||
|
||||
property string cfg_wrapper: plasmoid.configuration.wrapper
|
||||
property alias cfg_archFlags: archFlags.text
|
||||
property alias cfg_sudoBin: sudoBin.text
|
||||
property alias cfg_rebootSystem: rebootSystem.checked
|
||||
property string cfg_mirrors: plasmoid.configuration.mirrors
|
||||
property bool mirrorsGeneratorEnabled: cfg_mirrors !== "false"
|
||||
property alias cfg_mirrorsAge: mirrorsAge.value
|
||||
property alias cfg_mirrorCount: mirrorCount.value
|
||||
property alias cfg_mirrorProgress: mirrorProgress.checked
|
||||
property var countryList: []
|
||||
property string cfg_dynamicUrl: plasmoid.configuration.dynamicUrl
|
||||
|
||||
property alias cfg_flatpakRemoveUnused: flatpakRemoveUnused.checked
|
||||
property string cfg_flatpakFlags: plasmoid.configuration.flatpakFlags
|
||||
|
||||
property alias cfg_widgetConfirmation: widgetConfirmation.checked
|
||||
property alias cfg_restartShell: restartShell.checked
|
||||
property alias cfg_restartCommand: restartCommand.text
|
||||
property alias cfg_trayEnabledByDefault: trayEnabledByDefault.checked
|
||||
|
||||
property var pkg: plasmoid.configuration.packages
|
||||
property var terminals: plasmoid.configuration.terminals
|
||||
property alias cfg_preExec: preExec.text
|
||||
property alias cfg_postExec: postExec.text
|
||||
|
||||
property int currentTab
|
||||
signal tabChanged(currentTab: int)
|
||||
onCurrentTabChanged: tabChanged(currentTab)
|
||||
|
||||
header: Kirigami.NavigationTabBar {
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "akonadiconsole"
|
||||
text: i18n("General")
|
||||
checked: currentTab === 0
|
||||
onTriggered: currentTab = 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "apdatifier-package"
|
||||
text: "Arch"
|
||||
checked: currentTab === 1
|
||||
onTriggered: currentTab = 1
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "apdatifier-flatpak"
|
||||
text: "Flatpak"
|
||||
checked: currentTab === 2
|
||||
onTriggered: currentTab = 2
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "start-here-kde-plasma-symbolic"
|
||||
text: i18n("Widgets")
|
||||
checked: currentTab === 3
|
||||
onTriggered: currentTab = 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: terminalTab
|
||||
visible: currentTab === 0
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Terminal") + ":"
|
||||
|
||||
ComboBox {
|
||||
model: terminals
|
||||
textRole: "name"
|
||||
enabled: terminals
|
||||
|
||||
onCurrentIndexChanged: cfg_terminal = model[currentIndex]["value"]
|
||||
|
||||
Component.onCompleted: {
|
||||
if (terminals) {
|
||||
currentIndex = JS.setIndex(plasmoid.configuration.terminal, terminals)
|
||||
|
||||
if (!plasmoid.configuration.terminal) {
|
||||
plasmoid.configuration.terminal = model[0]["value"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.UrlButton {
|
||||
url: "https://github.com/exequtic/apdatifier#supported-terminals"
|
||||
text: i18n("Not installed")
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
color: Kirigami.Theme.neutralTextColor
|
||||
visible: !terminals
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: termFont
|
||||
text: i18n("Use NerdFont icons")
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("If your terminal utilizes any <b>Nerd Font</b>, icons from that font will be used.")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: tmuxSession
|
||||
text: i18n("tmux session")
|
||||
enabled: pkg.tmux
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: idleInhibit
|
||||
text: i18n("Idle Inhibit")
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Disables automatic sleep and screen lock while upgrading.")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Pre/post upgrade scripts")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Pre-exec") + ":"
|
||||
|
||||
TextField {
|
||||
id: preExec
|
||||
placeholderText: i18n("Command or script path")
|
||||
placeholderTextColor: "grey"
|
||||
}
|
||||
|
||||
Button {
|
||||
icon.name: "document-open"
|
||||
onClicked: fileDialogPreExec.open()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogPreExec
|
||||
fileMode: FileDialog.OpenFile
|
||||
onAccepted: preExec.text = selectedFile.toString().substring(7)
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Running your command or script BEFORE the upgrade.<br>For example, you can specify your command to update the mirrorlist if you have unofficial repositories.")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Post-exec") + ":"
|
||||
|
||||
TextField {
|
||||
id: postExec
|
||||
placeholderText: i18n("Command or script path")
|
||||
placeholderTextColor: "grey"
|
||||
}
|
||||
|
||||
Button {
|
||||
icon.name: "document-open"
|
||||
onClicked: fileDialogPostExec.open()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogPostExec
|
||||
fileMode: FileDialog.OpenFile
|
||||
onAccepted: postExec.text = selectedFile.toString().substring(7)
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Running your command or script AFTER the upgrade.<br>For example, you can specify your command to upgrade something else.")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Management")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: mngHideFlatpak
|
||||
text: i18n("Without Flatpak options")
|
||||
}
|
||||
CheckBox {
|
||||
id: mngHideHeaders
|
||||
text: i18n("Hide headers")
|
||||
}
|
||||
CheckBox {
|
||||
id: mngHideHelp
|
||||
text: i18n("Hide help")
|
||||
}
|
||||
CheckBox {
|
||||
id: mngHideNums
|
||||
text: i18n("Hide option numbers")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Fzf options") + ":"
|
||||
|
||||
TextField {
|
||||
id: fzfOptions
|
||||
placeholderText: "--style full --layout default"
|
||||
placeholderTextColor: "grey"
|
||||
Layout.preferredWidth: 250
|
||||
onTextChanged: {
|
||||
var sanitized = fzfOptions.text.replace(/["'\\$`\n\r]/g, "")
|
||||
if (sanitized !== fzfOptions.text)
|
||||
fzfOptions.text = sanitized
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Override default fzf options.<br>See <b>fzf --help</b> for available options.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: archTab
|
||||
visible: currentTab === 1
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
enabled: plasmoid.configuration.aur
|
||||
Kirigami.FormData.label: i18n("Wrapper") + ":"
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
ButtonGroup {
|
||||
id: wrappersGroup
|
||||
}
|
||||
RadioButton {
|
||||
ButtonGroup.group: wrappersGroup
|
||||
text: "paru"
|
||||
enabled: pkg.paru
|
||||
checked: plasmoid.configuration.wrapper === text
|
||||
onCheckedChanged: if (checked) cfg_wrapper = text
|
||||
}
|
||||
RadioButton {
|
||||
ButtonGroup.group: wrappersGroup
|
||||
text: "yay"
|
||||
enabled: pkg.yay
|
||||
checked: plasmoid.configuration.wrapper === text
|
||||
onCheckedChanged: if (checked) cfg_wrapper = text
|
||||
}
|
||||
RadioButton {
|
||||
ButtonGroup.group: wrappersGroup
|
||||
text: "pikaur"
|
||||
enabled: pkg.pikaur
|
||||
checked: plasmoid.configuration.wrapper === text
|
||||
onCheckedChanged: if (checked) cfg_wrapper = text
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: !plasmoid.configuration.aur
|
||||
Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
font.bold: true
|
||||
color: Kirigami.Theme.negativeTextColor
|
||||
text: i18n("AUR disabled in search settings")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Upgrade options") + ":"
|
||||
|
||||
Label {
|
||||
text: plasmoid.configuration.aur ? cfg_wrapper+" "+"-Syu" : cfg_sudoBin+" "+"pacman -Syu"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: archFlags
|
||||
onTextChanged: {
|
||||
var allow = /^[a-z0-9\- ]*$/
|
||||
if (!allow.test(archFlags.text))
|
||||
archFlags.text = archFlags.text.replace(/[^a-z0-9\- ]/g, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: "sudobin:"
|
||||
spacing: 0
|
||||
enabled: pkg.pacman
|
||||
|
||||
TextField {
|
||||
id: sudoBin
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Reboot system") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: rebootSystem
|
||||
text: i18n("Suggest after upgrading")
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("This option suggests restarting the system after upgrading critical packages.<br><br><b>Note that not all critical packages require a full system restart; some may only need a session restart or no action at all, such as when an updated package is not currently running (e.g., an alternative kernel) or not in use (e.g., an alternative driver).</b>")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Kirigami.FormData.label: i18n("Pacman Mirrorlist Generator")
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
height: 30
|
||||
Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
font.bold: true
|
||||
color: Kirigami.Theme.negativeTextColor
|
||||
text: i18n("Only for official repositories")+"!\n"+" (core, extra, multilib, ...)"
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Also see https://archlinux.org/mirrorlist (click button to open link)")
|
||||
onClicked: Qt.openUrlExternally("https://archlinux.org/mirrorlist")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: generator
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
Kirigami.FormData.label: i18n("Generator") + ":"
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: generator
|
||||
id: mirrors
|
||||
text: i18n("Disabled")
|
||||
enabled: pkg.pacman
|
||||
checked: {
|
||||
plasmoid.configuration.mirrors === "false"
|
||||
}
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_mirrors = "false"
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (!checked && !enabled) {
|
||||
checked = true
|
||||
plasmoid.configuration.mirrors = "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: generator
|
||||
text: i18n("Always ask")
|
||||
checked: plasmoid.configuration.mirrors === "alwaysAsk"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_mirrors = "alwaysAsk"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
RadioButton {
|
||||
ButtonGroup.group: generator
|
||||
text: i18n("Ask if older than")
|
||||
checked: plasmoid.configuration.mirrors === "age"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_mirrors = "age"
|
||||
}
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: mirrorsAge
|
||||
from: 1
|
||||
to: 999
|
||||
stepSize: 1
|
||||
value: mirrorsAge
|
||||
}
|
||||
|
||||
Label {
|
||||
text: i18np("day", "days", mirrorsAge.value)
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: generator
|
||||
text: i18n("No ask, force refresh")
|
||||
checked: plasmoid.configuration.mirrors === "force"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_mirrors = "force"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Protocol") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: http
|
||||
text: "http"
|
||||
onClicked: updateUrl()
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: https
|
||||
text: "https"
|
||||
onClicked: updateUrl()
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("IP version") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: ipv4
|
||||
text: "IPv4"
|
||||
onClicked: updateUrl()
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: ipv6
|
||||
text: "IPv6"
|
||||
onClicked: updateUrl()
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Mirror status") + ":"
|
||||
id: mirrorstatus
|
||||
text: i18n("Enable")
|
||||
onClicked: updateUrl()
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Number output") + ":"
|
||||
|
||||
SpinBox {
|
||||
id: mirrorCount
|
||||
from: 0
|
||||
to: 10
|
||||
stepSize: 1
|
||||
value: mirrorCount
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("Number of servers to write to mirrorlist file. 0 for all.")
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
Kirigami.FormData.label: i18n("Show progress") + ":"
|
||||
id: mirrorProgress
|
||||
text: i18n("Enable")
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Country") + ":"
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
|
||||
Label {
|
||||
textFormat: Text.RichText
|
||||
text: {
|
||||
var matchResult = cfg_dynamicUrl.match(/country=([A-Z]+)/g)
|
||||
if (matchResult !== null) {
|
||||
var countries = matchResult.map(str => str.split("=")[1]).join(", ")
|
||||
return countries
|
||||
} else {
|
||||
return '<a style="color: ' + Kirigami.Theme.negativeTextColor + '">' + i18n("Select at least one!") + '</a>'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("You must select at least one country, otherwise all will be chosen by default. <br><br><b>The more countries you select, the longer it will take to generate the mirrors!</b> <br><br>It is optimal to choose <b>1-2</b> countries closest to you.")
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.maximumWidth: archTab.width / 2.5
|
||||
Layout.maximumHeight: 200
|
||||
enabled: mirrorsGeneratorEnabled
|
||||
|
||||
ScrollView {
|
||||
Layout.preferredWidth: archTab.width / 2.5
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
GridLayout {
|
||||
columns: 1
|
||||
|
||||
Repeater {
|
||||
model: countryListModel
|
||||
delegate: CheckBox {
|
||||
text: model.text
|
||||
checked: model.checked
|
||||
onClicked: {
|
||||
model.checked = checked
|
||||
checked ? countryList.push(model.code) : countryList.splice(countryList.indexOf(model.code), 1)
|
||||
updateUrl()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: flatpakTab
|
||||
visible: currentTab === 2
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Uninstall unused") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: flatpakRemoveUnused
|
||||
text: i18n("Enable")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: flatpakFlags
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
Kirigami.FormData.label: i18n("Upgrade options") + ":"
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: flatpakFlags
|
||||
text: i18n("Normal")
|
||||
checked: plasmoid.configuration.flatpakFlags === ""
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_flatpakFlags = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: flatpakFlags
|
||||
text: i18n("Normal, skip questions")
|
||||
checked: plasmoid.configuration.flatpakFlags === "--assumeyes"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_flatpakFlags = "--assumeyes"
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: flatpakFlags
|
||||
text: i18n("Non interactive, skip questions")
|
||||
checked: plasmoid.configuration.flatpakFlags === "--noninteractive"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_flatpakFlags = "--noninteractive"
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: flatpakFlags
|
||||
text: i18n("Verbose")
|
||||
checked: plasmoid.configuration.flatpakFlags === "--verbose"
|
||||
onCheckedChanged: {
|
||||
if (checked) cfg_flatpakFlags = "--verbose"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
id: widgetsTab
|
||||
visible: currentTab === 3
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Upgrade confirmation") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: widgetConfirmation
|
||||
text: i18n("Enable")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Restart plasmashell") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: restartShell
|
||||
text: i18n("Suggest after upgrading")
|
||||
}
|
||||
|
||||
Kirigami.ContextualHelpButton {
|
||||
toolTipText: i18n("After upgrading widget, the old version will still remain in memory until you restart plasmashell. To avoid doing this manually, enable this option.")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Command") + ":"
|
||||
|
||||
TextField {
|
||||
id: restartCommand
|
||||
enabled: restartShell.checked
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Kirigami.FormData.isSection: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Kirigami.FormData.label: i18n("Apdatifier tray icon") + ":"
|
||||
|
||||
CheckBox {
|
||||
id: trayEnabledByDefault
|
||||
text: i18n("Enabled by default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (tmuxSession.checked && !pkg.tmux) tmuxSession.checked = plasmoid.configuration.tmuxSession = false
|
||||
|
||||
if(cfg_dynamicUrl) {
|
||||
var urlParams = plasmoid.configuration.dynamicUrl.split("?")[1].split("&")
|
||||
|
||||
for (var i = 0; i < urlParams.length; i++) {
|
||||
var param = urlParams[i]
|
||||
if (param.includes("use_mirror_status=on")) mirrorstatus.checked = true
|
||||
if (/protocol=http\b/.test(param)) http.checked = true
|
||||
if (param.includes("protocol=https")) https.checked = true
|
||||
if (param.includes("ip_version=4")) ipv4.checked = true
|
||||
if (param.includes("ip_version=6")) ipv6.checked = true
|
||||
if (param.includes("country=")) {
|
||||
var country = decodeURIComponent(param.split("=")[1])
|
||||
countryList.push(country)
|
||||
for (var j = 0; j < countryListModel.count; ++j) {
|
||||
if (countryListModel.get(j).code === country) {
|
||||
countryListModel.get(j).checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateUrl() {
|
||||
var params = ""
|
||||
if (http.checked) params += "&protocol=http"
|
||||
if (https.checked) params += "&protocol=https"
|
||||
if (ipv4.checked) params += "&ip_version=4"
|
||||
if (ipv6.checked) params += "&ip_version=6"
|
||||
if (mirrorstatus.checked) params += "&use_mirror_status=on"
|
||||
|
||||
for (var i = 0; i < countryList.length; i++) {
|
||||
params += "&country=" + countryList[i]
|
||||
}
|
||||
|
||||
var baseUrl = "https://archlinux.org/mirrorlist/?"
|
||||
cfg_dynamicUrl = baseUrl + params.substring(1)
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: countryListModel
|
||||
|
||||
function createCountryList() {
|
||||
let countries =
|
||||
"Australia:AU, Austria:AT, Azerbaijan:AZ, Bangladesh:BD, Belarus:BY, Belgium:BE, " +
|
||||
"Brazil:BR, Bulgaria:BG, Cambodia:KH, Canada:CA, Chile:CL, China:CN, Colombia:CO, " +
|
||||
"Croatia:HR, Czech Republic:CZ, Denmark:DK, Ecuador:EC, Estonia:EE, Finland:FI, " +
|
||||
"France:FR, Georgia:GE, Germany:DE, Greece:GR, Hong Kong:HK, Hungary:HU, Iceland:IS, " +
|
||||
"India:IN, Indonesia:ID, Iran:IR, Israel:IL, Italy:IT, Japan:JP, Kazakhstan:KZ, Kenya:KE, " +
|
||||
"Latvia:LV, Lithuania:LT, Luxembourg:LU, Malaysia:MY, Mauritius:MU, Mexico:MX, Moldova:MD, " +
|
||||
"Morocco:MA, Nepal:NP, Netherlands:NL, New Caledonia:NC, New Zealand:NZ, North Macedonia:MK, " +
|
||||
"Norway:NO, Paraguay:PY, Poland:PL, Portugal:PT, Romania:RO, Russia:RU, Réunion:RE, " +
|
||||
"Saudi Arabia:SA, Serbia:RS, Singapore:SG, Slovakia:SK, Slovenia:SI, South Africa:ZA, " +
|
||||
"South Korea:KR, Spain:ES, Sweden:SE, Switzerland:CH, Taiwan:TW, Thailand:TH, Turkey:TR, " +
|
||||
"Ukraine:UA, United Arab Emirates:AE, United Kingdom:GB, United States:US, Uzbekistan:UZ, Vietnam:VN"
|
||||
|
||||
countries.split(", ").map(item => {
|
||||
let [country, code] = item.split(":")
|
||||
countryListModel.append({text: country, code: code, checked: false})
|
||||
})
|
||||
}
|
||||
|
||||
Component.onCompleted: createCountryList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtNetwork
|
||||
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
|
||||
import "components"
|
||||
import "representation" as Rep
|
||||
import "../tools/tools.js" as JS
|
||||
|
||||
PlasmoidItem {
|
||||
id: root
|
||||
compactRepresentation: Rep.Panel {}
|
||||
fullRepresentation: Rep.Expanded {
|
||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 24
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 16
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
}
|
||||
|
||||
switchWidth: Kirigami.Units.gridUnit * 24
|
||||
switchHeight: Kirigami.Units.gridUnit * 16
|
||||
|
||||
Plasmoid.busy: plasmoid.location === PlasmaCore.Types.Floating ? false : ((cfg.busyIndicator || "spinner") === "spinner" ? sts.busy : false)
|
||||
Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground
|
||||
Plasmoid.icon: plasmoid.configuration.selectedIcon
|
||||
|
||||
function updatePlasmoidStatus() {
|
||||
if (sts.count >= cfg.hideIconPolicy || sts.busy || sts.error || panelConfigurationMode) {
|
||||
Plasmoid.status = PlasmaCore.Types.ActiveStatus
|
||||
} else {
|
||||
Plasmoid.status = inTray ? PlasmaCore.Types.PassiveStatus : PlasmaCore.Types.HiddenStatus
|
||||
}
|
||||
}
|
||||
|
||||
property int hideIconPolicy: cfg.hideIconPolicy
|
||||
property int countUpdates: sts.count
|
||||
onHideIconPolicyChanged: updatePlasmoidStatus()
|
||||
onCountUpdatesChanged: updatePlasmoidStatus()
|
||||
|
||||
toolTipMainText: sts.paused ? i18n("Auto check disabled") : ""
|
||||
toolTipSubText: sts.busy ? sts.statusMsg : sts.checkTimeTooltip
|
||||
|
||||
hideOnWindowDeactivate: !pinned
|
||||
|
||||
property bool isOnline: cfg.checkConn ? (NetworkInformation.reachability === NetworkInformation.Reachability.Online) : true
|
||||
property bool isMetered: NetworkInformation.isMetered && cfg.respectMeteredConn
|
||||
property bool inTray: (plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentDrawsPlasmoidHeading)
|
||||
property bool onDesktop: plasmoid.location === PlasmaCore.Types.Floating
|
||||
property bool horizontal: plasmoid.location === PlasmaCore.Types.TopEdge || plasmoid.location === PlasmaCore.Types.BottomEdge
|
||||
property bool panelConfigurationMode: Plasmoid.containment.corona?.editMode ?? false
|
||||
property bool pinned: false
|
||||
property bool listCompactMode: false
|
||||
property var cache: []
|
||||
property string checkMode: plasmoid.configuration.checkMode
|
||||
property bool sorting: plasmoid.configuration.sorting
|
||||
property string rules: plasmoid.configuration.rules || ""
|
||||
property var pkg: plasmoid.configuration.packages || ""
|
||||
property var cfg: plasmoid.configuration
|
||||
property var configuration: JSON.stringify(cfg)
|
||||
|
||||
QtObject {
|
||||
id: sts
|
||||
property bool init: false
|
||||
property var errors: []
|
||||
property int count: 0
|
||||
property double lastCheck: 0
|
||||
property bool busy: true
|
||||
property bool upgrading: false
|
||||
property bool error: !busy && errors.length > 0
|
||||
property bool paused: !busy && !scheduler.running && cfg.checkMode !== "manual"
|
||||
property string statusMsg: ""
|
||||
property string statusIco: ""
|
||||
property string checkTimeTooltip: ""
|
||||
property var proc: null
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: listModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: newsModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: activeNewsModel
|
||||
}
|
||||
|
||||
Notification {
|
||||
id: notify
|
||||
}
|
||||
|
||||
Plasmoid.contextualActions: [
|
||||
PlasmaCore.Action {
|
||||
text: i18n("Check updates")
|
||||
icon.name: "view-refresh"
|
||||
enabled: !sts.upgrading
|
||||
onTriggered: sts.proc ? JS.stopCheck() : JS.checkUpdates()
|
||||
},
|
||||
PlasmaCore.Action {
|
||||
text: i18n("Upgrade system")
|
||||
icon.name: "akonadiconsole"
|
||||
enabled: (cfg.terminal && cfg.tmuxSession && sts.count) || (cfg.terminal && !sts.busy && sts.count)
|
||||
onTriggered: JS.upgradeSystem()
|
||||
},
|
||||
PlasmaCore.Action {
|
||||
text: i18n("Management")
|
||||
icon.name: "tools"
|
||||
enabled: cfg.terminal && pkg.pacman
|
||||
onTriggered: JS.management()
|
||||
}
|
||||
]
|
||||
|
||||
Timer {
|
||||
id: scheduler
|
||||
interval: 10 * 1000
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: JS.searchScheduler()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: upgradeTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: JS.upgradingState()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 1000
|
||||
onTriggered: JS.saveConfig()
|
||||
}
|
||||
|
||||
onIsOnlineChanged: (!isOnline && sts.proc) && JS.stopCheck()
|
||||
onCheckModeChanged: sts.init && scheduler.restart()
|
||||
onSortingChanged: sts.init && JS.refreshListModel()
|
||||
onRulesChanged: sts.init && JS.refreshListModel()
|
||||
onConfigurationChanged: saveTimer.start()
|
||||
Component.onCompleted: JS.init()
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kitemmodels
|
||||
import org.kde.plasma.extras
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.plasma.components
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
|
||||
import "../components"
|
||||
import "../scrollview" as View
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
Representation {
|
||||
property string currVersion: "v2.9.9"
|
||||
property bool searchFieldOpen: false
|
||||
property bool expanded: root.expanded
|
||||
onExpandedChanged: {
|
||||
if (plasmoid.configuration.switchDefaultTab && !expanded)
|
||||
listCompactMode = (plasmoid.configuration.defaultTab !== 0)
|
||||
}
|
||||
|
||||
function svg(icon) {
|
||||
return Qt.resolvedUrl("../assets/icons/" + icon + ".svg")
|
||||
}
|
||||
|
||||
property var backgroundHidden: (Plasmoid.formFactor === PlasmaCore.Types.Planar) && (Plasmoid.userBackgroundHints === PlasmaCore.Types.ShadowBackground)
|
||||
onBackgroundHiddenChanged: topHeader.background.visible = bottomHeader.background.visible = !backgroundHidden
|
||||
|
||||
|
||||
property bool activeNewsItems: false
|
||||
function checkActiveNewsItems() {
|
||||
activeNewsItems = false
|
||||
for (let i = 0; i < newsModel.count; ++i) {
|
||||
if (newsModel.get(i).removed === false) {
|
||||
activeNewsItems = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: newsModel
|
||||
function onDataChanged() {
|
||||
checkActiveNewsItems()
|
||||
}
|
||||
function onCountChanged() {
|
||||
checkActiveNewsItems()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: checkActiveNewsItems()
|
||||
|
||||
header: PlasmoidHeading {
|
||||
id: topHeader
|
||||
visible: (cfg.showStatusText || cfg.showToolBar) && !sts.error
|
||||
contentItem: RowLayout {
|
||||
id: toolBar
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Kirigami.Units.iconSizes.medium
|
||||
Layout.maximumHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
RowLayout {
|
||||
id: status
|
||||
Layout.alignment: cfg.showToolBar ? Qt.AlignLeft : Qt.AlignHCenter
|
||||
spacing: Kirigami.Units.smallSpacing / 2
|
||||
visible: cfg.showStatusText
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
|
||||
Kirigami.Icon {
|
||||
height: parent.height
|
||||
width: parent.height
|
||||
anchors.centerIn: parent
|
||||
source: cfg.ownIconsUI ? svg(sts.statusIco) : sts.statusIco
|
||||
color: Kirigami.Theme.colorSet
|
||||
scale: cfg.ownIconsUI ? 0.7 : 0.9
|
||||
isMask: cfg.ownIconsUI
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: toolBar.width - toolBarButtons.width - Kirigami.Units.iconSizes.smallMedium
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: sts.statusMsg
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: toolBarButtons
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
visible: cfg.showToolBar
|
||||
|
||||
ToolbarButton {
|
||||
id: searchButton
|
||||
tooltipText: i18n("Filter by package name")
|
||||
iconSource: cfg.ownIconsUI ? svg("toolbar_search") : "search"
|
||||
visible: cfg.searchButton && sts.count
|
||||
enabled: visible && swipeView.currentIndex === 0
|
||||
onClicked: {
|
||||
if (searchFieldOpen) searchField.text = ""
|
||||
searchFieldOpen = !searchField.visible
|
||||
searchField.focus = searchFieldOpen
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: sts.paused ? i18n("Enable auto search updates") : i18n("Disable auto search updates")
|
||||
iconSource: cfg.ownIconsUI ? (!sts.paused ? svg("toolbar_pause") : svg("toolbar_start"))
|
||||
: (!sts.paused ? "media-playback-paused" : "media-playback-playing")
|
||||
iconColor: sts.paused ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.colorSet
|
||||
enabled: !sts.busy
|
||||
visible: enabled && cfg.intervalButton && cfg.checkMode !== "manual"
|
||||
onClicked: JS.switchScheduler()
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: cfg.sorting ? i18n("Sort packages by name") : i18n("Sort packages by repository")
|
||||
iconSource: cfg.ownIconsUI ? svg("toolbar_sort") : "sort-name"
|
||||
visible: cfg.sortButton && !sts.busy && sts.count
|
||||
enabled: visible && swipeView.currentIndex === 0
|
||||
onClicked: cfg.sorting = !cfg.sorting
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: listCompactMode ? i18n("Extended") : i18n("Compact")
|
||||
iconSource: cfg.ownIconsUI ? (listCompactMode ? svg("tab_extended") : svg("tab_compact"))
|
||||
: (listCompactMode ? "view-split-top-bottom" : "view-split-left-right")
|
||||
|
||||
|
||||
visible: cfg.viewButton && sts.count && swipeView.currentIndex === 0
|
||||
enabled: visible
|
||||
onClicked: listCompactMode = !listCompactMode
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
id: managementButton
|
||||
tooltipText: i18n("Management")
|
||||
iconSource: cfg.ownIconsUI ? svg("toolbar_management") : "tools"
|
||||
enabled: !sts.busy && pkg.pacman !== "" && cfg.terminal
|
||||
visible: enabled && cfg.managementButton
|
||||
onClicked: { buttonTooltip.hide(); JS.management() }
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
id: upgradeButton
|
||||
tooltipText: i18n("Upgrade system")
|
||||
iconSource: cfg.ownIconsUI ? svg("toolbar_upgrade") : "akonadiconsole"
|
||||
enabled: !sts.busy && sts.count && cfg.terminal
|
||||
visible: enabled && cfg.upgradeButton
|
||||
onClicked: { buttonTooltip.hide(); JS.upgradeSystem() }
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: sts.busy ? i18n("Stop checking") : i18n("Check updates")
|
||||
iconSource: cfg.ownIconsUI ? (sts.busy ? svg("toolbar_stop") : svg("toolbar_check"))
|
||||
: (sts.busy ? "media-playback-stopped" : "view-refresh")
|
||||
visible: cfg.checkButton && !sts.upgrading
|
||||
onClicked: sts.proc ? JS.stopCheck() : JS.checkUpdates()
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: i18n("Open settings")
|
||||
iconSource: cfg.ownIconsUI ? svg("toolbar_settings") : "settings-configure"
|
||||
visible: cfg.settingsButton && !inTray && !sts.busy
|
||||
onClicked: plasmoid.internalAction("configure").trigger()
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
tooltipText: pinned ? i18n("Unpin window") : i18n("Keep open")
|
||||
iconSource: cfg.ownIconsUI ? (pinned ? svg("toolbar_unpin") : svg("toolbar_pin"))
|
||||
: (pinned ? "window-unpin" : "window-pin")
|
||||
visible: cfg.pinButton && !inTray && !onDesktop
|
||||
onClicked: pinned = !pinned
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: PlasmoidHeading {
|
||||
id: bottomHeader
|
||||
spacing: 0
|
||||
topPadding: 0
|
||||
height: Kirigami.Units.iconSizes.medium
|
||||
visible: cfg.tabBarVisible && cfg.feedsEnabled && !sts.error
|
||||
|
||||
contentItem: TabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
position: TabBar.Footer
|
||||
currentIndex: swipeView.currentIndex
|
||||
onCurrentIndexChanged: {
|
||||
swipeView.currentIndex = currentIndex
|
||||
if (swipeView.currentIndex >= 1) {
|
||||
searchFieldOpen = false
|
||||
searchField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
id: "updates",
|
||||
icon: "status_package",
|
||||
fallback: "kpackagekit-updates",
|
||||
label: i18n("Updates"),
|
||||
},
|
||||
cfg.feedsEnabled ? {
|
||||
id: "news",
|
||||
icon: "status_news",
|
||||
fallback: "news-subscribe",
|
||||
label: i18n("News"),
|
||||
} : null
|
||||
].filter(Boolean)
|
||||
|
||||
delegate: TabButton {
|
||||
required property var modelData
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Theme.inherit: true
|
||||
Item { Layout.fillWidth: true }
|
||||
Kirigami.Icon {
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
source: cfg.ownIconsUI ? svg(modelData.icon) : modelData.fallback
|
||||
color: (modelData.id === "news" && activeNewsItems) ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.colorSet
|
||||
isMask: cfg.ownIconsUI
|
||||
smooth: true
|
||||
}
|
||||
Label { text: modelData.label; visible: cfg.tabBarTexts }
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: sts.busy
|
||||
sourceComponent: ProgressBar { from: 0; to: 100; indeterminate: true }
|
||||
}
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing * 2
|
||||
|
||||
id: searchField
|
||||
clearButtonShown: true
|
||||
visible: searchFieldOpen && sts.count
|
||||
placeholderText: i18n("Filter by package name")
|
||||
onTextChanged: modelList.setFilterFixedString(text)
|
||||
}
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2
|
||||
text: "<b>" + i18n("Check out release notes")+" "+currVersion+"</b>"
|
||||
type: Kirigami.MessageType.Positive
|
||||
visible: !searchFieldOpen && isOnline &&
|
||||
plasmoid.configuration.version.localeCompare(currVersion, undefined, { numeric: true, sensitivity: 'base' }) < 0
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
tooltip: i18n("Select...")
|
||||
icon.name: "application-menu"
|
||||
expandible: true
|
||||
|
||||
Kirigami.Action {
|
||||
text: "GitHub"
|
||||
icon.name: "internet-web-browser-symbolic"
|
||||
onTriggered: Qt.openUrlExternally("https://github.com/exequtic/apdatifier/releases")
|
||||
}
|
||||
Kirigami.Action {
|
||||
text: i18n("Dismiss")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: plasmoid.configuration.version = currVersion
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2
|
||||
text: "<b>" + i18n("No internet connection") + "</b>"
|
||||
type: Kirigami.MessageType.Error
|
||||
visible: !isOnline
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
currentIndex: 0
|
||||
Repeater {
|
||||
model: [
|
||||
{ component: "updates" },
|
||||
cfg.feedsEnabled ? { component: "news" } : null
|
||||
].filter(Boolean)
|
||||
|
||||
delegate: Loader {
|
||||
required property var modelData
|
||||
sourceComponent: modelData.component === "updates" ? updatesComp : newsComp
|
||||
}
|
||||
}
|
||||
|
||||
Component { id: updatesComp; View.Updates {} }
|
||||
Component { id: newsComp; View.News { id: newsPage } }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
z: 9998
|
||||
anchors.fill: parent
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
visible: errorLoader.active
|
||||
}
|
||||
Loader {
|
||||
z: 9999
|
||||
id: errorLoader
|
||||
anchors.centerIn: parent
|
||||
active: !sts.busy && sts.error
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Kirigami.Icon {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
source: cfg.ownIconsUI ? svg("status_error") : "error"
|
||||
isMask: cfg.ownIconsUI
|
||||
}
|
||||
Kirigami.Heading {
|
||||
text: i18np("%1 error occurred", "%1 errors occurred", sts.errors.length)
|
||||
type: Kirigami.Heading.Primary
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
|
||||
|
||||
TextArea {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
readOnly: true
|
||||
wrapMode: Text.Wrap
|
||||
font.family: "Monospace"
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
text: sts.errors.map(err => err?.type ? `${err.type}: ${err.message}` : `${err.message}`).join("\n\n")
|
||||
color: Kirigami.Theme.textColor
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
icon.name: "checkmark"
|
||||
text: "OK"
|
||||
onClicked: {
|
||||
sts.errors = []
|
||||
JS.setStatusBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KSortFilterProxyModel {
|
||||
id: modelList
|
||||
sourceModel: listModel
|
||||
filterRoleName: "name"
|
||||
filterRowCallback: (sourceRow, sourceParent) => {
|
||||
return sourceModel.data(sourceModel.index(sourceRow, 0, sourceParent), filterRole).includes(searchField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import "../components" as QQC
|
||||
import "../../tools/tools.js" as JS
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
property bool wasExpanded: false
|
||||
|
||||
readonly property bool counterSide: !inTray && horizontal && plasmoid.configuration.counterMode === "side"
|
||||
readonly property bool counterEnabled: plasmoid.configuration.counterMode !== "disabled"
|
||||
readonly property bool counterCenter: plasmoid.configuration.counterBadgePosition === "center"
|
||||
readonly property bool pauseBadgeEnabled: plasmoid.configuration.pauseBadgePosition !== "disabled"
|
||||
readonly property bool updatedBadgeEnabled: plasmoid.configuration.updatedBadgePosition !== "disabled"
|
||||
|
||||
readonly property bool onDesktop: plasmoid.location === PlasmaCore.Types.Floating
|
||||
readonly property bool pulseActive: (plasmoid.configuration.busyIndicator === "pulse") && sts.busy && !onDesktop
|
||||
|
||||
function darkColor(color) {
|
||||
return Kirigami.ColorUtils.brightnessForColor(color) === Kirigami.ColorUtils.Dark
|
||||
}
|
||||
readonly property bool isDarkText: darkColor(Kirigami.Theme.textColor)
|
||||
readonly property color lightText: isDarkText ? Kirigami.Theme.backgroundColor : Kirigami.Theme.textColor
|
||||
readonly property color darkText: isDarkText ? Kirigami.Theme.textColor : Kirigami.Theme.backgroundColor
|
||||
|
||||
readonly property string errorIcon: cfg.ownIconsUI ? "status_error" : "error"
|
||||
readonly property string updatedIcon: cfg.ownIconsUI ? "status_updated" : "checkmark"
|
||||
readonly property string pausedIcon: cfg.ownIconsUI ? "toolbar_pause" : "media-playback-paused"
|
||||
|
||||
readonly property real panelIconSize: horizontal ? parent.height : parent.width
|
||||
readonly property real iconSize: inTray ? Math.min(parent.width, parent.height) : panelIconSize
|
||||
readonly property real counterPixelSize: Math.max(iconSize / 4, iconSize * (cfg.counterFontSize / 10))
|
||||
|
||||
implicitHeight: iconSize
|
||||
implicitWidth: viewLoader.item ? viewLoader.item.implicitWidth : iconSize
|
||||
|
||||
Layout.preferredHeight: implicitHeight
|
||||
Layout.minimumHeight: implicitHeight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
Layout.minimumWidth: implicitWidth
|
||||
|
||||
hoverEnabled: true
|
||||
acceptedButtons: cfg.rightAction ? Qt.AllButtons : Qt.LeftButton | Qt.MiddleButton
|
||||
|
||||
onEntered: sts.checkTimeTooltip = JS.getCheckTime()
|
||||
|
||||
onPressed: mouse => {
|
||||
wasExpanded = expanded
|
||||
if (!cfg.rightAction && mouse.button == Qt.RightButton) mouse.accepted = false
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button == Qt.LeftButton) expanded = !wasExpanded
|
||||
if (mouse.button == Qt.MiddleButton && cfg.middleAction) JS[cfg.middleAction]()
|
||||
if (mouse.button == Qt.RightButton && cfg.rightAction) JS[cfg.rightAction]()
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: (event) => {
|
||||
if (scrollTimer.running) return
|
||||
scrollTimer.start()
|
||||
var action = event.angleDelta.y > 0 ? cfg.scrollUpAction : cfg.scrollDownAction
|
||||
if (!action) return
|
||||
JS[action]()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: scrollTimer
|
||||
interval: 500
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: viewLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: counterSide ? iconWithCounterSide : iconWithCounterBadge
|
||||
}
|
||||
|
||||
Component {
|
||||
id: iconWithCounterSide
|
||||
|
||||
RowLayout {
|
||||
implicitHeight: mouseArea.iconSize
|
||||
layoutDirection: cfg.counterSidePosition === "left" ? Qt.RightToLeft : Qt.LeftToRight
|
||||
spacing: 0
|
||||
anchors.centerIn: parent
|
||||
|
||||
Item { Layout.preferredWidth: Kirigami.Units.smallSpacing + cfg.counterGapsOuter }
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: mouseArea.iconSize
|
||||
Layout.preferredWidth: mouseArea.iconSize
|
||||
|
||||
Kirigami.Icon {
|
||||
id: panelIcon
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
source: JS.setIcon(plasmoid.icon)
|
||||
active: mouseArea.containsMouse
|
||||
|
||||
layer.enabled: sts.error
|
||||
layer.effect: sts.error ? errorShadowEffect : null
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: badgesLayer
|
||||
active: sts.init
|
||||
}
|
||||
}
|
||||
|
||||
QQC.Pulse {
|
||||
target: panelIcon
|
||||
running: mouseArea.pulseActive
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: cfg.counterGapsInner
|
||||
visible: counterText.visible
|
||||
}
|
||||
|
||||
Label {
|
||||
id: counterText
|
||||
visible: mouseArea.counterEnabled && !sts.busy && sts.count
|
||||
font.family: plasmoid.configuration.counterFontFamily || Kirigami.Theme.defaultFont.family
|
||||
font.pixelSize: Math.round(mouseArea.counterPixelSize)
|
||||
font.bold: cfg.counterFontBold
|
||||
fontSizeMode: Text.FixedSize
|
||||
text: sts.count
|
||||
}
|
||||
|
||||
Item { Layout.preferredWidth: Kirigami.Units.smallSpacing + cfg.counterGapsOuter }
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: iconWithCounterBadge
|
||||
|
||||
Item {
|
||||
implicitWidth: mouseArea.iconSize
|
||||
implicitHeight: mouseArea.iconSize
|
||||
|
||||
Item {
|
||||
width: mouseArea.iconSize
|
||||
height: mouseArea.iconSize
|
||||
anchors.centerIn: parent
|
||||
|
||||
Kirigami.Icon {
|
||||
id: trayIcon
|
||||
anchors.fill: parent
|
||||
source: JS.setIcon(plasmoid.icon)
|
||||
active: mouseArea.containsMouse
|
||||
|
||||
layer.enabled: sts.error
|
||||
layer.effect: sts.error ? errorShadowEffect : null
|
||||
}
|
||||
|
||||
QQC.Pulse {
|
||||
target: trayIcon
|
||||
running: mouseArea.pulseActive
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: frame
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: counterFrame
|
||||
width: mouseArea.counterCenter ? frame.width : counter.implicitWidth + 2
|
||||
height: mouseArea.counterCenter ? frame.height : counter.implicitHeight
|
||||
radius: cfg.counterRadius
|
||||
opacity: cfg.counterOpacity / 10
|
||||
color: cfg.counterColor ? cfg.counterColor : Kirigami.Theme.backgroundColor
|
||||
visible: mouseArea.counterEnabled && !sts.busy && sts.count
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 0
|
||||
radius: 2
|
||||
samples: radius * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
|
||||
state: cfg.counterBadgePosition
|
||||
|
||||
states: [
|
||||
State { name: "center"; AnchorChanges { target: counterFrame; anchors.horizontalCenter: frame.horizontalCenter; anchors.verticalCenter: frame.verticalCenter } },
|
||||
State { name: "topLeft"; AnchorChanges { target: counterFrame; anchors.top: frame.top; anchors.left: frame.left } },
|
||||
State { name: "topRight"; AnchorChanges { target: counterFrame; anchors.top: frame.top; anchors.right: frame.right } },
|
||||
State { name: "bottomLeft"; AnchorChanges { target: counterFrame; anchors.bottom: frame.bottom; anchors.left: frame.left } },
|
||||
State { name: "bottomRight"; AnchorChanges { target: counterFrame; anchors.bottom: frame.bottom; anchors.right: frame.right } }
|
||||
]
|
||||
|
||||
anchors {
|
||||
topMargin: state.includes("top") ? cfg.counterOffsetX : 0
|
||||
leftMargin: state.includes("Left") ? cfg.counterOffsetY : 0
|
||||
rightMargin: state.includes("Right") ? cfg.counterOffsetY : 0
|
||||
bottomMargin: state.includes("bottom") ? cfg.counterOffsetX : 0
|
||||
}
|
||||
|
||||
transitions: Transition { AnchorAnimation { duration: 120 } }
|
||||
}
|
||||
|
||||
Label {
|
||||
id: counter
|
||||
anchors.centerIn: counterFrame
|
||||
text: sts.count
|
||||
font.family: plasmoid.configuration.counterFontFamily || Kirigami.Theme.defaultFont.family
|
||||
font.pixelSize: Math.round(mouseArea.counterPixelSize)
|
||||
font.bold: cfg.counterFontBold
|
||||
color: cfg.counterColor ? (mouseArea.darkColor(counterFrame.color) ? mouseArea.lightText : mouseArea.darkText) : Kirigami.Theme.textColor
|
||||
visible: counterFrame.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: badgesLayer
|
||||
active: sts.init
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: badgesLayer
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
QQC.Badge {
|
||||
iconName: updatedIcon
|
||||
position: cfg.updatedBadgePosition
|
||||
iconColor: Kirigami.Theme.positiveTextColor
|
||||
visible: !sts.busy && !sts.count && updatedBadgeEnabled
|
||||
}
|
||||
QQC.Badge {
|
||||
iconName: pausedIcon
|
||||
position: cfg.pauseBadgePosition
|
||||
iconColor: Kirigami.Theme.neutralTextColor
|
||||
visible: sts.paused && pauseBadgeEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: errorShadowEffect
|
||||
DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 0
|
||||
radius: 6
|
||||
samples: 16
|
||||
color: "red"
|
||||
spread: 0.35
|
||||
// transparentBorder: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
|
||||
import org.kde.plasma.components
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
ListView {
|
||||
model: newsModel
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
topMargin: spacing
|
||||
rightMargin: spacing
|
||||
leftMargin: spacing
|
||||
bottomMargin: spacing
|
||||
anchors.fill: parent
|
||||
|
||||
ScrollBar.vertical: Scroll {
|
||||
policy: newsModel.count > 0 ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
|
||||
}
|
||||
|
||||
delegate: Kirigami.AbstractCard {
|
||||
contentItem: Item {
|
||||
implicitWidth: delegateLayout.implicitWidth
|
||||
implicitHeight: delegateLayout.implicitHeight
|
||||
GridLayout {
|
||||
id: delegateLayout
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
rowSpacing: Kirigami.Units.largeSpacing
|
||||
columnSpacing: Kirigami.Units.largeSpacing
|
||||
columns: width > Kirigami.Units.gridUnit * 20 ? 4 : 2
|
||||
opacity: model.removed ? 0.5 : 1.0
|
||||
Kirigami.Icon {
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumHeight: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredWidth: height
|
||||
source: model.title.includes("Arch") ? "apdatifier-plasmoid" :
|
||||
model.title.includes("Plasma") ? "note-symbolic" :
|
||||
model.title.includes("Apps") ? "applications-all-symbolic" :
|
||||
model.title.includes("Community") ? "start-here-kde-plasma-symbolic"
|
||||
: "news-subscribe"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
text: model.title
|
||||
font.bold: true
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
text: Qt.formatDateTime(new Date(model.timestamp * 1000), Qt.DefaultLocaleShortDate)
|
||||
opacity: 0.6
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
text: model.article
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Controls.Button {
|
||||
ToolTip { id: tip; text: i18n("Read article") }
|
||||
icon.name: "internet-web-browser-symbolic"
|
||||
onClicked: {
|
||||
tip.hide()
|
||||
Qt.openUrlExternally(model.link)
|
||||
JS.removeNewsItem(index)
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: !model.removed
|
||||
ToolTip { text: i18n("Mark as read") }
|
||||
icon.name: model.removed ? "backup" : "delete"
|
||||
onClicked: JS.removeNewsItem(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: newsModel.count === 0
|
||||
sourceComponent: Kirigami.PlaceholderMessage {
|
||||
icon.name: "news-subscribe"
|
||||
text: i18n("No unread news")
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition { animations: [ NumberAnimation { properties: "opacity,scale"; from: 0; to: 1; duration: 380; easing.type: Easing.OutBack; easing.overshoot: 1.12 } ] }
|
||||
addDisplaced: Transition { animations: [
|
||||
NumberAnimation { property: "y"; duration: 340; easing.type: Easing.OutCubic },
|
||||
NumberAnimation { properties: "opacity,scale"; to: 1; duration: 340; easing.type: Easing.OutBack; easing.overshoot: 1.08 }
|
||||
] }
|
||||
displaced: Transition { animations: [
|
||||
NumberAnimation { property: "y"; duration: 340; easing.type: Easing.OutBack; easing.overshoot: 1.05 },
|
||||
NumberAnimation { properties: "opacity,scale"; to: 1; duration: 260; easing.type: Easing.OutQuad }
|
||||
] }
|
||||
move: Transition { animations: [
|
||||
NumberAnimation { property: "y"; duration: 360; easing.type: Easing.OutBack; easing.overshoot: 1.06 },
|
||||
NumberAnimation { properties: "opacity,scale"; to: 1; duration: 260; easing.type: Easing.OutQuad }
|
||||
] }
|
||||
moveDisplaced: Transition { animations: [
|
||||
NumberAnimation { property: "y"; duration: 340; easing.type: Easing.OutBack; easing.overshoot: 1.04 },
|
||||
NumberAnimation { properties: "opacity,scale"; to: 1; duration: 260; easing.type: Easing.OutQuad }
|
||||
] }
|
||||
remove: Transition { animations: [
|
||||
NumberAnimation { property: "x"; to: width + 120; duration: 300; easing.type: Easing.InBack; easing.overshoot: 1.15 },
|
||||
NumberAnimation { property: "opacity"; to: 0; duration: 240; easing.type: Easing.InQuad },
|
||||
NumberAnimation { property: "scale"; to: 0.92; duration: 300; easing.type: Easing.InBack }
|
||||
] }
|
||||
removeDisplaced: Transition { animations: [
|
||||
NumberAnimation { property: "y"; duration: 340; easing.type: Easing.OutBack; easing.overshoot: 1.03 },
|
||||
NumberAnimation { properties: "opacity,scale"; to: 1; duration: 260; easing.type: Easing.OutQuad }
|
||||
] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.ksvg
|
||||
import org.kde.plasma.extras
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "../../tools/tools.js" as JS
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: view
|
||||
|
||||
property bool compactMode: listCompactMode
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
ScrollBar.vertical: Scroll {
|
||||
policy: !sts.count ? ScrollBar.AlwaysOff : ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
model: modelList
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: view.compactMode ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.smallSpacing
|
||||
|
||||
delegate: Item {
|
||||
width: listView.width
|
||||
height: delegateItem.height
|
||||
|
||||
Rectangle {
|
||||
id: delegateItem
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
rightMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
height: mainColumn.implicitHeight
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
property bool expanded: false
|
||||
property bool hovered: false
|
||||
property var pkg: []
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 220
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onHoveredChanged: delegateItem.hovered = hovered
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: delegateItem.expanded = !delegateItem.expanded
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
property color hColor: Kirigami.Theme.highlightColor
|
||||
property bool highlighted: (delegateItem.hovered && !listView.moving) || delegateItem.expanded
|
||||
|
||||
anchors.fill: parent
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
color: highlighted ? Qt.rgba(hColor.r, hColor.g, hColor.b, 0.4) : "transparent"
|
||||
border.width: highlighted ? 1 : 0
|
||||
border.color: Qt.darker(Qt.rgba(hColor.r, hColor.g, hColor.b, 0.8), 2)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: mainRow
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
id: pkgIcon
|
||||
Layout.preferredWidth: view.compactMode ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: view.compactMode ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.medium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
source: model.IC
|
||||
isMask: false
|
||||
}
|
||||
|
||||
// Extended
|
||||
RowLayout {
|
||||
visible: !view.compactMode
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.NM
|
||||
color: model.IM ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.textColor
|
||||
elide: Text.ElideRight
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: versionDiffLabel
|
||||
onLoaded: {
|
||||
item.oldVer = model.VO
|
||||
item.newVer = model.VN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
sourceComponent: infoChip
|
||||
active: cfg.extendedShowRepository !== false
|
||||
visible: active
|
||||
onLoaded: item.text = model.RE
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
sourceComponent: infoChip
|
||||
active: model.RN && cfg.extendedShowInstallReason !== false
|
||||
visible: active
|
||||
onLoaded: {
|
||||
item.text = model.RN
|
||||
item.reason = model.RN || ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compact
|
||||
RowLayout {
|
||||
visible: view.compactMode
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.NM
|
||||
color: model.IM ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.textColor
|
||||
elide: Text.ElideRight
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
sourceComponent: infoChip
|
||||
active: (cfg.compactInfo || "repository") === "repository"
|
||||
visible: active
|
||||
onLoaded: item.text = model.RE
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
sourceComponent: infoChip
|
||||
active: model.RN && (cfg.compactInfo || "repository") === "installReason"
|
||||
visible: active
|
||||
onLoaded: {
|
||||
item.text = model.RN
|
||||
item.reason = model.RN || ""
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: versionDiffLabel
|
||||
active: (cfg.compactInfo || "repository") === "versionDiff"
|
||||
visible: active
|
||||
onLoaded: {
|
||||
item.oldVer = model.VO
|
||||
item.newVer = model.VN
|
||||
item.hAlign = Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: detailsLoader
|
||||
Layout.fillWidth: true
|
||||
active: delegateItem.expanded
|
||||
visible: active
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: details
|
||||
width: parent.width
|
||||
columns: 2
|
||||
rowSpacing: Kirigami.Units.smallSpacing / 4
|
||||
columnSpacing: 40
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: delegateItem.pkg.length
|
||||
|
||||
delegate: Label {
|
||||
property bool header: !(index % 2)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font: Kirigami.Theme.smallFont
|
||||
opacity: header ? 0.6 : 1
|
||||
text: {
|
||||
if (header)
|
||||
return "<b>" + delegateItem.pkg[index] + ":</b>"
|
||||
if (delegateItem.pkg[index].indexOf("://") !== -1)
|
||||
return "<a href=\"" + delegateItem.pkg[index] + "\">" + delegateItem.pkg[index] + "</a>"
|
||||
return delegateItem.pkg[index]
|
||||
}
|
||||
textFormat: header ? Text.StyledText : delegateItem.pkg[index].indexOf("://") !== -1 ? Text.StyledText : Text.PlainText
|
||||
wrapMode: header ? Text.NoWrap : Text.WordWrap
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: upgradeButton
|
||||
// Layout.alignment: Qt.AlignHCenter
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
visible: (model.ID || model.CN) && cfg.terminal
|
||||
icon.name: "edit-download"
|
||||
text: i18n("Upgrade package")
|
||||
onClicked: JS.upgradePackage(model.NM, model.ID, model.CN)
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Kirigami.Units.smallSpacing * 2
|
||||
visible: upgradeButton.visible
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const F = [
|
||||
["TP", i18n("Package type")],
|
||||
["DE", i18n("Description")],
|
||||
["AU", i18n("Author")],
|
||||
["RE", i18n("Repository")],
|
||||
["LN", "URL"],
|
||||
["ID", i18n("App ID")],
|
||||
["BR", i18n("Branch")],
|
||||
["CM", i18n("Commit")],
|
||||
["RT", i18n("Runtime")],
|
||||
["GR", i18n("Groups")],
|
||||
["PR", i18n("Provides")],
|
||||
["DP", i18n("Depends on")],
|
||||
["RQ", i18n("Required by")],
|
||||
["CF", i18n("Conflicts with")],
|
||||
["RP", i18n("Replaces")],
|
||||
["IS", i18n("Installed size")],
|
||||
["DS", i18n("Download size")],
|
||||
["DT", i18n("Install date")],
|
||||
["RN", i18n("Install reason")]
|
||||
]
|
||||
|
||||
const details = []
|
||||
for (const [k, l] of F) {
|
||||
if (!model[k]) continue
|
||||
|
||||
let value = model[k]
|
||||
|
||||
if (k === "RN") {
|
||||
value = model.RN === "explicit" ? i18n("Explicitly installed")
|
||||
: model.RN === "dependency" ? i18n("Installed as a dependency")
|
||||
: i18n("Orphaned dependency")
|
||||
}
|
||||
|
||||
details.push(l, value)
|
||||
}
|
||||
|
||||
delegateItem.pkg = details
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
|
||||
active: !sts.count
|
||||
sourceComponent: Kirigami.PlaceholderMessage {
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
icon.name: "checkmark"
|
||||
text: i18n("System updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: infoChip
|
||||
|
||||
Rectangle {
|
||||
property alias text: label.text
|
||||
property string reason: ""
|
||||
readonly property color bgColor: {
|
||||
if (reason) {
|
||||
if (!cfg.colorizeInstallReason)
|
||||
return Kirigami.Theme.highlightColor
|
||||
else if (reason === "explicit")
|
||||
return Kirigami.Theme.positiveBackgroundColor
|
||||
else if (reason === "dependency")
|
||||
return Kirigami.Theme.neutralBackgroundColor
|
||||
else
|
||||
return Kirigami.Theme.negativeBackgroundColor
|
||||
}
|
||||
|
||||
return Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
radius: 10
|
||||
color: Qt.rgba(bgColor.r, bgColor.g, bgColor.b, 0.3)
|
||||
implicitWidth: label.contentWidth + 16
|
||||
implicitHeight: label.contentHeight
|
||||
|
||||
border.width: 1
|
||||
border.color: Qt.lighter(Qt.rgba(bgColor.r, bgColor.g, bgColor.b, 0.5), 1.2)
|
||||
|
||||
Label {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
font: Kirigami.Theme.smallFont
|
||||
color: Qt.lighter(bgColor, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: versionDiffLabel
|
||||
|
||||
Label {
|
||||
property string oldVer: ""
|
||||
property string newVer: ""
|
||||
property int hAlign: Text.AlignLeft
|
||||
|
||||
horizontalAlignment: hAlign
|
||||
text: cfg.highlightVersionDiff ? versionDiff(oldVer, newVer) : `${oldVer} → ${newVer}`
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: Text.StyledText
|
||||
elide: Text.ElideRight
|
||||
opacity: 0.7
|
||||
|
||||
function versionDiff(oldVer, newVer) {
|
||||
if (oldVer === newVer) return oldVer
|
||||
|
||||
let i = 0
|
||||
|
||||
while (i < oldVer.length &&
|
||||
i < newVer.length &&
|
||||
oldVer[i] === newVer[i]) {
|
||||
++i
|
||||
}
|
||||
|
||||
const prefix = oldVer.slice(0, i)
|
||||
const oldDiff = oldVer.slice(i)
|
||||
const newDiff = newVer.slice(i)
|
||||
|
||||
return `${prefix}<font color="${Kirigami.Theme.negativeTextColor}">${oldDiff}</font> → ${prefix}<font color="${Kirigami.Theme.positiveTextColor}">${newDiff}</font>`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"KPlugin": {
|
||||
"Authors": [
|
||||
{
|
||||
"Name": "Evgeny Kazantsev",
|
||||
"Email": "exequtic@gmail.com"
|
||||
}
|
||||
],
|
||||
"Category": "System Information",
|
||||
"Description": "Arch Update Notifier",
|
||||
"Icon": "apdatifier-plasmoid",
|
||||
"Id": "com.github.exequtic.apdatifier",
|
||||
"Name": "Apdatifier",
|
||||
"EnabledByDefault": false,
|
||||
"Version": "2.9.9",
|
||||
"License": "MIT",
|
||||
"BugReportUrl": "https://github.com/exequtic/apdatifier/issues",
|
||||
"Website": "https://github.com/exequtic/apdatifier"
|
||||
},
|
||||
"KPackageStructure": "Plasma/Applet",
|
||||
"X-Plasma-API-Minimum-Version": "6.0",
|
||||
"X-Plasma-NotificationArea": "true",
|
||||
"X-Plasma-NotificationAreaCategory": "SystemServices"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
> Version 7 of Zren's i18n scripts.
|
||||
|
||||
## New Translations
|
||||
|
||||
Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/exequtic/apdatifier/issues/new), name the file with the extension `.txt`, attach the txt file to the issue (drag and drop).
|
||||
|
||||
Or if you know how to make a pull request:
|
||||
|
||||
Copy the [`template.pot`](template.pot) file to [`./po`](po) directory and name it your locale's code (Eg: `en`/`de`/`fr`) with the extension `.po`. Then fill out all the `msgstr ""`.
|
||||
|
||||
## Scripts
|
||||
|
||||
* `sh ./merge` will parse the `i18n()` calls in the `*.qml` files and write it to the `template.pot` file. Then it will merge any changes into the `*.po` language files.
|
||||
* `sh ./build` will convert the `*.po` files to it's binary `*.mo` version and move it to `contents/locale/...`
|
||||
|
||||
## Links
|
||||
|
||||
* https://zren.github.io/kde/docs/widget/#translations-i18n
|
||||
* https://github.com/Zren/plasma-applet-lib/tree/master/package/translate
|
||||
|
||||
## Status
|
||||
| Locale | Lines | % Done|
|
||||
|----------|---------|-------|
|
||||
| Template | 330 | |
|
||||
| de | 261/330 | 79% |
|
||||
| es | 261/330 | 79% |
|
||||
| fr | 316/330 | 95% |
|
||||
| hu_HU | 306/330 | 92% |
|
||||
| ko | 261/330 | 79% |
|
||||
| nl | 261/330 | 79% |
|
||||
| pl | 261/330 | 79% |
|
||||
| pt_BR | 299/330 | 90% |
|
||||
| ru | 330/330 | 100% |
|
||||
| tr | 261/330 | 79% |
|
||||
| uk | 261/330 | 79% |
|
||||
| zh_CN | 294/330 | 89% |
|
||||
| zh_HK | 294/330 | 89% |
|
||||
| zh_TW | 294/330 | 89% |
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Version: 6
|
||||
|
||||
# This script will convert the *.po files to *.mo files, rebuilding the package/contents/locale folder.
|
||||
# Feature discussion: https://phabricator.kde.org/D5209
|
||||
# Eg: contents/locale/fr_CA/LC_MESSAGES/plasma_applet_org.kde.plasma.eventcalendar.mo
|
||||
|
||||
DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd`
|
||||
plasmoidName=com.github.exequtic.apdatifier
|
||||
widgetName="${plasmoidName##*.}" # Strip namespace
|
||||
website=https://github.com/exequtic/apdatifier
|
||||
bugAddress="$website"
|
||||
packageRoot=".." # Root of translatable sources
|
||||
projectName="plasma_applet_${plasmoidName}" # project name
|
||||
|
||||
#---
|
||||
if [ -z "$plasmoidName" ]; then
|
||||
echo "[build] Error: Couldn't read plasmoidName."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -z "$(which msgfmt)" ]; then
|
||||
echo "[build] Error: msgfmt command not found. Need to install gettext"
|
||||
echo "[build] Running 'sudo apt install gettext'"
|
||||
sudo apt install gettext
|
||||
echo "[build] gettext installation should be finished. Going back to installing translations."
|
||||
fi
|
||||
|
||||
#---
|
||||
echo "[build] Compiling messages"
|
||||
|
||||
catalogs=`find ./po -name '*.po' | sort`
|
||||
for cat in $catalogs; do
|
||||
catLocale=`basename ${cat%.*}`
|
||||
msgfmt -o "${catLocale}.mo" "$cat"
|
||||
|
||||
installPath="$DIR/../contents/locale/${catLocale}/LC_MESSAGES/${projectName}.mo"
|
||||
|
||||
echo "[build] Install ${cat} to ${installPath}"
|
||||
mkdir -p "$(dirname "$installPath")"
|
||||
mv "${catLocale}.mo" "${installPath}"
|
||||
done
|
||||
|
||||
echo "[build] Done building messages"
|
||||
|
||||
if [ "$1" = "--restartplasma" ]; then
|
||||
echo "[build] Restarting plasmashell"
|
||||
killall plasmashell
|
||||
kstart5 plasmashell
|
||||
echo "[build] Done restarting plasmashell"
|
||||
else
|
||||
echo "[build] (re)install the plasmoid and restart plasmashell to test."
|
||||
fi
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
# Version: 22
|
||||
|
||||
# https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems
|
||||
# https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems/Outside_KDE_repositories
|
||||
# https://invent.kde.org/sysadmin/l10n-scripty/-/blob/master/extract-messages.sh
|
||||
|
||||
DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd`
|
||||
plasmoidName=com.github.exequtic.apdatifier
|
||||
widgetName="${plasmoidName##*.}" # Strip namespace
|
||||
website=https://github.com/exequtic/apdatifier
|
||||
bugAddress="$website"
|
||||
packageRoot=".." # Root of translatable sources
|
||||
projectName="plasma_applet_${plasmoidName}" # project name
|
||||
|
||||
#---
|
||||
if [ -z "$plasmoidName" ]; then
|
||||
echo "[merge] Error: Couldn't read plasmoidName."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -z "$(which xgettext)" ]; then
|
||||
echo "[merge] Error: xgettext command not found. Need to install gettext"
|
||||
echo "[merge] Running 'sudo apt install gettext'"
|
||||
sudo apt install gettext
|
||||
echo "[merge] gettext installation should be finished. Going back to merging translations."
|
||||
fi
|
||||
|
||||
#---
|
||||
echo "[merge] Extracting messages"
|
||||
potArgs="--from-code=UTF-8 --width=200 --add-location=file"
|
||||
|
||||
# See Ki18n's extract-messages.sh for a full example:
|
||||
# https://invent.kde.org/sysadmin/l10n-scripty/-/blob/master/extract-messages.sh#L25
|
||||
# The -kN_ and -kaliasLocale keywords are mentioned in the Outside_KDE_repositories wiki.
|
||||
# We don't need -kN_ since we don't use intltool-extract but might as well keep it.
|
||||
# I have no idea what -kaliasLocale is used for. Googling aliasLocale found only listed kde1 code.
|
||||
# We don't need to parse -ki18nd since that'll extract messages from other domains.
|
||||
find "${packageRoot}" -name '*.cpp' -o -name '*.h' -o -name '*.c' -o -name '*.qml' -o -name '*.js' -o -name 'messages' | sort > "${DIR}/infiles.list"
|
||||
xgettext \
|
||||
${potArgs} \
|
||||
--files-from="${DIR}/infiles.list" \
|
||||
-C -kde \
|
||||
-ci18n \
|
||||
-ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 \
|
||||
-kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \
|
||||
-kxi18n:1 -kxi18nc:1c,2 -kxi18np:1,2 -kxi18ncp:1c,2,3 \
|
||||
-kkxi18n:1 -kkxi18nc:1c,2 -kkxi18np:1,2 -kkxi18ncp:1c,2,3 \
|
||||
-kI18N_NOOP:1 -kI18NC_NOOP:1c,2 \
|
||||
-kI18N_NOOP2:1c,2 -kI18N_NOOP2_NOSTRIP:1c,2 \
|
||||
-ktr2i18n:1 -ktr2xi18n:1 \
|
||||
-kN_:1 \
|
||||
-kaliasLocale \
|
||||
--package-name="${widgetName}" \
|
||||
--msgid-bugs-address="${bugAddress}" \
|
||||
-D "${packageRoot}" \
|
||||
-D "${DIR}" \
|
||||
-o "template.pot.new" \
|
||||
|| \
|
||||
{ echo "[merge] error while calling xgettext. aborting."; exit 1; }
|
||||
|
||||
sed -i 's/"Content-Type: text\/plain; charset=CHARSET\\n"/"Content-Type: text\/plain; charset=UTF-8\\n"/' "template.pot.new"
|
||||
sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in LANGUAGE"'/' "template.pot.new"
|
||||
sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "template.pot.new"
|
||||
|
||||
if [ -f "template.pot" ]; then
|
||||
newPotDate=`grep "POT-Creation-Date:" template.pot.new | sed 's/.\{3\}$//'`
|
||||
oldPotDate=`grep "POT-Creation-Date:" template.pot | sed 's/.\{3\}$//'`
|
||||
sed -i 's/'"${newPotDate}"'/'"${oldPotDate}"'/' "template.pot.new"
|
||||
changes=`diff "template.pot" "template.pot.new"`
|
||||
if [ ! -z "$changes" ]; then
|
||||
# There's been changes
|
||||
sed -i 's/'"${oldPotDate}"'/'"${newPotDate}"'/' "template.pot.new"
|
||||
mv "template.pot.new" "template.pot"
|
||||
|
||||
addedKeys=`echo "$changes" | grep "> msgid" | cut -c 9- | sort`
|
||||
removedKeys=`echo "$changes" | grep "< msgid" | cut -c 9- | sort`
|
||||
echo ""
|
||||
echo "Added Keys:"
|
||||
echo "$addedKeys"
|
||||
echo ""
|
||||
echo "Removed Keys:"
|
||||
echo "$removedKeys"
|
||||
echo ""
|
||||
|
||||
else
|
||||
# No changes
|
||||
rm "template.pot.new"
|
||||
fi
|
||||
else
|
||||
# template.pot didn't already exist
|
||||
mv "template.pot.new" "template.pot"
|
||||
fi
|
||||
|
||||
potMessageCount=`expr $(grep -Pzo 'msgstr ""\n(\n|$)' "template.pot" | grep -c 'msgstr ""')`
|
||||
echo "| Locale | Lines | % Done|" > "./Status.md"
|
||||
echo "|----------|---------|-------|" >> "./Status.md"
|
||||
entryFormat="| %-8s | %7s | %5s |"
|
||||
templateLine=`perl -e "printf(\"$entryFormat\", \"Template\", \"${potMessageCount}\", \"\")"`
|
||||
echo "$templateLine" >> "./Status.md"
|
||||
|
||||
rm "${DIR}/infiles.list"
|
||||
echo "[merge] Done extracting messages"
|
||||
|
||||
#---
|
||||
echo "[merge] Merging messages"
|
||||
catalogs=`find ./po -name '*.po' | sort`
|
||||
for cat in $catalogs; do
|
||||
echo "[merge] $cat"
|
||||
catLocale=`basename ${cat%.*}`
|
||||
|
||||
widthArg=""
|
||||
catUsesGenerator=`grep "X-Generator:" "$cat"`
|
||||
if [ -z "$catUsesGenerator" ]; then
|
||||
widthArg="--width=400"
|
||||
fi
|
||||
|
||||
compendiumArg=""
|
||||
if [ ! -z "$COMPENDIUM_DIR" ]; then
|
||||
langCode=`basename "${cat%.*}"`
|
||||
compendiumPath=`realpath "$COMPENDIUM_DIR/compendium-${langCode}.po"`
|
||||
if [ -f "$compendiumPath" ]; then
|
||||
echo "compendiumPath=$compendiumPath"
|
||||
compendiumArg="--compendium=$compendiumPath"
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "$cat" "$cat.new"
|
||||
sed -i 's/"Content-Type: text\/plain; charset=CHARSET\\n"/"Content-Type: text\/plain; charset=UTF-8\\n"/' "$cat.new"
|
||||
|
||||
msgmerge \
|
||||
${widthArg} \
|
||||
--add-location=file \
|
||||
--no-fuzzy-matching \
|
||||
${compendiumArg} \
|
||||
-o "$cat.new" \
|
||||
"$cat.new" "${DIR}/template.pot"
|
||||
|
||||
sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new"
|
||||
sed -i 's/# Translation of '"${widgetName}"' in LANGUAGE/'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new"
|
||||
sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "$cat.new"
|
||||
|
||||
poEmptyMessageCount=`expr $(grep -Pzo 'msgstr ""\n(\n|$)' "$cat.new" | grep -c 'msgstr ""')`
|
||||
poMessagesDoneCount=`expr $potMessageCount - $poEmptyMessageCount`
|
||||
poCompletion=`perl -e "printf(\"%d\", $poMessagesDoneCount * 100 / $potMessageCount)"`
|
||||
poLine=`perl -e "printf(\"$entryFormat\", \"$catLocale\", \"${poMessagesDoneCount}/${potMessageCount}\", \"${poCompletion}%\")"`
|
||||
echo "$poLine" >> "./Status.md"
|
||||
|
||||
# mv "$cat" "$cat.old"
|
||||
mv "$cat.new" "$cat"
|
||||
done
|
||||
echo "[merge] Done merging messages"
|
||||
|
||||
#---
|
||||
# Populate ReadMe.md
|
||||
echo "[merge] Updating translate/ReadMe.md"
|
||||
sed -i -E 's`share\/plasma\/plasmoids\/(.+)\/translate`share/plasma/plasmoids/'"${plasmoidName}"'/translate`' ./ReadMe.md
|
||||
if [[ "$website" == *"github.com"* ]]; then
|
||||
sed -i -E 's`\[new issue\]\(https:\/\/github\.com\/(.+)\/(.+)\/issues\/new\)`[new issue]('"${website}"'/issues/new)`' ./ReadMe.md
|
||||
fi
|
||||
sed -i '/^|/ d' ./ReadMe.md # Remove status table from ReadMe
|
||||
cat ./Status.md >> ./ReadMe.md
|
||||
rm ./Status.md
|
||||
|
||||
echo "[merge] Done"
|
||||