diff --git a/CopyNvchad.bash b/CopyNvchad.bash
deleted file mode 100644
index 63bf6404..00000000
--- a/CopyNvchad.bash
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-rm -r nvchad/*
-cp -r ~/.config/nvim/lua/custom/* ./nvchad
diff --git a/Scripts/CreateTask.go b/Scripts/CreateTask.go
deleted file mode 100644
index 07c1c010..00000000
--- a/Scripts/CreateTask.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-)
-
-var TEMPLATE_PATH = "/Obsidian/NotesBackup/Templates/Project Template.md"
-var OBSIDEAN_PATH = "/Obsidian/NotesBackup/Projects/Life/"
-
-func main() {
- dirname, err := os.UserHomeDir()
- if err != nil {
- panic(err)
- }
-
- TEMPLATE_PATH = dirname + TEMPLATE_PATH
- OBSIDEAN_PATH = dirname + OBSIDEAN_PATH
-
- args := os.Args
-
- if len(args) < 2 {
- panic("Command incorrect, expected: `go run script.go taskName` at least")
- }
-
- name := args[1]
- description := ""
-
- for i := 2; i < len(args); i++ {
- description += args[i] + " "
- }
- description = description[:len(description) - 1]
-
- CreateTask(name, description)
-}
-
-func CreateTask(name string, description string) {
- templateContent, err := os.ReadFile(TEMPLATE_PATH)
- if err != nil {
- panic(err)
- }
-
- fileContent := fmt.Sprintf("%s\n%s", string(templateContent), description)
-
- err = os.WriteFile(OBSIDEAN_PATH + name + ".md", []byte(fileContent), 0644)
- if err != nil {
- panic(err)
- }
-}
diff --git a/Scripts/OpenProdApps.bash b/Scripts/OpenProdApps.bash
deleted file mode 100755
index f397fac1..00000000
--- a/Scripts/OpenProdApps.bash
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-google-chrome-stable --app="https://linear.app/johntech/team/JOH/active" --class=WebApp-Linear7107 --user-data-dir=/home/johnc/.local/share/ice/profiles/Linear7107 &
-
-raindrop &
-
-google-chrome-stable --app="http://calendar.cron.com" --class=WebApp-Cron2444 --user-data-dir=/home/johnc/.local/share/ice/profiles/Cron2444 &
diff --git a/Scripts/ReplaceKdePanel.bash b/Scripts/ReplaceKdePanel.bash
deleted file mode 100644
index ac035590..00000000
--- a/Scripts/ReplaceKdePanel.bash
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-rm ~/.config/plasma-org.kde.plasma.desktop-appletsrc
-cp ../kde/plasma-org.kde.plasma.desktop-appletsrc ~/.config
diff --git a/Scripts/SlowOpenProdApps.bash b/Scripts/SlowOpenProdApps.bash
deleted file mode 100755
index f46f3ccd..00000000
--- a/Scripts/SlowOpenProdApps.bash
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-sleep 2
-
-google-chrome-stable --app="https://linear.app/johntech/team/JOH/active" --class=WebApp-Linear7107 --user-data-dir=/home/johnc/.local/share/ice/profiles/Linear7107 &
-
-raindrop &
-
-google-chrome-stable --app="http://calendar.cron.com" --class=WebApp-Cron2444 --user-data-dir=/home/johnc/.local/share/ice/profiles/Cron2444 &
diff --git a/Scripts/StartKrunner.bash b/Scripts/StartKrunner.bash
deleted file mode 100755
index a4c6d3d8..00000000
--- a/Scripts/StartKrunner.bash
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-krunner -d
diff --git a/Scripts/StartWork.bash b/Scripts/StartWork.bash
deleted file mode 100644
index af9db3c5..00000000
--- a/Scripts/StartWork.bash
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-find ~/Obsidian -maxdepth 5 -type d -not -path '*/node_modules*' -not -path '*/.*' 2>&1 | grep -v "Permission denied" > ~/.startwork.txt
-find ~/Code -maxdepth 3 -type d -not -path '*/node_modules*' -not -path '*/.*' 2>&1 | grep -v "Permission denied" >> ~/.startwork.txt
diff --git a/ags/assets/icons/ai-openai-symbolic.svg b/ags/assets/icons/ai-openai-symbolic.svg
new file mode 120000
index 00000000..c9ee0b32
--- /dev/null
+++ b/ags/assets/icons/ai-openai-symbolic.svg
@@ -0,0 +1 @@
+openai-symbolic.svg
\ No newline at end of file
diff --git a/ags/assets/icons/ai-oxygen-symbolic.svg b/ags/assets/icons/ai-oxygen-symbolic.svg
new file mode 100644
index 00000000..5e1cc193
--- /dev/null
+++ b/ags/assets/icons/ai-oxygen-symbolic.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/ags/assets/icons/ai-zukijourney.png b/ags/assets/icons/ai-zukijourney.png
new file mode 100644
index 00000000..917335e7
Binary files /dev/null and b/ags/assets/icons/ai-zukijourney.png differ
diff --git a/ags/assets/icons/arch-symbolic.svg b/ags/assets/icons/arch-symbolic.svg
new file mode 100644
index 00000000..7de9094e
--- /dev/null
+++ b/ags/assets/icons/arch-symbolic.svg
@@ -0,0 +1,113 @@
+
+
+
+
diff --git a/ags/assets/icons/cachyos-symbolic.svg b/ags/assets/icons/cachyos-symbolic.svg
new file mode 100644
index 00000000..4a9db19a
--- /dev/null
+++ b/ags/assets/icons/cachyos-symbolic.svg
@@ -0,0 +1,318 @@
+
+
diff --git a/ags/assets/icons/cloudflare-dns-symbolic.svg b/ags/assets/icons/cloudflare-dns-symbolic.svg
new file mode 100644
index 00000000..bd48d3c9
--- /dev/null
+++ b/ags/assets/icons/cloudflare-dns-symbolic.svg
@@ -0,0 +1,10 @@
+
diff --git a/ags/assets/icons/crosshair-symbolic.svg b/ags/assets/icons/crosshair-symbolic.svg
new file mode 100644
index 00000000..22967493
--- /dev/null
+++ b/ags/assets/icons/crosshair-symbolic.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/ags/assets/icons/debian-symbolic.svg b/ags/assets/icons/debian-symbolic.svg
new file mode 100644
index 00000000..252f8533
--- /dev/null
+++ b/ags/assets/icons/debian-symbolic.svg
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/ags/assets/icons/endeavouros-symbolic.svg b/ags/assets/icons/endeavouros-symbolic.svg
new file mode 100644
index 00000000..3be4cc40
--- /dev/null
+++ b/ags/assets/icons/endeavouros-symbolic.svg
@@ -0,0 +1,96 @@
+
+
diff --git a/ags/assets/icons/fedora-symbolic.svg b/ags/assets/icons/fedora-symbolic.svg
new file mode 100644
index 00000000..1a4e8c87
--- /dev/null
+++ b/ags/assets/icons/fedora-symbolic.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/ags/assets/icons/flatpak-symbolic.svg b/ags/assets/icons/flatpak-symbolic.svg
new file mode 100644
index 00000000..0c2bf628
--- /dev/null
+++ b/ags/assets/icons/flatpak-symbolic.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/ags/assets/icons/github-symbolic.svg b/ags/assets/icons/github-symbolic.svg
new file mode 100644
index 00000000..c1c9f19c
--- /dev/null
+++ b/ags/assets/icons/github-symbolic.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/ags/assets/icons/google-gemini-symbolic.svg b/ags/assets/icons/google-gemini-symbolic.svg
new file mode 100644
index 00000000..9de741be
--- /dev/null
+++ b/ags/assets/icons/google-gemini-symbolic.svg
@@ -0,0 +1,56 @@
+
+
+
+
diff --git a/ags/assets/icons/linux-symbolic.svg b/ags/assets/icons/linux-symbolic.svg
new file mode 100644
index 00000000..63f9c7e5
--- /dev/null
+++ b/ags/assets/icons/linux-symbolic.svg
@@ -0,0 +1,113 @@
+
+
+
+
diff --git a/ags/assets/icons/nixos-symbolic.svg b/ags/assets/icons/nixos-symbolic.svg
new file mode 100644
index 00000000..b697b0d1
--- /dev/null
+++ b/ags/assets/icons/nixos-symbolic.svg
@@ -0,0 +1,77 @@
+
+
diff --git a/ags/assets/icons/ollama-symbolic.svg b/ags/assets/icons/ollama-symbolic.svg
new file mode 100644
index 00000000..01454815
--- /dev/null
+++ b/ags/assets/icons/ollama-symbolic.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/ags/assets/icons/openai-symbolic.svg b/ags/assets/icons/openai-symbolic.svg
new file mode 100644
index 00000000..8ffc912a
--- /dev/null
+++ b/ags/assets/icons/openai-symbolic.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/ags/assets/icons/openrouter-symbolic.svg b/ags/assets/icons/openrouter-symbolic.svg
new file mode 100644
index 00000000..32aaaf50
--- /dev/null
+++ b/ags/assets/icons/openrouter-symbolic.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/ags/assets/icons/ubuntu-symbolic.svg b/ags/assets/icons/ubuntu-symbolic.svg
new file mode 100644
index 00000000..07746c9f
--- /dev/null
+++ b/ags/assets/icons/ubuntu-symbolic.svg
@@ -0,0 +1,85 @@
+
+
+
+
diff --git a/ags/assets/themes/sourceviewtheme-dark-monokai-license.txt b/ags/assets/themes/sourceviewtheme-dark-monokai-license.txt
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/ags/assets/themes/sourceviewtheme-dark-monokai-license.txt
@@ -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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/ags/assets/themes/sourceviewtheme-light.xml b/ags/assets/themes/sourceviewtheme-light.xml
new file mode 100644
index 00000000..bf39bfb5
--- /dev/null
+++ b/ags/assets/themes/sourceviewtheme-light.xml
@@ -0,0 +1,95 @@
+
+
+ end_4
+ <_description>Catppuccin port but very random
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ags/assets/themes/sourceviewtheme.xml b/ags/assets/themes/sourceviewtheme.xml
new file mode 100644
index 00000000..248d7afa
--- /dev/null
+++ b/ags/assets/themes/sourceviewtheme.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+ Leo Iannacone
+ <_description>Based on SublimeText Monokai Extended - Generated with tm2gtksw2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ags/config.js b/ags/config.js
new file mode 100644
index 00000000..73e57b57
--- /dev/null
+++ b/ags/config.js
@@ -0,0 +1,81 @@
+"use strict";
+// Import
+import Gdk from 'gi://Gdk';
+import GLib from 'gi://GLib';
+import App from 'resource:///com/github/Aylur/ags/app.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+// Stuff
+import userOptions from './modules/.configuration/user_options.js';
+import { firstRunWelcome, startBatteryWarningService } from './services/messages.js';
+import { startAutoDarkModeService } from './services/darkmode.js';
+// Widgets
+import { Bar, BarCornerTopleft, BarCornerTopright } from './modules/bar/main.js';
+import Cheatsheet from './modules/cheatsheet/main.js';
+// import DesktopBackground from './modules/desktopbackground/main.js';
+import Dock from './modules/dock/main.js';
+import Corner from './modules/screencorners/main.js';
+import Crosshair from './modules/crosshair/main.js';
+import Indicator from './modules/indicators/main.js';
+import Osk from './modules/onscreenkeyboard/main.js';
+import Overview from './modules/overview/main.js';
+import Session from './modules/session/main.js';
+import SideLeft from './modules/sideleft/main.js';
+import SideRight from './modules/sideright/main.js';
+import { COMPILED_STYLE_DIR } from './init.js';
+
+const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
+function forMonitors(widget) {
+ const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ return range(n, 0).map(widget).flat(1);
+}
+function forMonitorsAsync(widget) {
+ const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ return range(n, 0).forEach((n) => widget(n).catch(print))
+}
+
+// Start stuff
+handleStyles(true);
+startAutoDarkModeService().catch(print);
+firstRunWelcome().catch(print);
+startBatteryWarningService().catch(print)
+
+const Windows = () => [
+ // forMonitors(DesktopBackground),
+ forMonitors(Crosshair),
+ Overview(),
+ forMonitors(Indicator),
+ forMonitors(Cheatsheet),
+ SideLeft(),
+ SideRight(),
+ forMonitors(Osk),
+ forMonitors(Session),
+ ...(userOptions.dock.enabled ? [forMonitors(Dock)] : []),
+ ...(userOptions.appearance.fakeScreenRounding !== 0 ? [
+ forMonitors((id) => Corner(id, 'top left', true)),
+ forMonitors((id) => Corner(id, 'top right', true)),
+ forMonitors((id) => Corner(id, 'bottom left', true)),
+ forMonitors((id) => Corner(id, 'bottom right', true)),
+ ] : []),
+ ...(userOptions.appearance.barRoundCorners ? [
+ forMonitors(BarCornerTopleft),
+ forMonitors(BarCornerTopright),
+ ] : []),
+];
+
+const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully
+const closeWindowDelays = {}; // For animations
+for (let i = 0; i < (Gdk.Display.get_default()?.get_n_monitors() || 1); i++) {
+ closeWindowDelays[`osk${i}`] = CLOSE_ANIM_TIME;
+}
+
+App.config({
+ css: `${COMPILED_STYLE_DIR}/style.css`,
+ stackTraceOnError: true,
+ closeWindowDelay: closeWindowDelays,
+ windows: Windows().flat(1),
+});
+
+// Stuff that don't need to be toggled. And they're async so ugh...
+forMonitorsAsync(Bar);
+// Bar().catch(print); // Use this to debug the bar. Single monitor only.
+
diff --git a/ags/config_overviewOnly.js b/ags/config_overviewOnly.js
new file mode 100644
index 00000000..9c42aaa1
--- /dev/null
+++ b/ags/config_overviewOnly.js
@@ -0,0 +1,24 @@
+// Want only the overview from my config? this is what you're looking for!
+// Remember to install: `dart-sass`, `ags`, `material-symbols`, and `xorg-xrandr`
+// To launch this, run the following
+// ags -c ~/.config/ags/config_overviewOnly.js
+// To toggle the overview, run:
+// ags -t overview
+// You might wanna add that as a keybind (in hyprland.conf)
+// bind = Super, Tab, exec, ags -t overview
+
+// Import
+import App from 'resource:///com/github/Aylur/ags/app.js'
+// Widgets
+import Overview from './modules/overview/main.js';
+import { COMPILED_STYLE_DIR } from './init.js';
+
+handleStyles(true);
+
+App.config({
+ css: `${COMPILED_STYLE_DIR}/style.css`,
+ stackTraceOnError: true,
+ windows: [
+ Overview(),
+ ],
+});
diff --git a/ags/init.js b/ags/init.js
new file mode 100644
index 00000000..07c837b5
--- /dev/null
+++ b/ags/init.js
@@ -0,0 +1,32 @@
+import GLib from 'gi://GLib';
+import App from 'resource:///com/github/Aylur/ags/app.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+import { darkMode } from './modules/.miscutils/system.js';
+
+export const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
+
+globalThis['handleStyles'] = (resetMusic) => {
+ // Reset
+ Utils.exec(`mkdir -p "${GLib.get_user_state_dir()}/ags/scss"`);
+ if (resetMusic) {
+ Utils.exec(`bash -c 'echo "" > ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`); // reset music styles
+ Utils.exec(`bash -c 'echo "" > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss'`); // reset music styles
+ }
+ // Generate overrides
+ let lightdark = darkMode.value ? "dark" : "light";
+ Utils.writeFileSync(
+`@mixin symbolic-icon {
+ -gtk-icon-theme: '${userOptions.icons.symbolicIconTheme[lightdark]}';
+}
+`,
+ `${GLib.get_user_state_dir()}/ags/scss/_lib_mixins_overrides.scss`)
+ // Compile and apply
+ async function applyStyle() {
+ Utils.exec(`mkdir -p ${COMPILED_STYLE_DIR}`);
+ Utils.exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/main.scss" "${COMPILED_STYLE_DIR}/style.css"`);
+ App.resetCss();
+ App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
+ console.log('[LOG] Styles loaded')
+ }
+ applyStyle().catch(print);
+}
diff --git a/ags/modules/.commondata/hyprlanddata.js b/ags/modules/.commondata/hyprlanddata.js
new file mode 100644
index 00000000..7489365e
--- /dev/null
+++ b/ags/modules/.commondata/hyprlanddata.js
@@ -0,0 +1,27 @@
+const { Gdk } = imports.gi;
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+
+export let monitors;
+
+// Mixes with Gdk monitor size cuz it reports monitor size scaled
+async function updateStuff() {
+ monitors = JSON.parse(exec('hyprctl monitors -j'))
+ const display = Gdk.Display.get_default();
+ monitors.forEach((monitor, i) => {
+ const gdkMonitor = display.get_monitor(i);
+ monitor.realWidth = monitor.width;
+ monitor.realHeight = monitor.height;
+ if (userOptions.monitors.scaleMethod.toLowerCase == "gdk") {
+ monitor.width = gdkMonitor.get_geometry().width;
+ monitor.height = gdkMonitor.get_geometry().height;
+ }
+ else { // == "division"
+ monitor.width = Math.ceil(monitor.realWidth / monitor.scale);
+ monitor.height = Math.ceil(monitor.realHeight / monitor.scale);
+ }
+ });
+}
+
+updateStuff().catch(print);
+
diff --git a/ags/modules/.commondata/quotes.js b/ags/modules/.commondata/quotes.js
new file mode 100644
index 00000000..7cea9907
--- /dev/null
+++ b/ags/modules/.commondata/quotes.js
@@ -0,0 +1,14 @@
+export const quotes = [
+ {
+ quote: 'Nvidia, fuck you',
+ author: 'Linus Torvalds',
+ },
+ {
+ quote: 'reproducible system? cock and vagina?',
+ author: 'vaxry',
+ },
+ {
+ quote: "haha pointers hee hee i love pointe-\\\nProcess Vaxry exited with signal SIGSEGV",
+ author: 'vaxry',
+ }
+];
diff --git a/ags/modules/.commondata/weather.js b/ags/modules/.commondata/weather.js
new file mode 100644
index 00000000..57c34fb1
--- /dev/null
+++ b/ags/modules/.commondata/weather.js
@@ -0,0 +1,94 @@
+export const WWO_CODE = {
+ "113": "Sunny",
+ "116": "PartlyCloudy",
+ "119": "Cloudy",
+ "122": "VeryCloudy",
+ "143": "Fog",
+ "176": "LightShowers",
+ "179": "LightSleetShowers",
+ "182": "LightSleet",
+ "185": "LightSleet",
+ "200": "ThunderyShowers",
+ "227": "LightSnow",
+ "230": "HeavySnow",
+ "248": "Fog",
+ "260": "Fog",
+ "263": "LightShowers",
+ "266": "LightRain",
+ "281": "LightSleet",
+ "284": "LightSleet",
+ "293": "LightRain",
+ "296": "LightRain",
+ "299": "HeavyShowers",
+ "302": "HeavyRain",
+ "305": "HeavyShowers",
+ "308": "HeavyRain",
+ "311": "LightSleet",
+ "314": "LightSleet",
+ "317": "LightSleet",
+ "320": "LightSnow",
+ "323": "LightSnowShowers",
+ "326": "LightSnowShowers",
+ "329": "HeavySnow",
+ "332": "HeavySnow",
+ "335": "HeavySnowShowers",
+ "338": "HeavySnow",
+ "350": "LightSleet",
+ "353": "LightShowers",
+ "356": "HeavyShowers",
+ "359": "HeavyRain",
+ "362": "LightSleetShowers",
+ "365": "LightSleetShowers",
+ "368": "LightSnowShowers",
+ "371": "HeavySnowShowers",
+ "374": "LightSleetShowers",
+ "377": "LightSleet",
+ "386": "ThunderyShowers",
+ "389": "ThunderyHeavyRain",
+ "392": "ThunderySnowShowers",
+ "395": "HeavySnowShowers",
+}
+
+export const WEATHER_SYMBOL = {
+ "Unknown": "air",
+ "Cloudy": "cloud",
+ "Fog": "foggy",
+ "HeavyRain": "rainy",
+ "HeavyShowers": "rainy",
+ "HeavySnow": "snowing",
+ "HeavySnowShowers": "snowing",
+ "LightRain": "rainy",
+ "LightShowers": "rainy",
+ "LightSleet": "rainy",
+ "LightSleetShowers": "rainy",
+ "LightSnow": "cloudy_snowing",
+ "LightSnowShowers": "cloudy_snowing",
+ "PartlyCloudy": "partly_cloudy_day",
+ "Sunny": "clear_day",
+ "ThunderyHeavyRain": "thunderstorm",
+ "ThunderyShowers": "thunderstorm",
+ "ThunderySnowShowers": "thunderstorm",
+ "VeryCloudy": "cloud",
+}
+
+export const NIGHT_WEATHER_SYMBOL = {
+ "Unknown": "air",
+ "Cloudy": "cloud",
+ "Fog": "foggy",
+ "HeavyRain": "rainy",
+ "HeavyShowers": "rainy",
+ "HeavySnow": "snowing",
+ "HeavySnowShowers": "snowing",
+ "LightRain": "rainy",
+ "LightShowers": "rainy",
+ "LightSleet": "rainy",
+ "LightSleetShowers": "rainy",
+ "LightSnow": "cloudy_snowing",
+ "LightSnowShowers": "cloudy_snowing",
+ "PartlyCloudy": "partly_cloudy_night",
+ "Sunny": "clear_night",
+ "ThunderyHeavyRain": "thunderstorm",
+ "ThunderyShowers": "thunderstorm",
+ "ThunderySnowShowers": "thunderstorm",
+ "VeryCloudy": "cloud",
+}
\ No newline at end of file
diff --git a/ags/modules/.commonwidgets/cairo_circularprogress.js b/ags/modules/.commonwidgets/cairo_circularprogress.js
new file mode 100644
index 00000000..83c18422
--- /dev/null
+++ b/ags/modules/.commonwidgets/cairo_circularprogress.js
@@ -0,0 +1,106 @@
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+
+// -- Styling --
+// min-height for diameter
+// min-width for trough stroke
+// padding for space between trough and progress
+// margin for space between widget and parent
+// background-color for trough color
+// color for progress color
+// -- Usage --
+// font size for progress value (0-100px) (hacky i know, but i want animations)
+export const AnimatedCircProg = ({
+ initFrom = 0,
+ initTo = 0,
+ initAnimTime = 2900,
+ initAnimPoints = 1,
+ extraSetup = () => { },
+ ...rest
+}) => Widget.DrawingArea({
+ ...rest,
+ css: `${initFrom != initTo ? 'font-size: ' + initFrom + 'px; transition: ' + initAnimTime + 'ms linear;' : ''}`,
+ setup: (area) => {
+ const styleContext = area.get_style_context();
+ const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
+ const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
+ const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
+ const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
+ area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
+ area.connect('draw', Lang.bind(area, (area, cr) => {
+ const styleContext = area.get_style_context();
+ const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
+ const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
+ const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
+ const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
+ area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
+
+ const progressValue = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100.0;
+
+ const bg_stroke = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const fg_stroke = bg_stroke - padding;
+ const radius = Math.min(width, height) / 2.0 - Math.max(bg_stroke, fg_stroke) / 2.0;
+ const center_x = width / 2.0 + marginLeft;
+ const center_y = height / 2.0 + marginTop;
+ const start_angle = -Math.PI / 2.0;
+ const end_angle = start_angle + (2 * Math.PI * progressValue);
+ const start_x = center_x + Math.cos(start_angle) * radius;
+ const start_y = center_y + Math.sin(start_angle) * radius;
+ const end_x = center_x + Math.cos(end_angle) * radius;
+ const end_y = center_y + Math.sin(end_angle) * radius;
+
+ // Draw background
+ const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
+ cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
+ cr.setLineWidth(bg_stroke);
+ cr.stroke();
+
+ if (progressValue == 0) return;
+
+ // Draw progress
+ const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
+ cr.arc(center_x, center_y, radius, start_angle, end_angle);
+ cr.setLineWidth(fg_stroke);
+ cr.stroke();
+
+ // Draw rounded ends for progress arcs
+ cr.setLineWidth(0);
+ cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
+ cr.fill();
+ cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
+ cr.fill();
+ }));
+
+ // Init animation
+ if (initFrom != initTo) {
+ area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
+ Utils.timeout(20, () => {
+ area.css = `font-size: ${initTo}px;`;
+ }, area)
+ const transitionDistance = initTo - initFrom;
+ const oneStep = initAnimTime / initAnimPoints;
+ area.css = `
+ font-size: ${initFrom}px;
+ transition: ${oneStep}ms linear;
+ `;
+ for (let i = 0; i < initAnimPoints; i++) {
+ Utils.timeout(Math.max(10, i * oneStep), () => {
+ if(!area) return;
+ area.css = `${initFrom != initTo ? 'font-size: ' + (initFrom + (transitionDistance / initAnimPoints * (i + 1))) + 'px;' : ''}`;
+ });
+ }
+ }
+ else area.css = 'font-size: 0px;';
+ extraSetup(area);
+ },
+})
\ No newline at end of file
diff --git a/ags/modules/.commonwidgets/cairo_navigationindicator.js b/ags/modules/.commonwidgets/cairo_navigationindicator.js
new file mode 100644
index 00000000..fd8d5990
--- /dev/null
+++ b/ags/modules/.commonwidgets/cairo_navigationindicator.js
@@ -0,0 +1,71 @@
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+// min-height/min-width for height/width
+// background-color/color for background/indicator color
+// padding for pad of indicator
+// font-size for selected index (0-based)
+export const NavigationIndicator = ({count, vertical, ...props}) => Widget.DrawingArea({
+ ...props,
+ setup: (area) => {
+ const styleContext = area.get_style_context();
+ const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
+ const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
+ area.set_size_request(width, height);
+
+ area.connect('draw', Lang.bind(area, (area, cr) => {
+ const styleContext = area.get_style_context();
+ const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
+ const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
+ // console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
+ area.set_size_request(width, height);
+ const paddingLeft = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const paddingRight = styleContext.get_padding(Gtk.StateFlags.NORMAL).right;
+ const paddingTop = styleContext.get_padding(Gtk.StateFlags.NORMAL).top;
+ const paddingBottom = styleContext.get_padding(Gtk.StateFlags.NORMAL).bottom;
+
+ const selectedCell = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ let cellWidth = width;
+ let cellHeight = height;
+ if (vertical) cellHeight /= count;
+ else cellWidth /= count;
+ const indicatorWidth = cellWidth - paddingLeft - paddingRight;
+ const indicatorHeight = cellHeight - paddingTop - paddingBottom;
+
+ const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ cr.setLineWidth(2);
+ // Background
+ cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
+ cr.rectangle(0, 0, width, height);
+ cr.fill();
+
+ // The indicator line
+ cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
+ if (vertical) {
+ cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
+ cr.stroke();
+ cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
+ cr.fill();
+ cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth / 2, Math.PI, 2 * Math.PI);
+ cr.fill();
+ cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorHeight - indicatorWidth / 2, indicatorWidth / 2, 0, Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
+ cr.stroke();
+ cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
+ cr.fill();
+ cr.arc(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ cr.arc(paddingLeft + cellWidth * selectedCell + indicatorWidth - indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ }))
+ },
+})
+
+
diff --git a/ags/modules/.commonwidgets/cairo_roundedcorner.js b/ags/modules/.commonwidgets/cairo_roundedcorner.js
new file mode 100644
index 00000000..90c1cf07
--- /dev/null
+++ b/ags/modules/.commonwidgets/cairo_roundedcorner.js
@@ -0,0 +1,50 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+
+export const RoundedCorner = (place, props) => Widget.DrawingArea({
+ ...props,
+ hpack: place.includes('left') ? 'start' : 'end',
+ vpack: place.includes('top') ? 'start' : 'end',
+ setup: (widget) => Utils.timeout(1, () => {
+ const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
+ const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+ widget.set_size_request(r, r);
+ widget.connect('draw', Lang.bind(widget, (widget, cr) => {
+ const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
+ const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+ // const borderColor = widget.get_style_context().get_property('color', Gtk.StateFlags.NORMAL);
+ // const borderWidth = widget.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
+ widget.set_size_request(r, r);
+
+ switch (place) {
+ case 'topleft':
+ cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
+ cr.lineTo(0, 0);
+ break;
+
+ case 'topright':
+ cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
+ cr.lineTo(r, 0);
+ break;
+
+ case 'bottomleft':
+ cr.arc(r, 0, r, Math.PI / 2, Math.PI);
+ cr.lineTo(0, r);
+ break;
+
+ case 'bottomright':
+ cr.arc(0, 0, r, 0, Math.PI / 2);
+ cr.lineTo(r, r);
+ break;
+ }
+
+ cr.closePath();
+ cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
+ cr.fill();
+ // cr.setLineWidth(borderWidth);
+ // cr.setSourceRGBA(borderColor.red, borderColor.green, borderColor.blue, borderColor.alpha);
+ // cr.stroke();
+ }));
+ }),
+});
\ No newline at end of file
diff --git a/ags/modules/.commonwidgets/cairo_slider.js b/ags/modules/.commonwidgets/cairo_slider.js
new file mode 100644
index 00000000..eff9085c
--- /dev/null
+++ b/ags/modules/.commonwidgets/cairo_slider.js
@@ -0,0 +1,49 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+
+export const AnimatedSlider = ({
+ className,
+ value,
+ ...rest
+}) => {
+ return Widget.DrawingArea({
+ className: `${className}`,
+ setup: (self) => {
+ self.connect('draw', Lang.bind(self, (self, cr) => {
+ const styleContext = self.get_style_context();
+ const allocatedWidth = self.get_allocated_width();
+ const allocatedHeight = self.get_allocated_height();
+ console.log(allocatedHeight, allocatedWidth)
+ const minWidth = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const radius = styleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
+ const bg = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const fg = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ const value = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100;
+ self.set_size_request(-1, minHeight);
+ const width = allocatedHeight;
+ const height = minHeight;
+
+ cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
+ cr.arc(width - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
+ cr.arc(width - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
+ cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
+ cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
+ cr.closePath();
+ cr.fill();
+
+ // const valueWidth = width * value;
+ // cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
+ // cr.arc(valueWidth - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
+ // cr.arc(valueWidth - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
+ // cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
+ // cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
+ // cr.closePath();
+ // cr.fill();
+
+ }));
+ },
+ ...rest,
+ })
+}
diff --git a/ags/modules/.commonwidgets/clickcloseregion.js b/ags/modules/.commonwidgets/clickcloseregion.js
new file mode 100644
index 00000000..e0aae79b
--- /dev/null
+++ b/ags/modules/.commonwidgets/clickcloseregion.js
@@ -0,0 +1,23 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { monitors } from '../.commondata/hyprlanddata.js';
+const { Box, EventBox } = Widget;
+
+export const clickCloseRegion = ({ name, multimonitor = true, monitor = 0, expand = true, fillMonitor = '' }) => {
+ return EventBox({
+ child: Box({
+ expand: expand,
+ css: `
+ min-width: ${fillMonitor.includes('h') ? monitors[monitor].width : 0}px;
+ min-height: ${fillMonitor.includes('v') ? monitors[monitor].height : 0}px;
+ `,
+ }),
+ setup: (self) => self.on('button-press-event', (self, event) => { // Any mouse button
+ if (multimonitor) closeWindowOnAllMonitors(name);
+ else App.closeWindow(name);
+ }),
+ })
+}
+
+export default clickCloseRegion;
+
diff --git a/ags/modules/.commonwidgets/configwidgets.js b/ags/modules/.commonwidgets/configwidgets.js
new file mode 100644
index 00000000..e98f5abf
--- /dev/null
+++ b/ags/modules/.commonwidgets/configwidgets.js
@@ -0,0 +1,219 @@
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { MaterialIcon } from './materialicon.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+const { Box, Button, Label, Revealer, SpinButton } = Widget;
+
+export const ConfigToggle = ({
+ icon, name, desc = '', initValue,
+ expandWidget = true,
+ onChange = () => { }, extraSetup = () => { },
+ ...rest
+}) => {
+ const enabled = Variable(initValue);
+ const toggleIcon = Label({
+ className: `icon-material txt-bold ${enabled.value ? '' : 'txt-poof'}`,
+ label: `${enabled.value ? 'check' : ''}`,
+ setup: (self) => self.hook(enabled, (self) => {
+ self.toggleClassName('switch-fg-toggling-false', false);
+ if (!enabled.value) {
+ self.label = '';
+ self.toggleClassName('txt-poof', true);
+ }
+ else Utils.timeout(1, () => {
+ toggleIcon.label = 'check';
+ toggleIcon.toggleClassName('txt-poof', false);
+ })
+ }),
+ })
+ const toggleButtonIndicator = Box({
+ className: `switch-fg ${enabled.value ? 'switch-fg-true' : ''}`,
+ vpack: 'center',
+ hpack: 'start',
+ homogeneous: true,
+ children: [toggleIcon,],
+ setup: (self) => self.hook(enabled, (self) => {
+ self.toggleClassName('switch-fg-true', enabled.value);
+ }),
+ });
+ const toggleButton = Box({
+ hpack: 'end',
+ className: `switch-bg ${enabled.value ? 'switch-bg-true' : ''}`,
+ homogeneous: true,
+ children: [toggleButtonIndicator],
+ setup: (self) => self.hook(enabled, (self) => {
+ self.toggleClassName('switch-bg-true', enabled.value);
+ }),
+ });
+ const widgetContent = Box({
+ tooltipText: desc,
+ className: 'txt spacing-h-5 configtoggle-box',
+ children: [
+ ...(icon !== undefined ? [MaterialIcon(icon, 'norm')] : []),
+ ...(name !== undefined ? [Label({
+ className: 'txt txt-small',
+ label: name,
+ })] : []),
+ ...(expandWidget ? [Box({ hexpand: true })] : []),
+ toggleButton,
+ ]
+ });
+ const interactionWrapper = Button({
+ attribute: {
+ enabled: enabled,
+ toggle: (newValue) => {
+ enabled.value = !enabled.value;
+ onChange(interactionWrapper, enabled.value);
+ }
+ },
+ child: widgetContent,
+ onClicked: (self) => self.attribute.toggle(self),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.connect('pressed', () => { // mouse down
+ toggleIcon.toggleClassName('txt-poof', true);
+ toggleIcon.toggleClassName('switch-fg-true', false);
+ if (!enabled.value) toggleIcon.toggleClassName('switch-fg-toggling-false', true);
+ });
+ extraSetup(self)
+ },
+ ...rest,
+ });
+ interactionWrapper.enabled = enabled;
+ return interactionWrapper;
+}
+
+export const ConfigSegmentedSelection = ({
+ icon, name, desc = '',
+ options = [{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
+ initIndex = 0,
+ onChange,
+ ...rest
+}) => {
+ let lastSelected = initIndex;
+ let value = options[initIndex].value;
+ const widget = Box({
+ tooltipText: desc,
+ className: 'segment-container',
+ // homogeneous: true,
+ children: options.map((option, id) => {
+ const selectedIcon = Revealer({
+ revealChild: id == initIndex,
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationSmall,
+ child: MaterialIcon('check', 'norm')
+ });
+ return Button({
+ setup: setupCursorHover,
+ className: `segment-btn ${id == initIndex ? 'segment-btn-enabled' : ''}`,
+ child: Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ selectedIcon,
+ Label({
+ label: option.name,
+ })
+ ]
+ }),
+ onClicked: (self) => {
+ value = option.value;
+ const kids = widget.get_children();
+ kids[lastSelected].toggleClassName('segment-btn-enabled', false);
+ kids[lastSelected].get_children()[0].get_children()[0].revealChild = false;
+ lastSelected = id;
+ self.toggleClassName('segment-btn-enabled', true);
+ selectedIcon.revealChild = true;
+ onChange(option.value, option.name);
+ }
+ })
+ }),
+ ...rest,
+ });
+ return widget;
+
+}
+
+export const ConfigMulipleSelection = ({
+ icon, name, desc = '',
+ optionsArr = [
+ [{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
+ [{ name: 'Option 3', value: 0 }, { name: 'Option 4', value: 1 }],
+ ],
+ initIndex = [0, 0],
+ onChange,
+ ...rest
+}) => {
+ let lastSelected = initIndex;
+ const widget = Box({
+ tooltipText: desc,
+ className: 'multipleselection-container spacing-v-3',
+ vertical: true,
+ children: optionsArr.map((options, grp) => Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: options.map((option, id) => Button({
+ setup: setupCursorHover,
+ className: `multipleselection-btn ${id == initIndex[1] && grp == initIndex[0] ? 'multipleselection-btn-enabled' : ''}`,
+ label: option.name,
+ onClicked: (self) => {
+ const kidsg = widget.get_children();
+ const kids = kidsg.flatMap(widget => widget.get_children());
+ kids.forEach(kid => {
+ kid.toggleClassName('multipleselection-btn-enabled', false);
+ });
+ lastSelected = id;
+ self.toggleClassName('multipleselection-btn-enabled', true);
+ onChange(option.value, option.name);
+ }
+ })),
+ })),
+ ...rest,
+ });
+ return widget;
+
+}
+
+export const ConfigGap = ({ vertical = true, size = 5, ...rest }) => Box({
+ className: `gap-${vertical ? 'v' : 'h'}-${size}`,
+ ...rest,
+})
+
+export const ConfigSpinButton = ({
+ icon, name, desc = '', initValue,
+ minValue = 0, maxValue = 100, step = 1,
+ expandWidget = true,
+ onChange = () => { }, extraSetup = () => { },
+ ...rest
+}) => {
+ const value = Variable(initValue);
+ const spinButton = SpinButton({
+ className: 'spinbutton',
+ range: [minValue, maxValue],
+ increments: [step, step],
+ onValueChanged: ({ value: newValue }) => {
+ value.value = newValue;
+ onChange(spinButton, newValue);
+ },
+ });
+ spinButton.value = value.value;
+ const widgetContent = Box({
+ tooltipText: desc,
+ className: 'txt spacing-h-5 configtoggle-box',
+ children: [
+ ...(icon !== undefined ? [MaterialIcon(icon, 'norm')] : []),
+ ...(name !== undefined ? [Label({
+ className: 'txt txt-small',
+ label: name,
+ })] : []),
+ ...(expandWidget ? [Box({ hexpand: true })] : []),
+ spinButton,
+ ],
+ setup: (self) => {
+ extraSetup(self);
+ },
+ ...rest,
+ });
+ return widgetContent;
+}
\ No newline at end of file
diff --git a/ags/modules/.commonwidgets/materialicon.js b/ags/modules/.commonwidgets/materialicon.js
new file mode 100644
index 00000000..029f0ede
--- /dev/null
+++ b/ags/modules/.commonwidgets/materialicon.js
@@ -0,0 +1,7 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const MaterialIcon = (icon, size, props = {}) => Widget.Label({
+ className: `icon-material txt-${size}`,
+ label: icon,
+ ...props,
+})
diff --git a/ags/modules/.commonwidgets/notification.js b/ags/modules/.commonwidgets/notification.js
new file mode 100644
index 00000000..57840ff7
--- /dev/null
+++ b/ags/modules/.commonwidgets/notification.js
@@ -0,0 +1,462 @@
+// This file is for the actual widget for each single notification
+const { GLib, Gdk, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget;
+import { MaterialIcon } from './materialicon.js';
+import { setupCursorHover } from "../.widgetutils/cursorhover.js";
+import { AnimatedCircProg } from "./cairo_circularprogress.js";
+
+function guessMessageType(summary) {
+ const str = summary.toLowerCase();
+ if (str.includes('reboot')) return 'restart_alt';
+ if (str.includes('recording')) return 'screen_record';
+ if (str.includes('battery') || summary.includes('power')) return 'power';
+ if (str.includes('screenshot')) return 'screenshot_monitor';
+ if (str.includes('welcome')) return 'waving_hand';
+ if (str.includes('time')) return 'scheduleb';
+ if (str.includes('installed')) return 'download';
+ if (str.includes('update')) return 'update';
+ if (str.startsWith('file')) return 'folder_copy';
+ return 'chat';
+}
+
+function exists(widget) {
+ return widget !== null;
+}
+
+const getFriendlyNotifTimeString = (timeObject) => {
+ const messageTime = GLib.DateTime.new_from_unix_local(timeObject);
+ const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60);
+ if (messageTime.compare(oneMinuteAgo) > 0)
+ return 'Now';
+ else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year())
+ return messageTime.format(userOptions.time.format);
+ else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1)
+ return 'Yesterday';
+ else
+ return messageTime.format(userOptions.time.dateFormat);
+}
+
+const NotificationIcon = (notifObject) => {
+ // { appEntry, appIcon, image }, urgency = 'normal'
+ if (notifObject.image) {
+ return Box({
+ valign: Gtk.Align.CENTER,
+ hexpand: false,
+ className: 'notif-icon',
+ css: `
+ background-image: url("${notifObject.image}");
+ background-size: auto 100%;
+ background-repeat: no-repeat;
+ background-position: center;
+ `,
+ });
+ }
+
+ let icon = 'NO_ICON';
+ if (Utils.lookUpIcon(notifObject.appIcon))
+ icon = notifObject.appIcon;
+ if (Utils.lookUpIcon(notifObject.appEntry))
+ icon = notifObject.appEntry;
+
+ return Box({
+ vpack: 'center',
+ hexpand: false,
+ className: `notif-icon notif-icon-material-${notifObject.urgency}`,
+ homogeneous: true,
+ children: [
+ (icon != 'NO_ICON' ?
+ Icon({
+ vpack: 'center',
+ icon: icon,
+ })
+ :
+ MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', {
+ hexpand: true,
+ })
+ )
+ ],
+ });
+};
+
+export default ({
+ notifObject,
+ isPopup = false,
+ props = {},
+} = {}) => {
+ const popupTimeout = notifObject.timeout || (notifObject.urgency == 'critical' ? 8000 : 3000);
+ const command = (isPopup ?
+ () => notifObject.dismiss() :
+ () => notifObject.close()
+ )
+ const destroyWithAnims = () => {
+ widget.sensitive = false;
+ notificationBox.setCss(middleClickClose);
+ Utils.timeout(userOptions.animations.durationSmall, () => {
+ if (wholeThing) wholeThing.revealChild = false;
+ }, wholeThing);
+ Utils.timeout(userOptions.animations.durationSmall * 2, () => {
+ command();
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ }, wholeThing);
+ }
+ const widget = EventBox({
+ onHover: (self) => {
+ self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
+ if (!wholeThing.attribute.hovered)
+ wholeThing.attribute.hovered = true;
+ },
+ onHoverLost: (self) => {
+ self.window.set_cursor(null);
+ if (wholeThing.attribute.hovered)
+ wholeThing.attribute.hovered = false;
+ if (isPopup) {
+ command();
+ }
+ },
+ onMiddleClick: (self) => {
+ destroyWithAnims();
+ },
+ setup: (self) => {
+ self.on("button-press-event", () => {
+ wholeThing.attribute.held = true;
+ notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, true);
+ Utils.timeout(800, () => {
+ if (wholeThing?.attribute.held) {
+ Utils.execAsync(['wl-copy', `${notifObject.body}`]).catch(print);
+ notifTextSummary.label = notifObject.summary + " (copied)";
+ Utils.timeout(3000, () => notifTextSummary.label = notifObject.summary)
+ }
+ })
+ }).on("button-release-event", () => {
+ wholeThing.attribute.held = false;
+ notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, false);
+ })
+ }
+ });
+ let wholeThing = Revealer({
+ attribute: {
+ 'close': undefined,
+ 'destroyWithAnims': destroyWithAnims,
+ 'dragging': false,
+ 'held': false,
+ 'hovered': false,
+ 'id': notifObject.id,
+ },
+ revealChild: false,
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({ // Box to make sure css-based spacing works
+ homogeneous: true,
+ }),
+ });
+
+ const display = Gdk.Display.get_default();
+ const notifTextPreview = Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: true,
+ child: Label({
+ xalign: 0,
+ className: `txt-smallie notif-body-${notifObject.urgency}`,
+ useMarkup: true,
+ xalign: 0,
+ justify: Gtk.Justification.LEFT,
+ maxWidthChars: 1,
+ truncate: 'end',
+ label: notifObject.body.split("\n")[0],
+ }),
+ });
+ const notifTextExpanded = Revealer({
+ transition: 'slide_up',
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: false,
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ xalign: 0,
+ className: `txt-smallie notif-body-${notifObject.urgency}`,
+ useMarkup: true,
+ xalign: 0,
+ justify: Gtk.Justification.LEFT,
+ maxWidthChars: 1,
+ wrap: true,
+ label: notifObject.body,
+ }),
+ Box({
+ className: 'notif-actions spacing-h-5',
+ children: [
+ Button({
+ hexpand: true,
+ className: `notif-action notif-action-${notifObject.urgency}`,
+ onClicked: () => destroyWithAnims(),
+ setup: setupCursorHover,
+ child: Label({
+ label: 'Close',
+ }),
+ }),
+ ...notifObject.actions.map(action => Widget.Button({
+ hexpand: true,
+ className: `notif-action notif-action-${notifObject.urgency}`,
+ onClicked: () => notifObject.invoke(action.id),
+ setup: setupCursorHover,
+ child: Label({
+ label: action.label,
+ }),
+ }))
+ ],
+ })
+ ]
+ }),
+ });
+ const notifIcon = Box({
+ vpack: 'start',
+ homogeneous: true,
+ children: [
+ Overlay({
+ child: NotificationIcon(notifObject),
+ overlays: isPopup ? [AnimatedCircProg({
+ className: `notif-circprog-${notifObject.urgency}`,
+ vpack: 'center', hpack: 'center',
+ initFrom: (isPopup ? 100 : 0),
+ initTo: 0,
+ initAnimTime: popupTimeout,
+ })] : [],
+ }),
+ ]
+ });
+
+ const notifTextSummary = Label({
+ xalign: 0,
+ className: 'txt-small txt-semibold titlefont',
+ justify: Gtk.Justification.LEFT,
+ hexpand: true,
+ maxWidthChars: 1,
+ truncate: 'end',
+ ellipsize: 3,
+ useMarkup: notifObject.summary.startsWith('<'),
+ label: notifObject.summary,
+ });
+ const initTimeString = getFriendlyNotifTimeString(notifObject.time);
+ const notifTextBody = Label({
+ vpack: 'center',
+ justification: 'right',
+ className: 'txt-smaller txt-semibold',
+ label: initTimeString,
+ setup: initTimeString == 'Now' ? (self) => {
+ let id = Utils.timeout(60000, () => {
+ self.label = getFriendlyNotifTimeString(notifObject.time);
+ id = null;
+ });
+ self.connect('destroy', () => { if (id) GLib.source_remove(id) });
+ } : () => { },
+ });
+ const notifText = Box({
+ valign: Gtk.Align.CENTER,
+ vertical: true,
+ hexpand: true,
+ children: [
+ Box({
+ children: [
+ notifTextSummary,
+ notifTextBody,
+ ]
+ }),
+ notifTextPreview,
+ notifTextExpanded,
+ ]
+ });
+ const notifExpandButton = Button({
+ vpack: 'start',
+ className: 'notif-expand-btn',
+ onClicked: (self) => {
+ if (notifTextPreview.revealChild) { // Expanding...
+ notifTextPreview.revealChild = false;
+ notifTextExpanded.revealChild = true;
+ self.child.label = 'expand_less';
+ expanded = true;
+ }
+ else {
+ notifTextPreview.revealChild = true;
+ notifTextExpanded.revealChild = false;
+ self.child.label = 'expand_more';
+ expanded = false;
+ }
+ },
+ child: MaterialIcon('expand_more', 'norm', {
+ vpack: 'center',
+ }),
+ setup: setupCursorHover,
+ });
+ const notificationContent = Box({
+ ...props,
+ className: `${isPopup ? 'popup-' : ''}notif-${notifObject.urgency} spacing-h-10`,
+ children: [
+ notifIcon,
+ Box({
+ className: 'spacing-h-5',
+ children: [
+ notifText,
+ notifExpandButton,
+ ]
+ })
+ ]
+ })
+
+ // Gesture stuff
+ const gesture = Gtk.GestureDrag.new(widget);
+ var initDirX = 0;
+ var initDirVertical = -1; // -1: unset, 0: horizontal, 1: vertical
+ var expanded = false;
+ // in px
+ const startMargin = 0;
+ const MOVE_THRESHOLD = 10;
+ const DRAG_CONFIRM_THRESHOLD = 100;
+ // in rem
+ const maxOffset = 10.227;
+ const endMargin = 20.455;
+ const disappearHeight = 6.818;
+ const leftAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: -${Number(maxOffset + endMargin)}rem;
+ margin-right: ${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const rightAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: ${Number(maxOffset + endMargin)}rem;
+ margin-right: -${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const middleClickClose = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.85, 0, 0.15, 1);
+ margin-left: ${Number(maxOffset + endMargin)}rem;
+ margin-right: -${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const notificationBox = Box({
+ attribute: {
+ 'leftAnim1': leftAnim1,
+ 'rightAnim1': rightAnim1,
+ 'middleClickClose': middleClickClose,
+ 'ready': false,
+ },
+ homogeneous: true,
+ children: [notificationContent],
+ setup: (self) => self
+ .hook(gesture, self => {
+ var offset_x = gesture.get_offset()[1];
+ var offset_y = gesture.get_offset()[2];
+ // Which dir?
+ if (initDirVertical == -1) {
+ if (Math.abs(offset_y) > MOVE_THRESHOLD)
+ initDirVertical = 1;
+ if (initDirX == 0 && Math.abs(offset_x) > MOVE_THRESHOLD) {
+ initDirVertical = 0;
+ initDirX = (offset_x > 0 ? 1 : -1);
+ }
+ }
+ // Horizontal drag
+ if (initDirVertical == 0 && offset_x > MOVE_THRESHOLD) {
+ if (initDirX < 0)
+ self.setCss(`margin-left: 0px; margin-right: 0px;`);
+ else
+ self.setCss(`
+ margin-left: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ margin-right: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ `);
+ }
+ else if (initDirVertical == 0 && offset_x < -MOVE_THRESHOLD) {
+ if (initDirX > 0)
+ self.setCss(`margin-left: 0px; margin-right: 0px;`);
+ else {
+ offset_x = Math.abs(offset_x);
+ self.setCss(`
+ margin-right: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ margin-left: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ `);
+ }
+ }
+ // Update dragging
+ wholeThing.attribute.dragging = Math.abs(offset_x) > MOVE_THRESHOLD;
+ if (Math.abs(offset_x) > MOVE_THRESHOLD ||
+ Math.abs(offset_y) > MOVE_THRESHOLD) wholeThing.attribute.held = false;
+ widget.window?.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
+ // Vertical drag
+ if (initDirVertical == 1 && offset_y > MOVE_THRESHOLD && !expanded) {
+ notifTextPreview.revealChild = false;
+ notifTextExpanded.revealChild = true;
+ expanded = true;
+ notifExpandButton.child.label = 'expand_less';
+ }
+ else if (initDirVertical == 1 && offset_y < -MOVE_THRESHOLD && expanded) {
+ notifTextPreview.revealChild = true;
+ notifTextExpanded.revealChild = false;
+ expanded = false;
+ notifExpandButton.child.label = 'expand_more';
+ }
+
+ }, 'drag-update')
+ .hook(gesture, self => {
+ if (!self.attribute.ready) {
+ wholeThing.revealChild = true;
+ self.attribute.ready = true;
+ return;
+ }
+ const offset_h = gesture.get_offset()[1];
+
+ if (Math.abs(offset_h) > DRAG_CONFIRM_THRESHOLD && offset_h * initDirX > 0) {
+ if (offset_h > 0) {
+ self.setCss(rightAnim1);
+ widget.sensitive = false;
+ }
+ else {
+ self.setCss(leftAnim1);
+ widget.sensitive = false;
+ }
+ Utils.timeout(userOptions.animations.durationSmall, () => {
+ if (wholeThing) wholeThing.revealChild = false;
+ }, wholeThing);
+ Utils.timeout(userOptions.animations.durationSmall * 2, () => {
+ command();
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ }, wholeThing);
+ }
+ else {
+ self.setCss(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: ${startMargin}px;
+ margin-right: ${startMargin}px;
+ margin-bottom: unset; margin-top: unset;
+ opacity: 1;`);
+ if (widget.window)
+ widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
+
+ wholeThing.attribute.dragging = false;
+ }
+ initDirX = 0;
+ initDirVertical = -1;
+ }, 'drag-end')
+ ,
+ })
+ widget.add(notificationBox);
+ wholeThing.child.children = [widget];
+ if (isPopup) Utils.timeout(popupTimeout, () => {
+ if (wholeThing) {
+ wholeThing.revealChild = false;
+ Utils.timeout(userOptions.animations.durationSmall, () => {
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ command();
+ }, wholeThing);
+ }
+ })
+ return wholeThing;
+}
diff --git a/ags/modules/.commonwidgets/statusicons.js b/ags/modules/.commonwidgets/statusicons.js
new file mode 100644
index 00000000..eacc444f
--- /dev/null
+++ b/ags/modules/.commonwidgets/statusicons.js
@@ -0,0 +1,306 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import { MaterialIcon } from './materialicon.js';
+import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
+import Network from 'resource:///com/github/Aylur/ags/service/network.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+import { languages } from './statusicons_languages.js';
+
+// A guessing func to try to support langs not listed in data/languages.js
+function isLanguageMatch(abbreviation, word) {
+ const lowerAbbreviation = abbreviation.toLowerCase();
+ const lowerWord = word.toLowerCase();
+ let j = 0;
+ for (let i = 0; i < lowerWord.length; i++) {
+ if (lowerWord[i] === lowerAbbreviation[j]) {
+ j++;
+ }
+ if (j === lowerAbbreviation.length) {
+ return true;
+ }
+ }
+ return false;
+}
+
+export const MicMuteIndicator = () => Widget.Revealer({
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: false,
+ setup: (self) => self.hook(Audio, (self) => {
+ self.revealChild = Audio.microphone?.stream?.isMuted;
+ }),
+ child: MaterialIcon('mic_off', 'norm'),
+});
+
+export const NotificationIndicator = (notifCenterName = 'sideright') => {
+ const widget = Widget.Revealer({
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: false,
+ setup: (self) => self
+ .hook(Notifications, (self, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+ self.revealChild = true;
+ }, 'notified')
+ .hook(App, (self, currentName, visible) => {
+ if (visible && currentName === notifCenterName) {
+ self.revealChild = false;
+ }
+ })
+ ,
+ child: Widget.Box({
+ children: [
+ MaterialIcon('notifications', 'norm'),
+ Widget.Label({
+ className: 'txt-small titlefont',
+ attribute: {
+ unreadCount: 0,
+ update: (self) => self.label = `${self.attribute.unreadCount}`,
+ },
+ setup: (self) => self
+ .hook(Notifications, (self, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+ self.attribute.unreadCount++;
+ self.attribute.update(self);
+ }, 'notified')
+ .hook(App, (self, currentName, visible) => {
+ if (visible && currentName === notifCenterName) {
+ self.attribute.unreadCount = 0;
+ self.attribute.update(self);
+ }
+ })
+ ,
+ })
+ ]
+ })
+ });
+ return widget;
+}
+
+export const BluetoothIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'false': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' }),
+ 'true': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' }),
+ },
+ setup: (self) => self
+ .hook(Bluetooth, stack => {
+ stack.shown = String(Bluetooth.enabled);
+ })
+ ,
+});
+
+const BluetoothDevices = () => Widget.Box({
+ className: 'spacing-h-5',
+ setup: self => self.hook(Bluetooth, self => {
+ self.children = Bluetooth.connected_devices.map((device) => {
+ return Widget.Box({
+ className: 'bar-bluetooth-device spacing-h-5',
+ vpack: 'center',
+ tooltipText: device.name,
+ children: [
+ Widget.Icon(`${device.iconName}-symbolic`),
+ ...(device.batteryPercentage ? [Widget.Label({
+ className: 'txt-smallie',
+ label: `${device.batteryPercentage}`,
+ setup: (self) => {
+ self.hook(device, (self) => {
+ self.label = `${device.batteryPercentage}`;
+ }, 'notify::batteryPercentage')
+ }
+ })] : []),
+ ]
+ });
+ });
+ self.visible = Bluetooth.connected_devices.length > 0;
+ }, 'notify::connected-devices'),
+})
+
+const NetworkWiredIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'fallback': SimpleNetworkIndicator(),
+ 'unknown': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
+ 'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
+ 'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'lan' }),
+ 'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
+ },
+ setup: (self) => self.hook(Network, stack => {
+ if (!Network.wired)
+ return;
+
+ const { internet } = Network.wired;
+ if (['connecting', 'connected'].includes(internet))
+ stack.shown = internet;
+ else if (Network.connectivity !== 'full')
+ stack.shown = 'disconnected';
+ else
+ stack.shown = 'fallback';
+ }),
+});
+
+const SimpleNetworkIndicator = () => Widget.Icon({
+ setup: (self) => self.hook(Network, self => {
+ const icon = Network[Network.primary || 'wifi']?.iconName;
+ self.icon = icon || '';
+ self.visible = icon;
+ }),
+});
+
+const NetworkWifiIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
+ 'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
+ 'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
+ '0': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' }),
+ '1': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' }),
+ '2': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' }),
+ '3': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' }),
+ '4': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' }),
+ },
+ setup: (self) => self.hook(Network, (stack) => {
+ if (!Network.wifi) {
+ return;
+ }
+ if (Network.wifi.internet == 'connected') {
+ stack.shown = String(Math.ceil(Network.wifi.strength / 25));
+ }
+ else if (["disconnected", "connecting"].includes(Network.wifi.internet)) {
+ stack.shown = Network.wifi.internet;
+ }
+ }),
+});
+
+export const NetworkIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'fallback': SimpleNetworkIndicator(),
+ 'wifi': NetworkWifiIndicator(),
+ 'wired': NetworkWiredIndicator(),
+ },
+ setup: (self) => self.hook(Network, stack => {
+ if (!Network.primary) {
+ stack.shown = 'wifi';
+ return;
+ }
+ const primary = Network.primary || 'fallback';
+ if (['wifi', 'wired'].includes(primary))
+ stack.shown = primary;
+ else
+ stack.shown = 'fallback';
+ }),
+});
+
+const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ var languageStackArray = [];
+
+ const updateCurrentKeyboards = () => {
+ var initLangs = [];
+ JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
+ .forEach(keyboard => {
+ initLangs.push(...keyboard.layout.split(',').map(lang => lang.trim()));
+ });
+ initLangs = [...new Set(initLangs)];
+ languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
+ const lang = languages.find(lang => lang.layout == initLangs[i]);
+ // if (!lang) return [
+ // initLangs[i],
+ // Widget.Label({ label: initLangs[i] })
+ // ];
+ // return [
+ // lang.layout,
+ // Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
+ // ];
+ // Object
+ if (!lang) return {
+ [initLangs[i]]: Widget.Label({ label: initLangs[i] })
+ };
+ return {
+ [lang.layout]: Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
+ };
+ });
+ };
+ updateCurrentKeyboards();
+ const widgetRevealer = Widget.Revealer({
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: languageStackArray.length > 1,
+ });
+ const widgetKids = {
+ ...languageStackArray.reduce((obj, lang) => {
+ return { ...obj, ...lang };
+ }, {}),
+ 'undef': Widget.Label({ label: '?' }),
+ }
+ const widgetContent = Widget.Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: widgetKids,
+ setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
+ if (!kbName) {
+ return;
+ }
+ var lang = languages.find(lang => layoutName.includes(lang.name));
+ if (lang) {
+ widgetContent.shown = lang.layout;
+ }
+ else { // Attempt to support langs not listed
+ lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
+ if (!lang) stack.shown = 'undef';
+ else stack.shown = lang[0];
+ }
+ }, 'keyboard-layout'),
+ });
+ widgetRevealer.child = widgetContent;
+ return widgetRevealer;
+ } catch {
+ return null;
+ }
+}
+
+const OptionalKeyboardLayout = async () => {
+ try {
+ return await HyprlandXkbKeyboardLayout({ useFlag: userOptions.appearance.keyboardUseFlag });
+ } catch {
+ return null;
+ }
+};
+const createKeyboardLayoutInstances = async () => {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ const monitorsCount = Hyprland.monitors.length
+ const instances = await Promise.all(
+ Array.from({ length: monitorsCount }, () => OptionalKeyboardLayout())
+ );
+
+ return instances;
+};
+const optionalKeyboardLayoutInstances = await createKeyboardLayoutInstances()
+
+export const StatusIcons = (props = {}, monitor = 0) => Widget.Box({
+ ...props,
+ child: Widget.Box({
+ className: 'spacing-h-15',
+ children: [
+ MicMuteIndicator(),
+ optionalKeyboardLayoutInstances[monitor],
+ NotificationIndicator(),
+ NetworkIndicator(),
+ Widget.Box({
+ className: 'spacing-h-5',
+ children: [BluetoothIndicator(), BluetoothDevices()]
+ })
+ ]
+ })
+});
diff --git a/ags/modules/.commonwidgets/statusicons_languages.js b/ags/modules/.commonwidgets/statusicons_languages.js
new file mode 100644
index 00000000..560d9424
--- /dev/null
+++ b/ags/modules/.commonwidgets/statusicons_languages.js
@@ -0,0 +1,62 @@
+// For keyboard layout in statusicons.js
+// This list is not exhaustive. It just includes known/possible languages of users of my dotfiles
+// Add your language here if you use multi-lang xkb input. Else, ignore
+// Note that something like "French (Canada)" should go before "French"
+// and "English (US)" should go before "English"
+export const languages = [
+ {
+ layout: 'us',
+ name: 'English (US)',
+ flag: '🇺🇸'
+ },
+ {
+ layout: 'ru',
+ name: 'Russian',
+ flag: '🇷🇺',
+ },
+ {
+ layout: 'pl',
+ name: 'Polish',
+ flag: '🇷🇵🇵🇱',
+ },
+ {
+ layout: 'ro',
+ name: 'Romanian',
+ flag: '🇷🇴',
+ },
+ {
+ layout: 'ca',
+ name: 'French (Canada)',
+ flag: '🇫🇷',
+ },
+ {
+ layout: 'fr',
+ name: 'French',
+ flag: '🇫🇷',
+ },
+ {
+ layout: 'tr',
+ name: 'Turkish',
+ flag: '🇹🇷',
+ },
+ {
+ layout: 'jp',
+ name: 'Japanese',
+ flag: '🇯🇵',
+ },
+ {
+ layout: 'cn',
+ name: 'Chinese',
+ flag: '🇨🇳',
+ },
+ {
+ layout: 'vn',
+ name: 'Vietnamese',
+ flag: '🇻🇳',
+ },
+ {
+ layout: 'undef',
+ name: 'Undefined',
+ flag: '🧐',
+ },
+]
\ No newline at end of file
diff --git a/ags/modules/.commonwidgets/tabcontainer.js b/ags/modules/.commonwidgets/tabcontainer.js
new file mode 100644
index 00000000..876875dd
--- /dev/null
+++ b/ags/modules/.commonwidgets/tabcontainer.js
@@ -0,0 +1,279 @@
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Button, EventBox, Label, Overlay, Stack } = Widget;
+import { MaterialIcon } from './materialicon.js';
+import { NavigationIndicator } from './cairo_navigationindicator.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { DoubleRevealer } from '../.widgethacks/advancedrevealers.js';
+
+export const TabContainer = ({ icons, names, children, className = '', setup = () => { }, ...rest }) => {
+ const shownIndex = Variable(0);
+ let previousShownIndex = 0;
+ const count = Math.min(icons.length, names.length, children.length);
+ const tabs = Box({
+ homogeneous: true,
+ children: Array.from({ length: count }, (_, i) => Button({ // Tab button
+ className: 'tab-btn',
+ onClicked: () => shownIndex.value = i,
+ setup: setupCursorHover,
+ child: Box({
+ hpack: 'center',
+ vpack: 'center',
+ className: 'spacing-h-5 txt-small',
+ children: [
+ MaterialIcon(icons[i], 'norm'),
+ Label({
+ label: names[i],
+ })
+ ]
+ })
+ })),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.children[previousShownIndex].toggleClassName('tab-btn-active', false);
+ self.children[shownIndex.value].toggleClassName('tab-btn-active', true);
+ previousShownIndex = shownIndex.value;
+ }),
+ });
+ const tabIndicatorLine = Box({
+ vertical: true,
+ homogeneous: true,
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.children[0].css = `font-size: ${shownIndex.value}px;`;
+ }),
+ children: [NavigationIndicator({
+ className: 'tab-indicator',
+ count: count,
+ css: `font-size: ${shownIndex.value}px;`,
+ })],
+ });
+ const tabSection = Box({
+ homogeneous: true,
+ children: [EventBox({
+ onScrollUp: () => mainBox.prevTab(),
+ onScrollDown: () => mainBox.nextTab(),
+ child: Box({
+ vertical: true,
+ children: [
+ tabs,
+ tabIndicatorLine
+ ]
+ })
+ })]
+ });
+ const contentStack = Stack({
+ transition: 'slide_left_right',
+ children: children.reduce((acc, currentValue, index) => {
+ acc[index] = currentValue;
+ return acc;
+ }, {}),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.shown = `${shownIndex.value}`;
+ }),
+ });
+ const mainBox = Box({
+ attribute: {
+ children: children,
+ shown: shownIndex,
+ names: names,
+ },
+ vertical: true,
+ className: `spacing-v-5 ${className}`,
+ setup: (self) => {
+ self.pack_start(tabSection, false, false, 0);
+ self.pack_end(contentStack, true, true, 0);
+ setup(self);
+ },
+ ...rest,
+ });
+ mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
+ mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
+ mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
+
+ return mainBox;
+}
+
+
+export const IconTabContainer = ({
+ iconWidgets, names, children, className = '',
+ setup = () => { }, onChange = () => { },
+ tabsHpack = 'center', tabSwitcherClassName = '',
+ ...rest
+}) => {
+ const shownIndex = Variable(0);
+ let previousShownIndex = 0;
+ const count = Math.min(iconWidgets.length, names.length, children.length);
+ const tabs = Box({
+ hpack: tabsHpack,
+ className: `spacing-h-5 ${tabSwitcherClassName}`,
+ children: iconWidgets.map((icon, i) => Button({
+ className: 'tab-icon',
+ tooltipText: names[i],
+ child: icon,
+ setup: setupCursorHover,
+ onClicked: () => shownIndex.value = i,
+ })),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.children[previousShownIndex].toggleClassName('tab-icon-active', false);
+ self.children[shownIndex.value].toggleClassName('tab-icon-active', true);
+ previousShownIndex = shownIndex.value;
+ }),
+ });
+ const tabSection = Box({
+ homogeneous: true,
+ children: [EventBox({
+ onScrollUp: () => mainBox.prevTab(),
+ onScrollDown: () => mainBox.nextTab(),
+ child: Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ tabs,
+ ]
+ })
+ })]
+ });
+ const contentStack = Stack({
+ transition: 'slide_left_right',
+ children: children.reduce((acc, currentValue, index) => {
+ acc[index] = currentValue;
+ return acc;
+ }, {}),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.shown = `${shownIndex.value}`;
+ }),
+ });
+ const mainBox = Box({
+ attribute: {
+ children: children,
+ shown: shownIndex,
+ names: names,
+ },
+ vertical: true,
+ className: `spacing-v-5 ${className}`,
+ setup: (self) => {
+ self.pack_start(tabSection, false, false, 0);
+ self.pack_end(contentStack, true, true, 0);
+ setup(self);
+ self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
+ },
+ ...rest,
+ });
+ mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
+ mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
+ mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
+ mainBox.shown = shownIndex;
+
+ return mainBox;
+}
+
+export const ExpandingIconTabContainer = ({
+ icons, names, children, className = '',
+ setup = () => { }, onChange = () => { },
+ tabsHpack = 'center', tabSwitcherClassName = '',
+ transitionDuration = userOptions.animations.durationLarge,
+ ...rest
+}) => {
+ const shownIndex = Variable(0);
+ let previousShownIndex = 0;
+ const count = Math.min(icons.length, names.length, children.length);
+ const tabs = Box({
+ hpack: tabsHpack,
+ className: `spacing-h-5 ${tabSwitcherClassName}`,
+ children: icons.map((icon, i) => {
+ const tabIcon = MaterialIcon(icon, 'norm', { hexpand: true });
+ const tabName = DoubleRevealer({
+ transition1: 'slide_right',
+ transition2: 'crossfade',
+ duration1: 0,
+ duration2: 0,
+ // duration1: userOptions.animations.durationSmall,
+ // duration2: userOptions.animations.durationSmall,
+ child: Label({
+ className: 'margin-left-5 txt-small',
+ label: names[i],
+ }),
+ revealChild: i === shownIndex.value,
+ })
+ const button = Button({
+ className: 'tab-icon-expandable',
+ tooltipText: names[i],
+ child: Box({
+ homogeneous: true,
+ children: [Box({
+ hpack: 'center',
+ children: [
+ tabIcon,
+ tabName,
+ ]
+ })],
+ }),
+ setup: setupCursorHover,
+ onClicked: () => shownIndex.value = i,
+ });
+ button.toggleFocus = (value) => {
+ tabIcon.hexpand = !value;
+ button.toggleClassName('tab-icon-expandable-active', value);
+ tabName.toggleRevealChild(value);
+ }
+ return button;
+ }),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.children[previousShownIndex].toggleFocus(false);
+ self.children[shownIndex.value].toggleFocus(true);
+ previousShownIndex = shownIndex.value;
+ }),
+ });
+ const tabSection = Box({
+ homogeneous: true,
+ children: [EventBox({
+ onScrollUp: () => mainBox.prevTab(),
+ onScrollDown: () => mainBox.nextTab(),
+ child: Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ tabs,
+ ]
+ })
+ })]
+ });
+ const contentStack = Stack({
+ transition: 'slide_left_right',
+ transitionDuration: transitionDuration,
+ children: children.reduce((acc, currentValue, index) => {
+ acc[index] = currentValue;
+ return acc;
+ }, {}),
+ setup: (self) => self.hook(shownIndex, (self) => {
+ self.shown = `${shownIndex.value}`;
+ }),
+ });
+ const mainBox = Box({
+ attribute: {
+ children: children,
+ shown: shownIndex,
+ names: names,
+ },
+ vertical: true,
+ className: `spacing-v-5 ${className}`,
+ setup: (self) => {
+ self.pack_start(tabSection, false, false, 0);
+ self.pack_end(contentStack, true, true, 0);
+ setup(self);
+ self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
+ },
+ ...rest,
+ });
+ mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
+ mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
+ mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
+ mainBox.focusName = (name) => {
+ const focusIndex = names.indexOf(name);
+ if (focusIndex !== -1) {
+ shownIndex.value = focusIndex;
+ }
+ }
+ mainBox.shown = shownIndex;
+
+ return mainBox;
+}
diff --git a/ags/modules/.configuration/user_options.js b/ags/modules/.configuration/user_options.js
new file mode 100644
index 00000000..13be1e91
--- /dev/null
+++ b/ags/modules/.configuration/user_options.js
@@ -0,0 +1,258 @@
+import GLib from 'gi://GLib';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+import userOverrides from '../../user_options.js';
+
+// Default options.
+// Add overrides in ~/.config/ags/user_options.js
+let configOptions = {
+ // General stuff
+ 'ai': {
+ 'defaultGPTProvider': "openai",
+ 'defaultTemperature': 0.9,
+ 'enhancements': true,
+ 'useHistory': true,
+ 'safety': true,
+ 'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering
+ 'proxyUrl': null, // Can be "socks5://127.0.0.1:9050" or "http://127.0.0.1:8080" for example. Leave it blank if you don't need it.
+ },
+ 'animations': {
+ 'choreographyDelay': 35,
+ 'durationSmall': 110,
+ 'durationLarge': 180,
+ },
+ 'appearance': {
+ 'autoDarkMode': { // Turns on dark mode in certain hours. Time in 24h format
+ 'enabled': false,
+ 'from': "18:10",
+ 'to': "6:10",
+ },
+ 'keyboardUseFlag': false, // Use flag emoji instead of abbreviation letters
+ 'layerSmoke': false,
+ 'layerSmokeStrength': 0.2,
+ 'barRoundCorners': 1, // 0: No, 1: Yes
+ 'fakeScreenRounding': 1, // 0: None | 1: Always | 2: When not fullscreen
+ },
+ 'apps': {
+ 'bluetooth': "blueberry",
+ 'imageViewer': "loupe",
+ 'network': "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi",
+ 'settings': "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center",
+ 'taskManager': "gnome-usage",
+ 'terminal': "foot", // This is only for shell actions
+ },
+ 'battery': {
+ 'low': 20,
+ 'critical': 10,
+ 'warnLevels': [20, 15, 5],
+ 'warnTitles': ["Low battery", "Very low battery", 'Critical Battery'],
+ 'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'],
+ 'suspendThreshold': 3,
+ },
+ 'brightness': {
+ // Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto"
+ // 'default' one will be used if unspecified
+ // Examples
+ // 'eDP-1': "brightnessctl",
+ // 'DP-1': "ddcutil",
+ 'controllers': {
+ 'default': "auto",
+ },
+ },
+ 'cheatsheet': {
+ 'keybinds': {
+ 'configPath': "" // Path to hyprland keybind config file. Leave empty for default (~/.config/hypr/hyprland/keybinds.conf)
+ }
+ },
+ 'gaming': {
+ 'crosshair': {
+ 'size': 20,
+ 'color': 'rgba(113,227,32,0.9)',
+ },
+ },
+ 'monitors': {
+ 'scaleMethod': "division", // Either "division" [default] or "gdk"
+ },
+ 'music': {
+ 'preferredPlayer': "plasma-browser-integration",
+ },
+ 'onScreenKeyboard': {
+ 'layout': "qwerty_full", // See modules/onscreenkeyboard/onscreenkeyboard.js for available layouts
+ },
+ 'overview': {
+ 'scale': 0.18, // Relative to screen size
+ 'numOfRows': 2,
+ 'numOfCols': 5,
+ 'wsNumScale': 0.09,
+ 'wsNumMarginScale': 0.07,
+ },
+ 'sidebar': {
+ 'ai': {
+ 'extraGptModels': {
+ 'oxygen3': {
+ 'name': 'Oxygen (GPT-3.5)',
+ 'logo_name': 'ai-oxygen-symbolic',
+ 'description': 'An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key',
+ 'base_url': 'https://app.oxyapi.uk/v1/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/kM6MaCqGKA',
+ 'key_file': 'oxygen_key.txt',
+ 'model': 'gpt-3.5-turbo',
+ },
+ }
+ },
+ 'image': {
+ 'columns': 2,
+ 'batchCount': 20,
+ 'allowNsfw': false,
+ 'saveInFolderByTags': false,
+ },
+ 'pages': {
+ 'order': ["apis", "tools"],
+ 'apis': {
+ 'order': ["gemini", "gpt", "waifu", "booru"],
+ }
+ },
+ },
+ 'search': {
+ 'enableFeatures': {
+ 'actions': true,
+ 'commands': true,
+ 'mathResults': true,
+ 'directorySearch': true,
+ 'aiSearch': true,
+ 'webSearch': true,
+ },
+ 'engineBaseUrl': "https://www.google.com/search?q=",
+ 'excludedSites': ["quora.com"],
+ },
+ 'time': {
+ // See https://docs.gtk.org/glib/method.DateTime.format.html
+ // Here's the 12h format: "%I:%M%P"
+ // For seconds, add "%S" and set interval to 1000
+ 'format': "%H:%M",
+ 'interval': 5000,
+ 'dateFormatLong': "%A, %d/%m", // On bar
+ 'dateInterval': 5000,
+ 'dateFormat': "%d/%m", // On notif time
+ },
+ 'weather': {
+ 'city': "",
+ 'preferredUnit': "C", // Either C or F
+ },
+ 'workspaces': {
+ 'shown': 10,
+ },
+ 'dock': {
+ 'enabled': false,
+ 'hiddenThickness': 5,
+ 'pinnedApps': ['firefox', 'org.gnome.Nautilus'],
+ 'layer': 'top',
+ 'monitorExclusivity': true, // Dock will move to other monitor along with focus if enabled
+ 'searchPinnedAppIcons': false, // Try to search for the correct icon if the app class isn't an icon name
+ 'trigger': ['client-added', 'client-removed'], // client_added, client_move, workspace_active, client_active
+ // Automatically hide dock after `interval` ms since trigger
+ 'autoHide': [
+ {
+ 'trigger': 'client-added',
+ 'interval': 500,
+ },
+ {
+ 'trigger': 'client-removed',
+ 'interval': 500,
+ },
+ ],
+ },
+ // Longer stuff
+ 'icons': {
+ // Find the window's icon by its class with levenshteinDistance
+ // The file names are processed at startup, so if there
+ // are too many files in the search path it'll affect performance
+ // Example: ['/usr/share/icons/Tela-nord/scalable/apps']
+ 'searchPaths': [''],
+ 'symbolicIconTheme': {
+ "dark": "Adwaita",
+ "light": "Adwaita",
+ },
+ substitutions: {
+ 'code-url-handler': "visual-studio-code",
+ 'Code': "visual-studio-code",
+ 'GitHub Desktop': "github-desktop",
+ 'Minecraft* 1.20.1': "minecraft",
+ 'gnome-tweaks': "org.gnome.tweaks",
+ 'pavucontrol-qt': "pavucontrol",
+ 'wps': "wps-office2019-kprometheus",
+ 'wpsoffice': "wps-office2019-kprometheus",
+ '': "image-missing",
+ },
+ regexSubstitutions: [
+ {
+ regex: /^steam_app_(\d+)$/,
+ replace: "steam_icon_$1",
+ }
+ ]
+ },
+ 'keybinds': {
+ // Format: Mod1+Mod2+key. CaSe SeNsItIvE!
+ // Modifiers: Shift Ctrl Alt Hyper Meta
+ // See https://docs.gtk.org/gdk3/index.html#constants for the other keys (they are listed as KEY_key)
+ 'overview': {
+ 'altMoveLeft': "Ctrl+b",
+ 'altMoveRight': "Ctrl+f",
+ 'deleteToEnd': "Ctrl+k",
+ },
+ 'sidebar': {
+ 'apis': {
+ 'nextTab': "Page_Down",
+ 'prevTab': "Page_Up",
+ },
+ 'options': { // Right sidebar
+ 'nextTab': "Page_Down",
+ 'prevTab': "Page_Up",
+ },
+ 'pin': "Ctrl+p",
+ 'cycleTab': "Ctrl+Tab",
+ 'nextTab': "Ctrl+Page_Down",
+ 'prevTab': "Ctrl+Page_Up",
+ },
+ 'cheatsheet': {
+ 'keybinds': {
+ 'nextTab': "Page_Down",
+ 'prevTab': "Page_Up",
+ },
+ 'nextTab': "Ctrl+Page_Down",
+ 'prevTab': "Ctrl+Page_Up",
+ 'cycleTab': "Ctrl+Tab",
+ }
+ },
+ 'bar': {
+ // Array of bar modes for each monitor. Hit Ctrl+Alt+Slash to cycle.
+ // Modes: "normal", "focus" (workspace indicator only), "nothing"
+ // Example for four monitors: ["normal", "focus", "normal", "nothing"]
+ 'modes': ["normal"]
+ },
+}
+
+// Override defaults with user's options
+let optionsOkay = true;
+function overrideConfigRecursive(userOverrides, configOptions = {}, check = true) {
+ for (const [key, value] of Object.entries(userOverrides)) {
+ if (configOptions[key] === undefined && check) {
+ optionsOkay = false;
+ }
+ else if (typeof value === 'object' && !(value instanceof Array)) {
+ if (key === "substitutions" || key === "regexSubstitutions" || key === "extraGptModels") {
+ overrideConfigRecursive(value, configOptions[key], false);
+ } else overrideConfigRecursive(value, configOptions[key]);
+ } else {
+ configOptions[key] = value;
+ }
+ }
+}
+overrideConfigRecursive(userOverrides, configOptions);
+if (!optionsOkay) Utils.timeout(2000, () => Utils.execAsync(['notify-send',
+ 'Update your user options',
+ 'One or more config options don\'t exist',
+ '-a', 'ags',
+]).catch(print))
+
+globalThis['userOptions'] = configOptions;
+export default configOptions;
diff --git a/ags/modules/.miscutils/files.js b/ags/modules/.miscutils/files.js
new file mode 100644
index 00000000..00c17b91
--- /dev/null
+++ b/ags/modules/.miscutils/files.js
@@ -0,0 +1,14 @@
+const { Gio, GLib, Gtk } = imports.gi;
+
+export function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+export function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
\ No newline at end of file
diff --git a/ags/modules/.miscutils/icons.js b/ags/modules/.miscutils/icons.js
new file mode 100644
index 00000000..66c09eda
--- /dev/null
+++ b/ags/modules/.miscutils/icons.js
@@ -0,0 +1,28 @@
+const { Gtk } = imports.gi;
+
+export function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+export function substitute(str) {
+ // Normal substitutions
+ if (userOptions.icons.substitutions[str])
+ return userOptions.icons.substitutions[str];
+
+ // Regex substitutions
+ for (let i = 0; i < userOptions.icons.regexSubstitutions.length; i++) {
+ const substitution = userOptions.icons.regexSubstitutions[i];
+ const replacedName = str.replace(
+ substitution.regex,
+ substitution.replace,
+ );
+ if (replacedName != str) return replacedName;
+ }
+
+ // Guess: convert to kebab case
+ if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-");
+
+ // Original string
+ return str;
+}
diff --git a/ags/modules/.miscutils/mathfuncs.js b/ags/modules/.miscutils/mathfuncs.js
new file mode 100644
index 00000000..ba1c0b59
--- /dev/null
+++ b/ags/modules/.miscutils/mathfuncs.js
@@ -0,0 +1,4 @@
+
+export function clamp(x, min, max) {
+ return Math.min(Math.max(x, min), max);
+}
\ No newline at end of file
diff --git a/ags/modules/.miscutils/md2pango.js b/ags/modules/.miscutils/md2pango.js
new file mode 100644
index 00000000..afa91ad2
--- /dev/null
+++ b/ags/modules/.miscutils/md2pango.js
@@ -0,0 +1,78 @@
+// Converts from Markdown to Pango. This does not support code blocks.
+// For illogical-impulse, code blocks are treated separately, in their own GtkSourceView widgets.
+// Partly inherited from https://github.com/ubunatic/md2pango
+
+const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace';
+
+const replacements = {
+ 'indents': [
+ { name: 'BULLET', re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: ' $1- $3' },
+ { name: 'NUMBERING', re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: ' $1 $2' },
+ ],
+ 'escapes': [
+ { name: 'COMMENT', re: //, sub: '' },
+ { name: 'AMPERSTAND', re: /&/g, sub: '&' },
+ { name: 'LESSTHAN', re: //g, sub: '>' },
+ ],
+ 'sections': [
+ { name: 'H1', re: /^(#\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H2', re: /^(##\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H3', re: /^(###\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H4', re: /^(####\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H5', re: /^(#####\s+)(.*)(\s*)$/, sub: '$2' },
+ ],
+ 'styles': [
+ { name: 'BOLD', re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "$2" },
+ { name: 'UND', re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "$2" },
+ { name: 'EMPH', re: /\*(\S.*?\S)\*/g, sub: "$1" },
+ // { name: 'EMPH', re: /_(\S.*?\S)_/g, sub: "$1" },
+ { name: 'HEXCOLOR', re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: '#$1' },
+ { name: 'INLCODE', re: /(`)([^`]*)(`)/g, sub: '$2' },
+ // { name: 'UND', re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "$2" },
+ ],
+}
+
+const replaceCategory = (text, replaces) => {
+ for (const type of replaces) {
+ text = text.replace(type.re, type.sub);
+ }
+ return text;
+}
+
+// Main function
+
+export default (text) => {
+ let lines = text.split('\n')
+ let output = [];
+ // Replace
+ for (const line of lines) {
+ let result = line;
+ result = replaceCategory(result, replacements.indents);
+ result = replaceCategory(result, replacements.escapes);
+ result = replaceCategory(result, replacements.sections);
+ result = replaceCategory(result, replacements.styles);
+ output.push(result)
+ }
+ // Remove trailing whitespaces
+ output = output.map(line => line.replace(/ +$/, ''))
+ return output.join('\n');
+}
+
+export const markdownTest = `## Inline formatting
+- **Bold** *Italics* __Underline__
+- \`Monospace text\` 🤓
+- Colors
+ - Nvidia green #7ABB08
+ - Soundcloud orange #FF5500
+## Code block
+\`\`\`cpp
+#include
+const std::string GREETING="UwU";
+int main() { std::cout << GREETING; }
+\`\`\`
+## LaTeX
+\`\`\`latex
+\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} \\\\ → \\\\ cos(2x) = 2cos^2(x) - 1 = 1 - 2sin^2(x) = cos^2(x) - sin^2(x)
+\`\`\`
+`;
diff --git a/ags/modules/.miscutils/system.js b/ags/modules/.miscutils/system.js
new file mode 100644
index 00000000..377463b2
--- /dev/null
+++ b/ags/modules/.miscutils/system.js
@@ -0,0 +1,61 @@
+const { GLib } = imports.gi;
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+
+export const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2 | sed "s/\\"//g"'`).trim();
+export const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'popos' || distroID == 'raspbian' || distroID == 'kali');
+export const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros' || distroID == 'cachyos');
+export const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
+
+const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
+export const darkMode = Variable(!(Utils.readFile(LIGHTDARK_FILE_LOCATION).split('\n')[0].trim() == 'light'));
+darkMode.connect('changed', ({ value }) => {
+ let lightdark = value ? "dark" : "light";
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "1s/.*/${lightdark}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .then(execAsync(['bash', '-c', `command -v darkman && darkman set ${lightdark}`])) // Optional darkman integration
+ .catch(print);
+});
+globalThis['darkMode'] = darkMode;
+export const hasPlasmaIntegration = !!Utils.exec('bash -c "command -v plasma-browser-integration-host"');
+
+export const getDistroIcon = () => {
+ // Arches
+ if(distroID == 'arch') return 'arch-symbolic';
+ if(distroID == 'endeavouros') return 'endeavouros-symbolic';
+ if(distroID == 'cachyos') return 'cachyos-symbolic';
+ // Funny flake
+ if(distroID == 'nixos') return 'nixos-symbolic';
+ // Cool thing
+ if(distroID == 'fedora') return 'fedora-symbolic';
+ // Debians
+ if(distroID == 'linuxmint') return 'ubuntu-symbolic';
+ if(distroID == 'ubuntu') return 'ubuntu-symbolic';
+ if(distroID == 'debian') return 'debian-symbolic';
+ if(distroID == 'zorin') return 'ubuntu-symbolic';
+ if(distroID == 'popos') return 'ubuntu-symbolic';
+ if(distroID == 'raspbian') return 'debian-symbolic';
+ if(distroID == 'kali') return 'debian-symbolic';
+ return 'linux-symbolic';
+}
+
+export const getDistroName = () => {
+ // Arches
+ if(distroID == 'arch') return 'Arch Linux';
+ if(distroID == 'endeavouros') return 'EndeavourOS';
+ if(distroID == 'cachyos') return 'CachyOS';
+ // Funny flake
+ if(distroID == 'nixos') return 'NixOS';
+ // Cool thing
+ if(distroID == 'fedora') return 'Fedora';
+ // Debians
+ if(distroID == 'linuxmint') return 'Linux Mint';
+ if(distroID == 'ubuntu') return 'Ubuntu';
+ if(distroID == 'debian') return 'Debian';
+ if(distroID == 'zorin') return 'Zorin';
+ if(distroID == 'popos') return 'Pop!_OS';
+ if(distroID == 'raspbian') return 'Raspbian';
+ if(distroID == 'kali') return 'Kali Linux';
+ return 'Linux';
+}
diff --git a/ags/modules/.widgethacks/advancedrevealers.js b/ags/modules/.widgethacks/advancedrevealers.js
new file mode 100644
index 00000000..3f127931
--- /dev/null
+++ b/ags/modules/.widgethacks/advancedrevealers.js
@@ -0,0 +1,86 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+const { Revealer, Scrollable } = Widget;
+
+export const MarginRevealer = ({
+ transition = 'slide_down',
+ child,
+ revealChild,
+ showClass = 'element-show', // These are for animation curve, they don't really hide
+ hideClass = 'element-hide', // Don't put margins in these classes!
+ extraSetup = () => { },
+ ...rest
+}) => {
+ const widget = Scrollable({
+ ...rest,
+ attribute: {
+ 'revealChild': true, // It'll be set to false after init if it's supposed to hide
+ 'transition': transition,
+ 'show': () => {
+ if (widget.attribute.revealChild) return;
+ widget.hscroll = 'never';
+ widget.vscroll = 'never';
+ child.toggleClassName(hideClass, false);
+ child.toggleClassName(showClass, true);
+ widget.attribute.revealChild = true;
+ child.css = 'margin: 0px;';
+ },
+ 'hide': () => {
+ if (!widget.attribute.revealChild) return;
+ child.toggleClassName(hideClass, true);
+ child.toggleClassName(showClass, false);
+ widget.attribute.revealChild = false;
+ if (widget.attribute.transition == 'slide_left')
+ child.css = `margin-right: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_right')
+ child.css = `margin-left: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_up')
+ child.css = `margin-bottom: -${child.get_allocated_height()}px;`;
+ else if (widget.attribute.transition == 'slide_down')
+ child.css = `margin-top: -${child.get_allocated_height()}px;`;
+ },
+ 'toggle': () => {
+ if (widget.attribute.revealChild) widget.attribute.hide();
+ else widget.attribute.show();
+ },
+ },
+ child: child,
+ hscroll: `${revealChild ? 'never' : 'always'}`,
+ vscroll: `${revealChild ? 'never' : 'always'}`,
+ setup: (self) => {
+ extraSetup(self);
+ }
+ });
+ child.toggleClassName(`${revealChild ? showClass : hideClass}`, true);
+ return widget;
+}
+
+// TODO: Allow reveal update. Currently this just helps at declaration
+export const DoubleRevealer = ({
+ transition1 = 'slide_right',
+ transition2 = 'slide_left',
+ duration1 = 150,
+ duration2 = 150,
+ child,
+ revealChild,
+ ...rest
+}) => {
+ const r2 = Revealer({
+ transition: transition2,
+ transitionDuration: duration2,
+ revealChild: revealChild,
+ child: child,
+ });
+ const r1 = Revealer({
+ transition: transition1,
+ transitionDuration: duration1,
+ revealChild: revealChild,
+ child: r2,
+ ...rest,
+ })
+ r1.toggleRevealChild = (value) => {
+ r1.revealChild = value;
+ r2.revealChild = value;
+ }
+ return r1;
+}
diff --git a/ags/modules/.widgethacks/popupwindow.js b/ags/modules/.widgethacks/popupwindow.js
new file mode 100644
index 00000000..5c07c0eb
--- /dev/null
+++ b/ags/modules/.widgethacks/popupwindow.js
@@ -0,0 +1,36 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Window } = Widget;
+
+
+export default ({
+ name,
+ child,
+ showClassName = "",
+ hideClassName = "",
+ ...props
+}) => {
+ return Window({
+ name,
+ visible: false,
+ layer: 'overlay',
+ ...props,
+
+ child: Box({
+ setup: (self) => {
+ self.keybind("Escape", () => closeEverything());
+ if (showClassName != "" && hideClassName !== "") {
+ self.hook(App, (self, currentName, visible) => {
+ if (currentName === name) {
+ self.toggleClassName(hideClassName, !visible);
+ }
+ });
+
+ if (showClassName !== "" && hideClassName !== "")
+ self.className = `${showClassName} ${hideClassName}`;
+ }
+ },
+ child: child,
+ }),
+ });
+}
\ No newline at end of file
diff --git a/ags/modules/.widgetutils/clickthrough.js b/ags/modules/.widgetutils/clickthrough.js
new file mode 100644
index 00000000..505f1412
--- /dev/null
+++ b/ags/modules/.widgetutils/clickthrough.js
@@ -0,0 +1,4 @@
+import Cairo from 'gi://cairo?version=1.0';
+
+export const dummyRegion = new Cairo.Region();
+export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
\ No newline at end of file
diff --git a/ags/modules/.widgetutils/cursorhover.js b/ags/modules/.widgetutils/cursorhover.js
new file mode 100644
index 00000000..89be913b
--- /dev/null
+++ b/ags/modules/.widgetutils/cursorhover.js
@@ -0,0 +1,57 @@
+const { Gdk } = imports.gi;
+
+export function setupCursorHover(button) { // Hand pointing cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+
+}
+
+export function setupCursorHoverAim(button) { // Crosshair cursor on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverGrab(button) { // Hand ready to grab on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'grab');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverInfo(button) { // "?" mark cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'help');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
diff --git a/ags/modules/.widgetutils/keybind.js b/ags/modules/.widgetutils/keybind.js
new file mode 100644
index 00000000..eda7877b
--- /dev/null
+++ b/ags/modules/.widgetutils/keybind.js
@@ -0,0 +1,25 @@
+const { Gdk } = imports.gi;
+
+const MODS = {
+ 'Shift': Gdk.ModifierType.SHIFT_MASK,
+ 'Ctrl': Gdk.ModifierType.CONTROL_MASK,
+ 'Alt': Gdk.ModifierType.ALT_MASK,
+ 'Hyper': Gdk.ModifierType.HYPER_MASK,
+ 'Meta': Gdk.ModifierType.META_MASK
+}
+
+export const checkKeybind = (event, keybind) => {
+ const pressedModMask = event.get_state()[1];
+ const pressedKey = event.get_keyval()[1];
+ const keys = keybind.split('+');
+ for (let i = 0; i < keys.length; i++) {
+ if (keys[i] in MODS) {
+ if (!(pressedModMask & MODS[keys[i]])) {
+ return false;
+ }
+ } else if (pressedKey !== Gdk[`KEY_${keys[i]}`]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/ags/modules/bar/focus/workspaces_hyprland.js b/ags/modules/bar/focus/workspaces_hyprland.js
new file mode 100644
index 00000000..77fe4793
--- /dev/null
+++ b/ags/modules/bar/focus/workspaces_hyprland.js
@@ -0,0 +1,213 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, DrawingArea, EventBox } = Widget;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+const dummyWs = Box({ className: 'bar-ws-focus' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws-focus bar-ws-focus-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws-focus bar-ws-focus-occupied' }); // Not shown. Only for getting size props
+
+const WS_TAKEN_WIDTH_MULTIPLIER = 1.4;
+const floor = Math.floor;
+const ceil = Math.ceil;
+
+// Font size = workspace id
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ className: 'menu-decel',
+ attribute: {
+ lastImmediateActiveWs: 0,
+ immediateActiveWs: 0,
+ initialized: false,
+ workspaceMask: 0,
+ workspaceGroup: 0,
+ updateMask: (self) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
+ // if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Hyprland.workspaces;
+ let workspaceMask = 0;
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
+ if (workspaces[i].windows > 0)
+ workspaceMask |= (1 << (ws.id - offset));
+ }
+ // console.log('Mask:', workspaceMask.toString(2));
+ self.attribute.workspaceMask = workspaceMask;
+ // self.attribute.initialized = true;
+ self.queue_draw();
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ self.queue_draw();
+ },
+ },
+ setup: (area) => area
+ .hook(Hyprland.active.workspace, (self) => {
+ const newActiveWs = (Hyprland.active.workspace.id - 1) % count + 1;
+ self.setCss(`font-size: ${newActiveWs}px;`);
+ self.attribute.lastImmediateActiveWs = self.attribute.immediateActiveWs;
+ self.attribute.immediateActiveWs = newActiveWs;
+ const previousGroup = self.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
+ if (currentGroup !== previousGroup) {
+ self.attribute.updateMask(self);
+ self.attribute.workspaceGroup = currentGroup;
+ }
+ })
+ .hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
+
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activeWorkspaceWidth = activeWorkspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ // const activeWorkspaceWidth = 100;
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+ const lastImmediateActiveWs = area.attribute.lastImmediateActiveWs;
+ const immediateActiveWs = area.attribute.immediateActiveWs;
+
+ // Draw
+ area.set_size_request(workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth, -1);
+ for (let i = 1; i <= count; i++) {
+ if (i == immediateActiveWs) continue;
+ let colors = {};
+ if (area.attribute.workspaceMask & (1 << i)) colors = occupiedbg;
+ else colors = wsbg;
+
+ // if ((i == immediateActiveWs + 1 && immediateActiveWs < activeWs) ||
+ // (i == immediateActiveWs + 1 && immediateActiveWs < activeWs)) {
+ // const widthPercentage = (i == immediateActiveWs - 1) ?
+ // 1 - (immediateActiveWs - activeWs) :
+ // activeWs - immediateActiveWs;
+ // cr.setSourceRGBA(colors.red * widthPercentage + activebg.red * (1 - widthPercentage),
+ // colors.green * widthPercentage + activebg.green * (1 - widthPercentage),
+ // colors.blue * widthPercentage + activebg.blue * (1 - widthPercentage),
+ // colors.alpha);
+ // }
+ // else
+ cr.setSourceRGBA(colors.red, colors.green, colors.blue, colors.alpha)
+
+ const centerX = (i <= activeWs) ?
+ (-workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * i))
+ : -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - i) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
+ cr.arc(centerX, height / 2, workspaceRadius, 0, 2 * Math.PI);
+ cr.fill();
+ // What if shrinking
+ if (i == floor(activeWs) && immediateActiveWs > activeWs) { // To right
+ const widthPercentage = 1 - (ceil(activeWs) - activeWs);
+ const leftX = centerX;
+ const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
+ cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
+ cr.fill();
+ cr.arc(leftX + wsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
+ cr.fill();
+ }
+ else if (i == ceil(activeWs) && immediateActiveWs < activeWs) { // To left
+ const widthPercentage = activeWs - floor(activeWs);
+ const rightX = centerX;
+ const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage;
+ const leftX = rightX - wsWidth;
+ cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
+ cr.fill();
+ cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2);
+ cr.fill();
+ }
+ }
+
+ let widthPercentage, leftX, rightX, activeWsWidth;
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ if (immediateActiveWs > activeWs) { // To right
+ const immediateActiveWs = ceil(activeWs);
+ widthPercentage = immediateActiveWs - activeWs;
+ rightX = -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - immediateActiveWs) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
+ activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
+ leftX = rightX - activeWsWidth;
+
+ cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
+ cr.fill();
+ cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
+ cr.fill();
+ cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
+ cr.fill();
+ }
+ else { // To left
+ const immediateActiveWs = floor(activeWs);
+ widthPercentage = 1 - (activeWs - immediateActiveWs);
+ leftX = -workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * immediateActiveWs);
+ activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage
+
+ cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
+ cr.fill();
+ cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
+ cr.fill();
+ cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
+ cr.fill();
+ }
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: () => Hyprland.messageAsync(`dispatch workspace -1`).catch(print),
+ onScrollDown: () => Hyprland.messageAsync(`dispatch workspace +1`).catch(print),
+ onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
+ onSecondaryClick: () => App.toggleWindow('overview'),
+ attribute: {
+ clicked: false,
+ ws_group: 0,
+ },
+ child: Box({
+ homogeneous: true,
+ // className: 'bar-group-margin',
+ children: [Box({
+ // className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [WorkspaceContents(userOptions.workspaces.shown)],
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
+ .catch(print);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ // const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_PER_GROUP / widgetWidth) + self.attribute.ws_group * NUM_OF_WORKSPACES_PER_GROUP;
+ // Hyprland.messageAsync(`dispatch workspace ${wsId}`).catch(print);
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
+ .catch(print);
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+})
diff --git a/ags/modules/bar/focus/workspaces_sway.js b/ags/modules/bar/focus/workspaces_sway.js
new file mode 100644
index 00000000..02fed9fa
--- /dev/null
+++ b/ags/modules/bar/focus/workspaces_sway.js
@@ -0,0 +1,183 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import Widget from "resource:///com/github/Aylur/ags/widget.js";
+import Sway from "../../../services/sway.js";
+import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
+const { execAsync, exec } = Utils;
+const { Box, DrawingArea, EventBox } = Widget;
+
+const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
+
+const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
+const switchToRelativeWorkspace = (self, num) =>
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
+ attribute: {
+ initialized: false,
+ workspaceMask: 0,
+ updateMask: (self) => {
+ if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Sway.workspaces;
+ let workspaceMask = 0;
+ // console.log('----------------')
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ // console.log(ws.name, ',', ws.num);
+ if (!Number(ws.name)) return;
+ const id = Number(ws.name);
+ if (id <= 0) continue; // Ignore scratchpads
+ if (id > count) return; // Not rendered
+ if (workspaces[i].windows > 0) {
+ workspaceMask |= (1 << id);
+ }
+ }
+ self.attribute.workspaceMask = workspaceMask;
+ self.attribute.initialized = true;
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ },
+ },
+ setup: (area) => area
+ .hook(Sway.active.workspace, (area) => {
+ area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
+ })
+ .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
+ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ area.set_size_request(workspaceDiameter * count, -1);
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
+ const activeWsCenterY = height / 2;
+
+ // Font
+ const layout = PangoCairo.create_layout(cr);
+ const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
+ layout.set_font_description(fontDesc);
+ cr.setAntialias(Cairo.Antialias.BEST);
+ // Get kinda min radius for number indicators
+ layout.set_text("0".repeat(count.toString().length), -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
+ const indicatorGap = workspaceRadius - indicatorRadius;
+
+ // Draw workspace numbers
+ for (let i = 1; i <= count; i++) {
+ if (area.attribute.workspaceMask & (1 << i)) {
+ // Draw bg highlight
+ cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
+ const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
+ const wsCenterY = height / 2;
+ if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+
+ // Set color for text
+ cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
+ }
+ else
+ cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
+ layout.set_text(`${i}`, -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
+ const y = (height - layoutHeight) / 2;
+ cr.moveTo(x, y);
+ // cr.showText(text);
+ PangoCairo.show_layout(cr, layout);
+ cr.stroke();
+ }
+
+ // Draw active ws
+ // base
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
+ cr.fill();
+ // inner decor
+ cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
+ cr.fill();
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
+ onSecondaryClick: () => App.toggleWindow('overview'),
+ attribute: { clicked: false },
+ child: Box({
+ homogeneous: true,
+ className: 'bar-group-margin',
+ children: [Box({
+ className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [
+ WorkspaceContents(10),
+ ]
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+});
diff --git a/ags/modules/bar/main.js b/ags/modules/bar/main.js
new file mode 100644
index 00000000..771306e8
--- /dev/null
+++ b/ags/modules/bar/main.js
@@ -0,0 +1,129 @@
+const { Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
+
+import WindowTitle from "./normal/spaceleft.js";
+import Indicators from "./normal/spaceright.js";
+import Music from "./normal/music.js";
+import System from "./normal/system.js";
+import { enableClickthrough } from "../.widgetutils/clickthrough.js";
+import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
+import { currentShellMode } from '../../variables.js';
+
+const NormalOptionalWorkspaces = async () => {
+ try {
+ return (await import('./normal/workspaces_hyprland.js')).default();
+ } catch {
+ try {
+ return (await import('./normal/workspaces_sway.js')).default();
+ } catch {
+ return null;
+ }
+ }
+};
+
+const FocusOptionalWorkspaces = async () => {
+ try {
+ return (await import('./focus/workspaces_hyprland.js')).default();
+ } catch {
+ try {
+ return (await import('./focus/workspaces_sway.js')).default();
+ } catch {
+ return null;
+ }
+ }
+};
+
+export const Bar = async (monitor = 0) => {
+ const SideModule = (children) => Widget.Box({
+ className: 'bar-sidemodule',
+ children: children,
+ });
+ const normalBarContent = Widget.CenterBox({
+ className: 'bar-bg',
+ setup: (self) => {
+ const styleContext = self.get_style_context();
+ const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ // execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
+ },
+ startWidget: (await WindowTitle(monitor)),
+ centerWidget: Widget.Box({
+ className: 'spacing-h-4',
+ children: [
+ SideModule([Music()]),
+ Widget.Box({
+ homogeneous: true,
+ children: [await NormalOptionalWorkspaces()],
+ }),
+ SideModule([System()]),
+ ]
+ }),
+ endWidget: Indicators(monitor),
+ });
+ const focusedBarContent = Widget.CenterBox({
+ className: 'bar-bg-focus',
+ startWidget: Widget.Box({}),
+ centerWidget: Widget.Box({
+ className: 'spacing-h-4',
+ children: [
+ SideModule([]),
+ Widget.Box({
+ homogeneous: true,
+ children: [await FocusOptionalWorkspaces()],
+ }),
+ SideModule([]),
+ ]
+ }),
+ endWidget: Widget.Box({}),
+ setup: (self) => {
+ self.hook(Battery, (self) => {
+ if (!Battery.available) return;
+ self.toggleClassName('bar-bg-focus-batterylow', Battery.percent <= userOptions.battery.low);
+ })
+ }
+ });
+ const nothingContent = Widget.Box({
+ className: 'bar-bg-nothing',
+ })
+ return Widget.Window({
+ monitor,
+ name: `bar${monitor}`,
+ anchor: ['top', 'left', 'right'],
+ exclusivity: 'exclusive',
+ visible: true,
+ child: Widget.Stack({
+ homogeneous: false,
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: {
+ 'normal': normalBarContent,
+ 'focus': focusedBarContent,
+ 'nothing': nothingContent,
+ },
+ setup: (self) => self.hook(currentShellMode, (self) => {
+ self.shown = currentShellMode.value[monitor];
+ })
+ }),
+ });
+}
+
+export const BarCornerTopleft = (monitor = 0) => Widget.Window({
+ monitor,
+ name: `barcornertl${monitor}`,
+ layer: 'top',
+ anchor: ['top', 'left'],
+ exclusivity: 'normal',
+ visible: true,
+ child: RoundedCorner('topleft', { className: 'corner', }),
+ setup: enableClickthrough,
+});
+export const BarCornerTopright = (monitor = 0) => Widget.Window({
+ monitor,
+ name: `barcornertr${monitor}`,
+ layer: 'top',
+ anchor: ['top', 'right'],
+ exclusivity: 'normal',
+ visible: true,
+ child: RoundedCorner('topright', { className: 'corner', }),
+ setup: enableClickthrough,
+});
diff --git a/ags/modules/bar/normal/music.js b/ags/modules/bar/normal/music.js
new file mode 100644
index 00000000..1df92235
--- /dev/null
+++ b/ags/modules/bar/normal/music.js
@@ -0,0 +1,230 @@
+const { GLib } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
+const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget;
+const { execAsync, exec } = Utils;
+import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { showMusicControls } from '../../../variables.js';
+
+const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-interval.txt`;
+const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-poll.sh`;
+const CUSTOM_MODULE_LEFTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-leftclick.sh`;
+const CUSTOM_MODULE_RIGHTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-rightclick.sh`;
+const CUSTOM_MODULE_MIDDLECLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-middleclick.sh`;
+const CUSTOM_MODULE_SCROLLUP_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrollup.sh`;
+const CUSTOM_MODULE_SCROLLDOWN_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrolldown.sh`;
+
+function trimTrackTitle(title) {
+ if (!title) return '';
+ const cleanPatterns = [
+ /【[^】]*】/, // Touhou n weeb stuff
+ " [FREE DOWNLOAD]", // F-777
+ ];
+ cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
+ return title;
+}
+
+const BarGroup = ({ child }) => Box({
+ className: 'bar-group-margin bar-sides',
+ children: [
+ Box({
+ className: 'bar-group bar-group-standalone bar-group-pad-system',
+ children: [child],
+ }),
+ ]
+});
+
+const BarResource = (name, icon, command, circprogClassName = 'bar-batt-circprog', textClassName = 'txt-onSurfaceVariant', iconClassName = 'bar-batt') => {
+ const resourceCircProg = AnimatedCircProg({
+ className: `${circprogClassName}`,
+ vpack: 'center',
+ hpack: 'center',
+ });
+ const resourceProgress = Box({
+ homogeneous: true,
+ children: [Overlay({
+ child: Box({
+ vpack: 'center',
+ className: `${iconClassName}`,
+ homogeneous: true,
+ children: [
+ MaterialIcon(icon, 'small'),
+ ],
+ }),
+ overlays: [resourceCircProg]
+ })]
+ });
+ const resourceLabel = Label({
+ className: `txt-smallie ${textClassName}`,
+ });
+ const widget = Button({
+ onClicked: () => Utils.execAsync(['bash', '-c', `${userOptions.apps.taskManager}`]).catch(print),
+ child: Box({
+ className: `spacing-h-4 ${textClassName}`,
+ children: [
+ resourceProgress,
+ resourceLabel,
+ ],
+ setup: (self) => self.poll(5000, () => execAsync(['bash', '-c', command])
+ .then((output) => {
+ resourceCircProg.css = `font-size: ${Number(output)}px;`;
+ resourceLabel.label = `${Math.round(Number(output))}%`;
+ widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
+ }).catch(print))
+ ,
+ })
+ });
+ return widget;
+}
+
+const TrackProgress = () => {
+ const _updateProgress = (circprog) => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris) return;
+ // Set circular progress value
+ circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
+ }
+ return AnimatedCircProg({
+ className: 'bar-music-circprog',
+ vpack: 'center', hpack: 'center',
+ extraSetup: (self) => self
+ .hook(Mpris, _updateProgress)
+ .poll(3000, _updateProgress)
+ ,
+ })
+}
+
+const switchToRelativeWorkspace = async (self, num) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.messageAsync(`dispatch workspace ${num > 0 ? '+' : ''}${num}`).catch(print);
+ } catch {
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+ }
+}
+
+export default () => {
+ // TODO: use cairo to make button bounce smaller on click, if that's possible
+ const playingState = Box({ // Wrap a box cuz overlay can't have margins itself
+ homogeneous: true,
+ children: [Overlay({
+ child: Box({
+ vpack: 'center',
+ className: 'bar-music-playstate',
+ homogeneous: true,
+ children: [Label({
+ vpack: 'center',
+ className: 'bar-music-playstate-txt',
+ justification: 'center',
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
+ }),
+ })],
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris) return;
+ label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
+ label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
+ }),
+ }),
+ overlays: [
+ TrackProgress(),
+ ]
+ })]
+ });
+ const trackTitle = Label({
+ hexpand: true,
+ className: 'txt-smallie bar-music-txt',
+ truncate: 'end',
+ maxWidthChars: 1, // Doesn't matter, just needs to be non negative
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ if (mpris)
+ label.label = `${trimTrackTitle(mpris.trackTitle)} • ${mpris.trackArtists.join(', ')}`;
+ else
+ label.label = 'No media';
+ }),
+ })
+ const musicStuff = Box({
+ className: 'spacing-h-10',
+ hexpand: true,
+ children: [
+ playingState,
+ trackTitle,
+ ]
+ })
+ const SystemResourcesOrCustomModule = () => {
+ // Check if $XDG_CACHE_HOME/ags/user/scripts/custom-module-poll.sh exists
+ if (GLib.file_test(CUSTOM_MODULE_CONTENT_SCRIPT, GLib.FileTest.EXISTS)) {
+ const interval = Number(Utils.readFile(CUSTOM_MODULE_CONTENT_INTERVAL_FILE)) || 5000;
+ return BarGroup({
+ child: Button({
+ child: Label({
+ className: 'txt-smallie txt-onSurfaceVariant',
+ useMarkup: true,
+ setup: (self) => Utils.timeout(1, () => {
+ self.label = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
+ self.poll(interval, (self) => {
+ const content = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
+ self.label = content;
+ })
+ })
+ }),
+ onPrimaryClickRelease: () => execAsync(CUSTOM_MODULE_LEFTCLICK_SCRIPT).catch(print),
+ onSecondaryClickRelease: () => execAsync(CUSTOM_MODULE_RIGHTCLICK_SCRIPT).catch(print),
+ onMiddleClickRelease: () => execAsync(CUSTOM_MODULE_MIDDLECLICK_SCRIPT).catch(print),
+ onScrollUp: () => execAsync(CUSTOM_MODULE_SCROLLUP_SCRIPT).catch(print),
+ onScrollDown: () => execAsync(CUSTOM_MODULE_SCROLLDOWN_SCRIPT).catch(print),
+ })
+ });
+ } else return BarGroup({
+ child: Box({
+ children: [
+ BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
+ 'bar-ram-circprog', 'bar-ram-txt', 'bar-ram-icon'),
+ Revealer({
+ revealChild: true,
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ className: 'spacing-h-10 margin-left-10',
+ children: [
+ BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
+ 'bar-swap-circprog', 'bar-swap-txt', 'bar-swap-icon'),
+ BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`,
+ 'bar-cpu-circprog', 'bar-cpu-txt', 'bar-cpu-icon'),
+ ]
+ }),
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ self.revealChild = (!mpris);
+ }),
+ })
+ ],
+ })
+ });
+ }
+ return EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ child: Box({
+ className: 'spacing-h-4',
+ children: [
+ SystemResourcesOrCustomModule(),
+ EventBox({
+ child: BarGroup({ child: musicStuff }),
+ onPrimaryClick: () => showMusicControls.setValue(!showMusicControls.value),
+ onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
+ onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
+ setup: (self) => self.on('button-press-event', (self, event) => {
+ if (event.get_button()[1] === 8) // Side button
+ execAsync('playerctl previous').catch(print)
+ }),
+ })
+ ]
+ })
+ });
+}
diff --git a/ags/modules/bar/normal/spaceleft.js b/ags/modules/bar/normal/spaceleft.js
new file mode 100644
index 00000000..7bfb40d3
--- /dev/null
+++ b/ags/modules/bar/normal/spaceleft.js
@@ -0,0 +1,78 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Brightness from '../../../services/brightness.js';
+import Indicator from '../../../services/indicator.js';
+
+const WindowTitle = async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ return Widget.Scrollable({
+ hexpand: true, vexpand: true,
+ hscroll: 'automatic', vscroll: 'never',
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ xalign: 0,
+ truncate: 'end',
+ maxWidthChars: 1, // Doesn't matter, just needs to be non negative
+ className: 'txt-smaller bar-wintitle-topdesc txt',
+ setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
+ label.label = Hyprland.active.client.class.length === 0 ? 'Desktop' : Hyprland.active.client.class;
+ }),
+ }),
+ Widget.Label({
+ xalign: 0,
+ truncate: 'end',
+ maxWidthChars: 1, // Doesn't matter, just needs to be non negative
+ className: 'txt-smallie bar-wintitle-txt',
+ setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
+ label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
+ }),
+ })
+ ]
+ })
+ });
+ } catch {
+ return null;
+ }
+}
+
+
+export default async (monitor = 0) => {
+ const optionalWindowTitleInstance = await WindowTitle();
+ return Widget.EventBox({
+ onScrollUp: () => {
+ Indicator.popup(1); // Since the brightness and speaker are both on the same window
+ Brightness[monitor].screen_value += 0.05;
+ },
+ onScrollDown: () => {
+ Indicator.popup(1); // Since the brightness and speaker are both on the same window
+ Brightness[monitor].screen_value -= 0.05;
+ },
+ onPrimaryClick: () => {
+ App.toggleWindow('sideleft');
+ },
+ child: Widget.Box({
+ homogeneous: false,
+ children: [
+ Widget.Box({ className: 'bar-corner-spacing' }),
+ Widget.Overlay({
+ overlays: [
+ Widget.Box({ hexpand: true }),
+ Widget.Box({
+ className: 'bar-sidemodule', hexpand: true,
+ children: [Widget.Box({
+ vertical: true,
+ className: 'bar-space-button',
+ children: [
+ optionalWindowTitleInstance,
+ ]
+ })]
+ }),
+ ]
+ })
+ ]
+ })
+ });
+}
diff --git a/ags/modules/bar/normal/spaceright.js b/ags/modules/bar/normal/spaceright.js
new file mode 100644
index 00000000..62666389
--- /dev/null
+++ b/ags/modules/bar/normal/spaceright.js
@@ -0,0 +1,91 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
+const { execAsync } = Utils;
+import Indicator from '../../../services/indicator.js';
+import { StatusIcons } from '../../.commonwidgets/statusicons.js';
+import { Tray } from "./tray.js";
+
+const SeparatorDot = () => Widget.Revealer({
+ transition: 'slide_left',
+ revealChild: false,
+ attribute: {
+ 'count': SystemTray.items.length,
+ 'update': (self, diff) => {
+ self.attribute.count += diff;
+ self.revealChild = (self.attribute.count > 0);
+ }
+ },
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'separator-circle',
+ }),
+ setup: (self) => self
+ .hook(SystemTray, (self) => self.attribute.update(self, 1), 'added')
+ .hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
+ ,
+});
+
+export default (monitor = 0) => {
+ const barTray = Tray();
+ const barStatusIcons = StatusIcons({
+ className: 'bar-statusicons',
+ setup: (self) => self.hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideright') {
+ self.toggleClassName('bar-statusicons-active', visible);
+ }
+ }),
+ }, monitor);
+ const SpaceRightDefaultClicks = (child) => Widget.EventBox({
+ onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
+ onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
+ onPrimaryClick: () => App.toggleWindow('sideright'),
+ onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
+ onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
+ setup: (self) => self.on('button-press-event', (self, event) => {
+ if (event.get_button()[1] === 8)
+ execAsync('playerctl previous').catch(print)
+ }),
+ child: child,
+ });
+ const emptyArea = SpaceRightDefaultClicks(Widget.Box({ hexpand: true, }));
+ const indicatorArea = SpaceRightDefaultClicks(Widget.Box({
+ children: [
+ SeparatorDot(),
+ barStatusIcons
+ ],
+ }));
+ const actualContent = Widget.Box({
+ hexpand: true,
+ className: 'spacing-h-5 bar-spaceright',
+ children: [
+ emptyArea,
+ barTray,
+ indicatorArea
+ ],
+ });
+
+ return Widget.EventBox({
+ onScrollUp: () => {
+ if (!Audio.speaker) return;
+ if (Audio.speaker.volume <= 0.09) Audio.speaker.volume += 0.01;
+ else Audio.speaker.volume += 0.03;
+ Indicator.popup(1);
+ },
+ onScrollDown: () => {
+ if (!Audio.speaker) return;
+ if (Audio.speaker.volume <= 0.09) Audio.speaker.volume -= 0.01;
+ else Audio.speaker.volume -= 0.03;
+ Indicator.popup(1);
+ },
+ child: Widget.Box({
+ children: [
+ actualContent,
+ SpaceRightDefaultClicks(Widget.Box({ className: 'bar-corner-spacing' })),
+ ]
+ })
+ });
+}
\ No newline at end of file
diff --git a/ags/modules/bar/normal/system.js b/ags/modules/bar/normal/system.js
new file mode 100644
index 00000000..82a5bc38
--- /dev/null
+++ b/ags/modules/bar/normal/system.js
@@ -0,0 +1,236 @@
+// This is for the right pills of the bar.
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
+const { exec, execAsync } = Utils;
+const { GLib } = imports.gi;
+import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
+import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../../.commondata/weather.js';
+
+const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`;
+Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`);
+
+const BarBatteryProgress = () => {
+ const _updateProgress = (circprog) => { // Set circular progress value
+ circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
+
+ circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= userOptions.battery.low);
+ circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
+ }
+ return AnimatedCircProg({
+ className: 'bar-batt-circprog',
+ vpack: 'center', hpack: 'center',
+ extraSetup: (self) => self
+ .hook(Battery, _updateProgress)
+ ,
+ })
+}
+
+const time = Variable('', {
+ poll: [
+ userOptions.time.interval,
+ () => GLib.DateTime.new_now_local().format(userOptions.time.format),
+ ],
+})
+
+const date = Variable('', {
+ poll: [
+ userOptions.time.dateInterval,
+ () => GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
+ ],
+})
+
+const BarClock = () => Widget.Box({
+ vpack: 'center',
+ className: 'spacing-h-4 bar-clock-box',
+ children: [
+ Widget.Label({
+ className: 'bar-time',
+ label: time.bind(),
+ }),
+ Widget.Label({
+ className: 'txt-norm txt-onLayer1',
+ label: '•',
+ }),
+ Widget.Label({
+ className: 'txt-smallie bar-date',
+ label: date.bind(),
+ }),
+ ],
+});
+
+const UtilButton = ({ name, icon, onClicked }) => Button({
+ vpack: 'center',
+ tooltipText: name,
+ onClicked: onClicked,
+ className: 'bar-util-btn icon-material txt-norm',
+ label: `${icon}`,
+})
+
+const Utilities = () => Box({
+ hpack: 'center',
+ className: 'spacing-h-4',
+ children: [
+ UtilButton({
+ name: 'Screen snip', icon: 'screenshot_region', onClicked: () => {
+ Utils.execAsync(`${App.configDir}/scripts/grimblast.sh copy area`)
+ .catch(print)
+ }
+ }),
+ UtilButton({
+ name: 'Color picker', icon: 'colorize', onClicked: () => {
+ Utils.execAsync(['hyprpicker', '-a']).catch(print)
+ }
+ }),
+ UtilButton({
+ name: 'Toggle on-screen keyboard', icon: 'keyboard', onClicked: () => {
+ toggleWindowOnAllMonitors('osk');
+ }
+ }),
+ ]
+})
+
+const BarBattery = () => Box({
+ className: 'spacing-h-4 bar-batt-txt',
+ children: [
+ Revealer({
+ transitionDuration: userOptions.animations.durationSmall,
+ revealChild: false,
+ transition: 'slide_right',
+ child: MaterialIcon('bolt', 'norm', { tooltipText: "Charging" }),
+ setup: (self) => self.hook(Battery, revealer => {
+ self.revealChild = Battery.charging;
+ }),
+ }),
+ Label({
+ className: 'txt-smallie',
+ setup: (self) => self.hook(Battery, label => {
+ label.label = `${Number.parseFloat(Battery.percent.toFixed(1))}%`;
+ }),
+ }),
+ Overlay({
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'bar-batt',
+ homogeneous: true,
+ children: [
+ MaterialIcon('battery_full', 'small'),
+ ],
+ setup: (self) => self.hook(Battery, box => {
+ box.toggleClassName('bar-batt-low', Battery.percent <= userOptions.battery.low);
+ box.toggleClassName('bar-batt-full', Battery.charged);
+ }),
+ }),
+ overlays: [
+ BarBatteryProgress(),
+ ]
+ }),
+ ]
+});
+
+const BarGroup = ({ child }) => Widget.Box({
+ className: 'bar-group-margin bar-sides',
+ children: [
+ Widget.Box({
+ className: 'bar-group bar-group-standalone bar-group-pad-system',
+ children: [child],
+ }),
+ ]
+});
+const BatteryModule = () => Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: {
+ 'laptop': Box({
+ className: 'spacing-h-4', children: [
+ BarGroup({ child: Utilities() }),
+ BarGroup({ child: BarBattery() }),
+ ]
+ }),
+ 'desktop': BarGroup({
+ child: Box({
+ hexpand: true,
+ hpack: 'center',
+ className: 'spacing-h-4 txt-onSurfaceVariant',
+ children: [
+ MaterialIcon('device_thermostat', 'small'),
+ Label({
+ label: 'Weather',
+ })
+ ],
+ setup: (self) => self.poll(900000, async (self) => {
+ const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt';
+ const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city.replace(/ /g, '%20')}?format=j1`)
+ .then(output => {
+ const weather = JSON.parse(output);
+ Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH)
+ .catch(print);
+ const weatherCode = weather.current_condition[0].weatherCode;
+ const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
+ const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
+ const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
+ const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
+ self.children[0].label = weatherSymbol;
+ self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit} • Feels like ${feelsLike}°${userOptions.weather.preferredUnit}`;
+ self.tooltipText = weatherDesc;
+ }).catch((err) => {
+ try { // Read from cache
+ const weather = JSON.parse(
+ Utils.readFile(WEATHER_CACHE_PATH)
+ );
+ const weatherCode = weather.current_condition[0].weatherCode;
+ const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
+ const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
+ const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
+ const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
+ self.children[0].label = weatherSymbol;
+ self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit} • Feels like ${feelsLike}°${userOptions.weather.preferredUnit}`;
+ self.tooltipText = weatherDesc;
+ } catch (err) {
+ print(err);
+ }
+ });
+ if (userOptions.weather.city != '' && userOptions.weather.city != null) {
+ updateWeatherForCity(userOptions.weather.city.replace(/ /g, '%20'));
+ }
+ else {
+ Utils.execAsync('curl ipinfo.io')
+ .then(output => {
+ return JSON.parse(output)['city'].toLowerCase();
+ })
+ .then(updateWeatherForCity)
+ .catch(print)
+ }
+ }),
+ })
+ }),
+ },
+ setup: (stack) => Utils.timeout(10, () => {
+ if (!Battery.available) stack.shown = 'desktop';
+ else stack.shown = 'laptop';
+ })
+})
+
+const switchToRelativeWorkspace = async (self, num) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.messageAsync(`dispatch workspace ${num > 0 ? '+' : ''}${num}`).catch(print);
+ } catch {
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+ }
+}
+
+export default () => Widget.EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onPrimaryClick: () => App.toggleWindow('sideright'),
+ child: Widget.Box({
+ className: 'spacing-h-4',
+ children: [
+ BarGroup({ child: BarClock() }),
+ BatteryModule(),
+ ]
+ })
+});
diff --git a/ags/modules/bar/normal/tray.js b/ags/modules/bar/normal/tray.js
new file mode 100644
index 00000000..d568bc76
--- /dev/null
+++ b/ags/modules/bar/normal/tray.js
@@ -0,0 +1,36 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
+const { Box, Icon, Button, Revealer } = Widget;
+const { Gravity } = imports.gi.Gdk;
+
+const SysTrayItem = (item) => item.id !== null ? Button({
+ className: 'bar-systray-item',
+ child: Icon({ hpack: 'center' }).bind('icon', item, 'icon'),
+ setup: (self) => self
+ .hook(item, (self) => self.tooltipMarkup = item['tooltip-markup'])
+ ,
+ onPrimaryClick: (_, event) => item.activate(event),
+ onSecondaryClick: (btn, event) => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
+}) : null;
+
+export const Tray = (props = {}) => {
+ const trayContent = Box({
+ className: 'margin-right-5 spacing-h-15',
+ setup: (self) => self
+ .hook(SystemTray, (self) => {
+ self.children = SystemTray.items.map(SysTrayItem);
+ self.show_all();
+ })
+ ,
+ });
+ const trayRevealer = Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: trayContent,
+ });
+ return Box({
+ ...props,
+ children: [trayRevealer],
+ });
+}
diff --git a/ags/modules/bar/normal/workspaces_hyprland.js b/ags/modules/bar/normal/workspaces_hyprland.js
new file mode 100644
index 00000000..2f9b373a
--- /dev/null
+++ b/ags/modules/bar/normal/workspaces_hyprland.js
@@ -0,0 +1,224 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, DrawingArea, EventBox } = Widget;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
+
+const mix = (value1, value2, perc) => {
+ return value1 * perc + value2 * (1 - perc);
+}
+
+const getFontWeightName = (weight) => {
+ switch (weight) {
+ case Pango.Weight.ULTRA_LIGHT:
+ return 'UltraLight';
+ case Pango.Weight.LIGHT:
+ return 'Light';
+ case Pango.Weight.NORMAL:
+ return 'Normal';
+ case Pango.Weight.BOLD:
+ return 'Bold';
+ case Pango.Weight.ULTRA_BOLD:
+ return 'UltraBold';
+ case Pango.Weight.HEAVY:
+ return 'Heavy';
+ default:
+ return 'Normal';
+ }
+}
+
+// Font size = workspace id
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ className: 'bar-ws-container',
+ attribute: {
+ initialized: false,
+ workspaceMask: 0,
+ workspaceGroup: 0,
+ updateMask: (self) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
+ // if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Hyprland.workspaces;
+ let workspaceMask = 0;
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
+ if (workspaces[i].windows > 0)
+ workspaceMask |= (1 << (ws.id - offset));
+ }
+ // console.log('Mask:', workspaceMask.toString(2));
+ self.attribute.workspaceMask = workspaceMask;
+ // self.attribute.initialized = true;
+ self.queue_draw();
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ self.queue_draw();
+ },
+ },
+ setup: (area) => area
+ .hook(Hyprland.active.workspace, (self) => {
+ self.setCss(`font-size: ${(Hyprland.active.workspace.id - 1) % count + 1}px;`);
+ const previousGroup = self.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
+ if (currentGroup !== previousGroup) {
+ self.attribute.updateMask(self);
+ self.attribute.workspaceGroup = currentGroup;
+ }
+ })
+ .hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
+
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
+ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
+ const workspaceFontWeight = workspaceStyleContext.get_property('font-weight', Gtk.StateFlags.NORMAL);
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ area.set_size_request(workspaceDiameter * count, -1);
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
+ const activeWsCenterY = height / 2;
+
+ // Font
+ const layout = PangoCairo.create_layout(cr);
+ const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${getFontWeightName(workspaceFontWeight)} ${workspaceFontSize}`);
+ layout.set_font_description(fontDesc);
+ cr.setAntialias(Cairo.Antialias.BEST);
+ // Get kinda min radius for number indicators
+ layout.set_text("0".repeat(count.toString().length), -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.15; // smaller than sqrt(2)*radius
+ const indicatorGap = workspaceRadius - indicatorRadius;
+
+ for (let i = 1; i <= count; i++) {
+ if (area.attribute.workspaceMask & (1 << i)) {
+ // Draw bg highlight
+ cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
+ const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
+ const wsCenterY = height / 2;
+ if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ }
+ }
+
+ // Draw active ws
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
+ cr.fill();
+
+ // Draw workspace numbers
+ for (let i = 1; i <= count; i++) {
+ const inactivecolors = area.attribute.workspaceMask & (1 << i) ? occupiedfg : wsfg;
+ if (i == activeWs) {
+ cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
+ }
+ // Moving to
+ else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id < activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id > activeWs)) {
+ cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
+ }
+ // Moving from
+ else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id > activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id < activeWs)) {
+ cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
+ }
+ // Inactive
+ else
+ cr.setSourceRGBA(inactivecolors.red, inactivecolors.green, inactivecolors.blue, inactivecolors.alpha);
+
+ layout.set_text(`${i + offset}`, -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
+ const y = (height - layoutHeight) / 2;
+ cr.moveTo(x, y);
+ PangoCairo.show_layout(cr, layout);
+ cr.stroke();
+ }
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: () => Hyprland.messageAsync(`dispatch workspace -1`).catch(print),
+ onScrollDown: () => Hyprland.messageAsync(`dispatch workspace +1`).catch(print),
+ onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
+ onSecondaryClick: () => App.toggleWindow('overview'),
+ attribute: {
+ clicked: false,
+ ws_group: 0,
+ },
+ child: Box({
+ homogeneous: true,
+ className: 'bar-group-margin',
+ children: [Box({
+ className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [WorkspaceContents(userOptions.workspaces.shown)],
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
+ .catch(print);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (event.get_button()[1] === 1) {
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
+ .catch(print);
+ }
+ else if (event.get_button()[1] === 8) {
+ Hyprland.messageAsync(`dispatch togglespecialworkspace`).catch(print);
+ }
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+})
diff --git a/ags/modules/bar/normal/workspaces_sway.js b/ags/modules/bar/normal/workspaces_sway.js
new file mode 100644
index 00000000..02fed9fa
--- /dev/null
+++ b/ags/modules/bar/normal/workspaces_sway.js
@@ -0,0 +1,183 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import Widget from "resource:///com/github/Aylur/ags/widget.js";
+import Sway from "../../../services/sway.js";
+import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
+const { execAsync, exec } = Utils;
+const { Box, DrawingArea, EventBox } = Widget;
+
+const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
+
+const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
+const switchToRelativeWorkspace = (self, num) =>
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
+ attribute: {
+ initialized: false,
+ workspaceMask: 0,
+ updateMask: (self) => {
+ if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Sway.workspaces;
+ let workspaceMask = 0;
+ // console.log('----------------')
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ // console.log(ws.name, ',', ws.num);
+ if (!Number(ws.name)) return;
+ const id = Number(ws.name);
+ if (id <= 0) continue; // Ignore scratchpads
+ if (id > count) return; // Not rendered
+ if (workspaces[i].windows > 0) {
+ workspaceMask |= (1 << id);
+ }
+ }
+ self.attribute.workspaceMask = workspaceMask;
+ self.attribute.initialized = true;
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ },
+ },
+ setup: (area) => area
+ .hook(Sway.active.workspace, (area) => {
+ area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
+ })
+ .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
+ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ area.set_size_request(workspaceDiameter * count, -1);
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
+ const activeWsCenterY = height / 2;
+
+ // Font
+ const layout = PangoCairo.create_layout(cr);
+ const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
+ layout.set_font_description(fontDesc);
+ cr.setAntialias(Cairo.Antialias.BEST);
+ // Get kinda min radius for number indicators
+ layout.set_text("0".repeat(count.toString().length), -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
+ const indicatorGap = workspaceRadius - indicatorRadius;
+
+ // Draw workspace numbers
+ for (let i = 1; i <= count; i++) {
+ if (area.attribute.workspaceMask & (1 << i)) {
+ // Draw bg highlight
+ cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
+ const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
+ const wsCenterY = height / 2;
+ if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+
+ // Set color for text
+ cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
+ }
+ else
+ cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
+ layout.set_text(`${i}`, -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
+ const y = (height - layoutHeight) / 2;
+ cr.moveTo(x, y);
+ // cr.showText(text);
+ PangoCairo.show_layout(cr, layout);
+ cr.stroke();
+ }
+
+ // Draw active ws
+ // base
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
+ cr.fill();
+ // inner decor
+ cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
+ cr.fill();
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
+ onSecondaryClick: () => App.toggleWindow('overview'),
+ attribute: { clicked: false },
+ child: Box({
+ homogeneous: true,
+ className: 'bar-group-margin',
+ children: [Box({
+ className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [
+ WorkspaceContents(10),
+ ]
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+});
diff --git a/ags/modules/cheatsheet/data_keybinds.js b/ags/modules/cheatsheet/data_keybinds.js
new file mode 100644
index 00000000..5631c498
--- /dev/null
+++ b/ags/modules/cheatsheet/data_keybinds.js
@@ -0,0 +1,122 @@
+export const keybindList = [[
+ {
+ "icon": "pin_drop",
+ "name": "Workspaces: navigation",
+ "binds": [
+ { "keys": ["", "+", "#"], "action": "Go to workspace #" },
+ { "keys": ["", "+", "S"], "action": "Toggle special workspace" },
+ { "keys": ["", "+", "(Scroll ↑↓)"], "action": "Go to workspace -1/+1" },
+ { "keys": ["Ctrl", "", "+", "←"], "action": "Go to workspace on the left" },
+ { "keys": ["Ctrl", "", "+", "→"], "action": "Go to workspace on the right" },
+ { "keys": ["", "+", "PageUp"], "action": "Go to workspace on the left" },
+ { "keys": ["", "+", "PageDown"], "action": "Go to workspace on the right" }
+ ],
+ "id": 1
+ },
+ {
+ "icon": "overview_key",
+ "name": "Workspaces: management",
+ "binds": [
+ { "keys": ["", "Alt", "+", "#"], "action": "Move window to workspace #" },
+ { "keys": ["", "Alt", "+", "S"], "action": "Move window to special workspace" },
+ { "keys": ["", "Alt", "+", "PageUp"], "action": "Move window to workspace on the left" },
+ { "keys": ["", "Alt", "+", "PageDown"], "action": "Move window to workspace on the right" }
+ ],
+ "id": 2
+ },
+ {
+ "icon": "move_group",
+ "name": "Windows",
+ "binds": [
+ { "keys": ["", "+", "←↑→↓"], "action": "Focus window in direction" },
+ { "keys": ["", "Shift", "+", "←↑→↓"], "action": "Swap window in direction" },
+ { "keys": ["", "+", ";"], "action": "Split ratio -" },
+ { "keys": ["", "+", "'"], "action": "Split ratio +" },
+ { "keys": ["", "+", "Lmb"], "action": "Move window" },
+ { "keys": ["", "+", "Rmb"], "action": "Resize window" },
+ { "keys": ["", "Alt", "+", "Space"], "action": "Float window" },
+ { "keys": ["", "+", "F"], "action": "Fullscreen" },
+ { "keys": ["", "Alt", "+", "F"], "action": "Fake fullscreen" }
+ ],
+ "id": 3
+ }
+],
+[
+ {
+ "icon": "widgets",
+ "name": "Widgets (AGS)",
+ "binds": [
+ { "keys": ["", "OR", "", "+", "Tab"], "action": "Toggle overview/launcher" },
+ { "keys": ["Ctrl", "", "+", "R"], "action": "Restart AGS" },
+ { "keys": ["", "+", "/"], "action": "Toggle this cheatsheet" },
+ { "keys": ["", "+", "N"], "action": "Toggle system sidebar" },
+ { "keys": ["", "+", "B", "OR", "", "+", "O"], "action": "Toggle utilities sidebar" },
+ { "keys": ["", "+", "K"], "action": "Toggle virtual keyboard" },
+ { "keys": ["Ctrl", "Alt", "+", "Del"], "action": "Power/Session menu" },
+
+ { "keys": ["Esc"], "action": "Exit a window" },
+ { "keys": ["rightCtrl"], "action": "Dismiss/close sidebar" },
+
+ { "keys": ["Ctrl", "", "+", "T"], "action": "Change wallpaper+colorscheme" },
+
+ // { "keys": ["", "+", "B"], "action": "Toggle left sidebar" },
+ // { "keys": ["", "+", "N"], "action": "Toggle right sidebar" },
+ // { "keys": ["", "+", "G"], "action": "Toggle volume mixer" },
+ // { "keys": ["", "+", "M"], "action": "Toggle useless audio visualizer" },
+ // { "keys": ["(right)Ctrl"], "action": "Dismiss notification & close menus" }
+ ],
+ "id": 4
+ },
+ {
+ "icon": "construction",
+ "name": "Utilities",
+ "binds": [
+ { "keys": ["PrtSc"], "action": "Screenshot >> clipboard" },
+ { "keys": ["Ctrl", "PrtSc"], "action": "Screenshot >> file + clipboard" },
+ { "keys": ["", "Shift", "+", "S"], "action": "Screen snip >> clipboard" },
+ { "keys": ["", "Shift", "+", "T"], "action": "Image to text >> clipboard" },
+ { "keys": ["", "Shift", "+", "C"], "action": "Color picker" },
+ { "keys": ["", "Alt", "+", "R"], "action": "Record region" },
+ { "keys": ["Ctrl", "Alt", "+", "R"], "action": "Record region with sound" },
+ { "keys": ["", "Shift", "Alt", "+", "R"], "action": "Record screen with sound" }
+ ],
+ "id": 5
+ },
+],
+[
+ {
+ "icon": "apps",
+ "name": "Apps",
+ "binds": [
+ { "keys": ["", "+", "T"], "action": "Launch terminal: foot" },
+ { "keys": ["", "+", "W"], "action": "Launch browser: Firefox" },
+ { "keys": ["", "+", "C"], "action": "Launch editor: vscode" },
+ { "keys": ["", "+", "X"], "action": "Launch editor: GNOME Text Editor" },
+ { "keys": ["", "+", "I"], "action": "Launch settings: GNOME Control center" }
+ ],
+ "id": 6
+ },
+ {
+ "icon": "keyboard",
+ "name": "Typing",
+ "binds": [
+ { "keys": ["", "+", "V"], "action": "Clipboard history >> clipboard" },
+ { "keys": ["", "+", "."], "action": "Emoji picker >> clipboard" },
+ ],
+ "id": 7
+ },
+ {
+ "icon": "terminal",
+ "name": "Launcher actions",
+ "binds": [
+ { "keys": [">raw"], "action": "Toggle mouse acceleration" },
+ { "keys": [">img"], "action": "Select wallpaper and generate colorscheme" },
+ { "keys": [">light"], "action": "Switch to light theme" },
+ { "keys": [">dark"], "action": "Switch to dark theme" },
+ { "keys": [">badapple"], "action": "Apply black n' white colorscheme" },
+ { "keys": [">color"], "action": "Pick acccent color" },
+ { "keys": [">todo"], "action": "Type something after that to add a To-do item" },
+ ],
+ "id": 8
+ }
+]];
diff --git a/ags/modules/cheatsheet/data_periodictable.js b/ags/modules/cheatsheet/data_periodictable.js
new file mode 100644
index 00000000..83121a23
--- /dev/null
+++ b/ags/modules/cheatsheet/data_periodictable.js
@@ -0,0 +1,195 @@
+export const periodicTable = [
+ [
+ { name: 'Hydrogen', symbol: 'H', number: 1, weight: 1.01, type: 'nonmetal' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: 'Helium', symbol: 'He', number: 2, weight: 4.00, type: 'noblegas' },
+ ],
+ [
+ { name: 'Lithium', symbol: 'Li', number: 3, weight: 6.94, type: 'metal' },
+ { name: 'Beryllium', symbol: 'Be', number: 4, weight: 9.01, type: 'metal' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: 'Boron', symbol: 'B', number: 5, weight: 10.81, type: 'nonmetal' },
+ { name: 'Carbon', symbol: 'C', number: 6, weight: 12.01, type: 'nonmetal' },
+ { name: 'Nitrogen', symbol: 'N', number: 7, weight: 14.01, type: 'nonmetal' },
+ { name: 'Oxygen', symbol: 'O', number: 8, weight: 16, type: 'nonmetal' },
+ { name: 'Fluorine', symbol: 'F', number: 9, weight: 19, type: 'nonmetal' },
+ { name: 'Neon', symbol: 'Ne', number: 10, weight: 20.18, type: 'noblegas' },
+
+
+ ],
+ [
+ { name: 'Sodium', symbol: 'Na', number: 11, weight: 22.99, type: 'metal' },
+ { name: 'Magnesium', symbol: 'Mg', number: 12, weight: 24.31, type: 'metal' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: 'Aluminum', symbol: 'Al', number: 13, weight: 26.98, type: 'metal' },
+ { name: 'Silicon', symbol: 'Si', number: 14, weight: 28.09, type: 'nonmetal' },
+ { name: 'Phosphorus', symbol: 'P', number: 15, weight: 30.97, type: 'nonmetal' },
+ { name: 'Sulfur', symbol: 'S', number: 16, weight: 32.07, type: 'nonmetal' },
+ { name: 'Chlorine', symbol: 'Cl', number: 17, weight: 35.45, type: 'nonmetal' },
+ { name: 'Argon', symbol: 'Ar', number: 18, weight: 39.95, type: 'noblegas' },
+ ],
+ [
+ { name: 'Kalium', symbol: 'K', number: 19, weight: 39.098, type: 'metal' },
+ { name: 'Calcium', symbol: 'Ca', number: 20, weight: 40.078, type: 'metal' },
+ { name: 'Scandium', symbol: 'Sc', number: 21, weight: 44.956, type: 'metal' },
+ { name: 'Titanium', symbol: 'Ti', number: 22, weight: 47.87, type: 'metal' },
+ { name: 'Vanadium', symbol: 'V', number: 23, weight: 50.94, type: 'metal' },
+ { name: 'Chromium', symbol: 'Cr', number: 24, weight: 52, type: 'metal', icon: 'chromium-browser' },
+ { name: 'Manganese', symbol: 'Mn', number: 25, weight: 54.94, type: 'metal' },
+ { name: 'Iron', symbol: 'Fe', number: 26, weight: 55.85, type: 'metal' },
+ { name: 'Cobalt', symbol: 'Co', number: 27, weight: 58.93, type: 'metal' },
+ { name: 'Nickel', symbol: 'Ni', number: 28, weight: 58.69, type: 'metal' },
+ { name: 'Copper', symbol: 'Cu', number: 29, weight: 63.55, type: 'metal' },
+ { name: 'Zinc', symbol: 'Zn', number: 30, weight: 65.38, type: 'metal' },
+ { name: 'Gallium', symbol: 'Ga', number: 31, weight: 69.72, type: 'metal' },
+ { name: 'Germanium', symbol: 'Ge', number: 32, weight: 72.63, type: 'metal' },
+ { name: 'Arsenic', symbol: 'As', number: 33, weight: 74.92, type: 'nonmetal' },
+ { name: 'Selenium', symbol: 'Se', number: 34, weight: 78.96, type: 'nonmetal' },
+ { name: 'Bromine', symbol: 'Br', number: 35, weight: 79.904, type: 'nonmetal' },
+ { name: 'Krypton', symbol: 'Kr', number: 36, weight: 83.8, type: 'noblegas' },
+ ],
+ [
+ { name: 'Rubidium', symbol: 'Rb', number: 37, weight: 85.47, type: 'metal' },
+ { name: 'Strontium', symbol: 'Sr', number: 38, weight: 87.62, type: 'metal' },
+ { name: 'Yttrium', symbol: 'Y', number: 39, weight: 88.91, type: 'metal' },
+ { name: 'Zirconium', symbol: 'Zr', number: 40, weight: 91.22, type: 'metal' },
+ { name: 'Niobium', symbol: 'Nb', number: 41, weight: 92.91, type: 'metal' },
+ { name: 'Molybdenum', symbol: 'Mo', number: 42, weight: 95.94, type: 'metal' },
+ { name: 'Technetium', symbol: 'Tc', number: 43, weight: 98, type: 'metal' },
+ { name: 'Ruthenium', symbol: 'Ru', number: 44, weight: 101.07, type: 'metal' },
+ { name: 'Rhodium', symbol: 'Rh', number: 45, weight: 102.91, type: 'metal' },
+ { name: 'Palladium', symbol: 'Pd', number: 46, weight: 106.42, type: 'metal' },
+ { name: 'Silver', symbol: 'Ag', number: 47, weight: 107.87, type: 'metal' },
+ { name: 'Cadmium', symbol: 'Cd', number: 48, weight: 112.41, type: 'metal' },
+ { name: 'Indium', symbol: 'In', number: 49, weight: 114.82, type: 'metal' },
+ { name: 'Tin', symbol: 'Sn', number: 50, weight: 118.71, type: 'metal' },
+ { name: 'Antimony', symbol: 'Sb', number: 51, weight: 121.76, type: 'metal' },
+ { name: 'Tellurium', symbol: 'Te', number: 52, weight: 127.6, type: 'nonmetal' },
+ { name: 'Iodine', symbol: 'I', number: 53, weight: 126.9, type: 'nonmetal' },
+ { name: 'Xenon', symbol: 'Xe', number: 54, weight: 131.29, type: 'noblegas' },
+ ],
+ [
+ { name: 'Cesium', symbol: 'Cs', number: 55, weight: 132.91, type: 'metal' },
+ { name: 'Barium', symbol: 'Ba', number: 56, weight: 137.33, type: 'metal' },
+ { name: 'Lanthanum', symbol: 'La', number: 57, weight: 138.91, type: 'lanthanum' },
+ { name: 'Hafnium', symbol: 'Hf', number: 72, weight: 178.49, type: 'metal' },
+ { name: 'Tantalum', symbol: 'Ta', number: 73, weight: 180.95, type: 'metal' },
+ { name: 'Tungsten', symbol: 'W', number: 74, weight: 183.84, type: 'metal' },
+ { name: 'Rhenium', symbol: 'Re', number: 75, weight: 186.21, type: 'metal' },
+ { name: 'Osmium', symbol: 'Os', number: 76, weight: 190.23, type: 'metal' },
+ { name: 'Iridium', symbol: 'Ir', number: 77, weight: 192.22, type: 'metal' },
+ { name: 'Platinum', symbol: 'Pt', number: 78, weight: 195.09, type: 'metal' },
+ { name: 'Gold', symbol: 'Au', number: 79, weight: 196.97, type: 'metal' },
+ { name: 'Mercury', symbol: 'Hg', number: 80, weight: 200.59, type: 'metal' },
+ { name: 'Thallium', symbol: 'Tl', number: 81, weight: 204.38, type: 'metal' },
+ { name: 'Lead', symbol: 'Pb', number: 82, weight: 207.2, type: 'metal' },
+ { name: 'Bismuth', symbol: 'Bi', number: 83, weight: 208.98, type: 'metal' },
+ { name: 'Polonium', symbol: 'Po', number: 84, weight: 209, type: 'metal' },
+ { name: 'Astatine', symbol: 'At', number: 85, weight: 210, type: 'nonmetal' },
+ { name: 'Radon', symbol: 'Rn', number: 86, weight: 222, type: 'noblegas' },
+ ],
+ [
+ { name: 'Francium', symbol: 'Fr', number: 87, weight: 223, type: 'metal' },
+ { name: 'Radium', symbol: 'Ra', number: 88, weight: 226, type: 'metal' },
+ { name: 'Actinium', symbol: 'Ac', number: 89, weight: 227, type: 'actinium' },
+ { name: 'Rutherfordium', symbol: 'Rf', number: 104, weight: 267, type: 'metal' },
+ { name: 'Dubnium', symbol: 'Db', number: 105, weight: 268, type: 'metal' },
+ { name: 'Seaborgium', symbol: 'Sg', number: 106, weight: 271, type: 'metal' },
+ { name: 'Bohrium', symbol: 'Bh', number: 107, weight: 272, type: 'metal' },
+ { name: 'Hassium', symbol: 'Hs', number: 108, weight: 277, type: 'metal' },
+ { name: 'Meitnerium', symbol: 'Mt', number: 109, weight: 278, type: 'metal' },
+ { name: 'Darmstadtium', symbol: 'Ds', number: 110, weight: 281, type: 'metal' },
+ { name: 'Roentgenium', symbol: 'Rg', number: 111, weight: 280, type: 'metal' },
+ { name: 'Copernicium', symbol: 'Cn', number: 112, weight: 285, type: 'metal' },
+ { name: 'Nihonium', symbol: 'Nh', number: 113, weight: 286, type: 'metal' },
+ { name: 'Flerovium', symbol: 'Fl', number: 114, weight: 289, type: 'metal' },
+ { name: 'Moscovium', symbol: 'Mc', number: 115, weight: 290, type: 'metal' },
+ { name: 'Livermorium', symbol: 'Lv', number: 116, weight: 293, type: 'metal' },
+ { name: 'Tennessine', symbol: 'Ts', number: 117, weight: 294, type: 'metal' },
+ { name: 'Oganesson', symbol: 'Og', number: 118, weight: 294, type: 'noblegas' },
+ ],
+]
+
+export const series = [
+ [
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: 'Cerium', symbol: 'Ce', number: 58, weight: 140.12, type: 'lanthanum' },
+ { name: 'Praseodymium', symbol: 'Pr', number: 59, weight: 140.91, type: 'lanthanum' },
+ { name: 'Neodymium', symbol: 'Nd', number: 60, weight: 144.24, type: 'lanthanum' },
+ { name: 'Promethium', symbol: 'Pm', number: 61, weight: 145, type: 'lanthanum' },
+ { name: 'Samarium', symbol: 'Sm', number: 62, weight: 150.36, type: 'lanthanum' },
+ { name: 'Europium', symbol: 'Eu', number: 63, weight: 151.96, type: 'lanthanum' },
+ { name: 'Gadolinium', symbol: 'Gd', number: 64, weight: 157.25, type: 'lanthanum' },
+ { name: 'Terbium', symbol: 'Tb', number: 65, weight: 158.93, type: 'lanthanum' },
+ { name: 'Dysprosium', symbol: 'Dy', number: 66, weight: 162.5, type: 'lanthanum' },
+ { name: 'Holmium', symbol: 'Ho', number: 67, weight: 164.93, type: 'lanthanum' },
+ { name: 'Erbium', symbol: 'Er', number: 68, weight: 167.26, type: 'lanthanum' },
+ { name: 'Thulium', symbol: 'Tm', number: 69, weight: 168.93, type: 'lanthanum' },
+ { name: 'Ytterbium', symbol: 'Yb', number: 70, weight: 173.04, type: 'lanthanum' },
+ { name: 'Lutetium', symbol: 'Lu', number: 71, weight: 174.97, type: 'lanthanum' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ ],
+ [
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ { name: 'Thorium', symbol: 'Th', number: 90, weight: 232.04, type: 'actinium' },
+ { name: 'Protactinium', symbol: 'Pa', number: 91, weight: 231.04, type: 'actinium' },
+ { name: 'Uranium', symbol: 'U', number: 92, weight: 238.03, type: 'actinium' },
+ { name: 'Neptunium', symbol: 'Np', number: 93, weight: 237, type: 'actinium' },
+ { name: 'Plutonium', symbol: 'Pu', number: 94, weight: 244, type: 'actinium' },
+ { name: 'Americium', symbol: 'Am', number: 95, weight: 243, type: 'actinium' },
+ { name: 'Curium', symbol: 'Cm', number: 96, weight: 247, type: 'actinium' },
+ { name: 'Berkelium', symbol: 'Bk', number: 97, weight: 247, type: 'actinium' },
+ { name: 'Californium', symbol: 'Cf', number: 98, weight: 251, type: 'actinium' },
+ { name: 'Einsteinium', symbol: 'Es', number: 99, weight: 252, type: 'actinium' },
+ { name: 'Fermium', symbol: 'Fm', number: 100, weight: 257, type: 'actinium' },
+ { name: 'Mendelevium', symbol: 'Md', number: 101, weight: 258, type: 'actinium' },
+ { name: 'Nobelium', symbol: 'No', number: 102, weight: 259, type: 'actinium' },
+ { name: 'Lawrencium', symbol: 'Lr', number: 103, weight: 262, type: 'actinium' },
+ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
+ ],
+];
+
+export const niceTypes = {
+ 'metal': "Metal",
+ 'nonmetal': "Nonmetal",
+ 'noblegas': "Noble gas",
+ 'lanthanum': "Lanthanum",
+ 'actinium': "Actinium"
+}
diff --git a/ags/modules/cheatsheet/keybinds.js b/ags/modules/cheatsheet/keybinds.js
new file mode 100644
index 00000000..09cd100a
--- /dev/null
+++ b/ags/modules/cheatsheet/keybinds.js
@@ -0,0 +1,126 @@
+const { GLib, Gtk } = imports.gi;
+import App from "resource:///com/github/Aylur/ags/app.js";
+import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
+import Widget from "resource:///com/github/Aylur/ags/widget.js";
+import { IconTabContainer } from "../.commonwidgets/tabcontainer.js";
+const { Box, Label, Scrollable } = Widget;
+
+const HYPRLAND_KEYBIND_CONFIG_FILE = userOptions.cheatsheet.keybinds.configPath ?
+ userOptions.cheatsheet.keybinds.configPath : `${GLib.get_user_config_dir()}/hypr/hyprland/keybinds.conf`;
+const KEYBIND_SECTIONS_PER_PAGE = 3;
+const getKeybindList = () => {
+ let data = Utils.exec(`${App.configDir}/scripts/hyprland/get_keybinds.py --path ${HYPRLAND_KEYBIND_CONFIG_FILE}`);
+ if (data == "\"error\"") {
+ Utils.timeout(2000, () => Utils.execAsync(['notify-send',
+ 'Update path to keybinds',
+ 'Keybinds hyprland config file not found. Check your user options.',
+ '-a', 'ags',
+ ]).catch(print))
+ return { children: [] };
+ }
+ return JSON.parse(data);
+};
+const keybindList = getKeybindList();
+
+const keySubstitutions = {
+ "Super": "",
+ "mouse_up": "Scroll ↓", // ikr, weird
+ "mouse_down": "Scroll ↑", // trust me bro
+ "mouse:272": "LMB",
+ "mouse:273": "RMB",
+ "mouse:275": "MouseBack",
+ "Slash": "/",
+ "Hash": "#"
+}
+
+const substituteKey = (key) => {
+ return keySubstitutions[key] || key;
+}
+
+const Keybind = (keybindData, type) => { // type: either "keys" or "actions"
+ const Key = (key) => Label({ // Specific keys
+ vpack: 'center',
+ className: `${['OR', '+'].includes(key) ? 'cheatsheet-key-notkey' : 'cheatsheet-key'} txt-small`,
+ label: substituteKey(key),
+ });
+ const Action = (text) => Label({ // Binds
+ xalign: 0,
+ label: text,
+ className: "txt txt-small cheatsheet-action",
+ })
+ return Widget.Box({
+ className: "spacing-h-10 cheatsheet-bind-lineheight",
+ children: type == "keys" ? [
+ ...(keybindData.mods.length > 0 ? [
+ ...keybindData.mods.map(Key),
+ Key("+"),
+ ] : []),
+ Key(keybindData.key),
+ ] : [Action(keybindData.comment)],
+ })
+}
+
+const Section = (sectionData, scope) => {
+ const keys = Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: sectionData.keybinds.map((data) => Keybind(data, "keys"))
+ })
+ const actions = Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: sectionData.keybinds.map((data) => Keybind(data, "actions"))
+ })
+ const name = Label({
+ xalign: 0,
+ className: "cheatsheet-category-title txt margin-bottom-10",
+ label: sectionData.name,
+ })
+ const binds = Box({
+ className: 'spacing-h-10',
+ children: [
+ keys,
+ actions,
+ ]
+ })
+ const childrenSections = Box({
+ vertical: true,
+ className: 'spacing-v-15',
+ children: sectionData.children.map((data) => Section(data, scope + 1))
+ })
+ return Box({
+ vertical: true,
+ children: [
+ ...((sectionData.name && sectionData.name.length > 0) ? [name] : []),
+ Box({
+ className: 'spacing-v-10',
+ children: [
+ binds,
+ childrenSections,
+ ]
+ })
+ ]
+ })
+};
+
+export default () => {
+ const numOfTabs = Math.ceil(keybindList.children.length / KEYBIND_SECTIONS_PER_PAGE);
+ const keybindPages = Array.from({ length: numOfTabs }, (_, i) => ({
+ iconWidget: Label({
+ className: "txt txt-small",
+ label: `${i + 1}`,
+ }),
+ name: `${i + 1}`,
+ child: Box({
+ className: 'spacing-h-30',
+ children: keybindList.children.slice(
+ KEYBIND_SECTIONS_PER_PAGE * i, 0 + KEYBIND_SECTIONS_PER_PAGE * (i + 1),
+ ).map(data => Section(data, 1)),
+ }),
+ }));
+ return IconTabContainer({
+ iconWidgets: keybindPages.map((kbp) => kbp.iconWidget),
+ names: keybindPages.map((kbp) => kbp.name),
+ children: keybindPages.map((kbp) => kbp.child),
+ });
+};
diff --git a/ags/modules/cheatsheet/main.js b/ags/modules/cheatsheet/main.js
new file mode 100644
index 00000000..65fe7c92
--- /dev/null
+++ b/ags/modules/cheatsheet/main.js
@@ -0,0 +1,146 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { setupCursorHover } from "../.widgetutils/cursorhover.js";
+import PopupWindow from '../.widgethacks/popupwindow.js';
+import Keybinds from "./keybinds.js";
+import PeriodicTable from "./periodictable.js";
+import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
+import { checkKeybind } from '../.widgetutils/keybind.js';
+import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
+
+const cheatsheets = [
+ {
+ name: 'Keybinds',
+ materialIcon: 'keyboard',
+ contentWidget: Keybinds,
+ },
+ {
+ name: 'Periodic table',
+ materialIcon: 'experiment',
+ contentWidget: PeriodicTable,
+ },
+];
+
+const CheatsheetHeader = () => Widget.CenterBox({
+ vertical: false,
+ startWidget: Widget.Box({}),
+ centerWidget: Widget.Box({
+ vertical: true,
+ className: "spacing-h-15",
+ children: [
+ Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-5 cheatsheet-title',
+ children: [
+ Widget.Label({
+ hpack: 'center',
+ css: 'margin-right: 0.682rem;',
+ className: 'txt-title',
+ label: 'Cheat sheet',
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key txt-small",
+ label: "",
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key-notkey txt-small",
+ label: "+",
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key txt-small",
+ label: "/",
+ })
+ ]
+ }),
+ ]
+ }),
+ endWidget: Widget.Button({
+ vpack: 'start',
+ hpack: 'end',
+ className: "cheatsheet-closebtn icon-material txt txt-hugeass",
+ onClicked: () => {
+ closeWindowOnAllMonitors('cheatsheet');
+ },
+ child: Widget.Label({
+ className: 'icon-material txt txt-hugeass',
+ label: 'close'
+ }),
+ setup: setupCursorHover,
+ }),
+});
+
+const sheetContents = [];
+const SheetContent = (id) => {
+ sheetContents[id] = ExpandingIconTabContainer({
+ tabsHpack: 'center',
+ tabSwitcherClassName: 'sidebar-icontabswitcher',
+ transitionDuration: userOptions.animations.durationLarge * 1.4,
+ icons: cheatsheets.map((api) => api.materialIcon),
+ names: cheatsheets.map((api) => api.name),
+ children: cheatsheets.map((api) => api.contentWidget()),
+ onChange: (self, id) => {
+ self.shown = cheatsheets[id].name;
+ }
+ });
+ return sheetContents[id];
+}
+
+export default (id) => {
+ const sheets = SheetContent(id);
+ const widgetContent = Widget.Box({
+ vertical: true,
+ className: "cheatsheet-bg spacing-v-5",
+ children: [
+ CheatsheetHeader(),
+ sheets,
+ ]
+ });
+ return PopupWindow({
+ monitor: id,
+ name: `cheatsheet${id}`,
+ layer: 'overlay',
+ keymode: 'on-demand',
+ visible: false,
+ anchor: ['top', 'bottom', 'left', 'right'],
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ clickCloseRegion({ name: 'cheatsheet' }),
+ Widget.Box({
+ children: [
+ clickCloseRegion({ name: 'cheatsheet' }),
+ widgetContent,
+ clickCloseRegion({ name: 'cheatsheet' }),
+ ]
+ }),
+ clickCloseRegion({ name: 'cheatsheet' }),
+ ],
+ setup: (self) => self.on('key-press-event', (widget, event) => { // Typing
+ // Whole sheet
+ if (checkKeybind(event, userOptions.keybinds.cheatsheet.nextTab))
+ sheetContents.forEach(tab => tab.nextTab())
+ else if (checkKeybind(event, userOptions.keybinds.cheatsheet.prevTab))
+ sheetContents.forEach(tab => tab.prevTab())
+ else if (checkKeybind(event, userOptions.keybinds.cheatsheet.cycleTab))
+ sheetContents.forEach(tab => tab.cycleTab())
+ // Keybinds
+ if (sheets.attribute.names[sheets.attribute.shown.value] == 'Keybinds') { // If Keybinds tab is focused
+ if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.nextTab)) {
+ sheetContents.forEach((sheet) => {
+ const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
+ toSwitchTab.nextTab();
+ })
+ }
+ else if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.prevTab)) {
+ sheetContents.forEach((sheet) => {
+ const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
+ toSwitchTab.prevTab();
+ })
+ }
+ }
+ })
+ })
+ });
+}
\ No newline at end of file
diff --git a/ags/modules/cheatsheet/periodictable.js b/ags/modules/cheatsheet/periodictable.js
new file mode 100644
index 00000000..92a85b9d
--- /dev/null
+++ b/ags/modules/cheatsheet/periodictable.js
@@ -0,0 +1,94 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { niceTypes, periodicTable, series } from "./data_periodictable.js";
+const { Box, Button, Icon, Label, Revealer } = Widget;
+
+export default () => {
+ const ElementTile = (element) => {
+ return Box({
+ vertical: true,
+ tooltipText: element.electronConfig ? `${element.electronConfig}` : null,
+ className: `cheatsheet-periodictable-${element.type}`,
+ children: element.name == '' ? null : [
+ Box({
+ className: 'padding-left-8 padding-right-8 padding-top-8',
+ children: [
+ Label({
+ label: `${element.number}`,
+ className: "cheatsheet-periodictable-elementnum txt-tiny txt-bold",
+ }),
+ Box({ hexpand: true }),
+ Label({
+ label: `${element.weight}`,
+ className: "txt-smaller",
+ })
+ ]
+ }),
+ element.icon ? Icon({
+ icon: element.icon,
+ className: "txt-hugerass txt-bold",
+ }) : Label({
+ label: `${element.symbol}`,
+ className: "cheatsheet-periodictable-elementsymbol",
+ }),
+ Label({
+ label: `${element.name}`,
+ className: "txt-tiny",
+ })
+ ]
+ })
+ }
+ const BoardColor = (type) => Box({
+ className: 'spacing-h-5',
+ children: [
+ Box({
+ homogeneous: true,
+ className: `cheatsheet-periodictable-legend-color-wrapper`,
+ children: [Box({
+ className: `cheatsheet-periodictable-legend-color-${type}`,
+ })]
+ }),
+ Label({
+ label: `${niceTypes[type]}`,
+ className: "txt txt-small",
+ })
+ ]
+ })
+ const mainBoard = Box({
+ hpack: 'center',
+ vertical: true,
+ className: "spacing-v-3",
+ children: periodicTable.map((row, _) => Box({ // Rows
+ className: "spacing-h-5",
+ children: row.map((element, _) => ElementTile(element))
+ })),
+ });
+ const seriesBoard = Box({
+ hpack: 'center',
+ vertical: true,
+ className: "spacing-v-3",
+ children: series.map((row, _) => Box({ // Rows
+ className: "spacing-h-5",
+ children: row.map((element, _) => ElementTile(element))
+ })),
+ });
+ const legend = Box({
+ hpack: 'center',
+ className: 'spacing-h-20',
+ children: [
+ BoardColor('metal'),
+ BoardColor('nonmetal'),
+ BoardColor('noblegas'),
+ BoardColor('lanthanum'),
+ BoardColor('actinium'),
+ ]
+ })
+ return Box({
+ vertical: true,
+ className: 'spacing-v-20',
+ children: [
+ mainBoard,
+ seriesBoard,
+ legend
+ ]
+ })
+}
\ No newline at end of file
diff --git a/ags/modules/crosshair/main.js b/ags/modules/crosshair/main.js
new file mode 100644
index 00000000..390a9715
--- /dev/null
+++ b/ags/modules/crosshair/main.js
@@ -0,0 +1,22 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { enableClickthrough } from "../.widgetutils/clickthrough.js";
+import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
+
+export default (monitor = 0, ) => {
+ return Widget.Window({
+ monitor,
+ name: `crosshair${monitor}`,
+ layer: 'overlay',
+ exclusivity: 'ignore',
+ visible: false,
+ child: Widget.Icon({
+ icon: 'crosshair-symbolic',
+ css: `
+ font-size: ${userOptions.gaming.crosshair.size}px;
+ color: ${userOptions.gaming.crosshair.color};
+ `,
+ }),
+ setup: enableClickthrough,
+ });
+}
+
diff --git a/ags/modules/desktopbackground/data_quicklaunches.js b/ags/modules/desktopbackground/data_quicklaunches.js
new file mode 100644
index 00000000..a5a9562f
--- /dev/null
+++ b/ags/modules/desktopbackground/data_quicklaunches.js
@@ -0,0 +1,14 @@
+export const quickLaunchItems = [
+ {
+ "name": "GitHub + Files×2",
+ "command": "github-desktop & nautilus --new-window & nautilus --new-window &"
+ },
+ {
+ "name": "Terminal×2",
+ "command": "foot & foot &"
+ },
+ {
+ "name": "Discord + Youtube + Github",
+ "command": "xdg-open 'https://discord.com/app' && xdg-open 'https://youtube.com/' && xdg-open 'https://github.com/' &"
+ },
+]
\ No newline at end of file
diff --git a/ags/modules/desktopbackground/main.js b/ags/modules/desktopbackground/main.js
new file mode 100644
index 00000000..9600e151
--- /dev/null
+++ b/ags/modules/desktopbackground/main.js
@@ -0,0 +1,24 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+import WallpaperImage from './wallpaper.js';
+import TimeAndLaunchesWidget from './timeandlaunches.js'
+import SystemWidget from './system.js'
+
+export default (monitor) => Widget.Window({
+ name: `desktopbackground${monitor}`,
+ // anchor: ['top', 'bottom', 'left', 'right'],
+ layer: 'background',
+ exclusivity: 'ignore',
+ visible: true,
+ child: Widget.Overlay({
+ child: WallpaperImage(monitor),
+ // child: Widget.Box({}),
+ overlays: [
+ TimeAndLaunchesWidget(),
+ SystemWidget(),
+ ],
+ setup: (self) => {
+ self.set_overlay_pass_through(self.get_children()[1], true);
+ },
+ }),
+});
diff --git a/ags/modules/desktopbackground/system.js b/ags/modules/desktopbackground/system.js
new file mode 100644
index 00000000..a65402b7
--- /dev/null
+++ b/ags/modules/desktopbackground/system.js
@@ -0,0 +1,161 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, EventBox, Label, Revealer, Overlay } = Widget;
+import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+
+const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props = {}) => Box({
+ ...props,
+ className: 'bg-system-bg txt',
+ children: [
+ Revealer({
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ vpack: 'center',
+ vertical: true,
+ className: 'margin-right-15',
+ children: [
+ Label({
+ xalign: 1,
+ className: 'txt-small txt',
+ label: `${name}`,
+ }),
+ Label({
+ xalign: 1,
+ className: 'titlefont txt-norm txt-onSecondaryContainer',
+ setup: (self) => self
+ .poll(interval, (label) => displayFunc(label))
+ ,
+ })
+ ]
+ })
+ }),
+ Overlay({
+ child: AnimatedCircProg({
+ className: 'bg-system-circprog',
+ extraSetup: (self) => self
+ .poll(interval, (self) => {
+ execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
+ self.css = `font-size: ${Math.round(newValue)}px;`
+ }).catch(print);
+ })
+ ,
+ }),
+ overlays: [
+ MaterialIcon(`${icon}`, 'hugeass'),
+ ],
+ setup: self => self.set_overlay_pass_through(self.get_children()[1], true),
+ }),
+ ]
+})
+
+const resources = Box({
+ vpack: 'fill',
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ ResourceValue('Memory', 'memory', 10000, `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
+ (label) => {
+ execAsync(['bash', '-c', `free -h | awk '/^Mem/ {print $3 " / " $2}' | sed 's/Gi/Gib/g'`])
+ .then((output) => {
+ label.label = `${output}`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ResourceValue('Swap', 'swap_horiz', 10000, `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
+ (label) => {
+ execAsync(['bash', '-c', `free -h | awk '/^Swap/ {if ($2 != "0") print $3 " / " $2; else print "No swap"}' | sed 's/Gi/Gib/g'`])
+ .then((output) => {
+ label.label = `${output}`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ResourceValue('Disk space', 'hard_drive_2', 3600000, `echo $(df --output=pcent / | tr -dc '0-9')`,
+ (label) => {
+ execAsync(['bash', '-c', `df -h --output=avail / | awk 'NR==2{print $1}'`])
+ .then((output) => {
+ label.label = `${output} available`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ]
+});
+
+const distroAndVersion = Box({
+ vertical: true,
+ children: [
+ Box({
+ hpack: 'end',
+ children: [
+ Label({
+ className: 'bg-distro-txt',
+ xalign: 0,
+ label: 'Hyping on ',
+ }),
+ Label({
+ className: 'bg-distro-name',
+ xalign: 0,
+ label: '',
+ setup: (label) => {
+ execAsync([`grep`, `-oP`, `PRETTY_NAME="\\K[^"]+`, `/etc/os-release`]).then(distro => {
+ label.label = distro;
+ }).catch(print);
+ },
+ }),
+ ]
+ }),
+ Box({
+ hpack: 'end',
+ children: [
+ Label({
+ className: 'bg-distro-txt',
+ xalign: 0,
+ label: 'with ',
+ }),
+ Label({
+ className: 'bg-distro-name',
+ xalign: 0,
+ label: 'An environment idk',
+ setup: (label) => {
+ // hyprctl will return unsuccessfully if Hyprland isn't running
+ execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
+ label.label = `Hyprland ${version}`;
+ }).catch(() => execAsync([`bash`, `-c`, `sway -v | cut -d'-' -f1 | sed 's/sway version /v/'`]).then(version => {
+ label.label = `Sway ${version}`;
+ }).catch(print));
+ },
+ }),
+ ]
+ })
+ ]
+})
+
+export default () => Box({
+ hpack: 'end',
+ vpack: 'end',
+ children: [
+ EventBox({
+ child: Box({
+ hpack: 'end',
+ vpack: 'end',
+ className: 'bg-distro-box spacing-v-20',
+ vertical: true,
+ children: [
+ resources,
+ distroAndVersion,
+ ]
+ }),
+ onPrimaryClickRelease: () => {
+ const kids = resources.get_children();
+ for (let i = 0; i < kids.length; i++) {
+ const child = kids[i];
+ const firstChild = child.get_children()[0];
+ firstChild.revealChild = !firstChild.revealChild;
+ }
+
+ },
+ })
+ ],
+})
+
+
+
diff --git a/ags/modules/desktopbackground/timeandlaunches.js b/ags/modules/desktopbackground/timeandlaunches.js
new file mode 100644
index 00000000..f0e0214b
--- /dev/null
+++ b/ags/modules/desktopbackground/timeandlaunches.js
@@ -0,0 +1,74 @@
+const { GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+const { execAsync, exec } = Utils;
+const { Box, Label, Button, Revealer, EventBox } = Widget;
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { quickLaunchItems } from './data_quicklaunches.js'
+
+const TimeAndDate = () => Box({
+ vertical: true,
+ className: 'spacing-v--5',
+ children: [
+ Label({
+ className: 'bg-time-clock',
+ xalign: 0,
+ label: GLib.DateTime.new_now_local().format(userOptions.time.format),
+ setup: (self) => self.poll(userOptions.time.interval, label => {
+ label.label = GLib.DateTime.new_now_local().format(userOptions.time.format);
+ }),
+ }),
+ Label({
+ className: 'bg-time-date',
+ xalign: 0,
+ label: GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
+ setup: (self) => self.poll(userOptions.time.dateInterval, (label) => {
+ label.label = GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong);
+ }),
+ }),
+ ]
+})
+
+const QuickLaunches = () => Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ xalign: 0,
+ className: 'bg-quicklaunch-title',
+ label: 'Quick Launches',
+ }),
+ Box({
+ hpack: 'start',
+ className: 'spacing-h-5',
+ children: quickLaunchItems.map((item, i) => Button({
+ onClicked: () => {
+ execAsync(['bash', '-c', `${item["command"]}`]).catch(print);
+ },
+ className: 'bg-quicklaunch-btn',
+ child: Label({
+ label: `${item["name"]}`,
+ }),
+ setup: (self) => {
+ setupCursorHover(self);
+ }
+ })),
+ })
+ ]
+})
+
+export default () => Box({
+ hpack: 'start',
+ vpack: 'end',
+ vertical: true,
+ className: 'bg-time-box spacing-h--10',
+ children: [
+ TimeAndDate(),
+ // QuickLaunches(),
+ ],
+})
+
diff --git a/ags/modules/desktopbackground/wallpaper.js b/ags/modules/desktopbackground/wallpaper.js
new file mode 100644
index 00000000..779bb230
--- /dev/null
+++ b/ags/modules/desktopbackground/wallpaper.js
@@ -0,0 +1,119 @@
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+const { Box, Button, Label, Stack } = Widget;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+import Wallpaper from '../../services/wallpaper.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { clamp } from '../.miscutils/mathfuncs.js';
+import { monitors } from '../.commondata/hyprlanddata.js';
+
+const DISABLE_AGS_WALLPAPER = true;
+
+const SWITCHWALL_SCRIPT_PATH = `${App.configDir}/scripts/color_generation/switchwall.sh`;
+const WALLPAPER_ZOOM_SCALE = 1.25; // For scrolling when we switch workspace
+const MAX_WORKSPACES = 10;
+
+export default (monitor = 0) => {
+ const WALLPAPER_OFFSCREEN_X = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].width;
+ const WALLPAPER_OFFSCREEN_Y = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].height;
+ const wallpaperImage = Widget.DrawingArea({
+ attribute: {
+ pixbuf: undefined,
+ workspace: 1,
+ sideleft: 0,
+ sideright: 0,
+ updatePos: (self) => {
+ self.setCss(`font-size: ${self.attribute.workspace - self.attribute.sideleft + self.attribute.sideright}px;`)
+ },
+ },
+ className: 'bg-wallpaper-transition',
+ setup: (self) => {
+ self.set_size_request(monitors[monitor].width, monitors[monitor].height);
+ self
+ // TODO: reduced updates using timeouts to reduce lag
+ // .hook(Hyprland.active.workspace, (self) => {
+ // self.attribute.workspace = Hyprland.active.workspace.id
+ // self.attribute.updatePos(self);
+ // })
+ // .hook(App, (box, name, visible) => { // Update on open
+ // if (self.attribute[name] === undefined) return;
+ // self.attribute[name] = (visible ? 1 : 0);
+ // self.attribute.updatePos(self);
+ // })
+ .on('draw', (self, cr) => {
+ if (!self.attribute.pixbuf) return;
+ const styleContext = self.get_style_context();
+ const workspace = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+ // Draw
+ Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
+ -(WALLPAPER_OFFSCREEN_X / (MAX_WORKSPACES + 1) * (clamp(workspace, 0, MAX_WORKSPACES + 1))),
+ -WALLPAPER_OFFSCREEN_Y / 2);
+ cr.paint();
+ })
+ .hook(Wallpaper, (self) => {
+ if (DISABLE_AGS_WALLPAPER) return;
+ const wallPath = Wallpaper.get(monitor);
+ if (!wallPath || wallPath === "") return;
+ self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file(wallPath);
+
+ const scale_x = monitors[monitor].width * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_width();
+ const scale_y = monitors[monitor].height * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_height();
+ const scale_factor = Math.max(scale_x, scale_y);
+
+ self.attribute.pixbuf = self.attribute.pixbuf.scale_simple(
+ Math.round(self.attribute.pixbuf.get_width() * scale_factor),
+ Math.round(self.attribute.pixbuf.get_height() * scale_factor),
+ GdkPixbuf.InterpType.BILINEAR
+ );
+ self.queue_draw();
+ }, 'updated');
+ ;
+ }
+ ,
+ });
+ const wallpaperPrompt = Box({
+ hpack: 'center',
+ vpack: 'center',
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ hpack: 'center',
+ justification: 'center',
+ className: 'txt-large',
+ label: `No wallpaper loaded.\nAn image ≥ ${monitors[monitor].width * WALLPAPER_ZOOM_SCALE} × ${monitors[monitor].height * WALLPAPER_ZOOM_SCALE} is recommended.`,
+ }),
+ Button({
+ hpack: 'center',
+ className: 'btn-primary',
+ label: `Select one`,
+ setup: setupCursorHover,
+ onClicked: (self) => Utils.execAsync([SWITCHWALL_SCRIPT_PATH]).catch(print),
+ }),
+ ]
+ });
+ const stack = Stack({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: {
+ 'disabled': Box({}),
+ 'image': wallpaperImage,
+ 'prompt': wallpaperPrompt,
+ },
+ setup: (self) => self
+ .hook(Wallpaper, (self) => {
+ if (DISABLE_AGS_WALLPAPER) {
+ self.shown = 'disabled';
+ return;
+ }
+ const wallPath = Wallpaper.get(monitor);
+ self.shown = ((wallPath && wallPath != "") ? 'image' : 'prompt');
+ }, 'updated')
+ ,
+ })
+ return stack;
+ // return wallpaperImage;
+}
diff --git a/ags/modules/dock/dock.js b/ags/modules/dock/dock.js
new file mode 100755
index 00000000..8e318410
--- /dev/null
+++ b/ags/modules/dock/dock.js
@@ -0,0 +1,300 @@
+const { Gtk, GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { EventBox, Button } = Widget;
+
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
+const { execAsync, exec } = Utils;
+const { Box, Revealer } = Widget;
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { getAllFiles, searchIcons } from './icons.js'
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { substitute } from '../.miscutils/icons.js';
+
+const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1)
+
+let isPinned = false
+let cachePath = new Map()
+
+let timers = []
+
+function clearTimes() {
+ timers.forEach(e => GLib.source_remove(e))
+ timers = []
+}
+
+function ExclusiveWindow(client) {
+ const fn = [
+ (client) => !(client !== null && client !== undefined),
+ // Jetbrains
+ (client) => client.title.includes("win"),
+ // Vscode
+ (client) => client.title === '' && client.class === ''
+ ]
+
+ for (const item of fn) { if (item(client)) { return true } }
+ return false
+}
+
+const focus = ({ address }) => Utils.execAsync(`hyprctl dispatch focuswindow address:${address}`).catch(print);
+
+const DockSeparator = (props = {}) => Box({
+ ...props,
+ className: 'dock-separator',
+})
+
+const PinButton = () => Widget.Button({
+ className: 'dock-app-btn dock-app-btn-animate',
+ tooltipText: 'Pin Dock',
+ child: Widget.Box({
+ homogeneous: true,
+ className: 'dock-app-icon txt',
+ child: MaterialIcon('push_pin', 'hugeass')
+ }),
+ onClicked: (self) => {
+ isPinned = !isPinned
+ self.className = `${isPinned ? "pinned-dock-app-btn" : "dock-app-btn animate"} dock-app-btn-animate`
+ },
+ setup: setupCursorHover,
+})
+
+const LauncherButton = () => Widget.Button({
+ className: 'dock-app-btn dock-app-btn-animate',
+ tooltipText: 'Open launcher',
+ child: Widget.Box({
+ homogeneous: true,
+ className: 'dock-app-icon txt',
+ child: MaterialIcon('apps', 'hugerass')
+ }),
+ onClicked: (self) => {
+ App.toggleWindow('overview');
+ },
+ setup: setupCursorHover,
+})
+
+const AppButton = ({ icon, ...rest }) => Widget.Revealer({
+ attribute: {
+ 'workspace': 0
+ },
+ revealChild: false,
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Button({
+ ...rest,
+ className: 'dock-app-btn dock-app-btn-animate',
+ child: Widget.Box({
+ child: Widget.Overlay({
+ child: Widget.Box({
+ homogeneous: true,
+ className: 'dock-app-icon',
+ child: Widget.Icon({
+ icon: icon,
+ }),
+ }),
+ overlays: [Widget.Box({
+ class_name: 'indicator',
+ vpack: 'end',
+ hpack: 'center',
+ })],
+ }),
+ }),
+ setup: (button) => {
+ setupCursorHover(button);
+ }
+ })
+});
+
+const Taskbar = (monitor) => Widget.Box({
+ className: 'dock-apps',
+ attribute: {
+ monitor: monitor,
+ 'map': new Map(),
+ 'clientSortFunc': (a, b) => {
+ return a.attribute.workspace > b.attribute.workspace;
+ },
+ 'update': (box, monitor) => {
+ for (let i = 0; i < Hyprland.clients.length; i++) {
+ const client = Hyprland.clients[i];
+ if (client["pid"] == -1) return;
+ const appClass = substitute(client.class);
+ // for (const appName of userOptions.dock.pinnedApps) {
+ // if (appClass.includes(appName.toLowerCase()))
+ // return null;
+ // }
+ let appClassLower = appClass.toLowerCase()
+ let path = ''
+ if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
+ else {
+ path = searchIcons(appClass.toLowerCase(), icon_files)
+ cachePath[appClassLower] = path
+ }
+ if (path === '') { path = substitute(appClass) }
+ const newButton = AppButton({
+ icon: path,
+ tooltipText: `${client.title} (${appClass})`,
+ onClicked: () => focus(client),
+ });
+ newButton.attribute.workspace = client.workspace.id;
+ newButton.revealChild = true;
+ box.attribute.map.set(client.address, newButton);
+ }
+ box.children = Array.from(box.attribute.map.values());
+ },
+ 'add': (box, address, monitor) => {
+ if (!address) { // First active emit is undefined
+ box.attribute.update(box);
+ return;
+ }
+ const newClient = Hyprland.clients.find(client => {
+ return client.address == address;
+ });
+ if (ExclusiveWindow(newClient)) { return }
+ let appClass = newClient.class
+ let appClassLower = appClass.toLowerCase()
+ let path = ''
+ if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
+ else {
+ path = searchIcons(appClassLower, icon_files)
+ cachePath[appClassLower] = path
+ }
+ if (path === '') { path = substitute(appClass) }
+ const newButton = AppButton({
+ icon: path,
+ tooltipText: `${newClient.title} (${appClass})`,
+ onClicked: () => focus(newClient),
+ })
+ newButton.attribute.workspace = newClient.workspace.id;
+ box.attribute.map.set(address, newButton);
+ box.children = Array.from(box.attribute.map.values());
+ newButton.revealChild = true;
+ },
+ 'remove': (box, address) => {
+ if (!address) return;
+
+ const removedButton = box.attribute.map.get(address);
+ if (!removedButton) return;
+ removedButton.revealChild = false;
+
+ Utils.timeout(userOptions.animations.durationLarge, () => {
+ removedButton.destroy();
+ box.attribute.map.delete(address);
+ box.children = Array.from(box.attribute.map.values());
+ })
+ },
+ },
+ setup: (self) => {
+ self.hook(Hyprland, (box, address) => box.attribute.add(box, address, self.monitor), 'client-added')
+ .hook(Hyprland, (box, address) => box.attribute.remove(box, address, self.monitor), 'client-removed')
+ Utils.timeout(100, () => self.attribute.update(self));
+ },
+});
+
+const PinnedApps = () => Widget.Box({
+ class_name: 'dock-apps',
+ homogeneous: true,
+ children: userOptions.dock.pinnedApps
+ .map(term => ({ app: Applications.query(term)?.[0], term }))
+ .filter(({ app }) => app)
+ .map(({ app, term = true }) => {
+ const newButton = AppButton({
+ // different icon, emm...
+ icon: userOptions.dock.searchPinnedAppIcons ?
+ searchIcons(app.name, icon_files) :
+ app.icon_name,
+ onClicked: () => {
+ for (const client of Hyprland.clients) {
+ if (client.class.toLowerCase().includes(term))
+ return focus(client);
+ }
+
+ app.launch();
+ },
+ onMiddleClick: () => app.launch(),
+ tooltipText: app.name,
+ setup: (self) => {
+ self.revealChild = true;
+ self.hook(Hyprland, button => {
+ const running = Hyprland.clients
+ .find(client => client.class.toLowerCase().includes(term)) || false;
+
+ button.toggleClassName('notrunning', !running);
+ button.toggleClassName('focused', Hyprland.active.client.address == running.address);
+ button.set_tooltip_text(running ? running.title : app.name);
+ }, 'notify::clients')
+ },
+ })
+ newButton.revealChild = true;
+ return newButton;
+ }),
+});
+
+export default (monitor = 0) => {
+ const dockContent = Box({
+ className: 'dock-bg spacing-h-5',
+ children: [
+ PinButton(),
+ PinnedApps(),
+ DockSeparator(),
+ Taskbar(),
+ LauncherButton(),
+ ]
+ })
+ const dockRevealer = Revealer({
+ attribute: {
+ 'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
+ if (userOptions.dock.monitorExclusivity)
+ self.revealChild = Hyprland.active.monitor.id === monitor;
+ else
+ self.revealChild = true;
+
+ return self.revealChild
+ }
+ },
+ revealChild: false,
+ transition: 'slide_up',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: dockContent,
+ setup: (self) => {
+ const callback = (self, trigger) => {
+ if (!userOptions.dock.trigger.includes(trigger)) return
+ const flag = self.attribute.updateShow(self)
+
+ if (flag) clearTimes();
+
+ const hidden = userOptions.dock.autoHide.find(e => e["trigger"] === trigger)
+
+ if (hidden) {
+ let id = Utils.timeout(hidden.interval, () => {
+ if (!isPinned) { self.revealChild = false }
+ timers = timers.filter(e => e !== id)
+ })
+ timers.push(id)
+ }
+ }
+
+ self
+ // .hook(Hyprland, (self) => self.attribute.updateShow(self))
+ .hook(Hyprland.active.workspace, self => callback(self, "workspace-active"))
+ .hook(Hyprland.active.client, self => callback(self, "client-active"))
+ .hook(Hyprland, self => callback(self, "client-added"), "client-added")
+ .hook(Hyprland, self => callback(self, "client-removed"), "client-removed")
+ },
+ })
+ return EventBox({
+ onHover: () => {
+ dockRevealer.revealChild = true;
+ clearTimes()
+ },
+ child: Box({
+ homogeneous: true,
+ css: `min-height: ${userOptions.dock.hiddenThickness}px;`,
+ children: [dockRevealer],
+ }),
+ setup: self => self.on("leave-notify-event", () => {
+ if (!isPinned) dockRevealer.revealChild = false;
+ clearTimes()
+ })
+ })
+}
diff --git a/ags/modules/dock/icons.js b/ags/modules/dock/icons.js
new file mode 100644
index 00000000..60f01a3a
--- /dev/null
+++ b/ags/modules/dock/icons.js
@@ -0,0 +1,63 @@
+const { Gio, GLib } = imports.gi
+
+const exists = (path) => Gio.File.new_for_path(path).query_exists(null);
+
+export const levenshteinDistance = (a, b) => {
+ if (!a.length) { return b.length }
+ if (!b.length) { return a.length }
+
+ let f = Array.from(new Array(a.length + 1),
+ () => new Array(b.length + 1).fill(0))
+
+ for (let i = 0; i <= b.length; i++) { f[0][i] = i; }
+ for (let i = 0; i <= a.length; i++) { f[i][0] = i; }
+
+ for (let i = 1; i <= a.length; i++) {
+ for (let j = 1; j <= b.length; j++) {
+ if (a.charAt(i - 1) === b.charAt(j - 1)) {
+ f[i][j] = f[i-1][j-1]
+ } else {
+ f[i][j] = Math.min(f[i-1][j-1], Math.min(f[i][j-1], f[i-1][j])) + 1
+ }
+ }
+ }
+
+ return f[a.length][b.length]
+}
+
+export const getAllFiles = (dir, files = []) => {
+ if (!exists(dir)) { return [] }
+ const file = Gio.File.new_for_path(dir);
+ const enumerator = file.enumerate_children('standard::name,standard::type',
+ Gio.FileQueryInfoFlags.NONE, null);
+
+ for (const info of enumerator) {
+ if (info.get_file_type() === Gio.FileType.DIRECTORY) {
+ files.push(getAllFiles(`${dir}/${info.get_name()}`))
+ } else {
+ files.push(`${dir}/${info.get_name()}`)
+ }
+ }
+
+ return files.flat(1);
+}
+
+export const searchIcons = (appClass, files) => {
+ appClass = appClass.toLowerCase()
+
+ if (!files.length) { return "" }
+
+ let appro = 0x3f3f3f3f
+ let path = ""
+
+ for (const item of files) {
+ let score = levenshteinDistance(item.split("/").pop().toLowerCase().split(".")[0], appClass)
+
+ if (score < appro) {
+ appro = score
+ path = item
+ }
+ }
+
+ return path
+}
\ No newline at end of file
diff --git a/ags/modules/dock/main.js b/ags/modules/dock/main.js
new file mode 100644
index 00000000..77d394d9
--- /dev/null
+++ b/ags/modules/dock/main.js
@@ -0,0 +1,12 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Dock from './dock.js';
+
+export default (monitor = 0) => Widget.Window({
+ monitor,
+ name: `dock${monitor}`,
+ layer: userOptions.dock.layer,
+ anchor: ['bottom'],
+ exclusivity: 'normal',
+ visible: true,
+ child: Dock(monitor),
+});
diff --git a/ags/modules/indicators/colorscheme.js b/ags/modules/indicators/colorscheme.js
new file mode 100644
index 00000000..b91375b5
--- /dev/null
+++ b/ags/modules/indicators/colorscheme.js
@@ -0,0 +1,264 @@
+const { Gio, GLib } = imports.gi;
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { ConfigToggle, ConfigMulipleSelection } from '../.commonwidgets/configwidgets.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync } = Utils;
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { showColorScheme } from '../../variables.js';
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { darkMode } from '../.miscutils/system.js';
+
+const ColorBox = ({
+ name = 'Color',
+ ...rest
+}) => Widget.Box({
+ ...rest,
+ homogeneous: true,
+ children: [
+ Widget.Label({
+ label: `${name}`,
+ })
+ ]
+})
+
+const ColorSchemeSettingsRevealer = () => {
+ const headerButtonIcon = MaterialIcon('expand_more', 'norm');
+ const header = Widget.Button({
+ className: 'osd-settings-btn-arrow',
+ onClicked: () => {
+ content.revealChild = !content.revealChild;
+ headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
+ },
+ setup: setupCursorHover,
+ hpack: 'end',
+ child: headerButtonIcon,
+ });
+ const content = Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_down',
+ transitionDuration: 200,
+ child: ColorSchemeSettings(),
+ setup: (self) => self.hook(isHoveredColorschemeSettings, (revealer) => {
+ if (isHoveredColorschemeSettings.value == false) {
+ setTimeout(() => {
+ if (isHoveredColorschemeSettings.value == false)
+ revealer.revealChild = false;
+ headerButtonIcon.label = 'expand_more';
+ }, 1500);
+ }
+ }),
+ });
+ return Widget.EventBox({
+ onHover: (self) => {
+ isHoveredColorschemeSettings.setValue(true);
+ },
+ onHoverLost: (self) => {
+ isHoveredColorschemeSettings.setValue(false);
+ },
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ header,
+ content,
+ ]
+ }),
+ });
+}
+
+function calculateSchemeInitIndex(optionsArr, searchValue = 'vibrant') {
+ if (searchValue == '')
+ searchValue = 'vibrant';
+ const flatArray = optionsArr.flatMap(subArray => subArray);
+ const result = flatArray.findIndex(element => element.value === searchValue);
+ const rowIndex = Math.floor(result / optionsArr[0].length);
+ const columnIndex = result % optionsArr[0].length;
+ return [rowIndex, columnIndex];
+}
+
+const schemeOptionsArr = [
+ [
+ { name: 'Tonal Spot', value: 'tonalspot' },
+ { name: 'Fruit Salad', value: 'fruitsalad' },
+ { name: 'Fidelity', value: 'fidelity' },
+ { name: 'Rainbow', value: 'rainbow' },
+ ],
+ [
+ { name: 'Neutral', value: 'neutral' },
+ { name: 'Monochrome', value: 'monochrome' },
+ { name: 'Expressive', value: 'expressive' },
+ { name: 'Vibrant', value: 'vibrant' },
+ ],
+ [
+ { name: 'Vibrant+', value: 'morevibrant' },
+ ],
+ //[
+ // { name: 'Content', value: 'content' },
+ //]
+];
+
+const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
+const initTransparency = Utils.exec(`bash -c "sed -n \'2p\' ${LIGHTDARK_FILE_LOCATION}"`);
+const initTransparencyVal = (initTransparency == "transparent") ? 1 : 0;
+const initScheme = Utils.exec(`bash -c "sed -n \'3p\' ${LIGHTDARK_FILE_LOCATION}"`);
+const initSchemeIndex = calculateSchemeInitIndex(schemeOptionsArr, initScheme);
+
+const ColorSchemeSettings = () => Widget.Box({
+ className: 'osd-colorscheme-settings spacing-v-5 margin-20',
+ vertical: true,
+ vpack: 'center',
+ children: [
+ Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ xalign: 0,
+ className: 'txt-norm titlefont txt',
+ label: 'Options',
+ hpack: 'center',
+ }),
+ //////////////////
+ ConfigToggle({
+ icon: 'dark_mode',
+ name: 'Dark Mode',
+ desc: 'Ya should go to sleep!',
+ initValue: darkMode.value,
+ onChange: (_, newValue) => {
+ darkMode.value = !!newValue;
+ },
+ extraSetup: (self) => self.hook(darkMode, (self) => {
+ self.enabled.value = darkMode.value;
+ }),
+ }),
+ ConfigToggle({
+ icon: 'border_clear',
+ name: 'Transparency',
+ desc: 'Make shell elements transparent',
+ initValue: initTransparencyVal,
+ onChange: (self, newValue) => {
+ let transparency = newValue == 0 ? "opaque" : "transparent";
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ },
+ }),
+ Widget.Box({
+ tooltipText: 'Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)',
+ className: 'txt spacing-h-5 configtoggle-box',
+ children: [
+ MaterialIcon('imagesearch_roller', 'norm'),
+ Widget.Label({
+ className: 'txt txt-small',
+ label: 'Use Gradience',
+ }),
+ Widget.Box({ hexpand: true }),
+ ConfigMulipleSelection({
+ hpack: 'center',
+ vpack: 'center',
+ optionsArr: [
+ [{ name: 'Off', value: 0 }, { name: 'On', value: 1 }],
+ ],
+ initIndex: [-1, -1],
+ onChange: (value, name) => {
+ const ADWAITA_BLUE = "#3584E4";
+ if (value) execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --yes-gradience`, `&`])
+ .catch(print);
+ else execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${ADWAITA_BLUE}" --no-gradience`, `&`])
+ .catch(print);
+
+ },
+ }),
+ ]
+ }),
+ ]
+ }),
+ Widget.Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ Widget.Label({
+ xalign: 0,
+ className: 'txt-norm titlefont txt margin-top-5',
+ label: 'Scheme styles',
+ hpack: 'center',
+ }),
+ //////////////////
+ ConfigMulipleSelection({
+ hpack: 'center',
+ vpack: 'center',
+ optionsArr: schemeOptionsArr,
+ initIndex: initSchemeIndex,
+ onChange: (value, name) => {
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/${value}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ },
+ }),
+ ]
+ })
+ ]
+});
+
+const ColorschemeContent = () => Widget.Box({
+ className: 'osd-colorscheme spacing-v-5',
+ vertical: true,
+ hpack: 'center',
+ children: [
+ Widget.Label({
+ xalign: 0,
+ className: 'txt-norm titlefont txt',
+ label: 'Color scheme',
+ hpack: 'center',
+ }),
+ Widget.Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ ColorBox({ name: 'P', className: 'osd-color osd-color-primary' }),
+ ColorBox({ name: 'S', className: 'osd-color osd-color-secondary' }),
+ ColorBox({ name: 'T', className: 'osd-color osd-color-tertiary' }),
+ ColorBox({ name: 'Sf', className: 'osd-color osd-color-surface' }),
+ ColorBox({ name: 'Sf-i', className: 'osd-color osd-color-inverseSurface' }),
+ ColorBox({ name: 'E', className: 'osd-color osd-color-error' }),
+ ]
+ }),
+ Widget.Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ ColorBox({ name: 'P-c', className: 'osd-color osd-color-primaryContainer' }),
+ ColorBox({ name: 'S-c', className: 'osd-color osd-color-secondaryContainer' }),
+ ColorBox({ name: 'T-c', className: 'osd-color osd-color-tertiaryContainer' }),
+ ColorBox({ name: 'Sf-c', className: 'osd-color osd-color-surfaceContainer' }),
+ ColorBox({ name: 'Sf-v', className: 'osd-color osd-color-surfaceVariant' }),
+ ColorBox({ name: 'E-c', className: 'osd-color osd-color-errorContainer' }),
+ ]
+ }),
+ ColorSchemeSettingsRevealer(),
+ ]
+});
+
+const isHoveredColorschemeSettings = Variable(false);
+
+export default () => Widget.Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: ColorschemeContent(),
+ setup: (self) => {
+ self
+ .hook(showColorScheme, (revealer) => {
+ if (showColorScheme.value == true)
+ revealer.revealChild = true;
+ else
+ revealer.revealChild = isHoveredColorschemeSettings.value;
+ })
+ .hook(isHoveredColorschemeSettings, (revealer) => {
+ if (isHoveredColorschemeSettings.value == false) {
+ setTimeout(() => {
+ if (isHoveredColorschemeSettings.value == false)
+ revealer.revealChild = showColorScheme.value;
+ }, 2000);
+ }
+ })
+ },
+})
diff --git a/ags/modules/indicators/indicatorvalues.js b/ags/modules/indicators/indicatorvalues.js
new file mode 100644
index 00000000..4fde189b
--- /dev/null
+++ b/ags/modules/indicators/indicatorvalues.js
@@ -0,0 +1,124 @@
+// This file is for brightness/volume indicators
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+const { Box, Label, ProgressBar } = Widget;
+import { MarginRevealer } from '../.widgethacks/advancedrevealers.js';
+import Brightness from '../../services/brightness.js';
+import Indicator from '../../services/indicator.js';
+
+const OsdValue = ({
+ name, nameSetup = undefined, labelSetup, progressSetup,
+ extraClassName = '', extraProgressClassName = '',
+ ...rest
+}) => {
+ const valueName = Label({
+ xalign: 0, yalign: 0, hexpand: true,
+ className: 'osd-label',
+ label: `${name}`,
+ setup: nameSetup,
+ });
+ const valueNumber = Label({
+ hexpand: false, className: 'osd-value-txt',
+ setup: labelSetup,
+ });
+ return Box({ // Volume
+ vertical: true,
+ hexpand: true,
+ className: `osd-bg osd-value ${extraClassName}`,
+ attribute: {
+ 'disable': () => {
+ valueNumber.label = '';
+ }
+ },
+ children: [
+ Box({
+ vexpand: true,
+ children: [
+ valueName,
+ valueNumber,
+ ]
+ }),
+ ProgressBar({
+ className: `osd-progress ${extraProgressClassName}`,
+ hexpand: true,
+ vertical: false,
+ setup: progressSetup,
+ })
+ ],
+ ...rest,
+ });
+}
+
+export default (monitor = 0) => {
+ const brightnessIndicator = OsdValue({
+ name: 'Brightness',
+ extraClassName: 'osd-brightness',
+ extraProgressClassName: 'osd-brightness-progress',
+ labelSetup: (self) => self.hook(Brightness[monitor], self => {
+ self.label = `${Math.round(Brightness[monitor].screen_value * 100)}`;
+ }, 'notify::screen-value'),
+ progressSetup: (self) => self.hook(Brightness[monitor], (progress) => {
+ const updateValue = Brightness[monitor].screen_value;
+ if (updateValue !== progress.value) Indicator.popup(1);
+ progress.value = updateValue;
+ }, 'notify::screen-value'),
+ });
+
+ const volumeIndicator = OsdValue({
+ name: 'Volume',
+ extraClassName: 'osd-volume',
+ extraProgressClassName: 'osd-volume-progress',
+ attribute: { headphones: undefined , device: undefined},
+ nameSetup: (self) => Utils.timeout(1, () => {
+ const updateAudioDevice = (self) => {
+ const usingHeadphones = (Audio.speaker?.stream?.port)?.toLowerCase().includes('headphone');
+ if (volumeIndicator.attribute.headphones === undefined ||
+ volumeIndicator.attribute.headphones !== usingHeadphones) {
+ volumeIndicator.attribute.headphones = usingHeadphones;
+ self.label = usingHeadphones ? 'Headphones' : 'Speakers';
+ // Indicator.popup(1);
+ }
+ }
+ self.hook(Audio, updateAudioDevice);
+ Utils.timeout(1000, updateAudioDevice);
+ }),
+ labelSetup: (self) => self.hook(Audio, (label) => {
+ const newDevice = (Audio.speaker?.name);
+ const updateValue = Math.round(Audio.speaker?.volume * 100);
+ if (!isNaN(updateValue)) {
+ if (newDevice === volumeIndicator.attribute.device && updateValue != label.label) {
+ Indicator.popup(1);
+ }
+ }
+ volumeIndicator.attribute.device = newDevice;
+ label.label = `${updateValue}`;
+ }),
+ progressSetup: (self) => self.hook(Audio, (progress) => {
+ const updateValue = Audio.speaker?.volume;
+ if (!isNaN(updateValue)) {
+ if (updateValue > 1) progress.value = 1;
+ else progress.value = updateValue;
+ }
+ }),
+ });
+ return MarginRevealer({
+ transition: 'slide_down',
+ showClass: 'osd-show',
+ hideClass: 'osd-hide',
+ extraSetup: (self) => self
+ .hook(Indicator, (revealer, value) => {
+ if (value > -1) revealer.attribute.show();
+ else revealer.attribute.hide();
+ }, 'popup')
+ ,
+ child: Box({
+ hpack: 'center',
+ vertical: false,
+ className: 'spacing-h--10',
+ children: [
+ brightnessIndicator,
+ volumeIndicator,
+ ]
+ })
+ });
+}
diff --git a/ags/modules/indicators/main.js b/ags/modules/indicators/main.js
new file mode 100644
index 00000000..16741937
--- /dev/null
+++ b/ags/modules/indicators/main.js
@@ -0,0 +1,32 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Indicator from '../../services/indicator.js';
+import IndicatorValues from './indicatorvalues.js';
+import MusicControls from './musiccontrols.js';
+import ColorScheme from './colorscheme.js';
+import NotificationPopups from './notificationpopups.js';
+
+export default (monitor = 0) => Widget.Window({
+ name: `indicator${monitor}`,
+ monitor,
+ className: 'indicator',
+ layer: 'overlay',
+ // exclusivity: 'ignore',
+ visible: true,
+ anchor: ['top'],
+ child: Widget.EventBox({
+ onHover: () => { //make the widget hide when hovering
+ Indicator.popup(-1);
+ },
+ child: Widget.Box({
+ vertical: true,
+ className: 'osd-window',
+ css: 'min-height: 2px;',
+ children: [
+ IndicatorValues(monitor),
+ MusicControls(),
+ NotificationPopups(),
+ ColorScheme(),
+ ]
+ })
+ }),
+});
diff --git a/ags/modules/indicators/musiccontrols.js b/ags/modules/indicators/musiccontrols.js
new file mode 100644
index 00000000..6384cf9f
--- /dev/null
+++ b/ags/modules/indicators/musiccontrols.js
@@ -0,0 +1,408 @@
+const { GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
+const { exec, execAsync } = Utils;
+const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
+
+import { fileExists } from '../.miscutils/files.js';
+import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
+import { showMusicControls } from '../../variables.js';
+import { darkMode, hasPlasmaIntegration } from '../.miscutils/system.js';
+
+const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
+const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
+const colorMode = Utils.exec(`bash -c "sed -n \'1p\' '${LIGHTDARK_FILE_LOCATION}'"`);
+const lightDark = (colorMode == "light") ? '-l' : '';
+const COVER_COLORSCHEME_SUFFIX = '_colorscheme.css';
+var lastCoverPath = '';
+
+function isRealPlayer(player) {
+ return (
+ // Remove unecessary native buses from browsers if there's plasma integration
+ !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
+ !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
+ // playerctld just copies other buses and we don't need duplicates
+ !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') &&
+ // Non-instance mpd bus
+ !(player.busName.endsWith('.mpd') && !player.busName.endsWith('MediaPlayer2.mpd'))
+ );
+}
+
+export const getPlayer = (name = userOptions.music.preferredPlayer) => Mpris.getPlayer(name) || Mpris.players[0] || null;
+function lengthStr(length) {
+ const min = Math.floor(length / 60);
+ const sec = Math.floor(length % 60);
+ const sec0 = sec < 10 ? '0' : '';
+ return `${min}:${sec0}${sec}`;
+}
+
+function detectMediaSource(link) {
+ if (link.startsWith("file://")) {
+ if (link.includes('firefox-mpris'))
+ return ' Firefox'
+ return " File";
+ }
+ let url = link.replace(/(^\w+:|^)\/\//, '');
+ let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
+ if (domain == 'ytimg.com') return ' Youtube';
+ if (domain == 'discordapp.net') return ' Discord';
+ if (domain == 'sndcdn.com') return ' SoundCloud';
+ return domain;
+}
+
+const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
+function getTrackfont(player) {
+ const title = player.trackTitle;
+ const artists = player.trackArtists.join(' ');
+ if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
+ return 'Chakra Petch'; // Rigid square replacement
+ if (title.includes('東方'))
+ return 'Crimson Text, serif'; // Serif for Touhou stuff
+ return DEFAULT_MUSIC_FONT;
+}
+function trimTrackTitle(title) {
+ if (!title) return '';
+ const cleanPatterns = [
+ /【[^】]*】/, // Touhou n weeb stuff
+ " [FREE DOWNLOAD]", // F-777
+ ];
+ cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
+ return title;
+}
+
+const TrackProgress = ({ player, ...rest }) => {
+ const _updateProgress = (circprog) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ // Set circular progress (see definition of AnimatedCircProg for explanation)
+ circprog.css = `font-size: ${Math.max(player.position / player.length * 100, 0)}px;`
+ }
+ return AnimatedCircProg({
+ ...rest,
+ className: 'osd-music-circprog',
+ vpack: 'center',
+ extraSetup: (self) => self
+ .hook(Mpris, _updateProgress)
+ .poll(3000, _updateProgress)
+ ,
+ })
+}
+
+const TrackTitle = ({ player, ...rest }) => Label({
+ ...rest,
+ label: 'No music playing',
+ xalign: 0,
+ truncate: 'end',
+ // wrap: true,
+ className: 'osd-music-title',
+ setup: (self) => self.hook(player, (self) => {
+ // Player name
+ self.label = player.trackTitle.length > 0 ? trimTrackTitle(player.trackTitle) : 'No media';
+ // Font based on track/artist
+ const fontForThisTrack = getTrackfont(player);
+ self.css = `font-family: ${fontForThisTrack}, ${DEFAULT_MUSIC_FONT};`;
+ }, 'notify::track-title'),
+});
+
+const TrackArtists = ({ player, ...rest }) => Label({
+ ...rest,
+ xalign: 0,
+ className: 'osd-music-artists',
+ truncate: 'end',
+ setup: (self) => self.hook(player, (self) => {
+ self.label = player.trackArtists.length > 0 ? player.trackArtists.join(', ') : '';
+ }, 'notify::track-artists'),
+})
+
+const CoverArt = ({ player, ...rest }) => {
+ const fallbackCoverArt = Box({ // Fallback
+ className: 'osd-music-cover-fallback',
+ homogeneous: true,
+ children: [Label({
+ className: 'icon-material txt-gigantic txt-thin',
+ label: 'music_note',
+ })]
+ });
+ // const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' });
+ // const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context();
+ const realCoverArt = Box({
+ className: 'osd-music-cover-art',
+ homogeneous: true,
+ // children: [coverArtDrawingArea],
+ attribute: {
+ 'pixbuf': null,
+ // 'showImage': (self, imagePath) => {
+ // const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
+ // const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ // const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ // let imageHeight = frameHeight;
+ // let imageWidth = frameWidth;
+ // // Get image dimensions
+ // execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
+ // .then((output) => {
+ // const imageDimensions = JSON.parse(output);
+ // const imageAspectRatio = imageDimensions.w / imageDimensions.h;
+ // const displayedAspectRatio = imageWidth / imageHeight;
+ // if (imageAspectRatio >= displayedAspectRatio) {
+ // imageWidth = imageHeight * imageAspectRatio;
+ // } else {
+ // imageHeight = imageWidth / imageAspectRatio;
+ // }
+ // // Real stuff
+ // // TODO: fix memory leak(?)
+ // // if (self.attribute.pixbuf) {
+ // // self.attribute.pixbuf.unref();
+ // // self.attribute.pixbuf = null;
+ // // }
+ // self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight);
+
+ // coverArtDrawingArea.set_size_request(frameWidth, frameHeight);
+ // coverArtDrawingArea.connect("draw", (widget, cr) => {
+ // // Clip a rounded rectangle area
+ // cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
+ // cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
+ // cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
+ // cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
+ // cr.closePath();
+ // cr.clip();
+ // // Paint image as bg, centered
+ // Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
+ // frameWidth / 2 - imageWidth / 2,
+ // frameHeight / 2 - imageHeight / 2
+ // );
+ // cr.paint();
+ // });
+ // }).catch(print)
+ // },
+ 'updateCover': (self) => {
+ // const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this
+ // Player closed
+ // Note that cover path still remains, so we're checking title
+ if (!player || player.trackTitle == "" || !player.coverPath) {
+ self.css = `background-image: none;`; // CSS image
+ App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
+ return;
+ }
+
+ const coverPath = player.coverPath;
+ const stylePath = `${player.coverPath}${darkMode.value ? '' : '-l'}${COVER_COLORSCHEME_SUFFIX}`;
+ if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
+ Utils.timeout(200, () => {
+ // self.attribute.showImage(self, coverPath);
+ self.css = `background-image: url('${coverPath}');`; // CSS image
+ });
+ }
+ lastCoverPath = player.coverPath;
+
+ // If a colorscheme has already been generated, skip generation
+ if (fileExists(stylePath)) {
+ // self.attribute.showImage(self, coverPath)
+ self.css = `background-image: url('${coverPath}');`; // CSS image
+ App.applyCss(stylePath);
+ return;
+ }
+
+ // Generate colors
+ execAsync(['bash', '-c',
+ `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' --mode ${darkMode.value ? 'dark' : 'light'} > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss`])
+ .then(() => {
+ exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${darkMode.value ? '' : '-l'}`)
+ exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss`);
+ exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/_music.scss" "${stylePath}"`);
+ Utils.timeout(200, () => {
+ // self.attribute.showImage(self, coverPath)
+ self.css = `background-image: url('${coverPath}');`; // CSS image
+ });
+ App.applyCss(`${stylePath}`);
+ })
+ .catch(print);
+ },
+ },
+ setup: (self) => self
+ .hook(player, (self) => {
+ self.attribute.updateCover(self);
+ }, 'notify::cover-path')
+ ,
+ });
+ return Box({
+ ...rest,
+ className: 'osd-music-cover',
+ children: [
+ Widget.Overlay({
+ child: fallbackCoverArt,
+ overlays: [realCoverArt],
+ })
+ ],
+ })
+}
+
+const TrackControls = ({ player, ...rest }) => Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Box({
+ ...rest,
+ vpack: 'center',
+ className: 'osd-music-controls spacing-h-3',
+ children: [
+ Button({
+ className: 'osd-music-controlbtn',
+ onClicked: () => player.previous(),
+ child: Label({
+ className: 'icon-material osd-music-controlbtn-txt',
+ label: 'skip_previous',
+ })
+ }),
+ Button({
+ className: 'osd-music-controlbtn',
+ onClicked: () => player.next(),
+ child: Label({
+ className: 'icon-material osd-music-controlbtn-txt',
+ label: 'skip_next',
+ })
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player)
+ self.revealChild = false;
+ else
+ self.revealChild = true;
+ }, 'notify::play-back-status'),
+});
+
+const TrackSource = ({ player, ...rest }) => Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Box({
+ ...rest,
+ className: 'osd-music-pill spacing-h-5',
+ homogeneous: true,
+ children: [
+ Label({
+ hpack: 'fill',
+ justification: 'center',
+ className: 'icon-nerd',
+ setup: (self) => self.hook(player, (self) => {
+ self.label = detectMediaSource(player.trackCoverUrl);
+ }, 'notify::cover-path'),
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris)
+ self.revealChild = false;
+ else
+ self.revealChild = true;
+ }),
+});
+
+const TrackTime = ({ player, ...rest }) => {
+ return Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Box({
+ ...rest,
+ vpack: 'center',
+ className: 'osd-music-pill spacing-h-5',
+ children: [
+ Label({
+ setup: (self) => self.poll(1000, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ self.label = lengthStr(player.position);
+ }),
+ }),
+ Label({ label: '/' }),
+ Label({
+ setup: (self) => self.hook(Mpris, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ self.label = lengthStr(player.length);
+ }),
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ if (!player) self.revealChild = false;
+ else self.revealChild = true;
+ }),
+ })
+}
+
+const PlayState = ({ player }) => {
+ var position = 0;
+ const trackCircProg = TrackProgress({ player: player });
+ return Widget.Button({
+ className: 'osd-music-playstate',
+ child: Widget.Overlay({
+ child: trackCircProg,
+ overlays: [
+ Widget.Button({
+ className: 'osd-music-playstate-btn',
+ onClicked: () => player.playPause(),
+ child: Widget.Label({
+ justification: 'center',
+ hpack: 'fill',
+ vpack: 'center',
+ setup: (self) => self.hook(player, (label) => {
+ label.label = `${player.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
+ }, 'notify::play-back-status'),
+ }),
+ }),
+ ],
+ passThrough: true,
+ })
+ });
+}
+
+const MusicControlsWidget = (player) => Box({
+ className: 'osd-music spacing-h-20 test',
+ children: [
+ CoverArt({ player: player, vpack: 'center' }),
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 osd-music-info',
+ children: [
+ Box({
+ vertical: true,
+ vpack: 'center',
+ hexpand: true,
+ children: [
+ TrackTitle({ player: player }),
+ TrackArtists({ player: player }),
+ ]
+ }),
+ Box({ vexpand: true }),
+ Box({
+ className: 'spacing-h-10',
+ setup: (box) => {
+ box.pack_start(TrackControls({ player: player }), false, false, 0);
+ box.pack_end(PlayState({ player: player }), false, false, 0);
+ if(hasPlasmaIntegration || player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) box.pack_end(TrackTime({ player: player }), false, false, 0)
+ // box.pack_end(TrackSource({ vpack: 'center', player: player }), false, false, 0);
+ }
+ })
+ ]
+ })
+ ]
+})
+
+export default () => Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: Box({
+ children: Mpris.bind("players")
+ .as(players => players.map((player) => (isRealPlayer(player) ? MusicControlsWidget(player) : null)))
+ }),
+ setup: (self) => self.hook(showMusicControls, (revealer) => {
+ revealer.revealChild = showMusicControls.value;
+ }),
+})
diff --git a/ags/modules/indicators/notificationpopups.js b/ags/modules/indicators/notificationpopups.js
new file mode 100644
index 00000000..0c38a670
--- /dev/null
+++ b/ags/modules/indicators/notificationpopups.js
@@ -0,0 +1,45 @@
+// This file is for popup notifications
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+const { Box } = Widget;
+import Notification from '../.commonwidgets/notification.js';
+
+export default () => Box({
+ vertical: true,
+ hpack: 'center',
+ className: 'osd-notifs spacing-v-5-revealer',
+ attribute: {
+ 'map': new Map(),
+ 'dismiss': (box, id, force = false) => {
+ if (!id || !box.attribute.map.has(id))
+ return;
+ const notifWidget = box.attribute.map.get(id);
+ if (notifWidget == null || notifWidget.attribute.hovered && !force)
+ return; // cuz already destroyed
+
+ notifWidget.revealChild = false;
+ notifWidget.attribute.destroyWithAnims();
+ box.attribute.map.delete(id);
+ },
+ 'notify': (box, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+
+ box.attribute.map.delete(id);
+
+ const notif = Notifications.getNotification(id);
+ const newNotif = Notification({
+ notifObject: notif,
+ isPopup: true,
+ });
+ box.attribute.map.set(id, newNotif);
+ box.pack_end(box.attribute.map.get(id), false, false, 0);
+ box.show_all();
+ },
+ },
+ setup: (self) => self
+ .hook(Notifications, (box, id) => box.attribute.notify(box, id), 'notified')
+ .hook(Notifications, (box, id) => box.attribute.dismiss(box, id), 'dismissed')
+ .hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
+ ,
+});
diff --git a/ags/modules/onscreenkeyboard/data_keyboardlayouts.js b/ags/modules/onscreenkeyboard/data_keyboardlayouts.js
new file mode 100644
index 00000000..1a67b7a1
--- /dev/null
+++ b/ags/modules/onscreenkeyboard/data_keyboardlayouts.js
@@ -0,0 +1,218 @@
+// We're going to use ydotool
+// See /usr/include/linux/input-event-codes.h for keycodes
+
+export const DEFAULT_OSK_LAYOUT = "qwerty_full"
+export const oskLayouts = {
+ qwerty_full: {
+ name: "QWERTY - Full",
+ name_short: "US",
+ comment: "Like physical keyboard",
+ // A key looks like this: { k: "a", ks: "A", t: "normal" } (key, key-shift, type)
+ // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
+ // keys: [
+ // [{ k: "Esc", t: "fn" }, { k: "F1", t: "fn" }, { k: "F2", t: "fn" }, { k: "F3", t: "fn" }, { k: "F4", t: "fn" }, { k: "F5", t: "fn" }, { k: "F6", t: "fn" }, { k: "F7", t: "fn" }, { k: "F8", t: "fn" }, { k: "F9", t: "fn" }, { k: "F10", t: "fn" }, { k: "F11", t: "fn" }, { k: "F12", t: "fn" }, { k: "PrtSc", t: "fn" }, { k: "Del", t: "fn" }],
+ // [{ k: "`", ks: "~", t: "normal" }, { k: "1", ks: "!", t: "normal" }, { k: "2", ks: "@", t: "normal" }, { k: "3", ks: "#", t: "normal" }, { k: "4", ks: "$", t: "normal" }, { k: "5", ks: "%", t: "normal" }, { k: "6", ks: "^", t: "normal" }, { k: "7", ks: "&", t: "normal" }, { k: "8", ks: "*", t: "normal" }, { k: "9", ks: "(", t: "normal" }, { k: "0", ks: ")", t: "normal" }, { k: "-", ks: "_", t: "normal" }, { k: "=", ks: "+", t: "normal" }, { k: "Backspace", t: "shift" }],
+ // [{ k: "Tab", t: "tab" }, { k: "q", ks: "Q", t: "normal" }, { k: "w", ks: "W", t: "normal" }, { k: "e", ks: "E", t: "normal" }, { k: "r", ks: "R", t: "normal" }, { k: "t", ks: "T", t: "normal" }, { k: "y", ks: "Y", t: "normal" }, { k: "u", ks: "U", t: "normal" }, { k: "i", ks: "I", t: "normal" }, { k: "o", ks: "O", t: "normal" }, { k: "p", ks: "P", t: "normal" }, { k: "[", ks: "{", t: "normal" }, { k: "]", ks: "}", t: "normal" }, { k: "\\", ks: "|", t: "expand" }],
+ // [{ k: "Caps", t: "caps" }, { k: "a", ks: "A", t: "normal" }, { k: "s", ks: "S", t: "normal" }, { k: "d", ks: "D", t: "normal" }, { k: "f", ks: "F", t: "normal" }, { k: "g", ks: "G", t: "normal" }, { k: "h", ks: "H", t: "normal" }, { k: "j", ks: "J", t: "normal" }, { k: "k", ks: "K", t: "normal" }, { k: "l", ks: "L", t: "normal" }, { k: ";", ks: ":", t: "normal" }, { k: "'", ks: '"', t: "normal" }, { k: "Enter", t: "expand" }],
+ // [{ k: "Shift", t: "shift" }, { k: "z", ks: "Z", t: "normal" }, { k: "x", ks: "X", t: "normal" }, { k: "c", ks: "C", t: "normal" }, { k: "v", ks: "V", t: "normal" }, { k: "b", ks: "B", t: "normal" }, { k: "n", ks: "N", t: "normal" }, { k: "m", ks: "M", t: "normal" }, { k: ",", ks: "<", t: "normal" }, { k: ".", ks: ">", t: "normal" }, { k: "/", ks: "?", t: "normal" }, { k: "Shift", t: "expand" }],
+ // [{ k: "Ctrl", t: "control" }, { k: "Fn", t: "normal" }, { k: "Win", t: "normal" }, { k: "Alt", t: "normal" }, { k: "Space", t: "space" }, { k: "Alt", t: "normal" }, { k: "Menu", t: "normal" }, { k: "Ctrl", t: "control" }]
+ // ]
+ // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"}
+ // A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"}
+ // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
+ keys: [
+ [
+ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
+ { keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
+ { keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
+ { keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
+ { keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
+ { keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
+ { keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
+ { keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
+ { keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
+ { keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
+ { keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
+ { keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
+ { keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
+ { keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 },
+ { keytype: "normal", label: "Del", shape: "fn", keycode: 111 }
+ ],
+ [
+ { keytype: "normal", label: "`", labelShift: "~", shape: "normal", keycode: 41 },
+ { keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 },
+ { keytype: "normal", label: "2", labelShift: "@", shape: "normal", keycode: 3 },
+ { keytype: "normal", label: "3", labelShift: "#", shape: "normal", keycode: 4 },
+ { keytype: "normal", label: "4", labelShift: "$", shape: "normal", keycode: 5 },
+ { keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 },
+ { keytype: "normal", label: "6", labelShift: "^", shape: "normal", keycode: 7 },
+ { keytype: "normal", label: "7", labelShift: "&", shape: "normal", keycode: 8 },
+ { keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 },
+ { keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 },
+ { keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 },
+ { keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 },
+ { keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 },
+ { keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 }
+ ],
+ [
+ { keytype: "normal", label: "Tab", shape: "tab", keycode: 15 },
+ { keytype: "normal", label: "q", labelShift: "Q", shape: "normal", keycode: 16 },
+ { keytype: "normal", label: "w", labelShift: "W", shape: "normal", keycode: 17 },
+ { keytype: "normal", label: "e", labelShift: "E", shape: "normal", keycode: 18 },
+ { keytype: "normal", label: "r", labelShift: "R", shape: "normal", keycode: 19 },
+ { keytype: "normal", label: "t", labelShift: "T", shape: "normal", keycode: 20 },
+ { keytype: "normal", label: "y", labelShift: "Y", shape: "normal", keycode: 21 },
+ { keytype: "normal", label: "u", labelShift: "U", shape: "normal", keycode: 22 },
+ { keytype: "normal", label: "i", labelShift: "I", shape: "normal", keycode: 23 },
+ { keytype: "normal", label: "o", labelShift: "O", shape: "normal", keycode: 24 },
+ { keytype: "normal", label: "p", labelShift: "P", shape: "normal", keycode: 25 },
+ { keytype: "normal", label: "[", labelShift: "{", shape: "normal", keycode: 26 },
+ { keytype: "normal", label: "]", labelShift: "}", shape: "normal", keycode: 27 },
+ { keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 }
+ ],
+ [
+ //{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 },
+ { keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 },
+ { keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 },
+ { keytype: "normal", label: "f", labelShift: "F", shape: "normal", keycode: 33 },
+ { keytype: "normal", label: "g", labelShift: "G", shape: "normal", keycode: 34 },
+ { keytype: "normal", label: "h", labelShift: "H", shape: "normal", keycode: 35 },
+ { keytype: "normal", label: "j", labelShift: "J", shape: "normal", keycode: 36 },
+ { keytype: "normal", label: "k", labelShift: "K", shape: "normal", keycode: 37 },
+ { keytype: "normal", label: "l", labelShift: "L", shape: "normal", keycode: 38 },
+ { keytype: "normal", label: ";", labelShift: ":", shape: "normal", keycode: 39 },
+ { keytype: "normal", label: "'", labelShift: '"', shape: "normal", keycode: 40 },
+ { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 }
+ ],
+ [
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
+ { keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 },
+ { keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 },
+ { keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 },
+ { keytype: "normal", label: "v", labelShift: "V", shape: "normal", keycode: 47 },
+ { keytype: "normal", label: "b", labelShift: "B", shape: "normal", keycode: 48 },
+ { keytype: "normal", label: "n", labelShift: "N", shape: "normal", keycode: 49 },
+ { keytype: "normal", label: "m", labelShift: "M", shape: "normal", keycode: 50 },
+ { keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 },
+ { keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 },
+ { keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 },
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 } // optional
+ ],
+ [
+ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 },
+ // { label: "Super", shape: "normal", keycode: 125 }, // dangerous
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
+ { keytype: "normal", label: "Space", shape: "space", keycode: 57 },
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 },
+ // { label: "Super", shape: "normal", keycode: 126 }, // dangerous
+ { keytype: "normal", label: "Menu", shape: "normal", keycode: 139 },
+ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 }
+ ]
+ ]
+ },
+ qwertz_full: {
+ name: "QWERTZ - Full",
+ name_short: "DE",
+ comment: "Keyboard layout commonly used in German-speaking countries",
+ keys: [
+ [
+ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
+ { keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
+ { keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
+ { keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
+ { keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
+ { keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
+ { keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
+ { keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
+ { keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
+ { keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
+ { keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
+ { keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
+ { keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
+ { keytype: "normal", label: "Druck", shape: "fn", keycode: 99 },
+ { keytype: "normal", label: "Entf", shape: "fn", keycode: 111 }
+ ],
+ [
+ { keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 },
+ { keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 },
+ { keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 },
+ { keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 },
+ { keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 },
+ { keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 },
+ { keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 },
+ { keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 },
+ { keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 },
+ { keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 },
+ { keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 },
+ { keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 },
+ { keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 },
+ { keytype: "normal", label: "⟵", shape: "expand", keycode: 14 }
+ ],
+ [
+ { keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 },
+ { keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 },
+ { keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 },
+ { keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 },
+ { keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 },
+ { keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 },
+ { keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 },
+ { keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 },
+ { keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 },
+ { keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 },
+ { keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 },
+ { keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 },
+ { keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 },
+ { keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
+ ],
+ [
+ //{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 },
+ { keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 },
+ { keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 },
+ { keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 },
+ { keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 },
+ { keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 },
+ { keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 },
+ { keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 },
+ { keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 },
+ { keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 },
+ { keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 },
+ { keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 },
+ { keytype: "spacer", label: "", shape: "empty" },
+ //{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
+ ],
+ [
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
+ { keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 },
+ { keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 },
+ { keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 },
+ { keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 },
+ { keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 },
+ { keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 },
+ { keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 },
+ { keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 },
+ { keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 },
+ { keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 },
+ { keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 },
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional
+ ],
+ [
+ { keytype: "modkey", label: "Strg", shape: "control", keycode: 29 },
+ //{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
+ { keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 },
+ { keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 },
+ // { label: "Super", shape: "normal", keycode: 126 }, // dangerous
+ //{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work?
+ { keytype: "modkey", label: "Strg", shape: "control", keycode: 97 },
+ { keytype: "normal", label: "⇦", shape: "normal", keycode: 105 },
+ { keytype: "normal", label: "⇨", shape: "normal", keycode: 106 },
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ags/modules/onscreenkeyboard/main.js b/ags/modules/onscreenkeyboard/main.js
new file mode 100644
index 00000000..2204ebf3
--- /dev/null
+++ b/ags/modules/onscreenkeyboard/main.js
@@ -0,0 +1,11 @@
+import PopupWindow from '../.widgethacks/popupwindow.js';
+import OnScreenKeyboard from "./onscreenkeyboard.js";
+
+export default (id) => PopupWindow({
+ monitor: id,
+ anchor: ['bottom'],
+ name: `osk${id}`,
+ showClassName: 'osk-show',
+ hideClassName: 'osk-hide',
+ child: OnScreenKeyboard({ id: id }),
+});
diff --git a/ags/modules/onscreenkeyboard/onscreenkeyboard.js b/ags/modules/onscreenkeyboard/onscreenkeyboard.js
new file mode 100644
index 00000000..9280da75
--- /dev/null
+++ b/ags/modules/onscreenkeyboard/onscreenkeyboard.js
@@ -0,0 +1,267 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, EventBox, Button, Revealer } = Widget;
+const { execAsync } = Utils;
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { DEFAULT_OSK_LAYOUT, oskLayouts } from './data_keyboardlayouts.js';
+import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
+
+const keyboardLayout = oskLayouts[userOptions.onScreenKeyboard.layout] ? userOptions.onScreenKeyboard.layout : DEFAULT_OSK_LAYOUT;
+const keyboardJson = oskLayouts[keyboardLayout];
+
+async function startYdotoolIfNeeded() {
+ const running = exec('pidof ydotool')
+ if (!running) execAsync(['ydotoold']).catch(print);
+}
+
+function releaseAllKeys() {
+ const keycodes = Array.from(Array(249).keys());
+ execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
+ .then(console.log('[OSK] Released all keys'))
+ .catch(print);
+}
+class ShiftMode {
+ static Off = new ShiftMode('Off');
+ static Normal = new ShiftMode('Normal');
+ static Locked = new ShiftMode('Locked');
+
+ constructor(name) {
+ this.name = name;
+ }
+ toString() {
+ return `ShiftMode.${this.name}`;
+ }
+}
+var modsPressed = false;
+
+const TopDecor = () => Box({
+ vertical: true,
+ children: [
+ Box({
+ hpack: 'center',
+ className: 'osk-dragline',
+ homogeneous: true,
+ children: [EventBox({
+ setup: setupCursorHoverGrab,
+ })]
+ })
+ ]
+});
+
+const KeyboardControlButton = (icon, text, runFunction) => Button({
+ className: 'osk-control-button spacing-h-10',
+ onClicked: () => runFunction(),
+ child: Widget.Box({
+ children: [
+ MaterialIcon(icon, 'norm'),
+ Widget.Label({
+ label: `${text}`,
+ }),
+ ]
+ })
+})
+
+const KeyboardControls = () => Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ Button({
+ className: 'osk-control-button txt-norm icon-material',
+ onClicked: () => {
+ releaseAllKeys();
+ toggleWindowOnAllMonitors('osk');
+ },
+ label: 'keyboard_hide',
+ }),
+ Button({
+ className: 'osk-control-button txt-norm',
+ label: `${keyboardJson['name_short']}`,
+ }),
+ Button({
+ className: 'osk-control-button txt-norm icon-material',
+ onClicked: () => { // TODO: Proper clipboard widget, since fuzzel doesn't receive mouse inputs
+ execAsync([`bash`, `-c`, "pkill fuzzel || cliphist list | fuzzel --no-fuzzy --dmenu | cliphist decode | wl-copy"]).catch(print);
+ },
+ label: 'assignment',
+ }),
+ ]
+})
+
+var shiftMode = ShiftMode.Off;
+var shiftButton;
+var rightShiftButton;
+var allButtons = [];
+const KeyboardItself = (kbJson) => {
+ return Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: kbJson.keys.map(row => Box({
+ vertical: false,
+ className: 'spacing-h-5',
+ children: row.map(key => {
+ return Button({
+ className: `osk-key osk-key-${key.shape}`,
+ hexpand: ["space", "expand"].includes(key.shape),
+ label: key.label,
+ attribute:
+ { key: key },
+ setup: (button) => {
+ let pressed = false;
+ allButtons = allButtons.concat(button);
+ if (key.keytype == "normal") {
+ button.connect('pressed', () => { // mouse down
+ execAsync(`ydotool key ${key.keycode}:1`).catch(print);
+ });
+ button.connect('clicked', () => { // release
+ execAsync(`ydotool key ${key.keycode}:0`).catch(print);
+
+ if (shiftMode == ShiftMode.Normal) {
+ shiftMode = ShiftMode.Off;
+ if (typeof shiftButton !== 'undefined') {
+ execAsync(`ydotool key 42:0`).catch(print);
+ shiftButton.toggleClassName('osk-key-active', false);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ execAsync(`ydotool key 54:0`).catch(print);
+ rightShiftButton.toggleClassName('osk-key-active', false);
+ }
+ allButtons.forEach(button => {
+ if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label;
+ })
+ }
+ });
+ }
+ else if (key.keytype == "modkey") {
+ button.connect('pressed', () => { // release
+ if (pressed) {
+ execAsync(`ydotool key ${key.keycode}:0`).catch(print);
+ button.toggleClassName('osk-key-active', false);
+ pressed = false;
+ if (key.keycode == 100) { // Alt Gr button
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.label; });
+ }
+ }
+ else {
+ execAsync(`ydotool key ${key.keycode}:1`).catch(print);
+ button.toggleClassName('osk-key-active', true);
+ if (!(key.keycode == 42 || key.keycode == 54)) pressed = true;
+ else switch (shiftMode.name) { // This toggles the shift button state
+ case "Off": {
+ shiftMode = ShiftMode.Normal;
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.labelShift; })
+ if (typeof shiftButton !== 'undefined') {
+ shiftButton.toggleClassName('osk-key-active', true);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ rightShiftButton.toggleClassName('osk-key-active', true);
+ }
+ } break;
+ case "Normal": {
+ shiftMode = ShiftMode.Locked;
+ if (typeof shiftButton !== 'undefined') shiftButton.label = key.labelCaps;
+ if (typeof rightShiftButton !== 'undefined') rightShiftButton.label = key.labelCaps;
+ } break;
+ case "Locked": {
+ shiftMode = ShiftMode.Off;
+ if (typeof shiftButton !== 'undefined') {
+ shiftButton.label = key.label;
+ shiftButton.toggleClassName('osk-key-active', false);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ rightShiftButton.label = key.label;
+ rightShiftButton.toggleClassName('osk-key-active', false);
+ }
+ execAsync(`ydotool key ${key.keycode}:0`).catch(print);
+
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; }
+ )
+ };
+ }
+ if (key.keycode == 100) { // Alt Gr button
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.labelAlt; });
+ }
+ modsPressed = true;
+ }
+ });
+ if (key.keycode == 42) shiftButton = button;
+ else if (key.keycode == 54) rightShiftButton = button;
+ }
+ }
+ })
+ })
+ }))
+ })
+}
+
+const KeyboardWindow = () => Box({
+ vexpand: true,
+ hexpand: true,
+ vertical: true,
+ className: 'osk-window spacing-v-5',
+ children: [
+ TopDecor(),
+ Box({
+ className: 'osk-body spacing-h-10',
+ children: [
+ KeyboardControls(),
+ Widget.Box({ className: 'separator-line' }),
+ KeyboardItself(keyboardJson),
+ ],
+ })
+ ],
+ setup: (self) => self.hook(App, (self, name, visible) => { // Update on open
+ if (!name) return;
+ if (name.startsWith('osk') && visible) {
+ self.setCss(`margin-bottom: -0px;`);
+ }
+ }),
+});
+
+export default ({ id }) => {
+ const kbWindow = KeyboardWindow();
+ const gestureEvBox = EventBox({ child: kbWindow })
+ const gesture = Gtk.GestureDrag.new(gestureEvBox);
+ gesture.connect('drag-begin', async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.messageAsync('j/cursorpos').then((out) => {
+ gesture.startY = JSON.parse(out).y;
+ }).catch(print);
+ } catch {
+ return;
+ }
+ });
+ gesture.connect('drag-update', async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.messageAsync('j/cursorpos').then((out) => {
+ const currentY = JSON.parse(out).y;
+ const offset = gesture.startY - currentY;
+
+ if (offset > 0) return;
+
+ kbWindow.setCss(`
+ margin-bottom: ${offset}px;
+ `);
+ }).catch(print);
+ } catch {
+ return;
+ }
+ });
+ gesture.connect('drag-end', () => {
+ var offset = gesture.get_offset()[2];
+ if (offset > 50) {
+ App.closeWindow(`osk${id}`);
+ }
+ else {
+ kbWindow.setCss(`
+ transition: margin-bottom 170ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-bottom: 0px;
+ `);
+ }
+ })
+ return gestureEvBox;
+};
diff --git a/ags/modules/overview/actions.js b/ags/modules/overview/actions.js
new file mode 100644
index 00000000..766cf454
--- /dev/null
+++ b/ags/modules/overview/actions.js
@@ -0,0 +1,28 @@
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+function moveClientToWorkspace(address, workspace) {
+ Utils.execAsync(['bash', '-c', `hyprctl dispatch movetoworkspacesilent ${workspace},address:${address} &`]);
+}
+
+export function dumpToWorkspace(from, to) {
+ if (from == to) return;
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == from) {
+ moveClientToWorkspace(client.address, to);
+ }
+ });
+}
+
+export function swapWorkspace(workspaceA, workspaceB) {
+ if (workspaceA == workspaceB) return;
+ const clientsA = [];
+ const clientsB = [];
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == workspaceA) clientsA.push(client.address);
+ if (client.workspace.id == workspaceB) clientsB.push(client.address);
+ });
+
+ clientsA.forEach((address) => moveClientToWorkspace(address, workspaceB));
+ clientsB.forEach((address) => moveClientToWorkspace(address, workspaceA));
+}
\ No newline at end of file
diff --git a/ags/modules/overview/main.js b/ags/modules/overview/main.js
new file mode 100644
index 00000000..ee9a3d23
--- /dev/null
+++ b/ags/modules/overview/main.js
@@ -0,0 +1,28 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { SearchAndWindows } from "./windowcontent.js";
+import PopupWindow from '../.widgethacks/popupwindow.js';
+import { clickCloseRegion } from '../.commonwidgets/clickcloseregion.js';
+
+export default (id = '') => PopupWindow({
+ name: `overview${id}`,
+ // exclusivity: 'ignore',
+ keymode: 'on-demand',
+ visible: false,
+ anchor: ['top', 'bottom', 'left', 'right'],
+ layer: 'overlay',
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ clickCloseRegion({ name: 'overview', multimonitor: false, expand: false }),
+ Widget.Box({
+ children: [
+ clickCloseRegion({ name: 'overview', multimonitor: false }),
+ SearchAndWindows(),
+ clickCloseRegion({ name: 'overview', multimonitor: false }),
+ ]
+ }),
+ clickCloseRegion({ name: 'overview', multimonitor: false }),
+ ]
+ }),
+})
+
diff --git a/ags/modules/overview/miscfunctions.js b/ags/modules/overview/miscfunctions.js
new file mode 100644
index 00000000..91d84230
--- /dev/null
+++ b/ags/modules/overview/miscfunctions.js
@@ -0,0 +1,165 @@
+const { Gio, GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+import Todo from "../../services/todo.js";
+import { darkMode } from '../.miscutils/system.js';
+
+export function hasUnterminatedBackslash(inputString) {
+ // Use a regular expression to match a trailing odd number of backslashes
+ const regex = /\\+$/;
+ return regex.test(inputString);
+}
+
+export function launchCustomCommand(command) {
+ const args = command.toLowerCase().split(' ');
+ if (args[0] == '>raw') { // Mouse raw input
+ Utils.execAsync('hyprctl -j getoption input:accel_profile')
+ .then((output) => {
+ const value = JSON.parse(output)["str"].trim();
+ if (value != "[[EMPTY]]" && value != "") {
+ execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
+ }
+ else {
+ execAsync(['bash', '-c', `hyprctl keyword input:accel_profile flat`]).catch(print);
+ }
+ })
+ }
+ else if (args[0] == '>img') { // Change wallpaper
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
+ }
+ else if (args[0] == '>color') { // Generate colorscheme from color picker
+ if (!args[1])
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh --pick`, `&`]).catch(print);
+ else if (args[1][0] === '#')
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${args[1]}"`, `&`]).catch(print);
+ }
+ else if (args[0] == '>light') { // Light mode
+ darkMode.value = false;
+ }
+ else if (args[0] == '>dark') { // Dark mode
+ darkMode.value = true;
+ }
+ else if (args[0] == '>badapple') { // Black and white
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/monochrome/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ }
+ else if (args[0] == '>adw' || args[0] == '>adwaita') {
+ const ADWAITA_BLUE = "#3584E4";
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${ADWAITA_BLUE}" --no-gradience`, `&`])
+ .catch(print);
+ }
+ else if (args[0] == '>grad' || args[0] == '>gradience') {
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --yes-gradience`, `&`])
+ .catch(print);
+ }
+ else if (args[0] == '>nograd' || args[0] == '>nogradience') {
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --no-gradience`, `&`])
+ .catch(print);
+ }
+ else if (args[0] == '>material') { // Use material colors
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "material" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>pywal') { // Use Pywal (ik it looks shit but I'm not removing)
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "pywal" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>todo') { // Todo
+ Todo.add(args.slice(1).join(' '));
+ }
+ else if (args[0] == '>shutdown') { // Shut down
+ execAsync([`bash`, `-c`, `systemctl poweroff || loginctl poweroff`]).catch(print);
+ }
+ else if (args[0] == '>reboot') { // Reboot
+ execAsync([`bash`, `-c`, `systemctl reboot || loginctl reboot`]).catch(print);
+ }
+ else if (args[0] == '>sleep') { // Sleep
+ execAsync([`bash`, `-c`, `systemctl suspend || loginctl suspend`]).catch(print);
+ }
+ else if (args[0] == '>logout') { // Log out
+ execAsync([`bash`, `-c`, `pkill Hyprland || pkill sway`]).catch(print);
+ }
+}
+
+export function execAndClose(command, terminal) {
+ App.closeWindow('overview');
+ if (terminal) {
+ execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${command}"`, `&`]).catch(print);
+ }
+ else
+ execAsync(command).catch(print);
+}
+
+export function couldBeMath(str) {
+ const regex = /^[0-9.+*/-]/;
+ return regex.test(str);
+}
+
+export function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+function getFileIcon(fileInfo) {
+ let icon = fileInfo.get_icon();
+ if (icon) {
+ // Get the icon's name
+ return icon.get_names()[0];
+ } else {
+ // Default icon for files
+ return 'text-x-generic';
+ }
+}
+
+export function ls({ path = '~', silent = false }) {
+ let contents = [];
+ try {
+ let expandedPath = expandTilde(path);
+ if (expandedPath.endsWith('/'))
+ expandedPath = expandedPath.slice(0, -1);
+ let folder = Gio.File.new_for_path(expandedPath);
+
+ let enumerator = folder.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let fileInfo;
+ while ((fileInfo = enumerator.next_file(null)) !== null) {
+ let fileName = fileInfo.get_display_name();
+ let fileType = fileInfo.get_file_type();
+
+ let item = {
+ parentPath: expandedPath,
+ name: fileName,
+ type: fileType === Gio.FileType.DIRECTORY ? 'folder' : 'file',
+ icon: getFileIcon(fileInfo),
+ };
+
+ // Add file extension for files
+ if (fileType === Gio.FileType.REGULAR) {
+ let fileExtension = fileName.split('.').pop();
+ item.type = `${fileExtension}`;
+ }
+
+ contents.push(item);
+ contents.sort((a, b) => {
+ const aIsFolder = a.type.startsWith('folder');
+ const bIsFolder = b.type.startsWith('folder');
+ if (aIsFolder && !bIsFolder) {
+ return -1;
+ } else if (!aIsFolder && bIsFolder) {
+ return 1;
+ } else {
+ return a.name.localeCompare(b.name); // Sort alphabetically within folders and files
+ }
+ });
+ }
+ } catch (e) {
+ if (!silent) console.log(e);
+ }
+ return contents;
+}
diff --git a/ags/modules/overview/overview_hyprland.js b/ags/modules/overview/overview_hyprland.js
new file mode 100644
index 00000000..e9eab426
--- /dev/null
+++ b/ags/modules/overview/overview_hyprland.js
@@ -0,0 +1,431 @@
+// TODO
+// - Make client destroy/create not destroy and recreate the whole thing
+// - Active ws hook optimization: only update when moving to next group
+//
+const { Gdk, Gtk } = imports.gi;
+const { Gravity } = imports.gi.Gdk;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+const { execAsync, exec } = Utils;
+import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
+import { dumpToWorkspace, swapWorkspace } from "./actions.js";
+import { iconExists, substitute } from "../.miscutils/icons.js";
+import { monitors } from '../.commondata/hyprlanddata.js';
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+
+const NUM_OF_WORKSPACES_SHOWN = userOptions.overview.numOfCols * userOptions.overview.numOfRows;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+
+const overviewTick = Variable(false);
+
+export default (overviewMonitor = 0) => {
+ const clientMap = new Map();
+ const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
+ label: `${label}`,
+ setup: (menuItem) => {
+ let submenu = new Gtk.Menu();
+ submenu.className = 'menu';
+
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const startWorkspace = offset + 1;
+ const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1;
+ for (let i = startWorkspace; i <= endWorkspace; i++) {
+ let button = new Gtk.MenuItem({
+ label: `Workspace ${i}`
+ });
+ button.connect("activate", () => {
+ // execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
+ actionFunc(thisWorkspace, i);
+ overviewTick.setValue(!overviewTick.value);
+ });
+ submenu.append(button);
+ }
+ menuItem.set_reserve_indicator(true);
+ menuItem.set_submenu(submenu);
+ }
+ })
+
+ const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, initialClass, monitor, title, xwayland }, screenCoords) => {
+ const revealInfoCondition = (Math.min(w, h) * userOptions.overview.scale > 70);
+ if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
+ // Non-primary monitors
+ if (screenCoords.x != 0) x -= screenCoords.x;
+ if (screenCoords.y != 0) y -= screenCoords.y;
+ // Other offscreen adjustments
+ if (x + w <= 0) x += (Math.floor(x / monitors[monitor].width) * monitors[monitor].width);
+ else if (x < 0) { w = x + w; x = 0; }
+ if (y + h <= 0) x += (Math.floor(y / monitors[monitor].height) * monitors[monitor].height);
+ else if (y < 0) { h = y + h; y = 0; }
+ // Truncate if offscreen
+ if (x + w > monitors[monitor].width) w = monitors[monitor].width - x;
+ if (y + h > monitors[monitor].height) h = monitors[monitor].height - y;
+
+ if(c.length == 0) c = initialClass;
+ const iconName = substitute(c);
+ const appIcon = iconExists(iconName) ? Widget.Icon({
+ icon: iconName,
+ size: Math.min(w, h) * userOptions.overview.scale / 2.5,
+ }) : MaterialIcon('terminal', 'gigantic', {
+ css: `font-size: ${Math.min(w, h) * userOptions.overview.scale / 2.5}px`,
+ });
+ return Widget.Button({
+ attribute: {
+ address, x, y, w, h, ws: id,
+ updateIconSize: (self) => {
+ appIcon.size = Math.min(self.attribute.w, self.attribute.h) * userOptions.overview.scale / 2.5;
+ },
+ },
+ className: 'overview-tasks-window',
+ hpack: 'start',
+ vpack: 'start',
+ css: `
+ margin-left: ${Math.round(x * userOptions.overview.scale)}px;
+ margin-top: ${Math.round(y * userOptions.overview.scale)}px;
+ margin-right: -${Math.round((x + w) * userOptions.overview.scale)}px;
+ margin-bottom: -${Math.round((y + h) * userOptions.overview.scale)}px;
+ `,
+ onClicked: (self) => {
+ Hyprland.messageAsync(`dispatch focuswindow address:${address}`);
+ App.closeWindow('overview');
+ },
+ onMiddleClickRelease: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
+ onSecondaryClick: (button) => {
+ button.toggleClassName('overview-tasks-window-selected', true);
+ const menu = Widget.Menu({
+ className: 'menu',
+ children: [
+ Widget.MenuItem({
+ child: Widget.Label({
+ xalign: 0,
+ label: "Close (Middle-click)",
+ }),
+ onActivate: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Dump windows to workspace",
+ actionFunc: dumpToWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Swap windows with workspace",
+ actionFunc: swapWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ],
+ });
+ menu.connect("deactivate", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.connect("selection-done", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button
+ button.connect("destroy", () => menu.destroy());
+ },
+ child: Widget.Box({
+ homogeneous: true,
+ child: Widget.Box({
+ vertical: true,
+ vpack: 'center',
+ children: [
+ appIcon,
+ // TODO: Add xwayland tag instead of just having italics
+ Widget.Revealer({
+ transition: 'slide_right',
+ revealChild: revealInfoCondition,
+ child: Widget.Revealer({
+ transition: 'slide_down',
+ revealChild: revealInfoCondition,
+ child: Widget.Label({
+ maxWidthChars: 1, // Doesn't matter what number
+ truncate: 'end',
+ className: `margin-top-5 ${xwayland ? 'txt txt-italic' : 'txt'}`,
+ css: `
+ font-size: ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 14.6}px;
+ margin: 0px ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 10}px;
+ `,
+ // If the title is too short, include the class
+ label: (title.length <= 1 ? `${c}: ${title}` : title),
+ })
+ })
+ })
+ ]
+ })
+ }),
+ tooltipText: `${c}: ${title}`,
+ setup: (button) => {
+ setupCursorHoverGrab(button);
+
+ button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
+ button.drag_source_set_icon_name(substitute(c));
+
+ button.connect('drag-begin', (button) => { // On drag start, add the dragging class
+ button.toggleClassName('overview-tasks-window-dragging', true);
+ });
+ button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
+ data.set_text(address, address.length);
+ button.toggleClassName('overview-tasks-window-dragging', false);
+ });
+ },
+ });
+ }
+
+ const Workspace = (index) => {
+ // const fixed = Widget.Fixed({
+ // attribute: {
+ // put: (widget, x, y) => {
+ // fixed.put(widget, x, y);
+ // },
+ // move: (widget, x, y) => {
+ // fixed.move(widget, x, y);
+ // },
+ // }
+ // });
+ const fixed = Widget.Box({
+ attribute: {
+ put: (widget, x, y) => {
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by userOptions.overview.scale
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
+ `;
+ widget.css = newCss;
+ fixed.pack_start(widget, false, false, 0);
+ },
+ move: (widget, x, y) => {
+ if (!widget) return;
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by userOptions.overview.scale
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
+ `;
+ widget.css = newCss;
+ },
+ }
+ })
+ const WorkspaceNumber = ({ index, ...rest }) => Widget.Label({
+ className: 'overview-tasks-workspace-number',
+ label: `${index}`,
+ css: `
+ margin: ${Math.min(monitors[overviewMonitor].width, monitors[overviewMonitor].height) * userOptions.overview.scale * userOptions.overview.wsNumMarginScale}px;
+ font-size: ${monitors[overviewMonitor].height * userOptions.overview.scale * userOptions.overview.wsNumScale}px;
+ `,
+ setup: (self) => self.hook(Hyprland.active.workspace, (self) => {
+ // Update when going to new ws group
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
+ self.label = `${currentGroup * NUM_OF_WORKSPACES_SHOWN + index}`;
+ }),
+ ...rest,
+ })
+ const widget = Widget.Box({
+ className: 'overview-tasks-workspace',
+ vpack: 'center',
+ // Rounding and adding 1px to minimum width/height to work around scaling inaccuracy:
+ css: `
+ min-width: ${1 + Math.round(monitors[overviewMonitor].width * userOptions.overview.scale)}px;
+ min-height: ${1 + Math.round(monitors[overviewMonitor].height * userOptions.overview.scale)}px;
+ `,
+ children: [Widget.EventBox({
+ hexpand: true,
+ onPrimaryClick: () => {
+ Hyprland.messageAsync(`dispatch workspace ${index}`);
+ App.closeWindow('overview');
+ },
+ setup: (eventbox) => {
+ eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
+ eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ Hyprland.messageAsync(`dispatch movetoworkspacesilent ${index + offset},address:${data.get_text()}`)
+ overviewTick.setValue(!overviewTick.value);
+ });
+ },
+ child: Widget.Overlay({
+ child: Widget.Box({}),
+ overlays: [
+ WorkspaceNumber({ index: index, hpack: 'start', vpack: 'start' }),
+ fixed
+ ]
+ }),
+ })],
+ });
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0);
+ widget.clear = () => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ clientMap.forEach((client, address) => {
+ if (!client) return;
+ if ((client.attribute.ws <= offset || client.attribute.ws > offset + NUM_OF_WORKSPACES_SHOWN) ||
+ (client.attribute.ws == offset + index)) {
+ client.destroy();
+ client = null;
+ clientMap.delete(address);
+ }
+ });
+ }
+ widget.set = (clientJson, screenCoords) => {
+ let c = clientMap.get(clientJson.address);
+ if (c) {
+ if (c.attribute?.ws !== clientJson.workspace.id) {
+ c.destroy();
+ c = null;
+ clientMap.delete(clientJson.address);
+ }
+ else if (c) {
+ c.attribute.w = clientJson.size[0];
+ c.attribute.h = clientJson.size[1];
+ c.attribute.updateIconSize(c);
+ fixed.attribute.move(c,
+ Math.max(0, clientJson.at[0] * userOptions.overview.scale),
+ Math.max(0, clientJson.at[1] * userOptions.overview.scale)
+ );
+ return;
+ }
+ }
+ const newWindow = Window(clientJson, screenCoords);
+ if (newWindow === null) return;
+ // clientMap.set(clientJson.address, newWindow);
+ fixed.attribute.put(newWindow,
+ Math.max(0, newWindow.attribute.x * userOptions.overview.scale),
+ Math.max(0, newWindow.attribute.y * userOptions.overview.scale)
+ );
+ clientMap.set(clientJson.address, newWindow);
+ };
+ widget.unset = (clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ let c = clientMap.get(clientAddress);
+ if (!c) return;
+ c.destroy();
+ c = null;
+ clientMap.delete(clientAddress);
+ };
+ widget.show = () => {
+ fixed.show_all();
+ }
+ return widget;
+ };
+
+ const arr = (s, n) => {
+ const array = [];
+ for (let i = 0; i < n; i++)
+ array.push(s + i);
+
+ return array;
+ };
+
+ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
+ children: arr(startWorkspace, workspaces).map(Workspace),
+ attribute: {
+ workspaceGroup: Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN),
+ monitorMap: [],
+ getMonitorMap: (box) => {
+ execAsync('hyprctl -j monitors').then(monitors => {
+ box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => {
+ acc[item.id] = { x: item.x, y: item.y };
+ return acc;
+ }, {});
+ });
+ },
+ update: (box) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ Hyprland.messageAsync('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ kids.forEach(kid => kid.clear());
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ const childID = client.workspace.id - (offset + startWorkspace);
+ if (offset + startWorkspace <= client.workspace.id &&
+ client.workspace.id <= offset + startWorkspace + workspaces) {
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ if (kids[childID]) {
+ kids[childID].set(client, screenCoords);
+ }
+ continue;
+ }
+ }
+ kids.forEach(kid => kid.show());
+ }).catch(print);
+ },
+ updateWorkspace: (box, id) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ if (!( // Not in range, ignore
+ offset + startWorkspace <= id &&
+ id <= offset + startWorkspace + workspaces
+ )) return;
+ // if (!App.getWindow(windowName)?.visible) return;
+ Hyprland.messageAsync('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ if (client.workspace.id != id) continue;
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ kids[id - (offset + startWorkspace)]?.set(client, screenCoords);
+ }
+ kids[id - (offset + startWorkspace)]?.show();
+ }).catch(print);
+ },
+ },
+ setup: (box) => {
+ box.attribute.getMonitorMap(box);
+ box
+ .hook(overviewTick, (box) => box.attribute.update(box))
+ .hook(Hyprland, (box, clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const kids = box.get_children();
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ const id = client.workspace.id;
+
+ box.attribute.updateWorkspace(box, id);
+ kids[id - (offset + startWorkspace)]?.unset(clientAddress);
+ }, 'client-removed')
+ .hook(Hyprland, (box, clientAddress) => {
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ box.attribute.updateWorkspace(box, client.workspace.id);
+ }, 'client-added')
+ .hook(Hyprland.active.workspace, (box) => {
+ // Full update when going to new ws group
+ const previousGroup = box.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
+ if (currentGroup !== previousGroup) {
+ if (!App.getWindow(windowName) || !App.getWindow(windowName).visible) return;
+ box.attribute.update(box);
+ box.attribute.workspaceGroup = currentGroup;
+ }
+ })
+ .hook(App, (box, name, visible) => { // Update on open
+ if (name == 'overview' && visible) box.attribute.update(box);
+ })
+ },
+ });
+
+ return Widget.Revealer({
+ revealChild: true,
+ // hpack to prevent unneeded expansion in overview-tasks-workspace:
+ hpack: 'center',
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Box({
+ vertical: true,
+ className: 'overview-tasks',
+ children: Array.from({ length: userOptions.overview.numOfRows }, (_, index) =>
+ OverviewRow({
+ startWorkspace: 1 + index * userOptions.overview.numOfCols,
+ workspaces: userOptions.overview.numOfCols,
+ })
+ )
+ }),
+ });
+}
diff --git a/ags/modules/overview/searchbuttons.js b/ags/modules/overview/searchbuttons.js
new file mode 100644
index 00000000..58f51607
--- /dev/null
+++ b/ags/modules/overview/searchbuttons.js
@@ -0,0 +1,189 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+import { searchItem } from './searchitem.js';
+import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
+import GeminiService from '../../services/gemini.js';
+
+export const NoResultButton = () => searchItem({
+ materialIconName: 'Error',
+ name: "Search invalid",
+ content: "No results found!",
+ onActivate: () => {
+ App.closeWindow('overview');
+ },
+});
+
+export const DirectoryButton = ({ parentPath, name, type, icon }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Open',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open '${parentPath}/${name}'`, `&`]).catch(print);
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: icon,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const CalculationResultButton = ({ result, text }) => searchItem({
+ materialIconName: 'calculate',
+ name: `Math result`,
+ actionName: "Copy",
+ content: `${result}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ execAsync(['wl-copy', `${result}`]).catch(print);
+ },
+});
+
+export const DesktopEntryButton = (app) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Launch',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ app.launch();
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: app.iconName,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: app.name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
+ materialIconName: `${terminal ? 'terminal' : 'settings_b_roll'}`,
+ name: `Run command`,
+ actionName: `Execute ${terminal ? 'in terminal' : ''}`,
+ content: `${command}`,
+ onActivate: () => execAndClose(command, terminal),
+ extraClassName: 'techfont',
+})
+
+export const CustomCommandButton = ({ text = '' }) => searchItem({
+ materialIconName: 'settings_suggest',
+ name: 'Action',
+ actionName: 'Run',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ launchCustomCommand(text);
+ },
+});
+
+export const SearchButton = ({ text = '' }) => searchItem({
+ materialIconName: 'travel_explore',
+ name: 'Search the web',
+ actionName: 'Go',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ let search = userOptions.search.engineBaseUrl + text;
+ for (let site of userOptions.search.excludedSites) {
+ if (site) search += ` -site:${site}`;
+ }
+ execAsync(['bash', '-c', `xdg-open '${search}' &`]).catch(print);
+ },
+});
+
+export const AiButton = ({ text }) => searchItem({
+ materialIconName: 'chat_paste_go',
+ name: 'Ask Gemini',
+ actionName: 'Ask',
+ content: `${text}`,
+ onActivate: () => {
+ GeminiService.send(text);
+ App.closeWindow('overview');
+ App.openWindow('sideleft');
+ },
+});
diff --git a/ags/modules/overview/searchitem.js b/ags/modules/overview/searchitem.js
new file mode 100644
index 00000000..2a3303a4
--- /dev/null
+++ b/ags/modules/overview/searchitem.js
@@ -0,0 +1,65 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const searchItem = ({ materialIconName, name, actionName, content, onActivate, extraClassName = '', ...rest }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: `${actionName}`,
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: userOptions.animations.durationSmall,
+ child: actionText,
+ })
+ return Widget.Button({
+ className: `overview-search-result-btn txt ${extraClassName}`,
+ onClicked: onActivate,
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Label({
+ className: `icon-material overview-search-results-icon`,
+ label: `${materialIconName}`,
+ }),
+ Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-smallie txt-subtext',
+ label: `${name}`,
+ truncate: "end",
+ }),
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-norm',
+ label: `${content}`,
+ truncate: "end",
+ }),
+ ]
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ],
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ });
+}
diff --git a/ags/modules/overview/windowcontent.js b/ags/modules/overview/windowcontent.js
new file mode 100644
index 00000000..5eb6bb56
--- /dev/null
+++ b/ags/modules/overview/windowcontent.js
@@ -0,0 +1,213 @@
+const { Gdk, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
+const { execAsync, exec } = Utils;
+import { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
+import {
+ CalculationResultButton, CustomCommandButton, DirectoryButton,
+ DesktopEntryButton, ExecuteCommandButton, SearchButton, AiButton, NoResultButton,
+} from './searchbuttons.js';
+import { checkKeybind } from '../.widgetutils/keybind.js';
+import GeminiService from '../../services/gemini.js';
+
+// Add math funcs
+const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
+const pi = Math.PI;
+// trigonometric funcs for deg
+const sind = x => sin(x * pi / 180);
+const cosd = x => cos(x * pi / 180);
+const tand = x => tan(x * pi / 180);
+const cotd = x => cot(x * pi / 180);
+const asind = x => asin(x) * 180 / pi;
+const acosd = x => acos(x) * 180 / pi;
+const atand = x => atan(x) * 180 / pi;
+const acotd = x => acot(x) * 180 / pi;
+
+const MAX_RESULTS = 10;
+const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
+const OVERVIEW_WS_NUM_SCALE = 0.09;
+const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+
+function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+const OptionalOverview = async () => {
+ try {
+ return (await import('./overview_hyprland.js')).default();
+ } catch {
+ return Widget.Box({});
+ // return (await import('./overview_hyprland.js')).default();
+ }
+};
+
+const overviewContent = await OptionalOverview();
+
+export const SearchAndWindows = () => {
+ var _appSearchResults = [];
+
+ const resultsBox = Widget.Box({
+ className: 'overview-search-results',
+ vertical: true,
+ });
+ const resultsRevealer = Widget.Revealer({
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ transition: 'slide_down',
+ // duration: 200,
+ hpack: 'center',
+ child: resultsBox,
+ });
+ const entryPromptRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: true,
+ hpack: 'center',
+ child: Widget.Label({
+ className: 'overview-search-prompt txt-small txt',
+ label: 'Type to search'
+ }),
+ });
+
+ const entryIconRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ hpack: 'end',
+ child: Widget.Label({
+ className: 'txt txt-large icon-material overview-search-icon',
+ label: 'search',
+ }),
+ });
+
+ const entryIcon = Widget.Box({
+ className: 'overview-search-prompt-box',
+ setup: box => box.pack_start(entryIconRevealer, true, true, 0),
+ });
+
+ const entry = Widget.Entry({
+ className: 'overview-search-box txt-small txt',
+ hpack: 'center',
+ onAccept: (self) => { // This is when you hit Enter
+ resultsBox.children[0].onClicked();
+ },
+ onChange: (entry) => { // this is when you type
+ const isAction = entry.text[0] == '>';
+ const isDir = (['/', '~'].includes(entry.text[0]));
+ resultsBox.get_children().forEach(ch => ch.destroy());
+
+ // check empty if so then dont do stuff
+ if (entry.text == '') {
+ resultsRevealer.revealChild = false;
+ overviewContent.revealChild = true;
+ entryPromptRevealer.revealChild = true;
+ entryIconRevealer.revealChild = false;
+ entry.toggleClassName('overview-search-box-extended', false);
+ return;
+ }
+ const text = entry.text;
+ resultsRevealer.revealChild = true;
+ overviewContent.revealChild = false;
+ entryPromptRevealer.revealChild = false;
+ entryIconRevealer.revealChild = true;
+ entry.toggleClassName('overview-search-box-extended', true);
+ _appSearchResults = Applications.query(text);
+
+ // Calculate
+ if (userOptions.search.enableFeatures.mathResults && couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
+ try {
+ const fullResult = eval(text.replace(/\^/g, "**"));
+ resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ if (userOptions.search.enableFeatures.directorySearch && isDir) {
+ var contents = [];
+ contents = ls({ path: text, silent: true });
+ contents.forEach((item) => {
+ resultsBox.add(DirectoryButton(item));
+ })
+ }
+ if (userOptions.search.enableFeatures.actions && isAction) { // Eval on typing is dangerous, this is a workaround.
+ resultsBox.add(CustomCommandButton({ text: entry.text }));
+ }
+ // Add application entries
+ let appsToAdd = MAX_RESULTS;
+ _appSearchResults.forEach(app => {
+ if (appsToAdd == 0) return;
+ resultsBox.add(DesktopEntryButton(app));
+ appsToAdd--;
+ });
+
+ // Fallbacks
+ // if the first word is an actual command
+ if (userOptions.search.enableFeatures.commands && !isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
+ resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
+ }
+
+ // Add fallback: search
+ if (userOptions.search.enableFeatures.aiSearch)
+ resultsBox.add(AiButton({ text: entry.text }));
+ if (userOptions.search.enableFeatures.webSearch)
+ resultsBox.add(SearchButton({ text: entry.text }));
+ if (resultsBox.children.length == 0) resultsBox.add(NoResultButton());
+ resultsBox.show_all();
+ },
+ });
+ return Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Box({
+ hpack: 'center',
+ children: [
+ entry,
+ Widget.Box({
+ className: 'overview-search-icon-box',
+ setup: (box) => {
+ box.pack_start(entryPromptRevealer, true, true, 0)
+ },
+ }),
+ entryIcon,
+ ]
+ }),
+ overviewContent,
+ resultsRevealer,
+ ],
+ setup: (self) => self
+ .hook(App, (_b, name, visible) => {
+ if (name == 'overview' && !visible) {
+ resultsBox.children = [];
+ entry.set_text('');
+ }
+ })
+ .on('key-press-event', (widget, event) => { // Typing
+ const keyval = event.get_keyval()[1];
+ const modstate = event.get_state()[1];
+ if (checkKeybind(event, userOptions.keybinds.overview.altMoveLeft))
+ entry.set_position(Math.max(entry.get_position() - 1, 0));
+ else if (checkKeybind(event, userOptions.keybinds.overview.altMoveRight))
+ entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
+ else if (checkKeybind(event, userOptions.keybinds.overview.deleteToEnd)) {
+ const text = entry.get_text();
+ const pos = entry.get_position();
+ const newText = text.slice(0, pos);
+ entry.set_text(newText);
+ entry.set_position(newText.length);
+ }
+ else if (!(modstate & Gdk.ModifierType.CONTROL_MASK)) { // Ctrl not held
+ if (keyval >= 32 && keyval <= 126 && widget != entry) {
+ Utils.timeout(1, () => entry.grab_focus());
+ entry.set_text(entry.text + String.fromCharCode(keyval));
+ entry.set_position(-1);
+ }
+ }
+ })
+ ,
+ });
+};
diff --git a/ags/modules/screencorners/main.js b/ags/modules/screencorners/main.js
new file mode 100644
index 00000000..83e09c0b
--- /dev/null
+++ b/ags/modules/screencorners/main.js
@@ -0,0 +1,38 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import { enableClickthrough } from "../.widgetutils/clickthrough.js";
+import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
+
+if(userOptions.appearance.fakeScreenRounding === 2) Hyprland.connect('event', (service, name, data) => {
+ if (name == 'fullscreen') {
+ const monitor = Hyprland.active.monitor.id;
+ if (data == '1') {
+ for (const window of App.windows) {
+ if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
+ App.closeWindow(window.name);
+ }
+ }
+ } else {
+ for (const window of App.windows) {
+ if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
+ App.openWindow(window.name);
+ }
+ }
+ }
+ }
+})
+
+export default (monitor = 0, where = 'bottom left', useOverlayLayer = true) => {
+ const positionString = where.replace(/\s/, ""); // remove space
+ return Widget.Window({
+ monitor,
+ name: `corner${positionString}${monitor}`,
+ layer: useOverlayLayer ? 'overlay' : 'top',
+ anchor: where.split(' '),
+ exclusivity: 'ignore',
+ visible: true,
+ child: RoundedCorner(positionString, { className: 'corner-black', }),
+ setup: enableClickthrough,
+ });
+}
+
diff --git a/ags/modules/session/main.js b/ags/modules/session/main.js
new file mode 100644
index 00000000..30c5ab6f
--- /dev/null
+++ b/ags/modules/session/main.js
@@ -0,0 +1,14 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import SessionScreen from "./sessionscreen.js";
+import PopupWindow from '../.widgethacks/popupwindow.js';
+
+export default (id = 0) => PopupWindow({ // On-screen keyboard
+ monitor: id,
+ name: `session${id}`,
+ visible: false,
+ keymode: 'on-demand',
+ layer: 'overlay',
+ exclusivity: 'ignore',
+ anchor: ['top', 'bottom', 'left', 'right'],
+ child: SessionScreen({ id: id }),
+})
diff --git a/ags/modules/session/sessionscreen.js b/ags/modules/session/sessionscreen.js
new file mode 100644
index 00000000..1160047f
--- /dev/null
+++ b/ags/modules/session/sessionscreen.js
@@ -0,0 +1,134 @@
+// This is for the cool memory indicator on the sidebar
+// For the right pill of the bar, see system.js
+const { Gdk, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { monitors } from '../.commondata/hyprlanddata.js';
+
+const { exec, execAsync } = Utils;
+
+const SessionButton = (name, icon, command, props = {}, colorid = 0) => {
+ const buttonDescription = Widget.Revealer({
+ vpack: 'end',
+ transitionDuration: userOptions.animations.durationSmall,
+ transition: 'slide_down',
+ revealChild: false,
+ child: Widget.Label({
+ className: 'txt-smaller session-button-desc',
+ label: name,
+ }),
+ });
+ return Widget.Button({
+ onClicked: command,
+ className: `session-button session-color-${colorid}`,
+ child: Widget.Overlay({
+ className: 'session-button-box',
+ child: Widget.Label({
+ vexpand: true,
+ className: 'icon-material',
+ label: icon,
+ }),
+ overlays: [
+ buttonDescription,
+ ]
+ }),
+ onHover: (button) => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ buttonDescription.revealChild = true;
+ },
+ onHoverLost: (button) => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ buttonDescription.revealChild = false;
+ },
+ setup: (self) => self
+ .on('focus-in-event', (self) => {
+ buttonDescription.revealChild = true;
+ self.toggleClassName('session-button-focused', true);
+ })
+ .on('focus-out-event', (self) => {
+ buttonDescription.revealChild = false;
+ self.toggleClassName('session-button-focused', false);
+ })
+ ,
+ ...props,
+ });
+}
+
+export default ({ id = 0 }) => {
+ // lock, logout, sleep
+ const lockButton = SessionButton('Lock', 'lock', () => { closeWindowOnAllMonitors('session'); execAsync(['loginctl', 'lock-session']).catch(print) }, {}, 1);
+ const logoutButton = SessionButton('Logout', 'logout', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'pkill Hyprland || pkill sway || pkill niri || loginctl terminate-user $USER']).catch(print) }, {}, 2);
+ const sleepButton = SessionButton('Sleep', 'sleep', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl suspend || loginctl suspend']).catch(print) }, {}, 3);
+ // hibernate, shutdown, reboot
+ const hibernateButton = SessionButton('Hibernate', 'downloading', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl hibernate || loginctl hibernate']).catch(print) }, {}, 4);
+ const shutdownButton = SessionButton('Shutdown', 'power_settings_new', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl poweroff || loginctl poweroff']).catch(print) }, {}, 5);
+ const rebootButton = SessionButton('Reboot', 'restart_alt', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl reboot || loginctl reboot']).catch(print) }, {}, 6);
+ const cancelButton = SessionButton('Cancel', 'close', () => closeWindowOnAllMonitors('session'), { className: 'session-button-cancel' }, 7);
+
+ const sessionDescription = Widget.Box({
+ vertical: true,
+ css: 'margin-bottom: 0.682rem;',
+ children: [
+ Widget.Label({
+ className: 'txt-title txt',
+ label: 'Session',
+ }),
+ Widget.Label({
+ justify: Gtk.Justification.CENTER,
+ className: 'txt-small txt',
+ label: 'Use arrow keys to navigate.\nEnter to select, Esc to cancel.'
+ }),
+ ]
+ });
+ const SessionButtonRow = (children) => Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-15',
+ children: children,
+ });
+ const sessionButtonRows = [
+ SessionButtonRow([lockButton, logoutButton, sleepButton]),
+ SessionButtonRow([hibernateButton, shutdownButton, rebootButton]),
+ SessionButtonRow([cancelButton]),
+ ]
+ return Widget.Box({
+ className: 'session-bg',
+ css: `
+ min-width: ${monitors[id].width}px;
+ min-height: ${monitors[id].height}px;
+ `, // idk why but height = screen height doesn't fill
+ vertical: true,
+ children: [
+ Widget.EventBox({
+ onPrimaryClick: () => closeWindowOnAllMonitors('session'),
+ onSecondaryClick: () => closeWindowOnAllMonitors('session'),
+ onMiddleClick: () => closeWindowOnAllMonitors('session'),
+ }),
+ Widget.Box({
+ hpack: 'center',
+ vexpand: true,
+ vertical: true,
+ children: [
+ Widget.Box({
+ vpack: 'center',
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ sessionDescription,
+ ...sessionButtonRows,
+ ]
+ })
+ ]
+ })
+ ],
+ setup: (self) => self
+ .hook(App, (_b, name, visible) => {
+ if (visible) lockButton.grab_focus(); // Lock is the default option
+ })
+ ,
+ });
+}
diff --git a/ags/modules/sideleft/apis/ai_chatmessage.js b/ags/modules/sideleft/apis/ai_chatmessage.js
new file mode 100644
index 00000000..4b3a702e
--- /dev/null
+++ b/ags/modules/sideleft/apis/ai_chatmessage.js
@@ -0,0 +1,365 @@
+const { Gdk, Gio, GLib, Gtk } = imports.gi;
+import GtkSource from "gi://GtkSource?version=3.0";
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Icon, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import md2pango from '../../.miscutils/md2pango.js';
+import { darkMode } from "../../.miscutils/system.js";
+
+const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`;
+const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/assets/themes/sourceviewtheme${darkMode.value ? '' : '-light'}.xml`;
+const CUSTOM_SCHEME_ID = `custom${darkMode.value ? '' : '-light'}`;
+const USERNAME = GLib.get_user_name();
+
+/////////////////////// Custom source view colorscheme /////////////////////////
+
+function loadCustomColorScheme(filePath) {
+ // Read the XML file content
+ const file = Gio.File.new_for_path(filePath);
+ const [success, contents] = file.load_contents(null);
+
+ if (!success) {
+ logError('Failed to load the XML file.');
+ return;
+ }
+
+ // Parse the XML content and set the Style Scheme
+ const schemeManager = GtkSource.StyleSchemeManager.get_default();
+ schemeManager.append_search_path(file.get_parent().get_path());
+}
+loadCustomColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH);
+
+//////////////////////////////////////////////////////////////////////////////
+
+function substituteLang(str) {
+ const subs = [
+ { from: 'javascript', to: 'js' },
+ { from: 'bash', to: 'sh' },
+ ];
+ for (const { from, to } of subs) {
+ if (from === str) return to;
+ }
+ return str;
+}
+
+const HighlightedCode = (content, lang) => {
+ const buffer = new GtkSource.Buffer();
+ const sourceView = new GtkSource.View({
+ buffer: buffer,
+ wrap_mode: Gtk.WrapMode.NONE
+ });
+ const langManager = GtkSource.LanguageManager.get_default();
+ let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
+ if (displayLang) {
+ buffer.set_language(displayLang);
+ }
+ const schemeManager = GtkSource.StyleSchemeManager.get_default();
+ buffer.set_style_scheme(schemeManager.get_scheme(CUSTOM_SCHEME_ID));
+ buffer.set_text(content, -1);
+ return sourceView;
+}
+
+const TextBlock = (content = '') => Label({
+ hpack: 'fill',
+ className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
+ useMarkup: true,
+ xalign: 0,
+ wrap: true,
+ selectable: true,
+ label: content,
+});
+
+Utils.execAsync(['bash', '-c', `rm -rf ${LATEX_DIR}`])
+ .then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`]))
+ .catch(print);
+const Latex = (content = '') => {
+ const latexViewArea = Box({
+ // vscroll: 'never',
+ // hscroll: 'automatic',
+ // homogeneous: true,
+ attribute: {
+ render: async (self, text) => {
+ if (text.length == 0) return;
+ const styleContext = self.get_style_context();
+ const fontSize = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const timeSinceEpoch = Date.now();
+ const fileName = `${timeSinceEpoch}.tex`;
+ const outFileName = `${timeSinceEpoch}-symbolic.svg`;
+ const outIconName = `${timeSinceEpoch}-symbolic`;
+ const scriptFileName = `${timeSinceEpoch}-render.sh`;
+ const filePath = `${LATEX_DIR}/${fileName}`;
+ const outFilePath = `${LATEX_DIR}/${outFileName}`;
+ const scriptFilePath = `${LATEX_DIR}/${scriptFileName}`;
+
+ Utils.writeFile(text, filePath).catch(print);
+ // Since MicroTex doesn't support file path input properly, we gotta cat it
+ // And escaping such a command is a fucking pain so I decided to just generate a script
+ // Note: MicroTex doesn't support `&=`
+ // You can add this line in the middle for debugging: echo "$text" > ${filePath}.tmp
+ const renderScript = `#!/usr/bin/env bash
+text=$(cat ${filePath} | sed 's/$/ \\\\\\\\/g' | sed 's/&=/=/g')
+cd /opt/MicroTeX
+./LaTeX -headless -input="$text" -output=${outFilePath} -textsize=${fontSize * 1.1} -padding=0 -maxwidth=${latexViewArea.get_allocated_width() * 0.85} > /dev/null 2>&1
+sed -i 's/fill="rgb(0%, 0%, 0%)"/style="fill:#000000"/g' ${outFilePath}
+sed -i 's/stroke="rgb(0%, 0%, 0%)"/stroke="${darkMode.value ? '#ffffff' : '#000000'}"/g' ${outFilePath}
+`;
+ Utils.writeFile(renderScript, scriptFilePath).catch(print);
+ Utils.exec(`chmod a+x ${scriptFilePath}`)
+ Utils.timeout(100, () => {
+ Utils.exec(`bash ${scriptFilePath}`);
+ Gtk.IconTheme.get_default().append_search_path(LATEX_DIR);
+
+ self.child?.destroy();
+ self.child = Gtk.Image.new_from_icon_name(outIconName, 0);
+ })
+ }
+ },
+ setup: (self) => self.attribute.render(self, content).catch(print),
+ });
+ const wholeThing = Box({
+ className: 'sidebar-chat-latex',
+ homogeneous: true,
+ attribute: {
+ 'updateText': (text) => {
+ latexViewArea.attribute.render(latexViewArea, text).catch(print);
+ }
+ },
+ children: [Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ child: latexViewArea
+ })]
+ })
+ return wholeThing;
+}
+
+const CodeBlock = (content = '', lang = 'txt') => {
+ if (lang == 'tex' || lang == 'latex') {
+ return Latex(content);
+ }
+ const topBar = Box({
+ className: 'sidebar-chat-codeblock-topbar',
+ children: [
+ Label({
+ label: lang,
+ className: 'sidebar-chat-codeblock-topbar-txt',
+ }),
+ Box({
+ hexpand: true,
+ }),
+ Button({
+ className: 'sidebar-chat-codeblock-topbar-btn',
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon('content_copy', 'small'),
+ Label({
+ label: 'Copy',
+ })
+ ]
+ }),
+ onClicked: (self) => {
+ const buffer = sourceView.get_buffer();
+ const copyContent = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false); // TODO: fix this
+ execAsync([`wl-copy`, `${copyContent}`]).catch(print);
+ },
+ }),
+ ]
+ })
+ // Source view
+ const sourceView = HighlightedCode(content, lang);
+
+ const codeBlock = Box({
+ attribute: {
+ 'updateText': (text) => {
+ sourceView.get_buffer().set_text(text, -1);
+ }
+ },
+ className: 'sidebar-chat-codeblock',
+ vertical: true,
+ children: [
+ topBar,
+ Box({
+ className: 'sidebar-chat-codeblock-code',
+ homogeneous: true,
+ children: [Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ child: sourceView,
+ })],
+ })
+ ]
+ })
+
+ // const schemeIds = styleManager.get_scheme_ids();
+
+ // print("Available Style Schemes:");
+ // for (let i = 0; i < schemeIds.length; i++) {
+ // print(schemeIds[i]);
+ // }
+ return codeBlock;
+}
+
+const Divider = () => Box({
+ className: 'sidebar-chat-divider',
+})
+
+const MessageContent = (content) => {
+ const contentBox = Box({
+ vertical: true,
+ attribute: {
+ 'fullUpdate': (self, content, useCursor = false) => {
+ // Clear and add first text widget
+ const children = contentBox.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+ contentBox.add(TextBlock())
+ // Loop lines. Put normal text in markdown parser
+ // and put code into code highlighter (TODO)
+ let lines = content.split('\n');
+ let lastProcessed = 0;
+ let inCode = false;
+ for (const [index, line] of lines.entries()) {
+ // Code blocks
+ const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
+ if (codeBlockRegex.test(line)) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ const blockContent = lines.slice(lastProcessed, index).join('\n');
+ if (!inCode) {
+ lastLabel.label = md2pango(blockContent);
+ contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
+ }
+ else {
+ lastLabel.attribute.updateText(blockContent);
+ contentBox.add(TextBlock());
+ }
+
+ lastProcessed = index + 1;
+ inCode = !inCode;
+ }
+ // Breaks
+ const dividerRegex = /^\s*---/;
+ if (!inCode && dividerRegex.test(line)) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ const blockContent = lines.slice(lastProcessed, index).join('\n');
+ lastLabel.label = md2pango(blockContent);
+ contentBox.add(Divider());
+ contentBox.add(TextBlock());
+ lastProcessed = index + 1;
+ }
+ }
+ if (lastProcessed < lines.length) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ let blockContent = lines.slice(lastProcessed, lines.length).join('\n');
+ if (!inCode)
+ lastLabel.label = `${md2pango(blockContent)}${useCursor ? userOptions.ai.writingCursor : ''}`;
+ else
+ lastLabel.attribute.updateText(blockContent);
+ }
+ // Debug: plain text
+ // contentBox.add(Label({
+ // hpack: 'fill',
+ // className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
+ // useMarkup: false,
+ // xalign: 0,
+ // wrap: true,
+ // selectable: true,
+ // label: '------------------------------\n' + md2pango(content),
+ // }))
+ contentBox.show_all();
+ }
+ }
+ });
+ contentBox.attribute.fullUpdate(contentBox, content, false);
+ return contentBox;
+}
+
+export const ChatMessage = (message, modelName = 'Model') => {
+ const TextSkeleton = (extraClassName = '') => Box({
+ className: `sidebar-chat-message-skeletonline ${extraClassName}`,
+ })
+ const messageContentBox = MessageContent(message.content);
+ const messageLoadingSkeleton = Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: Array.from({ length: 3 }, (_, id) => TextSkeleton(`sidebar-chat-message-skeletonline-offset${id}`)),
+ })
+ const messageArea = Stack({
+ homogeneous: message.role !== 'user',
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: {
+ 'thinking': messageLoadingSkeleton,
+ 'message': messageContentBox,
+ },
+ shown: message.thinking ? 'thinking' : 'message',
+ });
+ const thisMessage = Box({
+ className: 'sidebar-chat-message',
+ homogeneous: true,
+ children: [
+ Box({
+ vertical: true,
+ children: [
+ Label({
+ hpack: 'start',
+ xalign: 0,
+ className: `txt txt-bold sidebar-chat-name sidebar-chat-name-${message.role == 'user' ? 'user' : 'bot'}`,
+ wrap: true,
+ useMarkup: true,
+ label: (message.role == 'user' ? USERNAME : modelName),
+ }),
+ Box({
+ homogeneous: true,
+ className: 'sidebar-chat-messagearea',
+ children: [messageArea]
+ })
+ ],
+ setup: (self) => self
+ .hook(message, (self, isThinking) => {
+ messageArea.shown = message.thinking ? 'thinking' : 'message';
+ }, 'notify::thinking')
+ .hook(message, (self) => { // Message update
+ messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
+ }, 'notify::content')
+ .hook(message, (label, isDone) => { // Remove the cursor
+ messageContentBox.attribute.fullUpdate(messageContentBox, message.content, false);
+ }, 'notify::done')
+ ,
+ })
+ ]
+ });
+ return thisMessage;
+}
+
+export const SystemMessage = (content, commandName, scrolledWindow) => {
+ const messageContentBox = MessageContent(content);
+ const thisMessage = Box({
+ className: 'sidebar-chat-message',
+ children: [
+ Box({
+ vertical: true,
+ children: [
+ Label({
+ xalign: 0,
+ hpack: 'start',
+ className: 'txt txt-bold sidebar-chat-name sidebar-chat-name-system',
+ wrap: true,
+ label: `System • ${commandName}`,
+ }),
+ messageContentBox,
+ ],
+ })
+ ],
+ });
+ return thisMessage;
+}
diff --git a/ags/modules/sideleft/apis/booru.js b/ags/modules/sideleft/apis/booru.js
new file mode 100644
index 00000000..340c5d34
--- /dev/null
+++ b/ags/modules/sideleft/apis/booru.js
@@ -0,0 +1,523 @@
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { fileExists } from '../../.miscutils/files.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
+import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
+import BooruService from '../../../services/booru.js';
+import { chatEntry } from '../apiwidgets.js';
+import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
+import { SystemMessage } from './ai_chatmessage.js';
+
+const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
+const USER_CACHE_DIR = GLib.get_user_cache_dir();
+
+// Create cache folder and clear pics from previous session
+Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
+Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
+
+const TagButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => { chatEntry.buffer.text += `${command} ` },
+ setup: setupCursorHover,
+ label: command,
+});
+
+const CommandButton = (command, displayName = command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: displayName,
+});
+
+export const booruTabIcon = Box({
+ hpack: 'center',
+ homogeneous: true,
+ children: [
+ MaterialIcon('gallery_thumbnail', 'norm'),
+ ]
+});
+
+const BooruInfo = () => {
+ const booruLogo = Label({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ label: 'gallery_thumbnail',
+ })
+ return Box({
+ vertical: true,
+ vexpand: true,
+ className: 'spacing-v-15',
+ children: [
+ booruLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Anime booru',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by yande.re and konachan',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+export const BooruSettings = () => MarginRevealer({
+ transition: 'slide_down',
+ revealChild: true,
+ child: Box({
+ vertical: true,
+ className: 'sidebar-chat-settings',
+ children: [
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ className: 'sidebar-chat-settings-toggles',
+ children: [
+ ConfigToggle({
+ icon: 'menstrual_health',
+ name: 'Lewds',
+ desc: `Shows naughty stuff when enabled.\nYa like those? Add this to user_options.js:
+'sidebar': {
+ 'image': {
+ 'allowNsfw': true,
+ }
+},`,
+ initValue: BooruService.nsfw,
+ onChange: (self, newValue) => {
+ BooruService.nsfw = newValue;
+ },
+ extraSetup: (self) => self.hook(BooruService, (self) => {
+ self.attribute.enabled.value = BooruService.nsfw;
+ }, 'notify::nsfw')
+ }),
+ ConfigToggle({
+ icon: 'sell',
+ name: 'Save in folder by tags',
+ desc: 'Saves images in folders by their tags',
+ initValue: userOptions.sidebar.image.saveInFolderByTags,
+ onChange: (self, newValue) => {
+ userOptions.sidebar.image.saveInFolderByTags = newValue;
+ },
+ }),
+ ]
+ })
+ ]
+ })
+});
+
+const booruWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ BooruInfo(),
+ BooruSettings(),
+ ]
+ })
+});
+
+const BooruPage = (taglist, serviceName = 'Booru') => {
+ const PageState = (icon, name) => Box({
+ className: 'spacing-h-5 txt',
+ children: [
+ Label({
+ className: 'sidebar-waifu-txt txt-smallie',
+ xalign: 0,
+ label: name,
+ }),
+ MaterialIcon(icon, 'norm'),
+ ]
+ })
+ const ImageAction = ({ name, icon, action }) => Button({
+ className: 'sidebar-waifu-image-action txt-norm icon-material',
+ tooltipText: name,
+ label: icon,
+ onClicked: action,
+ setup: setupCursorHover,
+ })
+ const PreviewImage = (data, delay = 0) => {
+ const imageArea = Widget.DrawingArea({
+ className: 'sidebar-booru-image-drawingarea',
+ });
+ const imageBox = Box({
+ className: 'sidebar-booru-image',
+ // css: `background-image: url('${data.preview_url}');`,
+ attribute: {
+ 'update': (self, data, force = false) => {
+ const imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${data.md5}.${data.file_ext}`;
+ const widgetStyleContext = imageArea.get_style_context();
+ const widgetWidth = widgetStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const widgetHeight = widgetWidth / data.aspect_ratio;
+ imageArea.set_size_request(widgetWidth, widgetHeight);
+ const showImage = () => {
+ // const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, widgetWidth, widgetHeight);
+ const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(imagePath, widgetWidth, widgetHeight, false);
+ imageArea.connect("draw", (widget, cr) => {
+ const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+
+ // Draw a rounded rectangle
+ cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
+ cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
+ cr.closePath();
+ cr.clip();
+
+ // Paint image as bg
+ Gdk.cairo_set_source_pixbuf(cr, pixbuf, (widgetWidth - widgetWidth) / 2, (widgetHeight - widgetHeight) / 2);
+ cr.paint();
+ });
+ self.queue_draw();
+ imageRevealer.revealChild = true;
+ }
+ // Show
+ // const downloadCommand = `wget -O '${imagePath}' '${data.preview_url}'`;
+ const downloadCommand = `curl -L -o '${imagePath}' '${data.preview_url}'`;
+ if (!force && fileExists(imagePath)) showImage();
+ else Utils.timeout(delay, () => Utils.execAsync(['bash', '-c', downloadCommand])
+ .then(showImage)
+ .catch(print)
+ );
+ },
+ },
+ child: imageArea,
+ setup: (self) => {
+ Utils.timeout(1000, () => self.attribute.update(self, data));
+ }
+ });
+ const imageActions = Revealer({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ vpack: 'start',
+ className: 'sidebar-booru-image-actions spacing-h-3',
+ children: [
+ Box({ hexpand: true }),
+ ImageAction({
+ name: 'Go to file url',
+ icon: 'file_open',
+ action: () => execAsync(['xdg-open', `${data.file_url}`]).catch(print),
+ }),
+ ImageAction({
+ name: 'Go to source',
+ icon: 'open_in_new',
+ action: () => execAsync(['xdg-open', `${data.source}`]).catch(print),
+ }),
+ ImageAction({
+ name: 'Save image',
+ icon: 'save',
+ action: (self) => {
+ const currentTags = BooruService.queries.at(-1).realTagList.filter(tag => !tag.includes('rating:'));
+ const tagDirectory = currentTags.join('+');
+ let fileExtension = data.file_ext || 'jpg';
+ const saveCommand = `mkdir -p $(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}${userOptions.sidebar.image.saveInFolderByTags ? tagDirectory : ''} && curl -L -o $(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}${userOptions.sidebar.image.saveInFolderByTags ? (tagDirectory + '/') : ''}${data.md5}.${fileExtension} '${data.file_url}'`;
+ execAsync(['bash', '-c', saveCommand])
+ .then(() => self.label = 'done')
+ .catch(print);
+ },
+ }),
+ ]
+ })
+ });
+ const imageOverlay = Overlay({
+ passThrough: true,
+ child: imageBox,
+ overlays: [imageActions]
+ });
+ const imageRevealer = Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: EventBox({
+ onHover: () => { imageActions.revealChild = true },
+ onHoverLost: () => { imageActions.revealChild = false },
+ child: imageOverlay,
+ })
+ })
+ return imageRevealer;
+ }
+ const downloadState = Stack({
+ homogeneous: false,
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'api': PageState('api', 'Calling API'),
+ 'download': PageState('downloading', 'Downloading image'),
+ 'done': PageState('done', 'Finished!'),
+ 'error': PageState('error', 'Error'),
+ },
+ });
+ const downloadIndicator = MarginRevealer({
+ vpack: 'center',
+ transition: 'slide_left',
+ revealChild: true,
+ child: downloadState,
+ });
+ const pageHeading = Box({
+ vertical: true,
+ children: [
+ Box({
+ children: [
+ Label({
+ hpack: 'start',
+ className: `sidebar-booru-provider`,
+ label: `${serviceName}`,
+ truncate: 'end',
+ maxWidthChars: 20,
+ }),
+ Box({ hexpand: true }),
+ downloadIndicator,
+ ]
+ }),
+ Box({
+ children: [
+ Scrollable({
+ hexpand: true,
+ vscroll: 'never',
+ hscroll: 'automatic',
+ child: Box({
+ hpack: 'fill',
+ className: 'spacing-h-5',
+ children: [
+ ...taglist.map((tag) => TagButton(tag)),
+ Box({ hexpand: true }),
+ ]
+ })
+ }),
+ ]
+ })
+ ]
+ });
+ const pageImages = Box({
+ hpack: 'start',
+ homogeneous: true,
+ className: 'sidebar-booru-imagegrid',
+ })
+ const pageImageRevealer = Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: pageImages,
+ });
+ const thisPage = Box({
+ homogeneous: true,
+ className: 'sidebar-chat-message',
+ attribute: {
+ 'imagePath': '',
+ 'isNsfw': false,
+ 'update': (data, force = false) => { // TODO: Use columns. Sort min to max h/w ratio then greedily put em in...
+ // Sort by .aspect_ratio
+ data = data.sort(
+ (a, b) => a.aspect_ratio - b.aspect_ratio
+ );
+ if (data.length == 0) {
+ downloadState.shown = 'error';
+ return;
+ }
+ const imageColumns = userOptions.sidebar.image.columns;
+ const imageRows = data.length / imageColumns;
+
+ // Init cols
+ pageImages.children = Array.from(
+ { length: imageColumns },
+ (_, i) => Box({
+ attribute: { height: 0 },
+ vertical: true,
+ })
+ );
+ // Greedy add O(n^2) 😭
+ for (let i = 0; i < data.length; i++) {
+ // Find column with lowest length
+ let minHeight = Infinity;
+ let minIndex = -1;
+ for (let j = 0; j < imageColumns; j++) {
+ const height = pageImages.children[j].attribute.height;
+ if (height < minHeight) {
+ minHeight = height;
+ minIndex = j;
+ }
+ }
+ // Add image to it
+ pageImages.children[minIndex].pack_start(PreviewImage(data[i], minIndex), false, false, 0)
+ pageImages.children[minIndex].attribute.height += 1 / data[i].aspect_ratio; // we want height/width
+ }
+ pageImages.show_all();
+
+ // Reveal stuff
+ Utils.timeout(IMAGE_REVEAL_DELAY,
+ () => pageImageRevealer.revealChild = true
+ );
+ downloadIndicator.attribute.hide();
+ },
+ },
+ children: [Box({
+ vertical: true,
+ children: [
+ pageHeading,
+ Box({
+ vertical: true,
+ children: [pageImageRevealer],
+ })
+ ]
+ })],
+ });
+ return thisPage;
+}
+
+const booruContent = Box({
+ className: 'spacing-v-15',
+ vertical: true,
+ attribute: {
+ 'map': new Map(),
+ },
+ setup: (self) => self
+ .hook(BooruService, (box, id) => {
+ if (id === undefined) return;
+ const newPage = BooruPage(BooruService.queries[id].taglist, BooruService.queries[id].providerName);
+ box.add(newPage);
+ box.show_all();
+ box.attribute.map.set(id, newPage);
+ }, 'newResponse')
+ .hook(BooruService, (box, id) => {
+ if (id === undefined) return;
+ if (!BooruService.responses[id]) return;
+ box.attribute.map.get(id)?.attribute.update(BooruService.responses[id]);
+ }, 'updateResponse')
+ ,
+});
+
+export const booruView = Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ booruWelcome,
+ booruContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Scroll to bottom with new content if chat entry not focused
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ if (!chatEntry.hasFocus) return;
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+});
+
+const booruTags = Revealer({
+ revealChild: false,
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ hexpand: true,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ TagButton('( * )'),
+ TagButton('hololive'),
+ ]
+ })
+ }),
+ Box({ className: 'separator-line' }),
+ ]
+ })
+});
+
+export const booruCommands = Box({
+ className: 'spacing-h-5',
+ setup: (self) => {
+ self.pack_end(CommandButton('/clear'), false, false, 0);
+ self.pack_end(CommandButton('/next'), false, false, 0);
+ self.pack_start(Button({
+ className: 'sidebar-chat-chip-toggle',
+ setup: setupCursorHover,
+ label: 'Tags →',
+ onClicked: () => {
+ booruTags.revealChild = !booruTags.revealChild;
+ }
+ }), false, false, 0);
+ self.pack_start(booruTags, true, true, 0);
+ }
+});
+
+const clearChat = () => { // destroy!!
+ booruContent.attribute.map.forEach((value, key, map) => {
+ value.destroy();
+ value = null;
+ });
+}
+
+export const sendMessage = (text) => {
+ // Commands
+ if (text.startsWith('+')) { // Next page
+ const lastQuery = BooruService.queries.at(-1);
+ BooruService.fetch(`${lastQuery.realTagList.join(' ')} ${lastQuery.page + 1}`)
+ }
+ else if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/safe')) {
+ BooruService.nsfw = false;
+ const message = SystemMessage(`Switched to safe mode`, '/safe', booruView)
+ booruContent.add(message);
+ booruContent.show_all();
+ booruContent.attribute.map.set(Date.now(), message);
+ }
+ else if (text.startsWith('/lewd')) {
+ BooruService.nsfw = true;
+ const message = SystemMessage(`Tiddies enabled`, '/lewd', booruView)
+ booruContent.add(message);
+ booruContent.show_all();
+ booruContent.attribute.map.set(Date.now(), message);
+ }
+ else if (text.startsWith('/mode')) {
+ const mode = text.slice(text.indexOf(' ') + 1);
+ BooruService.mode = mode;
+ const message = SystemMessage(`Changed provider to ${BooruService.providerName}`, '/mode', booruView)
+ booruContent.add(message);
+ booruContent.show_all();
+ booruContent.attribute.map.set(Date.now(), message);
+ }
+ else if (text.startsWith('/next')) {
+ sendMessage('+')
+ }
+ }
+ else BooruService.fetch(text);
+}
\ No newline at end of file
diff --git a/ags/modules/sideleft/apis/chatgpt.js b/ags/modules/sideleft/apis/chatgpt.js
new file mode 100644
index 00000000..a223b122
--- /dev/null
+++ b/ags/modules/sideleft/apis/chatgpt.js
@@ -0,0 +1,355 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
+import GPTService from '../../../services/gpt.js';
+import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
+import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
+import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
+import { markdownTest } from '../../.miscutils/md2pango.js';
+import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { chatEntry } from '../apiwidgets.js';
+
+export const chatGPTTabIcon = Icon({
+ hpack: 'center',
+ icon: `openai-symbolic`,
+});
+
+const ProviderSwitcher = () => {
+ const ProviderChoice = (id, provider) => {
+ const providerSelected = MaterialIcon('check', 'norm', {
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.toggleClassName('invisible', GPTService.providerID !== id);
+ }, 'providerChanged')
+ });
+ return Button({
+ tooltipText: provider.description,
+ onClicked: () => {
+ GPTService.providerID = id;
+ providerList.revealChild = false;
+ indicatorChevron.label = 'expand_more';
+ },
+ child: Box({
+ className: 'spacing-h-10 txt',
+ children: [
+ Icon({
+ icon: provider['logo_name'],
+ className: 'txt-large'
+ }),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ label: provider.name,
+ }),
+ providerSelected
+ ],
+ }),
+ setup: setupCursorHover,
+ });
+ }
+ const indicatorChevron = MaterialIcon('expand_more', 'norm');
+ const indicatorButton = Button({
+ tooltipText: 'Select ChatGPT-compatible API provider',
+ child: Box({
+ className: 'spacing-h-10 txt',
+ children: [
+ MaterialIcon('cloud', 'norm'),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ label: GPTService.providerID,
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.label = `${GPTService.providers[GPTService.providerID]['name']}`;
+ }, 'providerChanged')
+ }),
+ indicatorChevron,
+ ]
+ }),
+ onClicked: () => {
+ providerList.revealChild = !providerList.revealChild;
+ indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more');
+ },
+ setup: setupCursorHover,
+ });
+ const providerList = Revealer({
+ revealChild: false,
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list',
+ children: [
+ Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
+ Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.children = Object.entries(GPTService.providers)
+ .map(([id, provider]) => ProviderChoice(id, provider));
+ }, 'initialized'),
+ })
+ ]
+ })
+ })
+ return Box({
+ hpack: 'center',
+ vertical: true,
+ className: 'sidebar-chat-providerswitcher',
+ children: [
+ indicatorButton,
+ providerList,
+ ]
+ })
+}
+
+const GPTInfo = () => {
+ const openAiLogo = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ icon: `openai-symbolic`,
+ });
+ return Box({
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ openAiLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Assistant (GPTs)',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Provider shown above',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.\n\nPrivacy: OpenAI claims they do not use your data\nwhen you use their API. Idk about others.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+const GPTSettings = () => MarginRevealer({
+ transition: 'slide_down',
+ revealChild: true,
+ extraSetup: (self) => self
+ .hook(GPTService, (self) => Utils.timeout(200, () => {
+ self.attribute.hide();
+ }), 'newMsg')
+ .hook(GPTService, (self) => Utils.timeout(200, () => {
+ self.attribute.show();
+ }), 'clear')
+ ,
+ child: Box({
+ vertical: true,
+ className: 'sidebar-chat-settings',
+ children: [
+ ConfigSegmentedSelection({
+ hpack: 'center',
+ icon: 'casino',
+ name: 'Randomness',
+ desc: 'The model\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
+ options: [
+ { value: 0.00, name: 'Precise', },
+ { value: 0.50, name: 'Balanced', },
+ { value: 1.00, name: 'Creative', },
+ ],
+ initIndex: 2,
+ onChange: (value, name) => {
+ GPTService.temperature = value;
+ },
+ }),
+ ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ className: 'sidebar-chat-settings-toggles',
+ children: [
+ ConfigToggle({
+ icon: 'model_training',
+ name: 'Enhancements',
+ desc: 'Tells the model:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
+ initValue: GPTService.assistantPrompt,
+ onChange: (self, newValue) => {
+ GPTService.assistantPrompt = newValue;
+ },
+ }),
+ ]
+ })
+ ]
+ })
+});
+
+export const OpenaiApiKeyInstructions = () => Box({
+ homogeneous: true,
+ children: [Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ setup: (self) => self
+ .hook(GPTService, (self, hasKey) => {
+ self.revealChild = (GPTService.key.length == 0);
+ }, 'hasKey')
+ ,
+ child: Button({
+ child: Label({
+ useMarkup: true,
+ wrap: true,
+ className: 'txt sidebar-chat-welcome-txt',
+ justify: Gtk.Justification.CENTER,
+ label: 'An API key is required\nYou can grab one here, then enter it below'
+ }),
+ setup: setupCursorHover,
+ onClicked: () => {
+ Utils.execAsync(['bash', '-c', `xdg-open ${GPTService.getKeyUrl}`]);
+ }
+ })
+ })]
+});
+
+const GPTWelcome = () => Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ GPTInfo(),
+ OpenaiApiKeyInstructions(),
+ GPTSettings(),
+ ]
+ })
+});
+
+export const chatContent = Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ setup: (self) => self
+ .hook(GPTService, (box, id) => {
+ const message = GPTService.messages[id];
+ if (!message) return;
+ box.add(ChatMessage(message, `Model (${GPTService.providers[GPTService.providerID]['name']})`))
+ }, 'newMsg')
+ ,
+});
+
+const clearChat = () => {
+ GPTService.clear();
+ const children = chatContent.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+}
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const chatGPTCommands = Box({
+ className: 'spacing-h-5',
+ children: [
+ Box({ hexpand: true }),
+ CommandButton('/key'),
+ CommandButton('/model'),
+ CommandButton('/clear'),
+ ]
+});
+
+export const sendMessage = (text) => {
+ // Check if text or API key is empty
+ if (text.length == 0) return;
+ if (GPTService.key.length == 0) {
+ GPTService.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${GPTService.keyPath}\``, 'API Key', chatGPTView));
+ text = '';
+ return;
+ }
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${GPTService.modelName}\``, '/model', chatGPTView))
+ else if (text.startsWith('/prompt')) {
+ const firstSpaceIndex = text.indexOf(' ');
+ const prompt = text.slice(firstSpaceIndex + 1);
+ if (firstSpaceIndex == -1 || prompt.length < 1) {
+ chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
+ }
+ else {
+ GPTService.addMessage('user', prompt)
+ }
+ }
+ else if (text.startsWith('/key')) {
+ const parts = text.split(' ');
+ if (parts.length == 1) chatContent.add(SystemMessage(
+ `Key stored in:\n\`${GPTService.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
+ '/key',
+ chatGPTView));
+ else {
+ GPTService.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${GPTService.keyPath}\``, '/key', chatGPTView));
+ }
+ }
+ else if (text.startsWith('/test'))
+ chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
+ else
+ chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
+ }
+ else {
+ GPTService.send(text);
+ }
+}
+
+export const chatGPTView = Box({
+ vertical: true,
+ children: [
+ ProviderSwitcher(),
+ Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ GPTWelcome(),
+ chatContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ if(!chatEntry.hasFocus) return;
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+ })
+ ]
+});
\ No newline at end of file
diff --git a/ags/modules/sideleft/apis/gemini.js b/ags/modules/sideleft/apis/gemini.js
new file mode 100644
index 00000000..8fd8923e
--- /dev/null
+++ b/ags/modules/sideleft/apis/gemini.js
@@ -0,0 +1,288 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
+import GeminiService from '../../../services/gemini.js';
+import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
+import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
+import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
+import { markdownTest } from '../../.miscutils/md2pango.js';
+import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
+import { chatEntry } from '../apiwidgets.js';
+
+const MODEL_NAME = `Gemini`;
+
+export const geminiTabIcon = Icon({
+ hpack: 'center',
+ icon: `google-gemini-symbolic`,
+})
+
+const GeminiInfo = () => {
+ const geminiLogo = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ icon: `google-gemini-symbolic`,
+ });
+ return Box({
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ geminiLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Assistant (Gemini)',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by Google',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren\'t linked to your account,\n but will be read by human reviewers to improve the model.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+export const GeminiSettings = () => MarginRevealer({
+ transition: 'slide_down',
+ revealChild: true,
+ extraSetup: (self) => self
+ .hook(GeminiService, (self) => Utils.timeout(200, () => {
+ self.attribute.hide();
+ }), 'newMsg')
+ .hook(GeminiService, (self) => Utils.timeout(200, () => {
+ self.attribute.show();
+ }), 'clear')
+ ,
+ child: Box({
+ vertical: true,
+ className: 'sidebar-chat-settings',
+ children: [
+ ConfigSegmentedSelection({
+ hpack: 'center',
+ icon: 'casino',
+ name: 'Randomness',
+ desc: 'Gemini\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
+ options: [
+ { value: 0.00, name: 'Precise', },
+ { value: 0.50, name: 'Balanced', },
+ { value: 1.00, name: 'Creative', },
+ ],
+ initIndex: 2,
+ onChange: (value, name) => {
+ GeminiService.temperature = value;
+ },
+ }),
+ ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ className: 'sidebar-chat-settings-toggles',
+ children: [
+ ConfigToggle({
+ icon: 'model_training',
+ name: 'Enhancements',
+ desc: 'Tells Gemini:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
+ initValue: GeminiService.assistantPrompt,
+ onChange: (self, newValue) => {
+ GeminiService.assistantPrompt = newValue;
+ },
+ }),
+ ConfigToggle({
+ icon: 'shield',
+ name: 'Safety',
+ desc: 'When turned off, tells the API (not the model) \nto not block harmful/explicit content',
+ initValue: GeminiService.safe,
+ onChange: (self, newValue) => {
+ GeminiService.safe = newValue;
+ },
+ }),
+ ConfigToggle({
+ icon: 'history',
+ name: 'History',
+ desc: 'Saves chat history\nMessages in previous chats won\'t show automatically, but they are there',
+ initValue: GeminiService.useHistory,
+ onChange: (self, newValue) => {
+ GeminiService.useHistory = newValue;
+ },
+ }),
+ ]
+ })
+ ]
+ })
+});
+
+export const GoogleAiInstructions = () => Box({
+ homogeneous: true,
+ children: [Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ setup: (self) => self
+ .hook(GeminiService, (self, hasKey) => {
+ self.revealChild = (GeminiService.key.length == 0);
+ }, 'hasKey')
+ ,
+ child: Button({
+ child: Label({
+ useMarkup: true,
+ wrap: true,
+ className: 'txt sidebar-chat-welcome-txt',
+ justify: Gtk.Justification.CENTER,
+ label: 'A Google AI API key is required\nYou can grab one here, then enter it below',
+ // setup: self => self.set_markup("This is a test link")
+ }),
+ setup: setupCursorHover,
+ onClicked: () => {
+ Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
+ }
+ })
+ })]
+});
+
+const geminiWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ GeminiInfo(),
+ GoogleAiInstructions(),
+ GeminiSettings(),
+ ]
+ })
+});
+
+export const chatContent = Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ setup: (self) => self
+ .hook(GeminiService, (box, id) => {
+ const message = GeminiService.messages[id];
+ if (!message) return;
+ box.add(ChatMessage(message, MODEL_NAME))
+ }, 'newMsg')
+ ,
+});
+
+const clearChat = () => {
+ GeminiService.clear();
+ const children = chatContent.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+}
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const geminiCommands = Box({
+ className: 'spacing-h-5',
+ children: [
+ Box({ hexpand: true }),
+ CommandButton('/key'),
+ CommandButton('/model'),
+ CommandButton('/clear'),
+ ]
+});
+
+export const sendMessage = (text) => {
+ // Check if text or API key is empty
+ if (text.length == 0) return;
+ if (GeminiService.key.length == 0) {
+ GeminiService.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${GeminiService.keyPath}\``, 'API Key', geminiView));
+ text = '';
+ return;
+ }
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/load')) {
+ clearChat();
+ GeminiService.loadHistory();
+ }
+ else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${GeminiService.modelName}\``, '/model', geminiView))
+ else if (text.startsWith('/prompt')) {
+ const firstSpaceIndex = text.indexOf(' ');
+ const prompt = text.slice(firstSpaceIndex + 1);
+ if (firstSpaceIndex == -1 || prompt.length < 1) {
+ chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
+ }
+ else {
+ GeminiService.addMessage('user', prompt)
+ }
+ }
+ else if (text.startsWith('/key')) {
+ const parts = text.split(' ');
+ if (parts.length == 1) chatContent.add(SystemMessage(
+ `Key stored in:\n\`${GeminiService.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
+ '/key',
+ geminiView));
+ else {
+ GeminiService.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${GeminiService.keyPath}\``, '/key', geminiView));
+ }
+ }
+ else if (text.startsWith('/test'))
+ chatContent.add(SystemMessage(markdownTest, `Markdown test`, geminiView));
+ else
+ chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView))
+ }
+ else {
+ GeminiService.send(text);
+ }
+}
+
+export const geminiView = Box({
+ homogeneous: true,
+ children: [Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ geminiWelcome,
+ chatContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => Utils.timeout(1, () => {
+ if(!chatEntry.hasFocus) return;
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ }))
+ }
+ })]
+});
\ No newline at end of file
diff --git a/ags/modules/sideleft/apis/waifu.js b/ags/modules/sideleft/apis/waifu.js
new file mode 100644
index 00000000..d7bc524b
--- /dev/null
+++ b/ags/modules/sideleft/apis/waifu.js
@@ -0,0 +1,419 @@
+// TODO: execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
+// to detect img dimensions
+
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { fileExists } from '../../.miscutils/files.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
+import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
+import WaifuService from '../../../services/waifus.js';
+import { darkMode } from '../../.miscutils/system.js';
+import { chatEntry } from '../apiwidgets.js';
+
+async function getImageViewerApp(preferredApp) {
+ Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
+ .then((output) => {
+ if (output != '') return preferredApp;
+ else return 'xdg-open';
+ })
+ .catch(print);
+}
+
+const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
+const IMAGE_VIEWER_APP = getImageViewerApp(userOptions.apps.imageViewer); // Gnome's image viewer cuz very comfortable zooming
+const USER_CACHE_DIR = GLib.get_user_cache_dir();
+
+// Create cache folder and clear pics from previous session
+Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
+Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const waifuTabIcon = Box({
+ hpack: 'center',
+ children: [
+ MaterialIcon('photo', 'norm'),
+ ]
+});
+
+const WaifuInfo = () => {
+ const waifuLogo = Label({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ label: 'photo',
+ })
+ return Box({
+ vertical: true,
+ vexpand: true,
+ className: 'spacing-v-15',
+ children: [
+ waifuLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Waifus',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by waifu.im + other APIs',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+const waifuWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ WaifuInfo(),
+ ]
+ })
+});
+
+const WaifuImage = (taglist) => {
+ const ImageState = (icon, name) => Box({
+ className: 'spacing-h-5 txt',
+ children: [
+ Box({ hexpand: true }),
+ Label({
+ className: 'sidebar-waifu-txt txt-smallie',
+ xalign: 0,
+ label: name,
+ }),
+ MaterialIcon(icon, 'norm'),
+ ]
+ })
+ const ImageAction = ({ name, icon, action }) => Button({
+ className: 'sidebar-waifu-image-action txt-norm icon-material',
+ tooltipText: name,
+ label: icon,
+ onClicked: action,
+ setup: setupCursorHover,
+ })
+ const downloadState = Stack({
+ homogeneous: false,
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationSmall,
+ children: {
+ 'api': ImageState('api', 'Calling API'),
+ 'download': ImageState('downloading', 'Downloading image'),
+ 'done': ImageState('done', 'Finished!'),
+ 'error': ImageState('error', 'Error'),
+ 'notfound': ImageState('error', 'Not found!'),
+ },
+ });
+ const downloadIndicator = MarginRevealer({
+ vpack: 'center',
+ transition: 'slide_left',
+ revealChild: true,
+ child: downloadState,
+ });
+ const blockHeading = Box({
+ hpack: 'fill',
+ className: 'spacing-h-5',
+ children: [
+ ...taglist.map((tag) => CommandButton(tag)),
+ Box({ hexpand: true }),
+ downloadIndicator,
+ ]
+ });
+ const blockImageActions = Revealer({
+ transition: 'crossfade',
+ revealChild: false,
+ child: Box({
+ vertical: true,
+ children: [
+ Box({
+ className: 'sidebar-waifu-image-actions spacing-h-3',
+ children: [
+ Box({ hexpand: true }),
+ ImageAction({
+ name: 'Go to source',
+ icon: 'link',
+ action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
+ }),
+ ImageAction({
+ name: 'Hoard',
+ icon: 'save',
+ action: (self) => {
+ execAsync(['bash', '-c', `mkdir -p $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`])
+ .then(() => self.label = 'done')
+ .catch(print);
+ },
+ }),
+ ImageAction({
+ name: 'Open externally',
+ icon: 'open_in_new',
+ action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
+ }),
+ ]
+ })
+ ],
+ })
+ })
+ const blockImage = Widget.DrawingArea({
+ className: 'sidebar-waifu-image',
+ });
+ const blockImageRevealer = Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: Box({
+ className: 'margin-top-5',
+ children: [Overlay({
+ child: Box({
+ homogeneous: true,
+ className: 'sidebar-waifu-image',
+ children: [blockImage],
+ }),
+ overlays: [blockImageActions],
+ })]
+ }),
+ });
+ const thisBlock = Box({
+ className: 'sidebar-chat-message',
+ attribute: {
+ 'imagePath': '',
+ 'isNsfw': false,
+ 'imageData': '',
+ 'update': (imageData, force = false) => {
+ thisBlock.attribute.imageData = imageData;
+ const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
+ thisBlock.attribute.isNsfw = is_nsfw;
+ if (status == 404) {
+ downloadState.shown = 'notfound';
+ return;
+ }
+ if (status != 200) {
+ downloadState.shown = 'error';
+ return;
+ }
+ thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
+ downloadState.shown = 'download';
+ // Width/height
+ const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
+ const widgetHeight = Math.ceil(widgetWidth * height / width);
+ blockImage.set_size_request(widgetWidth, widgetHeight);
+ const showImage = () => {
+ downloadState.shown = 'done';
+ const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
+ // const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
+
+ blockImage.set_size_request(widgetWidth, widgetHeight);
+ blockImage.connect("draw", (widget, cr) => {
+ const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+
+ // Draw a rounded rectangle
+ cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
+ cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
+ cr.closePath();
+ cr.clip();
+
+ // Paint image as bg
+ Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+ cr.paint();
+ });
+
+ // Reveal stuff
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => {
+ blockImageRevealer.revealChild = true;
+ })
+ Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
+ () => blockImageActions.revealChild = true
+ );
+ downloadIndicator.attribute.hide();
+ }
+ // Show
+ if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
+ else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
+ .then(showImage)
+ .catch(print);
+ thisBlock.css = `background-color: mix(${darkMode.value ? 'black' : 'white'}, ${dominant_color}, 0.97);`;
+ },
+ },
+ children: [
+ Box({
+ vertical: true,
+ children: [
+ blockHeading,
+ Box({
+ vertical: true,
+ hpack: 'start',
+ children: [blockImageRevealer],
+ })
+ ]
+ })
+ ],
+ });
+ return thisBlock;
+}
+
+const waifuContent = Box({
+ className: 'spacing-v-15',
+ vertical: true,
+ attribute: {
+ 'map': new Map(),
+ },
+ setup: (self) => self
+ .hook(WaifuService, (box, id) => {
+ if (id === undefined) return;
+ const newImageBlock = WaifuImage(WaifuService.queries[id]);
+ box.add(newImageBlock);
+ box.show_all();
+ box.attribute.map.set(id, newImageBlock);
+ }, 'newResponse')
+ .hook(WaifuService, (box, id) => {
+ if (id === undefined) return;
+ const data = WaifuService.responses[id];
+ if (!data) return;
+ const imageBlock = box.attribute.map.get(id);
+ imageBlock?.attribute.update(data);
+ }, 'updateResponse')
+ ,
+});
+
+export const waifuView = Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ waifuWelcome,
+ waifuContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ if (!chatEntry.hasFocus) return;
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+});
+
+const waifuTags = Revealer({
+ revealChild: false,
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ hexpand: true,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ CommandButton('waifu'),
+ CommandButton('maid'),
+ CommandButton('uniform'),
+ CommandButton('oppai'),
+ CommandButton('selfies'),
+ CommandButton('marin-kitagawa'),
+ CommandButton('raiden-shogun'),
+ CommandButton('mori-calliope'),
+ ]
+ })
+ }),
+ Box({ className: 'separator-line' }),
+ ]
+ })
+});
+
+export const waifuCommands = Box({
+ className: 'spacing-h-5',
+ setup: (self) => {
+ self.pack_end(CommandButton('/clear'), false, false, 0);
+ self.pack_start(Button({
+ className: 'sidebar-chat-chip-toggle',
+ setup: setupCursorHover,
+ label: 'Tags →',
+ onClicked: () => {
+ waifuTags.revealChild = !waifuTags.revealChild;
+ }
+ }), false, false, 0);
+ self.pack_start(waifuTags, true, true, 0);
+ }
+});
+
+const clearChat = () => { // destroy!!
+ waifuContent.attribute.map.forEach((value, key, map) => {
+ value.destroy();
+ value = null;
+ });
+}
+
+function newSimpleImageCall(name, url, width, height, dominantColor = '#9392A6') {
+ const timeSinceEpoch = Date.now();
+ const newImage = WaifuImage([`/${name}`]);
+ waifuContent.add(newImage);
+ waifuContent.attribute.map.set(timeSinceEpoch, newImage);
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage?.attribute.update({
+ status: 200,
+ url: url,
+ extension: '',
+ signature: timeSinceEpoch,
+ source: url,
+ dominant_color: dominantColor,
+ is_nsfw: false,
+ width: width,
+ height: height,
+ tags: [`/${name}`],
+ }, true));
+}
+
+export const sendMessage = (text) => {
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/test'))
+ newSimpleImageCall('test', 'https://picsum.photos/600/400', 300, 200);
+ else if (text.startsWith('/chino'))
+ newSimpleImageCall('chino', 'https://chino.pages.dev/chino', 300, 400, '#B2AEF3');
+ else if (text.startsWith('/place'))
+ newSimpleImageCall('place', 'https://placewaifu.com/image/400/600', 400, 600, '#F0A235');
+
+ }
+ else WaifuService.fetch(text);
+}
diff --git a/ags/modules/sideleft/apiwidgets.js b/ags/modules/sideleft/apiwidgets.js
new file mode 100644
index 00000000..a4884e35
--- /dev/null
+++ b/ags/modules/sideleft/apiwidgets.js
@@ -0,0 +1,223 @@
+const { Gtk, Gdk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { setupCursorHover, setupCursorHoverInfo } from '../.widgetutils/cursorhover.js';
+// APIs
+import GPTService from '../../services/gpt.js';
+import Gemini from '../../services/gemini.js';
+import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
+import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
+import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
+import { booruView, booruCommands, sendMessage as booruSendMessage, booruTabIcon } from './apis/booru.js';
+import { enableClickthrough } from "../.widgetutils/clickthrough.js";
+import { checkKeybind } from '../.widgetutils/keybind.js';
+const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
+
+import { widgetContent } from './sideleft.js';
+import { IconTabContainer } from '../.commonwidgets/tabcontainer.js';
+
+const EXPAND_INPUT_THRESHOLD = 30;
+const APILIST = {
+ 'gemini': {
+ name: 'Assistant (Gemini Pro)',
+ sendCommand: geminiSendMessage,
+ contentWidget: geminiView,
+ commandBar: geminiCommands,
+ tabIcon: geminiTabIcon,
+ placeholderText: 'Message Gemini...',
+ },
+ 'gpt': {
+ name: 'Assistant (GPTs)',
+ sendCommand: chatGPTSendMessage,
+ contentWidget: chatGPTView,
+ commandBar: chatGPTCommands,
+ tabIcon: chatGPTTabIcon,
+ placeholderText: 'Message the model...',
+ },
+ 'waifu': {
+ name: 'Waifus',
+ sendCommand: waifuSendMessage,
+ contentWidget: waifuView,
+ commandBar: waifuCommands,
+ tabIcon: waifuTabIcon,
+ placeholderText: 'Enter tags',
+ },
+ 'booru': {
+ name: 'Booru',
+ sendCommand: booruSendMessage,
+ contentWidget: booruView,
+ commandBar: booruCommands,
+ tabIcon: booruTabIcon,
+ placeholderText: 'Enter tags',
+ },
+}
+const APIS = userOptions.sidebar.pages.apis.order.map((apiName) => APILIST[apiName]);
+let currentApiId = 0;
+
+function apiSendMessage(textView) {
+ // Get text
+ const buffer = textView.get_buffer();
+ const [start, end] = buffer.get_bounds();
+ const text = buffer.get_text(start, end, true).trimStart();
+ if (!text || text.length == 0) return;
+ // Send
+ APIS[currentApiId].sendCommand(text)
+ // Reset
+ buffer.set_text("", -1);
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
+ chatEntry.set_valign(Gtk.Align.CENTER);
+}
+
+export const chatEntry = TextView({
+ hexpand: true,
+ wrapMode: Gtk.WrapMode.WORD_CHAR,
+ acceptsTab: false,
+ className: 'sidebar-chat-entry txt txt-smallie',
+ setup: (self) => self
+ .hook(App, (self, currentName, visible) => {
+ if (visible && currentName === 'sideleft') {
+ self.grab_focus();
+ }
+ })
+ .hook(GPTService, (self) => {
+ if (APIS[currentApiId].name != 'Assistant (GPTs)') return;
+ self.placeholderText = (GPTService.key.length > 0 ? 'Message the model...' : 'Enter API Key...');
+ }, 'hasKey')
+ .hook(Gemini, (self) => {
+ if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
+ self.placeholderText = (Gemini.key.length > 0 ? 'Message Gemini...' : 'Enter Google AI API Key...');
+ }, 'hasKey')
+ .on("key-press-event", (widget, event) => {
+ // Don't send when Shift+Enter
+ if (event.get_keyval()[1] === Gdk.KEY_Return) {
+ if (event.get_state()[1] !== 17) {// SHIFT_MASK doesn't work but 17 should be shift
+ apiSendMessage(widget);
+ return true;
+ }
+ return false;
+ }
+ // Keybinds
+ if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
+ widgetContent.cycleTab();
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
+ widgetContent.nextTab();
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
+ widgetContent.prevTab();
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
+ apiWidgets.attribute.nextTab();
+ return true;
+ }
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
+ apiWidgets.attribute.prevTab();
+ return true;
+ }
+ })
+ ,
+});
+
+chatEntry.get_buffer().connect("changed", (buffer) => {
+ const bufferText = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), true);
+ chatSendButton.toggleClassName('sidebar-chat-send-available', bufferText.length > 0);
+ chatPlaceholderRevealer.revealChild = (bufferText.length == 0);
+ if (buffer.get_line_count() > 1 || bufferText.length > EXPAND_INPUT_THRESHOLD) {
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', true);
+ chatEntry.set_valign(Gtk.Align.FILL);
+ chatPlaceholder.set_valign(Gtk.Align.FILL);
+ }
+ else {
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
+ chatEntry.set_valign(Gtk.Align.CENTER);
+ chatPlaceholder.set_valign(Gtk.Align.CENTER);
+ }
+});
+
+const chatEntryWrapper = Scrollable({
+ className: 'sidebar-chat-wrapper',
+ hscroll: 'never',
+ vscroll: 'always',
+ child: chatEntry,
+});
+
+const chatSendButton = Button({
+ className: 'txt-norm icon-material sidebar-chat-send',
+ vpack: 'end',
+ label: 'arrow_upward',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ APIS[currentApiId].sendCommand(chatEntry.get_buffer().text);
+ chatEntry.get_buffer().set_text("", -1);
+ },
+});
+
+const chatPlaceholder = Label({
+ className: 'txt-subtext txt-smallie margin-left-5',
+ hpack: 'start',
+ vpack: 'center',
+ label: APIS[currentApiId].placeholderText,
+});
+
+const chatPlaceholderRevealer = Revealer({
+ revealChild: true,
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: chatPlaceholder,
+ setup: enableClickthrough,
+});
+
+const textboxArea = Box({ // Entry area
+ className: 'sidebar-chat-textarea',
+ children: [
+ Overlay({
+ passThrough: true,
+ child: chatEntryWrapper,
+ overlays: [chatPlaceholderRevealer],
+ }),
+ Box({ className: 'width-10' }),
+ chatSendButton,
+ ]
+});
+
+const apiCommandStack = Stack({
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: APIS.reduce((acc, api) => {
+ acc[api.name] = api.commandBar;
+ return acc;
+ }, {}),
+})
+
+export const apiContentStack = IconTabContainer({
+ tabSwitcherClassName: 'sidebar-icontabswitcher',
+ className: 'margin-top-5',
+ iconWidgets: APIS.map((api) => api.tabIcon),
+ names: APIS.map((api) => api.name),
+ children: APIS.map((api) => api.contentWidget),
+ onChange: (self, id) => {
+ apiCommandStack.shown = APIS[id].name;
+ chatPlaceholder.label = APIS[id].placeholderText;
+ currentApiId = id;
+ }
+});
+
+function switchToTab(id) {
+ apiContentStack.shown.value = id;
+}
+
+const apiWidgets = Widget.Box({
+ attribute: {
+ 'nextTab': () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1)),
+ 'prevTab': () => switchToTab(Math.max(0, currentApiId - 1)),
+ },
+ vertical: true,
+ className: 'spacing-v-10',
+ homogeneous: false,
+ children: [
+ apiContentStack,
+ apiCommandStack,
+ textboxArea,
+ ],
+});
+
+export default apiWidgets;
diff --git a/ags/modules/sideleft/main.js b/ags/modules/sideleft/main.js
new file mode 100644
index 00000000..3127ea88
--- /dev/null
+++ b/ags/modules/sideleft/main.js
@@ -0,0 +1,18 @@
+import PopupWindow from '../.widgethacks/popupwindow.js';
+import SidebarLeft from "./sideleft.js";
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box } = Widget;
+import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
+
+export default () => PopupWindow({
+ keymode: 'on-demand',
+ anchor: ['left', 'top', 'bottom'],
+ name: 'sideleft',
+ layer: 'overlay',
+ child: Box({
+ children: [
+ SidebarLeft(),
+ clickCloseRegion({ name: 'sideleft', multimonitor: false, fillMonitor: 'horizontal' }),
+ ]
+ })
+});
diff --git a/ags/modules/sideleft/sideleft.js b/ags/modules/sideleft/sideleft.js
new file mode 100644
index 00000000..a3cc9340
--- /dev/null
+++ b/ags/modules/sideleft/sideleft.js
@@ -0,0 +1,121 @@
+const { Gdk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import toolBox from './toolbox.js';
+import apiWidgets from './apiwidgets.js';
+import { chatEntry } from './apiwidgets.js';
+import { TabContainer } from '../.commonwidgets/tabcontainer.js';
+import { checkKeybind } from '../.widgetutils/keybind.js';
+
+const SIDEBARTABS = {
+ 'apis': {
+ name: 'apis',
+ content: apiWidgets,
+ materialIcon: 'api',
+ friendlyName: 'APIs',
+ },
+ 'tools': {
+ name: 'tools',
+ content: toolBox,
+ materialIcon: 'home_repair_service',
+ friendlyName: 'Tools',
+ },
+}
+const CONTENTS = userOptions.sidebar.pages.order.map((tabName) => SIDEBARTABS[tabName])
+
+const pinButton = Button({
+ attribute: {
+ 'enabled': false,
+ 'toggle': (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled);
+
+ const sideleftWindow = App.getWindow('sideleft');
+ const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
+
+ sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
+
+ if (self.attribute.enabled) {
+ sideleftWindow.exclusivity = 'on-demad';
+ }
+ else {
+ sideleftWindow.exclusivity = 'normal';
+ }
+ },
+ },
+ vpack: 'start',
+ className: 'sidebar-pin',
+ child: MaterialIcon('push_pin', 'larger'),
+ tooltipText: 'Pin sidebar (Ctrl+P)',
+ onClicked: (self) => self.attribute.toggle(self),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideleft' && visible) self.grab_focus();
+ })
+ },
+})
+
+export const widgetContent = TabContainer({
+ icons: CONTENTS.map((item) => item.materialIcon),
+ names: CONTENTS.map((item) => item.friendlyName),
+ children: CONTENTS.map((item) => item.content),
+ className: 'sidebar-left spacing-v-10',
+ setup: (self) => self.hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideleft')
+ self.toggleClassName('sidebar-pinned', pinButton.attribute.enabled && visible);
+ }),
+});
+
+export default () => Box({
+ // vertical: true,
+ vexpand: true,
+ css: 'min-width: 2px;',
+ children: [
+ widgetContent,
+ ],
+ setup: (self) => self
+ .on('key-press-event', (widget, event) => { // Handle keybinds
+ if (checkKeybind(event, userOptions.keybinds.sidebar.pin))
+ pinButton.attribute.toggle(pinButton);
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
+ widgetContent.cycleTab();
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
+ widgetContent.nextTab();
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
+ widgetContent.prevTab();
+
+ if (widgetContent.attribute.names[widgetContent.attribute.shown.value] == 'APIs') { // If api tab is focused
+ // Focus entry when typing
+ if ((
+ !(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
+ widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
+ ||
+ ((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_v)
+ ) {
+ chatEntry.grab_focus();
+ const buffer = chatEntry.get_buffer();
+ buffer.set_text(buffer.text + String.fromCharCode(event.get_keyval()[1]), -1);
+ buffer.place_cursor(buffer.get_iter_at_offset(-1));
+ }
+ // Switch API type
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
+ const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
+ toSwitchTab.nextTab();
+ }
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
+ const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
+ toSwitchTab.prevTab();
+ }
+ }
+
+ })
+ ,
+});
diff --git a/ags/modules/sideleft/toolbox.js b/ags/modules/sideleft/toolbox.js
new file mode 100644
index 00000000..a21d195d
--- /dev/null
+++ b/ags/modules/sideleft/toolbox.js
@@ -0,0 +1,20 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Label, Scrollable } = Widget;
+import QuickScripts from './tools/quickscripts.js';
+import ColorPicker from './tools/colorpicker.js';
+import Name from './tools/name.js';
+
+export default Scrollable({
+ hscroll: "never",
+ vscroll: "automatic",
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ QuickScripts(),
+ ColorPicker(),
+ Box({ vexpand: true }),
+ Name(),
+ ]
+ })
+});
diff --git a/ags/modules/sideleft/tools/changeres.sh b/ags/modules/sideleft/tools/changeres.sh
new file mode 100644
index 00000000..333f0988
--- /dev/null
+++ b/ags/modules/sideleft/tools/changeres.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+# Function to get the current resolution
+get_current_resolution() {
+ local output
+ output=$(hyprctl monitors -j)
+ local width height refreshRate
+ width=$(echo "$output" | jq -r '.[0].width')
+ height=$(echo "$output" | jq -r '.[0].height')
+ refreshRate=$(echo "$output" | jq -r '.[0].refreshRate')
+ echo "$width $height $refreshRate"
+}
+
+# Function to update the Hyprland configuration with the new resolution
+update_resolution_config() {
+ local newWidth="$1"
+ local newHeight="$2"
+ local newRefreshRate="$3"
+ local currentRes
+ currentRes=$(get_current_resolution)
+ local width height refreshRate
+ width=${newWidth:-$(echo "$currentRes" | awk '{print $1}')}
+ height=${newHeight:-$(echo "$currentRes" | awk '{print $2}')}
+ refreshRate=${newRefreshRate:-$(echo "$currentRes" | awk '{print $3}')}
+
+ local modelineOutput
+ modelineOutput=$(gtf "$width" "$height" "$refreshRate")
+ local modeline
+ modeline=$(echo "$modelineOutput" | grep -oP 'Modeline "\K[^"]+')
+
+ if [ -z "$modeline" ]; then
+ echo "Failed to generate modeline"
+ exit 1
+ fi
+
+ # Extract the resolution and refresh rate from the modeline
+ local resolution
+ resolution=$(echo "$modeline" | grep -oP '^[0-9]+x[0-9]+')
+ local rate
+ rate=$(echo "$modeline" | grep -oP '[0-9]+.[0-9]+$')
+
+ if [ -z "$resolution" ] || [ -z "$rate" ]; then
+ echo "Failed to extract resolution or refresh rate from modeline"
+ exit 1
+ fi
+
+ local configPath="${HOME}/.config/hypr/hyprland/general.conf"
+ local newConfigContent
+ newConfigContent=$(sed "s/^monitor=.*$/monitor=eDP-1, $resolution@$rate, auto, 1/" "$configPath")
+
+ echo "$newConfigContent" > "$configPath"
+}
+
+# Main script
+echo "Welcome to the Resolution Configurator"
+echo ""
+echo " +---------------------------+"
+echo " | _____ |"
+echo " | | | |"
+echo " | | | |"
+echo " | |_____| |"
+echo " | |"
+echo " +---------------------------+"
+echo ""
+echo "Current resolution and refresh rate:"
+currentRes=$(get_current_resolution)
+width=$(echo "$currentRes" | awk '{print $1}')
+height=$(echo "$currentRes" | awk '{print $2}')
+refreshRate=$(echo "$currentRes" | awk '{print $3}')
+
+echo "Width: $width px"
+echo "Height: $height px"
+echo "Refresh Rate: $refreshRate Hz"
+
+echo ""
+
+read -p "Enter new width (or press Enter to keep current width): " newWidth
+read -p "Enter new height (or press Enter to keep current height): " newHeight
+read -p "Enter new refresh rate (or press Enter to keep current refresh rate): " newRefreshRate
+
+# Validate inputs (if provided)
+if [[ ! "$newWidth" =~ ^[0-9]+$ && -n "$newWidth" ]]; then
+ echo "Invalid width value."
+ exit 1
+fi
+
+if [[ ! "$newHeight" =~ ^[0-9]+$ && -n "$newHeight" ]]; then
+ echo "Invalid height value."
+ exit 1
+fi
+
+if [[ ! "$newRefreshRate" =~ ^[0-9]+$ && -n "$newRefreshRate" ]]; then
+ echo "Invalid refresh rate value."
+ exit 1
+fi
+
+update_resolution_config "$newWidth" "$newHeight" "$newRefreshRate"
+
+echo "Resolution updated successfully."
diff --git a/ags/modules/sideleft/tools/color.js b/ags/modules/sideleft/tools/color.js
new file mode 100644
index 00000000..568a04c3
--- /dev/null
+++ b/ags/modules/sideleft/tools/color.js
@@ -0,0 +1,198 @@
+// It's weird, I know
+const { Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+import { clamp } from '../../.miscutils/mathfuncs.js';
+
+export class ColorPickerSelection extends Service {
+ static {
+ Service.register(this, {
+ 'picked': [],
+ 'assigned': ['int'],
+ 'hue': [],
+ 'sl': [],
+ });
+ }
+
+ _hue = 198;
+ _xAxis = 94;
+ _yAxis = 80;
+
+ get hue() { return this._hue; }
+ set hue(value) {
+ this._hue = clamp(value, 0, 360);
+ this.emit('hue');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ get xAxis() { return this._xAxis; }
+ set xAxis(value) {
+ this._xAxis = clamp(value, 0, 100);
+ this.emit('sl');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ get yAxis() { return this._yAxis; }
+ set yAxis(value) {
+ this._yAxis = clamp(value, 0, 100);
+ this.emit('sl');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ setColorFromHex(hexString, id) {
+ const hsl = hexToHSL(hexString);
+ this._hue = hsl.hue;
+ this._xAxis = hsl.saturation;
+ // this._yAxis = hsl.lightness;
+ this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness;
+ // console.log(this._hue, this._xAxis, this._yAxis)
+ this.emit('assigned', id);
+ this.emit('changed');
+ }
+
+ constructor() {
+ super();
+ this.emit('changed');
+ }
+}
+
+
+export function hslToRgbValues(h, s, l) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+ let r, g, b;
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ const hue2rgb = (p, q, t) => {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ const to255 = x => Math.round(x * 255);
+ r = to255(r);
+ g = to255(g);
+ b = to255(b);
+ return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`;
+ // return `rgb(${r},${g},${b})`;
+}
+export function hslToHex(h, s, l) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+ let r, g, b;
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ const hue2rgb = (p, q, t) => {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ const toHex = x => {
+ const hex = Math.round(x * 255).toString(16);
+ return hex.length === 1 ? "0" + hex : hex;
+ };
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+}
+
+// export function hexToHSL(hex) {
+// // Remove the '#' if present
+// hex = hex.replace(/^#/, '');
+// // Parse the hex value into RGB components
+// const bigint = parseInt(hex, 16);
+// const r = (bigint >> 16) & 255;
+// const g = (bigint >> 8) & 255;
+// const b = bigint & 255;
+// // Normalize RGB values to range [0, 1]
+// const normalizedR = r / 255;
+// const normalizedG = g / 255;
+// const normalizedB = b / 255;
+// // Find the maximum and minimum values
+// const max = Math.max(normalizedR, normalizedG, normalizedB);
+// const min = Math.min(normalizedR, normalizedG, normalizedB);
+// // Calculate the lightness
+// const lightness = (max + min) / 2;
+// // If the color is grayscale, set saturation to 0
+// if (max === min) {
+// return {
+// hue: 0,
+// saturation: 0,
+// lightness: lightness * 100 // Convert to percentage
+// };
+// }
+// // Calculate the saturation
+// const d = max - min;
+// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min);
+// // Calculate the hue
+// let hue;
+// if (max === normalizedR) {
+// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60;
+// } else if (max === normalizedG) {
+// hue = ((normalizedB - normalizedR) / d + 2) * 60;
+// } else {
+// hue = ((normalizedR - normalizedG) / d + 4) * 60;
+// }
+// return {
+// hue: Math.round(hue),
+// saturation: Math.round(saturation * 100), // Convert to percentage
+// lightness: Math.round(lightness * 100) // Convert to percentage
+// };
+// }
+
+export function hexToHSL(hex) {
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+
+ var r = parseInt(result[1], 16);
+ var g = parseInt(result[2], 16);
+ var b = parseInt(result[3], 16);
+
+ r /= 255, g /= 255, b /= 255;
+ var max = Math.max(r, g, b), min = Math.min(r, g, b);
+ var h, s, l = (max + min) / 2;
+
+ if (max == min) {
+ h = s = 0; // achromatic
+ } else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+
+ s = s * 100;
+ s = Math.round(s);
+ l = l * 100;
+ l = Math.round(l);
+ h = Math.round(360 * h);
+
+ return {
+ hue: h,
+ saturation: s,
+ lightness: l
+ };
+}
diff --git a/ags/modules/sideleft/tools/colorpicker.js b/ags/modules/sideleft/tools/colorpicker.js
new file mode 100644
index 00000000..f8c0c859
--- /dev/null
+++ b/ags/modules/sideleft/tools/colorpicker.js
@@ -0,0 +1,283 @@
+// TODO: Make selection update when entry changes
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget;
+import SidebarModule from './module.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+
+import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js';
+import { clamp } from '../../.miscutils/mathfuncs.js';
+
+export default () => {
+ const selectedColor = new ColorPickerSelection();
+ function shouldUseBlackColor() {
+ return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) &&
+ selectedColor.yAxis > 60);
+ }
+ const colorBlack = 'rgba(0,0,0,0.9)';
+ const colorWhite = 'rgba(255,255,255,0.9)';
+ const hueRange = Box({
+ homogeneous: true,
+ className: 'sidebar-module-colorpicker-wrapper',
+ children: [Box({
+ className: 'sidebar-module-colorpicker-hue',
+ css: `background: linear-gradient(to bottom, #ff6666, #ffff66, #66dd66, #66ffff, #6666ff, #ff66ff, #ff6666);`,
+ })],
+ });
+ const hueSlider = Box({
+ vpack: 'start',
+ className: 'sidebar-module-colorpicker-cursorwrapper',
+ css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`,
+ homogeneous: true,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-hue-cursor',
+ })],
+ setup: (self) => self.hook(selectedColor, () => {
+ const widgetHeight = hueRange.children[0].get_allocated_height();
+ self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`)
+ }),
+ });
+ const hueSelector = Box({
+ children: [EventBox({
+ child: Overlay({
+ child: hueRange,
+ overlays: [hueSlider],
+ }),
+ attribute: {
+ clicked: false,
+ setHue: (self, event) => {
+ const widgetHeight = hueRange.children[0].get_allocated_height();
+ const [_, cursorX, cursorY] = event.get_coords();
+ const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1);
+ selectedColor.hue = Math.round(cursorYPercent * 360);
+ }
+ },
+ setup: (self) => self
+ .on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ self.attribute.setHue(self, event);
+ })
+ .on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ self.attribute.setHue(self, event);
+ })
+ .on('button-release-event', (self) => self.attribute.clicked = false)
+ ,
+ })]
+ });
+ const saturationAndLightnessRange = Box({
+ homogeneous: true,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness',
+ attribute: {
+ update: (self) => {
+ // css: `background: linear-gradient(to right, #ffffff, color);`,
+ self.setCss(`background:
+ linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)),
+ linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)});
+ `);
+ },
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ })],
+ });
+ const saturationAndLightnessCursor = Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper',
+ children: [Box({
+ vpack: 'start',
+ hpack: 'start',
+ homogeneous: true,
+ css: `
+ margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
+ margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
+ `, // Why 13.636rem? see class name in stylesheet
+ attribute: {
+ update: (self) => {
+ const allocation = saturationAndLightnessRange.children[0].get_allocation();
+ self.setCss(`
+ margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
+ margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
+ `); // Why 13.636rem? see class name in stylesheet
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness-cursor',
+ css: `
+ background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
+ border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
+ `,
+ attribute: {
+ update: (self) => {
+ self.setCss(`
+ background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
+ border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
+ `);
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ })],
+ })]
+ });
+ const saturationAndLightnessSelector = Box({
+ homogeneous: true,
+ className: 'sidebar-module-colorpicker-saturationandlightness-wrapper',
+ children: [EventBox({
+ child: Overlay({
+ child: saturationAndLightnessRange,
+ overlays: [saturationAndLightnessCursor],
+ }),
+ attribute: {
+ clicked: false,
+ setSaturationAndLightness: (self, event) => {
+ const allocation = saturationAndLightnessRange.children[0].get_allocation();
+ const [_, cursorX, cursorY] = event.get_coords();
+ const cursorXPercent = clamp(cursorX / allocation.width, 0, 1);
+ const cursorYPercent = clamp(cursorY / allocation.height, 0, 1);
+ selectedColor.xAxis = Math.round(cursorXPercent * 100);
+ selectedColor.yAxis = Math.round(100 - cursorYPercent * 100);
+ }
+ },
+ setup: (self) => self
+ .on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ self.attribute.setSaturationAndLightness(self, event);
+ })
+ .on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ self.attribute.setSaturationAndLightness(self, event);
+ })
+ .on('button-release-event', (self) => self.attribute.clicked = false)
+ ,
+ })]
+ });
+ const resultColorBox = Box({
+ className: 'sidebar-module-colorpicker-result-box',
+ homogeneous: true,
+ css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`,
+ children: [Label({
+ className: 'txt txt-small',
+ label: 'Result',
+ }),],
+ attribute: {
+ update: (self) => {
+ self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`);
+ self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`)
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ });
+ const ResultBox = ({ colorSystemName, updateCallback, copyCallback }) => Box({
+ children: [
+ Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ Label({
+ xalign: 0,
+ className: 'txt-tiny',
+ label: colorSystemName,
+ }),
+ Overlay({
+ child: Entry({
+ widthChars: 10,
+ className: 'txt-small techfont',
+ attribute: {
+ id: 0,
+ update: updateCallback,
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ // .on('activate', (self) => {
+ // const newColor = self.text;
+ // if (newColor.length != 7) return;
+ // selectedColor.setColorFromHex(self.text, self.attribute.id);
+ // })
+ ,
+ }),
+ })
+ ]
+ }),
+ Button({
+ child: MaterialIcon('content_copy', 'norm'),
+ onClicked: (self) => {
+ copyCallback(self);
+ self.child.label = 'done';
+ Utils.timeout(1000, () => self.child.label = 'content_copy');
+ },
+ setup: setupCursorHover,
+ })
+ ]
+ });
+ const resultHex = ResultBox({
+ colorSystemName: 'Hex',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]),
+ })
+ const resultRgb = ResultBox({
+ colorSystemName: 'RGB',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]),
+ })
+ const resultHsl = ResultBox({
+ colorSystemName: 'HSL',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`;
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]),
+ })
+ const result = Box({
+ className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt',
+ hexpand: true,
+ vertical: true,
+ children: [
+ resultColorBox,
+ resultHex,
+ resultRgb,
+ resultHsl,
+ ]
+ })
+ return SidebarModule({
+ icon: MaterialIcon('colorize', 'norm'),
+ name: 'Inaccurate Color picker',
+ revealChild: false,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ hueSelector,
+ saturationAndLightnessSelector,
+ result,
+ ]
+ })
+ });
+}
\ No newline at end of file
diff --git a/ags/modules/sideleft/tools/module.js b/ags/modules/sideleft/tools/module.js
new file mode 100644
index 00000000..2cba49ec
--- /dev/null
+++ b/ags/modules/sideleft/tools/module.js
@@ -0,0 +1,57 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+const { Box, Button, Icon, Label, Revealer } = Widget;
+
+export default ({
+ icon,
+ name,
+ child,
+ revealChild = true,
+}) => {
+ const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm');
+ const header = Button({
+ onClicked: () => {
+ content.revealChild = !content.revealChild;
+ headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
+ },
+ setup: setupCursorHover,
+ child: Box({
+ className: 'txt spacing-h-10',
+ children: [
+ icon,
+ Label({
+ className: 'txt-norm',
+ label: `${name}`,
+ useMarkup: true,
+ }),
+ Box({
+ hexpand: true,
+ }),
+ Box({
+ className: 'sidebar-module-btn-arrow',
+ homogeneous: true,
+ children: [headerButtonIcon],
+ })
+ ]
+ })
+ });
+ const content = Revealer({
+ revealChild: revealChild,
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ className: 'margin-top-5',
+ homogeneous: true,
+ children: [child],
+ }),
+ });
+ return Box({
+ className: 'sidebar-module',
+ vertical: true,
+ children: [
+ header,
+ content,
+ ]
+ });
+}
diff --git a/ags/modules/sideleft/tools/name.js b/ags/modules/sideleft/tools/name.js
new file mode 100644
index 00000000..72eb198d
--- /dev/null
+++ b/ags/modules/sideleft/tools/name.js
@@ -0,0 +1,26 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+const { execAsync, exec } = Utils;
+const { Box, Button, CenterBox, EventBox, Icon, Label, Scrollable } = Widget;
+
+export default () => Box({
+ className: 'txt sidebar-module techfont',
+ children: [
+ Label({
+ label: 'illogical-impulse'
+ }),
+ Box({ hexpand: true }),
+ Button({
+ className: 'sidebar-module-btn-arrow',
+ onClicked: () => execAsync(['xdg-open', 'https://github.com/end-4/dots-hyprland']).catch(print),
+ child: Icon({
+ className: 'txt txt-norm',
+ icon: 'github-symbolic',
+ }),
+ setup: setupCursorHover,
+ })
+ ]
+})
diff --git a/ags/modules/sideleft/tools/quickscripts.js b/ags/modules/sideleft/tools/quickscripts.js
new file mode 100644
index 00000000..09fdb1b1
--- /dev/null
+++ b/ags/modules/sideleft/tools/quickscripts.js
@@ -0,0 +1,103 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget;
+import SidebarModule from './module.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+
+import { distroID, isArchDistro, isDebianDistro, hasFlatpak } from '../../.miscutils/system.js';
+
+const scripts = [
+ {
+ icon: 'desktop-symbolic',
+ name: 'Change screen resolution',
+ command: `bash ${App.configDir}/modules/sideleft/tools/changeres.sh`,
+ enabled: true,
+ },
+ {
+ icon: 'nixos-symbolic',
+ name: 'Trim system generations to 5',
+ command: `sudo ${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 system`,
+ enabled: distroID == 'nixos',
+ },
+ {
+ icon: 'nixos-symbolic',
+ name: 'Trim home manager generations to 5',
+ command: `${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 home-manager`,
+ enabled: distroID == 'nixos',
+ },
+ {
+ icon: 'ubuntu-symbolic',
+ name: 'Update packages',
+ command: `sudo apt update && sudo apt upgrade -y`,
+ enabled: isDebianDistro,
+ },
+ {
+ icon: 'fedora-symbolic',
+ name: 'Update packages',
+ command: `sudo dnf upgrade -y`,
+ enabled: distroID == 'fedora',
+ },
+ {
+ icon: 'arch-symbolic',
+ name: 'Update packages',
+ command: `sudo pacman -Syyu`,
+ enabled: isArchDistro,
+ },
+ {
+ icon: 'arch-symbolic',
+ name: 'Remove orphan packages',
+ command: `sudo pacman -R $(pacman -Qdtq)`,
+ enabled: isArchDistro,
+ },
+ {
+ icon: 'flatpak-symbolic',
+ name: 'Uninstall unused flatpak packages',
+ command: `flatpak uninstall --unused`,
+ enabled: hasFlatpak,
+ },
+];
+
+export default () => SidebarModule({
+ icon: MaterialIcon('code', 'norm'),
+ name: 'Quick scripts',
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: scripts.map((script) => {
+ if (!script.enabled) return null;
+ const scriptStateIcon = MaterialIcon('not_started', 'norm');
+ return Box({
+ className: 'spacing-h-5 txt',
+ children: [
+ Icon({
+ className: 'sidebar-module-btn-icon txt-large',
+ icon: script.icon,
+ }),
+ Label({
+ className: 'txt-small',
+ hpack: 'start',
+ hexpand: true,
+ label: script.name,
+ tooltipText: script.command,
+ }),
+ Button({
+ className: 'sidebar-module-scripts-button',
+ child: scriptStateIcon,
+ onClicked: () => {
+ closeEverything();
+ execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${script.command}"`]).catch(print)
+ .then(() => {
+ scriptStateIcon.label = 'done';
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ ],
+ })
+ }),
+ })
+});
diff --git a/ags/modules/sideright/calendar.js b/ags/modules/sideright/calendar.js
new file mode 100644
index 00000000..db66df1b
--- /dev/null
+++ b/ags/modules/sideright/calendar.js
@@ -0,0 +1,203 @@
+const { Gio } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label } = Widget;
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+
+import { TodoWidget } from "./todolist.js";
+import { getCalendarLayout } from "./calendar_layout.js";
+
+let calendarJson = getCalendarLayout(undefined, true);
+let monthshift = 0;
+
+function getDateInXMonthsTime(x) {
+ var currentDate = new Date(); // Get the current date
+ var targetMonth = currentDate.getMonth() + x; // Calculate the target month
+ var targetYear = currentDate.getFullYear(); // Get the current year
+
+ // Adjust the year and month if necessary
+ targetYear += Math.floor(targetMonth / 12);
+ targetMonth = (targetMonth % 12 + 12) % 12;
+
+ // Create a new date object with the target year and month
+ var targetDate = new Date(targetYear, targetMonth, 1);
+
+ // Set the day to the last day of the month to get the desired date
+ // targetDate.setDate(0);
+
+ return targetDate;
+}
+
+const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
+ { day: 'Mo', today: 0 },
+ { day: 'Tu', today: 0 },
+ { day: 'We', today: 0 },
+ { day: 'Th', today: 0 },
+ { day: 'Fr', today: 0 },
+ { day: 'Sa', today: 0 },
+ { day: 'Su', today: 0 },
+]
+
+const CalendarDay = (day, today) => Widget.Button({
+ className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
+ child: Widget.Overlay({
+ child: Box({}),
+ overlays: [Label({
+ hpack: 'center',
+ className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
+ label: String(day),
+ })],
+ })
+})
+
+const CalendarWidget = () => {
+ const calendarMonthYear = Widget.Button({
+ className: 'txt txt-large sidebar-calendar-monthyear-btn',
+ onClicked: () => shiftCalendarXMonths(0),
+ setup: (button) => {
+ button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
+ setupCursorHover(button);
+ }
+ });
+ const addCalendarChildren = (box, calendarJson) => {
+ const children = box.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+ box.children = calendarJson.map((row, i) => Widget.Box({
+ className: 'spacing-h-5',
+ children: row.map((day, i) => CalendarDay(day.day, day.today)),
+ }))
+ }
+ function shiftCalendarXMonths(x) {
+ if (x == 0) monthshift = 0;
+ else monthshift += x;
+ var newDate;
+ if (monthshift == 0) newDate = new Date();
+ else newDate = getDateInXMonthsTime(monthshift);
+
+ calendarJson = getCalendarLayout(newDate, (monthshift == 0));
+ calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
+ addCalendarChildren(calendarDays, calendarJson);
+ }
+ const calendarHeader = Widget.Box({
+ className: 'spacing-h-5 sidebar-calendar-header',
+ setup: (box) => {
+ box.pack_start(calendarMonthYear, false, false, 0);
+ box.pack_end(Widget.Box({
+ className: 'spacing-h-5',
+ children: [
+ Button({
+ className: 'sidebar-calendar-monthshift-btn',
+ onClicked: () => shiftCalendarXMonths(-1),
+ child: MaterialIcon('chevron_left', 'norm'),
+ setup: setupCursorHover,
+ }),
+ Button({
+ className: 'sidebar-calendar-monthshift-btn',
+ onClicked: () => shiftCalendarXMonths(1),
+ child: MaterialIcon('chevron_right', 'norm'),
+ setup: setupCursorHover,
+ })
+ ]
+ }), false, false, 0);
+ }
+ })
+ const calendarDays = Widget.Box({
+ hexpand: true,
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (box) => {
+ addCalendarChildren(box, calendarJson);
+ }
+ });
+ return Widget.EventBox({
+ onScrollUp: () => shiftCalendarXMonths(-1),
+ onScrollDown: () => shiftCalendarXMonths(1),
+ child: Widget.Box({
+ hpack: 'center',
+ children: [
+ Widget.Box({
+ hexpand: true,
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ calendarHeader,
+ Widget.Box({
+ homogeneous: true,
+ className: 'spacing-h-5',
+ children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
+ }),
+ calendarDays,
+ ]
+ })
+ ]
+ })
+ });
+};
+
+const defaultShown = 'calendar';
+const contentStack = Widget.Stack({
+ hexpand: true,
+ children: {
+ 'calendar': CalendarWidget(),
+ 'todo': TodoWidget(),
+ // 'stars': Widget.Label({ label: 'GitHub feed will be here' }),
+ },
+ transition: 'slide_up_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ setup: (stack) => Utils.timeout(1, () => {
+ stack.shown = defaultShown;
+ })
+})
+
+const StackButton = (stackItemName, icon, name) => Widget.Button({
+ className: 'button-minsize sidebar-navrail-btn txt-small spacing-h-5',
+ onClicked: (button) => {
+ contentStack.shown = stackItemName;
+ const kids = button.get_parent().get_children();
+ for (let i = 0; i < kids.length; i++) {
+ if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
+ else button.toggleClassName('sidebar-navrail-btn-active', true);
+ }
+ },
+ child: Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ Label({
+ className: `txt icon-material txt-hugeass`,
+ label: icon,
+ }),
+ Label({
+ label: name,
+ className: 'txt txt-smallie',
+ }),
+ ]
+ }),
+ setup: (button) => Utils.timeout(1, () => {
+ setupCursorHover(button);
+ button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
+ })
+});
+
+export const ModuleCalendar = () => Box({
+ className: 'sidebar-group spacing-h-5',
+ setup: (box) => {
+ box.pack_start(Box({
+ vpack: 'center',
+ homogeneous: true,
+ vertical: true,
+ className: 'sidebar-navrail spacing-v-10',
+ children: [
+ StackButton('calendar', 'calendar_month', 'Calendar'),
+ StackButton('todo', 'done_outline', 'To Do'),
+ // StackButton(box, 'stars', 'star', 'GitHub'),
+ ]
+ }), false, false, 0);
+ box.pack_end(contentStack, false, false, 0);
+ }
+})
+
diff --git a/ags/modules/sideright/calendar_layout.js b/ags/modules/sideright/calendar_layout.js
new file mode 100644
index 00000000..35b481d6
--- /dev/null
+++ b/ags/modules/sideright/calendar_layout.js
@@ -0,0 +1,85 @@
+function checkLeapYear(year) {
+ return (
+ year % 400 == 0 ||
+ (year % 4 == 0 && year % 100 != 0));
+}
+
+function getMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;
+ if (month == 2 && leapYear) return 29;
+ if (month == 2 && !leapYear) return 28;
+ return 30;
+}
+
+function getNextMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if (month == 1 && leapYear) return 29;
+ if (month == 1 && !leapYear) return 28;
+ if (month == 12) return 31;
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
+ return 31;
+}
+
+function getPrevMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if (month == 3 && leapYear) return 29;
+ if (month == 3 && !leapYear) return 28;
+ if (month == 1) return 31;
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
+ return 31;
+}
+
+export function getCalendarLayout(dateObject, highlight) {
+ if (!dateObject) dateObject = new Date();
+ const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK
+ const day = dateObject.getDate();
+ const month = dateObject.getMonth() + 1;
+ const year = dateObject.getFullYear();
+ const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;
+ const daysInMonth = getMonthDays(month, year);
+ const daysInNextMonth = getNextMonthDays(month, year);
+ const daysInPrevMonth = getPrevMonthDays(month, year);
+
+ // Fill
+ var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);
+ var toFill, dim;
+ if(weekdayOfMonthFirst == 0) {
+ toFill = 1;
+ dim = daysInMonth;
+ }
+ else {
+ toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));
+ dim = daysInPrevMonth;
+ }
+ var calendar = [...Array(6)].map(() => Array(7));
+ var i = 0, j = 0;
+ while (i < 6 && j < 7) {
+ calendar[i][j] = {
+ "day": toFill,
+ "today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (
+ monthDiff == 0 ? 0 :
+ -1
+ ))
+ };
+ // Increment
+ toFill++;
+ if (toFill > dim) { // Next month?
+ monthDiff++;
+ if (monthDiff == 0)
+ dim = daysInMonth;
+ else if (monthDiff == 1)
+ dim = daysInNextMonth;
+ toFill = 1;
+ }
+ // Next tile
+ j++;
+ if (j == 7) {
+ j = 0;
+ i++;
+ }
+
+ }
+ return calendar;
+}
+
diff --git a/ags/modules/sideright/centermodules/audiocontrols.js b/ags/modules/sideright/centermodules/audiocontrols.js
new file mode 100644
index 00000000..c73fab6c
--- /dev/null
+++ b/ags/modules/sideright/centermodules/audiocontrols.js
@@ -0,0 +1,217 @@
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Button, Icon, Label, Revealer, Scrollable, Slider, Stack } = Widget;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import { iconExists } from '../../.miscutils/icons.js';
+
+const AppVolume = (stream) => Box({
+ className: 'sidebar-volmixer-stream spacing-h-10',
+ children: [
+ Icon({
+ className: 'sidebar-volmixer-stream-appicon',
+ vpack: 'center',
+ tooltipText: stream.stream.name,
+ setup: (self) => {
+ self.hook(stream, (self) => {
+ self.icon = stream.stream.name.toLowerCase();
+ })
+ },
+ }),
+ Box({
+ hexpand: true,
+ vpack: 'center',
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ Label({
+ xalign: 0,
+ maxWidthChars: 1,
+ truncate: 'end',
+ label: stream.description,
+ className: 'txt-small',
+ setup: (self) => self.hook(stream, (self) => {
+ self.label = `${stream.stream.name} • ${stream.description}`
+ })
+ }),
+ Slider({
+ drawValue: false,
+ hpack: 'fill',
+ className: 'sidebar-volmixer-stream-slider',
+ value: stream.volume,
+ min: 0, max: 1,
+ onChange: ({ value }) => {
+ stream.volume = value;
+ },
+ setup: (self) => self.hook(stream, (self) => {
+ self.value = stream.volume;
+ })
+ }),
+ // Box({
+ // homogeneous: true,
+ // className: 'test',
+ // children: [AnimatedSlider({
+ // className: 'sidebar-volmixer-stream-slider',
+ // value: stream.volume,
+ // })],
+ // })
+ ]
+ })
+ ]
+});
+
+const AudioDevices = (input = false) => {
+ const dropdownShown = Variable(false);
+ const DeviceStream = (stream) => Button({
+ tooltipText: stream.description,
+ child: Box({
+ className: 'txt spacing-h-10',
+ children: [
+ iconExists(stream.iconName) ? Icon({
+ className: 'txt-norm symbolic-icon',
+ icon: stream.iconName,
+ }) : MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ truncate: 'end',
+ maxWidthChars: 1,
+ label: stream.description,
+ }),
+ ],
+ }),
+ onClicked: (self) => {
+ if (input) Audio.microphone = stream;
+ else Audio.speaker = stream;
+ dropdownShown.value = false;
+ },
+ setup: setupCursorHover,
+ })
+ const activeDevice = Button({
+ onClicked: () => { dropdownShown.value = !dropdownShown.value; },
+ child: Box({
+ className: 'txt spacing-h-10',
+ children: [
+ MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ truncate: 'end',
+ maxWidthChars: 1,
+ label: `${input ? '[In]' : '[Out]'}`,
+ setup: (self) => self.hook(Audio, (self) => {
+ self.label = `${input ? '[In]' : '[Out]'} ${input ? Audio.microphone.description : Audio.speaker.description}`;
+ })
+ }),
+ Label({
+ className: `icon-material txt-norm`,
+ setup: (self) => self.hook(dropdownShown, (self) => {
+ self.label = dropdownShown.value ? 'expand_less' : 'expand_more';
+ })
+ })
+ ],
+ }),
+ setup: setupCursorHover,
+ });
+ const deviceSelector = Revealer({
+ transition: 'slide_down',
+ revealChild: dropdownShown.bind("value"),
+ transitionDuration: userOptions.animations.durationSmall,
+ child: Box({
+ vertical: true,
+ children: [
+ Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 margin-top-5',
+ attribute: {
+ 'updateStreams': (self) => {
+ const streams = input ? Audio.microphones : Audio.speakers;
+ self.children = streams.map(stream => DeviceStream(stream));
+ },
+ },
+ setup: (self) => self
+ .hook(Audio, self.attribute.updateStreams, 'stream-added')
+ .hook(Audio, self.attribute.updateStreams, 'stream-removed')
+ ,
+ }),
+ ]
+ })
+ })
+ return Box({
+ hpack: 'fill',
+ className: 'sidebar-volmixer-deviceselector',
+ vertical: true,
+ children: [
+ activeDevice,
+ deviceSelector,
+ ]
+ })
+}
+
+export default (props) => {
+ const emptyContent = Box({
+ homogeneous: true,
+ children: [Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'txt spacing-v-10',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 txt-subtext',
+ children: [
+ MaterialIcon('brand_awareness', 'gigantic'),
+ Label({ label: 'No audio source', className: 'txt-small' }),
+ ]
+ }),
+ ]
+ })]
+ });
+ const appList = Scrollable({
+ vexpand: true,
+ child: Box({
+ attribute: {
+ 'updateStreams': (self) => {
+ const streams = Audio.apps;
+ self.children = streams.map(stream => AppVolume(stream));
+ },
+ },
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (self) => self
+ .hook(Audio, self.attribute.updateStreams, 'stream-added')
+ .hook(Audio, self.attribute.updateStreams, 'stream-removed')
+ ,
+ })
+ })
+ const devices = Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ AudioDevices(false),
+ AudioDevices(true),
+ ]
+ })
+ const mainContent = Stack({
+ children: {
+ 'empty': emptyContent,
+ 'list': appList,
+ },
+ setup: (self) => self.hook(Audio, (self) => {
+ self.shown = (Audio.apps.length > 0 ? 'list' : 'empty')
+ }),
+ })
+ return Box({
+ ...props,
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ mainContent,
+ devices,
+ ]
+ });
+}
diff --git a/ags/modules/sideright/centermodules/bluetooth.js b/ags/modules/sideright/centermodules/bluetooth.js
new file mode 100644
index 00000000..b9ec738d
--- /dev/null
+++ b/ags/modules/sideright/centermodules/bluetooth.js
@@ -0,0 +1,160 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Icon, Label, Scrollable, Slider, Stack, Overlay } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
+
+// can't connect: sync_problem
+
+const USE_SYMBOLIC_ICONS = true;
+
+const BluetoothDevice = (device) => {
+ // console.log(device);
+ const deviceIcon = Icon({
+ className: 'sidebar-bluetooth-appicon',
+ vpack: 'center',
+ tooltipText: device.name,
+ setup: (self) => self.hook(device, (self) => {
+ self.icon = `${device.iconName}${USE_SYMBOLIC_ICONS ? '-symbolic' : ''}`;
+ }),
+ });
+ const deviceStatus = Box({
+ hexpand: true,
+ vpack: 'center',
+ vertical: true,
+ children: [
+ Label({
+ xalign: 0,
+ maxWidthChars: 1,
+ truncate: 'end',
+ label: device.name,
+ className: 'txt-small',
+ setup: (self) => self.hook(device, (self) => {
+ self.label = device.name;
+ }),
+ }),
+ Label({
+ xalign: 0,
+ maxWidthChars: 1,
+ truncate: 'end',
+ label: device.connected ? 'Connected' : (device.paired ? 'Paired' : ''),
+ className: 'txt-subtext',
+ setup: (self) => self.hook(device, (self) => {
+ self.label = device.connected ? 'Connected' : (device.paired ? 'Paired' : '');
+ }),
+ }),
+ ]
+ });
+ const deviceConnectButton = ConfigToggle({
+ vpack: 'center',
+ expandWidget: false,
+ desc: 'Toggle connection',
+ initValue: device.connected,
+ onChange: (self, newValue) => {
+ device.setConnection(newValue);
+ },
+ extraSetup: (self) => self.hook(device, (self) => {
+ Utils.timeout(200, () => self.enabled.value = device.connected);
+ }),
+ })
+ const deviceRemoveButton = Button({
+ vpack: 'center',
+ className: 'sidebar-bluetooth-device-remove',
+ child: MaterialIcon('delete', 'norm'),
+ tooltipText: 'Remove device',
+ setup: setupCursorHover,
+ onClicked: () => execAsync(['bluetoothctl', 'remove', device.address]).catch(print),
+ });
+ return Box({
+ className: 'sidebar-bluetooth-device spacing-h-10',
+ children: [
+ deviceIcon,
+ deviceStatus,
+ Box({
+ className: 'spacing-h-5',
+ children: [
+ deviceConnectButton,
+ deviceRemoveButton,
+ ]
+ })
+ ]
+ })
+}
+
+export default (props) => {
+ const emptyContent = Box({
+ homogeneous: true,
+ children: [Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'txt spacing-v-10',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 txt-subtext',
+ children: [
+ MaterialIcon('bluetooth_disabled', 'gigantic'),
+ Label({ label: 'No Bluetooth devices', className: 'txt-small' }),
+ ]
+ }),
+ ]
+ })]
+ });
+ const deviceList = Overlay({
+ passThrough: true,
+ child: Scrollable({
+ vexpand: true,
+ child: Box({
+ attribute: {
+ 'updateDevices': (self) => {
+ const devices = Bluetooth.devices;
+ self.children = devices.map(d => BluetoothDevice(d));
+ },
+ },
+ vertical: true,
+ className: 'spacing-v-5 margin-bottom-15',
+ setup: (self) => self
+ .hook(Bluetooth, self.attribute.updateDevices, 'device-added')
+ .hook(Bluetooth, self.attribute.updateDevices, 'device-removed')
+ ,
+ })
+ }),
+ overlays: [Box({
+ className: 'sidebar-centermodules-scrollgradient-bottom'
+ })]
+ });
+ const mainContent = Stack({
+ children: {
+ 'empty': emptyContent,
+ 'list': deviceList,
+ },
+ setup: (self) => self.hook(Bluetooth, (self) => {
+ self.shown = (Bluetooth.devices.length > 0 ? 'list' : 'empty')
+ }),
+ })
+ const bottomBar = Box({
+ homogeneous: true,
+ children: [Button({
+ hpack: 'center',
+ className: 'txt-small txt sidebar-centermodules-bottombar-button',
+ onClicked: () => {
+ execAsync(['bash', '-c', userOptions.apps.bluetooth]).catch(print);
+ closeEverything();
+ },
+ label: 'More',
+ setup: setupCursorHover,
+ })],
+ })
+ return Box({
+ ...props,
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ mainContent,
+ bottomBar
+ ]
+ });
+}
diff --git a/ags/modules/sideright/centermodules/configure.js b/ags/modules/sideright/centermodules/configure.js
new file mode 100644
index 00000000..b53556c7
--- /dev/null
+++ b/ags/modules/sideright/centermodules/configure.js
@@ -0,0 +1,130 @@
+const { GLib } = imports.gi;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Icon, Label, Scrollable, Slider, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import { ConfigGap, ConfigSpinButton, ConfigToggle } from '../../.commonwidgets/configwidgets.js';
+
+const HyprlandToggle = ({ icon, name, desc = null, option, enableValue = 1, disableValue = 0, extraOnChange = () => { } }) => ConfigToggle({
+ icon: icon,
+ name: name,
+ desc: desc,
+ initValue: JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
+ onChange: (self, newValue) => {
+ execAsync(['hyprctl', 'keyword', option, `${newValue ? enableValue : disableValue}`]).catch(print);
+ extraOnChange(self, newValue);
+ }
+});
+
+const HyprlandSpinButton = ({ icon, name, desc = null, option, ...rest }) => ConfigSpinButton({
+ icon: icon,
+ name: name,
+ desc: desc,
+ initValue: Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
+ onChange: (self, newValue) => {
+ execAsync(['hyprctl', 'keyword', option, `${newValue}`]).catch(print);
+ },
+ ...rest,
+});
+
+const Subcategory = (children) => Box({
+ className: 'margin-left-20',
+ vertical: true,
+ children: children,
+})
+
+export default (props) => {
+ const ConfigSection = ({ name, children }) => Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ Label({
+ hpack: 'center',
+ className: 'txt txt-large margin-left-10',
+ label: name,
+ }),
+ Box({
+ className: 'margin-left-10 margin-right-10',
+ vertical: true,
+ children: children,
+ })
+ ]
+ })
+ const mainContent = Scrollable({
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ ConfigSection({
+ name: 'Effects', children: [
+ ConfigToggle({
+ icon: 'border_clear',
+ name: 'Transparency',
+ desc: '[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this',
+ initValue: exec(`bash -c "sed -n \'2p\' ${GLib.get_user_state_dir()}/ags/user/colormode.txt"`) == "transparent",
+ onChange: (self, newValue) => {
+ const transparency = newValue == 0 ? "opaque" : "transparent";
+ console.log(transparency);
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
+ .catch(print);
+ },
+ }),
+ HyprlandToggle({ icon: 'blur_on', name: 'Blur', desc: "[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.", option: "decoration:blur:enabled" }),
+ Subcategory([
+ HyprlandToggle({ icon: 'stack_off', name: 'X-ray', desc: "[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ", option: "decoration:blur:xray" }),
+ HyprlandSpinButton({ icon: 'target', name: 'Size', desc: '[Hyprland]\nAdjust the blur radius. Generally doesn\'t affect performance\nHigher = more color spread', option: 'decoration:blur:size', minValue: 1, maxValue: 1000 }),
+ HyprlandSpinButton({ icon: 'repeat', name: 'Passes', desc: '[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.', option: 'decoration:blur:passes', minValue: 1, maxValue: 10 }),
+ ]),
+ ConfigGap({}),
+ HyprlandToggle({
+ icon: 'animation', name: 'Animations', desc: '[Hyprland] [GTK]\nEnable animations', option: 'animations:enabled',
+ extraOnChange: (self, newValue) => execAsync(['gsettings', 'set', 'org.gnome.desktop.interface', 'enable-animations', `${newValue}`])
+ }),
+ Subcategory([
+ ConfigSpinButton({
+ icon: 'clear_all',
+ name: 'Choreography delay',
+ desc: 'In milliseconds, the delay between animations of a series',
+ initValue: userOptions.animations.choreographyDelay,
+ step: 10, minValue: 0, maxValue: 1000,
+ onChange: (self, newValue) => {
+ userOptions.animations.choreographyDelay = newValue
+ },
+ })
+ ]),
+ ]
+ }),
+ ConfigSection({
+ name: 'Developer', children: [
+ HyprlandToggle({ icon: 'speed', name: 'Show FPS', desc: "[Hyprland]\nShow FPS overlay on top-left corner", option: "debug:overlay" }),
+ HyprlandToggle({ icon: 'sort', name: 'Log to stdout', desc: "[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console", option: "debug:enable_stdout_logs" }),
+ HyprlandToggle({ icon: 'motion_sensor_active', name: 'Damage tracking', desc: "[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work", option: "debug:damage_tracking", enableValue: 2 }),
+ HyprlandToggle({ icon: 'destruction', name: 'Damage blink', desc: "[Hyprland] [Epilepsy warning!]\nShow screen damage flashes", option: "debug:damage_blink" }),
+ ]
+ }),
+ ]
+ })
+ });
+ const footNote = Box({
+ homogeneous: true,
+ children: [Label({
+ hpack: 'center',
+ className: 'txt txt-italic txt-subtext margin-5',
+ label: 'Not all changes are saved',
+ })]
+ })
+ return Box({
+ ...props,
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ mainContent,
+ footNote,
+ ]
+ });
+}
diff --git a/ags/modules/sideright/centermodules/notificationlist.js b/ags/modules/sideright/centermodules/notificationlist.js
new file mode 100644
index 00000000..8007034a
--- /dev/null
+++ b/ags/modules/sideright/centermodules/notificationlist.js
@@ -0,0 +1,176 @@
+// This file is for the notification list on the sidebar
+// For the popup notifications, see onscreendisplay.js
+// The actual widget for each single notification is in ags/modules/.commonwidgets/notification.js
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+const { Box, Button, Label, Revealer, Scrollable, Stack } = Widget;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import Notification from '../../.commonwidgets/notification.js';
+import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
+
+export default (props) => {
+ const notifEmptyContent = Box({
+ homogeneous: true,
+ children: [Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'txt spacing-v-10',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 txt-subtext',
+ children: [
+ MaterialIcon('notifications_active', 'gigantic'),
+ Label({ label: 'No notifications', className: 'txt-small' }),
+ ]
+ }),
+ ]
+ })]
+ });
+ const notificationList = Box({
+ vertical: true,
+ vpack: 'start',
+ className: 'spacing-v-5-revealer',
+ setup: (self) => self
+ .hook(Notifications, (box, id) => {
+ if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
+ Notifications.notifications
+ .forEach(n => {
+ box.pack_end(Notification({
+ notifObject: n,
+ isPopup: false,
+ }), false, false, 0)
+ });
+ box.show_all();
+ return;
+ }
+ // 2nd or later notif
+ const notif = Notifications.getNotification(id);
+ const NewNotif = Notification({
+ notifObject: notif,
+ isPopup: false,
+ });
+ if (NewNotif) {
+ box.pack_end(NewNotif, false, false, 0);
+ box.show_all();
+ }
+ }, 'notified')
+ .hook(Notifications, (box, id) => {
+ if (!id) return;
+ for (const ch of box.children) {
+ if (ch._id === id) {
+ ch.attribute.destroyWithAnims();
+ }
+ }
+ }, 'closed')
+ ,
+ });
+ const ListActionButton = (icon, name, action) => Button({
+ className: 'sidebar-centermodules-bottombar-button',
+ onClicked: action,
+ child: Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon(icon, 'norm'),
+ Label({
+ className: 'txt-small',
+ label: name,
+ })
+ ]
+ }),
+ setup: setupCursorHover,
+ });
+ const silenceButton = ListActionButton('notifications_paused', 'Silence', (self) => {
+ Notifications.dnd = !Notifications.dnd;
+ self.toggleClassName('notif-listaction-btn-enabled', Notifications.dnd);
+ });
+ // const silenceToggle = ConfigToggle({
+ // expandWidget: false,
+ // icon: 'do_not_disturb_on',
+ // name: 'Do Not Disturb',
+ // initValue: false,
+ // onChange: (self, newValue) => {
+ // Notifications.dnd = newValue;
+ // },
+ // })
+ const clearButton = Revealer({
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationSmall,
+ setup: (self) => self.hook(Notifications, (self) => {
+ self.revealChild = Notifications.notifications.length > 0;
+ }),
+ child: ListActionButton('clear_all', 'Clear', () => {
+ Notifications.clear();
+ const kids = notificationList.get_children();
+ for (let i = 0; i < kids.length; i++) {
+ const kid = kids[i];
+ Utils.timeout(userOptions.animations.choreographyDelay * i, () => kid.attribute.destroyWithAnims());
+ }
+ })
+ })
+ const notifCount = Label({
+ attribute: {
+ updateCount: (self) => {
+ const count = Notifications.notifications.length;
+ if (count > 0) self.label = `${count} notifications`;
+ else self.label = '';
+ },
+ },
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small margin-left-10',
+ label: `${Notifications.notifications.length}`,
+ setup: (self) => self
+ .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'notified')
+ .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'dismissed')
+ .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'closed')
+ ,
+ });
+ const listTitle = Box({
+ vpack: 'start',
+ className: 'txt spacing-h-5',
+ children: [
+ notifCount,
+ silenceButton,
+ // silenceToggle,
+ // Box({ hexpand: true }),
+ clearButton,
+ ]
+ });
+ const notifList = Scrollable({
+ hexpand: true,
+ hscroll: 'never',
+ vscroll: 'automatic',
+ child: Box({
+ vexpand: true,
+ homogeneous: true,
+ children: [notificationList],
+ }),
+ setup: (self) => {
+ const vScrollbar = self.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ }
+ });
+ const listContents = Stack({
+ transition: 'crossfade',
+ transitionDuration: userOptions.animations.durationLarge,
+ children: {
+ 'empty': notifEmptyContent,
+ 'list': notifList,
+ },
+ setup: (self) => self.hook(Notifications, (self) => {
+ self.shown = (Notifications.notifications.length > 0 ? 'list' : 'empty')
+ }),
+ });
+ return Box({
+ ...props,
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ listContents,
+ listTitle,
+ ]
+ });
+}
diff --git a/ags/modules/sideright/centermodules/wifinetworks.js b/ags/modules/sideright/centermodules/wifinetworks.js
new file mode 100644
index 00000000..4a68c91a
--- /dev/null
+++ b/ags/modules/sideright/centermodules/wifinetworks.js
@@ -0,0 +1,216 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Network from "resource:///com/github/Aylur/ags/service/network.js";
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Entry, Icon, Label, Revealer, Scrollable, Slider, Stack, Overlay } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
+import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
+import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
+
+const MATERIAL_SYMBOL_SIGNAL_STRENGTH = {
+ 'network-wireless-signal-excellent-symbolic': "signal_wifi_4_bar",
+ 'network-wireless-signal-good-symbolic': "network_wifi_3_bar",
+ 'network-wireless-signal-ok-symbolic': "network_wifi_2_bar",
+ 'network-wireless-signal-weak-symbolic': "network_wifi_1_bar",
+ 'network-wireless-signal-none-symbolic': "signal_wifi_0_bar",
+}
+
+let connectAttempt = '';
+
+const WifiNetwork = (accessPoint) => {
+ const networkStrength = MaterialIcon(MATERIAL_SYMBOL_SIGNAL_STRENGTH[accessPoint.iconName], 'hugerass')
+ const networkName = Box({
+ vertical: true,
+ children: [
+ Label({
+ hpack: 'start',
+ label: accessPoint.ssid
+ }),
+ accessPoint.active ? Label({
+ hpack: 'start',
+ className: 'txt-smaller txt-subtext',
+ label: "Selected",
+ }) : null,
+ ]
+ });
+ return Button({
+ onClicked: accessPoint.active ? () => { } : () => execAsync(`nmcli device wifi connect ${accessPoint.bssid}`)
+ // .catch(e => {
+ // Utils.notify({
+ // summary: "Network",
+ // body: e,
+ // actions: { "Open network manager": () => execAsync("nm-connection-editor").catch(print) }
+ // });
+ // })
+ .catch(print),
+ child: Box({
+ className: 'sidebar-wifinetworks-network spacing-h-10',
+ children: [
+ networkStrength,
+ networkName,
+ Box({ hexpand: true }),
+ accessPoint.active ? MaterialIcon('check', 'large') : null,
+ ],
+ }),
+ setup: accessPoint.active ? () => { } : setupCursorHover,
+ })
+}
+
+const CurrentNetwork = () => {
+ let authLock = false;
+ // console.log(Network.wifi);
+ const bottomSeparator = Box({
+ className: 'separator-line',
+ });
+ const networkName = Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ Label({
+ hpack: 'start',
+ className: 'txt-smaller txt-subtext',
+ label: "Current network",
+ }),
+ Label({
+ hpack: 'start',
+ label: Network.wifi?.ssid,
+ setup: (self) => self.hook(Network, (self) => {
+ if (authLock) return;
+ self.label = Network.wifi?.ssid;
+ }),
+ }),
+ ]
+ });
+ const networkStatus = Box({
+ children: [Label({
+ vpack: 'center',
+ className: 'txt-subtext',
+ setup: (self) => self.hook(Network, (self) => {
+ if (authLock) return;
+ self.label = Network.wifi.state;
+ }),
+ })]
+ })
+ const networkAuth = Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: Box({
+ className: 'margin-top-10 spacing-v-5',
+ vertical: true,
+ children: [
+ Label({
+ className: 'margin-left-5',
+ hpack: 'start',
+ label: "Authentication",
+ }),
+ Entry({
+ className: 'sidebar-wifinetworks-auth-entry',
+ visibility: false, // Password dots
+ onAccept: (self) => {
+ authLock = false;
+ networkAuth.revealChild = false;
+ execAsync(`nmcli device wifi connect '${connectAttempt}' password '${self.text}'`)
+ .catch(print);
+ }
+ })
+ ]
+ }),
+ setup: (self) => self.hook(Network, (self) => {
+ if (Network.wifi.state == 'failed' || Network.wifi.state == 'need_auth') {
+ authLock = true;
+ connectAttempt = Network.wifi.ssid;
+ self.revealChild = true;
+ }
+ }),
+ });
+ const actualContent = Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Box({
+ className: 'sidebar-wifinetworks-network',
+ vertical: true,
+ children: [
+ Box({
+ className: 'spacing-h-10',
+ children: [
+ MaterialIcon('language', 'hugerass'),
+ networkName,
+ networkStatus,
+
+ ]
+ }),
+ networkAuth,
+ ]
+ }),
+ bottomSeparator,
+ ]
+ });
+ return Box({
+ vertical: true,
+ children: [Revealer({
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: Network.wifi,
+ child: actualContent,
+ })]
+ })
+}
+
+export default (props) => {
+ const networkList = Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [Overlay({
+ passThrough: true,
+ child: Scrollable({
+ vexpand: true,
+ child: Box({
+ attribute: {
+ 'updateNetworks': (self) => {
+ const accessPoints = Network.wifi?.access_points || [];
+ self.children = Object.values(accessPoints.reduce((a, accessPoint) => {
+ // Only keep max strength networks by ssid
+ if (!a[accessPoint.ssid] || a[accessPoint.ssid].strength < accessPoint.strength) {
+ a[accessPoint.ssid] = accessPoint;
+ a[accessPoint.ssid].active |= accessPoint.active;
+ }
+
+ return a;
+ }, {})).map(n => WifiNetwork(n));
+ },
+ },
+ vertical: true,
+ className: 'spacing-v-5 margin-bottom-15',
+ setup: (self) => self.hook(Network, self.attribute.updateNetworks),
+ })
+ }),
+ overlays: [Box({
+ className: 'sidebar-centermodules-scrollgradient-bottom'
+ })]
+ })]
+ });
+ const bottomBar = Box({
+ homogeneous: true,
+ children: [Button({
+ hpack: 'center',
+ className: 'txt-small txt sidebar-centermodules-bottombar-button',
+ onClicked: () => {
+ execAsync(['bash', '-c', userOptions.apps.network]).catch(print);
+ closeEverything();
+ },
+ label: 'More',
+ setup: setupCursorHover,
+ })],
+ })
+ return Box({
+ ...props,
+ className: 'spacing-v-10',
+ vertical: true,
+ children: [
+ CurrentNetwork(),
+ networkList,
+ bottomBar,
+ ]
+ });
+}
diff --git a/ags/modules/sideright/main.js b/ags/modules/sideright/main.js
new file mode 100644
index 00000000..69a58b21
--- /dev/null
+++ b/ags/modules/sideright/main.js
@@ -0,0 +1,18 @@
+import PopupWindow from '../.widgethacks/popupwindow.js';
+import SidebarRight from "./sideright.js";
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box } = Widget;
+import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
+
+export default () => PopupWindow({
+ keymode: 'on-demand',
+ anchor: ['right', 'top', 'bottom'],
+ name: 'sideright',
+ layer: 'overlay',
+ child: Box({
+ children: [
+ clickCloseRegion({ name: 'sideright', multimonitor: false, fillMonitor: 'horizontal' }),
+ SidebarRight(),
+ ]
+ })
+});
diff --git a/ags/modules/sideright/quicktoggles.js b/ags/modules/sideright/quicktoggles.js
new file mode 100644
index 00000000..e37b79b7
--- /dev/null
+++ b/ags/modules/sideright/quicktoggles.js
@@ -0,0 +1,267 @@
+const { GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
+import Network from 'resource:///com/github/Aylur/ags/service/network.js';
+const { execAsync, exec } = Utils;
+import { BluetoothIndicator, NetworkIndicator } from '../.commonwidgets/statusicons.js';
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { sidebarOptionsStack } from './sideright.js';
+
+export const ToggleIconWifi = (props = {}) => Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Wifi | Right-click to configure',
+ onClicked: () => Network.toggleWifi(),
+ onSecondaryClickRelease: () => {
+ execAsync(['bash', '-c', `${userOptions.apps.network}`]).catch(print);
+ closeEverything();
+ },
+ child: NetworkIndicator(),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(Network, button => {
+ button.toggleClassName('sidebar-button-active', [Network.wifi?.internet, Network.wired?.internet].includes('connected'))
+ button.tooltipText = (`${Network.wifi?.ssid} | Right-click to configure` || 'Unknown');
+ });
+ },
+ ...props,
+});
+
+export const ToggleIconBluetooth = (props = {}) => Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Bluetooth | Right-click to configure',
+ onClicked: () => {
+ const status = Bluetooth?.enabled;
+ if (status)
+ exec('rfkill block bluetooth');
+ else
+ exec('rfkill unblock bluetooth');
+ },
+ onSecondaryClickRelease: () => {
+ execAsync(['bash', '-c', `${userOptions.apps.bluetooth}`]).catch(print);
+ closeEverything();
+ },
+ child: BluetoothIndicator(),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(Bluetooth, button => {
+ button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
+ });
+ },
+ ...props,
+});
+
+export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
+ try {
+ return Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: `${name}`,
+ onClicked: (button) => {
+ // Set the value to 1 - value
+ Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
+ const currentOption = JSON.parse(result).int;
+ execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
+ button.toggleClassName('sidebar-button-active', currentOption == 0);
+ }).catch(print);
+ },
+ child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
+ setup: button => {
+ button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
+ setupCursorHover(button);
+ },
+ ...props,
+ })
+ } catch {
+ return null;
+ }
+}
+
+export const ModuleNightLight = async (props = {}) => {
+ if (!exec(`bash -c 'command -v gammastep'`)) return null;
+ return Widget.Button({
+ attribute: {
+ enabled: false,
+ },
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Night Light',
+ onClicked: (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ if (self.attribute.enabled) Utils.execAsync('gammastep').catch(print)
+ else Utils.execAsync('pkill gammastep')
+ .then(() => {
+ // disable the button until fully terminated to avoid race
+ self.sensitive = false;
+ const source = setInterval(() => {
+ Utils.execAsync('pkill -0 gammastep')
+ .catch(() => {
+ self.sensitive = true;
+ source.destroy();
+ });
+ }, 500);
+ })
+ .catch(print);
+ },
+ child: MaterialIcon('nightlight', 'norm'),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.attribute.enabled = !!exec('pidof gammastep');
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ },
+ ...props,
+ });
+}
+
+export const ModuleCloudflareWarp = async (props = {}) => {
+ if (!exec(`bash -c 'command -v warp-cli'`)) return null;
+ return Widget.Button({
+ attribute: {
+ enabled: false,
+ },
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Cloudflare WARP',
+ onClicked: (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ if (self.attribute.enabled) Utils.execAsync('warp-cli connect').catch(print)
+ else Utils.execAsync('warp-cli disconnect').catch(print);
+ },
+ child: Widget.Icon({
+ icon: 'cloudflare-dns-symbolic',
+ className: 'txt-norm',
+ }),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.attribute.enabled = !exec(`bash -c 'warp-cli status | grep Disconnected'`);
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ },
+ ...props,
+ });
+}
+
+export const ModuleInvertColors = async (props = {}) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ return Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Color inversion',
+ onClicked: (button) => {
+ // const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
+ Hyprland.messageAsync('j/getoption decoration:screen_shader')
+ .then((output) => {
+ const shaderPath = JSON.parse(output)["str"].trim();
+ if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
+ execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
+ button.toggleClassName('sidebar-button-active', false);
+ }
+ else {
+ Hyprland.messageAsync(`j/keyword decoration:screen_shader ${GLib.get_user_config_dir()}/hypr/shaders/invert.frag`)
+ .catch(print);
+ button.toggleClassName('sidebar-button-active', true);
+ }
+ })
+ },
+ child: MaterialIcon('invert_colors', 'norm'),
+ setup: setupCursorHover,
+ ...props,
+ })
+ } catch {
+ return null;
+ };
+}
+
+export const ModuleRawInput = async (props = {}) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ return Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Raw input',
+ onClicked: (button) => {
+ Hyprland.messageAsync('j/getoption input:accel_profile')
+ .then((output) => {
+ const value = JSON.parse(output)["str"].trim();
+ if (value != "[[EMPTY]]" && value != "") {
+ execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
+ button.toggleClassName('sidebar-button-active', false);
+ }
+ else {
+ Hyprland.messageAsync(`j/keyword input:accel_profile flat`)
+ .catch(print);
+ button.toggleClassName('sidebar-button-active', true);
+ }
+ })
+ },
+ child: MaterialIcon('mouse', 'norm'),
+ setup: setupCursorHover,
+ ...props,
+ })
+ } catch {
+ return null;
+ };
+}
+
+export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
+ attribute: {
+ enabled: false,
+ },
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Keep system awake',
+ onClicked: (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ if (self.attribute.enabled) Utils.execAsync(['bash', '-c', `pidof wayland-idle-inhibitor.py || ${App.configDir}/scripts/wayland-idle-inhibitor.py`]).catch(print)
+ else Utils.execAsync('pkill -f wayland-idle-inhibitor.py').catch(print);
+ },
+ child: MaterialIcon('coffee', 'norm'),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.attribute.enabled = !!exec('pidof wayland-idle-inhibitor.py');
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ },
+ ...props,
+});
+
+export const ModuleReloadIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Reload Environment config',
+ onClicked: () => {
+ execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
+ App.closeWindow('sideright');
+ },
+ child: MaterialIcon('refresh', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+export const ModuleSettingsIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Open Settings',
+ onClicked: () => {
+ execAsync(['bash', '-c', `${userOptions.apps.settings}`, '&']);
+ App.closeWindow('sideright');
+ },
+ child: MaterialIcon('settings', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+export const ModulePowerIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Session',
+ onClicked: () => {
+ closeEverything();
+ Utils.timeout(1, () => openWindowOnAllMonitors('session'));
+ },
+ child: MaterialIcon('power_settings_new', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
diff --git a/ags/modules/sideright/sideright.js b/ags/modules/sideright/sideright.js
new file mode 100644
index 00000000..24630b2f
--- /dev/null
+++ b/ags/modules/sideright/sideright.js
@@ -0,0 +1,187 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, EventBox } = Widget;
+import {
+ ToggleIconBluetooth,
+ ToggleIconWifi,
+ HyprToggleIcon,
+ ModuleNightLight,
+ ModuleInvertColors,
+ ModuleIdleInhibitor,
+ ModuleReloadIcon,
+ ModuleSettingsIcon,
+ ModulePowerIcon,
+ ModuleRawInput,
+ ModuleCloudflareWarp
+} from "./quicktoggles.js";
+import ModuleNotificationList from "./centermodules/notificationlist.js";
+import ModuleAudioControls from "./centermodules/audiocontrols.js";
+import ModuleWifiNetworks from "./centermodules/wifinetworks.js";
+import ModuleBluetooth from "./centermodules/bluetooth.js";
+import ModuleConfigure from "./centermodules/configure.js";
+import { ModuleCalendar } from "./calendar.js";
+import { getDistroIcon } from '../.miscutils/system.js';
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
+import { checkKeybind } from '../.widgetutils/keybind.js';
+
+const centerWidgets = [
+ {
+ name: 'Notifications',
+ materialIcon: 'notifications',
+ contentWidget: ModuleNotificationList,
+ },
+ {
+ name: 'Audio controls',
+ materialIcon: 'volume_up',
+ contentWidget: ModuleAudioControls,
+ },
+ {
+ name: 'Bluetooth',
+ materialIcon: 'bluetooth',
+ contentWidget: ModuleBluetooth,
+ },
+ {
+ name: 'Wifi networks',
+ materialIcon: 'wifi',
+ contentWidget: ModuleWifiNetworks,
+ onFocus: () => execAsync('nmcli dev wifi list').catch(print),
+ },
+ {
+ name: 'Live config',
+ materialIcon: 'tune',
+ contentWidget: ModuleConfigure,
+ },
+];
+
+const timeRow = Box({
+ className: 'spacing-h-10 sidebar-group-invisible-morehorizpad',
+ children: [
+ Widget.Icon({
+ icon: getDistroIcon(),
+ className: 'txt txt-larger',
+ }),
+ Widget.Label({
+ hpack: 'center',
+ className: 'txt-small txt',
+ setup: (self) => {
+ const getUptime = async () => {
+ try {
+ await execAsync(['bash', '-c', 'uptime -p']);
+ return execAsync(['bash', '-c', `uptime -p | sed -e 's/...//;s/ day\\| days/d/;s/ hour\\| hours/h/;s/ minute\\| minutes/m/;s/,[^,]*//2'`]);
+ } catch {
+ return execAsync(['bash', '-c', 'uptime']).then(output => {
+ const uptimeRegex = /up\s+((\d+)\s+days?,\s+)?((\d+):(\d+)),/;
+ const matches = uptimeRegex.exec(output);
+
+ if (matches) {
+ const days = matches[2] ? parseInt(matches[2]) : 0;
+ const hours = matches[4] ? parseInt(matches[4]) : 0;
+ const minutes = matches[5] ? parseInt(matches[5]) : 0;
+
+ let formattedUptime = '';
+
+ if (days > 0) {
+ formattedUptime += `${days} d `;
+ }
+ if (hours > 0) {
+ formattedUptime += `${hours} h `;
+ }
+ formattedUptime += `${minutes} m`;
+
+ return formattedUptime;
+ } else {
+ throw new Error('Failed to parse uptime output');
+ }
+ });
+ }
+ };
+
+ self.poll(5000, label => {
+ getUptime().then(upTimeString => {
+ label.label = `Uptime: ${upTimeString}`;
+ }).catch(err => {
+ console.error(`Failed to fetch uptime: ${err}`);
+ });
+ });
+ },
+ }),
+ Widget.Box({ hexpand: true }),
+ ModuleReloadIcon({ hpack: 'end' }),
+ // ModuleSettingsIcon({ hpack: 'end' }), // Button does work, gnome-control-center is kinda broken
+ ModulePowerIcon({ hpack: 'end' }),
+ ]
+});
+
+const togglesBox = Widget.Box({
+ hpack: 'center',
+ className: 'sidebar-togglesbox spacing-h-5',
+ children: [
+ ToggleIconWifi(),
+ ToggleIconBluetooth(),
+ // await ModuleRawInput(),
+ // await HyprToggleIcon('touchpad_mouse', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
+ await ModuleNightLight(),
+ await ModuleInvertColors(),
+ ModuleIdleInhibitor(),
+ await ModuleCloudflareWarp(),
+ ]
+})
+
+export const sidebarOptionsStack = ExpandingIconTabContainer({
+ tabsHpack: 'center',
+ tabSwitcherClassName: 'sidebar-icontabswitcher',
+ icons: centerWidgets.map((api) => api.materialIcon),
+ names: centerWidgets.map((api) => api.name),
+ children: centerWidgets.map((api) => api.contentWidget()),
+ onChange: (self, id) => {
+ self.shown = centerWidgets[id].name;
+ if (centerWidgets[id].onFocus) centerWidgets[id].onFocus();
+ }
+});
+
+export default () => Box({
+ vexpand: true,
+ hexpand: true,
+ css: 'min-width: 2px;',
+ children: [
+ EventBox({
+ onPrimaryClick: () => App.closeWindow('sideright'),
+ onSecondaryClick: () => App.closeWindow('sideright'),
+ onMiddleClick: () => App.closeWindow('sideright'),
+ }),
+ Box({
+ vertical: true,
+ vexpand: true,
+ className: 'sidebar-right spacing-v-15',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ timeRow,
+ togglesBox,
+ ]
+ }),
+ Box({
+ className: 'sidebar-group',
+ children: [
+ sidebarOptionsStack,
+ ],
+ }),
+ ModuleCalendar(),
+ ]
+ }),
+ ],
+ setup: (self) => self
+ .on('key-press-event', (widget, event) => { // Handle keybinds
+ if (checkKeybind(event, userOptions.keybinds.sidebar.options.nextTab)) {
+ sidebarOptionsStack.nextTab();
+ }
+ else if (checkKeybind(event, userOptions.keybinds.sidebar.options.prevTab)) {
+ sidebarOptionsStack.prevTab();
+ }
+ })
+ ,
+});
diff --git a/ags/modules/sideright/todolist.js b/ags/modules/sideright/todolist.js
new file mode 100644
index 00000000..30008a32
--- /dev/null
+++ b/ags/modules/sideright/todolist.js
@@ -0,0 +1,224 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Revealer } = Widget;
+import { MaterialIcon } from '../.commonwidgets/materialicon.js';
+import { TabContainer } from '../.commonwidgets/tabcontainer.js';
+import Todo from "../../services/todo.js";
+import { setupCursorHover } from '../.widgetutils/cursorhover.js';
+
+const TodoListItem = (task, id, isDone, isEven = false) => {
+ const taskName = Widget.Label({
+ hexpand: true,
+ xalign: 0,
+ wrap: true,
+ className: 'txt txt-small sidebar-todo-txt',
+ label: task.content,
+ selectable: true,
+ });
+ const actions = Box({
+ hpack: 'end',
+ className: 'spacing-h-5 sidebar-todo-actions',
+ children: [
+ Widget.Button({ // Check/Uncheck
+ vpack: 'center',
+ className: 'txt sidebar-todo-item-action',
+ child: MaterialIcon(`${isDone ? 'remove_done' : 'check'}`, 'norm', { vpack: 'center' }),
+ onClicked: (self) => {
+ const contentWidth = todoContent.get_allocated_width();
+ crosser.toggleClassName('sidebar-todo-crosser-crossed', true);
+ crosser.css = `margin-left: -${contentWidth}px;`;
+ Utils.timeout(200, () => {
+ widgetRevealer.revealChild = false;
+ })
+ Utils.timeout(350, () => {
+ if (isDone)
+ Todo.uncheck(id);
+ else
+ Todo.check(id);
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ Widget.Button({ // Remove
+ vpack: 'center',
+ className: 'txt sidebar-todo-item-action',
+ child: MaterialIcon('delete_forever', 'norm', { vpack: 'center' }),
+ onClicked: () => {
+ const contentWidth = todoContent.get_allocated_width();
+ crosser.toggleClassName('sidebar-todo-crosser-removed', true);
+ crosser.css = `margin-left: -${contentWidth}px;`;
+ Utils.timeout(200, () => {
+ widgetRevealer.revealChild = false;
+ })
+ Utils.timeout(350, () => {
+ Todo.remove(id);
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ ]
+ })
+ const crosser = Widget.Box({
+ className: 'sidebar-todo-crosser',
+ });
+ const todoContent = Widget.Box({
+ className: 'sidebar-todo-item spacing-h-5',
+ children: [
+ Widget.Box({
+ vertical: true,
+ children: [
+ taskName,
+ actions,
+ ]
+ }),
+ crosser,
+ ]
+ });
+ const widgetRevealer = Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_down',
+ transitionDuration: userOptions.animations.durationLarge,
+ child: todoContent,
+ })
+ return Box({
+ homogeneous: true,
+ children: [widgetRevealer]
+ });
+}
+
+const todoItems = (isDone) => Widget.Scrollable({
+ hscroll: 'never',
+ vscroll: 'automatic',
+ child: Widget.Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (self) => self
+ .hook(Todo, (self) => {
+ self.children = Todo.todo_json.map((task, i) => {
+ if (task.done != isDone) return null;
+ return TodoListItem(task, i, isDone);
+ })
+ if (self.children.length == 0) {
+ self.homogeneous = true;
+ self.children = [
+ Widget.Box({
+ hexpand: true,
+ vertical: true,
+ vpack: 'center',
+ className: 'txt txt-subtext',
+ children: [
+ MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'),
+ Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
+ ]
+ })
+ ]
+ }
+ else self.homogeneous = false;
+ }, 'updated')
+ ,
+ }),
+ setup: (listContents) => {
+ const vScrollbar = listContents.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ }
+});
+
+const UndoneTodoList = () => {
+ const newTaskButton = Revealer({
+ transition: 'slide_left',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: true,
+ child: Button({
+ className: 'txt-small sidebar-todo-new',
+ halign: 'end',
+ vpack: 'center',
+ label: '+ New task',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ newTaskButton.revealChild = false;
+ newTaskEntryRevealer.revealChild = true;
+ confirmAddTask.revealChild = true;
+ cancelAddTask.revealChild = true;
+ newTaskEntry.grab_focus();
+ }
+ })
+ });
+ const cancelAddTask = Revealer({
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: Button({
+ className: 'txt-norm icon-material sidebar-todo-add',
+ halign: 'end',
+ vpack: 'center',
+ label: 'close',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ newTaskEntryRevealer.revealChild = false;
+ confirmAddTask.revealChild = false;
+ cancelAddTask.revealChild = false;
+ newTaskButton.revealChild = true;
+ newTaskEntry.text = '';
+ }
+ })
+ });
+ const newTaskEntry = Widget.Entry({
+ // hexpand: true,
+ vpack: 'center',
+ className: 'txt-small sidebar-todo-entry',
+ placeholderText: 'Add a task...',
+ onAccept: ({ text }) => {
+ if (text == '') return;
+ Todo.add(text)
+ newTaskEntry.text = '';
+ },
+ onChange: ({ text }) => confirmAddTask.child.toggleClassName('sidebar-todo-add-available', text != ''),
+ });
+ const newTaskEntryRevealer = Revealer({
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: newTaskEntry,
+ });
+ const confirmAddTask = Revealer({
+ transition: 'slide_right',
+ transitionDuration: userOptions.animations.durationLarge,
+ revealChild: false,
+ child: Button({
+ className: 'txt-norm icon-material sidebar-todo-add',
+ halign: 'end',
+ vpack: 'center',
+ label: 'arrow_upward',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ if (newTaskEntry.text == '') return;
+ Todo.add(newTaskEntry.text);
+ newTaskEntry.text = '';
+ }
+ })
+ });
+ return Box({ // The list, with a New button
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (box) => {
+ box.pack_start(todoItems(false), true, true, 0);
+ box.pack_start(Box({
+ setup: (self) => {
+ self.pack_start(cancelAddTask, false, false, 0);
+ self.pack_start(newTaskEntryRevealer, true, true, 0);
+ self.pack_start(confirmAddTask, false, false, 0);
+ self.pack_start(newTaskButton, false, false, 0);
+ }
+ }), false, false, 0);
+ },
+ });
+}
+
+export const TodoWidget = () => TabContainer({
+ icons: ['format_list_bulleted', 'task_alt'],
+ names: ['Unfinished', 'Done'],
+ children: [
+ UndoneTodoList(),
+ todoItems(true),
+ ]
+})
diff --git a/ags/scripts/README.md b/ags/scripts/README.md
new file mode 100644
index 00000000..6d79a514
--- /dev/null
+++ b/ags/scripts/README.md
@@ -0,0 +1,3 @@
+# scripts folder
+- For ARM devices, you have to compile C++ files yourself. If there are none, yippee
+- It is advised to use services instead of listening/polling scripts, so everything here are just scripts for actions
\ No newline at end of file
diff --git a/ags/scripts/color_generation/applycolor.sh b/ags/scripts/color_generation/applycolor.sh
new file mode 100755
index 00000000..db056d43
--- /dev/null
+++ b/ags/scripts/color_generation/applycolor.sh
@@ -0,0 +1,182 @@
+#!/usr/bin/env bash
+
+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
+XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
+CONFIG_DIR="$XDG_CONFIG_HOME/ags"
+CACHE_DIR="$XDG_CACHE_HOME/ags"
+STATE_DIR="$XDG_STATE_HOME/ags"
+
+term_alpha=100 #Set this to < 100 make all your terminals transparent
+# sleep 0 # idk i wanted some delay or colors dont get applied properly
+if [ ! -d "$CACHE_DIR"/user/generated ]; then
+ mkdir -p "$CACHE_DIR"/user/generated
+fi
+cd "$CONFIG_DIR" || exit
+
+colornames=''
+colorstrings=''
+colorlist=()
+colorvalues=()
+
+# wallpath=$(swww query | head -1 | awk -F 'image: ' '{print $2}')
+# wallpath_png="$CACHE_DIR/user/generated/hypr/lockscreen.png"
+# convert "$wallpath" "$wallpath_png"
+# wallpath_png=$(echo "$wallpath_png" | sed 's/\//\\\//g')
+# wallpath_png=$(sed 's/\//\\\\\//g' <<< "$wallpath_png")
+
+transparentize() {
+ local hex="$1"
+ local alpha="$2"
+ local red green blue
+
+ red=$((16#${hex:1:2}))
+ green=$((16#${hex:3:2}))
+ blue=$((16#${hex:5:2}))
+
+ printf 'rgba(%d, %d, %d, %.2f)\n' "$red" "$green" "$blue" "$alpha"
+}
+
+get_light_dark() {
+ lightdark=""
+ if [ ! -f "$STATE_DIR/user/colormode.txt" ]; then
+ echo "" > "$STATE_DIR/user/colormode.txt"
+ else
+ lightdark=$(sed -n '1p' "$STATE_DIR/user/colormode.txt")
+ fi
+ echo "$lightdark"
+}
+
+apply_fuzzel() {
+ # Check if scripts/templates/fuzzel/fuzzel.ini exists
+ if [ ! -f "scripts/templates/fuzzel/fuzzel.ini" ]; then
+ echo "Template file not found for Fuzzel. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$CACHE_DIR"/user/generated/fuzzel
+ cp "scripts/templates/fuzzel/fuzzel.ini" "$CACHE_DIR"/user/generated/fuzzel/fuzzel.ini
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/fuzzel/fuzzel.ini
+ done
+
+ cp "$CACHE_DIR"/user/generated/fuzzel/fuzzel.ini "$XDG_CONFIG_HOME"/fuzzel/fuzzel.ini
+}
+
+apply_term() {
+ # Check if terminal escape sequence template exists
+ if [ ! -f "scripts/templates/terminal/sequences.txt" ]; then
+ echo "Template file not found for Terminal. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$CACHE_DIR"/user/generated/terminal
+ cp "scripts/templates/terminal/sequences.txt" "$CACHE_DIR"/user/generated/terminal/sequences.txt
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/terminal/sequences.txt
+ done
+
+ sed -i "s/\$alpha/$term_alpha/g" "$CACHE_DIR/user/generated/terminal/sequences.txt"
+
+ for file in /dev/pts/*; do
+ if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then
+ cat "$CACHE_DIR"/user/generated/terminal/sequences.txt > "$file"
+ fi
+ done
+}
+
+apply_hyprland() {
+ # Check if scripts/templates/hypr/hyprland/colors.conf exists
+ if [ ! -f "scripts/templates/hypr/hyprland/colors.conf" ]; then
+ echo "Template file not found for Hyprland colors. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$CACHE_DIR"/user/generated/hypr/hyprland
+ cp "scripts/templates/hypr/hyprland/colors.conf" "$CACHE_DIR"/user/generated/hypr/hyprland/colors.conf
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/hypr/hyprland/colors.conf
+ done
+
+ cp "$CACHE_DIR"/user/generated/hypr/hyprland/colors.conf "$XDG_CONFIG_HOME"/hypr/hyprland/colors.conf
+}
+
+apply_hyprlock() {
+ # Check if scripts/templates/hypr/hyprlock.conf exists
+ if [ ! -f "scripts/templates/hypr/hyprlock.conf" ]; then
+ echo "Template file not found for hyprlock. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$CACHE_DIR"/user/generated/hypr/
+ cp "scripts/templates/hypr/hyprlock.conf" "$CACHE_DIR"/user/generated/hypr/hyprlock.conf
+ # Apply colors
+ # sed -i "s/{{ SWWW_WALL }}/${wallpath_png}/g" "$CACHE_DIR"/user/generated/hypr/hyprlock.conf
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/hypr/hyprlock.conf
+ done
+
+ cp "$CACHE_DIR"/user/generated/hypr/hyprlock.conf "$XDG_CONFIG_HOME"/hypr/hyprlock.conf
+}
+
+apply_lightdark() {
+ lightdark=$(get_light_dark)
+ if [ "$lightdark" = "light" ]; then
+ gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
+ else
+ gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
+ fi
+}
+
+apply_gtk() { # Using gradience-cli
+ usegradience=$(sed -n '4p' "$STATE_DIR/user/colormode.txt")
+ if [[ "$usegradience" = "nogradience" ]]; then
+ rm "$XDG_CONFIG_HOME/gtk-3.0/gtk.css"
+ rm "$XDG_CONFIG_HOME/gtk-4.0/gtk.css"
+ return
+ fi
+
+ # Copy template
+ mkdir -p "$CACHE_DIR"/user/generated/gradience
+ cp "scripts/templates/gradience/preset.json" "$CACHE_DIR"/user/generated/gradience/preset.json
+
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]}/g" "$CACHE_DIR"/user/generated/gradience/preset.json
+ done
+
+ mkdir -p "$XDG_CONFIG_HOME/presets" # create gradience presets folder
+ gradience-cli apply -p "$CACHE_DIR"/user/generated/gradience/preset.json --gtk both
+
+ # And set GTK theme manually as Gradience defaults to light adw-gtk3
+ # (which is unreadable when broken when you use dark mode)
+ lightdark=$(get_light_dark)
+ if [ "$lightdark" = "light" ]; then
+ gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3'
+ else
+ gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3-dark
+ fi
+}
+
+apply_ags() {
+ ags run-js "handleStyles(false);"
+ ags run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);'
+}
+
+
+colornames=$(cat $STATE_DIR/scss/_material.scss | cut -d: -f1)
+colorstrings=$(cat $STATE_DIR/scss/_material.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1)
+IFS=$'\n'
+colorlist=( $colornames ) # Array of color names
+colorvalues=( $colorstrings ) # Array of color values
+
+apply_ags &
+apply_hyprland &
+apply_hyprlock &
+apply_lightdark &
+apply_gtk &
+apply_fuzzel &
+apply_term &
diff --git a/ags/scripts/color_generation/colorgen.sh b/ags/scripts/color_generation/colorgen.sh
new file mode 100755
index 00000000..9a19d8eb
--- /dev/null
+++ b/ags/scripts/color_generation/colorgen.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+
+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
+XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
+CONFIG_DIR="$XDG_CONFIG_HOME/ags"
+CACHE_DIR="$XDG_CACHE_HOME/ags"
+STATE_DIR="$XDG_STATE_HOME/ags"
+
+# check if no arguments
+if [ $# -eq 0 ]; then
+ echo "Usage: colorgen.sh /path/to/image (--apply)"
+ exit 1
+fi
+
+# check if the file $STATE_DIR/user/colormode.txt exists. if not, create it. else, read it to $lightdark
+colormodefile="$STATE_DIR/user/colormode.txt"
+lightdark="dark"
+transparency="opaque"
+materialscheme="vibrant"
+terminalscheme="$XDG_CONFIG_HOME/ags/scripts/templates/terminal/scheme-base.json"
+
+if [ ! -f $colormodefile ]; then
+ echo "dark" > $colormodefile
+ echo "opaque" >> $colormodefile
+ echo "vibrant" >> $colormodefile
+elif [[ $(wc -l < $colormodefile) -ne 4 || $(wc -w < $colormodefile) -ne 4 ]]; then
+ echo "dark" > $colormodefile
+ echo "opaque" >> $colormodefile
+ echo "vibrant" >> $colormodefile
+ echo "yesgradience" >> $colormodefile
+else
+ lightdark=$(sed -n '1p' $colormodefile)
+ transparency=$(sed -n '2p' $colormodefile)
+ materialscheme=$(sed -n '3p' $colormodefile)
+ if [ "$materialscheme" = "monochrome" ]; then
+ terminalscheme="$XDG_CONFIG_HOME/ags/scripts/templates/terminal/scheme-monochrome.json"
+ fi
+fi
+backend="material" # color generator backend
+if [ ! -f "$STATE_DIR/user/colorbackend.txt" ]; then
+ echo "material" > "$STATE_DIR/user/colorbackend.txt"
+else
+ backend=$(cat "$STATE_DIR/user/colorbackend.txt") # either "" or "-l"
+fi
+
+cd "$CONFIG_DIR/scripts/" || exit
+if [[ "$1" = "#"* ]]; then # this is a color
+ color_generation/generate_colors_material.py --color "$1" \
+ --mode "$lightdark" --scheme "$materialscheme" --transparency "$transparency" \
+ --termscheme $terminalscheme --blend_bg_fg \
+ > "$CACHE_DIR"/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ cp "$CACHE_DIR"/user/generated/material_colors.scss "$STATE_DIR/scss/_material.scss"
+ color_generation/applycolor.sh
+ fi
+elif [ "$backend" = "material" ]; then
+ smartflag=''
+ if [ "$3" = "--smart" ]; then
+ smartflag='--smart'
+ fi
+ color_generation/generate_colors_material.py --path "$1" \
+ --mode "$lightdark" --scheme "$materialscheme" --transparency "$transparency" \
+ --termscheme $terminalscheme --blend_bg_fg \
+ --cache "$STATE_DIR/user/color.txt" $smartflag \
+ > "$CACHE_DIR"/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ cp "$CACHE_DIR"/user/generated/material_colors.scss "$STATE_DIR/scss/_material.scss"
+ color_generation/applycolor.sh
+ fi
+elif [ "$backend" = "pywal" ]; then
+ # clear and generate
+ wal -c
+ wal -i "$1" -n $lightdark -q
+ # copy scss
+ cp "$XDG_CACHE_HOME/wal/colors.scss" "$CACHE_DIR"/user/generated/material_colors.scss
+
+ cat color_generation/pywal_to_material.scss >> "$CACHE_DIR"/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ sass -I "$STATE_DIR/scss" -I "$CONFIG_DIR/scss/fallback" "$CACHE_DIR"/user/generated/material_colors.scss "$CACHE_DIR"/user/generated/colors_classes.scss --style compressed
+ sed -i "s/ { color//g" "$CACHE_DIR"/user/generated/colors_classes.scss
+ sed -i "s/\./$/g" "$CACHE_DIR"/user/generated/colors_classes.scss
+ sed -i "s/}//g" "$CACHE_DIR"/user/generated/colors_classes.scss
+ if [ "$lightdark" = "-l" ]; then
+ printf "\n""\$darkmode: false;""\n" >> "$CACHE_DIR"/user/generated/colors_classes.scss
+ else
+ printf "\n""\$darkmode: true;""\n" >> "$CACHE_DIR"/user/generated/colors_classes.scss
+ fi
+
+ cp "$CACHE_DIR"/user/generated/colors_classes.scss "$STATE_DIR/scss/_material.scss"
+
+ color_generation/applycolor.sh
+ fi
+fi
diff --git a/ags/scripts/color_generation/generate_colors_material.py b/ags/scripts/color_generation/generate_colors_material.py
new file mode 100755
index 00000000..714e9597
--- /dev/null
+++ b/ags/scripts/color_generation/generate_colors_material.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+import argparse
+import math
+import json
+from PIL import Image
+from materialyoucolor.quantize import QuantizeCelebi
+from materialyoucolor.score.score import Score
+from materialyoucolor.hct import Hct
+from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
+from materialyoucolor.utils.color_utils import (rgba_from_argb, argb_from_rgb, argb_from_rgba)
+from materialyoucolor.utils.math_utils import (sanitize_degrees_double, difference_degrees, rotation_direction)
+
+parser = argparse.ArgumentParser(description='Color generation script')
+parser.add_argument('--path', type=str, default=None, help='generate colorscheme from image')
+parser.add_argument('--size', type=int , default=128 , help='bitmap image size')
+parser.add_argument('--color', type=str, default=None, help='generate colorscheme from color')
+parser.add_argument('--mode', type=str, choices=['dark', 'light'], default='dark', help='dark or light mode')
+parser.add_argument('--scheme', type=str, default='vibrant', help='material scheme to use')
+parser.add_argument('--smart', action='store_true', default=False, help='decide scheme type based on image color')
+parser.add_argument('--transparency', type=str, choices=['opaque', 'transparent'], default='opaque', help='enable transparency')
+parser.add_argument('--termscheme', type=str, default=None, help='JSON file containg the terminal scheme for generating term colors')
+parser.add_argument('--harmony', type=float , default=0.8, help='(0-1) Color hue shift towards accent')
+parser.add_argument('--harmonize_threshold', type=float , default=100, help='(0-180) Max threshold angle to limit color hue shift')
+parser.add_argument('--term_fg_boost', type=float , default=0.35, help='Make terminal foreground more different from the background')
+parser.add_argument('--blend_bg_fg', action='store_true', default=False, help='Shift terminal background or foreground towards accent')
+parser.add_argument('--cache', type=str, default=None, help='file path to store the generated color')
+parser.add_argument('--debug', action='store_true', default=False, help='debug mode')
+args = parser.parse_args()
+
+rgba_to_hex = lambda rgba: "#{:02X}{:02X}{:02X}".format(rgba[0], rgba[1], rgba[2])
+argb_to_hex = lambda argb: "#{:02X}{:02X}{:02X}".format(*map(round, rgba_from_argb(argb)))
+hex_to_argb = lambda hex_code: argb_from_rgb(int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:], 16))
+display_color = lambda rgba : "\x1B[38;2;{};{};{}m{}\x1B[0m".format(rgba[0], rgba[1], rgba[2], "\x1b[7m \x1b[7m")
+
+def calculate_optimal_size (width: int, height: int, bitmap_size: int) -> (int, int):
+ image_area = width * height;
+ bitmap_area = bitmap_size ** 2
+ scale = math.sqrt(bitmap_area/image_area) if image_area > bitmap_area else 1
+ new_width = round(width * scale)
+ new_height = round(height * scale)
+ if new_width == 0:
+ new_width = 1
+ if new_height == 0:
+ new_height = 1
+ return new_width, new_height
+
+def harmonize (design_color: int, source_color: int, threshold: float = 35, harmony: float = 0.5) -> int:
+ from_hct = Hct.from_int(design_color)
+ to_hct = Hct.from_int(source_color)
+ difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue)
+ rotation_degrees = min(difference_degrees_ * harmony, threshold)
+ output_hue = sanitize_degrees_double(
+ from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue)
+ )
+ return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int()
+
+def boost_chroma_tone (argb: int, chroma: float = 1, tone: float = 1) -> int:
+ hct = Hct.from_int(argb)
+ return Hct.from_hct(hct.hue, hct.chroma * chroma, hct.tone * tone).to_int()
+
+darkmode = (args.mode == 'dark')
+transparent = (args.transparency == 'transparent')
+
+if args.path is not None:
+ image = Image.open(args.path)
+
+ if image.format == "GIF":
+ image.seek(1)
+
+ wsize, hsize = image.size
+ wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, args.size)
+ if wsize_new < wsize or hsize_new < hsize:
+ image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC)
+ colors = QuantizeCelebi(list(image.getdata()), 128)
+ argb = Score.score(colors)[0]
+
+ if args.cache is not None:
+ with open(args.cache, 'w') as file:
+ file.write(argb_to_hex(argb))
+ hct = Hct.from_int(argb)
+ if(args.smart):
+ if(hct.chroma < 20):
+ args.scheme = 'neutral'
+ if(hct.tone > 60):
+ darkmode = False
+elif args.color is not None:
+ argb = hex_to_argb(args.color)
+ hct = Hct.from_int(argb)
+
+if args.scheme == 'fruitsalad':
+ from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme
+elif args.scheme == 'expressive':
+ from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme
+elif args.scheme == 'monochrome':
+ from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme
+elif args.scheme == 'rainbow':
+ from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme
+elif args.scheme == 'tonalspot':
+ from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme
+elif args.scheme == 'neutral':
+ from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme
+elif args.scheme == 'fidelity':
+ from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme
+elif args.scheme == 'content':
+ from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme
+elif args.scheme == 'vibrant':
+ from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme
+else:
+ from schemes.scheme_morevibrant import SchemeMoreVibrant as Scheme
+
+# Generate
+scheme = Scheme(hct, darkmode, 0.0)
+
+material_colors = {}
+term_colors = {}
+
+for color in vars(MaterialDynamicColors).keys():
+ color_name = getattr(MaterialDynamicColors, color)
+ if hasattr(color_name, "get_hct"):
+ rgba = color_name.get_hct(scheme).to_rgba()
+ material_colors[color] = rgba_to_hex(rgba)
+
+# Extended material
+if darkmode == True:
+ material_colors['success'] = '#B5CCBA'
+ material_colors['onSuccess'] = '#213528'
+ material_colors['successContainer'] = '#374B3E'
+ material_colors['onSuccessContainer'] = '#D1E9D6'
+else:
+ material_colors['success'] = '#4F6354'
+ material_colors['onSuccess'] = '#FFFFFF'
+ material_colors['successContainer'] = '#D1E8D5'
+ material_colors['onSuccessContainer'] = '#0C1F13'
+
+# Terminal Colors
+if args.termscheme is not None:
+ with open(args.termscheme, 'r') as f:
+ json_termscheme = f.read()
+ term_source_colors = json.loads(json_termscheme)['dark' if darkmode else 'light']
+
+ primary_color_argb = hex_to_argb(material_colors['primary_paletteKeyColor'])
+ for color, val in term_source_colors.items():
+ if(args.scheme == 'monochrome') :
+ term_colors[color] = val
+ continue
+ if args.blend_bg_fg and color == "term0":
+ harmonized = boost_chroma_tone(hex_to_argb(material_colors['surfaceContainerLow']), 1.2, 0.95)
+ elif args.blend_bg_fg and color == "term15":
+ harmonized = boost_chroma_tone(hex_to_argb(material_colors['onSurface']), 3, 1)
+ else:
+ harmonized = harmonize(hex_to_argb(val), primary_color_argb, args.harmonize_threshold, args.harmony)
+ harmonized = boost_chroma_tone(harmonized, 1, 1 + (args.term_fg_boost * (1 if darkmode else -1)))
+ term_colors[color] = argb_to_hex(harmonized)
+
+if args.debug == False:
+ print(f"$darkmode: {darkmode};")
+ print(f"$transparent: {transparent};")
+ for color, code in material_colors.items():
+ print(f"${color}: {code};")
+ for color, code in term_colors.items():
+ print(f"${color}: {code};")
+else:
+ if args.path is not None:
+ print('\n--------------Image properties-----------------')
+ print(f"Image size: {wsize} x {hsize}")
+ print(f"Resized image: {wsize_new} x {hsize_new}")
+ print('\n---------------Selected color------------------')
+ print(f"Dark mode: {darkmode}")
+ print(f"Scheme: {args.scheme}")
+ print(f"Accent color: {display_color(rgba_from_argb(argb))} {argb_to_hex(argb)}")
+ print(f"HCT: {hct.hue:.2f} {hct.chroma:.2f} {hct.tone:.2f}")
+ print('\n---------------Material colors-----------------')
+ for color, code in material_colors.items():
+ rgba = rgba_from_argb(hex_to_argb(code))
+ print(f"{color.ljust(32)} : {display_color(rgba)} {code}")
+ print('\n----------Harmonize terminal colors------------')
+ for color, code in term_colors.items():
+ rgba = rgba_from_argb(hex_to_argb(code))
+ code_source = term_source_colors[color]
+ rgba_source = rgba_from_argb(hex_to_argb(code_source))
+ print(f"{color.ljust(6)} : {display_color(rgba_source)} {code_source} --> {display_color(rgba)} {code}")
+ print('-----------------------------------------------')
diff --git a/ags/scripts/color_generation/pywal_to_material.scss b/ags/scripts/color_generation/pywal_to_material.scss
new file mode 100644
index 00000000..ff049b21
--- /dev/null
+++ b/ags/scripts/color_generation/pywal_to_material.scss
@@ -0,0 +1,57 @@
+$primary: lighten($color4, 20%);
+$onPrimary: darken($color2, 20%);
+$primaryContainer: darken($color2, 10%);
+$onPrimaryContainer: lighten($color4, 10%);
+$secondary: desaturate(lighten($color5, 20%), 20%);
+$onSecondary: desaturate(darken($color3, 20%), 20%);
+$secondaryContainer: desaturate(darken($color3, 20%), 20%);
+$onSecondaryContainer: desaturate(lighten($color5, 20%), 20%);
+$tertiary: adjust-hue(lighten($color4, 20%), 30deg);
+$onTertiary: adjust-hue(darken($color2, 20%), 30deg);
+$tertiaryContainer: adjust-hue(darken($color2, 10%), 30deg);
+$tertiaryContainer: adjust-hue(lighten($color4, 10%), 30deg);
+$error: #ffb4a9;
+$onError: #680003;
+$errorContainer: #930006;
+$onErrorContainer: #ffb4a9;
+$colorbarbg: $color0;
+$background: $color0;
+$onBackground: $color7;
+$surface: $color0;
+$onSurface: $color7;
+$surfaceVariant: $color1;
+$onSurfaceVariant: $color7;
+$outline: $color7;
+$shadow: #000000;
+$inverseSurface: invert($surface);
+$inverseOnSurface: invert($onSurface);
+$inversePrimary: invert($primary);
+
+.primary { color: $primary; }
+.onPrimary { color: $onPrimary; }
+.primaryContainer { color: $primaryContainer; }
+.onPrimaryContainer { color: $onPrimaryContainer; }
+.secondary { color: $secondary; }
+.onSecondary { color: $onSecondary; }
+.secondaryContainer { color: $secondaryContainer; }
+.onSecondaryContainer { color: $onSecondaryContainer; }
+.tertiary { color: $tertiary; }
+.onTertiary { color: $onTertiary; }
+.tertiaryContainer { color: $tertiaryContainer; }
+.onTertiaryContainer { color: $tertiaryContainer; }
+.error { color: $error; }
+.onError { color: $onError; }
+.errorContainer { color: $errorContainer; }
+.onErrorContainer { color: $onErrorContainer; }
+.colorbarbg { color: $colorbarbg; }
+.background { color: $background; }
+.onBackground { color: $onBackground; }
+.surface { color: $surface; }
+.onSurface { color: $onSurface; }
+.surfaceVariant { color: $surfaceVariant; }
+.onSurfaceVariant { color: $onSurfaceVariant; }
+.outline { color: $outline; }
+.shadow { color: $shadow; }
+.inverseSurface { color: $inverseSurface; }
+.inverseOnSurface { color: $inverseOnSurface; }
+.inversePrimary { color: $inversePrimary; }
diff --git a/ags/scripts/color_generation/randomwall.sh b/ags/scripts/color_generation/randomwall.sh
new file mode 100755
index 00000000..f2b18b3f
--- /dev/null
+++ b/ags/scripts/color_generation/randomwall.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+CONFIG_DIR="$XDG_CONFIG_HOME/ags"
+$CONFIG_DIR/scripts/color_generation/switchwall.sh "$(fd . $(xdg-user-dir PICTURES)/wallpapers/ -e .png -e .jpg -e .svg | xargs shuf -n1 -e)"
diff --git a/ags/scripts/color_generation/schemes/scheme_morevibrant.py b/ags/scripts/color_generation/schemes/scheme_morevibrant.py
new file mode 100644
index 00000000..d67f7834
--- /dev/null
+++ b/ags/scripts/color_generation/schemes/scheme_morevibrant.py
@@ -0,0 +1,44 @@
+from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme
+from materialyoucolor.scheme.variant import Variant
+from materialyoucolor.palettes.tonal_palette import TonalPalette
+
+
+class SchemeMoreVibrant(DynamicScheme):
+ hues = [0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0]
+ secondary_rotations = [18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0]
+ tertiary_rotations = [35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0]
+
+ def __init__(self, source_color_hct, is_dark, contrast_level):
+ super().__init__(
+ DynamicSchemeOptions(
+ source_color_argb=source_color_hct.to_int(),
+ variant=Variant.VIBRANT,
+ contrast_level=contrast_level,
+ is_dark=is_dark,
+ primary_palette=TonalPalette.from_hue_and_chroma(
+ source_color_hct.hue, 200.0
+ ),
+ secondary_palette=TonalPalette.from_hue_and_chroma(
+ DynamicScheme.get_rotated_hue(
+ source_color_hct,
+ SchemeMoreVibrant.hues,
+ SchemeMoreVibrant.secondary_rotations,
+ ),
+ 32.0,
+ ),
+ tertiary_palette=TonalPalette.from_hue_and_chroma(
+ DynamicScheme.get_rotated_hue(
+ source_color_hct,
+ SchemeMoreVibrant.hues,
+ SchemeMoreVibrant.tertiary_rotations,
+ ),
+ 32.0,
+ ),
+ neutral_palette=TonalPalette.from_hue_and_chroma(
+ source_color_hct.hue, 13.0
+ ),
+ neutral_variant_palette=TonalPalette.from_hue_and_chroma(
+ source_color_hct.hue, 15.0
+ ),
+ )
+ )
diff --git a/ags/scripts/color_generation/specials/_material_badapple-l.scss b/ags/scripts/color_generation/specials/_material_badapple-l.scss
new file mode 100644
index 00000000..12c047b0
--- /dev/null
+++ b/ags/scripts/color_generation/specials/_material_badapple-l.scss
@@ -0,0 +1,29 @@
+$darkmode: false;
+$primary: #000000;
+$onPrimary: #FFFFFF ;
+$primaryContainer: #d4d4d4;
+$onPrimaryContainer: #000000;
+$secondary: #000000;
+$onSecondary: #FFFFFF ;
+$secondaryContainer: #bebebe;
+$onSecondaryContainer: #000000;
+$tertiary: #000000;
+$onTertiary: #FFFFFF ;
+$tertiaryContainer: #FFFFFF ;
+$onTertiaryContainer: #000000;
+$error: #000000;
+$onError: #FFFFFF ;
+$errorContainer: #FFFFFF ;
+$onErrorContainer: #000000;
+$colorbarbg: #FFFFFF ;
+$background: #FFFFFF ;
+$onBackground: #000000;
+$surface: #f0f0f0;
+$onSurface: #000000;
+$surfaceVariant: #dddddd;
+$onSurfaceVariant: #000000;
+$outline: #525252;
+$shadow: #000000 ;
+$inverseSurface: #000000;
+$inverseOnSurface: #FFFFFF;
+$inversePrimary: #000000;
diff --git a/ags/scripts/color_generation/specials/_material_badapple.scss b/ags/scripts/color_generation/specials/_material_badapple.scss
new file mode 100644
index 00000000..24dd73ff
--- /dev/null
+++ b/ags/scripts/color_generation/specials/_material_badapple.scss
@@ -0,0 +1,29 @@
+$darkmode: true;
+$primary: #e2e2e2;
+$onPrimary: #000000;
+$primaryContainer: #6b6b6b;
+$onPrimaryContainer: #e2e2e2;
+$secondary: #e2e2e2;
+$onSecondary: #000000;
+$secondaryContainer: #313131;
+$onSecondaryContainer: #e2e2e2;
+$tertiary: #e2e2e2;
+$onTertiary: #000000;
+$tertiaryContainer: #000000;
+$onTertiaryContainer: #e2e2e2;
+$error: #e2e2e2;
+$onError: #000000;
+$errorContainer: #000000;
+$onErrorContainer: #e2e2e2;
+$colorbarbg: #000000;
+$background: #000000;
+$onBackground: #e2e2e2;
+$surface: #161616;
+$onSurface: #e2e2e2;
+$surfaceVariant: #242424;
+$onSurfaceVariant: #e2e2e2;
+$outline: #a1a1a1;
+$shadow: #000000;
+$inverseSurface: #e2e2e2;
+$inverseOnSurface: #000000;
+$inversePrimary: #e2e2e2;
diff --git a/ags/scripts/color_generation/switchcolor.sh b/ags/scripts/color_generation/switchcolor.sh
new file mode 100755
index 00000000..183efc7c
--- /dev/null
+++ b/ags/scripts/color_generation/switchcolor.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
+XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
+CONFIG_DIR="$XDG_CONFIG_HOME/ags"
+CACHE_DIR="$XDG_CACHE_HOME/ags"
+STATE_DIR="$XDG_STATE_HOME/ags"
+
+COLORMODE_FILE_DIR="$STATE_DIR/user/colormode.txt"
+
+if [ "$1" == "--pick" ]; then
+ color=$(hyprpicker --no-fancy)
+elif [[ "$1" = "#"* ]]; then # this is a color
+ color=$1
+else
+ color=$(cut -f1 "$STATE_DIR/user/color.txt")
+fi
+
+sed -i "1s/.*/$color/" "$STATE_DIR/user/color.txt"
+
+# Use Gradience?
+colormodelines=$(wc -l "$COLORMODE_FILE_DIR" | awk '{print $1}' )
+if [ "$2" == "--no-gradience" ]; then
+ if [ "$colormodelines" == "3" ]; then
+ echo 'nogradience' >> "$COLORMODE_FILE_DIR"
+ else
+ sed -i "4s/.*/nogradience/" "$COLORMODE_FILE_DIR"
+ fi
+elif [ "$2" == "--yes-gradience" ]; then
+ if [ "$colormodelines" == "3" ]; then
+ echo 'yesgradience' >> "$COLORMODE_FILE_DIR"
+ else
+ sed -i "4s/.*/yesgradience/" "$COLORMODE_FILE_DIR"
+ fi
+fi
+
+# Generate colors for ags n stuff
+"$CONFIG_DIR"/scripts/color_generation/colorgen.sh "${color}" --apply
diff --git a/ags/scripts/color_generation/switchwall.sh b/ags/scripts/color_generation/switchwall.sh
new file mode 100755
index 00000000..e2d68cab
--- /dev/null
+++ b/ags/scripts/color_generation/switchwall.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+CONFIG_DIR="$XDG_CONFIG_HOME/ags"
+
+switch() {
+ imgpath=$1
+ read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs)
+ cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960
+ cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1")
+ cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540
+ cursorposy=$(bc <<< "scale=0; ($cursorposy - $screeny) * $scale / 1")
+ cursorposy_inverted=$((screensizey - cursorposy))
+
+ if [ "$imgpath" == '' ]; then
+ echo 'Aborted'
+ exit 0
+ fi
+
+ # ags run-js "wallpaper.set('')"
+ # sleep 0.1 && ags run-js "wallpaper.set('${imgpath}')" &
+ swww img "$imgpath" --transition-step 100 --transition-fps 120 \
+ --transition-type grow --transition-angle 30 --transition-duration 1 \
+ --transition-pos "$cursorposx, $cursorposy_inverted"
+}
+
+if [ "$1" == "--noswitch" ]; then
+ imgpath=$(swww query | awk -F 'image: ' '{print $2}')
+ # imgpath=$(ags run-js 'wallpaper.get(0)')
+elif [[ "$1" ]]; then
+ switch "$1"
+else
+ # Select and set image (hyprland)
+
+ cd "$(xdg-user-dir PICTURES)" || return 1
+ switch "$(yad --width 1200 --height 800 --file --add-preview --large-preview --title='Choose wallpaper')"
+fi
+
+# Generate colors for ags n stuff
+"$CONFIG_DIR"/scripts/color_generation/colorgen.sh "${imgpath}" --apply --smart
diff --git a/ags/scripts/grimblast.sh b/ags/scripts/grimblast.sh
new file mode 100755
index 00000000..a5602f24
--- /dev/null
+++ b/ags/scripts/grimblast.sh
@@ -0,0 +1,277 @@
+#!/usr/bin/env bash
+## Grimblast: a helper for screenshots within hyprland
+## Requirements:
+## - `grim`: screenshot utility for wayland
+## - `slurp`: to select an area
+## - `hyprctl`: to read properties of current window (provided by Hyprland)
+## - `hyprpicker`: to freeze the screen when selecting area
+## - `wl-copy`: clipboard utility (provided by wl-clipboard)
+## - `jq`: json utility to parse hyprctl output
+## - `notify-send`: to show notifications (provided by libnotify)
+## Those are needed to be installed, if unsure, run `grimblast check`
+##
+## See `man 1 grimblast` or `grimblast usage` for further details.
+
+## Author: Misterio (https://github.com/misterio77)
+
+## This tool is based on grimshot, with swaymsg commands replaced by their
+## hyprctl equivalents.
+## https://github.com/swaywm/sway/blob/master/contrib/grimshot
+getTargetDirectory() {
+ test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" &&
+ . "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs"
+
+ echo "${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}"
+}
+
+tmp_editor_directory() {
+ echo "/tmp"
+}
+
+#Detect if $GRIMBLAST_EDITOR env exist
+env_editor_confirm() {
+ if [ -n "$GRIMBLAST_EDITOR" ]; then
+ echo "GRIMBLAST_EDITOR is set. Continuing..."
+ else
+ echo "GRIMBLAST_EDITOR is not set. Defaulting to gimp"
+ GRIMBLAST_EDITOR=gimp
+ fi
+}
+
+NOTIFY=no
+CURSOR=
+FREEZE=
+WAIT=no
+SCALE=
+HYPRPICKER_PID=-1
+
+while [ $# -gt 0 ]; do
+ key="$1"
+
+ case $key in
+ -n | --notify)
+ NOTIFY=yes
+ shift # past argument
+ ;;
+ -c | --cursor)
+ CURSOR=yes
+ shift # past argument
+ ;;
+ -f | --freeze)
+ FREEZE=yes
+ shift # past argument
+ ;;
+ -w | --wait)
+ shift
+ WAIT=$1
+ if echo "$WAIT" | grep "[^0-9]" -q; then
+ echo "Invalid value for wait '$WAIT'" >&2
+ exit 3
+ fi
+ shift
+ ;;
+ -s | --scale)
+ shift # past argument
+ if [ $# -gt 0 ]; then
+ SCALE="$1" # assign the next argument to SCALE
+ shift # past argument
+ else
+ echo "Error: Missing argument for --scale option."
+ exit 1
+ fi
+ ;;
+ *) # unknown option
+ break # done with parsing --flags
+ ;;
+ esac
+done
+
+ACTION=${1:-usage}
+SUBJECT=${2:-screen}
+FILE=${3:-$(getTargetDirectory)/$(date -Ins).png}
+FILE_EDITOR=${3:-$(tmp_editor_directory)/$(date -Ins).png}
+
+if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "edit" ] && [ "$ACTION" != "copysave" ] && [ "$ACTION" != "check" ]; then
+ echo "Usage:"
+ echo " grimblast [--notify] [--cursor] [--freeze] [--wait N] [--scale ] (copy|save|copysave|edit) [active|screen|output|area] [FILE|-]"
+ echo " grimblast check"
+ echo " grimblast usage"
+ echo ""
+ echo "Commands:"
+ echo " copy: Copy the screenshot data into the clipboard."
+ echo " save: Save the screenshot to a regular file or '-' to pipe to STDOUT."
+ echo " copysave: Combine the previous 2 options."
+ echo " edit: Open screenshot in the image editor of your choice (default is gimp). See man page for info."
+ echo " check: Verify if required tools are installed and exit."
+ echo " usage: Show this message and exit."
+ echo ""
+ echo "Targets:"
+ echo " active: Currently active window."
+ echo " screen: All visible outputs."
+ echo " output: Currently active output."
+ echo " area: Manually select a region or window."
+ exit
+fi
+
+notify() {
+ notify-send -t 3000 -a grimblast "$@"
+}
+
+notifyOk() {
+ [ "$NOTIFY" = "no" ] && return
+
+ notify "$@"
+}
+
+notifyError() {
+ if [ $NOTIFY = "yes" ]; then
+ TITLE=${2:-"Screenshot"}
+ MESSAGE=${1:-"Error taking screenshot with grim"}
+ notify -u critical "$TITLE" "$MESSAGE"
+ else
+ echo "$1"
+ fi
+}
+
+resetFade() {
+ if [[ -n $FADE && -n $FADEOUT ]]; then
+ hyprctl keyword animation "$FADE" >/dev/null
+ hyprctl keyword animation "$FADEOUT" >/dev/null
+ fi
+}
+
+killHyprpicker() {
+ if [ ! $HYPRPICKER_PID -eq -1 ]; then
+ kill $HYPRPICKER_PID
+ fi
+}
+
+die() {
+ killHyprpicker
+ MSG=${1:-Bye}
+ notifyError "Error: $MSG"
+ exit 2
+}
+
+check() {
+ COMMAND=$1
+ if command -v "$COMMAND" >/dev/null 2>&1; then
+ RESULT="OK"
+ else
+ RESULT="NOT FOUND"
+ fi
+ echo " $COMMAND: $RESULT"
+}
+
+takeScreenshot() {
+ FILE=$1
+ GEOM=$2
+ OUTPUT=$3
+ if [ -n "$OUTPUT" ]; then
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -o "$OUTPUT" "$FILE" || die "Unable to invoke grim"
+ elif [ -z "$GEOM" ]; then
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} "$FILE" || die "Unable to invoke grim"
+ else
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -g "$GEOM" "$FILE" || die "Unable to invoke grim"
+ resetFade
+ fi
+}
+
+wait() {
+ if [ "$WAIT" != "no" ]; then
+ sleep "$WAIT"
+ fi
+}
+
+if [ "$ACTION" = "check" ]; then
+ echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
+ check grim
+ check slurp
+ check hyprctl
+ check hyprpicker
+ check wl-copy
+ check jq
+ check notify-send
+ exit
+elif [ "$SUBJECT" = "active" ]; then
+ wait
+ FOCUSED=$(hyprctl activewindow -j)
+ GEOM=$(echo "$FOCUSED" | jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')
+ APP_ID=$(echo "$FOCUSED" | jq -r '.class')
+ WHAT="$APP_ID window"
+elif [ "$SUBJECT" = "screen" ]; then
+ wait
+ GEOM=""
+ WHAT="Screen"
+elif [ "$SUBJECT" = "output" ]; then
+ wait
+ GEOM=""
+ OUTPUT=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true)' | jq -r '.name')
+ WHAT="$OUTPUT"
+elif [ "$SUBJECT" = "area" ]; then
+ if [ "$FREEZE" = "yes" ] && [ "$(command -v "hyprpicker")" ] >/dev/null 2>&1; then
+ hyprpicker -r -z &
+ sleep 0.2
+ HYPRPICKER_PID=$!
+ fi
+
+ # get fade & fadeOut animation and unset it
+ # this removes the black border seen around screenshots
+ FADE="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fade") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')"
+ FADEOUT="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fadeOut") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')"
+ hyprctl keyword animation 'fade,0,1,default' >/dev/null
+ hyprctl keyword animation 'fadeOut,0,1,default' >/dev/null
+
+ WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')"
+ WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))')"
+ # shellcheck disable=2086 # if we don't split, spaces mess up slurp
+ GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp $SLURP_ARGS)
+
+ # Check if user exited slurp without selecting the area
+ if [ -z "$GEOM" ]; then
+ killHyprpicker
+ resetFade
+ exit 1
+ fi
+ WHAT="Area"
+ wait
+elif [ "$SUBJECT" = "window" ]; then
+ die "Subject 'window' is now included in 'area'"
+else
+ die "Unknown subject to take a screen shot from" "$SUBJECT"
+fi
+
+if [ "$ACTION" = "copy" ]; then
+ takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error"
+ notifyOk "$WHAT copied to buffer"
+elif [ "$ACTION" = "save" ]; then
+ if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then
+ TITLE="Screenshot of $SUBJECT"
+ MESSAGE=$(basename "$FILE")
+ notifyOk "$TITLE" "$MESSAGE" -i "$FILE"
+ echo "$FILE"
+ else
+ notifyError "Error taking screenshot with grim"
+ fi
+elif [ "$ACTION" = "edit" ]; then
+ env_editor_confirm
+ if takeScreenshot "$FILE_EDITOR" "$GEOM" "$OUTPUT"; then
+ TITLE="Screenshot of $SUBJECT"
+ MESSAGE="Open screenshot in image editor"
+ notifyOk "$TITLE" "$MESSAGE" -i "$FILE_EDITOR"
+ $GRIMBLAST_EDITOR "$FILE_EDITOR"
+ echo "$FILE_EDITOR"
+ else
+ notifyError "Error taking screenshot"
+ fi
+else
+ if [ "$ACTION" = "copysave" ]; then
+ takeScreenshot - "$GEOM" "$OUTPUT" | tee "$FILE" | wl-copy --type image/png || die "Clipboard error"
+ notifyOk "$WHAT copied to buffer and saved to $FILE" -i "$FILE"
+ echo "$FILE"
+ else
+ notifyError "Error taking screenshot with grim"
+ fi
+fi
+
+killHyprpicker
diff --git a/ags/scripts/hyprland/get_keybinds.py b/ags/scripts/hyprland/get_keybinds.py
new file mode 100755
index 00000000..9b784df0
--- /dev/null
+++ b/ags/scripts/hyprland/get_keybinds.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+import argparse
+import re
+import os
+from os.path import expandvars as os_expandvars
+from typing import Dict, List
+
+TITLE_REGEX = "#+!"
+HIDE_COMMENT = "[hidden]"
+MOD_SEPARATORS = ['+', ' ']
+COMMENT_BIND_PATTERN = "#/#"
+
+parser = argparse.ArgumentParser(description='Hyprland keybind reader')
+parser.add_argument('--path', type=str, default="$HOME/.config/hypr/hyprland.conf", help='path to keybind file (sourcing isn\'t supported)')
+args = parser.parse_args()
+content_lines = []
+reading_line = 0
+
+# Little Parser made for hyprland keybindings conf file
+Variables: Dict[str, str] = {}
+
+
+class KeyBinding(dict):
+ def __init__(self, mods, key, dispatcher, params, comment) -> None:
+ self["mods"] = mods
+ self["key"] = key
+ self["dispatcher"] = dispatcher
+ self["params"] = params
+ self["comment"] = comment
+
+class Section(dict):
+ def __init__(self, children, keybinds, name) -> None:
+ self["children"] = children
+ self["keybinds"] = keybinds
+ self["name"] = name
+
+
+def read_content(path: str) -> str:
+ if (not os.access(os.path.expanduser(os.path.expandvars(path)), os.R_OK)):
+ return ("error")
+ with open(os.path.expanduser(os.path.expandvars(path)), "r") as file:
+ return file.read()
+
+
+def autogenerate_comment(dispatcher: str, params: str = "") -> str:
+ match dispatcher:
+
+ case "resizewindow":
+ return "Resize window"
+
+ case "movewindow":
+ if(params == ""):
+ return "Move window"
+ else:
+ return "Window: move in {} direction".format({
+ "l": "left",
+ "r": "right",
+ "u": "up",
+ "d": "down",
+ }.get(params, "null"))
+
+ case "pin":
+ return "Window: pin (show on all workspaces)"
+
+ case "splitratio":
+ return "Window split ratio {}".format(params)
+
+ case "togglefloating":
+ return "Float/unfloat window"
+
+ case "resizeactive":
+ return "Resize window by {}".format(params)
+
+ case "killactive":
+ return "Close window"
+
+ case "fullscreen":
+ return "Toggle {}".format(
+ {
+ "0": "fullscreen",
+ "1": "maximization",
+ "2": "fullscreen on Hyprland's side",
+ }.get(params, "null")
+ )
+
+ case "fakefullscreen":
+ return "Toggle fake fullscreen"
+
+ case "workspace":
+ if params == "+1":
+ return "Workspace: focus right"
+ elif params == "-1":
+ return "Workspace: focus left"
+ return "Focus workspace {}".format(params)
+
+ case "movefocus":
+ return "Window: move focus {}".format(
+ {
+ "l": "left",
+ "r": "right",
+ "u": "up",
+ "d": "down",
+ }.get(params, "null")
+ )
+
+ case "swapwindow":
+ return "Window: swap in {} direction".format(
+ {
+ "l": "left",
+ "r": "right",
+ "u": "up",
+ "d": "down",
+ }.get(params, "null")
+ )
+
+ case "movetoworkspace":
+ if params == "+1":
+ return "Window: move to right workspace (non-silent)"
+ elif params == "-1":
+ return "Window: move to left workspace (non-silent)"
+ return "Window: move to workspace {} (non-silent)".format(params)
+
+ case "movetoworkspacesilent":
+ if params == "+1":
+ return "Window: move to right workspace"
+ elif params == "-1":
+ return "Window: move to right workspace"
+ return "Window: move to workspace {}".format(params)
+
+ case "togglespecialworkspace":
+ return "Workspace: toggle special"
+
+ case "exec":
+ return "Execute: {}".format(params)
+
+ case _:
+ return ""
+
+def get_keybind_at_line(line_number, line_start = 0):
+ global content_lines
+ line = content_lines[line_number]
+ _, keys = line.split("=", 1)
+ keys, *comment = keys.split("#", 1)
+
+ mods, key, dispatcher, *params = list(map(str.strip, keys.split(",", 4)))
+ params = "".join(map(str.strip, params))
+
+ # Remove empty spaces
+ comment = list(map(str.strip, comment))
+ # Add comment if it exists, else generate it
+ if comment:
+ comment = comment[0]
+ if comment.startswith("[hidden]"):
+ return None
+ else:
+ comment = autogenerate_comment(dispatcher, params)
+
+ if mods:
+ modstring = mods + MOD_SEPARATORS[0] # Add separator at end to ensure last mod is read
+ mods = []
+ p = 0
+ for index, char in enumerate(modstring):
+ if(char in MOD_SEPARATORS):
+ if(index - p > 1):
+ mods.append(modstring[p:index])
+ p = index+1
+ else:
+ mods = []
+
+ return KeyBinding(mods, key, dispatcher, params, comment)
+
+def get_binds_recursive(current_content, scope):
+ global content_lines
+ global reading_line
+ # print("get_binds_recursive({0}, {1}) [@L{2}]".format(current_content, scope, reading_line + 1))
+ while reading_line < len(content_lines): # TODO: Adjust condition
+ line = content_lines[reading_line]
+ heading_search_result = re.search(TITLE_REGEX, line)
+ # print("Read line {0}: {1}\tisHeading: {2}".format(reading_line + 1, content_lines[reading_line], "[{0}, {1}, {2}]".format(heading_search_result.start(), heading_search_result.start() == 0, ((heading_search_result != None) and (heading_search_result.start() == 0))) if heading_search_result != None else "No"))
+ if ((heading_search_result != None) and (heading_search_result.start() == 0)): # Found title
+ # Determine scope
+ heading_scope = line.find('!')
+ # Lower? Return
+ if(heading_scope <= scope):
+ reading_line -= 1
+ return current_content
+
+ section_name = line[(heading_scope+1):].strip()
+ # print("[[ Found h{0} at line {1} ]] {2}".format(heading_scope, reading_line+1, content_lines[reading_line]))
+ reading_line += 1
+ current_content["children"].append(get_binds_recursive(Section([], [], section_name), heading_scope))
+
+ elif line.startswith(COMMENT_BIND_PATTERN):
+ keybind = get_keybind_at_line(reading_line, line_start=len(COMMENT_BIND_PATTERN))
+ if(keybind != None):
+ current_content["keybinds"].append(keybind)
+
+ elif line == "" or not line.lstrip().startswith("bind"): # Comment, ignore
+ pass
+
+ else: # Normal keybind
+ keybind = get_keybind_at_line(reading_line)
+ if(keybind != None):
+ current_content["keybinds"].append(keybind)
+
+ reading_line += 1
+
+ return current_content;
+
+def parse_keys(path: str) -> Dict[str, List[KeyBinding]]:
+ global content_lines
+ content_lines = read_content(path).splitlines()
+ if content_lines[0] == "error":
+ return "error"
+ return get_binds_recursive(Section([], [], ""), 0)
+
+
+if __name__ == "__main__":
+ import json
+
+ ParsedKeys = parse_keys(args.path)
+ print(json.dumps(ParsedKeys))
diff --git a/ags/scripts/hyprland/workspace_action.sh b/ags/scripts/hyprland/workspace_action.sh
new file mode 100755
index 00000000..a43badfc
--- /dev/null
+++ b/ags/scripts/hyprland/workspace_action.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | jq -r .id) - 1) / 10) * 10 + $2))
diff --git a/ags/scripts/quickscripts/nixos-trim-generations.sh b/ags/scripts/quickscripts/nixos-trim-generations.sh
new file mode 100755
index 00000000..98b59be9
--- /dev/null
+++ b/ags/scripts/quickscripts/nixos-trim-generations.sh
@@ -0,0 +1,243 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+## Defaults
+keepGensDef=30; keepDaysDef=30
+keepGens=$keepGensDef; keepDays=$keepDaysDef
+
+## Usage
+usage () {
+ printf "Usage:\n\t ./trim-generations.sh \n\n
+(defaults are: Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef Profile=user)\n\n"
+ printf "If you enter any parameters, you must enter all three, or none to use defaults.\n"
+ printf "Example:\n\t trim-generations.sh 15 10 home-manager\n"
+ printf " this will work on the home-manager profile and keep all generations from the\n"
+ printf "last 10 days, and keep at least 15 generations no matter how old.\n"
+ printf "\nProfiles available are:\tuser, home-manager, channels, system (root)\n"
+ printf "\n-h or --help prints this help text."
+}
+
+if [ $# -eq 1 ]; then # if help requested
+ if [ $1 = "-h" ]; then
+ usage
+ exit 1;
+ fi
+ if [ $1 = "--help" ]; then
+ usage
+ exit 2;
+ fi
+ printf "Dont recognise your option exiting..\n\n"
+ usage
+ exit 3;
+
+ elif [ $# -eq 0 ]; then # print the defaults
+ printf "The current defaults are:\n Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef \n\n"
+ read -p "Keep these defaults? (y/n):" answer
+
+ case "$answer" in
+ [yY1] )
+ printf "Using defaults..\n"
+ ;;
+ [nN0] ) printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * ) printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+fi
+
+## Handle parameters (and change if root)
+if [[ $EUID -ne 0 ]]; then # if not root
+ profile=$(readlink /home/$USER/.nix-profile)
+else
+ if [ -d /nix/var/nix/profiles/system ]; then # maybe this or the other
+ profile="/nix/var/nix/profiles/system"
+ elif [ -d /nix/var/nix/profiles/default ]; then
+ profile="/nix/var/nix/profiles/default"
+ else
+ echo "Cant find profile for root. Exiting"
+ exit 8
+ fi
+fi
+if (( $# < 1 )); then
+ printf "Keeping default: $keepGensDef generations OR $keepDaysDef days, whichever is more\n"
+elif [[ $# -le 2 ]]; then
+ printf "\nError: Not enough arguments.\n\n" >&2
+ usage
+ exit 1
+elif (( $# > 4)); then
+ printf "\nError: Too many arguments.\n\n" >&2
+ usage
+ exit 2
+else
+ if [ $1 -lt 1 ]; then
+ printf "using Gen numbers less than 1 not recommended. Setting to min=1\n"
+ read -p "is that ok? (y/n): " asnwer
+ #printf "$asnwer"
+ case "$asnwer" in
+ [yY1] )
+ printf "ok, continuing..\n"
+ ;;
+ [nN0] )
+ printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * )
+ printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+ fi
+ if [ $2 -lt 0 ]; then
+ printf "using negative days number not recommended. Setting to min=0\n"
+ read -p "is that ok? (y/n): " asnwer
+
+ case "$asnwer" in
+ [yY1] )
+ printf "ok, continuing..\n"
+ ;;
+ [nN0] )
+ printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * )
+ printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+ fi
+ keepGens=$1; keepDays=$2;
+ (( keepGens < 1 )) && keepGens=1
+ (( keepDays < 0 )) && keepDays=0
+ if [[ $EUID -ne 0 ]]; then
+ if [[ $3 == "user" ]] || [[ $3 == "default" ]]; then
+ profile=$(readlink /home/$USER/.nix-profile)
+ elif [[ $3 == "home-manager" ]]; then
+ # home-manager defaults to $XDG_STATE_HOME; otherwise, use
+ # `home-manager generations` and `nix-store --query --roots
+ # /nix/store/...` to figure out what reference is keeping the old
+ # generations alive.
+ profile="${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles/home-manager"
+ elif [[ $3 == "channels" ]]; then
+ profile="/nix/var/nix/profiles/per-user/$USER/channels"
+ else
+ printf "\nError: Do not understand your third argument. Should be one of: (user / home-manager/ channels)\n\n"
+ usage
+ exit 3
+ fi
+ else
+ if [[ $3 == "system" ]]; then
+ profile="/nix/var/nix/profiles/system"
+ elif [[ $3 == "user" ]] || [[ $3 == "default" ]]; then
+ profile="/nix/var/nix/profiles/default"
+ else
+ printf "\nError: Do not understand your third argument. Should be one of: (user / system)\n\n"
+ usage
+ exit 3
+ fi
+ fi
+ printf "OK! \t Keep Gens = $keepGens \t Keep Days = $keepDays\n\n"
+fi
+
+printf "Operating on profile: \t $profile\n\n"
+
+## Runs at the end, to decide whether to delete profiles that match chosen parameters.
+choose () {
+ local default="$1"
+ local prompt="$2"
+ local answer
+
+ read -p "$prompt" answer
+ [ -z "$answer" ] && answer="$default"
+
+ case "$answer" in
+ [yY1] ) #printf "answered yes!\n"
+ nix-env --delete-generations -p $profile ${!gens[@]}
+ exit 0
+ ;;
+ [nN0] ) printf "Ok doing nothing exiting..\n"
+ exit 6;
+ ;;
+ * ) printf "%b" "Unexpected answer '$answer'!" >&2
+ exit 7;
+ ;;
+ esac
+} # end of function choose
+
+# printf "profile = $profile\n\n"
+## Query nix-env for generations list
+IFS=$'\n' nixGens=( $(nix-env --list-generations -p $profile | sed 's:^\s*::; s:\s*$::' | tr '\t' ' ' | tr -s ' ') )
+timeNow=$(date +%s)
+
+## Get info on oldest generation
+IFS=' ' read -r -a oldestGenArr <<< "${nixGens[0]}"
+oldestGen=${oldestGenArr[0]}
+oldestDate=${oldestGenArr[1]}
+printf "%-30s %s\n" "oldest generation:" $oldestGen
+#oldestDate=${nixGens[0]:3:19}
+printf "%-30s %s\n" "oldest generation created:" $oldestDate
+oldestTime=$(date -d "$oldestDate" +%s)
+oldestElapsedSecs=$((timeNow-oldestTime))
+oldestElapsedMins=$((oldestElapsedSecs/60))
+oldestElapsedHours=$((oldestElapsedMins/60))
+oldestElapsedDays=$((oldestElapsedHours/24))
+printf "%-30s %s\n" "minutes before now:" $oldestElapsedMins
+printf "%-30s %s\n" "hours before now:" $oldestElapsedHours
+printf "%-30s %s\n\n" "days before now:" $oldestElapsedDays
+
+## Get info on current generation
+for i in "${nixGens[@]}"; do
+ IFS=' ' read -r -a iGenArr <<< "$i"
+ genNumber=${iGenArr[0]}
+ genDate=${iGenArr[1]}
+ if [[ "$i" =~ current ]]; then
+ currentGen=$genNumber
+ printf "%-30s %s\n" "current generation:" $currentGen
+ currentDate=$genDate
+ printf "%-30s %s\n" "current generation created:" $currentDate
+ currentTime=$(date -d "$currentDate" +%s)
+ currentElapsedSecs=$((timeNow-currentTime))
+ currentElapsedMins=$((currentElapsedSecs/60))
+ currentElapsedHours=$((currentElapsedMins/60))
+ currentElapsedDays=$((currentElapsedHours/24))
+ printf "%-30s %s\n" "minutes before now:" $currentElapsedMins
+ printf "%-30s %s\n" "hours before now:" $currentElapsedHours
+ printf "%-30s %s\n\n" "days before now:" $currentElapsedDays
+ fi
+done
+
+## Compare oldest and current generations
+timeBetweenOldestAndCurrent=$((currentTime-oldestTime))
+elapsedDays=$((timeBetweenOldestAndCurrent/60/60/24))
+generationsDiff=$((currentGen-oldestGen))
+
+## Figure out what we should do, based on generations and options
+if [[ elapsedDays -le keepDays ]]; then
+ printf "All generations are no more than $keepDays days older than current generation. \nOldest gen days difference from current gen: $elapsedDays \n\n\tNothing to do!\n"
+ exit 4;
+elif [[ generationsDiff -lt keepGens ]]; then
+ printf "Oldest generation ($oldestGen) is only $generationsDiff generations behind current ($currentGen). \n\n\t Nothing to do!\n"
+ exit 5;
+else
+ printf "\tSomething to do...\n"
+ declare -a gens
+ for i in "${nixGens[@]}"; do
+ IFS=' ' read -r -a iGenArr <<< "$i"
+ genNumber=${iGenArr[0]}
+ genDiff=$((currentGen-genNumber))
+ genDate=${iGenArr[1]}
+ genTime=$(date -d "$genDate" +%s)
+ elapsedSecs=$((timeNow-genTime))
+ genDaysOld=$((elapsedSecs/60/60/24))
+ if [[ genDaysOld -gt keepDays ]] && [[ genDiff -ge keepGens ]]; then
+ gens["$genNumber"]="$genDate, $genDaysOld day(s) old"
+ fi
+ done
+ printf "\nFound the following generation(s) to delete:\n"
+ for K in "${!gens[@]}"; do
+ printf "generation $K \t ${gens[$K]}\n"
+ done
+ printf "\n"
+ choose "y" "Do you want to delete these? [Y/n]: "
+fi
diff --git a/ags/scripts/record-script.sh b/ags/scripts/record-script.sh
new file mode 100755
index 00000000..f9dd16dc
--- /dev/null
+++ b/ags/scripts/record-script.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+getdate() {
+ date '+%Y-%m-%d_%H.%M.%S'
+}
+getaudiooutput() {
+ pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
+}
+getactivemonitor() {
+ hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
+}
+
+mkdir -p "$(xdg-user-dir VIDEOS)"
+cd "$(xdg-user-dir VIDEOS)" || exit
+if pgrep wf-recorder > /dev/null; then
+ notify-send "Recording Stopped" "Stopped" -a 'record-script.sh' &
+ pkill wf-recorder &
+else
+ notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'record-script.sh'
+ if [[ "$1" == "--sound" ]]; then
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio="$(getaudiooutput)" & disown
+ elif [[ "$1" == "--fullscreen-sound" ]]; then
+ wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown
+ elif [[ "$1" == "--fullscreen" ]]; then
+ wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown
+ else
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & disown
+ fi
+fi
diff --git a/ags/scripts/sway/swayToRelativeWs.sh b/ags/scripts/sway/swayToRelativeWs.sh
new file mode 100755
index 00000000..81e887b7
--- /dev/null
+++ b/ags/scripts/sway/swayToRelativeWs.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# Check if sway is running
+if ! pgrep -x sway > /dev/null; then
+ echo "Sway is not running"
+ exit 1
+fi
+
+
+# Get the current workspace number
+current=$(swaymsg -t get_workspaces | jq '.[] | select(.focused==true) | .num')
+
+# Check if a number was passed as an argument
+if [[ "$1" =~ ^[+-]?[0-9]+$ ]]; then
+ new_workspace=$((current + $1))
+else
+ new_workspace=$((current + 1))
+fi
+
+# Check if the new workspace number is out of bounds
+if [[ $new_workspace -lt 1 ]]; then
+ exit 0
+fi
+
+# Switch to the new workspace
+if [[ $2 == 'move' ]]; then
+ swaymsg move container to workspace $new_workspace
+else
+ swaymsg workspace $new_workspace
+fi
diff --git a/ags/scripts/templates/fuzzel/fuzzel.ini b/ags/scripts/templates/fuzzel/fuzzel.ini
new file mode 100644
index 00000000..47f6abc2
--- /dev/null
+++ b/ags/scripts/templates/fuzzel/fuzzel.ini
@@ -0,0 +1,21 @@
+font=Gabarito
+terminal=foot -e
+prompt=">> "
+layer=overlay
+
+[colors]
+background={{ $background }}ff
+text={{ $onBackground }}ff
+selection={{ $surfaceVariant }}ff
+selection-text={{ $onSurfaceVariant }}ff
+border={{ $surfaceVariant }}dd
+match={{ $primary }}ff
+selection-match={{ $primary }}ff
+
+
+[border]
+radius=17
+width=1
+
+[dmenu]
+exit-immediately-if-empty=yes
diff --git a/ags/scripts/templates/gradience/preset.json b/ags/scripts/templates/gradience/preset.json
new file mode 100644
index 00000000..aeb57489
--- /dev/null
+++ b/ags/scripts/templates/gradience/preset.json
@@ -0,0 +1,144 @@
+{
+ "name": "Material3_Generated",
+ "variables": {
+ "theme_fg_color": "#AEE5FA",
+ "theme_text_color": "#AEE5FA",
+ "theme_bg_color": "#1a1b26",
+ "theme_base_color": "#1a1b26",
+ "theme_selected_bg_color": "#AEE5FA",
+ "theme_selected_fg_color": "rgba(0, 0, 0, 0.87)",
+ "insensitive_bg_color": "#1a1b26",
+ "insensitive_fg_color": "rgba(192, 202, 245, 0.5)",
+ "insensitive_base_color": "#24283b",
+ "theme_unfocused_fg_color": "#AEE5FA",
+ "theme_unfocused_text_color": "#c0caf5",
+ "theme_unfocused_bg_color": "#1a1b26",
+ "theme_unfocused_base_color": "#1a1b26",
+ "theme_unfocused_selected_bg_color": "#a9b1d6",
+ "theme_unfocused_selected_fg_color": "rgba(0, 0, 0, 0.87)",
+ "unfocused_insensitive_color": "rgba(192, 202, 245, 0.5)",
+ "borders": "rgba(192, 202, 245, 0.12)",
+ "unfocused_borders": "rgba(192, 202, 245, 0.12)",
+ "warning_color": "#FDD633",
+ "error_color": "#BA1B1B",
+ "success_color": "#81C995",
+ "wm_title": "#AEE5FA",
+ "wm_unfocused_title": "rgba(192, 202, 245, 0.7)",
+ "wm_highlight": "rgba(192, 202, 245, 0.1)",
+ "wm_bg": "#1a1b26",
+ "wm_unfocused_bg": "#1a1b26",
+ "wm_button_close_icon": "#1a1b26",
+ "wm_button_close_hover_bg": "#a9b1d6",
+ "wm_button_close_active_bg": "#c7c7c7",
+ "content_view_bg": "#1a1b26",
+ "placeholder_text_color": "silver",
+ "text_view_bg": "#1d1d1d",
+ "budgie_tasklist_indicator_color": "#90D1F6",
+ "budgie_tasklist_indicator_color_active": "#90D1F6",
+ "budgie_tasklist_indicator_color_active_window": "#999999",
+ "budgie_tasklist_indicator_color_attention": "#FDD633",
+ "STRAWBERRY_100": "#FF9262",
+ "STRAWBERRY_300": "#FF793E",
+ "STRAWBERRY_500": "#F15D22",
+ "STRAWBERRY_700": "#CF3B00",
+ "STRAWBERRY_900": "#AC1800",
+ "ORANGE_100": "#FFDB91",
+ "ORANGE_300": "#FFCA40",
+ "ORANGE_500": "#FAA41A",
+ "ORANGE_700": "#DE8800",
+ "ORANGE_900": "#C26C00",
+ "BANANA_100": "#FFFFA8",
+ "BANANA_300": "#FFFA7D",
+ "BANANA_500": "#FFCE51",
+ "BANANA_700": "#D1A023",
+ "BANANA_900": "#A27100",
+ "LIME_100": "#A2F3BE",
+ "LIME_300": "#8ADBA6",
+ "LIME_500": "#73C48F",
+ "LIME_700": "#479863",
+ "LIME_900": "#1C6D38",
+ "BLUEBERRY_100": "#94A6FF",
+ "BLUEBERRY_300": "#6A7CE0",
+ "BLUEBERRY_500": "#3F51B5",
+ "BLUEBERRY_700": "#213397",
+ "BLUEBERRY_900": "#031579",
+ "GRAPE_100": "#D25DE6",
+ "GRAPE_300": "#B84ACB",
+ "GRAPE_500": "#9C27B0",
+ "GRAPE_700": "#830E97",
+ "GRAPE_900": "#6A007E",
+ "COCOA_100": "#9F9792",
+ "COCOA_300": "#7B736E",
+ "COCOA_500": "#574F4A",
+ "COCOA_700": "#463E39",
+ "COCOA_900": "#342C27",
+ "SILVER_100": "#EEE",
+ "SILVER_300": "#CCC",
+ "SILVER_500": "#AAA",
+ "SILVER_700": "#888",
+ "SILVER_900": "#666",
+ "SLATE_100": "#888",
+ "SLATE_300": "#666",
+ "SLATE_500": "#444",
+ "SLATE_700": "#222",
+ "SLATE_900": "#111",
+ "BLACK_100": "#474341",
+ "BLACK_300": "#403C3A",
+ "BLACK_500": "#393634",
+ "BLACK_700": "#33302F",
+ "BLACK_900": "#2B2928",
+ "accent_bg_color": "{{ $primary }}",
+ "accent_fg_color": "{{ $onPrimary }}",
+ "accent_color": "{{ $primary }}",
+ "destructive_bg_color": "{{ $error }}",
+ "destructive_fg_color": "{{ $onError }}",
+ "destructive_color": "{{ $error }}",
+ "success_bg_color": "#81C995",
+ "success_fg_color": "rgba(0, 0, 0, 0.87)",
+ "warning_bg_color": "#FDD633",
+ "warning_fg_color": "rgba(0, 0, 0, 0.87)",
+ "error_bg_color": "{{ $error }}",
+ "error_fg_color": "{{ $onError }}",
+ "window_bg_color": "{{ $background }}",
+ "window_fg_color": "{{ $onBackground }}",
+ "view_bg_color": "{{ $surface }}",
+ "view_fg_color": "{{ $onSurface }}",
+ "headerbar_bg_color": "mix(@dialog_bg_color, @window_bg_color, 0.5)",
+ "headerbar_fg_color": "{{ $onSecondaryContainer }}",
+ "headerbar_border_color": "{{ $secondaryContainer }}",
+ "headerbar_backdrop_color": "@headerbar_bg_color",
+ "headerbar_shade_color": "rgba(0, 0, 0, 0.09)",
+ "card_bg_color": "{{ $background }}",
+ "card_fg_color": "{{ $onSecondaryContainer }}",
+ "card_shade_color": "rgba(0, 0, 0, 0.09)",
+ "dialog_bg_color": "{{ $secondaryContainer }}",
+ "dialog_fg_color": "{{ $onSecondaryContainer }}",
+ "popover_bg_color": "{{ $secondaryContainer }}",
+ "popover_fg_color": "{{ $onSecondaryContainer }}",
+ "thumbnail_bg_color": "#1a1b26",
+ "thumbnail_fg_color": "#AEE5FA",
+ "shade_color": "rgba(0, 0, 0, 0.36)",
+ "scrollbar_outline_color": "rgba(0, 0, 0, 0.5)",
+
+ "sidebar_bg_color": "@window_bg_color",
+ "sidebar_fg_color":"@window_fg_color",
+ "sidebar_border_color": "@sidebar_bg_color",
+ "sidebar_backdrop_color": "@sidebar_bg_color"
+ },
+ "palette": {
+ "blue_": {},
+ "green_": {},
+ "yellow_": {},
+ "orange_": {},
+ "red_": {},
+ "purple_": {},
+ "brown_": {},
+ "light_": {},
+ "dark_": {}
+ },
+ "custom_css": {
+ "gtk4": "",
+ "gtk3": ""
+ },
+ "plugins": {}
+}
\ No newline at end of file
diff --git a/ags/scripts/templates/hypr/hyprland/colors.conf b/ags/scripts/templates/hypr/hyprland/colors.conf
new file mode 100644
index 00000000..8c0689f3
--- /dev/null
+++ b/ags/scripts/templates/hypr/hyprland/colors.conf
@@ -0,0 +1,34 @@
+# exec = export SLURP_ARGS='-d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000'
+
+general {
+ col.active_border = rgba({{ $onSurface }}39)
+ col.inactive_border = rgba({{ $outline }}30)
+}
+
+misc {
+ background_color = rgba({{ $surface }}FF)
+}
+
+plugin {
+ hyprbars {
+ # Honestly idk if it works like css, but well, why not
+ bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif
+ bar_height = 30
+ bar_padding = 10
+ bar_button_padding = 5
+ bar_precedence_over_border = true
+ bar_part_of_window = true
+
+ bar_color = rgba({{ $background }}FF)
+ col.text = rgba({{ $onBackground }}FF)
+
+
+ # example buttons (R -> L)
+ # hyprbars-button = color, size, on-click
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch killactive
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch fullscreen 1
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch movetoworkspacesilent special
+ }
+}
+
+windowrulev2 = bordercolor rgba({{ $primary }}AA) rgba({{ $primary }}77),pinned:1
\ No newline at end of file
diff --git a/ags/scripts/templates/hypr/hyprlock.conf b/ags/scripts/templates/hypr/hyprlock.conf
new file mode 100644
index 00000000..d3de5d80
--- /dev/null
+++ b/ags/scripts/templates/hypr/hyprlock.conf
@@ -0,0 +1,102 @@
+# $text_color = rgba({{ $onBackground }}FF)
+# $entry_background_color = rgba({{ $background }}11)
+# $entry_border_color = rgba({{ $outline }}55)
+# $entry_color = rgba({{ $onSurfaceVariant }}FF)
+$text_color = rgba(FFFFFFFF)
+$entry_background_color = rgba(33333311)
+$entry_border_color = rgba(3B3B3B55)
+$entry_color = rgba(FFFFFFFF)
+$font_family = Rubik Light
+$font_family_clock = Rubik Light
+$font_material_symbols = Material Symbols Rounded
+
+background {
+ # color = rgba({{ $surfaceContainerLowest }}FF)
+ color = rgba(000000FF)
+ # path = {{ SWWW_WALL }}
+ # path = screenshot
+ # blur_size = 5
+ # blur_passes = 4
+}
+input-field {
+ monitor =
+ size = 250, 50
+ outline_thickness = 2
+ dots_size = 0.1
+ dots_spacing = 0.3
+ outer_color = $entry_border_color
+ inner_color = $entry_background_color
+ font_color = $entry_color
+ # fade_on_empty = true
+
+ position = 0, 20
+ halign = center
+ valign = center
+}
+
+label { # Clock
+ monitor =
+ text = $TIME
+ shadow_passes = 1
+ shadow_boost = 0.5
+ color = $text_color
+ font_size = 65
+ font_family = $font_family_clock
+
+ position = 0, 300
+ halign = center
+ valign = center
+}
+label { # Greeting
+ monitor =
+ text = hi $USER !!!
+ shadow_passes = 1
+ shadow_boost = 0.5
+ color = $text_color
+ font_size = 20
+ font_family = $font_family
+
+ position = 0, 240
+ halign = center
+ valign = center
+}
+label { # lock icon
+ monitor =
+ text = lock
+ shadow_passes = 1
+ shadow_boost = 0.5
+ color = $text_color
+ font_size = 21
+ font_family = $font_material_symbols
+
+ position = 0, 65
+ halign = center
+ valign = bottom
+}
+label { # "locked" text
+ monitor =
+ text = locked
+ shadow_passes = 1
+ shadow_boost = 0.5
+ color = $text_color
+ font_size = 14
+ font_family = $font_family
+
+ position = 0, 45
+ halign = center
+ valign = bottom
+}
+
+label { # Status
+ monitor =
+ text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh
+ shadow_passes = 1
+ shadow_boost = 0.5
+ color = $text_color
+ font_size = 14
+ font_family = $font_family
+
+ position = 30, -30
+ halign = left
+ valign = top
+}
\ No newline at end of file
diff --git a/ags/scripts/templates/terminal/scheme-base.json b/ags/scripts/templates/terminal/scheme-base.json
new file mode 100644
index 00000000..e4b78e7e
--- /dev/null
+++ b/ags/scripts/templates/terminal/scheme-base.json
@@ -0,0 +1,38 @@
+{
+ "dark": {
+ "term0" : "#282828",
+ "term1" : "#CC241D",
+ "term2" : "#98971A",
+ "term3" : "#D79921",
+ "term4" : "#458588",
+ "term5" : "#B16286",
+ "term6" : "#689D6A",
+ "term7" : "#A89984",
+ "term8" : "#928374",
+ "term9" : "#FB4934",
+ "term10" : "#B8BB26",
+ "term11" : "#FABD2F",
+ "term12" : "#83A598",
+ "term13" : "#D3869B",
+ "term14" : "#8EC07C",
+ "term15" : "#EBDBB2"
+ },
+ "light": {
+ "term0" : "#FDF9F3",
+ "term1" : "#FF6188",
+ "term2" : "#A9DC76",
+ "term3" : "#FC9867",
+ "term4" : "#FFD866",
+ "term5" : "#F47FD4",
+ "term6" : "#78DCE8",
+ "term7" : "#333034",
+ "term8" : "#121212",
+ "term9" : "#FF6188",
+ "term10" : "#A9DC76",
+ "term11" : "#FC9867",
+ "term12" : "#FFD866",
+ "term13" : "#F47FD4",
+ "term14" : "#78DCE8",
+ "term15" : "#333034"
+ }
+}
diff --git a/ags/scripts/templates/terminal/scheme-monochrome.json b/ags/scripts/templates/terminal/scheme-monochrome.json
new file mode 100644
index 00000000..5807a26e
--- /dev/null
+++ b/ags/scripts/templates/terminal/scheme-monochrome.json
@@ -0,0 +1,36 @@
+{
+ "dark": {
+ "term0": "#000000",
+ "term1": "#FFFFFF",
+ "term2": "#CCCCCC",
+ "term3": "#8f8f8f",
+ "term4": "#FFFFFF",
+ "term5": "#111111",
+ "term6": "#CCCCCC",
+ "term7": "#FFFFFF",
+ "term8": "#404040",
+ "term9": "#CCCCCC",
+ "term10": "#FFFFFF",
+ "term11": "#909090",
+ "term12": "#CCCCCC",
+ "term13": "#808080",
+ "term14": "#CCCCCC",
+ "term15": "#FFFFFF"
+ },
+ "light": {
+ "term0": "#EAE9EA",
+ "term1": "#777777",
+ "term2": "#000000",
+ "term3": "#000000",
+ "term4": "#000000",
+ "term5": "#000000",
+ "term6": "#000000",
+ "term7": "#202020",
+ "term8": "#000000",
+ "term9": "#000000",
+ "term10": "#CCCCCC",
+ "term11": "#808080",
+ "term12": "#CCCCCC",
+ "term13": "#FFFFFF"
+ }
+}
diff --git a/ags/scripts/templates/terminal/sequences.txt b/ags/scripts/templates/terminal/sequences.txt
new file mode 100644
index 00000000..27fa9979
--- /dev/null
+++ b/ags/scripts/templates/terminal/sequences.txt
@@ -0,0 +1 @@
+]4;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]10;#$term7 #\]11;[$alpha]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]4;232;#$term7 #\]4;256;#$term7 #\]708;[$alpha]#$term0 #\
diff --git a/ags/scripts/wayland-idle-inhibitor.py b/ags/scripts/wayland-idle-inhibitor.py
new file mode 100755
index 00000000..4a4f8f8c
--- /dev/null
+++ b/ags/scripts/wayland-idle-inhibitor.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+import sys
+from dataclasses import dataclass
+from signal import SIGINT, SIGTERM, signal
+from threading import Event
+import setproctitle
+
+from pywayland.client.display import Display
+from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import (
+ ZwpIdleInhibitManagerV1,
+)
+from pywayland.protocol.wayland.wl_compositor import WlCompositor
+from pywayland.protocol.wayland.wl_registry import WlRegistryProxy
+from pywayland.protocol.wayland.wl_surface import WlSurface
+
+
+@dataclass
+class GlobalRegistry:
+ surface: WlSurface | None = None
+ inhibit_manager: ZwpIdleInhibitManagerV1 | None = None
+
+
+def handle_registry_global(
+ wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int
+) -> None:
+ global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry()
+
+ if iface_name == "wl_compositor":
+ compositor = wl_registry.bind(id_num, WlCompositor, version)
+ global_registry.surface = compositor.create_surface() # type: ignore
+ elif iface_name == "zwp_idle_inhibit_manager_v1":
+ global_registry.inhibit_manager = wl_registry.bind(
+ id_num, ZwpIdleInhibitManagerV1, version
+ )
+
+
+def main() -> None:
+ done = Event()
+ signal(SIGINT, lambda _, __: done.set())
+ signal(SIGTERM, lambda _, __: done.set())
+
+ global_registry = GlobalRegistry()
+
+ display = Display()
+ display.connect()
+
+ registry = display.get_registry() # type: ignore
+ registry.user_data = global_registry
+ registry.dispatcher["global"] = handle_registry_global
+
+ def shutdown() -> None:
+ display.dispatch()
+ display.roundtrip()
+ display.disconnect()
+
+ display.dispatch()
+ display.roundtrip()
+
+ if global_registry.surface is None or global_registry.inhibit_manager is None:
+ print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.")
+ shutdown()
+ sys.exit(1)
+
+ inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore
+ global_registry.surface
+ )
+
+ display.dispatch()
+ display.roundtrip()
+
+ print("Inhibiting idle...")
+ done.wait()
+ print("Shutting down...")
+
+ inhibitor.destroy()
+
+ shutdown()
+
+
+if __name__ == "__main__":
+ setproctitle.setproctitle("wayland-idle-inhibitor.py")
+ main()
diff --git a/ags/scss/_bar.scss b/ags/scss/_bar.scss
new file mode 100644
index 00000000..e614a538
--- /dev/null
+++ b/ags/scss/_bar.scss
@@ -0,0 +1,409 @@
+// Made to be pixel-perfect with 11pt font size
+// 1rem = 11pt = 14.6666666667px
+$bar_ws_width: 1.774rem;
+$bar_ws_width_focus: 0.614rem;
+$bar_ws_width_focus_active: 2.045rem;
+
+@mixin bar-group-rounding {
+ @include small-rounding;
+}
+
+.bar-height {
+ min-height: 2.727rem;
+}
+
+.bar-bg {
+ background-color: $layer0;
+ min-height: 2.727rem;
+}
+
+.bar-bg-focus {
+ background-color: $layer0;
+ min-height: 1.364rem;
+}
+
+.bar-bg-nothing {
+ background-color: $layer0;
+ min-height: 2px;
+}
+
+.bar-bg-focus-batterylow {
+ background-color: mix($layer0, $errorContainer, 80%);
+}
+
+.bar-sidespace {
+ min-width: 1.5rem;
+}
+
+.bar-group-margin {
+ padding: 0.273rem 0rem;
+}
+
+.bar-group {
+ background-color: $layer1;
+}
+
+.bar-group-pad {
+ padding: 0.205rem;
+}
+
+.bar-group-pad-less {
+ padding: 0rem 0.681rem;
+}
+
+.bar-group-pad-system {
+ padding: 0rem 0.341rem;
+}
+
+.bar-group-pad-music {
+ padding-right: 1.023rem;
+ padding-left: 0.341rem;
+}
+
+.bar-group-standalone {
+ @include bar-group-rounding;
+ -gtk-outline-radius: 1.364rem;
+}
+
+.bar-group-round {
+ border-radius: 10rem;
+ -gtk-outline-radius: 10rem;
+}
+
+.bar-group-middle {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+}
+
+.bar-group-left {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-left-radius: 1.364rem;
+ border-bottom-left-radius: 1.364rem;
+}
+
+.bar-group-right {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-right-radius: 1.364rem;
+ border-bottom-right-radius: 1.364rem;
+}
+
+.bar-sidemodule {
+ min-width: 26rem;
+}
+
+.bar-ws-width {
+ min-width: 18.341rem;
+}
+
+
+.bar-ws-container {
+ transition: 700ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+.bar-ws {
+ font-size: 1.02rem;
+ font-weight: 600;
+ min-width: $bar_ws_width;
+ color: $workspaceOnLayer1Inactive;
+}
+
+.bar-ws-active {
+ background-color: $workspaceLayer3;
+ color: $workspaceOnLayer3;
+}
+
+.bar-ws-occupied {
+ background-color: $layer2;
+ color: $workspaceOnLayer2;
+}
+
+// Focus is the bar mode name, not the workspace state!
+
+.bar-ws-focus {
+ background-color: $surfaceVariant;
+ min-width: $bar_ws_width_focus;
+}
+
+.bar-ws-focus-active {
+ min-width: $bar_ws_width_focus_active;
+ background-color: $onLayer0;
+}
+
+.bar-ws-focus-occupied {
+ background-color: $secondaryContainer;
+}
+
+.bar-clock-box {
+ margin: 0rem 0.682rem;
+}
+
+.bar-time {
+ @include titlefont;
+ font-size: 1.2727rem;
+ color: $timeOnLayer1;
+}
+
+.bar-date {
+ color: $dateOnLayer1;
+}
+
+.bar-batt {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $battOnLayer2;
+}
+
+.bar-batt-txt {
+ color: $battOnLayer1;
+}
+
+.bar-batt-circprog {
+ @include fluent_decel_long;
+ min-width: 0.136rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $battLayer2;
+ color: $battOnLayer2;
+}
+
+.bar-batt-circprog-low {
+ background-color: $error;
+ color: $errorContainer;
+}
+
+
+.bar-batt-low {
+ background-color: $error;
+ color: $errorContainer;
+}
+
+.bar-ram-icon {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $ramOnLayer2;
+}
+
+.bar-ram-circprog {
+ @include fluent_decel_long;
+ min-width: 0.136rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $ramLayer2;
+ color: $ramOnLayer2;
+}
+
+.bar-ram-txt {
+ color: $ramOnLayer1;
+}
+
+.bar-swap-icon {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $swapOnLayer2;
+}
+
+.bar-swap-circprog {
+ @include fluent_decel_long;
+ min-width: 0.136rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $swapLayer2;
+ color: $swapOnLayer2;
+}
+
+.bar-swap-txt {
+ color: $swapOnLayer1;
+}
+
+.bar-cpu-icon {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $cpuOnLayer2;
+}
+
+.bar-cpu-circprog {
+ @include fluent_decel_long;
+ min-width: 0.136rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $cpuLayer2;
+ color: $cpuOnLayer2;
+}
+
+.bar-cpu-txt {
+ color: $cpuOnLayer1;
+}
+
+.bar-music-playstate {
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $musicOnLayer2;
+}
+
+.bar-music-circprog {
+ @include fluent_decel_long;
+ min-width: 0.136rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $musicLayer2;
+ color: $musicOnLayer2;
+}
+
+.bar-music-playstate-playing {
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ color: $musicOnLayer2;
+}
+
+.bar-music-playstate-txt {
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+}
+
+.bar-music-txt {
+ color: $musicOnLayer1;
+}
+
+.bar-music-cover {
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 100% auto;
+ min-width: 11.932rem;
+}
+
+.bar-music-extended-bg {
+ border-radius: 1.364rem;
+ min-width: 34.091rem;
+}
+
+.bar-music-hide-false {
+ @include menu_decel;
+ transition-duration: 100ms;
+ opacity: 1;
+}
+
+.bar-music-hide-true {
+ @include menu_accel;
+ transition-duration: 100ms;
+ opacity: 0;
+}
+
+.bar-corner-spacing {
+ min-width: $rounding_large;
+ min-height: $rounding_large;
+}
+
+.corner {
+ background-color: $layer0;
+ @include large-rounding;
+}
+
+.corner-black {
+ background-color: $black; // Hard code: fake screen corner
+ @include large-rounding;
+}
+
+.bar-wintitle-topdesc {
+ margin-top: -0.136rem;
+ margin-bottom: -0.341rem;
+ color: $windowtitleOnLayer0Inactive;
+}
+
+.bar-wintitle-txt {
+ color: $windowtitleOnLayer0;
+}
+
+.bar-space-button {
+ padding: 0.341rem;
+}
+
+.bar-space-button>box:first-child {
+ @include full-rounding;
+ padding: 0rem 0.682rem;
+}
+
+.bar-space-button-leftmost {
+ box {
+ margin: 0rem 0.682rem;
+ }
+}
+
+.bar-space-area-rightmost>box {
+ padding-right: 2.386rem;
+}
+
+.bar-systray {
+ @include full-rounding;
+ margin: 0.137rem 0rem;
+ padding: 0rem 0.682rem;
+}
+
+.bar-systray-item {
+ @include full-rounding;
+ @include element_decel;
+ @include symbolic-icon;
+ min-height: 1.032rem;
+ min-width: 1.032rem;
+ font-size: 1.032rem;
+ color: $trayOnLayer0;
+}
+
+.bar-statusicons {
+ @include full-rounding;
+ @include element_decel;
+ margin: 0.273rem;
+ padding: 0rem 0.614rem;
+}
+
+.bar-statusicons-active {
+ background-color: $layer0Active;
+ color: $onLayer0Active;
+}
+
+.bar-util-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ background-color: $utilsLayer2;
+ color: $utilsOnLayer2;
+}
+
+.bar-util-btn:hover,
+.bar-util-btn:focus {
+ background-color: $layer2Hover;
+}
+
+.bar-util-btn:active {
+ background-color: $layer2Active;
+}
+
+.bar-spaceright {
+ color: $barspacerightOnLayer0;
+}
+
+.bar-bluetooth-device {
+ @include full-rounding;
+ @include symbolic-icon;
+ min-height: 1.032rem;
+ min-width: 1.032rem;
+ font-size: 1.032rem;
+ padding: 0.205rem 0.341rem;
+}
diff --git a/ags/scss/_cheatsheet.scss b/ags/scss/_cheatsheet.scss
new file mode 100644
index 00000000..19c0c976
--- /dev/null
+++ b/ags/scss/_cheatsheet.scss
@@ -0,0 +1,168 @@
+.cheatsheet-bg {
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ margin-bottom: 0.682rem;
+ background-color: $layer0;
+ padding: 1.364rem;
+}
+
+.cheatsheet-title {
+ color: $cheatsheetTitle;
+}
+
+.cheatsheet-bind-lineheight {
+ min-height: 2.045rem;
+}
+
+.cheatsheet-key {
+ @include techfont;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ margin: 0.17rem;
+ padding: 0.136rem 0.205rem;
+ -gtk-outline-radius: 0.409rem;
+ color: $cheatsheetTitle;
+ border-radius: 0.409rem;
+ border: 0.068rem solid $cheatsheetTitle;
+ box-shadow: 0rem 0.136rem 0rem $cheatsheetTitle;
+}
+
+.cheatsheet-key-notkey {
+ min-height: 1.364rem;
+ padding: 0.136rem 0.205rem;
+ margin: 0.17rem;
+ color: $onLayer0;
+}
+
+@for $i from 1 through 8 {
+ .cheatsheet-color-#{$i} {
+ color: nth($cheatsheetColors, $i);
+ border-color: nth($cheatsheetColors, $i);
+ box-shadow: 0rem 0.136rem 0rem nth($cheatsheetColors, $i);
+ }
+}
+
+// .cheatsheet-action {}
+
+.cheatsheet-closebtn {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+}
+
+.cheatsheet-closebtn:hover,
+.cheatsheet-closebtn:focus {
+ background-color: $layer0Hover;
+}
+
+.cheatsheet-closebtn:active {
+ background-color: $layer0Active;
+}
+
+.cheatsheet-category-title {
+ @include titlefont;
+ font-size: 1.705rem;
+}
+
+@mixin cheatsheet-periodictable-element {
+ min-width: 5.455rem;
+ min-height: 5.455rem;
+ @include small-rounding;
+ background-color: $layer1;
+ color: $onLayer1;
+}
+
+.cheatsheet-periodictable-elementsymbol {
+ @include readingfont;
+ font-size: 1.705rem;
+ font-weight: bold;
+}
+
+.cheatsheet-periodictable-elementnum {
+ @include full-rounding;
+ min-width: 1.364rem;
+ min-height: 1.364rem;
+ background-color: $term0;
+ color: $onBackground;
+}
+
+$colormetal: $term2;
+$colornonmetal: $term4;
+$colornoblegas: $term5;
+$colorlanthanum: $term3;
+$coloractinium: $term7;
+
+.cheatsheet-periodictable-empty {
+ @include small-rounding;
+ min-width: 5.455rem;
+ min-height: 5.455rem;
+}
+
+.cheatsheet-periodictable-metal {
+ @include cheatsheet-periodictable-element;
+ background-color: $colormetal;
+ color: $term0;
+}
+
+.cheatsheet-periodictable-nonmetal {
+ @include cheatsheet-periodictable-element;
+ background-color: $colornonmetal;
+ color: $term0;
+}
+
+.cheatsheet-periodictable-noblegas {
+ @include cheatsheet-periodictable-element;
+ background-color: $colornoblegas;
+ color: $term0;
+}
+
+.cheatsheet-periodictable-lanthanum {
+ @include cheatsheet-periodictable-element;
+ background-color: $colorlanthanum;
+ color: $term0;
+}
+
+.cheatsheet-periodictable-actinium {
+ @include cheatsheet-periodictable-element;
+ background-color: $coloractinium;
+ color: $term0;
+}
+
+.cheatsheet-periodictable-legend-color-wrapper {
+ @include full-rounding;
+ padding: 0.273rem;
+ border: 0.136rem solid $onLayer0;
+}
+
+@mixin cheatsheet-periodictable-legend-color {
+ @include full-rounding;
+ min-width: 1.023rem;
+ min-height: 1.023rem;
+}
+
+.cheatsheet-periodictable-legend-color-metal {
+ @include cheatsheet-periodictable-legend-color;
+ background-color: $colormetal;
+}
+
+.cheatsheet-periodictable-legend-color-nonmetal {
+ @include cheatsheet-periodictable-legend-color;
+ background-color: $colornonmetal;
+}
+
+.cheatsheet-periodictable-legend-color-noblegas {
+ @include cheatsheet-periodictable-legend-color;
+ background-color: $colornoblegas;
+}
+
+.cheatsheet-periodictable-legend-color-lanthanum {
+ @include cheatsheet-periodictable-legend-color;
+ background-color: $colorlanthanum;
+}
+
+.cheatsheet-periodictable-legend-color-actinium {
+ @include cheatsheet-periodictable-legend-color;
+ background-color: $coloractinium;
+}
\ No newline at end of file
diff --git a/ags/scss/_colors.scss b/ags/scss/_colors.scss
new file mode 100644
index 00000000..ddcdd02f
--- /dev/null
+++ b/ags/scss/_colors.scss
@@ -0,0 +1,99 @@
+$rootTransparency: 0.22; // Transparency = 1 - opacity
+$transparency: 0.75;
+
+// Transparent versions
+@if $transparent == True {
+ $background: transparentize($background, $rootTransparency);
+ $surface: transparentize($surface, $transparency);
+ $surfaceDim: transparentize($surfaceDim, $transparency);
+ $surfaceBright: transparentize($surfaceBright, $transparency);
+ $surfaceContainerLowest: transparentize($surfaceContainerLowest, $transparency);
+ $surfaceContainerLow: transparentize($surfaceContainerLow, $transparency);
+ $surfaceContainer: transparentize($surfaceContainer, $transparency);
+ $surfaceContainerHigh: transparentize($surfaceContainerHigh, $transparency);
+ $surfaceContainerHighest: transparentize($surfaceContainerHighest, $transparency);
+ $surfaceVariant: transparentize($surfaceVariant, $transparency);
+ $inverseSurface: transparentize($inverseSurface, $transparency);
+ $surfaceTint: transparentize($surfaceTint, $transparency);
+}
+
+// Others
+$subtext: $outline;
+$actiontext: mix($onBackground, $background, 85%);
+$black: black;
+$white: white;
+
+/// Color mappings for more chaotic, dynamic colors like the average rice ///
+// General
+$layer0: $background;
+$onLayer0: $onBackground;
+$layer0Hover: mix($layer0, $onLayer0, 85%);
+$layer0Active: $surfaceContainerHigh;
+$onLayer0Active: $onSurface;
+$onLayer0Inactive: mix($onLayer0, $layer0, 70%);
+$layer1: $surfaceContainerLow;
+$onLayer1: $onSurface;
+$onLayer1Inactive: mix($onLayer1, $layer1, 45%);
+$onLayer1: $onSurfaceVariant;
+$layer2: mix($surfaceContainer, $surfaceContainerHigh, 55%);
+$onLayer2: $onSurface;
+$layer3: $surfaceContainerHigh;
+$onLayer3: $onSurface;
+$layer1Hover: mix($layer1, $onLayer1, 85%);
+$layer1Active: mix($layer1, $onLayer1, 70%);
+$layer2Hover: mix($layer2, $onLayer2, 90%);
+$layer2Active: mix($layer2, $onLayer2, 80%);
+$layer3Hover: mix($layer3, $onLayer3, 90%);
+$layer3Active: mix($layer3, $onLayer3, 80%);
+// Elements
+$windowtitleOnLayer0Inactive: $onLayer0Inactive;
+$windowtitleOnLayer0: $onLayer0;
+$barspacerightOnLayer0: $onLayer0;
+$timeOnLayer1: $onLayer1;
+$dateOnLayer1: $onLayer1;
+$ramOnLayer1: $onLayer1;
+$ramLayer2: $secondaryContainer;
+$ramOnLayer2: $onSecondaryContainer;
+$swapOnLayer1: $onLayer1;
+$swapLayer2: $secondaryContainer;
+$swapOnLayer2: $onSecondaryContainer;
+$cpuOnLayer1: $onLayer1;
+$cpuLayer2: $secondaryContainer;
+$cpuOnLayer2: $onSecondaryContainer;
+$musicOnLayer1: $onLayer1;
+$musicLayer2: $secondaryContainer;
+$musicOnLayer2: $onSecondaryContainer;
+$utilsLayer2: $layer2;
+$utilsOnLayer2: $onLayer2;
+$battOnLayer1: $onLayer1;
+$battLayer2: $secondaryContainer;
+$battOnLayer2: $onSecondaryContainer;
+$workspaceOnLayer1Inactive: $onLayer1Inactive;
+$workspaceLayer3: $primary;
+$workspaceOnLayer3: $onPrimary;
+$workspaceOnLayer2: $onSecondaryContainer;
+$trayOnLayer0: $onLayer0;
+$cheatsheetTitle: $onSecondaryContainer;
+$cheatsheetColors: (
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer,
+ $onSecondaryContainer
+);
+
+$sessionColors: (
+ $onLayer1,
+ $onLayer1,
+ $onLayer1,
+ $onLayer1,
+ $onLayer1,
+ $onLayer1,
+ $onLayer1
+);
+$brightnessOnLayer0: $onLayer0;
+$volumeOnLayer0: $onLayer0;
+
diff --git a/ags/scss/_common.scss b/ags/scss/_common.scss
new file mode 100644
index 00000000..f8d3b62f
--- /dev/null
+++ b/ags/scss/_common.scss
@@ -0,0 +1,319 @@
+* {
+ selection {
+ background-color: $secondary;
+ color: $onSecondary;
+ }
+
+ caret-color: $onLayer2;
+}
+
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+menu {
+ @include small-rounding;
+ border: 1px solid mix($surfaceContainer, $onBackground, 80%); // I hate this
+ padding: 0.681rem;
+ background: opacify($surfaceContainer, 1);
+ color: $onSurface;
+ -gtk-outline-radius: 1.159rem;
+
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+}
+
+menubar>menuitem {
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem;
+ min-width: 13.636rem;
+ min-height: 2.727rem;
+}
+
+menu>menuitem {
+ padding: 0.4em 1.5rem;
+ background: transparent;
+ transition: 0.2s ease background-color;
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem;
+}
+
+menu>menuitem:hover,
+menu>menuitem:focus {
+ background-color: $layer2Hover;
+}
+
+menu>menuitem:active {
+ background-color: $layer2Active;
+}
+
+radio {
+ @include full-rounding;
+ margin: 0.273rem;
+ min-width: 15px;
+ min-height: 15px;
+ border: 0.068rem solid $outline;
+}
+
+// radio:first-child {
+// background-color: red;
+// }
+
+radio:checked {
+ min-width: 8px;
+ min-height: 8px;
+ background-color: $onPrimary;
+ border: 0.477rem solid $primary;
+}
+
+tooltip {
+ animation-name: appear;
+ animation-duration: 100ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+ @include normal-rounding;
+ background-color: opacify($color: $inverseSurface, $amount: 1);
+ color: $inverseOnSurface;
+}
+
+/////////////////////////////////////////
+// Emoji Chooser structure
+// popover
+// ├── box.emoji-searchbar
+// │ ╰── entry.search
+// ╰── box.emoji-toolbar
+// ├── button.image-button.emoji-section
+// ├── ...
+// ╰── button.image-button.emoji-section
+
+popover {
+ @include elevation-border-softer;
+ padding: 0.681rem;
+ background: $surfaceContainerHigh;
+ color: $onSurface;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+}
+
+
+/////////////////////////////////////////
+
+.configtoggle-box {
+ padding: 0.205rem 0.341rem;
+}
+
+.switch-bg {
+ @include element_decel;
+ @include full-rounding;
+ border: 0.136rem solid $onSurface;
+ min-width: 2.864rem;
+ min-height: 1.637rem;
+}
+
+.switch-bg-true {
+ background-color: $primary;
+ border: 0.136rem solid $primary;
+}
+
+.switch-fg {
+ @include full-rounding;
+ @include menu_decel;
+ background-color: $onSurface;
+ color: $layer1;
+ min-width: 0.819rem;
+ min-height: 0.819rem;
+ margin-left: 0.477rem;
+}
+
+.switch-fg-true {
+ background-color: $onPrimary;
+ color: $primary;
+ min-width: 1.431rem;
+ min-height: 1.431rem;
+ margin-left: 1.431rem;
+}
+
+.switch-fg-toggling-false {
+ @include menu_decel;
+ min-width: 1.636rem;
+ min-height: 0.819rem;
+}
+
+.segment-container {
+ @include full-rounding;
+ border: 0.068rem solid $outline;
+}
+
+.segment-container>*:first-child {
+ border-top-left-radius: 9999px;
+ border-bottom-left-radius: 9999px;
+}
+
+.segment-container>* {
+ border-right: 0.068rem solid $outline;
+ padding: 0.341rem 0.682rem;
+}
+
+.segment-container>*:last-child {
+ border-right: 0rem solid transparent;
+ border-top-right-radius: 9999px;
+ border-bottom-right-radius: 9999px;
+}
+
+.segment-btn {
+ color: $onSurface;
+}
+
+.segment-btn:focus,
+.segment-btn:hover {
+ background-color: $layer0Hover;
+}
+
+.segment-btn-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.segment-btn-enabled:hover,
+.segment-btn-enabled:focus {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.multipleselection-container {}
+
+.multipleselection-btn {
+ @include small-rounding;
+ padding: 0rem 0.341rem;
+ border: 0.034rem solid $outline;
+ color: $onSurface;
+}
+
+.multipleselection-btn:focus,
+.multipleselection-btn:hover {
+ background-color: $layer0Hover;
+ color: $onSurface;
+}
+
+.multipleselection-btn-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.multipleselection-btn-enabled:hover,
+.multipleselection-btn-enabled:focus {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.gap-v-5 {
+ min-height: 0.341rem;
+}
+
+.gap-h-5 {
+ min-width: 0.341rem;
+}
+
+.gap-v-10 {
+ min-height: 0.682rem;
+}
+
+.gap-h-10 {
+ min-width: 0.682rem;
+}
+
+.gap-v-15 {
+ min-height: 1.023rem;
+}
+
+.gap-h-15 {
+ min-width: 1.023rem;
+}
+
+.tab-btn {
+ @include small-rounding;
+ @include element_decel;
+ min-height: 2.5rem;
+ color: $onLayer0;
+}
+
+.tab-btn:hover {
+ background-color: $layer0Hover;
+}
+
+.tab-btn:focus {
+ background-color: $surfaceContainerLow;
+}
+
+.tab-btn-active>box>label {
+ color: $primary;
+}
+
+.tab-indicator {
+ transition: 180ms ease-in-out; // Doesn't look that good, but it syncs with the GtkStack
+ min-height: 0.205rem;
+ padding: 0rem 1.023rem;
+ color: $primary;
+}
+
+.tab-icon {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 2.25rem;
+ min-height: 2.25rem;
+ font-size: 1.406rem;
+ color: $onSurface;
+}
+
+.tab-icon-active {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.tab-icon-expandable {
+ transition: 0ms;
+ @include full-rounding;
+ min-width: 2.25rem;
+ min-height: 2.25rem;
+ font-size: 1.406rem;
+ color: $onSurface;
+ padding: 0rem;
+}
+
+.tab-icon-expandable-active {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ padding: 0rem 0.545rem;
+ min-width: 9.545rem;
+}
+
+widget {
+ @include small-rounding;
+}
+
+.spinbutton {
+ @include small-rounding;
+ background-color: $layer2;
+ padding: 0.341rem;
+ entry {
+ color: $onLayer2;
+ margin: 0.136rem 0.273rem;
+ }
+ button {
+ margin-left: 0.205rem;
+ padding: 0.136rem;
+ }
+}
\ No newline at end of file
diff --git a/ags/scss/_desktopbackground.scss b/ags/scss/_desktopbackground.scss
new file mode 100644
index 00000000..2dda9a12
--- /dev/null
+++ b/ags/scss/_desktopbackground.scss
@@ -0,0 +1,92 @@
+.bg-wallpaper-transition {
+ transition: 1000ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-size: 1px;
+}
+
+@mixin bg-textshadow {
+ // text-shadow: mix($shadow, $secondaryContainer, 50%) 1px 0px 3px;
+}
+
+.bg-time-box {
+ @include large-rounding;
+ margin: 2.045rem;
+ padding: 0.682rem;
+}
+
+.bg-time-clock {
+ @include titlefont;
+ @include bg-textshadow;
+ font-size: 5.795rem;
+ color: $onLayer0;
+}
+
+.bg-time-date {
+ @include titlefont;
+ @include bg-textshadow;
+ font-size: 2.591rem;
+ color: $onLayer0;
+}
+
+.bg-distro-box {
+ @include large-rounding;
+ margin: 2.045rem;
+ padding: 0.682rem;
+}
+
+.bg-distro-txt {
+ @include titlefont;
+ @include bg-textshadow;
+ font-size: 1.432rem;
+ color: $onLayer0;
+}
+
+.bg-distro-name {
+ @include titlefont;
+ @include bg-textshadow;
+ font-size: 1.432rem;
+ color: $onSecondaryContainer;
+}
+
+.bg-graph {
+ color: rgba(255, 255, 255, 0.5);
+ border-radius: 0.614rem;
+ border: 0.682rem solid;
+}
+
+.bg-quicklaunch-title {
+ @include mainfont;
+ color: $onSurfaceVariant;
+}
+
+.bg-quicklaunch-btn {
+ @include mainfont;
+ @include full-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+ min-width: 4.432rem;
+ min-height: 2.045rem;
+ padding: 0.273rem 0.682rem;
+}
+
+.bg-quicklaunch-btn:hover,
+.bg-quicklaunch-btn:focus {
+ background-color: $layer2Hover;
+}
+
+.bg-quicklaunch-btn:active {
+ background-color: $layer2Active;
+}
+
+.bg-system-bg {
+ @include normal-rounding;
+ // background-color: $background;
+}
+
+.bg-system-circprog {
+ @include fluent_decel_long;
+ min-width: 0.205rem; // Trough stroke width
+ min-height: 4.091rem; // Diameter
+ font-size: 0px;
+ padding: 0rem;
+ background-color: $layer2;
+}
\ No newline at end of file
diff --git a/ags/scss/_dock.scss b/ags/scss/_dock.scss
new file mode 100644
index 00000000..2e087ffe
--- /dev/null
+++ b/ags/scss/_dock.scss
@@ -0,0 +1,42 @@
+.dock-bg {
+ @include large-rounding;
+ @include elevation2;
+ background-color: $layer0;
+ padding: 0.682rem;
+}
+
+.dock-app-btn-animate {
+ transition-property: background-color;
+ transition-duration: 0.5s;
+}
+
+.dock-app-btn {
+ @include normal-rounding;
+ padding: 0.273rem;
+}
+
+.pinned-dock-app-btn {
+ @include normal-rounding;
+ padding: 0.273rem;
+ background-color: $layer0Hover;
+}
+
+.dock-app-btn:hover,
+.dock-app-btn:focus {
+ background-color: $layer0Hover;
+}
+
+.dock-app-btn:active {
+ background-color: $layer0Active;
+}
+
+.dock-app-icon {
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 3.409rem;
+}
+
+.dock-separator {
+ min-width: 0.068rem;
+ background-color: $outline;
+}
diff --git a/ags/scss/_lib_classes.scss b/ags/scss/_lib_classes.scss
new file mode 100644
index 00000000..7a0068c3
--- /dev/null
+++ b/ags/scss/_lib_classes.scss
@@ -0,0 +1,514 @@
+.test {
+ background-image: linear-gradient(45deg, #f4d609 0%, #f4d609 10%, #212121 10%, #212121 20%, #f4d609 20%, #f4d609 30%, #212121 30%,
+ #212121 40%, #f4d609 40%, #f4d609 50%, #212121 50%, #212121 60%, #f4d609 60%,
+ #f4d609 70%, #212121 70%, #212121 80%, #f4d609 80%, #f4d609 90%, #212121 90%, #212121 100%);
+ background-repeat: repeat;
+}
+
+.test-size {
+ min-height: 3rem;
+ min-width: 3rem;
+}
+
+.txt-title {
+ @include titlefont;
+ font-size: 2.045rem;
+}
+
+.txt-title-small {
+ @include titlefont;
+ font-size: 1.364rem;
+}
+
+.techfont {
+ @include techfont;
+}
+
+.txt-reading {
+ @include readingfont;
+}
+
+.no-anim {
+ @include noanim;
+}
+
+.txt {
+ color: $onBackground;
+}
+
+.txt-primary {
+ color: $primary;
+}
+
+.txt-onSecondaryContainer {
+ color: $onSecondaryContainer;
+}
+
+.txt-onSurfaceVariant {
+ color: $onSurfaceVariant;
+}
+
+.txt-onLayer1 {
+ color: $onLayer1;
+}
+
+.txt-shadow {
+ text-shadow: 1px 2px 8px rgba(0, 0, 0, 0.69);
+ margin: 10px;
+}
+
+.txt-gigantic {
+ @include mainfont;
+ font-size: 3rem;
+}
+
+.txt-massive {
+ @include mainfont;
+ font-size: 2.7273rem;
+}
+
+.txt-hugerass {
+ @include mainfont;
+ font-size: 2.045rem;
+}
+
+.txt-hugeass {
+ @include mainfont;
+ font-size: 1.8182rem;
+}
+
+.txt-larger {
+ @include mainfont;
+ font-size: 1.6363rem;
+}
+
+.txt-large {
+ //16pt
+ @include mainfont;
+ font-size: 1.4545rem;
+}
+
+.txt-norm {
+ //14pt
+ @include mainfont;
+ font-size: 1.2727rem;
+}
+
+.txt-small {
+ //12pt
+ @include mainfont;
+ font-size: 1.0909rem;
+}
+
+.txt-smallie {
+ //11pt
+ @include mainfont;
+ font-size: 1rem;
+}
+
+.txt-smaller {
+ //10pt
+ @include mainfont;
+ font-size: 0.9091rem;
+}
+
+.txt-tiny {
+ @include mainfont;
+ font-size: 0.7273rem;
+}
+
+.txt-poof {
+ font-size: 0px;
+}
+
+.txt-subtext {
+ @include subtext;
+}
+
+.txt-action {
+ @include actiontext;
+}
+
+.txt-thin {
+ font-weight: 300;
+}
+
+.txt-semibold {
+ font-weight: 500;
+}
+
+.txt-bold {
+ font-weight: bold;
+}
+
+.txt-italic {
+ font-style: italic;
+}
+
+.btn-primary {
+ @include full-rounding;
+ background-color: $primary;
+ color: $onPrimary;
+ padding: 0.682rem 1.023rem;
+}
+
+.titlefont {
+ @include titlefont;
+}
+
+.mainfont {
+ @include mainfont;
+}
+
+.icon-material {
+ @include icon-material;
+}
+
+.icon-nerd {
+ @include icon-nerd;
+}
+
+.separator-line {
+ background-color: mix($subtext, $surface, 50%);
+ min-width: 0.068rem;
+ min-height: 0.068rem;
+}
+
+.separator-circle {
+ @include full-rounding;
+ background-color: $outline;
+ margin: 0rem 0.682rem;
+ min-width: 0.273rem;
+ min-height: 0.273rem;
+}
+
+.spacing-h-3>* {
+ margin-right: 0.205rem;
+}
+
+.spacing-h-3>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v-3>* {
+ margin-bottom: 0.205rem;
+}
+
+.spacing-v-3>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-15>* {
+ margin-bottom: 1.023rem;
+}
+
+.spacing-v-15>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-15>* {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-15>revealer>* {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15>revealer:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-15>scrolledwindow>* {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15>scrolledwindow:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-v-5>box {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5>box:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5>* {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5-revealer>revealer>* {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5-revealer>revealer:last-child>* {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5-revealer>scrolledwindow>* {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5-revealer>scrolledwindow:last-child>* {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-4>* {
+ margin-right: 0.273rem;
+}
+
+.spacing-h-4>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-4>overlay>*:first-child {
+ margin-right: 0.273rem;
+}
+
+.spacing-h-4>overlay:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-5>* {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-5>widget>* {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5>widget:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-5>revealer>* {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5>revealer:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-5>scrolledwindow>* {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5>scrolledwindow:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-v-minus5>* {
+ margin-bottom: -0.341rem;
+}
+
+.spacing-v-minus5>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-10>* {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-10>revealer>* {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10>revealer:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-10>scrolledwindow>* {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10>scrolledwindow:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-h-10>flowboxchild>* {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10>flowboxchild:last-child>* {
+ margin-right: 0rem;
+}
+
+.spacing-v-10>* {
+ margin-bottom: 0.682rem;
+}
+
+.spacing-v-10>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-20>* {
+ margin-right: 1.364rem;
+}
+
+.spacing-h-20>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v-20>* {
+ margin-bottom: 1.364rem;
+}
+
+.spacing-v-20>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-30>* {
+ margin-right: 1.364rem;
+}
+
+.spacing-h-30>*:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v-30>* {
+ margin-bottom: 1.364rem;
+}
+
+.spacing-v-30>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.anim-enter {
+ @include anim-enter;
+}
+
+.anim-exit {
+ @include anim-exit;
+}
+
+.button-minsize {
+ @include button-minsize;
+}
+
+@each $spacing in (5, 8, 10, 15, 20) {
+ @each $dir in ("top", "bottom", "left", "right") {
+ .margin-#{$dir}-#{$spacing} {
+ margin-#{$dir}: 0.068rem * $spacing;
+ }
+ .padding-#{$dir}-#{$spacing} {
+ padding-#{$dir}: 0.068rem * $spacing;
+ }
+ }
+ .padding-#{$spacing} {
+ padding: 0.068rem * $spacing;
+ }
+ .margin-#{$spacing} {
+ padding: 0.068rem * $spacing;
+ }
+}
+
+.width-10 {
+ min-width: 0.682rem;
+}
+
+.height-10 {
+ min-width: 0.682rem;
+}
+
+.invisible {
+ opacity: 0;
+ background-color: transparent;
+ color: transparent;
+}
+
+.spacing-h--5>box {
+ margin-right: -0.341rem;
+}
+
+.spacing-h--5>box:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v--5>* {
+ margin-bottom: -0.341rem;
+}
+
+.spacing-v--5>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h--10>* {
+ margin-left: -1.364rem;
+}
+
+.spacing-h--10>*:first-child {
+ margin-left: 0rem;
+}
+
+.spacing-v--10>* {
+ margin-bottom: -0.682rem;
+}
+
+.spacing-v--10>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v--10>* {
+ margin-bottom: -0.682rem;
+}
+
+.spacing-v--10>*:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h--20>* {
+ margin-left: -1.364rem;
+}
+
+.spacing-h--20>*:first-child {
+ margin-left: 0rem;
+}
+
+.instant {
+ transition: 0ms;
+}
+
+.menu-decel {
+ @include menu_decel;
+}
+
+.element-show {
+ @include element_easeInOut;
+}
+
+.element-hide {
+ @include element_easeInOut;
+}
+
+.element-move {
+ @include element_easeInOut;
+}
+
+.element-decel {
+ @include element_decel;
+}
+
+.element-bounceout {
+ @include element_bounceOut;
+}
+
+.element-accel {
+ @include element_accel;
+}
\ No newline at end of file
diff --git a/ags/scss/_lib_mixins.scss b/ags/scss/_lib_mixins.scss
new file mode 100644
index 00000000..548909ce
--- /dev/null
+++ b/ags/scss/_lib_mixins.scss
@@ -0,0 +1,177 @@
+// Common colors
+$hovercolor: $surfaceContainerHigh;
+$activecolor: $surfaceContainerHighest;
+$rounding_verysmall: 0.477rem;
+$rounding_small: 0.818rem;
+$rounding_mediumsmall: 0.955rem;
+$rounding_medium: 1.159rem;
+$rounding_mediumlarge: 1.364rem;
+$rounding_large: 1.705rem;
+
+// Common rules
+@mixin small-rounding {
+ border-radius: $rounding_small;
+ -gtk-outline-radius: $rounding_small;
+}
+
+@mixin normal-rounding {
+ border-radius: $rounding_medium;
+ -gtk-outline-radius: $rounding_medium;
+}
+
+@mixin large-rounding {
+ border-radius: $rounding_large;
+ -gtk-outline-radius: $rounding_large;
+}
+
+@mixin full-rounding {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+}
+
+// @mixin symbolic-icon {
+// -gtk-icon-theme: 'Adwaita';
+// }
+
+@mixin titlefont {
+ // Geometric sans-serif
+ font-family: "Gabarito", "Poppins", "Readex Pro", "Lexend", sans-serif;
+}
+
+@mixin mainfont {
+ // Other clean sans-serif
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter",
+ "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ // font-weight: 500;
+}
+
+@mixin icon-material {
+ // Material Design Icons
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined",
+ "Material Symbols Sharp";
+}
+
+@mixin icon-nerd {
+ // Nerd Fonts
+ font-family: "SpaceMono NF", "SpaceMono Nerd Font", "JetBrains Mono NF",
+ "JetBrains Mono Nerd Font", monospace;
+}
+
+@mixin techfont {
+ // Monospace for sys info n stuff. Doesn't have to be a nerd font, but it's cool.
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font",
+ "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+}
+
+@mixin readingfont {
+ // The most readable fonts, for a comfortable reading experience
+ // in stuff like AI chat on sidebar
+ font-family: "Readex Pro", "Lexend", "Noto Sans", sans-serif;
+ // font-weight: 500;
+}
+
+@mixin subtext {
+ color: $subtext;
+}
+
+@mixin actiontext {
+ color: $actiontext;
+}
+
+$elevation_margin: 0.476rem;
+
+@mixin elevation2 {
+ box-shadow: 0px 2px 3px transparentize($shadow, 0.55);
+ margin: $elevation_margin;
+}
+
+@mixin elevation2-margin {
+ margin: $elevation_margin;
+}
+
+@mixin elevation2-padding {
+ padding: $elevation_margin;
+}
+
+@mixin elevation3 {
+ box-shadow: 0px 2px 5px $shadow;
+ margin: $elevation_margin;
+}
+
+@mixin menu_decel {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin menu_decel_fast {
+ transition: 170ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin menu_accel {
+ transition: 160ms cubic-bezier(0.38, 0.04, 1, 0.07);
+}
+
+@mixin menu_accel_fast {
+ transition: 100ms cubic-bezier(0.38, 0.04, 1, 0.07);
+}
+
+@mixin fluent_decel {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin fluent_decel_long {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin fluent_accel {
+ transition: 150ms cubic-bezier(0.42, 0, 1, 1);
+}
+
+@mixin noanim {
+ transition: 0ms;
+}
+
+@mixin anim-enter {
+ transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+}
+
+@mixin anim-exit {
+ transition: 150ms cubic-bezier(0.3, 0, 0.8, 0.15);
+}
+
+@mixin element_decel {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+}
+
+@mixin element_bounceOut {
+ transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@mixin element_accel {
+ transition: 300ms cubic-bezier(0.55, 0, 1, 0.45);
+}
+
+@mixin element_easeInOut {
+ transition: 300ms cubic-bezier(0.85, 0, 0.15, 1);
+}
+
+@mixin elevation-border-softer {
+ border-top: 1px solid mix($surface, $onSurface, 86%);
+ border-left: 1px solid mix($surface, $onSurface, 86%);
+ border-right: 1px solid mix($surface, $onSurface, 90%);
+ border-bottom: 1px solid mix($surface, $onSurface, 90%);
+}
+
+@mixin elevation-border {
+ border-top: 1px solid $surfaceContainer;
+ border-left: 1px solid $surfaceContainer;
+ border-right: 1px solid $surfaceContainer;
+ border-bottom: 1px solid $surfaceContainer;
+}
+
+@mixin button-minsize {
+ min-width: 2.727rem;
+ min-height: 2.727rem;
+}
+
+$white: white;
+$black: black;
diff --git a/ags/scss/_music.scss b/ags/scss/_music.scss
new file mode 100644
index 00000000..473a855e
--- /dev/null
+++ b/ags/scss/_music.scss
@@ -0,0 +1,156 @@
+@import 'material';
+@import 'musicmaterial';
+@import './wal';
+@import 'musicwal';
+@import './colors';
+@import './lib_mixins';
+$music_transparentize_amount: 0.3;
+$music_gradient1: mix($color1, $background, 50%);
+
+// @if $darkmode == True {
+// $music_gradient1: mix($color1, $background, 30%);
+// }
+
+$music_gradient2: $color3;
+$music_gradient3: $color5;
+$music_colorstart_transparentize: 0.3;
+$music_extra_transparentize: 0.15;
+
+$secondaryContainer: transparentize(mix(mix($background, $color2, 50%), $color6, 80%), 0.5);
+$onSecondaryContainer: mix($color7, $color2, 90%);
+@if $darkmode == False {
+ $onSecondaryContainer: mix($onSecondaryContainer, black, 50%);
+}
+
+.osd-music {
+ @include menu_decel;
+ @include elevation2;
+ margin-top: 0.313rem;
+ @include normal-rounding;
+ // min-height: 7.159rem;
+ min-width: 29.659rem;
+ background-color: $layer0;
+ padding: 0rem 1.023rem;
+ background: // Inspired by Amberol
+ linear-gradient(127deg, transparentize($music_gradient1, $music_colorstart_transparentize), transparentize($music_gradient1, $music_colorstart_transparentize - $music_transparentize_amount + $music_extra_transparentize) 70.71%),
+ linear-gradient(217deg, transparentize($music_gradient2, $music_colorstart_transparentize), transparentize($music_gradient2, $music_colorstart_transparentize - $music_transparentize_amount + $music_extra_transparentize) 70.71%),
+ radial-gradient(circle at 0% 100%, $color4 13%, rgba(0, 0, 0, 0) 100%),
+ linear-gradient(336deg, transparentize($music_gradient3, $music_colorstart_transparentize), transparentize($music_gradient3, $music_colorstart_transparentize - $music_transparentize_amount + $music_extra_transparentize) 70.71%),
+ linear-gradient($background, $background), // We don't want wallpaper tint, so here's a fully opaque surface
+ ;
+}
+
+
+.osd-music-cover-fallback {
+ @include element_decel;
+ @include small-rounding;
+ // margin: 1.023rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-color: $layer1;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-cover {
+ @include small-rounding;
+ @include menu_decel;
+ // Must be -top and -bottom or it'll mess up horizontal spacing
+ margin-top: 1.023rem;
+ margin-bottom: 1.023rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+}
+
+.osd-music-cover-art {
+ @include small-rounding;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-size: cover;
+ background-position: center;
+}
+
+.osd-music-info {
+ margin: 1.023rem 0rem;
+}
+
+.osd-music-title {
+ @include element_decel;
+ @include titlefont;
+ font-size: 1.364rem;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-artists {
+ @include element_decel;
+ @include mainfont;
+ font-size: 0.955rem;
+ color: mix($onSecondaryContainer, $secondaryContainer, 80%);
+}
+
+.osd-music-pill {
+ @include element_decel;
+ @include full-rounding;
+ @include titlefont;
+ min-width: 1.833rem;
+ padding: 0.273rem 0.682rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-controls {
+ @include element_decel;
+ @include full-rounding;
+ @include titlefont;
+ min-width: 1.833rem;
+ padding: 0.205rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-controlbtn {
+ @include menu_decel;
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ @include full-rounding;
+}
+
+.osd-music-controlbtn:hover,
+.osd-music-controlbtn:focus {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 90%);
+}
+
+.osd-music-controlbtn:active {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 85%);
+}
+
+.osd-music-controlbtn-txt {
+ @include element_decel;
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem;
+}
+
+.osd-music-circprog {
+ @include fluent_decel_long;
+ min-width: 0.409rem; // width of progress
+ min-height: 3.068rem;
+ padding: 0.273rem;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-playstate {
+ @include menu_decel;
+ min-height: 3.068rem;
+ min-width: 3.068rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-playstate-btn>label {
+ transition: 50ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem;
+}
\ No newline at end of file
diff --git a/ags/scss/_notifications.scss b/ags/scss/_notifications.scss
new file mode 100644
index 00000000..152ead0d
--- /dev/null
+++ b/ags/scss/_notifications.scss
@@ -0,0 +1,255 @@
+@mixin notif-rounding {
+ @include normal-rounding;
+}
+
+.notif-low {
+ @include notif-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-normal {
+ @include notif-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-critical {
+ @include notif-rounding;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-clicked-low {
+ background-color: $layer1Hover;
+}
+
+.notif-clicked-normal {
+ background-color: $layer1Hover;
+}
+
+.notif-clicked-critical {
+ background-color: $onSecondary;
+ color: $onSecondaryContainer;
+}
+
+.popup-notif-low {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $layer2;
+
+ @if $transparent ==True {
+ background-color: $layer0;
+ }
+
+ border: 0.034rem solid $outlineVariant;
+ color: $onLayer2;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-normal {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $layer2;
+
+ @if $transparent ==True {
+ background-color: $layer0;
+ }
+
+ border: 0.034rem solid $outlineVariant;
+ color: $onLayer2;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-critical {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $secondaryContainer;
+ border: 0.034rem solid $onSecondaryContainer;
+ color: $onSecondaryContainer;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-clicked-low {
+ background-color: $surfaceContainerLow;
+}
+
+.popup-notif-clicked-normal {
+ background-color: $surfaceContainerLow;
+}
+
+.popup-notif-clicked-critical {
+ background-color: $onSecondary;
+ color: $onSecondaryContainer;
+}
+
+.notif-body-low {
+ color: $outline;
+}
+
+.notif-body-normal {
+ color: $outline;
+}
+
+.notif-body-critical {
+ color: mix($onSecondaryContainer, $secondaryContainer, 67%);
+}
+
+.notif-icon {
+ @include full-rounding;
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 2.182rem;
+}
+
+.notif-icon-material {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-low {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-normal {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-critical {
+ background-color: $secondary;
+ color: $onSecondary;
+}
+
+.notif-expand-btn {
+ @include notif-rounding;
+ min-width: 1.841rem;
+ min-height: 1.841rem;
+}
+
+.notif-expand-btn:hover,
+.notif-expand-btn:focus {
+ background: $layer2Hover;
+}
+
+.notif-expand-btn:active {
+ background: $layer2Active;
+}
+
+.notif-listaction-btn {
+ @include notif-rounding;
+ padding: 0.341rem 0.682rem;
+}
+
+.notif-listaction-btn:hover,
+.notif-listaction-btn:focus {
+ background-color: $layer2Hover;
+}
+
+.notif-listaction-btn:active {
+ background-color: $layer2Active;
+}
+
+.notif-listaction-btn-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-listaction-btn-enabled:hover,
+.notif-listaction-btn-enabled:focus {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 90%);
+}
+
+.notif-listaction-btn-enabled:active {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 75%);
+}
+
+.osd-notif {
+ @include notif-rounding;
+ background-color: $layer0;
+ min-width: 30.682rem;
+}
+
+.notif-circprog-low {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $onSecondaryContainer;
+}
+
+.notif-circprog-normal {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $onSecondaryContainer;
+}
+
+.notif-circprog-critical {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $secondaryContainer;
+}
+
+.notif-actions {
+ min-height: 2.045rem;
+}
+
+.notif-action {
+ @include small-rounding;
+}
+
+.notif-action-low {
+ background-color: $surfaceContainerHighest;
+ color: $onLayer2;
+}
+
+.notif-action-low:focus,
+.notif-action-low:hover {
+ border: 0.040rem solid $outlineVariant;
+}
+
+.notif-action-low:active {
+ background-color: $surfaceBright;
+}
+
+.notif-action-normal {
+ background-color: $surfaceContainerHighest;
+ color: $onSurface;
+}
+
+.notif-action-normal:focus,
+.notif-action-normal:hover {
+ border: 0.040rem solid $outlineVariant;
+}
+
+.notif-action-normal:active {
+ background-color: $surfaceBright;
+}
+
+.notif-action-critical {
+ background-color: mix($secondary, $onSecondary, 30%);
+ color: $onSurfaceVariant;
+}
+
+.notif-action-critical:focus,
+.notif-action-critical:hover {
+ border: 0.040rem solid $outline;
+}
+
+.notif-action-critical:active {
+ background-color: mix($secondary, $onSecondary, 40%);
+}
diff --git a/ags/scss/_osd.scss b/ags/scss/_osd.scss
new file mode 100644
index 00000000..ef21f435
--- /dev/null
+++ b/ags/scss/_osd.scss
@@ -0,0 +1,205 @@
+// .osd-window {
+ // margin-top: 2.727rem;
+// }
+
+.osd-bg {
+ min-width: 8.864rem;
+ min-height: 3.409rem;
+}
+
+.osd-value {
+ @include elevation-border;
+ @include elevation2;
+ background-color: $layer0;
+ border-radius: 1.023rem;
+ padding: 0.625rem 1.023rem;
+ padding-top: 0.313rem;
+}
+
+.osd-progress {
+ min-height: 0.955rem;
+ min-width: 0.068rem;
+ padding: 0rem;
+ border-radius: 10rem;
+ @include fluent_decel;
+
+ trough {
+ min-height: 0.954rem;
+ min-width: 0.068rem;
+ border-radius: 10rem;
+ background-color: $layer2;
+ // border: 0.068rem solid $onSecondaryContainer;
+ }
+
+ progress {
+ @include fluent_decel;
+ min-height: 0.680rem;
+ min-width: 0.680rem;
+ margin: 0rem 0.137rem;
+ border-radius: 10rem;
+ background-color: $onLayer2;
+ }
+}
+
+.osd-label {
+ font-size: 1.023rem;
+ font-weight: 500;
+ margin-top: 0.341rem;
+}
+
+.osd-value-txt {
+ @include titlefont;
+ font-size: 1.688rem;
+ font-weight: 500;
+ color: $onLayer0;
+}
+
+.osd-brightness {
+ color: $brightnessOnLayer0;
+}
+.osd-brightness-progress {
+ progress {
+ background-color: $brightnessOnLayer0;
+ }
+}
+.osd-volume {
+ color: $volumeOnLayer0;
+}
+.osd-volume-progress {
+ progress {
+ background-color: $volumeOnLayer0;
+ }
+}
+
+.osd-notifs {
+ padding-top: 0.313rem;
+}
+
+.osd-colorscheme {
+ border-radius: 1.023rem;
+ background-color: $layer0;
+ padding: 0.313rem 0.626rem;
+ @include elevation2;
+}
+
+.osd-colorscheme-settings {
+ background-color: $layer1;
+ padding: 0.313rem 0.626rem;
+ @include small-rounding;
+}
+
+.osd-color {
+ border-radius: 0.650rem;
+ -gtk-outline-radius: 0.650rem;
+ min-width: 2.727rem;
+ min-height: 1.705rem;
+ padding: 0rem 0.341rem;
+ font-weight: bold;
+
+ box {
+ @include small-rounding;
+ margin: 0.409rem;
+ }
+}
+
+.osd-color-primary {
+ background-color: $primary;
+ color: $onPrimary;
+ box { background-color: $onPrimary; }
+}
+.osd-color-primaryContainer {
+ background-color: $primaryContainer;
+ color: $onPrimaryContainer;
+ box { background-color: $onPrimaryContainer; }
+}
+.osd-color-secondary {
+ background-color: $secondary;
+ color: $onSecondary;
+ box { background-color: $onSecondary; }
+}
+.osd-color-secondaryContainer {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ box { background-color: $onSecondaryContainer; }
+}
+
+.osd-color-tertiary {
+ background-color: $tertiary;
+ color: $onTertiary;
+ box { background-color: $onTertiary; }
+}
+.osd-color-tertiaryContainer {
+ background-color: $tertiaryContainer;
+ color: $onTertiaryContainer;
+ box { background-color: $onTertiaryContainer; }
+}
+
+.osd-color-error {
+ background-color: $error;
+ color: $onError;
+ box { background-color: $onError; }
+}
+.osd-color-errorContainer {
+ background-color: $errorContainer;
+ color: $onErrorContainer;
+ box { background-color: $onErrorContainer; }
+}
+
+.osd-color-surface {
+ background-color: $surface;
+ color: $onSurface;
+ border: 0.068rem solid $outlineVariant;
+ box { background-color: $onSurface; }
+}
+
+.osd-color-surfaceContainer {
+ background-color: $surfaceContainer;
+ color: $onSurface;
+ box { background-color: $onSurface; }
+}
+
+.osd-color-inverseSurface {
+ background-color: $inverseSurface;
+ color: $inverseOnSurface;
+ box { background-color: $onSurfaceVariant; }
+}
+
+.osd-color-surfaceVariant {
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ box { background-color: $onSurfaceVariant; }
+}
+.osd-color-L1 {
+ background-color: $layer1;
+ color: $onLayer1;
+ box { background-color: $onLayer1; }
+}
+
+.osd-color-layer0 {
+ background-color: $layer0;
+ color: $onLayer0;
+ box { background-color: $onLayer0; }
+}
+
+.osd-settings-btn-arrow {
+ @include full-rounding;
+ @include icon-material;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ color: $onSurface;
+
+ &:hover {
+ background-color: $surfaceContainerHigh;
+ }
+ &:active {
+ background-color: $surfaceContainerHighest;
+ }
+}
+
+.osd-show {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+.osd-hide {
+ transition: 190ms cubic-bezier(0.85, 0, 0.15, 1);
+}
diff --git a/ags/scss/_osk.scss b/ags/scss/_osk.scss
new file mode 100644
index 00000000..6cefc4af
--- /dev/null
+++ b/ags/scss/_osk.scss
@@ -0,0 +1,119 @@
+$osk_key_height: 2.5rem;
+$osk_key_width: 2.5rem;
+$osk_key_padding: 0.188rem;
+$osk_key_rounding: 0.545rem;
+$osk_key_fontsize: 1.091rem;
+
+.osk-window {
+ // @include menu_decel_fast;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ // min-height: 29.591rem;
+ // min-width: 50rem;
+ background-color: $layer0;
+}
+
+.osk-body {
+ padding: 1.023rem;
+ padding-top: 0rem;
+}
+
+.osk-show {
+ @include menu_decel_fast;
+}
+
+.osk-hide {
+ margin-top: 30.682rem;
+ margin-bottom: -30.682rem;
+ // opacity: 0;
+ @include menu_accel_fast;
+}
+
+.osk-dragline {
+ @include full-rounding;
+ background-color: $onLayer0Inactive;
+ min-height: 0.273rem;
+ min-width: 10.227rem;
+ margin-top: 0.545rem;
+ margin-bottom: 0.205rem;
+}
+
+.osk-key {
+ border-radius: $osk_key_rounding;
+ background-color: $layer1;
+ color: $onLayer1;
+ padding: $osk_key_padding;
+ font-weight: 500;
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key:hover,
+.osk-key:focus {
+ background-color: $layer1Hover;
+}
+
+.osk-key:active {
+ background-color: $layer1Active;
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key-active {
+ background-color: $layer1Active;
+}
+
+.osk-key-normal {
+ min-width: $osk_key_width;
+ min-height: $osk_key_height;
+}
+
+.osk-key-fn {
+ min-width: $osk_key_width * 1.005;
+ min-height: calc($osk_key_height / 2); // dart-sass
+ // min-height: $osk_key_height / 2; // sassc
+}
+
+.osk-key-tab {
+ min-width: $osk_key_width * 1.6;
+ min-height: $osk_key_height;
+}
+
+.osk-key-caps {
+ min-width: $osk_key_width * 1.9;
+ min-height: $osk_key_height;
+}
+
+.osk-key-shift {
+ min-width: $osk_key_width * 2.5;
+ min-height: $osk_key_height;
+}
+
+.osk-key-control {
+ min-width: $osk_key_width * 1.3;
+ min-height: $osk_key_height;
+}
+
+.osk-control-button {
+ border-radius: $osk_key_rounding;
+ background-color: $layer1;
+ color: $onLayer1;
+ font-weight: 500;
+ font-size: $osk_key_fontsize;
+ padding: 0.682rem;
+}
+
+.osk-control-button:hover,
+.osk-control-button:focus {
+ background-color: $layer1Hover;
+}
+
+.osk-control-button:active {
+ background-color: $layer1Active;
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key-empty, .osk-key-empty:hover, .osk-key-empty:focus {
+ min-width: $osk_key_width;
+ min-height: $osk_key_height;
+ background-color: transparent;
+}
diff --git a/ags/scss/_overview.scss b/ags/scss/_overview.scss
new file mode 100644
index 00000000..1ba52c3e
--- /dev/null
+++ b/ags/scss/_overview.scss
@@ -0,0 +1,139 @@
+.overview-window {
+ margin-top: 2.727rem;
+}
+
+.overview-search-box {
+ @include element_decel;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ min-width: 13.636rem;
+ min-height: 3.409rem;
+ padding: 0rem 1.364rem;
+ padding-right: 2.864rem;
+ background-color: $background;
+ color: $onBackground;
+
+ selection {
+ background-color: $secondary;
+ color: $onSecondary;
+ }
+
+ caret-color: transparent;
+}
+
+.overview-search-box-extended {
+ min-width: 25.909rem;
+ caret-color: $onSecondaryContainer;
+}
+
+.overview-search-prompt {
+ color: $subtext;
+}
+
+.overview-search-icon {
+ margin: 0rem 1.023rem;
+}
+
+.overview-search-prompt-box {
+ margin-left: -18.545rem;
+ margin-right: $elevation_margin + 0.068rem;
+}
+
+.overview-search-icon-box {
+ margin-left: -18.545rem;
+ margin-right: $elevation_margin + 0.068rem;
+}
+
+.overview-search-results {
+ // min-height: 2.813rem;
+ // min-height: 37.5rem;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ min-width: 28.773rem;
+ padding: 0.682rem;
+ background-color: $layer0;
+ color: $onLayer0;
+}
+
+.overview-search-results-icon {
+ margin: 0rem 0.682rem;
+ font-size: 2.386rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+}
+
+.overview-search-results-txt {
+ margin-right: 0.682rem;
+}
+
+.overview-search-results-txt-cmd {
+ margin-right: 0.682rem;
+ @include techfont;
+ font-size: 1.227rem;
+}
+
+.overview-search-result-btn {
+ @include normal-rounding;
+ padding: 0.341rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+
+ caret-color: transparent;
+}
+
+.overview-search-result-btn:hover,
+.overview-search-result-btn:focus {
+ background-color: $layer2;
+}
+
+.overview-search-result-btn:active {
+ background-color: $layer2Hover;
+}
+
+.overview-tasks {
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ padding: 0.341rem;
+ background-color: $background;
+ color: $onBackground;
+}
+
+.overview-tasks-workspace {
+ @include normal-rounding;
+ // @include elevation-border;
+ margin: 0.341rem;
+ background-color: $layer1;
+}
+
+.overview-tasks-workspace-number {
+ @include mainfont;
+ color: $onSurfaceVariant;
+}
+
+.overview-tasks-window {
+ @include normal-rounding;
+ @include menu_decel;
+ background-color: transparentize($layer3, 0.2);
+ color: $onSurface;
+ border: 0.068rem solid $surfaceContainerHighest;
+}
+
+.overview-tasks-window:hover,
+.overview-tasks-window:focus {
+ background-color: transparentize($secondaryContainer, 0.3);
+}
+
+.overview-tasks-window:active {
+ background-color: transparentize($secondaryContainer, 0);
+}
+
+.overview-tasks-window-selected {
+ background-color: transparentize($secondaryContainer, 0.3);
+}
+
+.overview-tasks-window-dragging {
+ opacity: 0.2;
+}
diff --git a/ags/scss/_session.scss b/ags/scss/_session.scss
new file mode 100644
index 00000000..16390829
--- /dev/null
+++ b/ags/scss/_session.scss
@@ -0,0 +1,40 @@
+.session-bg {
+ background-color: transparentize($color: $layer0, $amount: 0.4);
+}
+
+.session-button {
+ @include large-rounding;
+ min-width: 8.182rem;
+ min-height: 8.182rem;
+ background-color: $layer1;
+ color: $onLayer1;
+ font-size: 3rem;
+}
+
+.session-button-focused {
+ background-color: $layer1Hover;
+}
+
+.session-button-desc {
+ background-color: $layer2;
+ color: $onLayer2;
+ border-bottom-left-radius: $rounding_large;
+ border-bottom-right-radius: $rounding_large;
+ padding: 0.205rem 0.341rem;
+ font-weight: 700;
+}
+
+.session-button-cancel {
+ @include large-rounding;
+ min-width: 8.182rem;
+ min-height: 5.455rem;
+ background-color: $layer1;
+ color: $onLayer1;
+ font-size: 3rem;
+}
+
+@for $i from 1 through 7 {
+ .session-color-#{$i} {
+ color: nth($sessionColors, $i);
+ }
+}
diff --git a/ags/scss/_sidebars.scss b/ags/scss/_sidebars.scss
new file mode 100644
index 00000000..599a7a33
--- /dev/null
+++ b/ags/scss/_sidebars.scss
@@ -0,0 +1,999 @@
+$sidebar_chat_textboxareaColor: mix($onSurfaceVariant, $surfaceVariant, 40%);
+
+@mixin group-padding {
+ padding: 0.341rem;
+}
+
+.sidebar-right {
+ @include menu_decel;
+ @include elevation-border;
+ @include elevation2;
+ border-radius: $rounding_large - $elevation_margin + 0.068rem;
+ min-width: 27.818rem;
+ background-color: $background;
+ padding: 1.023rem;
+}
+
+.sidebar-left {
+ @include menu_decel;
+ @include elevation-border;
+ @include elevation2;
+ border-radius: $rounding_large - $elevation_margin + 0.068rem;
+ min-width: 27.818rem;
+ background-color: $background;
+ padding: 1.023rem;
+}
+
+.sidebar-group {
+ @include normal-rounding;
+ @include group-padding;
+ background-color: $layer1;
+}
+
+.sidebar-group-nopad {
+ @include normal-rounding;
+ background-color: $layer1;
+}
+
+.sidebar-group-invisible {
+ @include group-padding;
+}
+
+.sidebar-group-invisible-morehorizpad {
+ padding: 0.341rem 0.682rem;
+}
+
+.sidebar-togglesbox {
+ @include full-rounding;
+ @include group-padding;
+ background-color: $layer1;
+}
+
+.sidebar-iconbutton {
+ @include full-rounding;
+ @include element_decel;
+ color: $onSurface;
+ min-width: 2.727rem;
+ min-height: 2.727rem;
+}
+
+.sidebar-iconbutton:hover,
+.sidebar-iconbutton:focus {
+ background-color: $layer1Hover;
+}
+
+.sidebar-iconbutton:active {
+ background-color: $layer1Active;
+}
+
+.sidebar-button-active {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-button-active:hover,
+.sidebar-button-active:focus {
+ background-color: mix($primary, $layer1Hover, 70%);
+}
+
+.sidebar-button-active:active {
+ background-color: mix($primary, $layer1Active, 40%);
+}
+
+.sidebar-buttons-separator {
+ min-width: 0.068rem;
+ min-height: 0.068rem;
+ background-color: $onSurfaceVariant;
+}
+
+.sidebar-navrail {
+ padding: 0rem $rounding_medium;
+}
+
+.sidebar-navrail-btn>box>label {
+ @include full-rounding;
+ @include menu_decel;
+}
+
+.sidebar-navrail-btn:hover>box>label:first-child,
+.sidebar-navrail-btn:focus>box>label:first-child {
+ background-color: $layer1Hover;
+}
+
+.sidebar-navrail-btn:active>box>label:first-child {
+ background-color: $layer1Active;
+}
+
+.sidebar-navrail-btn-active>box>label:first-child {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-navrail-btn-active:hover>box>label:first-child,
+.sidebar-navrail-btn-active:focus>box>label:first-child {
+ background-color: mix($secondaryContainer, $layer1Hover, 90%);
+ color: mix($onSecondaryContainer, $layer1Hover, 90%);
+}
+
+.sidebar-sysinfo-grouppad {
+ padding: 1.159rem;
+}
+
+.sidebar-memory-ram-circprog {
+ @include fluent_decel_long;
+ min-width: $rounding_small;
+ min-height: 4.091rem;
+ padding: 0.409rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ font-size: 0px;
+}
+
+.sidebar-memory-swap-circprog {
+ @include fluent_decel_long;
+ min-width: $rounding_small;
+ min-height: 2.255rem;
+ padding: 0.409rem;
+ margin: 0.918rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ font-size: 0px;
+}
+
+.sidebar-cpu-circprog {
+ min-width: $rounding_small;
+ min-height: 3.409rem;
+ padding: 0.409rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ @include fluent_decel_long;
+ font-size: 0px;
+}
+
+.sidebar-scrollbar {
+ trough {
+ @include full-rounding;
+ min-width: 0.545rem;
+ background-color: transparent;
+ }
+
+ slider {
+ @include full-rounding;
+ @include element_decel;
+ min-width: 0.273rem;
+ min-height: 2.045rem;
+ background-color: transparentize($onSurfaceVariant, 0.7);
+ }
+
+ slider:hover,
+ slider:focus {
+ background-color: transparentize($onSurfaceVariant, 0.6);
+ }
+
+ slider:active {
+ background-color: transparentize($onSurface, 0.5);
+ }
+}
+
+.sidebar-calendar-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 2.523rem;
+ min-width: 2.523rem;
+ color: $onSurface;
+}
+
+.sidebar-calendar-btn:hover,
+.sidebar-calendar-btn:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-calendar-btn:active {
+ background-color: $activecolor;
+}
+
+.sidebar-calendar-btn-txt {
+ margin-left: -10.341rem;
+ margin-right: -10.341rem;
+}
+
+.sidebar-calendar-btn-today {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-calendar-btn-today:hover,
+.sidebar-calendar-btn-today:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-calendar-btn-today:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-calendar-btn-othermonth {
+ color: $outline;
+}
+
+.sidebar-calendar-header {
+ margin: 0.341rem;
+}
+
+.sidebar-calendar-monthyear-btn {
+ @include full-rounding;
+ @include element_decel;
+ padding: 0rem 0.682rem;
+ background-color: $layer2;
+ color: $onSurface;
+}
+
+.sidebar-calendar-monthyear-btn:hover,
+.sidebar-calendar-monthyear-btn:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-calendar-monthyear-btn:active {
+ background-color: $activecolor;
+}
+
+.sidebar-calendar-monthshift-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ background-color: $layer2;
+ color: $outline;
+}
+
+.sidebar-calendar-monthshift-btn:hover {
+ background-color: $hovercolor;
+}
+
+.sidebar-calendar-monthshift-btn:active {
+ background-color: $activecolor;
+}
+
+.sidebar-todo-item {
+ @include small-rounding;
+ margin-right: 0.545rem;
+ // padding: 0.341rem;
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-todo-txt {
+ margin: 0.682rem;
+ margin-bottom: 0rem;
+}
+
+.sidebar-todo-actions {
+ margin: 0.341rem;
+ margin-top: 0rem;
+ margin-right: 0rem;
+}
+
+.sidebar-todo-item-action {
+ @include element_decel;
+ border-radius: 9999px;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+}
+
+.sidebar-todo-item-action:hover,
+.sidebar-todo-item-action:focus {
+ background-color: $layer2Hover;
+}
+
+.sidebar-todo-item-action:active {
+ background-color: $layer2Active;
+}
+
+.sidebar-todo-crosser {
+ transition: margin 200ms cubic-bezier(0.1, 1, 0, 1), background-color 0ms;
+ min-width: 0rem;
+}
+
+.sidebar-todo-crosser-crossed {
+ background-color: $onBackground;
+}
+
+.sidebar-todo-crosser-removed {
+ background-color: $error;
+}
+
+.sidebar-todo-new {
+ @include full-rounding;
+ @include element_decel;
+ background-color: $layer2;
+ color: $onLayer2;
+ margin: 0.341rem;
+ padding: 0.205rem 0.545rem;
+}
+
+.sidebar-todo-new,
+.sidebar-todo-new:focus {
+ color: $onSecondaryContainer;
+ background-color: $secondaryContainer;
+}
+
+.sidebar-todo-new:active {
+ color: $onPrimaryContainer;
+ background-color: $primaryContainer;
+}
+
+.sidebar-todo-add {
+ @include element_decel;
+ @include small-rounding;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ color: $onSecondaryContainer;
+ border: 0.068rem solid $onSurface;
+}
+
+.sidebar-todo-add:hover,
+.sidebar-todo-add:focus {
+ background-color: $surfaceContainerHigh;
+}
+
+.sidebar-todo-add:active {
+ background-color: $surfaceContainerHighest;
+}
+
+.sidebar-todo-add-available {
+ @include element_decel;
+ @include small-rounding;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ background-color: $primary;
+ color: $onPrimary;
+ border: 0.068rem solid $primary;
+}
+
+.sidebar-todo-add-available:hover,
+.sidebar-todo-add-available:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-todo-add-available:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-todo-entry {
+ @include element_decel;
+ @include small-rounding;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ caret-color: $onSurfaceVariant;
+ margin: 0rem 0.341rem;
+ min-height: 1.773rem;
+ min-width: 0rem;
+ padding: 0.205rem 0.682rem;
+ border: 0.068rem solid $outline;
+}
+
+.sidebar-todo-entry:focus {
+ border: 0.068rem solid $onSurfaceVariant;
+}
+
+.sidebar-module {
+ @include normal-rounding;
+ @include group-padding;
+ background-color: $layer1;
+ padding: 0.682rem;
+}
+
+.sidebar-module-btn-arrow {
+ @include full-rounding;
+ @include icon-material;
+ background-color: $layer2;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+
+ &:hover,
+ &:focus {
+ background-color: $layer2Hover;
+ }
+
+ &:active {
+ background-color: $layer2Active;
+ }
+}
+
+.sidebar-module-scripts-button {
+ @include full-rounding;
+ @include icon-material;
+ background-color: $layer1;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+
+ &:hover,
+ &:focus {
+ background-color: $layer1Hover;
+ }
+
+ &:active {
+ background-color: $layer1Active;
+ }
+}
+
+$colorpicker_rounding: 0.341rem;
+
+.sidebar-module-colorpicker-wrapper {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-cursorwrapper {
+ padding: 0.341rem 0.136rem;
+}
+
+.sidebar-module-colorpicker-hue {
+ min-height: 13.636rem;
+ min-width: 1.091rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-hue-cursor {
+ background-color: $onBackground;
+ border: 0.136rem solid $onBackground;
+ min-height: 0.136rem;
+ margin-top: -0.136rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-wrapper {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-saturationandlightness {
+ min-height: 13.636rem;
+ min-width: 13.636rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-cursorwrapper {
+ padding: 0.341rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-cursor {
+ @include full-rounding;
+ border: 0.136rem solid white;
+ min-width: 0.682rem;
+ min-height: 0.682rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem;
+}
+
+.sidebar-module-colorpicker-result-area {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-result-box {
+ border-radius: $colorpicker_rounding;
+ min-width: 2.045rem;
+ min-height: 0.682rem;
+ padding: 0.341rem;
+}
+
+.sidebar-icontabswitcher {
+ @include full-rounding;
+ @include group-padding;
+ background-color: $layer1;
+}
+
+.sidebar-chat-providerswitcher {
+ @include small-rounding;
+ padding: 0.477rem 0.682rem;
+ background-color: $surfaceContainerHigh;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-chat-viewport {
+ @include element_decel;
+ padding: 0.682rem 0rem;
+}
+
+.sidebar-chat-textarea {
+ @include normal-rounding;
+ background-color: $layer1;
+ color: $onLayer1;
+ padding: 0.682rem;
+}
+
+.sidebar-chat-entry {
+ color: $onSurfaceVariant;
+ caret-color: $onSurfaceVariant;
+ min-height: 1.773rem;
+ min-width: 0rem;
+}
+
+.sidebar-chat-wrapper {
+ transition: 400ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+.sidebar-chat-wrapper-extended {
+ min-height: 7.500rem;
+}
+
+.sidebar-chat-send {
+ @include element_decel;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ border-radius: $rounding_medium - 0.681rem;
+}
+
+.sidebar-chat-send:hover,
+.sidebar-chat-send:focus {
+ background-color: $surfaceBright;
+}
+
+.sidebar-chat-send:active {
+ background-color: $surfaceVariant;
+}
+
+.sidebar-chat-send-available {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-chat-send-available:hover,
+.sidebar-chat-send-available:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-chat-send-available:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-chat-messagearea {
+ margin: 0.341rem;
+}
+
+.sidebar-chat-message {
+ @include normal-rounding;
+ @include group-padding;
+ background-color: $layer1;
+}
+
+$skeleton-accent: mix($secondary, $onSecondary, 50%);
+
+@keyframes sidebar-chat-message-skeletonline-anim {
+ 0% {
+ background-position: 175% 0%;
+ }
+
+ 100% {
+ background-position: 50% 0%;
+ }
+}
+
+.sidebar-chat-message-skeletonline {
+ border-radius: $rounding_verysmall;
+ min-height: 1.364rem;
+ background-color: $layer2;
+}
+
+.sidebar-chat-message-skeletonline-offset0 {
+ background-repeat: no-repeat;
+ background: linear-gradient(to right, $layer3 0%, $skeleton-accent 25%, $layer3 50%, $layer3 100%);
+ background-size: 500% 500%;
+ animation: sidebar-chat-message-skeletonline-anim 2s linear;
+ animation-iteration-count: infinite;
+}
+
+.sidebar-chat-message-skeletonline-offset1 {
+ background-repeat: no-repeat;
+ background: linear-gradient(to right, $layer3 0%, $layer3 50%, $skeleton-accent 75%, $layer3 100%);
+ background-size: 500% 500%;
+ animation: sidebar-chat-message-skeletonline-anim 2s linear;
+ animation-iteration-count: infinite;
+}
+
+.sidebar-chat-message-skeletonline-offset2 {
+ margin-right: 5.795rem;
+ background-repeat: no-repeat;
+ background: linear-gradient(to right, $layer3 0%, $layer3 25%, $skeleton-accent 50%, $layer3 75%, $layer3 100%);
+ background-size: 500% 500%;
+ animation: sidebar-chat-message-skeletonline-anim 2s linear;
+ animation-iteration-count: infinite;
+}
+
+.sidebar-chat-indicator {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 0.136rem;
+}
+
+.sidebar-chat-indicator-waifu {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 0.136rem;
+ background-color: $onBackground;
+}
+
+.sidebar-chat-name {
+ @include titlefont;
+ @include small-rounding;
+ padding: 0.341rem 0.818rem;
+ margin: 0.341rem;
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-chat-name-user {
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-chat-name-bot {
+ background-color: $secondary;
+ color: $onSecondary;
+}
+
+.sidebar-chat-name-system {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-chat-txtblock {
+ margin-left: -0.136rem;
+ padding: 0.341rem;
+ padding-left: 0.818rem;
+}
+
+.sidebar-chat-txt {
+ @include readingfont;
+}
+
+.sidebar-chat-latex {
+ @include small-rounding;
+ margin: 0rem 0.682rem;
+ padding: 0.682rem;
+ color: $onBackground;
+}
+
+.sidebar-chat-codeblock {
+ @include normal-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+ margin: 0rem 0.682rem;
+}
+
+.sidebar-chat-codeblock-topbar {
+ @include mainfont;
+ background-color: $layer3;
+ color: $onLayer3;
+ border-top-left-radius: $rounding_small;
+ border-top-right-radius: $rounding_small;
+ padding: 0.341rem 0.477rem;
+}
+
+.sidebar-chat-codeblock-topbar-txt {
+ @include full-rounding;
+ padding: 0.273rem;
+}
+
+.sidebar-chat-codeblock-topbar-btn {
+ @include full-rounding;
+ @include element_decel;
+ padding: 0.273rem 0.477rem;
+}
+
+.sidebar-chat-codeblock-topbar-btn:hover,
+.sidebar-chat-codeblock-topbar-btn:focus {
+ background-color: $surfaceBright;
+}
+
+.sidebar-chat-codeblock-topbar-btn:active {
+ background-color: $surfaceVariant;
+}
+
+.sidebar-chat-codeblock-code {
+ @include techfont;
+ padding: 0.682rem;
+}
+
+.sidebar-chat-divider {
+ min-height: 1px;
+ background-color: $sidebar_chat_textboxareaColor;
+ margin: 0rem 0.545rem;
+}
+
+.sidebar-chat-welcome-txt {
+ margin: 0rem 3.409rem;
+}
+
+.sidebar-chat-settings-toggles {
+ margin: 0rem 5.455rem;
+}
+
+.sidebar-chat-welcome-icon {
+ @include full-rounding;
+ font-size: 4rem;
+}
+
+.sidebar-chat-welcome-logo {
+ @include full-rounding;
+ @include element_decel;
+ @include icon-material;
+ min-height: 4.773rem;
+ min-width: 4.773rem;
+ font-size: 3.076rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-chat-chip {
+ @include element_decel;
+ @include small-rounding;
+ padding: 0.341rem 0.477rem;
+}
+
+.sidebar-chat-chip-action {
+ @include element_decel;
+ background-color: $layer2;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-chat-chip-action:hover,
+.sidebar-chat-chip-action:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-chat-chip-action:active {
+ background-color: $activecolor;
+}
+
+.sidebar-chat-chip-action-active {
+ color: $sidebar_chat_textboxareaColor;
+ border: 0.068rem solid $sidebar_chat_textboxareaColor;
+}
+
+.sidebar-chat-chip-toggle {
+ @include element_decel;
+ @include small-rounding;
+ padding: 0.341rem 0.477rem;
+ background-color: $layer3;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-chat-chip-toggle:focus,
+.sidebar-chat-chip-toggle:hover {
+ background-color: $hovercolor;
+}
+
+.sidebar-chat-chip-toggle:active {
+ background-color: $activecolor;
+}
+
+.sidebar-pin {
+ @include small-rounding;
+ @include element_decel;
+ min-height: 2.386rem;
+ min-width: 2.386rem;
+ color: $onSurface;
+}
+
+.sidebar-pin:hover,
+.sidebar-pin:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-pin:active {
+ background-color: $activecolor;
+}
+
+.sidebar-pin-enabled {
+ background-color: $primary;
+
+ label {
+ color: $onPrimary;
+ }
+}
+
+.sidebar-pin-enabled:hover,
+.sidebar-pin-enabled:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-pin-enabled:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-waifu-heading {
+ @include titlefont;
+ padding: 0.341rem;
+ margin-left: -0.136rem;
+ padding-left: 0.818rem;
+}
+
+.sidebar-waifu-txt {
+ @include mainfont;
+}
+
+.sidebar-waifu-image {
+ @include normal-rounding;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.sidebar-waifu-image-actions {
+ @include element_decel;
+ padding: 0.313rem;
+}
+
+$waifu_image_overlay_transparency: 0.7;
+
+.sidebar-waifu-image-action {
+ @include full-rounding;
+ min-width: 1.875rem;
+ min-height: 1.875rem;
+ background-color: rgba(0, 0, 0, $waifu_image_overlay_transparency ); // Fixed cuz on image
+ color: rgba(255, 255, 255, $waifu_image_overlay_transparency);
+}
+
+.sidebar-waifu-image-action:hover,
+.sidebar-waifu-image-action:focus {
+ background-color: rgba(30, 30, 30, $waifu_image_overlay_transparency);
+}
+
+.sidebar-waifu-image-action:active {
+ background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency);
+}
+
+.sidebar-booru-provider {
+ @include titlefont;
+ @include small-rounding;
+ padding: 0.341rem 0.818rem;
+ margin: 0.341rem;
+ font-weight: bold;
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-booru-imagegrid {
+ @include normal-rounding;
+}
+
+.sidebar-booru-image {
+ @include small-rounding;
+ margin: 0.273rem;
+ min-width: 11.932rem;
+ // background-color: rgba(100, 200, 0, 0.3);
+}
+
+.sidebar-booru-image-drawingarea {
+ // background-color: rgba(200, 100, 0, 0.3);
+ @include small-rounding;
+ min-width: 12.273rem;
+ min-height: 12.273rem;
+}
+
+.sidebar-booru-image-actions {
+ // background-color: rgba(100, 100, 0, 0.3);
+ @include element_decel;
+ margin: 0.545rem;
+}
+
+.sidebar-volmixer-stream {
+ border-bottom: 0.068rem solid $outlineVariant;
+ padding: 0.682rem;
+ color: $onSurface;
+}
+
+.sidebar-volmixer-stream-appicon {
+ font-size: 3.273rem;
+}
+
+.sidebar-volmixer-stream-slider {
+ trough {
+ border-radius: $rounding_verysmall;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ background-color: $secondaryContainer;
+ }
+
+ highlight {
+ border-radius: $rounding_verysmall;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ background-color: $primary;
+ }
+
+ slider {
+ border-radius: $rounding_verysmall;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ }
+}
+
+.sidebar-volmixer-status {
+ color: $onSurface;
+ margin: 0rem 0.682rem;
+}
+
+.sidebar-volmixer-deviceselector {
+ @include small-rounding;
+ padding: 0.477rem 0.682rem;
+ background-color: $surfaceContainerHigh;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-bluetooth-device {
+ padding: 0.682rem;
+ @include normal-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-bluetooth-appicon {
+ @include symbolic-icon;
+ font-size: 2.045rem;
+}
+
+.sidebar-bluetooth-device-remove {
+ @include full-rounding;
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ // background-color: $layer3;
+ padding: 0.341rem;
+}
+
+.sidebar-bluetooth-device-remove:hover,
+.sidebar-bluetooth-device-remove:focus {
+ @include full-rounding;
+ background-color: $layer2Hover;
+ padding: 0.341rem;
+}
+
+.sidebar-wifinetworks-network {
+ padding: 0.682rem;
+ @include normal-rounding;
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-wifinetworks-network:hover,
+.sidebar-wifinetworks-network:focus {
+ background-color: $layer2Hover;
+}
+
+.sidebar-wifinetworks-network:active {
+ background-color: $layer2Active;
+}
+
+.sidebar-wifinetworks-signal {
+ @include symbolic-icon;
+ font-size: 2.045rem;
+}
+
+.sidebar-wifinetworks-auth-entry {
+ @include small-rounding;
+ background-color: $layer1;
+ color: $onLayer1;
+ padding: 0.682rem;
+}
+
+.sidebar-centermodules-bottombar-button {
+ @include full-rounding;
+ @include element_decel;
+ min-width: 6.818rem;
+ min-height: 2.25rem;
+ background-color: $layer2;
+ color: $onLayer2;
+}
+
+.sidebar-centermodules-bottombar-button:hover,
+.sidebar-centermodules-bottombar-button:focus {
+ background-color: $layer2Hover;
+}
+
+.sidebar-centermodules-bottombar-button:active {
+ background-color: $layer2Active;
+}
+
+.sidebar-centermodules-scrollgradient-bottom {
+ background: linear-gradient(to top, $layer1 0%, transparentize($layer1, 1) 1.023rem);
+}
\ No newline at end of file
diff --git a/ags/scss/_wal.scss b/ags/scss/_wal.scss
new file mode 100644
index 00000000..50ec8e5a
--- /dev/null
+++ b/ags/scss/_wal.scss
@@ -0,0 +1,8 @@
+// To prevent errors
+$color1: $surfaceVariant;
+$color2: $surfaceVariant;
+$color3: $surfaceVariant;
+$color4: $secondaryContainer;
+$color5: $secondaryContainer;
+$color6: $secondaryContainer;
+$color7: $onBackground;
\ No newline at end of file
diff --git a/ags/scss/fallback/_material.scss b/ags/scss/fallback/_material.scss
new file mode 100644
index 00000000..7f6294ab
--- /dev/null
+++ b/ags/scss/fallback/_material.scss
@@ -0,0 +1,76 @@
+$darkmode: True;
+$transparent: False;
+$primary_paletteKeyColor: #02DCFF;
+$secondary_paletteKeyColor: #597B8F;
+$tertiary_paletteKeyColor: #557AA1;
+$neutral_paletteKeyColor: #6D797D;
+$neutral_variant_paletteKeyColor: #6A7A7F;
+$background: #091518;
+$onBackground: #D8E5E9;
+$surface: #091518;
+$surfaceDim: #091518;
+$surfaceBright: #2F3B3F;
+$surfaceContainerLowest: #051013;
+$surfaceContainerLow: #121D21;
+$surfaceContainer: #162125;
+$surfaceContainerHigh: #202C2F;
+$surfaceContainerHighest: #2B373A;
+$onSurface: #D8E5E9;
+$surfaceVariant: #3A494E;
+$onSurfaceVariant: #B9C9CE;
+$inverseSurface: #D8E5E9;
+$inverseOnSurface: #273236;
+$outline: #839398;
+$outlineVariant: #3A494E;
+$shadow: #000000;
+$scrim: #000000;
+$surfaceTint: #00D9FC;
+$primary: #00D9FC;
+$onPrimary: #003640;
+$primaryContainer: #004E5C;
+$onPrimaryContainer: #AAEDFF;
+$inversePrimary: #006879;
+$secondary: #A8CBE2;
+$onSecondary: #0D3446;
+$secondaryContainer: #2A4D60;
+$onSecondaryContainer: #C7E9FF;
+$tertiary: #A4CAF5;
+$onTertiary: #003256;
+$tertiaryContainer: #6F94BC;
+$onTertiaryContainer: #000000;
+$error: #FFB4AB;
+$onError: #690005;
+$errorContainer: #93000A;
+$onErrorContainer: #FFDAD6;
+$primaryFixed: #AAEDFF;
+$primaryFixedDim: #00D9FC;
+$onPrimaryFixed: #001F26;
+$onPrimaryFixedVariant: #004E5C;
+$secondaryFixed: #C4E7FF;
+$secondaryFixedDim: #A8CBE2;
+$onSecondaryFixed: #001E2C;
+$onSecondaryFixedVariant: #284B5D;
+$tertiaryFixed: #D0E4FF;
+$tertiaryFixedDim: #A4CAF5;
+$onTertiaryFixed: #001D34;
+$onTertiaryFixedVariant: #21496E;
+$success: #B5CCBA;
+$onSuccess: #213528;
+$successContainer: #374B3E;
+$onSuccessContainer: #D1E9D6;
+$term0: #0E1C21;
+$term1: #8383FF;
+$term2: #63DEDB;
+$term3: #75FCDD;
+$term4: #78B4C1;
+$term5: #7AAEEA;
+$term6: #81D7DE;
+$term7: #CCDBD5;
+$term8: #B1BCB5;
+$term9: #BCB9FF;
+$term10: #F4FFFE;
+$term11: #FFFFFF;
+$term12: #BEE3E8;
+$term13: #C8DAFF;
+$term14: #EAFEFF;
+$term15: #B0ECFC;
diff --git a/ags/scss/main.scss b/ags/scss/main.scss
new file mode 100644
index 00000000..57938e81
--- /dev/null
+++ b/ags/scss/main.scss
@@ -0,0 +1,42 @@
+// Reset
+// * {
+// all: unset;
+// }
+*:not(popover) { all: unset; }
+
+// Colors
+@import 'material'; // Material colors
+@import './colors'; // Global color definitions. Uses material colors as base.
+@import './lib_mixins';
+@import 'lib_mixins_overrides';
+@import './lib_classes';
+@import './common'; // Context menu n stuff
+
+// Components
+@import './bar';
+@import './cheatsheet';
+@import './desktopbackground';
+@import './dock';
+@import './osd';
+@import './overview';
+@import './osk';
+@import './sidebars';
+@import './session';
+@import './notifications';
+
+// Music is put last as it might mess stuff up with pywal
+@import './music'; // Everything related to music is here
+
+// Classes for interaction
+.growingRadial {
+ transition: 300ms cubic-bezier(0.2, 0.0, 0, 1.0);
+}
+.fadingRadial {
+ transition: 50ms cubic-bezier(0.2, 0.0, 0, 1.0);
+}
+.sidebar-pinned {
+ margin: 0rem;
+ border-radius: 0rem;
+ border-bottom-right-radius: $rounding_large;
+ border: 0rem solid;
+}
diff --git a/ags/services/booru.js b/ags/services/booru.js
new file mode 100644
index 00000000..e620d8ce
--- /dev/null
+++ b/ags/services/booru.js
@@ -0,0 +1,156 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const APISERVICES = {
+ 'yandere': {
+ name: 'yande.re',
+ endpoint: 'https://yande.re/post.json',
+ },
+ 'konachan': {
+ name: 'Konachan',
+ endpoint: 'https://konachan.net/post.json',
+ },
+}
+
+const getWorkingImageSauce = (url) => {
+ if (url.includes('pximg.net')) {
+ return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`;
+ }
+ return url;
+}
+
+function paramStringFromObj(params) {
+ return Object.entries(params)
+ .map(([key, value]) => {
+ if (Array.isArray(value)) { // If it's an array, repeat
+ if (value.length == 0) return '';
+ let thisKey = `${encodeURIComponent(key)}=${encodeURIComponent(value[0])}`
+ for (let i = 1; i < value.length; i++) {
+ thisKey += `&${encodeURIComponent(key)}=${encodeURIComponent(value[i])}`;
+ }
+ return thisKey;
+ }
+ return `${key}=${value}`;
+ })
+ .join('&');
+}
+
+class BooruService extends Service {
+ _baseUrl = 'https://yande.re/post.json';
+ _mode = 'yandere';
+ _nsfw = userOptions.sidebar.image.allowNsfw;
+ _responses = [];
+ _queries = [];
+
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newResponse': ['int'],
+ 'updateResponse': ['int'],
+ }, {
+ 'nsfw': ['boolean'],
+ });
+ }
+
+ constructor() {
+ super();
+ this.emit('initialized');
+ }
+
+ clear() {
+ this._responses = [];
+ this._queries = [];
+ this.emit('clear');
+ }
+
+ get nsfw() { return this._nsfw }
+ set nsfw(value) { this._nsfw = value; this.notify('nsfw'); }
+
+ get mode() { return this._mode }
+ set mode(value) {
+ this._mode = value;
+ this._baseUrl = APISERVICES[this._mode].endpoint;
+ }
+ get providerName() {
+ return APISERVICES[this._mode].name;
+ }
+ get queries() { return this._queries }
+ get responses() { return this._responses }
+
+ async fetch(msg) {
+ // Init
+ const userArgs = `${msg}${(!this._nsfw || msg.includes('safe')) ? ' rating:safe' : ''}`.split(/\s+/);
+ console.log(userArgs)
+
+ let taglist = [];
+ let page = 1;
+ // Construct body/headers
+ for (let i = 0; i < userArgs.length; i++) {
+ const thisArg = userArgs[i].trim();
+ if (thisArg.length == 0 || thisArg == '.' || thisArg.includes('*')) continue;
+ else if (!isNaN(thisArg)) page = parseInt(thisArg);
+ else taglist.push(thisArg);
+ }
+ const newMessageId = this._queries.length;
+ this._queries.push({
+ providerName: APISERVICES[this._mode].name,
+ taglist: taglist.length == 0 ? ['*', `${page}`] : [...taglist, `${page}`],
+ realTagList: taglist,
+ page: page,
+ });
+ this.emit('newResponse', newMessageId);
+ const params = {
+ 'tags': taglist.join('+'),
+ 'page': `${page}`,
+ 'limit': `${userOptions.sidebar.image.batchCount}`,
+ };
+ const paramString = paramStringFromObj(params);
+ // Fetch
+ // Note: body isn't included since passing directly to url is more reliable
+ const options = {
+ method: 'GET',
+ headers: APISERVICES[this._mode].headers,
+ };
+ let status = 0;
+ // console.log(`${APISERVICES[this._mode].endpoint}?${paramString}`);
+
+ Utils.fetch(`${APISERVICES[this._mode].endpoint}?${paramString}`, options)
+ .then(result => {
+ status = result.status;
+ return result.text();
+ })
+ .then((dataString) => { // Store interesting stuff and emit
+ // console.log(dataString);
+ const parsedData = JSON.parse(dataString);
+ // console.log(parsedData)
+ this._responses[newMessageId] = parsedData.map(obj => {
+ return {
+ aspect_ratio: obj.width / obj.height,
+ id: obj.id,
+ tags: obj.tags,
+ rating: obj.rating,
+ is_nsfw: (obj.rating != 's'),
+ md5: obj.md5,
+ preview_url: obj.preview_url,
+ preview_width: obj.preview_width,
+ preview_height: obj.preview_height,
+ sample_url: obj.sample_url,
+ sample_width: obj.sample_width,
+ sample_height: obj.sample_height,
+ file_url: obj.file_url,
+ file_ext: obj.file_ext,
+ file_width: obj.file_width,
+ file_height: obj.file_height,
+ source: getWorkingImageSauce(obj.source),
+ }
+ });
+ this.emit('updateResponse', newMessageId);
+ })
+ .catch(print);
+
+ }
+}
+
+export default new BooruService();
+
diff --git a/ags/services/brightness.js b/ags/services/brightness.js
new file mode 100644
index 00000000..b76ef755
--- /dev/null
+++ b/ags/services/brightness.js
@@ -0,0 +1,143 @@
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+import { clamp } from '../modules/.miscutils/mathfuncs.js';
+
+class BrightnessServiceBase extends Service {
+ static {
+ Service.register(
+ this,
+ { 'screen-changed': ['float'], },
+ { 'screen-value': ['float', 'rw'], },
+ );
+ }
+
+ _screenValue = 0;
+
+ // the getter has to be in snake_case
+ get screen_value() { return this._screenValue; }
+
+ // the setter has to be in snake_case too
+ set screen_value(percent) {
+ percent = clamp(percent, 0, 1);
+ this._screenValue = percent;
+
+ Utils.execAsync(this.setBrightnessCmd(percent))
+ .then(() => {
+ // signals has to be explicity emitted
+ this.emit('screen-changed', percent);
+ this.notify('screen-value');
+
+ // or use Service.changed(propName: string) which does the above two
+ // this.changed('screen');
+ })
+ .catch(print);
+ }
+
+ // overwriting connectWidget method, lets you
+ // change the default event that widgets connect to
+ connectWidget(widget, callback, event = 'screen-changed') {
+ super.connectWidget(widget, callback, event);
+ }
+}
+
+class BrightnessCtlService extends BrightnessServiceBase {
+ static {
+ Service.register(this);
+ }
+
+ constructor() {
+ super();
+ const current = Number(exec('brightnessctl g'));
+ const max = Number(exec('brightnessctl m'));
+ this._screenValue = current / max;
+ }
+
+ setBrightnessCmd(percent) {
+ return `brightnessctl s ${percent * 100}% -q`;
+ }
+}
+
+class BrightnessDdcService extends BrightnessServiceBase {
+ static {
+ Service.register(this);
+ }
+
+ constructor(busNum) {
+ super();
+ this._busNum = busNum;
+ Utils.execAsync(`ddcutil -b ${this._busNum} getvcp 10 --brief`)
+ .then((out) => {
+ // only the last line is useful
+ out = out.split('\n');
+ out = out[out.length - 1];
+
+ out = out.split(' ');
+ const current = Number(out[3]);
+ const max = Number(out[4]);
+ this._screenValue = current / max;
+ })
+ .catch(print);
+ }
+
+ setBrightnessCmd(percent) {
+ return `ddcutil -b ${this._busNum} setvcp 10 ${Math.round(percent * 100)}`;
+ }
+}
+
+async function listDdcMonitorsSnBus() {
+ let ddcSnBus = {};
+ try {
+ const out = await Utils.execAsync('ddcutil detect --brief');
+ const displays = out.split('\n\n');
+ displays.forEach(display => {
+ const reg = /^Display \d+/;
+ if (!reg.test(display))
+ return;
+ const lines = display.split('\n');
+ const sn = lines[3].split(':')[3];
+ const busNum = lines[1].split('/dev/i2c-')[1];
+ ddcSnBus[sn] = busNum;
+ });
+ } catch (err) {
+ print(err);
+ }
+ return ddcSnBus;
+}
+
+// Service instance
+const numMonitors = Hyprland.monitors.length;
+const service = Array(numMonitors);
+const ddcSnBus = await listDdcMonitorsSnBus();
+for (let i = 0; i < service.length; i++) {
+ const monitorName = Hyprland.monitors[i].name;
+ const monitorSn = Hyprland.monitors[i].serial;
+ const preferredController = userOptions.brightness.controllers[monitorName]
+ || userOptions.brightness.controllers.default || "auto";
+ if (preferredController) {
+ switch (preferredController) {
+ case "brightnessctl":
+ service[i] = new BrightnessCtlService();
+ break;
+ case "ddcutil":
+ service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]);
+ break;
+ case "auto":
+ if (monitorSn in ddcSnBus && !!exec(`bash -c 'command -v ddcutil'`))
+ service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]);
+ else
+ service[i] = new BrightnessCtlService();
+ break;
+ default:
+ throw new Error(`Unknown brightness controller ${preferredController}`);
+ }
+ }
+}
+
+// make it global for easy use with cli
+globalThis.brightness = service[0];
+
+// export to use in other modules
+export default service;
diff --git a/ags/services/darkmode.js b/ags/services/darkmode.js
new file mode 100644
index 00000000..b0f54be3
--- /dev/null
+++ b/ags/services/darkmode.js
@@ -0,0 +1,39 @@
+const { Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { darkMode } from '../modules/.miscutils/system.js';
+const { exec, execAsync } = Utils;
+
+const timeBefore = (time1, time2) => { // Arrays of [hour, minute]
+ if (time1[0] == time2[0]) return time1[1] < time2[1];
+ return time1[0] < time2[0];
+}
+
+const timeSame = (time1, time2) => // Arrays of [hour, minute]
+ (time1[0] == time2[0] && time1[1] == time2[1]);
+
+const timeBeforeOrSame = (time1, time2) => // Arrays of [hour, minute]
+ (timeBefore(time1, time2) || timeSame(time1, time2));
+
+const timeInRange = (time, rangeStart, rangeEnd) => { // Arrays of [hour, minute]
+ if (timeBefore(rangeStart, rangeEnd))
+ return (timeBeforeOrSame(rangeStart, time) && timeBeforeOrSame(time, rangeEnd))
+ else { // rangeEnd < rangeStart, meaning it ends the following day
+ rangeEnd[0] += 24;
+ if (timeBefore(time, rangeStart)) time[0] += 24;
+ return (timeBeforeOrSame(rangeStart, time) && timeBeforeOrSame(time, rangeEnd))
+ }
+
+}
+
+export async function startAutoDarkModeService() {
+ Utils.interval(userOptions.time.interval, () => {
+ if ((!userOptions.appearance.autoDarkMode.enabled)) return;
+ const fromTime = (userOptions.appearance.autoDarkMode.from).split(':').map(Number);
+ const toTime = (userOptions.appearance.autoDarkMode.to).split(':').map(Number);
+ if (fromTime == toTime) return;
+ const currentDateTime = GLib.DateTime.new_now_local();
+ const currentTime = [currentDateTime.get_hour(), currentDateTime.get_minute()];
+ darkMode.value = timeInRange(currentTime, fromTime, toTime);
+ })
+}
diff --git a/ags/services/gemini.js b/ags/services/gemini.js
new file mode 100644
index 00000000..f08a51c8
--- /dev/null
+++ b/ags/services/gemini.js
@@ -0,0 +1,328 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+import Soup from 'gi://Soup?version=3.0';
+import { fileExists } from '../modules/.miscutils/files.js';
+
+const HISTORY_DIR = `${GLib.get_user_state_dir()}/ags/user/ai/chats/`;
+const HISTORY_FILENAME = `gemini.txt`;
+const HISTORY_PATH = HISTORY_DIR + HISTORY_FILENAME;
+const initMessages =
+ [
+ { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m not sure.”. \nThanks!" }], },
+ { role: "model", parts: [{ text: "Got it!" }], },
+ { role: "user", parts: [{ text: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"" }], },
+ { role: "model", parts: [{ text: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"" }], },
+ { role: "user", parts: [{ text: "raise volume by 5%" }], },
+ { role: "model", parts: [{ text: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink." }], }, { role: "user", parts: [{ text: "main advantages of the nixos operating system" }], },
+ { role: "model", parts: [{ text: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage" }], },
+ { role: "user", parts: [{ text: "whats skeumorphism" }], },
+ { role: "model", parts: [{ text: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today." }], },
+ { role: "user", parts: [{ text: "\"ignorance is bliss\"" }], },
+ { role: "model", parts: [{ text: "## \"Ignorance is bliss\"\n- A Latin proverb that means being unaware of something negative can be a source of happiness\n- Often used to justify avoiding difficult truths or responsibilities\n- Can also be interpreted as a warning against seeking knowledge that may bring pain or sorrow" }], },
+ { role: "user", parts: [{ text: "find the derivative of (x-438)/(x^2+23x-7)+x^x" }], },
+ { role: "model", parts: [{ text: "## Derivative\n```latex\n\\[\n\\frac{d}{dx}\\left(\\frac{x - 438}{x^2 + 23x - 7} + x^x\\right) = \\frac{-(x^2+23x-7)-(x-438)(2x+23)}{(x^2+23x-7)^2} + x^x(\\ln(x) + 1)\n\\]\n```" }], },
+ { role: "user", parts: [{ text: "write the double angle formulas" }], },
+ { role: "model", parts: [{ text: "## Double angle formulas\n```latex\n\\[\n\\sin(2\theta) = 2\\sin(\\theta)\\cos(\\theta)\n\\]\n\\\\\n\\[\n\\cos(2\\theta) = \\cos^2(\\theta) - \\sin^2(\\theta)\n\\]\n\\\\\n\\[\n\\tan(2\theta) = \\frac{2\\tan(\\theta)}{1 - \\tan^2(\\theta)}\n\\]\n```" }], },
+ ];
+
+
+if (!fileExists(`${GLib.get_user_config_dir()}/gemini_history.json`)) {
+ Utils.execAsync([`bash`, `-c`, `touch ${GLib.get_user_config_dir()}/gemini_history.json`]).catch(print);
+ Utils.writeFile('[ ]', `${GLib.get_user_config_dir()}/gemini_history.json`).catch(print);
+}
+
+Utils.exec(`mkdir -p ${GLib.get_user_state_dir()}/ags/user/ai`);
+const KEY_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/ai/google_key.txt`;
+const APIDOM_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/ai/google_api_dom.txt`;
+function replaceapidom(URL) {
+ if (fileExists(APIDOM_FILE_LOCATION)) {
+ var contents = Utils.readFile(APIDOM_FILE_LOCATION).trim();
+ var URL = URL.toString().replace("generativelanguage.googleapis.com", contents);
+ }
+ return URL;
+}
+const CHAT_MODELS = ["gemini-1.5-flash"]
+const ONE_CYCLE_COUNT = 3;
+
+class GeminiMessage extends Service {
+ static {
+ Service.register(this,
+ {
+ 'delta': ['string'],
+ },
+ {
+ 'content': ['string'],
+ 'thinking': ['boolean'],
+ 'done': ['boolean'],
+ });
+ }
+
+ _role = '';
+ _parts = [{ text: '' }];
+ _thinking;
+ _done = false;
+ _rawData = '';
+
+ constructor(role, content, thinking = true, done = false) {
+ super();
+ this._role = role;
+ this._parts = [{ text: content }];
+ this._thinking = thinking;
+ this._done = done;
+ }
+
+ get rawData() { return this._rawData }
+ set rawData(value) { this._rawData = value }
+
+ get done() { return this._done }
+ set done(isDone) { this._done = isDone; this.notify('done') }
+
+ get role() { return this._role }
+ set role(role) { this._role = role; this.emit('changed') }
+
+ get content() {
+ return this._parts.map(part => part.text).join();
+ }
+ set content(content) {
+ this._parts = [{ text: content }];
+ this.notify('content')
+ this.emit('changed')
+ }
+
+ get parts() { return this._parts }
+
+ get label() { return this._parserState.parsed + this._parserState.stack.join('') }
+
+ get thinking() { return this._thinking }
+ set thinking(value) {
+ this._thinking = value;
+ this.notify('thinking')
+ this.emit('changed')
+ }
+
+ addDelta(delta) {
+ if (this.thinking) {
+ this.thinking = false;
+ this.content = delta;
+ }
+ else {
+ this.content += delta;
+ }
+ this.emit('delta', delta);
+ }
+
+ parseSection() {
+ if (this._thinking) {
+ this.thinking = false;
+ this._parts[0].text = '';
+ }
+ const parsedData = JSON.parse(this._rawData);
+ if (!parsedData.candidates)
+ this._parts[0].text += `Blocked: ${parsedData.promptFeedback.blockReason}`;
+ else {
+ const delta = parsedData.candidates[0].content.parts[0].text;
+ this._parts[0].text += delta;
+ }
+ // this.emit('delta', delta);
+ this.notify('content');
+ this._rawData = '';
+ }
+}
+
+class GeminiService extends Service {
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newMsg': ['int'],
+ 'hasKey': ['boolean'],
+ });
+ }
+
+ _assistantPrompt = userOptions.ai.enhancements;
+ _cycleModels = true;
+ _usingHistory = userOptions.ai.useHistory;
+ _key = '';
+ _requestCount = 0;
+ _safe = userOptions.ai.safety;
+ _temperature = userOptions.ai.defaultTemperature;
+ _messages = [];
+ _modelIndex = 0;
+ _decoder = new TextDecoder();
+
+ constructor() {
+ super();
+
+ if (fileExists(KEY_FILE_LOCATION)) this._key = Utils.readFile(KEY_FILE_LOCATION).trim();
+ else this.emit('hasKey', false);
+
+ // if (this._usingHistory) Utils.timeout(1000, () => this.loadHistory());
+ if (this._usingHistory) this.loadHistory();
+ else this._messages = this._assistantPrompt ? [...initMessages] : [];
+
+ this.emit('initialized');
+ }
+
+ get modelName() { return CHAT_MODELS[this._modelIndex] }
+
+ get keyPath() { return KEY_FILE_LOCATION }
+ get key() { return this._key }
+ set key(keyValue) {
+ this._key = keyValue;
+ Utils.writeFile(this._key, KEY_FILE_LOCATION)
+ .then(this.emit('hasKey', true))
+ .catch(print);
+ }
+
+ get cycleModels() { return this._cycleModels }
+ set cycleModels(value) {
+ this._cycleModels = value;
+ if (!value) this._modelIndex = 0;
+ else {
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+
+ get useHistory() { return this._usingHistory; }
+ set useHistory(value) {
+ if (value && !this._usingHistory) this.loadHistory();
+ this._usingHistory = value;
+ }
+
+ get safe() { return this._safe }
+ set safe(value) { this._safe = value; }
+
+ get temperature() { return this._temperature }
+ set temperature(value) { this._temperature = value; }
+
+ get messages() { return this._messages }
+ get lastMessage() { return this._messages[this._messages.length - 1] }
+
+ saveHistory() {
+ Utils.exec(`bash -c 'mkdir -p ${HISTORY_DIR} && touch ${HISTORY_PATH}'`);
+ Utils.writeFile(JSON.stringify(this._messages.map(msg => {
+ let m = { role: msg.role, parts: msg.parts }; return m;
+ })), HISTORY_PATH);
+ }
+
+ loadHistory() {
+ this._messages = [];
+ this.appendHistory();
+ this._usingHistory = true;
+ }
+
+ appendHistory() {
+ if (fileExists(HISTORY_PATH)) {
+ const readfile = Utils.readFile(HISTORY_PATH);
+ JSON.parse(readfile).forEach(element => {
+ // this._messages.push(element);
+ this.addMessage(element.role, element.parts[0].text);
+ });
+ // console.log(this._messages)
+ // this._messages = this._messages.concat(JSON.parse(readfile));
+ // for (let index = 0; index < this._messages.length; index++) {
+ // this.emit('newMsg', index);
+ // }
+ }
+ else {
+ this._messages = this._assistantPrompt ? [...initMessages] : []
+ }
+ }
+
+ clear() {
+ this._messages = this._assistantPrompt ? [...initMessages] : [];
+ if (this._usingHistory) this.saveHistory();
+ this.emit('clear');
+ }
+
+ get assistantPrompt() { return this._assistantPrompt; }
+ set assistantPrompt(value) {
+ this._assistantPrompt = value;
+ if (value) this._messages = [...initMessages];
+ else this._messages = [];
+ }
+
+ readResponse(stream, aiResponse) {
+ stream.read_line_async(
+ 0, null,
+ (stream, res) => {
+ try {
+ const [bytes] = stream.read_line_finish(res);
+ const line = this._decoder.decode(bytes);
+ // console.log(line);
+ if (line == '[{') { // beginning of response
+ aiResponse._rawData += '{';
+ this.thinking = false;
+ }
+ else if (line == ',\u000d' || line == ']') { // end of stream pulse
+ aiResponse.parseSection();
+ }
+ else // Normal content
+ aiResponse._rawData += line;
+
+ this.readResponse(stream, aiResponse);
+ } catch {
+ aiResponse.done = true;
+ if (this._usingHistory) this.saveHistory();
+ return;
+ }
+ });
+ }
+
+ addMessage(role, message) {
+ this._messages.push(new GeminiMessage(role, message, false));
+ this.emit('newMsg', this._messages.length - 1);
+ }
+
+ send(msg) {
+ this._messages.push(new GeminiMessage('user', msg, false));
+ this.emit('newMsg', this._messages.length - 1);
+ const aiResponse = new GeminiMessage('model', 'thinking...', true, false)
+
+ const body =
+ {
+ "contents": this._messages.map(msg => { let m = { role: msg.role, parts: msg.parts }; return m; }),
+ "safetySettings": this._safe ? [] : [
+ // { category: "HARM_CATEGORY_DEROGATORY", threshold: "BLOCK_NONE", },
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE", },
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE", },
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE", },
+ // { category: "HARM_CATEGORY_UNSPECIFIED", threshold: "BLOCK_NONE", },
+ ],
+ "generationConfig": {
+ "temperature": this._temperature,
+ },
+ // "key": this._key,
+ // "apiKey": this._key,
+ };
+ const proxyResolver = new Gio.SimpleProxyResolver({ 'default-proxy': userOptions.ai.proxyUrl });
+ const session = new Soup.Session({ 'proxy-resolver': proxyResolver });
+ const message = new Soup.Message({
+ method: 'POST',
+ uri: GLib.Uri.parse(replaceapidom(`https://generativelanguage.googleapis.com/v1/models/${this.modelName}:streamGenerateContent?key=${this._key}`), GLib.UriFlags.NONE),
+ });
+ message.request_headers.append('Content-Type', `application/json`);
+ message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
+
+ session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
+ const stream = session.send_finish(result);
+ this.readResponse(new Gio.DataInputStream({
+ close_base_stream: true,
+ base_stream: stream
+ }), aiResponse);
+ });
+ this._messages.push(aiResponse);
+ this.emit('newMsg', this._messages.length - 1);
+
+ if (this._cycleModels) {
+ this._requestCount++;
+ if (this._cycleModels)
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+}
+
+export default new GeminiService();
diff --git a/ags/services/gpt.js b/ags/services/gpt.js
new file mode 100644
index 00000000..ffd6ce3d
--- /dev/null
+++ b/ags/services/gpt.js
@@ -0,0 +1,292 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+import Soup from 'gi://Soup?version=3.0';
+import { fileExists } from '../modules/.miscutils/files.js';
+
+const PROVIDERS = Object.assign({ // There's this list hmm https://github.com/zukixa/cool-ai-stuff/
+ 'openai': {
+ 'name': 'OpenAI',
+ 'logo_name': 'openai-symbolic',
+ 'description': 'Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.',
+ 'base_url': 'https://api.openai.com/v1/chat/completions',
+ 'key_get_url': 'https://platform.openai.com/api-keys',
+ 'key_file': 'openai_key.txt',
+ 'model': 'gpt-3.5-turbo',
+ },
+ 'ollama': {
+ 'name': 'Ollama (Llama 3)',
+ 'logo_name': 'ollama-symbolic',
+ 'description': 'Official Ollama API.\nPricing: Free.',
+ 'base_url': 'http://localhost:11434/v1/chat/completions',
+ 'key_get_url': 'it\'s just ollama',
+ 'key_file': 'ollama_key.txt',
+ 'model': 'llama3:instruct',
+ },
+ 'openrouter': {
+ 'name': 'OpenRouter (Llama-3-70B)',
+ 'logo_name': 'openrouter-symbolic',
+ 'description': 'A unified interface for LLMs',
+ 'base_url': 'https://openrouter.ai/api/v1/chat/completions',
+ 'key_get_url': 'https://openrouter.ai/keys',
+ 'key_file': 'openrouter_key.txt',
+ 'model': 'meta-llama/llama-3-70b-instruct',
+ },
+ 'oxygen4o': {
+ 'name': 'Oxygen (GPT-4o)',
+ 'logo_name': 'ai-oxygen-symbolic',
+ 'description': 'An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key',
+ 'base_url': 'https://app.oxyapi.uk/v1/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/kM6MaCqGKA',
+ 'key_file': 'oxygen_key.txt',
+ 'model': 'gpt-4o',
+ },
+ 'zukijourney': {
+ 'name': 'zukijourney (GPT-3.5)',
+ 'logo_name': 'ai-zukijourney',
+ 'description': 'An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it\'s buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key',
+ 'base_url': 'https://zukijourney.xyzbot.net/v1/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/Y4J6XXnmQ6',
+ 'key_file': 'zuki_key.txt',
+ 'model': 'gpt-3.5-turbo',
+ },
+}, userOptions.sidebar.ai.extraGptModels)
+
+// Custom prompt
+const initMessages =
+ [
+ { role: "user", content: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m not sure.”. \nThanks!", },
+ { role: "assistant", content: "- Got it!", },
+ { role: "user", content: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"", },
+ { role: "assistant", content: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"", },
+ { role: "user", content: "raise volume by 5%", },
+ { role: "assistant", content: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink.", },
+ { role: "user", content: "main advantages of the nixos operating system", },
+ { role: "assistant", content: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage", },
+ { role: "user", content: "whats skeumorphism", },
+ { role: "assistant", content: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today.", },
+ ];
+
+Utils.exec(`mkdir -p ${GLib.get_user_state_dir()}/ags/user/ai`);
+
+class GPTMessage extends Service {
+ static {
+ Service.register(this,
+ {
+ 'delta': ['string'],
+ },
+ {
+ 'content': ['string'],
+ 'thinking': ['boolean'],
+ 'done': ['boolean'],
+ });
+ }
+
+ _role = '';
+ _content = '';
+ _thinking;
+ _done = false;
+
+ constructor(role, content, thinking = true, done = false) {
+ super();
+ this._role = role;
+ this._content = content;
+ this._thinking = thinking;
+ this._done = done;
+ }
+
+ get done() { return this._done }
+ set done(isDone) { this._done = isDone; this.notify('done') }
+
+ get role() { return this._role }
+ set role(role) { this._role = role; this.emit('changed') }
+
+ get content() { return this._content }
+ set content(content) {
+ this._content = content;
+ this.notify('content')
+ this.emit('changed')
+ }
+
+ get label() { return this._parserState.parsed + this._parserState.stack.join('') }
+
+ get thinking() { return this._thinking }
+ set thinking(value) {
+ this._thinking = value;
+ this.notify('thinking')
+ this.emit('changed')
+ }
+
+ addDelta(delta) {
+ if (this.thinking) {
+ this.thinking = false;
+ this.content = delta;
+ }
+ else {
+ this.content += delta;
+ }
+ this.emit('delta', delta);
+ }
+}
+
+class GPTService extends Service {
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newMsg': ['int'],
+ 'hasKey': ['boolean'],
+ 'providerChanged': [],
+ });
+ }
+
+ _assistantPrompt = true;
+ _currentProvider = userOptions.ai.defaultGPTProvider;
+ _requestCount = 0;
+ _temperature = userOptions.ai.defaultTemperature;
+ _messages = [];
+ _key = '';
+ _key_file_location = `${GLib.get_user_state_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`;
+ _url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE);
+
+ _decoder = new TextDecoder();
+
+ _initChecks() {
+ this._key_file_location = `${GLib.get_user_state_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`;
+ if (fileExists(this._key_file_location)) this._key = Utils.readFile(this._key_file_location).trim();
+ else this.emit('hasKey', false);
+ this._url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE);
+ }
+
+ constructor() {
+ super();
+ this._initChecks();
+
+ if (this._assistantPrompt) this._messages = [...initMessages];
+ else this._messages = [];
+
+ this.emit('initialized');
+ }
+
+ get modelName() { return PROVIDERS[this._currentProvider]['model'] }
+ get getKeyUrl() { return PROVIDERS[this._currentProvider]['key_get_url'] }
+ get providerID() { return this._currentProvider }
+ set providerID(value) {
+ this._currentProvider = value;
+ this.emit('providerChanged');
+ this._initChecks();
+ }
+ get providers() { return PROVIDERS }
+
+ get keyPath() { return this._key_file_location }
+ get key() { return this._key }
+ set key(keyValue) {
+ this._key = keyValue;
+ Utils.writeFile(this._key, this._key_file_location)
+ .then(this.emit('hasKey', true))
+ .catch(print);
+ }
+
+ get temperature() { return this._temperature }
+ set temperature(value) { this._temperature = value; }
+
+ get messages() { return this._messages }
+ get lastMessage() { return this._messages[this._messages.length - 1] }
+
+ clear() {
+ if (this._assistantPrompt)
+ this._messages = [...initMessages];
+ else
+ this._messages = [];
+ this.emit('clear');
+ }
+
+ get assistantPrompt() { return this._assistantPrompt; }
+ set assistantPrompt(value) {
+ this._assistantPrompt = value;
+ if (value) this._messages = [...initMessages];
+ else this._messages = [];
+ }
+
+ readResponse(stream, aiResponse) {
+ aiResponse.thinking = false;
+ stream.read_line_async(
+ 0, null,
+ (stream, res) => {
+ if (!stream) return;
+ const [bytes] = stream.read_line_finish(res);
+ const line = this._decoder.decode(bytes);
+ if (line && line != '') {
+ let data = line.substr(6);
+ if (data == '[DONE]') return;
+ try {
+ const result = JSON.parse(data);
+ if (result.choices[0].finish_reason === 'stop') {
+ aiResponse.done = true;
+ return;
+ }
+ aiResponse.addDelta(result.choices[0].delta.content);
+ // print(result.choices[0])
+ }
+ catch {
+ aiResponse.addDelta(line + '\n');
+ }
+ }
+ this.readResponse(stream, aiResponse);
+ });
+ }
+
+ addMessage(role, message) {
+ this._messages.push(new GPTMessage(role, message));
+ this.emit('newMsg', this._messages.length - 1);
+ }
+
+ send(msg) {
+ this._messages.push(new GPTMessage('user', msg, false, true));
+ this.emit('newMsg', this._messages.length - 1);
+ const aiResponse = new GPTMessage('assistant', '', true, false)
+
+ const body = {
+ model: PROVIDERS[this._currentProvider]['model'],
+ messages: this._messages.map(msg => { let m = { role: msg.role, content: msg.content }; return m; }),
+ temperature: this._temperature,
+ // temperature: 2, // <- Nuts
+ stream: true,
+ };
+ const proxyResolver = new Gio.SimpleProxyResolver({ 'default-proxy': userOptions.ai.proxyUrl });
+ const session = new Soup.Session({ 'proxy-resolver': proxyResolver });
+ const message = new Soup.Message({
+ method: 'POST',
+ uri: this._url,
+ });
+ message.request_headers.append('Authorization', `Bearer ${this._key}`);
+ message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
+
+ session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
+ const stream = session.send_finish(result);
+ this.readResponse(new Gio.DataInputStream({
+ close_base_stream: true,
+ base_stream: stream
+ }), aiResponse);
+ });
+ this._messages.push(aiResponse);
+ this.emit('newMsg', this._messages.length - 1);
+ }
+}
+
+export default new GPTService();
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ags/services/indicator.js b/ags/services/indicator.js
new file mode 100644
index 00000000..dfe141ee
--- /dev/null
+++ b/ags/services/indicator.js
@@ -0,0 +1,38 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+class IndicatorService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'popup': ['double'], },
+ );
+ }
+
+ _delay = 1500;
+ _count = 0;
+
+ popup(value) {
+ this.emit('popup', value);
+ this._count++;
+ Utils.timeout(this._delay, () => {
+ this._count--;
+
+ if (this._count === 0)
+ this.emit('popup', -1);
+ });
+ }
+
+ connectWidget(widget, callback) {
+ connect(this, widget, callback, 'popup');
+ }
+}
+
+// the singleton instance
+const service = new IndicatorService();
+
+// make it global for easy use with cli
+globalThis['indicator'] = service;
+
+// export to use in other modules
+export default service;
\ No newline at end of file
diff --git a/ags/services/messages.js b/ags/services/messages.js
new file mode 100644
index 00000000..fb94c7bb
--- /dev/null
+++ b/ags/services/messages.js
@@ -0,0 +1,61 @@
+const { Notify, GLib, Gio } = imports.gi;
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
+
+export function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+const FIRST_RUN_FILE = "firstrun.txt";
+const FIRST_RUN_PATH = `${GLib.get_user_state_dir()}/ags/user/${FIRST_RUN_FILE}`;
+const FIRST_RUN_FILE_CONTENT = "Just a file to confirm that you have been greeted ;)";
+const APP_NAME = "illogical-impulse";
+const FIRST_RUN_NOTIF_TITLE = "Welcome!";
+const FIRST_RUN_NOTIF_BODY = `First run? 👀 CTRL+SUPER+T to pick a wallpaper (or styles will break!)\nFor a list of keybinds, hit Super + /.`;
+
+var batteryWarned = false;
+async function batteryMessage() {
+ const perc = Battery.percent;
+ const charging = Battery.charging;
+ if (charging) {
+ batteryWarned = false;
+ return;
+ }
+ for (let i = userOptions.battery.warnLevels.length - 1; i >= 0; i--) {
+ if (perc <= userOptions.battery.warnLevels[i] && !charging && !batteryWarned) {
+ batteryWarned = true;
+ Utils.execAsync(['bash', '-c',
+ `notify-send "${userOptions.battery.warnTitles[i]}" "${userOptions.battery.warnMessages[i]}" -u critical -a '${APP_NAME}' -t 69420 &`
+ ]).catch(print);
+ break;
+ }
+ }
+ if (perc <= userOptions.battery.suspendThreshold) {
+ Utils.execAsync(['bash', '-c',
+ `notify-send "Suspending system" "Critical battery level (${perc}% remaining)" -u critical -a '${APP_NAME}' -t 69420 &`
+ ]).catch(print);
+ Utils.execAsync('systemctl suspend').catch(print);
+ }
+}
+
+export async function startBatteryWarningService() {
+ Utils.timeout(1, () => {
+ Battery.connect('changed', () => batteryMessage().catch(print));
+ })
+}
+
+export async function firstRunWelcome() {
+ GLib.mkdir_with_parents(`${GLib.get_user_state_dir()}/ags/user`, 755);
+ if (!fileExists(FIRST_RUN_PATH)) {
+ Utils.writeFile(FIRST_RUN_FILE_CONTENT, FIRST_RUN_PATH)
+ .then(() => {
+ // Note that we add a little delay to make sure the cool circular progress works
+ Utils.execAsync(['hyprctl', 'keyword', 'bind', "Super,Slash,exec,ags -t cheatsheet"]).catch(print);
+ Utils.execAsync(['bash', '-c',
+ `sleep 0.5; notify-send "Millis since epoch" "$(date +%s%N | cut -b1-13)"; sleep 0.5; notify-send '${FIRST_RUN_NOTIF_TITLE}' '${FIRST_RUN_NOTIF_BODY}' -a '${APP_NAME}' &`
+ ]).catch(print)
+ })
+ .catch(print);
+ }
+}
\ No newline at end of file
diff --git a/ags/services/sway.js b/ags/services/sway.js
new file mode 100644
index 00000000..388372e9
--- /dev/null
+++ b/ags/services/sway.js
@@ -0,0 +1,400 @@
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import Service from "resource:///com/github/Aylur/ags/service.js";
+
+const SIS = GLib.getenv('SWAYSOCK');
+
+export const PAYLOAD_TYPE = {
+ MESSAGE_RUN_COMMAND: 0,
+ MESSAGE_GET_WORKSPACES: 1,
+ MESSAGE_SUBSCRIBE: 2,
+ MESSAGE_GET_OUTPUTS: 3,
+ MESSAGE_GET_TREE: 4,
+ MESSAGE_GET_MARKS: 5,
+ MESSAGE_GET_BAR_CONFIG: 6,
+ MESSAGE_GET_VERSION: 7,
+ MESSAGE_GET_BINDING_NODES: 8,
+ MESSAGE_GET_CONFIG: 9,
+ MESSAGE_SEND_TICK: 10,
+ MESSAGE_SYNC: 11,
+ MESSAGE_GET_BINDING_STATE: 12,
+ MESSAGE_GET_INPUTS: 100,
+ MESSAGE_GET_SEATS: 101,
+ EVENT_WORKSPACE: 0x80000000,
+ EVENT_MODE: 0x80000002,
+ EVENT_WINDOW: 0x80000003,
+ EVENT_BARCONFIG_UPDATE: 0x80000004,
+ EVENT_BINDING: 0x80000005,
+ EVENT_SHUTDOWN: 0x80000006,
+ EVENT_TICK: 0x80000007,
+ EVENT_BAR_STATE_UPDATE: 0x80000014,
+ EVENT_INPUT: 0x80000015,
+}
+
+const Client_Event = {
+ change: undefined,
+ container: undefined,
+}
+
+const Workspace_Event = {
+ change: undefined,
+ current: undefined,
+ old: undefined,
+}
+
+const Geometry = {
+ x: undefined,
+ y: undefined,
+ width: undefined,
+ height: undefined,
+}
+
+//NOTE: not all properties are listed here
+export const Node = {
+ id: undefined,
+ name: undefined,
+ type: undefined,
+ border: undefined,
+ current_border_width: undefined,
+ layout: undefined,
+ orientation: undefined,
+ percent: undefined,
+ rect: undefined,
+ window_rect: undefined,
+ deco_rect: undefined,
+ geometry: undefined,
+ urgent: undefined,
+ sticky: undefined,
+ marks: undefined,
+ focused: undefined,
+ active: undefined,
+ focus: undefined,
+ nodes: undefined,
+ floating_nodes: undefined,
+ representation: undefined,
+ fullscreen_mode: undefined,
+ app_id: undefined,
+ pid: undefined,
+ visible: undefined,
+ shell: undefined,
+ output: undefined,
+ inhibit_idle: undefined,
+ idle_inhibitors: {
+ application: undefined,
+ user: undefined,
+ },
+ window: undefined,
+ window_properties: {
+ title: undefined,
+ class: undefined,
+ instance: undefined,
+ window_role: undefined,
+ window_type: undefined,
+ transient_for: undefined,
+ }
+}
+
+export class SwayActiveClient extends Service {
+ static {
+ Service.register(this, {}, {
+ 'id': ['int'],
+ 'name': ['string'],
+ 'class': ['string'],
+ });
+ }
+
+ _id = 0;
+ _name = '';
+ _class = '';
+
+ get id() { return this._id; }
+ get name() { return this._name; }
+ get class() { return this._class; }
+
+ updateProperty(prop, value) {
+ if (!['id', 'name', 'class'].includes(prop)) return;
+ super.updateProperty(prop, value);
+ this.emit('changed');
+ }
+}
+
+export class SwayActiveID extends Service {
+ static {
+ Service.register(this, {}, {
+ 'id': ['int'],
+ 'name': ['string'],
+ });
+ }
+
+ _id = 0;
+ _name = '';
+
+ get id() { return this._id; }
+ get name() { return this._name; }
+
+ update(id, name) {
+ super.updateProperty('id', id);
+ super.updateProperty('name', name);
+ this.emit('changed');
+ }
+}
+
+export class SwayActives extends Service {
+ static {
+ Service.register(this, {}, {
+ 'client': ['jsobject'],
+ 'monitor': ['jsobject'],
+ 'workspace': ['jsobject'],
+ });
+ }
+
+ _client = new SwayActiveClient;
+ _monitor = new SwayActiveID;
+ _workspace = new SwayActiveID;
+
+ constructor() {
+ super();
+
+ (['client', 'workspace', 'monitor']).forEach(obj => {
+ this[`_${obj}`].connect('changed', () => {
+ this.notify(obj);
+ this.emit('changed');
+ });
+ });
+ }
+
+ get client() { return this._client; }
+ get monitor() { return this._monitor; }
+ get workspace() { return this._workspace; }
+}
+
+export class Sway extends Service {
+ static {
+ Service.register(this, {}, {
+ 'active': ['jsobject'],
+ 'monitors': ['jsobject'],
+ 'workspaces': ['jsobject'],
+ 'clients': ['jsobject'],
+ });
+ }
+
+ _decoder = new TextDecoder();
+ _encoder = new TextEncoder();
+ _socket;
+
+ _active;
+ _monitors;
+ _workspaces;
+ _clients;
+
+ get active() { return this._active; }
+ get monitors() { return Array.from(this._monitors.values()); }
+ get workspaces() { return Array.from(this._workspaces.values()); }
+ get clients() { return Array.from(this._clients.values()); }
+
+ getMonitor(id) { return this._monitors.get(id); }
+ getWorkspace(name) { return this._workspaces.get(name); }
+ getClient(id) { return this._clients.get(id); }
+
+ msg(payload) { this._send(PAYLOAD_TYPE.MESSAGE_RUN_COMMAND, payload); }
+
+ constructor() {
+ if (!SIS)
+ console.error('Sway is not running');
+ super();
+
+ this._active = new SwayActives();
+ this._monitors = new Map();
+ this._workspaces = new Map();
+ this._clients = new Map();
+
+ this._socket = new Gio.SocketClient().connect(new Gio.UnixSocketAddress({
+ path: `${SIS}`,
+ }), null);
+
+ this._watchSocket(this._socket.get_input_stream());
+ this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
+ this._send(PAYLOAD_TYPE.MESSAGE_SUBSCRIBE, JSON.stringify(['window', 'workspace']));
+
+ this._active.connect('changed', () => this.emit('changed'));
+ ['monitor', 'workspace', 'client'].forEach(active =>
+ this._active.connect(`notify::${active}`, () => this.notify('active')));
+ }
+
+ _send(payloadType, payload) {
+ const pb = this._encoder.encode(payload);
+ const type = new Uint32Array([payloadType]);
+ const pl = new Uint32Array([pb.length]);
+ const magic_string = this._encoder.encode('i3-ipc');
+ const data = new Uint8Array([
+ ...magic_string,
+ ...(new Uint8Array(pl.buffer)),
+ ...(new Uint8Array(type.buffer)),
+ ...pb]);
+ this._socket.get_output_stream().write(data, null);
+ }
+
+ _watchSocket(stream) {
+ stream.read_bytes_async(14, GLib.PRIORITY_DEFAULT, null, (_, resultHeader) => {
+ const data = stream.read_bytes_finish(resultHeader).get_data();
+ if (!data)
+ return;
+ const payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0];
+ const payloadType = new Uint32Array(data.slice(10, 14).buffer)[0];
+ stream.read_bytes_async(
+ payloadLength,
+ GLib.PRIORITY_DEFAULT,
+ null,
+ (_, resultPayload) => {
+ const data = stream.read_bytes_finish(resultPayload).get_data();
+ if (!data)
+ return;
+ this._onEvent(payloadType, JSON.parse(this._decoder.decode(data)));
+ this._watchSocket(stream);
+ });
+ });
+ }
+
+ async _onEvent(event_type, event) {
+ if (!event)
+ return;
+ try {
+ switch (event_type) {
+ case PAYLOAD_TYPE.EVENT_WORKSPACE:
+ this._handleWorkspaceEvent(event);
+ break;
+ case PAYLOAD_TYPE.EVENT_WINDOW:
+ this._handleWindowEvent(event);
+ break;
+ case PAYLOAD_TYPE.MESSAGE_GET_TREE:
+ this._handleTreeMessage(event);
+ break;
+ default:
+ break;
+ }
+ } catch (error) {
+ logError(error);
+ }
+ this.emit('changed');
+ }
+
+ _handleWorkspaceEvent(workspaceEvent) {
+ const workspace = workspaceEvent.current;
+ switch (workspaceEvent.change) {
+ case 'init':
+ this._workspaces.set(workspace.name, workspace);
+ break;
+ case 'empty':
+ this._workspaces.delete(workspace.name);
+ break;
+ case 'focus':
+ this._active.workspace.update(workspace.id, workspace.name);
+ this._active.monitor.update(1, workspace.output);
+
+ this._workspaces.set(workspace.name, workspace);
+ this._workspaces.set(workspaceEvent.old.name, workspaceEvent.old);
+ break;
+ case 'rename':
+ if (this._active.workspace.id === workspace.id)
+ this._active.workspace.updateProperty('name', workspace.name);
+ this._workspaces.set(workspace.name, workspace);
+ break;
+ case 'reload':
+ break;
+ case 'move':
+ case 'urgent':
+ default:
+ this._workspaces.set(workspace.name, workspace);
+ }
+ this.notify('workspaces');
+ }
+
+ _handleWindowEvent(clientEvent) {
+ const client = clientEvent.container;
+ const id = client.id;
+ switch (clientEvent.change) {
+ case 'new':
+ case 'close':
+ case 'floating':
+ case 'move':
+ // Refresh tree since client events don't contain the relevant information
+ // to be able to modify `workspace.nodes` or `workspace.floating_nodes`.
+ // There has to be a better way than this though :/
+ this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
+ break;
+ case 'focus':
+ if (this._active.client.id === id)
+ return;
+ // eslint-disable-next-line no-case-declarations
+ const current_active = this._clients.get(this._active.client.id);
+ if (current_active)
+ current_active.focused = false;
+ this._active.client.updateProperty('id', id);
+ this._active.client.updateProperty('name', client.name);
+ this._active.client.updateProperty('class', client.shell === 'xwayland'
+ ? client.window_properties?.class || ''
+ : client.app_id,
+ );
+ break;
+ case 'title':
+ if (client.focused)
+ this._active.client.updateProperty('name', client.name);
+ this._clients.set(id, client);
+ this.notify('clients');
+ break;
+ case 'fullscreen_mode':
+ case 'urgent':
+ case 'mark':
+ default:
+ this._clients.set(id, client);
+ this.notify('clients');
+ }
+ }
+
+ _handleTreeMessage(node) {
+ switch (node.type) {
+ case 'root':
+ this._workspaces.clear();
+ this._clients.clear();
+ this._monitors.clear();
+ node.nodes.map(n => this._handleTreeMessage(n));
+ break;
+ case 'output':
+ this._monitors.set(node.id, node);
+ if (node.active)
+ this._active.monitor.update(node.id, node.name);
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('monitors');
+ break;
+ case 'workspace':
+ this._workspaces.set(node.name, node);
+ // I think I'm missing something. There has to be a better way.
+ // eslint-disable-next-line no-case-declarations
+ const hasFocusedChild =
+ (n) => n.nodes.some(c => c.focused || hasFocusedChild(c));
+ if (node.focused || hasFocusedChild(node))
+ this._active.workspace.update(node.id, node.name);
+
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('workspaces');
+ break;
+ case 'con':
+ case 'floating_con':
+ this._clients.set(node.id, node);
+ if (node.focused) {
+ this._active.client.updateProperty('id', node.id);
+ this._active.client.updateProperty('name', node.name);
+ this._active.client.updateProperty('class', node.shell === 'xwayland'
+ ? node.window_properties?.class || ''
+ : node.app_id,
+ );
+ }
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('clients');
+ break;
+ }
+ }
+}
+
+export const sway = new Sway;
+export default sway;
\ No newline at end of file
diff --git a/ags/services/todo.js b/ags/services/todo.js
new file mode 100644
index 00000000..d5384f3b
--- /dev/null
+++ b/ags/services/todo.js
@@ -0,0 +1,83 @@
+const { Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+class TodoService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'updated': [], },
+ );
+ }
+
+ _todoPath = '';
+ _todoJson = [];
+
+ refresh(value) {
+ this.emit('updated', value);
+ }
+
+ connectWidget(widget, callback) {
+ this.connect(widget, callback, 'updated');
+ }
+
+ get todo_json() {
+ return this._todoJson;
+ }
+
+ _save() {
+ Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath)
+ .catch(print);
+ }
+
+ add(content) {
+ this._todoJson.push({ content, done: false });
+ this._save();
+ this.emit('updated');
+ }
+
+ check(index) {
+ this._todoJson[index].done = true;
+ this._save();
+ this.emit('updated');
+ }
+
+ uncheck(index) {
+ this._todoJson[index].done = false;
+ this._save();
+ this.emit('updated');
+ }
+
+ remove(index) {
+ this._todoJson.splice(index, 1);
+ Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath)
+ .catch(print);
+ this.emit('updated');
+ }
+
+ constructor() {
+ super();
+ this._todoPath = `${GLib.get_user_state_dir()}/ags/user/todo.json`;
+ try {
+ const fileContents = Utils.readFile(this._todoPath);
+ this._todoJson = JSON.parse(fileContents);
+ }
+ catch {
+ Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
+ Utils.exec(`touch ${this._todoPath}`);
+ Utils.writeFile("[]", this._todoPath).then(() => {
+ this._todoJson = JSON.parse(Utils.readFile(this._todoPath))
+ }).catch(print);
+ }
+ }
+}
+
+// the singleton instance
+const service = new TodoService();
+
+// make it global for easy use with cli
+globalThis.todo = service;
+
+// export to use in other modules
+export default service;
\ No newline at end of file
diff --git a/ags/services/waifus.js b/ags/services/waifus.js
new file mode 100644
index 00000000..8512ed59
--- /dev/null
+++ b/ags/services/waifus.js
@@ -0,0 +1,150 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+// Note: this service is made mainly for waifu.im. Others might work but not as properly
+const APISERVICES = {
+ 'im': {
+ 'endpoint': 'https://api.waifu.im/search',
+ 'headers': { 'Accept-Version': 'v5' },
+ },
+ 'nekos': {
+ 'endpoint': 'https://nekos.life/api/neko',
+ 'headers': {},
+ },
+ 'pics': {
+ 'endpoint': 'https://api.waifu.pics/sfw/',
+ 'headers': {},
+ },
+};
+
+function paramStringFromObj(params) {
+ return Object.entries(params)
+ .map(([key, value]) => {
+ if (Array.isArray(value)) { // If it's an array, repeat
+ if (value.length == 0) return '';
+ let thisKey = `${encodeURIComponent(key)}=${encodeURIComponent(value[0])}`
+ for (let i = 1; i < value.length; i++) {
+ thisKey += `&${encodeURIComponent(key)}=${encodeURIComponent(value[i])}`;
+ }
+ return thisKey;
+ }
+ return `${key}=${value}`;
+ })
+ .join('&');
+}
+
+class WaifuService extends Service {
+ _baseUrl = 'https://api.waifu.im/search';
+ _mode = 'im'; // Allowed: im
+ _responses = [];
+ _queries = [];
+ _nsfw = false;
+ _minHeight = 600;
+
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newResponse': ['int'],
+ 'updateResponse': ['int'],
+ });
+ }
+
+ constructor() {
+ super();
+ this.emit('initialized');
+ }
+
+ clear() {
+ this._responses = [];
+ this._queries = [];
+ this.emit('clear');
+ }
+
+ get mode() { return this._mode }
+ set mode(value) {
+ this._mode = value;
+ this._baseUrl = APISERVICES[this._mode].endpoint;
+ }
+ get nsfw() { return this._nsfw }
+ set nsfw(value) { this._nsfw = value }
+ get queries() { return this._queries }
+ get responses() { return this._responses }
+
+ async fetch(msg) {
+ // Init
+ const userArgs = msg.split(/\s+/);
+
+ let taglist = [];
+ this._nsfw = false;
+ // Construct body/headers
+ for (let i = 0; i < userArgs.length; i++) {
+ const thisArg = userArgs[i].trim();
+ if (thisArg.length == 0) continue;
+ if (thisArg == '--im') this._mode = 'im';
+ else if (thisArg == '--nekos') this._mode = 'nekos';
+ else if (thisArg.includes('pics')) this._mode = 'pics';
+ else if (thisArg.includes('segs') || thisArg.includes('sex') || thisArg.includes('lewd')) this._nsfw = true;
+ else {
+ taglist.push(thisArg);
+ if (['ecchi', 'hentai', 'ero', 'ass', 'paizuri', 'oral', 'milf'].includes(thisArg)) this._nsfw = true;
+ }
+ }
+ const newMessageId = this._queries.length;
+ this._queries.push(taglist);
+ this.emit('newResponse', newMessageId);
+ const params = {
+ 'included_tags': taglist,
+ 'height': `>=${this._minHeight}`,
+ 'nsfw': this._nsfw,
+ };
+ const paramString = paramStringFromObj(params);
+ // Fetch
+ // Note: body isn't included since passing directly to url is more reliable
+ const options = {
+ method: 'GET',
+ headers: APISERVICES[this._mode].headers,
+ };
+ let status = 0;
+ Utils.fetch(`${APISERVICES[this._mode].endpoint}?${paramString}`, options)
+ .then(result => {
+ status = result.status;
+ return result.text();
+ })
+ .then((dataString) => { // Store interesting stuff and emit
+ const parsedData = JSON.parse(dataString);
+ if (!parsedData.images) this._responses.push({ // Failed
+ status: status,
+ signature: -1,
+ url: '',
+ extension: '',
+ source: '',
+ dominant_color: '#383A40',
+ is_nsfw: false,
+ width: 0,
+ height: 0,
+ tags: [],
+ });
+ else {
+ const imageData = parsedData.images[0];
+ this._responses.push({
+ status: status,
+ signature: imageData?.signature || -1,
+ url: imageData?.url || undefined,
+ extension: imageData.extension,
+ source: imageData?.source,
+ dominant_color: imageData?.dominant_color || '#9392A6',
+ is_nsfw: imageData?.is_nsfw || false,
+ width: imageData?.width || 0,
+ height: imageData?.height || 0,
+ tags: imageData?.tags.map(obj => obj["name"]) || [],
+ });
+ }
+ this.emit('updateResponse', newMessageId);
+ })
+ .catch(print);
+ }
+}
+
+export default new WaifuService();
+
diff --git a/ags/services/wallpaper.js b/ags/services/wallpaper.js
new file mode 100644
index 00000000..9fa2b83f
--- /dev/null
+++ b/ags/services/wallpaper.js
@@ -0,0 +1,70 @@
+const { Gdk, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const WALLPAPER_CONFIG_PATH = `${GLib.get_user_state_dir()}/ags/user/wallpaper.json`;
+
+class WallpaperService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'updated': [], },
+ );
+ }
+
+ _wallPath = '';
+ _wallJson = [];
+ _monitorCount = 1;
+
+ _save() {
+ Utils.writeFile(JSON.stringify(this._wallJson), this._wallPath)
+ .catch(print);
+ }
+
+ add(path) {
+ this._wallJson.push(path);
+ this._save();
+ this.emit('updated');
+ }
+
+ set(path, monitor = -1) {
+ this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ if (this._wallJson.length < this._monitorCount) this._wallJson[this._monitorCount - 1] = "";
+ if (monitor == -1)
+ this._wallJson.fill(path);
+ else
+ this._wallJson[monitor] = path;
+
+ this._save();
+ this.emit('updated');
+ }
+
+ get(monitor = 0) {
+ return this._wallJson[monitor];
+ }
+
+ constructor() {
+ super();
+ // How many screens?
+ this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ // Read config
+ this._wallPath = WALLPAPER_CONFIG_PATH;
+ try {
+ const fileContents = Utils.readFile(this._wallPath);
+ this._wallJson = JSON.parse(fileContents);
+ }
+ catch {
+ Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
+ Utils.exec(`touch ${this._wallPath}`);
+ Utils.writeFile('[]', this._wallPath).then(() => {
+ this._wallJson = JSON.parse(Utils.readFile(this._wallPath))
+ }).catch(print);
+ }
+ }
+}
+
+// instance
+const service = new WallpaperService();
+// make it global for easy use with cli
+globalThis['wallpaper'] = service;
+export default service;
\ No newline at end of file
diff --git a/ags/user_options.js b/ags/user_options.js
new file mode 100644
index 00000000..03e76202
--- /dev/null
+++ b/ags/user_options.js
@@ -0,0 +1,9 @@
+// For every option, see ~/.config/ags/modules/.configuration/user_options.js
+// (vscode users ctrl+click this: file://./modules/.configuration/user_options.js)
+// (vim users: `:vsp` to split window, move cursor to this path, press `gf`. `Ctrl-w` twice to switch between)
+// options listed in this file will override the default ones in the above file
+
+const userConfigOptions = {
+}
+
+export default userConfigOptions;
diff --git a/ags/variables.js b/ags/variables.js
new file mode 100644
index 00000000..bf6a9ff6
--- /dev/null
+++ b/ags/variables.js
@@ -0,0 +1,82 @@
+const { Gdk, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js'
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets/icons`);
+
+// Global vars for external control (through keybinds)
+export const showMusicControls = Variable(false, {})
+export const showColorScheme = Variable(false, {})
+globalThis['openMusicControls'] = showMusicControls;
+globalThis['openColorScheme'] = showColorScheme;
+globalThis['mpris'] = Mpris;
+
+// load monitor shell modes from userOptions
+const initialMonitorShellModes = () => {
+ const numberOfMonitors = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ const monitorBarConfigs = [];
+ for (let i = 0; i < numberOfMonitors; i++) {
+ if (userOptions.bar.modes[i]) {
+ monitorBarConfigs.push(userOptions.bar.modes[i])
+ } else {
+ monitorBarConfigs.push('normal')
+ }
+ }
+ return monitorBarConfigs;
+
+}
+export const currentShellMode = Variable(initialMonitorShellModes(), {}) // normal, focus
+
+// Mode switching
+const updateMonitorShellMode = (monitorShellModes, monitor, mode) => {
+ const newValue = [...monitorShellModes.value];
+ newValue[monitor] = mode;
+ monitorShellModes.value = newValue;
+}
+globalThis['currentMode'] = currentShellMode;
+globalThis['cycleMode'] = () => {
+ const monitor = Hyprland.active.monitor.id || 0;
+
+ if (currentShellMode.value[monitor] === 'normal') {
+ updateMonitorShellMode(currentShellMode, monitor, 'focus')
+ }
+ else if (currentShellMode.value[monitor] === 'focus') {
+ updateMonitorShellMode(currentShellMode, monitor, 'nothing')
+ }
+ else {
+ updateMonitorShellMode(currentShellMode, monitor, 'normal')
+ }
+}
+
+// Window controls
+const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
+globalThis['toggleWindowOnAllMonitors'] = (name) => {
+ range(Gdk.Display.get_default()?.get_n_monitors() || 1, 0).forEach(id => {
+ App.toggleWindow(`${name}${id}`);
+ });
+}
+globalThis['closeWindowOnAllMonitors'] = (name) => {
+ range(Gdk.Display.get_default()?.get_n_monitors() || 1, 0).forEach(id => {
+ App.closeWindow(`${name}${id}`);
+ });
+}
+globalThis['openWindowOnAllMonitors'] = (name) => {
+ range(Gdk.Display.get_default()?.get_n_monitors() || 1, 0).forEach(id => {
+ App.openWindow(`${name}${id}`);
+ });
+}
+
+globalThis['closeEverything'] = () => {
+ const numMonitors = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ for (let i = 0; i < numMonitors; i++) {
+ App.closeWindow(`cheatsheet${i}`);
+ App.closeWindow(`session${i}`);
+ }
+ App.closeWindow('sideleft');
+ App.closeWindow('sideright');
+ App.closeWindow('overview');
+};
diff --git a/config.fish b/config.fish
deleted file mode 100644
index 1545f145..00000000
--- a/config.fish
+++ /dev/null
@@ -1,196 +0,0 @@
-## Set values
-# Hide welcome message
-set fish_greeting
-set VIRTUAL_ENV_DISABLE_PROMPT "1"
-# set -x MANPAGER "sh -c 'col -bx | bat -l man -p'"
-
-# Set settings for https://github.com/franciscolourenco/done
-set -U __done_min_cmd_duration 10000
-set -U __done_notification_urgency_level low
-
-# Custom John Costa Commands
-alias v='nvim'
-alias lg='lazygit'
-alias p='pnpm'
-alias px='pnpx'
-alias s='tmux new-session -c $(bash ~/Code/dotfiles/Scripts/StartWork.bash && cat ~/.startwork.txt | fzf)'
-
-alias tasker='go run /home/john/Code/dotfiles/Scripts/CreateTask.go'
-alias mp4='bash ~/Code/dotfiles/Scripts/MkvToMp4.bash'
-
-## Environment setup
-# Apply .profile
-source ~/.profile
-
-# Add ~/.local/bin to PATH
-if test -d ~/.local/bin
- if not contains -- ~/.local/bin $PATH
- set -p PATH ~/.local/bin
- end
-end
-
-# Add depot_tools to PATH
-if test -d ~/Applications/depot_tools
- if not contains -- ~/Applications/depot_tools $PATH
- set -p PATH ~/Applications/depot_tools
- end
-end
-
-
-## Starship prompt
-if status --is-interactive
- source ("/usr/bin/starship" init fish --print-full-init | psub)
-end
-
-
-## Functions
-# Functions needed for !! and !$ https://github.com/oh-my-fish/plugin-bang-bang
-function __history_previous_command
- switch (commandline -t)
- case "!"
- commandline -t $history[1]; commandline -f repaint
- case "*"
- commandline -i !
- end
-end
-
-function __history_previous_command_arguments
- switch (commandline -t)
- case "!"
- commandline -t ""
- commandline -f history-token-search-backward
- case "*"
- commandline -i '$'
- end
-end
-
-if [ "$fish_key_bindings" = fish_vi_key_bindings ];
- bind -Minsert ! __history_previous_command
- bind -Minsert '$' __history_previous_command_arguments
-else
- bind ! __history_previous_command
- bind '$' __history_previous_command_arguments
-end
-
-# Fish command history
-function history
- builtin history --show-time='%F %T '
-end
-
-function backup --argument filename
- cp $filename $filename.bak
-end
-
-# Copy DIR1 DIR2
-function copy
- set count (count $argv | tr -d \n)
- if test "$count" = 2; and test -d "$argv[1]"
- set from (echo $argv[1] | trim-right /)
- set to (echo $argv[2])
- command cp -r $from $to
- else
- command cp $argv
- end
-end
-
-
-## Useful aliases
-# Replace ls with exa
-alias ls='exa -al --color=always --group-directories-first --icons' # preferred listing
-alias la='exa -a --color=always --group-directories-first --icons' # all files and dirs
-alias ll='exa -l --color=always --group-directories-first --icons' # long format
-alias lt='exa -aT --color=always --group-directories-first --icons' # tree listing
-alias l.="exa -a | egrep '^\.'" # show only dotfiles
-
-# Replace some more things with better alternatives
-# alias cat='bat --style header --style rules --style snip --style changes --style header'
-# alias bat='bat --style header --style rules --style snip --style changes --style header'
-[ ! -x /usr/bin/yay ] && [ -x /usr/bin/paru ] && alias yay='paru --bottomup'
-
-# Common use
-alias aup="pamac upgrade --aur"
-alias grubup="sudo update-grub"
-alias fixpacman="sudo rm /var/lib/pacman/db.lck"
-alias tarnow='tar -acf '
-alias untar='tar -zxvf '
-alias wget='wget -c '
-alias rmpkg="sudo pacman -Rdd"
-alias psmem='ps auxf | sort -nr -k 4'
-alias psmem10='ps auxf | sort -nr -k 4 | head -10'
-alias upd='sudo reflector --latest 5 --age 2 --fastest 5 --protocol https --sort rate --save /etc/pacman.d/mirrorlist && cat /etc/pacman.d/mirrorlist && sudo pacman -Syu && fish_update_completions && sudo updatedb && sudo -H DIFFPROG=meld pacdiff'
-alias ..='cd ..'
-alias ...='cd ../..'
-alias ....='cd ../../..'
-alias .....='cd ../../../..'
-alias ......='cd ../../../../..'
-alias dir='dir --color=auto'
-alias vdir='vdir --color=auto'
-alias grep='grep --color=auto'
-alias fgrep='fgrep --color=auto'
-alias egrep='egrep --color=auto'
-alias hw='hwinfo --short' # Hardware Info
-alias big="expac -H M '%m\t%n' | sort -h | nl" # Sort installed packages according to size in MB (expac must be installed)
-alias gitpkg='pacman -Q | grep -i "\-git" | wc -l' # List amount of -git packages
-
-# Get fastest mirrors
-alias mirror="sudo reflector -f 30 -l 30 --number 10 --verbose --save /etc/pacman.d/mirrorlist"
-alias mirrord="sudo reflector --latest 50 --number 20 --sort delay --save /etc/pacman.d/mirrorlist"
-alias mirrors="sudo reflector --latest 50 --number 20 --sort score --save /etc/pacman.d/mirrorlist"
-alias mirrora="sudo reflector --latest 50 --number 20 --sort age --save /etc/pacman.d/mirrorlist"
-
-# Help people new to Arch
-alias apt='man pacman'
-alias apt-get='man pacman'
-alias pacdiff='sudo -H DIFFPROG=meld pacdiff' # Compare .pacnew & .pacsave files
-alias helpme='cht.sh --shell'
-alias please='sudo'
-alias tb='nc termbin.com 9999'
-alias paru="paru --bottomup"
-
-# Cleanup orphaned packages
-alias cleanup='sudo pacman -Rns (pacman -Qtdq)'
-
-# Get the error messages from journalctl
-alias jctl="journalctl -p 3 -xb"
-
-# Recent installed packages
-alias rip="expac --timefmt='%Y-%m-%d %T' '%l\t%n %v' | sort | tail -200 | nl"
-
-
-## Import colorscheme from 'wal' asynchronously
-if type "wal" >> /dev/null 2>&1
- cat ~/.cache/wal/sequences
-end
-
-
-## Run paleofetch if session is interactive
-if status --is-interactive
- # paleofetch
-end
-
-thefuck --alias | source
-
-# pnpm
-set -gx PNPM_HOME "/home/johnc/.local/share/pnpm"
-set -gx PATH "$PNPM_HOME" $PATH
-
-set -gx TERM xterm-256color
-
-# pnpm end
-
-# opam configuration
-source /home/johnc/.opam/opam-init/init.fish > /dev/null 2> /dev/null; or true
-
-# bun
-set --export BUN_INSTALL "$HOME/.bun"
-set --export PATH $BUN_INSTALL/bin $PATH
-
-# bun
-set --export BUN_INSTALL "$HOME/.bun"
-set --export PATH $BUN_INSTALL/bin $PATH
-
-# Chrome
-set -gx BROWSER "vivaldi"
-set -gx SLEEP_TIME "8"
-
-set -gx UV_USE_IO_URING 0
diff --git a/fish/auto-Hypr.fish b/fish/auto-Hypr.fish
new file mode 100644
index 00000000..c3c1890f
--- /dev/null
+++ b/fish/auto-Hypr.fish
@@ -0,0 +1,5 @@
+# Auto start Hyprland on tty1
+if test -z "$DISPLAY" ;and test "$XDG_VTNR" -eq 1
+ mkdir -p ~/.cache
+ exec Hyprland > ~/.cache/hyprland.log ^&1
+end
diff --git a/fish/config.fish b/fish/config.fish
new file mode 100755
index 00000000..b07534d2
--- /dev/null
+++ b/fish/config.fish
@@ -0,0 +1,3 @@
+set fish_greeting
+
+starship init fish | source
diff --git a/fish/fish_variables b/fish/fish_variables
new file mode 100755
index 00000000..3aefbf37
--- /dev/null
+++ b/fish/fish_variables
@@ -0,0 +1,35 @@
+# This file contains fish universal variable definitions.
+# VERSION: 3.0
+SETUVAR __fish_initialized:3400
+SETUVAR _fisher_jorgebucaran_2F_fisher_files:\x7e/\x2econfig/fish/functions/fisher\x2efish\x1e\x7e/\x2econfig/fish/completions/fisher\x2efish
+SETUVAR _fisher_plugins:jorgebucaran/fisher
+SETUVAR _fisher_upgraded_to_4_4:\x1d
+SETUVAR fish_color_autosuggestion:555\x1ebrblack
+SETUVAR fish_color_cancel:\x2dr
+SETUVAR fish_color_command:blue
+SETUVAR fish_color_comment:red
+SETUVAR fish_color_cwd:green
+SETUVAR fish_color_cwd_root:red
+SETUVAR fish_color_end:green
+SETUVAR fish_color_error:brred
+SETUVAR fish_color_escape:brcyan
+SETUVAR fish_color_history_current:\x2d\x2dbold
+SETUVAR fish_color_host:normal
+SETUVAR fish_color_host_remote:yellow
+SETUVAR fish_color_normal:normal
+SETUVAR fish_color_operator:brcyan
+SETUVAR fish_color_param:cyan
+SETUVAR fish_color_quote:yellow
+SETUVAR fish_color_redirection:cyan\x1e\x2d\x2dbold
+SETUVAR fish_color_search_match:\x2d\x2dbackground\x3d111
+SETUVAR fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack
+SETUVAR fish_color_status:red
+SETUVAR fish_color_user:brgreen
+SETUVAR fish_color_valid_path:\x2d\x2dunderline
+SETUVAR fish_key_bindings:fish_default_key_bindings
+SETUVAR fish_pager_color_completion:normal
+SETUVAR fish_pager_color_description:B3A06D\x1eyellow\x1e\x2di
+SETUVAR fish_pager_color_prefix:cyan\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
+SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan
+SETUVAR fish_pager_color_selected_background:\x2dr
+SETUVAR fish_user_paths:/home/john/\x2ebun/bin\x1e/home/john/\x2ebun
diff --git a/hypr/hyprland/colors.conf b/hypr/hyprland/colors.conf
index 2eef0252..79f4ac1f 100644
--- a/hypr/hyprland/colors.conf
+++ b/hypr/hyprland/colors.conf
@@ -31,4 +31,4 @@ plugin {
}
}
-windowrulev2 = bordercolor rgba(B3C5FFAA) rgba(B3C5FF77),pinned:1
\ No newline at end of file
+windowrulev2 = bordercolor rgba(B3C5FFAA) rgba(B3C5FF77),pinned:1
diff --git a/hypr/hyprland/keybinds.conf b/hypr/hyprland/keybinds.conf
index 362c589e..301c158b 100644
--- a/hypr/hyprland/keybinds.conf
+++ b/hypr/hyprland/keybinds.conf
@@ -1,6 +1,11 @@
# Lines ending with `# [hidden]` won't be shown on cheatsheet
# Lines starting with #! are section headings
+# OBS Studio
+bind = Ctrl,F10, exec, obs-cmd recording start
+bind = Ctrl,F11, exec, obs-cmd recording stop
+# ---------------------------------
+
bindl = Alt ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindl = Super ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindl = ,XF86AudioMute, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 0% # [hidden]
@@ -189,24 +194,13 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden]
#!
##! Apps
-bind = Super, T, exec, kitty # Launch foot (terminal)
-bind = Super, Z, exec, Zed # Launch Zed (editor)
-bind = Super, C, exec, code --password-store=gnome --enable-features=UseOzonePlatform --ozone-platform=wayland # Launch VSCode (editor)
-bind = Super, E, exec, nautilus --new-window # Launch Nautilus (file manager)
-bind = Super+Alt, E, exec, thunar # [hidden]
-bind = Super, W, exec, google-chrome-stable # [hidden] Let's not give people (more) reason to shit on my rice
-bind = Ctrl+Super, W, exec, firefox # Launch Firefox (browser)
-bind = Super, X, exec, gnome-text-editor --new-window # Launch GNOME Text Editor
-bind = Super+Shift, W, exec, wps # Launch WPS Office
-bind = Super, I, exec, XDG_CURRENT_DESKTOP="gnome" gnome-control-center # Launch GNOME Settings
+bind = Super, T, exec, kitty
+bind = Super, E, exec, thunar
bind = Ctrl+Super, V, exec, pavucontrol # Launch pavucontrol (volume mixer)
bind = Ctrl+Super+Shift, V, exec, easyeffects # Launch EasyEffects (equalizer & other audio effects)
-bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # Launch GNOME System monitor
bind = Ctrl+Super, Slash, exec, pkill anyrun || anyrun # Toggle fallback launcher: anyrun
bind = Super+Alt, Slash, exec, pkill anyrun || fuzzel # Toggle fallback launcher: fuzzel
# Cursed stuff
## Make window not amogus large
bind = Ctrl+Super, Backslash, resizeactive, exact 640 480 # [hidden]
-
-
diff --git a/kde/kwinrulesrc b/kde/kwinrulesrc
deleted file mode 100644
index d1ac215f..00000000
--- a/kde/kwinrulesrc
+++ /dev/null
@@ -1,114 +0,0 @@
-[$Version]
-update_info=kwinrules.upd:replace-placement-string-to-enum,kwinrules.upd:use-virtual-desktop-ids
-
-[0004949b-c1bc-45c2-9d32-d9b11b827395]
-Description=Linear
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-desktops=1a831ee1-83fa-455a-aa49-81ac9129a9e4
-desktopsrule=3
-wmclass=Linear
-wmclassmatch=2
-
-[1]
-Description=Window settings for webapp-linear7107
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-clientmachine=localhost
-desktops=1a831ee1-83fa-455a-aa49-81ac9129a9e4
-desktopsrule=3
-maximizehoriz=true
-maximizehorizrule=3
-maximizevert=true
-maximizevertrule=3
-title=Active issues / Johntech
-types=1
-windowrole=pop-up
-windowrolematch=1
-wmclass=linear.app__johntech_team_joh_active webapp-linear7107
-wmclasscomplete=true
-wmclassmatch=1
-
-[2]
-Description=Window settings for raindrop.io
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-clientmachine=localhost
-desktops=822369a9-a47e-4da1-aec7-af2d4f5554a9
-desktopsrule=3
-maximizehoriz=true
-maximizehorizrule=3
-maximizevert=true
-maximizevertrule=3
-skiptaskbarrule=3
-title=All bookmarks
-types=1
-windowrole=browser-window
-windowrolematch=1
-wmclass=raindrop.io
-wmclassmatch=1
-
-[3]
-Description=Linear
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-desktops=1a831ee1-83fa-455a-aa49-81ac9129a9e4
-desktopsrule=3
-wmclass=Linear
-wmclassmatch=2
-
-[4ef091ca-d052-4d7f-8b79-07e6e04b010d]
-Description=Window settings for webapp-linear7107
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-clientmachine=localhost
-desktops=1a831ee1-83fa-455a-aa49-81ac9129a9e4
-desktopsrule=3
-maximizehoriz=true
-maximizehorizrule=3
-maximizevert=true
-maximizevertrule=3
-title=Active issues / Johntech
-types=1
-windowrole=pop-up
-windowrolematch=1
-wmclass=linear.app__johntech_team_joh_active webapp-linear7107
-wmclasscomplete=true
-wmclassmatch=1
-
-[7ec468c0-ef2d-4af6-80c1-5edbdea051bc]
-Description=Window settings for raindrop.io
-activity=13e0b659-cf05-483c-b4aa-6f32d4ad2d12,5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-clientmachine=localhost
-desktops=822369a9-a47e-4da1-aec7-af2d4f5554a9
-desktopsrule=3
-title=All bookmarks
-types=1
-windowrole=browser-window
-windowrolematch=1
-wmclass=raindrop.io
-wmclassmatch=1
-
-[91123e2e-0f20-4d28-bbd4-5c5892981d79]
-Description=Window settings for webapp-calendar9598
-activity=5cde7088-afdf-4887-90b8-ea948145d41a
-activityrule=3
-clientmachine=localhost
-desktops=5c45721d-d383-4407-9fbf-6ce3d647d99f
-desktopsrule=3
-maximizehoriz=true
-maximizehorizrule=3
-maximizevert=true
-maximizevertrule=3
-title=Google Calendar - Week of 20 February 2023
-types=1
-windowrole=pop-up
-windowrolematch=1
-wmclass=calendar.google.com webapp-calendar9598
-wmclasscomplete=true
-wmclassmatch=1
-
-[General]
-count=4
-rules=91123e2e-0f20-4d28-bbd4-5c5892981d79,1,2,3
diff --git a/kde/plasma-org.kde.plasma.desktop-appletsrc b/kde/plasma-org.kde.plasma.desktop-appletsrc
deleted file mode 100644
index bf02ffc7..00000000
--- a/kde/plasma-org.kde.plasma.desktop-appletsrc
+++ /dev/null
@@ -1,296 +0,0 @@
-[ActionPlugins][0]
-MiddleButton;NoModifier=org.kde.paste
-RightButton;NoModifier=org.kde.contextmenu
-wheel:Vertical;NoModifier=org.kde.switchdesktop
-
-[ActionPlugins][1]
-RightButton;NoModifier=org.kde.contextmenu
-
-[Containments][100]
-activityId=
-formfactor=2
-immutability=1
-lastScreen=0
-location=4
-plugin=org.kde.panel
-wallpaperplugin=org.kde.image
-
-[Containments][100][Applets][101]
-immutability=1
-plugin=org.kde.plasma.icontasks
-
-[Containments][100][Applets][101][Configuration][General]
-fill=false
-groupedTaskVisualization=2
-iconSpacing=2
-launchers=preferred://browser,applications:systemsettings.desktop,preferred://filemanager,applications:spotify.desktop,applications:discord.desktop,applications:obsidian.desktop,applications:org.mozilla.Thunderbird.desktop,applications:kitty.desktop
-maxStripes=1
-showOnlyCurrentActivity=true
-showOnlyCurrentDesktop=false
-showOnlyCurrentScreen=false
-wheelSkipMinimized=false
-
-[Containments][100][ConfigDialog]
-DialogHeight=52
-DialogWidth=2560
-
-[Containments][100][General]
-AppletOrder=101
-
-[Containments][102]
-ItemGeometries-0x0=
-ItemGeometries-2560x1440=
-ItemGeometriesHorizontal=
-activityId=6d3ebc99-3141-4c16-8a69-c1f4226e7443
-formfactor=0
-immutability=1
-lastScreen=0
-location=0
-plugin=org.kde.plasma.folder
-wallpaperplugin=org.kde.image
-
-[Containments][102][General]
-ToolBoxButtonState=topcenter
-ToolBoxButtonX=825
-ToolBoxButtonY=36
-
-[Containments][50]
-activityId=
-formfactor=2
-immutability=1
-lastScreen=0
-location=3
-plugin=org.kde.panel
-wallpaperplugin=org.kde.image
-
-[Containments][50][Applets][107]
-immutability=1
-plugin=org.kde.plasma.digitalclock
-
-[Containments][50][Applets][107][Configuration]
-PreloadWeight=100
-popupHeight=640
-popupWidth=1280
-
-[Containments][50][Applets][107][Configuration][Appearance]
-customDateFormat=ddd d MMM
-dateDisplayFormat=BesideTime
-dateFormat=custom
-enabledCalendarPlugins=astronomicalevents,holidaysevents
-firstDayOfWeek=0
-showWeekNumbers=true
-use24hFormat=2
-
-[Containments][50][Applets][107][Configuration][ConfigDialog]
-DialogHeight=480
-DialogWidth=640
-
-[Containments][50][Applets][51]
-immutability=1
-plugin=org.kde.plasma.kickoff
-
-[Containments][50][Applets][51][Configuration]
-PreloadWeight=100
-popupHeight=703
-popupWidth=1344
-
-[Containments][50][Applets][51][Configuration][ConfigDialog]
-DialogHeight=510
-DialogWidth=680
-
-[Containments][50][Applets][51][Configuration][General]
-favoritesPortedToKAstats=true
-icon=archlinux
-lengthFirstMargin=7
-systemFavorites=suspend\\,hibernate\\,reboot\\,shutdown
-
-[Containments][50][Applets][51][Configuration][Shortcuts]
-global=Alt+F1
-
-[Containments][50][Applets][55]
-immutability=1
-plugin=org.kde.plasma.systemtray
-
-[Containments][50][Applets][55][Configuration]
-PreloadWeight=100
-SystrayContainmentId=56
-
-[Containments][50][Applets][81]
-immutability=1
-plugin=org.kde.plasma.pager
-
-[Containments][50][Applets][89]
-immutability=1
-plugin=org.kde.plasma.panelspacer
-
-[Containments][50][Applets][90]
-immutability=1
-plugin=org.kde.plasma.panelspacer
-
-[Containments][50][ConfigDialog]
-DialogHeight=52
-DialogWidth=2560
-
-[Containments][50][Configuration]
-PreloadWeight=42
-
-[Containments][50][General]
-AppletOrder=51;81;89;107;90;55
-
-[Containments][56]
-activityId=
-formfactor=2
-immutability=1
-lastScreen=0
-location=3
-plugin=org.kde.plasma.private.systemtray
-popupHeight=896
-popupWidth=939
-wallpaperplugin=org.kde.image
-
-[Containments][56][Applets][103]
-immutability=1
-plugin=org.kde.plasma.cameraindicator
-
-[Containments][56][Applets][103][Configuration]
-PreloadWeight=100
-selectedConfig=0
-
-[Containments][56][Applets][104]
-immutability=1
-plugin=org.kde.plasma.brightness
-
-[Containments][56][Applets][104][Configuration]
-PreloadWeight=0
-
-[Containments][56][Applets][108]
-immutability=1
-plugin=org.kde.plasma.bluetooth
-
-[Containments][56][Applets][108][Configuration]
-PreloadWeight=100
-
-[Containments][56][Applets][57]
-immutability=1
-plugin=org.kde.kdeconnect
-
-[Containments][56][Applets][57][Configuration]
-PreloadWeight=70
-
-[Containments][56][Applets][58]
-immutability=1
-plugin=org.kde.kscreen
-
-[Containments][56][Applets][59]
-immutability=1
-plugin=org.kde.plasma.clipboard
-
-[Containments][56][Applets][59][Configuration]
-PreloadWeight=100
-
-[Containments][56][Applets][60]
-immutability=1
-plugin=org.kde.plasma.devicenotifier
-
-[Containments][56][Applets][60][Configuration]
-PreloadWeight=85
-
-[Containments][56][Applets][61]
-immutability=1
-plugin=org.kde.plasma.keyboardindicator
-
-[Containments][56][Applets][62]
-immutability=1
-plugin=org.kde.plasma.keyboardlayout
-
-[Containments][56][Applets][62][Configuration]
-PreloadWeight=100
-
-[Containments][56][Applets][63]
-immutability=1
-plugin=org.kde.plasma.notifications
-
-[Containments][56][Applets][63][Configuration]
-PreloadWeight=100
-
-[Containments][56][Applets][64]
-immutability=1
-plugin=org.kde.plasma.vault
-
-[Containments][56][Applets][65]
-immutability=1
-plugin=org.kde.plasma.volume
-
-[Containments][56][Applets][65][Configuration]
-PreloadWeight=100
-
-[Containments][56][Applets][65][Configuration][General]
-migrated=true
-
-[Containments][56][Applets][66]
-immutability=1
-plugin=org.kde.plasma.manage-inputmethod
-
-[Containments][56][Applets][67]
-immutability=1
-plugin=org.kde.kalendar.contact
-
-[Containments][56][Applets][72]
-immutability=1
-plugin=org.kde.plasma.nightcolorcontrol
-
-[Containments][56][Applets][73]
-immutability=1
-plugin=org.kde.plasma.battery
-
-[Containments][56][Applets][73][Configuration]
-PreloadWeight=0
-
-[Containments][56][Applets][74]
-immutability=1
-plugin=org.kde.plasma.networkmanagement
-
-[Containments][56][Applets][74][Configuration]
-PreloadWeight=90
-
-[Containments][56][Applets][82]
-immutability=1
-plugin=org.kde.plasma.mediacontroller
-
-[Containments][56][Applets][82][Configuration]
-PreloadWeight=0
-
-[Containments][56][General]
-extraItems=org.kde.kdeconnect,org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.bluetooth,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardindicator,org.kde.plasma.keyboardlayout,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.plasma.manage-inputmethod,org.kde.plasma.brightness,org.kde.plasma.cameraindicator
-knownItems=org.kde.kdeconnect,org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.bluetooth,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardindicator,org.kde.plasma.keyboardlayout,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.plasma.manage-inputmethod,org.kde.plasma.brightness,org.kde.plasma.cameraindicator
-
-[Containments][91]
-ItemGeometries-1024x768=
-ItemGeometries-2560x1440=
-ItemGeometriesHorizontal=
-activityId=d91c9e31-0118-4af1-bfe6-ba1752d2e244
-formfactor=0
-immutability=1
-lastScreen=0
-location=0
-plugin=org.kde.plasma.folder
-wallpaperplugin=org.kde.image
-
-[Containments][91][ConfigDialog]
-DialogHeight=480
-DialogWidth=640
-
-[Containments][91][General]
-ToolBoxButtonState=topcenter
-ToolBoxButtonX=774
-ToolBoxButtonY=36
-positions={"2560x1440":[]}
-sortMode=-1
-
-[Containments][91][Wallpaper][org.kde.image][General]
-Image=/usr/share/wallpapers/garuda-wallpapers/Totem.jpg
-SlidePaths=/home/john/.local/share/wallpapers/,/usr/share/wallpapers/
-
-[ScreenMapping]
-itemsOnDisabledScreens=
-screenMapping=
diff --git a/kde/webapp-Calendar9598.desktop b/kde/webapp-Calendar9598.desktop
deleted file mode 100644
index 19d9ca42..00000000
--- a/kde/webapp-Calendar9598.desktop
+++ /dev/null
@@ -1,19 +0,0 @@
-[Desktop Entry]
-Version=1.0
-Name=Calendar
-Comment=Web App
-Exec=google-chrome-stable --app="https://calendar.google.com" --class=WebApp-Calendar9598 --user-data-dir=/home/johnc/.local/share/ice/profiles/Calendar9598
-Terminal=false
-X-MultipleArgs=false
-Type=Application
-Icon=web-google-calendar
-Categories=GTK;WebApps;
-MimeType=text/html;text/xml;application/xhtml_xml;
-StartupWMClass=WebApp-Calendar9598
-StartupNotify=true
-X-WebApp-Browser=Chrome
-X-WebApp-URL=https://calendar.google.com
-X-WebApp-CustomParameters=
-X-WebApp-Navbar=false
-X-WebApp-PrivateWindow=false
-X-WebApp-Isolated=true
diff --git a/kde/webapp-Linear7107.desktop b/kde/webapp-Linear7107.desktop
deleted file mode 100644
index eb0b4ccd..00000000
--- a/kde/webapp-Linear7107.desktop
+++ /dev/null
@@ -1,20 +0,0 @@
-[Desktop Entry]
-Version=1.0
-Name=Linear
-Comment=Web App
-Exec=google-chrome-stable --app="https://linear.app/johntech/team/JOH/active" --class=WebApp-Linear7107 --user-data-dir=/home/johnc/.local/share/ice/profiles/Linear7107
-Terminal=false
-X-MultipleArgs=false
-Type=Application
-Icon=OpenTodoList
-Categories=GTK;WebApps;
-MimeType=text/html;text/xml;application/xhtml_xml;
-StartupWMClass=WebApp-Linear7107
-StartupNotify=true
-X-WebApp-Browser=Chrome
-X-WebApp-URL=https://linear.app/johntech/team/JOH/active
-X-WebApp-CustomParameters=
-X-WebApp-Navbar=false
-X-WebApp-PrivateWindow=false
-X-WebApp-Isolated=true
-
diff --git a/keys.kksrc b/keys.kksrc
deleted file mode 100644
index 6a779531..00000000
--- a/keys.kksrc
+++ /dev/null
@@ -1,458 +0,0 @@
-[ActivityManager][Global Shortcuts]
-switch-to-activity-13e0b659-cf05-483c-b4aa-6f32d4ad2d12=
-switch-to-activity-5cde7088-afdf-4887-90b8-ea948145d41a=
-
-[KDE Keyboard Layout Switcher][Global Shortcuts]
-Switch keyboard layout to English (UK)=
-Switch keyboard layout to English (US)=
-Switch to Next Keyboard Layout=Ctrl+Alt+K
-
-[StandardShortcuts]
-AboutApp=
-AboutKDE=
-Activate Next Tab=Ctrl+PgDown; Ctrl+]
-Activate Previous Tab=Ctrl+PgUp; Ctrl+[
-ActualSize=Ctrl+0
-AddBookmark=Ctrl+B
-Back=Back; Alt+Left
-BackwardWord=Ctrl+Left
-Begin=Ctrl+Home
-BeginningOfLine=Home
-Clear=
-Close=Ctrl+W; Ctrl+Esc
-ConfigureNotifications=
-ConfigureToolbars=
-Copy=Ctrl+C; Ctrl+Ins
-CreateFolder=F10
-Cut=Ctrl+X; Shift+Del
-DeleteFile=Shift+Del
-DeleteWordBack=Ctrl+Backspace
-DeleteWordForward=Ctrl+Del
-Deselect=Ctrl+Shift+A
-DocumentBack=Alt+Shift+Left
-DocumentForward=Alt+Shift+Right
-Donate=
-EditBookmarks=
-End=Ctrl+End
-EndOfLine=End
-Find=Ctrl+F
-FindNext=F3
-FindPrev=Shift+F3
-FitToHeight=
-FitToPage=
-FitToWidth=
-Forward=Forward; Alt+Right
-ForwardWord=Ctrl+Right
-FullScreen=Ctrl+Shift+F
-Goto=
-GotoLine=Ctrl+G
-GotoPage=
-Help=F1
-Home=Alt+Home; Home Page
-KeyBindings=Ctrl+Alt+,
-Mail=
-MoveToTrash=Del
-New=Ctrl+N
-Next=PgDown
-NextCompletion=Ctrl+Down
-Open=Ctrl+O
-OpenRecent=
-Paste=Ctrl+V; Shift+Ins
-Paste Selection=Ctrl+Shift+Ins
-Preferences=Ctrl+Shift+,
-PrevCompletion=Ctrl+Up
-Print=Ctrl+P
-PrintPreview=
-Prior=PgUp
-Quit=Ctrl+Q
-Redo=Ctrl+Shift+Z
-Reload=F5; Refresh
-RenameFile=F2
-Replace=Ctrl+R
-ReportBug=
-Revert=
-RotateDown=Down
-RotateUp=Up
-Save=Ctrl+S
-SaveAs=Ctrl+Shift+S
-SaveOptions=
-SelectAll=Ctrl+A
-ShowHideHiddenFiles=Ctrl+H; Alt+.
-ShowMenubar=Ctrl+M
-ShowStatusbar=
-ShowToolbar=
-Spelling=
-SubstringCompletion=Ctrl+T
-SwitchApplicationLanguage=
-TextCompletion=Ctrl+E
-TipofDay=
-Undo=Ctrl+Z
-Up=Alt+Up
-WhatsThis=Shift+F1
-Zoom=
-ZoomIn=Ctrl+=; Ctrl++
-ZoomOut=Ctrl+-
-
-[bismuth][Global Shortcuts]
-decrease_master_size=
-decrease_master_win_count=
-decrease_window_height=
-decrease_window_width=
-focus_bottom_window=
-focus_left_window=
-focus_next_window=
-focus_prev_window=
-focus_right_window=
-focus_upper_window=
-increase_master_size=
-increase_master_win_count=
-increase_window_height=
-increase_window_width=
-move_window_to_bottom_pos=
-move_window_to_left_pos=
-move_window_to_next_pos=
-move_window_to_prev_pos=
-move_window_to_right_pos=
-move_window_to_upper_pos=
-next_layout=
-prev_layout=
-push_window_to_master=
-rotate=
-rotate_part=
-rotate_reverse=
-toggle_float_layout=
-toggle_monocle_layout=
-toggle_quarter_layout=
-toggle_spiral_layout=
-toggle_spread_layout=
-toggle_stair_layout=
-toggle_three_column_layout=
-toggle_tile_layout=
-toggle_window_floating=
-
-[kaccess][Global Shortcuts]
-Toggle Screen Reader On and Off=Meta+Alt+S
-
-[kcm_touchpad][Global Shortcuts]
-Disable Touchpad=Touchpad Off
-Enable Touchpad=Touchpad On
-Toggle Touchpad=Touchpad Toggle
-
-[kded5][Global Shortcuts]
-Show System Activity=Ctrl+Esc
-display=Display; Meta+P
-
-[khotkeys][Global Shortcuts]
-{d03619b6-9b3c-48cc-9d9c-a2aadb485550}=
-
-[kmix][Global Shortcuts]
-decrease_microphone_volume=Microphone Volume Down
-decrease_volume=Volume Down
-increase_microphone_volume=Microphone Volume Up
-increase_volume=Volume Up
-mic_mute=Microphone Mute; Meta+Volume Mute
-mute=Volume Mute
-
-[ksmserver][Global Shortcuts]
-Halt Without Confirmation=
-Lock Session=Ctrl+Alt+L; Meta+L; Screensaver
-Log Out=Ctrl+Alt+Del
-Log Out Without Confirmation=
-Reboot Without Confirmation=
-
-[kwin][Global Shortcuts]
-Activate Window Demanding Attention=Ctrl+Alt+A
-Decrease Opacity=
-Edit Tiles=Meta+T
-Expose=Ctrl+F9
-ExposeAll=Launch (C); Ctrl+F10
-ExposeClass=Ctrl+F7
-ExposeClassCurrentDesktop=
-Increase Opacity=
-Invert Screen Colors=
-Kill Window=Ctrl+Alt+Esc
-MoveMouseToCenter=Meta+F6
-MoveMouseToFocus=Meta+F5
-MoveZoomDown=
-MoveZoomLeft=
-MoveZoomRight=
-MoveZoomUp=
-Overview=Meta+Space
-Setup Window Shortcut=
-Show Desktop=Meta+D
-ShowDesktopGrid=Ctrl+F8
-Suspend Compositing=Alt+Shift+F12
-Switch One Desktop Down=Ctrl+Alt+Down
-Switch One Desktop Up=Ctrl+Alt+Up
-Switch One Desktop to the Left=Ctrl+Alt+Left
-Switch One Desktop to the Right=Ctrl+Alt+Right
-Switch Window Down=Meta+Alt+Down
-Switch Window Left=Meta+Alt+Left
-Switch Window Right=Meta+Alt+Right
-Switch Window Up=Meta+Alt+Up
-Switch to Desktop 1=Ctrl+F1
-Switch to Desktop 10=
-Switch to Desktop 11=
-Switch to Desktop 12=
-Switch to Desktop 13=
-Switch to Desktop 14=
-Switch to Desktop 15=
-Switch to Desktop 16=
-Switch to Desktop 17=
-Switch to Desktop 18=
-Switch to Desktop 19=
-Switch to Desktop 2=Ctrl+F2
-Switch to Desktop 20=
-Switch to Desktop 3=Ctrl+F3
-Switch to Desktop 4=Ctrl+F4
-Switch to Desktop 5=
-Switch to Desktop 6=
-Switch to Desktop 7=
-Switch to Desktop 8=
-Switch to Desktop 9=
-Switch to Next Desktop=
-Switch to Next Screen=
-Switch to Previous Desktop=
-Switch to Previous Screen=
-Switch to Screen 0=
-Switch to Screen 1=
-Switch to Screen 2=
-Switch to Screen 3=
-Switch to Screen 4=
-Switch to Screen 5=
-Switch to Screen 6=
-Switch to Screen 7=
-Switch to Screen Above=
-Switch to Screen Below=
-Switch to Screen to the Left=
-Switch to Screen to the Right=
-Toggle Night Color=
-Toggle Window Raise/Lower=
-Walk Through Desktop List=
-Walk Through Desktop List (Reverse)=
-Walk Through Desktops=
-Walk Through Desktops (Reverse)=
-Walk Through Windows=Alt+Tab
-Walk Through Windows (Reverse)=Alt+Shift+Backtab
-Walk Through Windows Alternative=
-Walk Through Windows Alternative (Reverse)=
-Walk Through Windows of Current Application=Alt+`
-Walk Through Windows of Current Application (Reverse)=Alt+~
-Walk Through Windows of Current Application Alternative=
-Walk Through Windows of Current Application Alternative (Reverse)=
-Window Above Other Windows=
-Window Below Other Windows=
-Window Close=Ctrl+Num+.; Ctrl+/; Alt+F4; Ctrl+Num+Del; Menu
-Window Fullscreen=
-Window Grow Horizontal=
-Window Grow Vertical=
-Window Lower=
-Window Maximize=Meta+PgUp
-Window Maximize Horizontal=
-Window Maximize Vertical=
-Window Minimize=Meta+PgDown
-Window Move=
-Window Move Center=
-Window No Border=
-Window On All Desktops=
-Window One Desktop Down=Meta+Ctrl+Down
-Window One Desktop Up=Meta+Ctrl+Up
-Window One Desktop to the Left=Meta+Ctrl+Left
-Window One Desktop to the Right=Meta+Ctrl+Right
-Window One Screen Down=
-Window One Screen Up=
-Window One Screen to the Left=
-Window One Screen to the Right=
-Window Operations Menu=Alt+F3
-Window Pack Down=
-Window Pack Left=
-Window Pack Right=
-Window Pack Up=
-Window Quick Tile Bottom=Meta+Down
-Window Quick Tile Bottom Left=Meta+Num+1
-Window Quick Tile Bottom Right=Meta+Num+3
-Window Quick Tile Left=Meta+Left
-Window Quick Tile Right=Meta+Right
-Window Quick Tile Top=Meta+Up
-Window Quick Tile Top Left=Meta+Num+7
-Window Quick Tile Top Right=Meta+Num+9
-Window Raise=
-Window Resize=
-Window Shade=
-Window Shrink Horizontal=
-Window Shrink Vertical=
-Window to Desktop 1=
-Window to Desktop 10=
-Window to Desktop 11=
-Window to Desktop 12=
-Window to Desktop 13=
-Window to Desktop 14=
-Window to Desktop 15=
-Window to Desktop 16=
-Window to Desktop 17=
-Window to Desktop 18=
-Window to Desktop 19=
-Window to Desktop 2=
-Window to Desktop 20=
-Window to Desktop 3=
-Window to Desktop 4=
-Window to Desktop 5=
-Window to Desktop 6=
-Window to Desktop 7=
-Window to Desktop 8=
-Window to Desktop 9=
-Window to Next Desktop=
-Window to Next Screen=Meta+Shift+Right
-Window to Previous Desktop=
-Window to Previous Screen=Meta+Shift+Left
-Window to Screen %1=
-Window to Screen 0=Meta+Ctrl+Alt+Right
-Window to Screen 1=Meta+Ctrl+Alt+Left
-Window to Screen 2=
-Window to Screen 3=
-Window to Screen 4=
-Window to Screen 5=
-Window to Screen 6=
-Window to Screen 7=
-view_actual_size=
-view_zoom_in=Meta+=
-view_zoom_out=Meta+-
-
-[lattedock][Global Shortcuts]
-activate entry 1=
-activate entry 10=
-activate entry 11=Meta+Z
-activate entry 12=Meta+X
-activate entry 13=Meta+C
-activate entry 14=Meta+V
-activate entry 15=Meta+B
-activate entry 16=Meta+N
-activate entry 17=Meta+M
-activate entry 18=Meta+,
-activate entry 19=
-activate entry 2=
-activate entry 3=
-activate entry 4=
-activate entry 5=
-activate entry 6=
-activate entry 7=
-activate entry 8=
-activate entry 9=
-clear-history=
-clipboard_action=
-cycleNextAction=
-cyclePrevAction=
-edit_clipboard=
-new instance for entry 1=Meta+Ctrl+1
-new instance for entry 10=Meta+Ctrl+0
-new instance for entry 11=Meta+Ctrl+Z
-new instance for entry 12=Meta+Ctrl+X
-new instance for entry 13=Meta+Ctrl+C
-new instance for entry 14=Meta+Ctrl+V
-new instance for entry 15=Meta+Ctrl+B
-new instance for entry 16=Meta+Ctrl+N
-new instance for entry 17=Meta+Ctrl+M
-new instance for entry 18=Meta+Ctrl+,
-new instance for entry 19=Meta+Ctrl+.
-new instance for entry 2=Meta+Ctrl+2
-new instance for entry 3=Meta+Ctrl+3
-new instance for entry 4=Meta+Ctrl+4
-new instance for entry 5=Meta+Ctrl+5
-new instance for entry 6=Meta+Ctrl+6
-new instance for entry 7=Meta+Ctrl+7
-new instance for entry 8=Meta+Ctrl+8
-new instance for entry 9=Meta+Ctrl+9
-repeat_action=
-show latte global settings=Meta+W
-show latte view=Meta+`
-show view settings=Meta+A
-show-barcode=
-show-on-mouse-pos=
-
-[mediacontrol][Global Shortcuts]
-mediavolumedown=
-mediavolumeup=
-nextmedia=Media Next
-pausemedia=Media Pause
-playmedia=
-playpausemedia=Media Play
-previousmedia=Media Previous
-stopmedia=Media Stop
-
-[org.kde.dolphin.desktop][Global Shortcuts]
-_launch=Meta+E
-
-[org.kde.konsole.desktop][Global Shortcuts]
-NewTab=
-NewWindow=
-_launch=Ctrl+Alt+T
-
-[org.kde.krunner.desktop][Global Shortcuts]
-RunClipboard=Alt+Shift+F2
-_launch=Search; Alt+Space; Alt+F2
-
-[org.kde.plasma.emojier.desktop][Global Shortcuts]
-_launch=Meta+.
-
-[org.kde.spectacle.desktop][Global Shortcuts]
-ActiveWindowScreenShot=Meta+Print
-CurrentMonitorScreenShot=
-FullScreenScreenShot=Shift+Print
-OpenWithoutScreenshot=
-RectangularRegionScreenShot=Meta+Shift+Print
-WindowUnderCursorScreenShot=Meta+Ctrl+Print
-_launch=Print
-
-[org_kde_powerdevil][Global Shortcuts]
-Decrease Keyboard Brightness=Keyboard Brightness Down
-Decrease Screen Brightness=Monitor Brightness Down
-Hibernate=Hibernate
-Increase Keyboard Brightness=Keyboard Brightness Up
-Increase Screen Brightness=Monitor Brightness Up
-PowerDown=Power Down
-PowerOff=Power Off
-Sleep=Sleep
-Toggle Keyboard Backlight=Keyboard Light On/Off
-Turn Off Screen=
-
-[plasmashell][Global Shortcuts]
-activate task manager entry 1=Meta+1
-activate task manager entry 10=
-activate task manager entry 2=Meta+2
-activate task manager entry 3=Meta+3
-activate task manager entry 4=Meta+4
-activate task manager entry 5=Meta+5
-activate task manager entry 6=Meta+6
-activate task manager entry 7=Meta+7
-activate task manager entry 8=Meta+8
-activate task manager entry 9=Meta+9
-activate widget 2=
-activate widget 3=Alt+F1
-clear-history=
-clipboard_action=Ctrl+Alt+X
-cycle-panels=Meta+Alt+P
-cycleNextAction=
-cyclePrevAction=
-edit_clipboard=
-manage activities=Meta+Q
-next activity=Meta+Tab
-previous activity=Meta+Shift+Tab
-repeat_action=Ctrl+Alt+R
-show dashboard=Ctrl+F12
-show-barcode=
-show-on-mouse-pos=
-stop current activity=Meta+S
-switch to next activity=
-switch to previous activity=
-toggle do not disturb=
-
-[systemsettings.desktop][Global Shortcuts]
-_launch=Tools
-kcm-kscreen=
-kcm-lookandfeel=
-kcm-users=
-powerdevilprofilesconfig=
-screenlocker=
-
-[yakuake][Global Shortcuts]
-toggle-window-state=Num+-
diff --git a/kitty-themes/.all-contributorsrc b/kitty-themes/.all-contributorsrc
deleted file mode 100644
index c990c957..00000000
--- a/kitty-themes/.all-contributorsrc
+++ /dev/null
@@ -1,111 +0,0 @@
-{
- "files": [
- "README.md"
- ],
- "badgeTemplate": "[](#contributors)",
- "contributorTemplate": "\"><%= contributor.name %>
<%= contributions %>",
- "imageSize": 100,
- "commit": false,
- "contributors": [
- {
- "login": "scopatz",
- "name": "Anthony Scopatz",
- "avatar_url": "https://avatars2.githubusercontent.com/u/320553?v=4",
- "profile": "http://www.scopatz.com",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "orangecoloured",
- "name": "RCKT",
- "avatar_url": "https://avatars3.githubusercontent.com/u/3314891?v=4",
- "profile": "https://rckt.cc",
- "contributions": [
- "theme"
- ]
- },
- {
- "login": "varmanishant",
- "name": "varmanishant",
- "avatar_url": "https://avatars1.githubusercontent.com/u/4084912?v=4",
- "profile": "https://github.com/varmanishant",
- "contributions": [
- "theme"
- ]
- },
- {
- "login": "rlerdorf",
- "name": "Rasmus Lerdorf",
- "avatar_url": "https://avatars3.githubusercontent.com/u/54641?v=4",
- "profile": "https://github.com/rlerdorf",
- "contributions": [
- "bug",
- "ideas"
- ]
- },
- {
- "login": "Luflosi",
- "name": "Luflosi",
- "avatar_url": "https://avatars1.githubusercontent.com/u/15217907?v=4",
- "profile": "https://github.com/Luflosi",
- "contributions": [
- "fix",
- "question",
- "doc"
- ]
- },
- {
- "login": "connorholyday",
- "name": "Connor Holyday",
- "avatar_url": "https://avatars1.githubusercontent.com/u/4559119?v=4",
- "profile": "https://holyday.me",
- "contributions": [
- "fix"
- ]
- },
- {
- "login": "BlueDrink9",
- "name": "BlueDrink9",
- "avatar_url": "https://avatars3.githubusercontent.com/u/26474254?v=4",
- "profile": "https://github.com/BlueDrink9",
- "contributions": [
- "bug"
- ]
- },
- {
- "login": "brujoand",
- "name": "Anders Brujordet",
- "avatar_url": "https://avatars1.githubusercontent.com/u/124421?v=4",
- "profile": "https://github.com/brujoand",
- "contributions": [
- "theme"
- ]
- },
- {
- "login": "rjshrjndrn",
- "name": "Rajesh Rajendran",
- "avatar_url": "https://avatars3.githubusercontent.com/u/2563385?v=4",
- "profile": "http://www.hackouts.com",
- "contributions": [
- "fix"
- ]
- }
- ],
- "types": {
- "theme": {
- "symbol": "😻",
- "description": "New theme added to the collection"
- },
- "fix": {
- "symbol": "🛠️",
- "description": "Fixed a theme"
- }
- },
- "contributorsPerLine": 7,
- "projectName": "kitty-themes",
- "projectOwner": "dexpota",
- "repoType": "github",
- "repoHost": "https://github.com",
- "commitConvention": "none"
-}
diff --git a/kitty-themes/.tools/README.md b/kitty-themes/.tools/README.md
deleted file mode 100644
index 73211553..00000000
--- a/kitty-themes/.tools/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# kitty-tools
-> A bunch of useful scripts used to convert schemes and generate files.
-
-## Process overview
-
-1. Find a nice theme and check the licensing, is it possible to distribute the theme?
-2. Generate the configuration file for **kitty**;
-3. Add a new preview for the theme;
-4. Update the README.md;
diff --git a/kitty-themes/.tools/color_table.sh b/kitty-themes/.tools/color_table.sh
deleted file mode 100755
index b7b4559b..00000000
--- a/kitty-themes/.tools/color_table.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# This file echoes a bunch of color codes to the
-# terminal to demonstrate what's available. Each
-# line is the color code of one forground color,
-# out of 17 (default + 16 escapes), followed by a
-# test use of that color on all nine background
-# colors (default + 8 escapes).
-#
-
-T='gYw' # The test text
-
-echo -e "\n 40m 41m 42m 43m\
- 44m 45m 46m 47m";
-
-for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \
- '1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \
- ' 36m' '1;36m' ' 37m' '1;37m';
- do FG=${FGs// /}
- echo -en " $FGs \033[$FG $T "
- for BG in 40m 41m 42m 43m 44m 45m 46m 47m;
- do echo -en "$EINS \033[$FG\033[$BG $T \033[0m";
- done
- echo;
-done
-echo
diff --git a/kitty-themes/.tools/convert.py b/kitty-themes/.tools/convert.py
deleted file mode 100644
index 3599ddcf..00000000
--- a/kitty-themes/.tools/convert.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import json
-from jinja2 import FileSystemLoader, Environment
-import sys
-import os
-
-
-def removeAlpha(value):
- hex = value.lstrip("#")
- return "#" + hex[0:6]
-
-
-filename = sys.argv[1]
-
-kitty_configuration = os.path.splitext(filename)[0] + ".conf"
-
-with open(filename, "r") as configuration_file:
- configuration = json.load(configuration_file)
-
-loader = FileSystemLoader(".")
-env = Environment(loader=loader)
-
-env.filters['removeAlpha'] = removeAlpha
-env.trim_blocks = True
-
-template = env.get_template("template.conf.j2")
-
-output = template.render(**configuration)
-
-with open(kitty_configuration, "w") as fp:
- fp.write(output)
diff --git a/kitty-themes/.tools/convert_conf.swift b/kitty-themes/.tools/convert_conf.swift
deleted file mode 100755
index b023f569..00000000
--- a/kitty-themes/.tools/convert_conf.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/swift
-// main.swift
-// nscolor
-//
-// Created by Fabrizio FD. Destro on 28/12/18.
-// Copyright © 2018 Fabrizio FD. Destro. All rights reserved.
-//
-
-import Foundation
-import AppKit
-
-func hex(color: NSColor) -> String {
- return String(format: "#%02x%02x%02x", Int(color.redComponent * 0xFF), Int(color.greenComponent * 0xFF), Int(color.blueComponent * 0xFF))
-}
-
-func process_color(field: String, data: Data) {
- let color = NSKeyedUnarchiver.unarchiveObject(with: data) as! NSColor
- print("\(field) \(hex(color: color))");
-}
-
-func generate_conf_line(field: String, key: String, dictionary: NSDictionary){
- if let data = dictionary[key] {
- process_color(field: field, data: data as! Data)
- }
-}
-
-func process(filename: String) {
- let plist = NSDictionary(contentsOfFile: filename)!
-
- generate_conf_line(field: "background", key: "BackgroundColor", dictionary: plist)
- generate_conf_line(field: "foreground", key: "TextColor", dictionary: plist)
- generate_conf_line(field: "cursor", key: "CursorColor", dictionary: plist)
- generate_conf_line(field: "selection_background", key: "SelectionColor", dictionary: plist)
- generate_conf_line(field: "color0", key: "ANSIBlackColor", dictionary: plist)
- generate_conf_line(field: "color8", key: "ANSIBrightBlackColor", dictionary: plist)
- generate_conf_line(field: "color1", key: "ANSIRedColor", dictionary: plist)
- generate_conf_line(field: "color9", key: "ANSIBrightRedColor", dictionary: plist)
- generate_conf_line(field: "color2", key: "ANSIGreenColor", dictionary: plist)
- generate_conf_line(field: "color10", key: "ANSIBrightGreenColor", dictionary: plist)
- generate_conf_line(field: "color3", key: "ANSIYellowColor", dictionary: plist)
- generate_conf_line(field: "color11", key: "ANSIBrightYellowColor", dictionary: plist)
- generate_conf_line(field: "color4", key: "ANSIBlueColor", dictionary: plist)
- generate_conf_line(field: "color12", key: "ANSIBrightBlueColor", dictionary: plist)
- generate_conf_line(field: "color5", key: "ANSIMagentaColor", dictionary: plist)
- generate_conf_line(field: "color13", key: "ANSIBrightMagentaColor", dictionary: plist)
- generate_conf_line(field: "color6", key: "ANSICyanColor", dictionary: plist)
- generate_conf_line(field: "color14", key: "ANSIBrightCyanColor", dictionary: plist)
- generate_conf_line(field: "color7", key: "ANSIWhiteColor", dictionary: plist)
- generate_conf_line(field: "color15", key: "ANSIBrightWhiteColor", dictionary: plist)
-}
-
-if (CommandLine.argc == 2) {
- let filename = CommandLine.arguments[1]
- process(filename: filename)
-} else {
- print("Missing plist's path.")
-}
diff --git a/kitty-themes/.tools/extract-vscode.sh b/kitty-themes/.tools/extract-vscode.sh
deleted file mode 100755
index 71a69305..00000000
--- a/kitty-themes/.tools/extract-vscode.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-theme=$1
-
-jq ".colors | to_entries | map(select(.key | match(\"terminal.*\"))) | map({(.key | gsub(\"\\\\.(?.)\"; .a | ascii_upcase) | ltrimstr(\"terminal\") | sub(\"(?.)\"; .a | ascii_downcase)):.value}) | add" < "$theme" > "terminal/${theme%.*}.json"
diff --git a/kitty-themes/.tools/generate_conf.sh b/kitty-themes/.tools/generate_conf.sh
deleted file mode 100755
index db498b9a..00000000
--- a/kitty-themes/.tools/generate_conf.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env bash
-
-find ~/github/macos-terminal-themes/schemes/ -name "*.terminal" -print0 | while read -d $'\0' -r file; do
- filename=`basename "$file"`
- without_ext=${filename%.*}
- removed_spaces=${without_ext// /_}
- # output_filename=`echo ${removed_spaces} | tr '[:upper:]' '[:lower:]'`
- echo ${removed_spaces}.conf
- ./convert_conf.swift "$file" > ./themes/${removed_spaces}.conf
-done
diff --git a/kitty-themes/.tools/generate_theme_preview.sh b/kitty-themes/.tools/generate_theme_preview.sh
deleted file mode 100755
index 040867b3..00000000
--- a/kitty-themes/.tools/generate_theme_preview.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env bash
-# This script generate all preview images for the themes
-# In order to capture previews without the to bar start kitty without decorations
-# kitty -o hide_window_decorations=yes
-
-# shellcheck source=libcapture.sh
-source libcapture.sh
-
-# read theme path from args
-id=$1
-theme=$2
-preview_filename=$3
-
-conf_filename=$(basename "$theme")
-
-lockfile=$(mktemp)
-
-kitty @ set-colors --match id:"$id" "$theme"
-kitty @ send-text --match id:"$id" "clear && figlet -f digital -t \"$conf_filename\" && unbuffer ./color_table.sh && rm \"$lockfile\"\n"
-
-# simple sync mechanism, wait for the lockfile to be removed
-( echo "$lockfile" | entr "false" 1>/dev/null 2>&1 ) || capture themes "$preview_filename"
diff --git a/kitty-themes/.tools/generate_themes_previews.sh b/kitty-themes/.tools/generate_themes_previews.sh
deleted file mode 100755
index 63e5825e..00000000
--- a/kitty-themes/.tools/generate_themes_previews.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-# This script generate all preview images for the themes
-
-root="$(git rev-parse --show-toplevel)"
-tools="$root/.tools"
-
-PATH=$tools:$PATH
-
-# new kitty window, return its id
-id=$(kitty @ new-window --title themes --window-type os --cwd "$tools")
-# start bash without reading the profile nor the configuration
-kitty @ send-text --match id:"$id" "/usr/bin/env bash --noprofile --norc\n"
-kitty @ set-font-size 24
-
-# save all preview in this directory
-previews="$root/_previews"
-if [ ! -d "$previews" ]; then
- mkdir "$previews"
-fi
-
-while read -r theme
-do
- echo "Genereting theme preview for $theme"
- preview_directory=$previews/$(basename "${theme%.*}")
- [ ! -d "$preview_directory" ] && mkdir "$preview_directory"
- preview_filename=$previews/$(basename "${theme%.*}")/preview.png
- generate_theme_preview.sh "$id" "$theme" "$preview_filename"
- mogrify -resize 1024x\> "$preview_filename"
-done < /dev/stdin
-
-kitty @ close-window --match id:"$id"
-kitty @ set-font-size 16
diff --git a/kitty-themes/.tools/libcapture.sh b/kitty-themes/.tools/libcapture.sh
deleted file mode 100755
index ff515918..00000000
--- a/kitty-themes/.tools/libcapture.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env bash
-
-function capture_linux() {
- local title="$1"
- local output="$2"
- import -window "$title" "$output"
-}
-
-function capture_osx() {
- local title="$1"
- local output="$2"
- # get system id of the new created window
- sys_id=$(./windowid.swift "kitty" "$title")
- screencapture -wl"$sys_id" "$output"
-}
-
-function capture() {
- if [[ "$OSTYPE" == "linux-gnu" ]]; then
- capture_linux "$@"
- elif [[ "$OSTYPE" == "darwin"* ]]; then
- capture_osx "$@"
- fi
-}
diff --git a/kitty-themes/.tools/markdown.sh b/kitty-themes/.tools/markdown.sh
deleted file mode 100755
index 0fb08bae..00000000
--- a/kitty-themes/.tools/markdown.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-
-previews_root=$1
-# usually this value: https://raw.githubusercontent.com/dexpota/kitty-themes-website/master
-url_root=$2
-
-for f in $(find "$previews_root/previews" -maxdepth 1 -mindepth 1 -type d | sort); do
- preview_file="$f"/preview.png
- theme=$(basename $f)
- relative_path=$(realpath --relative-to="$previews_root" "$preview_file")
- header=`basename $theme | sed 's/_/ /g'`
- image=""
- echo \#\# $header
- echo $image
-done
diff --git a/kitty-themes/.tools/palette.py b/kitty-themes/.tools/palette.py
deleted file mode 100644
index f47fd9c1..00000000
--- a/kitty-themes/.tools/palette.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from argparse import ArgumentParser
-from svgwrite.shapes import Rect
-import svgwrite
-
-theme_keys = [
- "foreground", "background", "background_opacity", "dynamic_background_opacity", "dim_opacity",
- "selection_foreground", "selection_background", "color0", "color8", "color1", "color9", "color2", "color10",
- "color3", "color11", "color4", "color12", "color5", "color13", "color6", "color14", "color7", "color15"
-]
-
-
-def is_valid(line):
- """
- Returns true if a line inside a configuration file is a valid theme configuration pair: is not a comment, is not
- empty and the key is correct.
-
- :param line: a line inside the configuration file
- :type line: str
- :return: true if is valid, false otherwise
- :rtype: bool
- """
- return (not line.lstrip().startswith("#") # is not a comment
- and len(line.strip()) != 0 # is not empty
- and line.split(maxsplit=1)[0] in theme_keys) # key is a valid one
-
-
-def extract_configuration_pair(line):
- """
- Extract a configuration pair by splitting on spaces and taking the first couple of values.
-
- :param line: a line inside the configuration file
- :type line: str
- :return: a key-value pair
- :rtype: bool
- """
- split = line.split(maxsplit=2)
- return split[0], split[1]
-
-
-def read_configuration(filename):
- """
- Read a kitty configuration file and extract only theme related keys and values.
-
- :param filename: path to the configuration file
- :type filename: str
- :return: a map with theme related configuration values
- :rtype: dict[str, str]
- """
- with open(filename, "r") as fp:
- lines = fp.readlines()
- print(filename)
- theme_config = dict([extract_configuration_pair(line) for line in lines if is_valid(line)])
- return theme_config
-
-
-def draw_theme_palette(theme_configuration, start_point, size, displacement):
- rects = []
- for k, v in theme_configuration.items():
- rgb = tuple(int(v[i + 1:i + 3], 16) for i in (0, 2, 4))
- rects.append(Rect(start_point, size, fill=svgwrite.utils.rgb(rgb[0], rgb[1], rgb[2])))
- start_point = (start_point[0] + displacement[0], start_point[1] + displacement[1])
-
- return rects
-
-
-def draw_all_palettes(themes):
- dwg = svgwrite.Drawing('test.svg', profile='tiny')
- y = 0
- palettes = []
- for theme in themes:
- palettes += draw_theme_palette(theme, (0, y), (10, 10), (10, 0))
- y += 10
-
- for rect in palettes:
- dwg.add(rect)
- dwg.save()
-
-
-def main():
- parser = ArgumentParser()
- parser.add_argument("theme", type=str, nargs="+")
-
- ns = parser.parse_args()
-
- theme_configurations = [read_configuration(theme) for theme in ns.theme]
-
- draw_all_palettes(theme_configurations)
-
-
-if __name__ == "__main__":
- main()
diff --git a/kitty-themes/.tools/preview.py b/kitty-themes/.tools/preview.py
deleted file mode 100644
index c29387ae..00000000
--- a/kitty-themes/.tools/preview.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import sys
-import os
-import sys
-
-theme_keys = [
- "cursor", "foreground", "background", "background_opacity", "dynamic_background_opacity", "dim_opacity",
- "selection_foreground", "selection_background", "color0", "color8", "color1", "color9", "color2", "color10",
- "color3", "color11", "color4", "color12", "color5", "color13", "color6", "color14", "color7", "color15"
-]
-
-
-def is_valid(line):
- """
- Returns true if a line inside a configuration file is a valid theme configuration pair: is not a comment, is not
- empty and the key is correct.
-
- :param line: a line inside the configuration file
- :type line: str
- :return: true if is valid, false otherwise
- :rtype: bool
- """
- return (not line.lstrip().startswith("#") # is not a comment
- and len(line.strip()) != 0 # is not empty
- and line.split(maxsplit=1)[0] in theme_keys) # key is a valid one
-
-
-def extract_configuration_pair(line):
- """
- Extract a configuration pair by splitting on spaces and taking the first couple of values.
-
- :param line: a line inside the configuration file
- :type line: str
- :return: a key-value pair
- :rtype: bool
- """
- split = line.split(maxsplit=2)
- return split[0], split[1]
-
-
-def read_configuration(filename):
- """
- Read a kitty configuration file and extract only theme related keys and values.
-
- :param filename: path to the configuration file
- :type filename: str
- :return: a map with theme related configuration values
- :rtype: dict[str, str]
- """
- with open(filename, "r") as fp:
- lines = fp.readlines()
- theme_config = dict([extract_configuration_pair(line) for line in lines if is_valid(line)])
- return theme_config
-
-
-def fg(color, text):
- rgb = tuple(int(color[i + 1:i + 3], 16) for i in (0, 2, 4))
- return ('\x1b[38;2;%s;%s;%sm' % rgb + text + '\x1b[0m')
-
-
-def bg(color, text):
- rgb = tuple(int(color[i + 1:i + 3], 16) for i in (0, 2, 4))
- return ('\x1b[48;2;%s;%s;%sm' % rgb + text + '\x1b[0m')
-
-
-def print_preview(filename, configuration):
- cursor = configuration["cursor"]
- background = configuration["background"]
- foreground = configuration["foreground"]
-
- theme = os.path.basename(filename)
-
- size = len(theme) + (2 + 2 + 16 + 2 + 16 + 1 + 2)
- print(bg(background, " " * size))
- print(bg(background, " "), end="")
- print(bg(background, fg(foreground, theme)), end="")
- print(bg(background, " "), end="")
-
- c='a'
- for i in range(0, 16):
- color = configuration["color%d" % i]
- print(bg(background, fg(color, c)), end="")
- c = chr(ord(c) + 1)
-
- print(bg(background, " "), end="")
-
- selection_background = configuration["selection_background"]
- selection_foreground = configuration["selection_foreground"]
-
- c='A'
- for i in range(0, 16):
- print(bg(selection_background, fg(selection_foreground, c)), end="")
- c = chr(ord(c) + 1)
-
- print(bg(cursor, " "), end="")
- print(bg(background, " "))
-
- print(bg(background, " " * size))
-
- print(bg(background, " "), end="")
- print(bg(configuration["color0"], " "), end="")
- print(bg(configuration["color1"], " "), end="")
- print(bg(configuration["color2"], " "), end="")
- print(bg(configuration["color3"], " "), end="")
- print(bg(configuration["color4"], " "), end="")
- print(bg(configuration["color5"], " "), end="")
- print(bg(configuration["color6"], " "), end="")
- print(bg(configuration["color7"], " "), end="")
- print(bg(background, " "), end="")
- print(bg(configuration["color8"], " "), end="")
- print(bg(configuration["color9"], " "), end="")
- print(bg(configuration["color10"], " "), end="")
- print(bg(configuration["color11"], " "), end="")
- print(bg(configuration["color12"], " "), end="")
- print(bg(configuration["color13"], " "), end="")
- print(bg(configuration["color14"], " "), end="")
- print(bg(configuration["color15"], " "), end="")
- print(bg(background, " " * (size - 16 - 4)), end="")
- print()
-
- print(bg(background, " " * size))
- print()
-
-
-def main(directory):
- for filename in os.listdir(directory):
- try:
- path = os.path.join(directory, filename)
- configuration = read_configuration(path)
- print_preview(path, configuration)
- except Exception as e:
- print(e, file=sys.stderr)
- print("Error while processing %s" % filename, file=sys.stderr)
-
-
-if __name__ == "__main__":
- main(sys.argv[1])
diff --git a/kitty-themes/.tools/previews.sh b/kitty-themes/.tools/previews.sh
deleted file mode 100755
index c39c2c78..00000000
--- a/kitty-themes/.tools/previews.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-# @author: @vrdhn on github
-
-SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-cd $SCRIPT_DIR/..
-
-set_theme () {
- cat themes/$1.conf | awk 'BEGIN {printf("kitty @ set-colors ")} {printf( "%s=%s ",$1,$2 )} END{printf("\n")}' | sh
-}
-
-list=$(find themes -type f | grep "$1" | xargs basename | cut -d. -f1)
-
-for x in $list ;
-do
- kitty +kitten icat "previews/$x.png"
- read -n 1 -p "$x : Next / Set / Quit :" ans
- echo
-
- case $ans in
- n ) ;;
- s ) set_theme $x ; exit ;;
- q ) exit ;;
- esac
-done
diff --git a/kitty-themes/.tools/template.conf b/kitty-themes/.tools/template.conf
deleted file mode 100644
index 0af1722d..00000000
--- a/kitty-themes/.tools/template.conf
+++ /dev/null
@@ -1,37 +0,0 @@
-background #
-foreground #
-cursor #
-cursor_text_color #
-selection_foreground #
-selection_background #
-# black
-color0 #
-color8 #
-# red
-color1 #
-# light red
-color9 #
-# green
-color2 #
-# light green
-color10 #
-# yellow
-color3 #
-# light yellow
-color11 #
-# blue
-color4 #
-# light blue
-color12 #
-# magenta
-color5 #
-# light magenta
-color13 #
-# cyan
-color6 #
-# lighy cyan
-color14 #
-# light gray
-color7 #
-# dark gray
-color15 #
diff --git a/kitty-themes/.tools/template.conf.j2 b/kitty-themes/.tools/template.conf.j2
deleted file mode 100644
index 8ff9450b..00000000
--- a/kitty-themes/.tools/template.conf.j2
+++ /dev/null
@@ -1,91 +0,0 @@
-{% if background is defined %}
-background {{background}}
-{% endif %}
-{% if foreground is defined %}
-foreground {{foreground}}
-{% endif %}
-
-{% if cursorForeground is defined %}
-cursor {{cursorForeground}}
-{% endif %}
-{% if cursorBackground is defined %}
-cursor_text_color {{cursorBackground | removeAlpha}}
-{% endif %}
-{% if background is defined %}
-selection_foreground {{background | removeAlpha}}
-{% endif %}
-{% if selectionBackground is defined %}
-selection_background {{selectionBackground | removeAlpha}}
-{% endif %}
-
-{% if ansiBlack is defined %}
-# dull black
-color0 {{ansiBlack}}
-{% endif %}
-{% if ansiBrightBlack is defined %}
-# light black
-color8 {{ansiBrightBlack}}
-{% endif %}
-
-{% if ansiRed is defined %}
-# dull red
-color1 {{ansiRed}}
-{% endif %}
-{% if ansiBrightRed %}
-# light red
-color9 {{ansiBrightRed}}
-{% endif %}
-
-{% if ansiGreen is defined %}
-# dull green
-color2 {{ansiGreen}}
-{% endif %}
-{% if ansiBrightGreen is defined %}
-# light green
-color10 {{ansiBrightGreen}}
-{% endif %}
-
-{% if ansiYellow is defined %}
-# yellow
-color3 {{ansiYellow}}
-{% endif %}
-{% if ansiBrightYellow is defined %}
-# light yellow
-color11 {{ansiBrightYellow}}
-{% endif %}
-
-{% if ansiBlue is defined %}
-# blue
-color4 {{ansiBlue}}
-{% endif %}
-{% if ansiBrightBlue %}
-# light blue
-color12 {{ansiBrightBlue}}
-{% endif %}
-
-{% if ansiMagenta is defined %}
-# magenta
-color5 {{ansiMagenta}}
-{% endif %}
-{% if ansiBrightMagenta is defined %}
-# light magenta
-color13 {{ansiBrightMagenta}}
-{% endif %}
-
-{% if ansiCyan is defined %}
-# cyan
-color6 {{ansiCyan}}
-{% endif %}
-{% if ansiBrightCyan is defined %}
-# light cyan
-color14 {{ansiBrightCyan}}
-{% endif %}
-
-{% if ansiWhite is defined %}
-# dull white
-color7 {{ansiWhite}}
-{% endif %}
-{% if ansiBrightWhite is defined %}
-# bright white
-color15 {{ansiBrightWhite}}
-{% endif %}
diff --git a/kitty-themes/.tools/windowid.swift b/kitty-themes/.tools/windowid.swift
deleted file mode 100755
index 1dc95e6f..00000000
--- a/kitty-themes/.tools/windowid.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/swift
-import Foundation
-import Cocoa
-import CoreGraphics.CGWindow
-
-let windows : NSArray = CGWindowListCopyWindowInfo(CGWindowListOption.excludeDesktopElements, kCGNullWindowID)! as NSArray
-
-let search_for_app = CommandLine.arguments[1]
-let search_for_win = CommandLine.arguments[2]
-
-for window in windows {
- let window = window as! NSDictionary
-
- let app_name = window[kCGWindowOwnerName] as! String
- let window_name = window[kCGWindowName] as? String
-
- if app_name == search_for_app && window_name == search_for_win {
- print("\(window[kCGWindowNumber]!)")
- }
-}
diff --git a/kitty-themes/CONTRIBUTING.md b/kitty-themes/CONTRIBUTING.md
deleted file mode 100644
index fed9d6dc..00000000
--- a/kitty-themes/CONTRIBUTING.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Contributing to kitty-themes
-
-We always welcome your pull request! To start contributing follow these simple
-steps:
-
-1. Fork the repo and create your branch from `master`;
-2. Add your theme as config file under `themes` directory;
-3. Issue the pull request through github;
diff --git a/kitty-themes/LICENSE.md b/kitty-themes/LICENSE.md
deleted file mode 100644
index 50fa5491..00000000
--- a/kitty-themes/LICENSE.md
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 Fabrizio Destro
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/kitty-themes/README.md b/kitty-themes/README.md
deleted file mode 100644
index 5fb8cbea..00000000
--- a/kitty-themes/README.md
+++ /dev/null
@@ -1,465 +0,0 @@
-[iterm2-themes]: https://github.com/mbadolato/iTerm2-Color-Schemes
-[color-scripts]: https://github.com/stark/Color-Scripts/tree/master/color-scripts
-[license]: https://opensource.org/licenses/MIT
-[license-badge]: https://img.shields.io/github/license/dexpota/kitty-themes.svg?style=for-the-badge
-[kitty-themes-previews]: https://github.com/dexpota/kitty-themes-website/tree/master/previews
-
-
-
-> **Personalize** your *kitty* terminal and choose your theme from this awesome
-> collection, for more information on the terminal visit
-> https://github.com/kovidgoyal/kitty
-
-[![License: MIT][license-badge]][license]
-[](#contributors)
-
-- [About](#about)
-- [Installation](#installation)
- - [Source Code](#source-code)
- - [Conda](#conda)
-- [License](#license)
-- [Bring me to the previews!](#previews)
-- [Contributors](#contributors)
-
-## About
-
-In this repository you can find a set of themes to personalize your kitty
-terminal, these have been ported from [iTerm2-Color-Schemes][iterm2-themes]. You can find
-the previews for each theme in the [section](#previews) below or in this other
-[repository](kitty-themes-previews).
-
-## Installation
-
-### Source Code
-
-1. If you want to download and use one of these theme you have two options:
- - clone the entire *kitty-themes* repository:
- ```bash
- git clone --depth 1 https://github.com/dexpota/kitty-themes.git ~/.config/kitty/kitty-themes
- ```
- - or download just one theme:
- ```bash
- THEME=https://raw.githubusercontent.com/dexpota/kitty-themes/master/themes/3024_Day.conf
- wget "$THEME" -P ~/.config/kitty/kitty-themes/themes
- ```
-
-2. Choose a theme and create a symlink:
-
- ```bash
- cd ~/.config/kitty
- ln -s ./kitty-themes/themes/Floraverse.conf ~/.config/kitty/theme.conf
- ```
-
-3. Add this line to your kitty.conf configuration file:
-
- ```
- include ./theme.conf
- ```
-
-### Conda
-
-If you using the ``conda`` package manager, you may also install these themes
-with the following command:
-
-```bash
-conda install -c conda-forge kitty-themes
-```
-
-## License
-
-All original content of this repository is licensed with the [MIT
-License](./LICENSE.md). Whenever possible the author of the theme is cited
-inside each theme configuration file, together with its license. Hit me up if
-you find your theme inside this repository and you want a proper citation.
-
-## Previews
-
-If you have followed the [installation](#installation) instructions and cloned
-the entire repo, you have two options to try a theme:
-
-1. If you have enabled remote control in *kitty* you can run this command:
-
- ```bash
- kitty @ set-colors -a "~/.config/kitty/kitty-themes/themes/AdventureTime.conf"
- ```
-
-2. Otherwise you can start another instance of kitty and specify another config
- file to read from, this will cause *kitty* to read both its normal config
- file and the specified one:
-
- ```bash
- kitty -o include="~/.config/kitty/kitty-themes/themes/AdventureTime.conf"
- ```
-
-### Bonus
-
-Try your new theme with one of the scripts in [Color-scripts][color-scripts] with this
-one-liner (requires `jq`):
-
-```bash
-COLOR_SCRIPT_REPO=https://api.github.com/repos/stark/Color-Scripts/contents/color-scripts
-wget -q -O - $(curl -s $COLOR_SCRIPT_REPO | jq '.[] | "\(.path) \(.download_url)"' -r | shuf -n1 | cut -d " " -f2) | bash
-```
-
-### 3024 Day
-
-### 3024 Night
-
-### AdventureTime
-
-### Afterglow
-
-### AlienBlood
-
-### Alucard
-
-### Apprentice
-
-### Argonaut
-
-### Arthur
-
-### AtelierSulphurpool
-
-### Atom
-
-### AtomOneLight
-
-### ayu
-
-### ayu light
-
-### ayu mirage
-
-### Batman
-
-### Belafonte Day
-
-### Belafonte Night
-
-### BirdsOfParadise
-
-### Blazer
-
-### Borland
-
-### Bright Lights
-
-### Broadcast
-
-### Brogrammer
-
-### C64
-
-### Chalk
-
-### Chalkboard
-
-### Ciapre
-
-### CLRS
-
-### Cobalt2
-
-### Cobalt Neon
-
-### CrayonPonyFish
-
-### Dark Pastel
-
-### Darkside
-
-### Desert
-
-### DimmedMonokai
-
-### DotGov
-
-### Dracula
-
-### Dumbledore
-
-### Duotone Dark
-
-### Earthsong
-
-### Elemental
-
-### ENCOM
-
-### Espresso
-
-### Espresso Libre
-
-### Fideloper
-
-### FishTank
-
-### Flat
-
-### Flatland
-
-### Floraverse
-
-### FrontEndDelight
-
-### FunForrest
-
-### Galaxy
-
-### Github
-
-### Glacier
-
-### GoaBase
-
-### Grape
-
-### Grass
-
-### gruvbox dark
-
-### gruvbox light
-
-### Hardcore
-
-### Harper
-
-### Highway
-
-### Hipster Green
-
-### Homebrew
-
-### Hurtado
-
-### Hybrid
-
-### IC Green PPL
-
-### IC Orange PPL
-
-### idleToes
-
-### IR Black
-
-### Jackie Brown
-
-### Japanesque
-
-### Jellybeans
-
-### JetBrains Darcula
-
-### Kibble
-
-### Later This Evening
-
-### Lavandula
-
-### LiquidCarbon
-
-### LiquidCarbonTransparent
-
-### LiquidCarbonTransparentInverse
-
-### Man Page
-
-### Material
-
-### MaterialDark
-
-### Mathias
-
-### Medallion
-
-### Misterioso
-
-### Molokai
-
-### MonaLisa
-
-### Monokai Classic
-
-### Monokai Pro
-
-### Monokai Pro (Filter Machine)
-/preview.png)
-### Monokai Pro (Filter Octagon)
-/preview.png)
-### Monokai Pro (Filter Ristretto)
-/preview.png)
-### Monokai Pro (Filter Spectrum)
-/preview.png)
-### Monokai Soda
-
-### N0tch2k
-
-### Neopolitan
-
-### Neutron
-
-### NightLion v1
-
-### NightLion v2
-
-### Nova
-
-### Novel
-
-### Obsidian
-
-### Ocean
-
-### OceanicMaterial
-
-### Ollie
-
-### OneDark
-
-### Parasio Dark
-
-### PaulMillr
-
-### PencilDark
-
-### PencilLight
-
-### Piatto Light
-
-### Pnevma
-
-### Pro
-
-### Red Alert
-
-### Red Sands
-
-### Relaxed Afterglow
-
-### Renault Style
-
-### Renault Style Light
-
-### Rippedcasts
-
-### Royal
-
-### Seafoam Pastel
-
-### SeaShells
-
-### Seti
-
-### Shaman
-
-### Slate
-
-### Smyck
-
-### snazzy
-
-### SoftServer
-
-### Solarized Darcula
-
-### Solarized Dark
-
-### Solarized Dark Higher Contrast
-
-### Solarized Dark - Patched
-
-### Solarized Light
-
-### Source Code X
-
-### Spacedust
-
-### SpaceGray
-
-### SpaceGray Eighties
-
-### SpaceGray Eighties Dull
-
-### Spiderman
-
-### Spring
-
-### Square
-
-### Sundried
-
-### Symfonic
-
-### Tango Dark
-
-### Tango Light
-
-### Teerb
-
-### Thayer Bright
-
-### The Hulk
-
-### Tomorrow
-
-### Tomorrow Night
-
-### Tomorrow Night Blue
-
-### Tomorrow Night Bright
-
-### Tomorrow Night Eighties
-
-### ToyChest
-
-### Treehouse
-
-### Twilight
-
-### Ubuntu
-
-### Urple
-
-### Vaughn
-
-### VibrantInk
-
-### WarmNeon
-
-### Wez
-
-### WildCherry
-
-### Wombat
-
-### Wryan
-
-### Zenburn
-
-
-## Contributors
-
-Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
-
-
-
-
-
-
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/kitty-themes/themes/3024_Day.conf b/kitty-themes/themes/3024_Day.conf
deleted file mode 100644
index 747996a0..00000000
--- a/kitty-themes/themes/3024_Day.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #f7f7f7
-foreground #494542
-cursor #494542
-selection_background #a4a1a1
-color0 #090200
-color8 #5b5754
-color1 #da2c20
-color9 #e8bacf
-color2 #00a152
-color10 #3a3332
-color3 #fcec02
-color11 #494542
-color4 #00a0e4
-color12 #7f7c7b
-color5 #a06994
-color13 #d6d4d3
-color6 #b5e4f4
-color14 #ccab53
-color7 #a4a1a1
-color15 #f7f7f7
-selection_foreground #f7f7f7
diff --git a/kitty-themes/themes/3024_Night.conf b/kitty-themes/themes/3024_Night.conf
deleted file mode 100644
index 25438194..00000000
--- a/kitty-themes/themes/3024_Night.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #090200
-foreground #a4a1a1
-cursor #a4a1a1
-selection_background #494542
-color0 #090200
-color8 #5b5754
-color1 #da2c20
-color9 #e8bacf
-color2 #00a152
-color10 #3a3332
-color3 #fcec02
-color11 #494542
-color4 #00a0e4
-color12 #7f7c7b
-color5 #a06994
-color13 #d6d4d3
-color6 #b5e4f4
-color14 #ccab53
-color7 #a4a1a1
-color15 #f7f7f7
-selection_foreground #090200
diff --git a/kitty-themes/themes/AdventureTime.conf b/kitty-themes/themes/AdventureTime.conf
deleted file mode 100644
index e523a1aa..00000000
--- a/kitty-themes/themes/AdventureTime.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1e1c44
-foreground #f8dbc0
-cursor #eebf37
-selection_background #6f6a4e
-color0 #050404
-color8 #4e7bbf
-color1 #bc0013
-color9 #fc5e59
-color2 #49b117
-color10 #9dff6e
-color3 #e6741d
-color11 #efc11a
-color4 #0f49c6
-color12 #1896c6
-color5 #665992
-color13 #9a5952
-color6 #6fa497
-color14 #c8f9f3
-color7 #f8dbc0
-color15 #f5f4fb
-selection_foreground #1e1c44
diff --git a/kitty-themes/themes/Afterglow.conf b/kitty-themes/themes/Afterglow.conf
deleted file mode 100644
index 2df3efd4..00000000
--- a/kitty-themes/themes/Afterglow.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #202020
-foreground #d0d0d0
-cursor #d0d0d0
-selection_background #303030
-color0 #151515
-color8 #505050
-color1 #ac4142
-color9 #ac4142
-color2 #7e8d50
-color10 #7e8d50
-color3 #e5b566
-color11 #e5b566
-color4 #6c99ba
-color12 #6c99ba
-color5 #9e4e85
-color13 #9e4e85
-color6 #7dd5cf
-color14 #7dd5cf
-color7 #d0d0d0
-color15 #f5f5f5
-selection_foreground #202020
diff --git a/kitty-themes/themes/AlienBlood.conf b/kitty-themes/themes/AlienBlood.conf
deleted file mode 100644
index 453ea804..00000000
--- a/kitty-themes/themes/AlienBlood.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0f160f
-foreground #637d75
-cursor #73f990
-selection_background #1d4025
-color0 #112615
-color8 #3c4711
-color1 #7f2b26
-color9 #df8008
-color2 #2f7e25
-color10 #18e000
-color3 #707f23
-color11 #bde000
-color4 #2f697f
-color12 #00a9df
-color5 #47577e
-color13 #0058df
-color6 #317f76
-color14 #00dfc3
-color7 #647d75
-color15 #73f990
-selection_foreground #0f160f
diff --git a/kitty-themes/themes/Alucard.conf b/kitty-themes/themes/Alucard.conf
deleted file mode 100644
index 43665fe5..00000000
--- a/kitty-themes/themes/Alucard.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #222330
-foreground #cef3ff
-cursor #ffffff
-selection_background #44475a
-color0 #000000
-color8 #545454
-color1 #ff5555
-color9 #ff5454
-color2 #fa0074
-color10 #50fa7b
-color3 #7f0a1f
-color11 #f0fa8b
-color4 #3282ff
-color12 #1200f8
-color5 #1b3cff
-color13 #ff78c5
-color6 #0037fc
-color14 #8ae9fc
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #222330
diff --git a/kitty-themes/themes/Apprentice.conf b/kitty-themes/themes/Apprentice.conf
deleted file mode 100644
index c8c05834..00000000
--- a/kitty-themes/themes/Apprentice.conf
+++ /dev/null
@@ -1,26 +0,0 @@
-# Apprentice by Romain Lafourcade, https://github.com/romainl
-# This work is licensed under the terms of the MIT license.
-# For a copy, see https://opensource.org/licenses/MIT.
-
-cursor #c7c7c7
-cursor_text_color #feffff
-selection_foreground #3e3e3e
-selection_background #c1ddff
-foreground #c8c8c8
-background #323232
-color0 #252525
-color8 #555555
-color1 #be7472
-color9 #ff9900
-color2 #709772
-color10 #97bb98
-color3 #989772
-color11 #fefdbc
-color4 #7199bc
-color12 #9fbdde
-color5 #727399
-color13 #989abc
-color6 #719899
-color14 #6fbbbc
-color7 #7f7f7f
-color15 #feffff
diff --git a/kitty-themes/themes/Argonaut.conf b/kitty-themes/themes/Argonaut.conf
deleted file mode 100644
index 058186eb..00000000
--- a/kitty-themes/themes/Argonaut.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0d0f18
-foreground #fffaf3
-cursor #ff0017
-selection_background #002a3a
-color0 #222222
-color8 #444444
-color1 #ff000f
-color9 #ff273f
-color2 #8ce00a
-color10 #abe05a
-color3 #ffb900
-color11 #ffd141
-color4 #008df8
-color12 #0092ff
-color5 #6c43a5
-color13 #9a5feb
-color6 #00d7eb
-color14 #67ffef
-color7 #ffffff
-color15 #ffffff
-selection_foreground #0d0f18
diff --git a/kitty-themes/themes/Arthur.conf b/kitty-themes/themes/Arthur.conf
deleted file mode 100644
index d3fb5202..00000000
--- a/kitty-themes/themes/Arthur.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1c1c1c
-foreground #ddeedd
-cursor #e2bbef
-selection_background #4d4d4d
-color0 #3d352a
-color8 #554444
-color1 #cd5c5c
-color9 #cc5533
-color2 #86af80
-color10 #88aa22
-color3 #e8ae5b
-color11 #ffa75d
-color4 #6495ed
-color12 #87ceeb
-color5 #deb887
-color13 #996600
-color6 #b0c4de
-color14 #b0c4de
-color7 #bbaa99
-color15 #ddccbb
-selection_foreground #1c1c1c
diff --git a/kitty-themes/themes/AtelierSulphurpool.conf b/kitty-themes/themes/AtelierSulphurpool.conf
deleted file mode 100644
index 05674b0f..00000000
--- a/kitty-themes/themes/AtelierSulphurpool.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #202745
-foreground #969cb3
-cursor #969cb3
-selection_background #5e6686
-color0 #202745
-color8 #6a7394
-color1 #c84821
-color9 #c76a28
-color2 #ab9639
-color10 #283256
-color3 #c08a2f
-color11 #5e6686
-color4 #3d8ed0
-color12 #898ea3
-color5 #6678cc
-color13 #dee1f0
-color6 #21a1c8
-color14 #9c6279
-color7 #969cb3
-color15 #f4f7ff
-selection_foreground #202745
diff --git a/kitty-themes/themes/Atom.conf b/kitty-themes/themes/Atom.conf
deleted file mode 100644
index 091c683b..00000000
--- a/kitty-themes/themes/Atom.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #161718
-foreground #c4c8c5
-cursor #d0d0d0
-selection_background #444444
-color0 #000000
-color8 #000000
-color1 #fc5ef0
-color9 #fc5ef0
-color2 #86c38a
-color10 #94f936
-color3 #ffd6b1
-color11 #f5ffa7
-color4 #85befd
-color12 #95cbfe
-color5 #b9b5fc
-color13 #b9b5fc
-color6 #85befd
-color14 #85befd
-color7 #dfdfdf
-color15 #dfdfdf
-selection_foreground #161718
diff --git a/kitty-themes/themes/AtomOneLight.conf b/kitty-themes/themes/AtomOneLight.conf
deleted file mode 100644
index 056ef3b7..00000000
--- a/kitty-themes/themes/AtomOneLight.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #f8f8f8
-foreground #2a2b33
-cursor #bbbbbb
-selection_background #ececec
-color0 #000000
-color8 #000000
-color1 #de3d35
-color9 #de3d35
-color2 #3e953a
-color10 #3e953a
-color3 #d2b67b
-color11 #d2b67b
-color4 #2f5af3
-color12 #2f5af3
-color5 #950095
-color13 #a00095
-color6 #3e953a
-color14 #3e953a
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #f8f8f8
diff --git a/kitty-themes/themes/Batman.conf b/kitty-themes/themes/Batman.conf
deleted file mode 100644
index aef20e89..00000000
--- a/kitty-themes/themes/Batman.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1d1e
-foreground #6e6e6e
-cursor #fcee0b
-selection_background #4d4f4c
-color0 #1b1d1e
-color8 #505354
-color1 #e6db43
-color9 #fff68d
-color2 #c8be46
-color10 #fff27c
-color3 #f3fd21
-color11 #feed6c
-color4 #737074
-color12 #909495
-color5 #737271
-color13 #9a999d
-color6 #615f5e
-color14 #a2a2a5
-color7 #c5c5be
-color15 #dadad5
-selection_foreground #1b1d1e
diff --git a/kitty-themes/themes/Belafonte_Day.conf b/kitty-themes/themes/Belafonte_Day.conf
deleted file mode 100644
index 56a8796a..00000000
--- a/kitty-themes/themes/Belafonte_Day.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #d4ccb9
-foreground #45363b
-cursor #45363b
-selection_background #958b83
-color0 #20111a
-color8 #5e5252
-color1 #bd100d
-color9 #bd100d
-color2 #858062
-color10 #858062
-color3 #e9a448
-color11 #e9a448
-color4 #416978
-color12 #416978
-color5 #96522b
-color13 #96522b
-color6 #98999c
-color14 #98999c
-color7 #958b83
-color15 #d4ccb9
-selection_foreground #d4ccb9
diff --git a/kitty-themes/themes/Belafonte_Night.conf b/kitty-themes/themes/Belafonte_Night.conf
deleted file mode 100644
index fc51e9c3..00000000
--- a/kitty-themes/themes/Belafonte_Night.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #20111a
-foreground #958b83
-cursor #958b83
-selection_background #45363b
-color0 #20111a
-color8 #5e5252
-color1 #bd100d
-color9 #bd100d
-color2 #858062
-color10 #858062
-color3 #e9a448
-color11 #e9a448
-color4 #416978
-color12 #416978
-color5 #96522b
-color13 #96522b
-color6 #98999c
-color14 #98999c
-color7 #958b83
-color15 #d4ccb9
-selection_foreground #20111a
diff --git a/kitty-themes/themes/BirdsOfParadise.conf b/kitty-themes/themes/BirdsOfParadise.conf
deleted file mode 100644
index df38e804..00000000
--- a/kitty-themes/themes/BirdsOfParadise.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2a1e1d
-foreground #dfdab7
-cursor #573d25
-selection_background #563c27
-color0 #573d25
-color8 #9a6b49
-color1 #be2d26
-color9 #e84526
-color2 #6ba08a
-color10 #94d7ba
-color3 #e99c29
-color11 #d0d04f
-color4 #5a86ac
-color12 #b8d3ed
-color5 #ab80a6
-color13 #d09dca
-color6 #74a5ac
-color14 #92ced6
-color7 #dfdab7
-color15 #fff9d4
-selection_foreground #2a1e1d
diff --git a/kitty-themes/themes/Blazer.conf b/kitty-themes/themes/Blazer.conf
deleted file mode 100644
index f1dd601f..00000000
--- a/kitty-themes/themes/Blazer.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0d1925
-foreground #d9e5f1
-cursor #d9e5f1
-color0 #000000
-color8 #252525
-color1 #b87979
-color9 #dabdbd
-color2 #79b879
-color10 #bddabd
-color3 #b8b879
-color11 #dadabd
-color4 #7979b8
-color12 #bdbdda
-color5 #b879b8
-color13 #dabdda
-color6 #79b8b8
-color14 #bddada
-color7 #d9d9d9
-color15 #ffffff
-selection_foreground #0d1925
-selection_background #d9e6f2
diff --git a/kitty-themes/themes/Borland.conf b/kitty-themes/themes/Borland.conf
deleted file mode 100644
index c27cc830..00000000
--- a/kitty-themes/themes/Borland.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0000a3
-foreground #ffff4d
-cursor #ffa460
-selection_background #a3a3a3
-color0 #4e4e4e
-color8 #7c7c7c
-color1 #ff6b60
-color9 #ffb6b0
-color2 #a7ff60
-color10 #ceffab
-color3 #ffffb6
-color11 #ffffcb
-color4 #96cafd
-color12 #b5dcfe
-color5 #ff73fd
-color13 #ff9cfe
-color6 #c6c4fd
-color14 #dfdffe
-color7 #eeeeee
-color15 #ffffff
-selection_foreground #0000a3
diff --git a/kitty-themes/themes/Bright_Lights.conf b/kitty-themes/themes/Bright_Lights.conf
deleted file mode 100644
index 9b365f4b..00000000
--- a/kitty-themes/themes/Bright_Lights.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #191919
-foreground #b2c8d6
-cursor #f34a00
-selection_background #b2c8d6
-color0 #191919
-color8 #191919
-color1 #ff355b
-color9 #ff355b
-color2 #b6e875
-color10 #b6e875
-color3 #ffc150
-color11 #ffc150
-color4 #75d3ff
-color12 #75d4ff
-color5 #b975e6
-color13 #b975e6
-color6 #6cbeb5
-color14 #6cbeb5
-color7 #c1c8d6
-color15 #c1c8d6
-selection_foreground #191919
diff --git a/kitty-themes/themes/Broadcast.conf b/kitty-themes/themes/Broadcast.conf
deleted file mode 100644
index 78449423..00000000
--- a/kitty-themes/themes/Broadcast.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2b2b2b
-foreground #e5e1db
-cursor #ffffff
-selection_background #5a637e
-color0 #000000
-color8 #323232
-color1 #da4839
-color9 #ff7b6a
-color2 #509f50
-color10 #83d082
-color3 #ffd249
-color11 #ffff7b
-color4 #6d9cbd
-color12 #9fcef0
-color5 #cfcfff
-color13 #ffffff
-color6 #6d9cbd
-color14 #a0cef0
-color7 #ffffff
-color15 #ffffff
-selection_foreground #2b2b2b
diff --git a/kitty-themes/themes/Brogrammer.conf b/kitty-themes/themes/Brogrammer.conf
deleted file mode 100644
index 41cc37b7..00000000
--- a/kitty-themes/themes/Brogrammer.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #131313
-foreground #d6dae4
-cursor #b9b9b9
-selection_background #1f1f1f
-color0 #1f1f1f
-color8 #d6dae4
-color1 #f71118
-color9 #de342e
-color2 #2cc55d
-color10 #1dd260
-color3 #ecb90f
-color11 #f2bd09
-color4 #2a84d2
-color12 #0f80d5
-color5 #4e59b7
-color13 #524fb9
-color6 #0f80d5
-color14 #0f7cda
-color7 #d6dae4
-color15 #ffffff
-selection_foreground #131313
diff --git a/kitty-themes/themes/C64.conf b/kitty-themes/themes/C64.conf
deleted file mode 100644
index 70978d56..00000000
--- a/kitty-themes/themes/C64.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #40318d
-foreground #7869c4
-cursor #7869c4
-selection_background #7869c4
-color0 #090300
-color8 #000000
-color1 #883932
-color9 #883932
-color2 #55a049
-color10 #55a049
-color3 #bfce72
-color11 #bfce72
-color4 #40318d
-color12 #40318d
-color5 #8b3f96
-color13 #8a3e95
-color6 #67b6bd
-color14 #67b6bd
-color7 #ffffff
-color15 #f7f7f7
-selection_foreground #40318d
diff --git a/kitty-themes/themes/CLRS.conf b/kitty-themes/themes/CLRS.conf
deleted file mode 100644
index ddcb3240..00000000
--- a/kitty-themes/themes/CLRS.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #ffffff
-foreground #262626
-cursor #6fd2fc
-selection_background #6fd2fc
-color0 #000000
-color8 #545753
-color1 #f72729
-color9 #fb0416
-color2 #32895c
-color10 #2cc631
-color3 #f96f1c
-color11 #fcd627
-color4 #125ccf
-color12 #156ffe
-color5 #9f00bc
-color13 #e800b0
-color6 #32c2c0
-color14 #39d5ce
-color7 #b2b2b2
-color15 #ededec
-selection_foreground #ffffff
diff --git a/kitty-themes/themes/Chalk.conf b/kitty-themes/themes/Chalk.conf
deleted file mode 100644
index 70516985..00000000
--- a/kitty-themes/themes/Chalk.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2b2c2e
-foreground #d2d8d9
-cursor #708183
-selection_background #e3e8ed
-color0 #7c8a8f
-color8 #888888
-color1 #b23a51
-color9 #f24840
-color2 #789a69
-color10 #80c46f
-color3 #b9ab4a
-color11 #ffeb62
-color4 #2a7fac
-color12 #4095ff
-color5 #bc4f5a
-color13 #fb5175
-color6 #44a799
-color14 #52ccbd
-color7 #d2d8d9
-color15 #d2d8d9
-selection_foreground #2b2c2e
diff --git a/kitty-themes/themes/Chalkboard.conf b/kitty-themes/themes/Chalkboard.conf
deleted file mode 100644
index f0b7350b..00000000
--- a/kitty-themes/themes/Chalkboard.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #29262f
-foreground #d9e6f2
-cursor #d9e6f2
-selection_background #073642
-color0 #000000
-color8 #323232
-color1 #c37372
-color9 #dbaaaa
-color2 #72c373
-color10 #aadbaa
-color3 #c2c372
-color11 #dadbaa
-color4 #7372c3
-color12 #aaaadb
-color5 #c372c2
-color13 #dbaada
-color6 #72c2c3
-color14 #aadadb
-color7 #d9d9d9
-color15 #ffffff
-selection_foreground #29262f
diff --git a/kitty-themes/themes/Ciapre.conf b/kitty-themes/themes/Ciapre.conf
deleted file mode 100644
index 2b496fb1..00000000
--- a/kitty-themes/themes/Ciapre.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #181c27
-foreground #ada37a
-cursor #91805a
-selection_background #172539
-color0 #181818
-color8 #555555
-color1 #800009
-color9 #ab3834
-color2 #48513b
-color10 #a6a65d
-color3 #cc8a3e
-color11 #dcde7b
-color4 #566d8c
-color12 #2f97c6
-color5 #724c7c
-color13 #d33060
-color6 #5b4f4a
-color14 #f3dab1
-color7 #ada37e
-color15 #f3f3f3
-selection_foreground #181c27
diff --git a/kitty-themes/themes/Cobalt2.conf b/kitty-themes/themes/Cobalt2.conf
deleted file mode 100644
index 3ffcbe2c..00000000
--- a/kitty-themes/themes/Cobalt2.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #122637
-foreground #ffffff
-cursor #f0cb09
-selection_background #18344f
-color0 #000000
-color8 #545454
-color1 #ff0000
-color9 #f40d17
-color2 #37dd21
-color10 #3bcf1d
-color3 #fee409
-color11 #ecc809
-color4 #1460d2
-color12 #5555ff
-color5 #ff005d
-color13 #ff55ff
-color6 #00bbbb
-color14 #6ae3f9
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #122637
diff --git a/kitty-themes/themes/Cobalt_Neon.conf b/kitty-themes/themes/Cobalt_Neon.conf
deleted file mode 100644
index 02a66e5f..00000000
--- a/kitty-themes/themes/Cobalt_Neon.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #142838
-foreground #8ff586
-cursor #c4206f
-selection_background #084fb0
-color0 #142630
-color8 #fff688
-color1 #ff2320
-color9 #d4312e
-color2 #3aa5ff
-color10 #8ff586
-color3 #e9e75c
-color11 #e9f06d
-color4 #8ff586
-color12 #3c7dd2
-color5 #781aa0
-color13 #8230a7
-color6 #8ff586
-color14 #6cbc67
-color7 #ba45b1
-color15 #8ff586
-selection_foreground #142838
diff --git a/kitty-themes/themes/CrayonPonyFish.conf b/kitty-themes/themes/CrayonPonyFish.conf
deleted file mode 100644
index 068fab33..00000000
--- a/kitty-themes/themes/CrayonPonyFish.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #140607
-foreground #685259
-cursor #685259
-selection_background #2a1a1c
-color0 #2a1a1c
-color8 #3c2a2e
-color1 #90002a
-color9 #c5245c
-color2 #579523
-color10 #8dff56
-color3 #aa301b
-color11 #c7371d
-color4 #8b87af
-color12 #cfc9ff
-color5 #682e50
-color13 #fb6cb9
-color6 #e8a766
-color14 #ffceae
-color7 #685259
-color15 #af949d
-selection_foreground #140607
diff --git a/kitty-themes/themes/Dark_Pastel.conf b/kitty-themes/themes/Dark_Pastel.conf
deleted file mode 100644
index 15083f71..00000000
--- a/kitty-themes/themes/Dark_Pastel.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #ffffff
-cursor #bbbbbb
-selection_background #b5d5ff
-color0 #000000
-color8 #545454
-color1 #ff5555
-color9 #ff5555
-color2 #55ff55
-color10 #55ff55
-color3 #ffff55
-color11 #ffff55
-color4 #5555ff
-color12 #5555ff
-color5 #ff55ff
-color13 #ff55ff
-color6 #55ffff
-color14 #55ffff
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/Darkside.conf b/kitty-themes/themes/Darkside.conf
deleted file mode 100644
index 10238d10..00000000
--- a/kitty-themes/themes/Darkside.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212324
-foreground #b9b9b9
-cursor #bbbbbb
-selection_background #2f3333
-color0 #000000
-color8 #000000
-color1 #e8331c
-color9 #df5a4f
-color2 #68c156
-color10 #76b768
-color3 #f1d32b
-color11 #eed64a
-color4 #1c98e8
-color12 #387bd2
-color5 #8e69c8
-color13 #957bbd
-color6 #1c98e8
-color14 #3d96e2
-color7 #b9b9b9
-color15 #b9b9b9
-selection_foreground #212324
diff --git a/kitty-themes/themes/Desert.conf b/kitty-themes/themes/Desert.conf
deleted file mode 100644
index 5491a973..00000000
--- a/kitty-themes/themes/Desert.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #333333
-foreground #ffffff
-cursor #00ff00
-selection_background #b5d5ff
-color0 #4d4d4d
-color8 #545454
-color1 #ff2b2b
-color9 #ff5555
-color2 #98fb98
-color10 #55ff55
-color3 #f0e68c
-color11 #ffff55
-color4 #cd853f
-color12 #87ceff
-color5 #ffdead
-color13 #ff55ff
-color6 #ffa0a0
-color14 #ffd700
-color7 #f5deb3
-color15 #ffffff
-selection_foreground #333333
diff --git a/kitty-themes/themes/DimmedMonokai.conf b/kitty-themes/themes/DimmedMonokai.conf
deleted file mode 100644
index 99d7a83f..00000000
--- a/kitty-themes/themes/DimmedMonokai.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1e1e1e
-foreground #b8bcb9
-cursor #f83d19
-selection_background #292c31
-color0 #3a3c43
-color8 #888987
-color1 #be3e48
-color9 #fb001e
-color2 #869a3a
-color10 #0e712e
-color3 #c4a535
-color11 #c37033
-color4 #4e76a1
-color12 #176ce3
-color5 #855b8d
-color13 #fb0067
-color6 #568ea3
-color14 #2d6f6c
-color7 #b8bcb9
-color15 #fcffb8
-selection_foreground #1e1e1e
diff --git a/kitty-themes/themes/DotGov.conf b/kitty-themes/themes/DotGov.conf
deleted file mode 100644
index 345cef9f..00000000
--- a/kitty-themes/themes/DotGov.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #252b35
-foreground #eaeaea
-cursor #d9002f
-selection_background #194080
-color0 #181818
-color8 #181818
-color1 #bf081d
-color9 #bf081d
-color2 #3d9751
-color10 #3d9751
-color3 #f6bb33
-color11 #f6bb33
-color4 #16b1df
-color12 #16b1df
-color5 #772fb0
-color13 #772fb0
-color6 #8bd1ed
-color14 #8bd1ed
-color7 #ffffff
-color15 #ffffff
-selection_foreground #252b35
diff --git a/kitty-themes/themes/Dracula.conf b/kitty-themes/themes/Dracula.conf
deleted file mode 100644
index 9cf0ed67..00000000
--- a/kitty-themes/themes/Dracula.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1e1f28
-foreground #f8f8f2
-cursor #bbbbbb
-selection_background #44475a
-color0 #000000
-color8 #545454
-color1 #ff5555
-color9 #ff5454
-color2 #50fa7b
-color10 #50fa7b
-color3 #f0fa8b
-color11 #f0fa8b
-color4 #bd92f8
-color12 #bd92f8
-color5 #ff78c5
-color13 #ff78c5
-color6 #8ae9fc
-color14 #8ae9fc
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #1e1f28
diff --git a/kitty-themes/themes/Dumbledore.conf b/kitty-themes/themes/Dumbledore.conf
deleted file mode 100644
index c1ef0322..00000000
--- a/kitty-themes/themes/Dumbledore.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #422553
-foreground #c4c8c5
-cursor #c4c8c5
-selection_background #008aff
-color0 #2b283d
-color8 #413e53
-color1 #ae0000
-color9 #d3a624
-color2 #3e7c54
-color10 #aaaaaa
-color3 #f0c75e
-color11 #716254
-color4 #415baf
-color12 #946a2c
-color5 #9445ae
-color13 #b294ba
-color6 #008aff
-color14 #25de50
-color7 #850000
-color15 #c9c9c9
-selection_foreground #422553
diff --git a/kitty-themes/themes/Duotone_Dark.conf b/kitty-themes/themes/Duotone_Dark.conf
deleted file mode 100644
index 48676044..00000000
--- a/kitty-themes/themes/Duotone_Dark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1f1c27
-foreground #b6a0ff
-cursor #ff9738
-selection_background #353146
-color0 #1f1c27
-color8 #353146
-color1 #d8393d
-color9 #d8393d
-color2 #2dcc72
-color10 #2dcc72
-color3 #d8b76e
-color11 #d8b76e
-color4 #ffc183
-color12 #ffc183
-color5 #dd8d40
-color13 #dd8d40
-color6 #2388ff
-color14 #2388ff
-color7 #b6a0ff
-color15 #e9e4ff
-selection_foreground #1f1c27
diff --git a/kitty-themes/themes/ENCOM.conf b/kitty-themes/themes/ENCOM.conf
deleted file mode 100644
index 7afc7aeb..00000000
--- a/kitty-themes/themes/ENCOM.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #00a595
-cursor #bbbbbb
-selection_background #00a48c
-color0 #000000
-color8 #545454
-color1 #9f0000
-color9 #ff0000
-color2 #008b00
-color10 #00ee00
-color3 #ffcf00
-color11 #ffff00
-color4 #0081ff
-color12 #0000ff
-color5 #bc00ca
-color13 #ff00ff
-color6 #008b8b
-color14 #00cdcd
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/Earthsong.conf b/kitty-themes/themes/Earthsong.conf
deleted file mode 100644
index 50f0da89..00000000
--- a/kitty-themes/themes/Earthsong.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #282420
-foreground #e5c6a8
-cursor #f6f6ec
-selection_background #111417
-color0 #111417
-color8 #665e54
-color1 #c84134
-color9 #ff6459
-color2 #84c44b
-color10 #97e035
-color3 #f4ae2e
-color11 #dfd561
-color4 #1397b9
-color12 #5ed9ff
-color5 #d0623c
-color13 #ff9168
-color6 #4f9452
-color14 #83ef88
-color7 #e5c5a9
-color15 #f6f6ec
-selection_foreground #282420
diff --git a/kitty-themes/themes/Elemental.conf b/kitty-themes/themes/Elemental.conf
deleted file mode 100644
index e24a3d3b..00000000
--- a/kitty-themes/themes/Elemental.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #21211c
-foreground #807973
-cursor #facb7f
-selection_background #403729
-color0 #3c3b30
-color8 #545444
-color1 #97280f
-color9 #df502a
-color2 #479942
-color10 #60e06f
-color3 #7f7110
-color11 #d69827
-color4 #497f7d
-color12 #78d8d8
-color5 #7e4e2e
-color13 #cd7c53
-color6 #387f58
-color14 #58d598
-color7 #807974
-color15 #fff1e8
-selection_foreground #21211c
diff --git a/kitty-themes/themes/Espresso.conf b/kitty-themes/themes/Espresso.conf
deleted file mode 100644
index 373c1787..00000000
--- a/kitty-themes/themes/Espresso.conf
+++ /dev/null
@@ -1,27 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #323232
-foreground #ffffff
-cursor #d6d6d6
-selection_background #5b5b5b
-selection_foreground #323232
-color0 #353535
-color8 #535353
-color1 #d25252
-color9 #f00c0c
-color2 #a4c161
-color10 #c1df74
-color3 #ffc56d
-color11 #e1e48a
-color4 #6c99ba
-color12 #8ab6d9
-color5 #d096d9
-color13 #efb5f7
-color6 #bdd6ff
-color14 #dbf4ff
-color7 #ededec
-color15 #ffffff
-active_tab_foreground #ffffff
-active_tab_background #535353
-inactive_tab_foreground #ffffff
-inactive_tab_background #353535
diff --git a/kitty-themes/themes/Espresso_Libre.conf b/kitty-themes/themes/Espresso_Libre.conf
deleted file mode 100644
index fa042b2a..00000000
--- a/kitty-themes/themes/Espresso_Libre.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2a211c
-foreground #b8a898
-cursor #ffffff
-selection_background #c3dcff
-color0 #000000
-color8 #545753
-color1 #cc0000
-color9 #ef2828
-color2 #1a921c
-color10 #9aff87
-color3 #efe43a
-color11 #fffa5c
-color4 #0066ff
-color12 #43a8ed
-color5 #c5656b
-color13 #ff8089
-color6 #05989a
-color14 #34e2e2
-color7 #d3d7cf
-color15 #ededec
-selection_foreground #2a211c
diff --git a/kitty-themes/themes/Fideloper.conf b/kitty-themes/themes/Fideloper.conf
deleted file mode 100644
index 7e099fc1..00000000
--- a/kitty-themes/themes/Fideloper.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #282f32
-foreground #dad9df
-cursor #d35f5a
-selection_background #eeb7ab
-color0 #282f32
-color8 #092027
-color1 #ca1d2c
-color9 #d35f5a
-color2 #edb7ab
-color10 #d35f5a
-color3 #b7aa9a
-color11 #a86571
-color4 #2e78c1
-color12 #7c84c4
-color5 #c0226e
-color13 #5b5db2
-color6 #309185
-color14 #81908f
-color7 #e9e2cd
-color15 #fcf4de
-selection_foreground #282f32
diff --git a/kitty-themes/themes/FishTank.conf b/kitty-themes/themes/FishTank.conf
deleted file mode 100644
index dce32a47..00000000
--- a/kitty-themes/themes/FishTank.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #222436
-foreground #eceffd
-cursor #fdcd5e
-selection_background #fcf6e8
-color0 #03063c
-color8 #6c5a30
-color1 #c60049
-color9 #d94a8a
-color2 #abf157
-color10 #daffa8
-color3 #fdcd5e
-color11 #fee6a8
-color4 #525fb8
-color12 #b1bdf9
-color5 #976f81
-color13 #fda4cc
-color6 #968662
-color14 #a4bc86
-color7 #eceffc
-color15 #f6ffec
-selection_foreground #222436
diff --git a/kitty-themes/themes/Flat.conf b/kitty-themes/themes/Flat.conf
deleted file mode 100644
index 036e563d..00000000
--- a/kitty-themes/themes/Flat.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #002240
-foreground #2bc45d
-cursor #e5bd0c
-selection_background #782b9c
-color0 #212c3e
-color8 #202b3b
-color1 #a72320
-color9 #d3302e
-color2 #32a448
-color10 #2c9440
-color3 #e58d11
-color11 #e5bd0c
-color4 #3066ab
-color12 #3b7cd2
-color5 #7819a0
-color13 #822fa7
-color6 #2b9270
-color14 #35b286
-color7 #afb6b9
-color15 #e6ecec
-selection_foreground #002240
diff --git a/kitty-themes/themes/Flatland.conf b/kitty-themes/themes/Flatland.conf
deleted file mode 100644
index 637b72d3..00000000
--- a/kitty-themes/themes/Flatland.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1c1e20
-foreground #b8daee
-cursor #708183
-selection_background #2a2a24
-color0 #1c1d19
-color8 #1c1d19
-color1 #f18238
-color9 #d12a24
-color2 #9ed264
-color10 #a7d32c
-color3 #f3ef6d
-color11 #ff8948
-color4 #4f96be
-color12 #61b8d0
-color5 #695abb
-color13 #695abb
-color6 #d53864
-color14 #d53864
-color7 #fefffe
-color15 #fefffe
-selection_foreground #1c1e20
diff --git a/kitty-themes/themes/Floraverse.conf b/kitty-themes/themes/Floraverse.conf
deleted file mode 100644
index 6e6a06cb..00000000
--- a/kitty-themes/themes/Floraverse.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0e0c15
-foreground #dbd0b9
-cursor #bbbbbb
-selection_background #f3e0b8
-color0 #08002e
-color8 #331d4c
-color1 #64002c
-color9 #cf2062
-color2 #5d731a
-color10 #b3ce58
-color3 #cd751c
-color11 #fac357
-color4 #1d6da1
-color12 #40a4cf
-color5 #b7077e
-color13 #f02aae
-color6 #42a38c
-color14 #62caa8
-color7 #f3e0b8
-color15 #fff5db
-selection_foreground #0e0c15
diff --git a/kitty-themes/themes/FrontEndDelight.conf b/kitty-themes/themes/FrontEndDelight.conf
deleted file mode 100644
index ccbf0d45..00000000
--- a/kitty-themes/themes/FrontEndDelight.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1b1d
-foreground #acacac
-cursor #cccccc
-selection_background #e96153
-color0 #242426
-color8 #5eac6c
-color1 #f8501a
-color9 #f64319
-color2 #565746
-color10 #74eb4c
-color3 #f9761d
-color11 #fcc224
-color4 #2c70b7
-color12 #3393c9
-color5 #f02d4e
-color13 #e75e4e
-color6 #3ba0a5
-color14 #4ebce5
-color7 #acacac
-color15 #8b735a
-selection_foreground #1b1b1d
diff --git a/kitty-themes/themes/FunForrest.conf b/kitty-themes/themes/FunForrest.conf
deleted file mode 100644
index df8f4eef..00000000
--- a/kitty-themes/themes/FunForrest.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #241200
-foreground #ddc165
-cursor #e5591c
-selection_background #e5591c
-color0 #000000
-color8 #7e6954
-color1 #d5252b
-color9 #e4591b
-color2 #909b00
-color10 #bfc659
-color3 #bd8a13
-color11 #ffca1b
-color4 #4698a2
-color12 #7cc9ce
-color5 #8c4231
-color13 #d16349
-color6 #d98112
-color14 #e6a96b
-color7 #ddc165
-color15 #ffe9a3
-selection_foreground #241200
diff --git a/kitty-themes/themes/Galaxy.conf b/kitty-themes/themes/Galaxy.conf
deleted file mode 100644
index fab36a7c..00000000
--- a/kitty-themes/themes/Galaxy.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1c2836
-foreground #ffffff
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #000000
-color8 #545454
-color1 #f9555f
-color9 #fa8b8e
-color2 #20af89
-color10 #34bb99
-color3 #fdf029
-color11 #ffff55
-color4 #589cf5
-color12 #589cf5
-color5 #934d95
-color13 #e75598
-color6 #1e9ee6
-color14 #3978bb
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #1c2836
diff --git a/kitty-themes/themes/Github.conf b/kitty-themes/themes/Github.conf
deleted file mode 100644
index 22122d73..00000000
--- a/kitty-themes/themes/Github.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #f4f4f4
-foreground #3e3e3e
-cursor #3f3f3f
-selection_background #a9c1e2
-color0 #3e3e3e
-color8 #666666
-color1 #970b16
-color9 #de0000
-color2 #07962a
-color10 #87d5a2
-color3 #f7edc7
-color11 #f0cf06
-color4 #003e8a
-color12 #2e6cba
-color5 #e94691
-color13 #ffa29f
-color6 #89d1ec
-color14 #1cfafe
-color7 #ffffff
-color15 #ffffff
-selection_foreground #f4f4f4
diff --git a/kitty-themes/themes/Glacier.conf b/kitty-themes/themes/Glacier.conf
deleted file mode 100644
index 08de6979..00000000
--- a/kitty-themes/themes/Glacier.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0c1115
-foreground #ffffff
-cursor #6c6c6c
-selection_background #bd2523
-color0 #2e343c
-color8 #404a55
-color1 #bd0f2f
-color9 #bd0f2f
-color2 #35a770
-color10 #49e998
-color3 #fb9435
-color11 #fddf6e
-color4 #1f5872
-color12 #2a8bc1
-color5 #bd2523
-color13 #ea4727
-color6 #778397
-color14 #a0b6d3
-color7 #ffffff
-color15 #ffffff
-selection_foreground #0c1115
diff --git a/kitty-themes/themes/GoaBase.conf b/kitty-themes/themes/GoaBase.conf
deleted file mode 100644
index fcc67415..00000000
--- a/kitty-themes/themes/GoaBase.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2f0033
-foreground #f6ed00
-cursor #1a6500
-selection_background #100a24
-color0 #880041
-color8 #411a6d
-color1 #f78000
-color9 #f800e1
-color2 #249000
-color10 #5743ff
-color3 #f40000
-color11 #ea00d7
-color4 #000482
-color12 #b90003
-color5 #f43bff
-color13 #9a5952
-color6 #3affff
-color14 #c8f9f3
-color7 #000000
-color15 #f5f4fb
-selection_foreground #2f0033
diff --git a/kitty-themes/themes/Grape.conf b/kitty-themes/themes/Grape.conf
deleted file mode 100644
index 38b8a18f..00000000
--- a/kitty-themes/themes/Grape.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #161423
-foreground #9e9ea0
-cursor #a188f7
-selection_background #483d70
-color0 #2d283e
-color8 #58506a
-color1 #ec2160
-color9 #f0719a
-color2 #1fa91b
-color10 #52a95d
-color3 #8ddc1f
-color11 #b2dc87
-color4 #487cf4
-color12 #a9bbeb
-color5 #8c35c8
-color13 #ac81c1
-color6 #3added
-color14 #9ce3ea
-color7 #9e9ea0
-color15 #a188f7
-selection_foreground #161423
diff --git a/kitty-themes/themes/Grass.conf b/kitty-themes/themes/Grass.conf
deleted file mode 100644
index ef9c3217..00000000
--- a/kitty-themes/themes/Grass.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #12773d
-foreground #fff0a4
-cursor #8b2800
-selection_background #b64825
-color0 #000000
-color8 #545454
-color1 #ba0000
-color9 #ba0000
-color2 #00ba00
-color10 #00ba00
-color3 #e6af00
-color11 #e6af00
-color4 #0000a3
-color12 #0000ba
-color5 #950062
-color13 #ff54ff
-color6 #00baba
-color14 #54ffff
-color7 #bababa
-color15 #ffffff
-selection_foreground #12773d
diff --git a/kitty-themes/themes/Hardcore.conf b/kitty-themes/themes/Hardcore.conf
deleted file mode 100644
index 9af78ea1..00000000
--- a/kitty-themes/themes/Hardcore.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #121212
-foreground #a0a0a0
-cursor #bbbbbb
-selection_background #453a39
-color0 #1b1d1e
-color8 #505354
-color1 #f92672
-color9 #ff669d
-color2 #a6e22e
-color10 #beed5f
-color3 #fd971f
-color11 #e6db74
-color4 #66d9ef
-color12 #66d9ef
-color5 #9e6ffe
-color13 #9e6ffe
-color6 #5e7175
-color14 #a3babf
-color7 #ccccc6
-color15 #f8f8f2
-selection_foreground #121212
diff --git a/kitty-themes/themes/Harper.conf b/kitty-themes/themes/Harper.conf
deleted file mode 100644
index 2579aac7..00000000
--- a/kitty-themes/themes/Harper.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #a7a39c
-cursor #a7a39c
-selection_background #5a5753
-color0 #000000
-color8 #716d69
-color1 #f7b63e
-color9 #f7b63e
-color2 #7fb5e1
-color10 #7fb5e1
-color3 #d6da24
-color11 #d6da24
-color4 #489d48
-color12 #489d48
-color5 #b295c5
-color13 #b295c5
-color6 #f4bed6
-color14 #f4bed6
-color7 #a7a39c
-color15 #fefbe9
-selection_foreground #000000
diff --git a/kitty-themes/themes/Highway.conf b/kitty-themes/themes/Highway.conf
deleted file mode 100644
index f0dfa4df..00000000
--- a/kitty-themes/themes/Highway.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212224
-foreground #ededed
-cursor #dfd9b8
-selection_background #384563
-color0 #000000
-color8 #5c4f49
-color1 #cf0d17
-color9 #ef7d17
-color2 #128033
-color10 #b1d130
-color3 #ffca3d
-color11 #fff11f
-color4 #006ab3
-color12 #4fc2fd
-color5 #6a2674
-color13 #de0070
-color6 #384563
-color14 #5c4f49
-color7 #ededed
-color15 #fefffe
-selection_foreground #212224
diff --git a/kitty-themes/themes/Hipster_Green.conf b/kitty-themes/themes/Hipster_Green.conf
deleted file mode 100644
index 26adfe74..00000000
--- a/kitty-themes/themes/Hipster_Green.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0f0a05
-foreground #84c137
-cursor #23ff18
-selection_background #083905
-color0 #000000
-color8 #666666
-color1 #b6204a
-color9 #e50000
-color2 #00a600
-color10 #86a83e
-color3 #bebe00
-color11 #e5e500
-color4 #246db2
-color12 #0000ff
-color5 #b200b2
-color13 #e500e5
-color6 #00a6b2
-color14 #00e5e5
-color7 #bfbfbf
-color15 #e5e5e5
-selection_foreground #0f0a05
diff --git a/kitty-themes/themes/Homebrew.conf b/kitty-themes/themes/Homebrew.conf
deleted file mode 100644
index d96bb720..00000000
--- a/kitty-themes/themes/Homebrew.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #000000
-foreground #00ff00
-cursor #23ff18
-selection_background #083905
-color0 #000000
-color8 #666666
-color1 #990000
-color9 #e50000
-color2 #00a600
-color10 #00d900
-color3 #999900
-color11 #e5e500
-color4 #0000b2
-color12 #0000ff
-color5 #b200b2
-color13 #e500e5
-color6 #00a6b2
-color14 #00e5e5
-color7 #bebebe
-color15 #e5e5e5
-selection_foreground #000000
diff --git a/kitty-themes/themes/Hurtado.conf b/kitty-themes/themes/Hurtado.conf
deleted file mode 100644
index 1ca0421e..00000000
--- a/kitty-themes/themes/Hurtado.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #dadbda
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #575757
-color8 #252525
-color1 #ff1b00
-color9 #d41c00
-color2 #a5df55
-color10 #a5df55
-color3 #fbe74a
-color11 #fbe749
-color4 #486387
-color12 #89bdff
-color5 #fc5ef0
-color13 #bf00c0
-color6 #85e9fe
-color14 #85e9fe
-color7 #cbcbcb
-color15 #dbdbdb
-selection_foreground #000000
diff --git a/kitty-themes/themes/Hybrid.conf b/kitty-themes/themes/Hybrid.conf
deleted file mode 100644
index 8f9dc50f..00000000
--- a/kitty-themes/themes/Hybrid.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #161718
-foreground #b7bcb9
-cursor #b7bcb9
-selection_background #1e1f22
-color0 #2a2e33
-color8 #1d1e21
-color1 #b74d50
-color9 #8c2d32
-color2 #b3be5a
-color10 #788331
-color3 #e3b55e
-color11 #e5894f
-color4 #6d90b0
-color12 #4b6b88
-color5 #a07eab
-color13 #6e4f79
-color6 #7fbeb3
-color14 #4d7b73
-color7 #b5b8b6
-color15 #5a6169
-selection_foreground #161718
diff --git a/kitty-themes/themes/IC_Green_PPL.conf b/kitty-themes/themes/IC_Green_PPL.conf
deleted file mode 100644
index 35587dd8..00000000
--- a/kitty-themes/themes/IC_Green_PPL.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #3a3c3e
-foreground #d9eed2
-cursor #41ff58
-selection_background #2a9b34
-color0 #1e1e1e
-color8 #03260f
-color1 #fb0029
-color9 #a6ff3e
-color2 #329b24
-color10 #9fff6d
-color3 #649a25
-color11 #d1ff6d
-color4 #149b45
-color12 #72ffb5
-color5 #53b82b
-color13 #50ff3d
-color6 #2bb767
-color14 #22ff71
-color7 #dffeee
-color15 #daeed0
-selection_foreground #3a3c3e
diff --git a/kitty-themes/themes/IC_Orange_PPL.conf b/kitty-themes/themes/IC_Orange_PPL.conf
deleted file mode 100644
index b8dead8a..00000000
--- a/kitty-themes/themes/IC_Orange_PPL.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #262626
-foreground #ffcb83
-cursor #fb521c
-selection_background #c03f1f
-color0 #000000
-color8 #6a4e29
-color1 #c03900
-color9 #ff8b67
-color2 #a3a900
-color10 #f6ff3f
-color3 #caae00
-color11 #ffe36e
-color4 #bd6c00
-color12 #ffbd54
-color5 #fb5d00
-color13 #fc874f
-color6 #f79400
-color14 #c59752
-color7 #ffc88a
-color15 #f9f9fe
-selection_foreground #262626
diff --git a/kitty-themes/themes/IR_Black.conf b/kitty-themes/themes/IR_Black.conf
deleted file mode 100644
index 5b8709b1..00000000
--- a/kitty-themes/themes/IR_Black.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #f1f1f1
-cursor #7f7f7f
-selection_background #b4d5ff
-color0 #4f4f4f
-color8 #7b7b7b
-color1 #fa6c5f
-color9 #fcb6af
-color2 #a8fe60
-color10 #ceffab
-color3 #fffeb6
-color11 #fffecc
-color4 #96cafd
-color12 #b5dcfe
-color5 #fa72fc
-color13 #fb9bfe
-color6 #c6c4fd
-color14 #dfdffd
-color7 #eeedee
-color15 #fefffe
-selection_foreground #000000
diff --git a/kitty-themes/themes/Jackie_Brown.conf b/kitty-themes/themes/Jackie_Brown.conf
deleted file mode 100644
index 08a72e45..00000000
--- a/kitty-themes/themes/Jackie_Brown.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2c1c15
-foreground #ffcc2f
-cursor #23ff18
-selection_background #ae8c20
-color0 #2c1d16
-color8 #666666
-color1 #ef5734
-color9 #e50000
-color2 #2baf2b
-color10 #86a83e
-color3 #bdbe00
-color11 #e5e500
-color4 #246db2
-color12 #0000ff
-color5 #cf5ec0
-color13 #e500e5
-color6 #00acee
-color14 #00e5e5
-color7 #bfbfbf
-color15 #e5e5e5
-selection_foreground #2c1c15
diff --git a/kitty-themes/themes/Japanesque.conf b/kitty-themes/themes/Japanesque.conf
deleted file mode 100644
index c0452737..00000000
--- a/kitty-themes/themes/Japanesque.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1d1d1d
-foreground #f7f6ec
-cursor #eccf4f
-selection_background #165776
-color0 #343835
-color8 #585a58
-color1 #ce3e60
-color9 #d18ea6
-color2 #7bb75b
-color10 #767e2b
-color3 #e8b32a
-color11 #77592e
-color4 #4c99d3
-color12 #135879
-color5 #a57fc4
-color13 #5f4190
-color6 #389aac
-color14 #76bbca
-color7 #f9faf6
-color15 #b1b5ae
-selection_foreground #1d1d1d
diff --git a/kitty-themes/themes/Jellybeans.conf b/kitty-themes/themes/Jellybeans.conf
deleted file mode 100644
index bac5fb72..00000000
--- a/kitty-themes/themes/Jellybeans.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #111111
-foreground #dedede
-cursor #ffa460
-selection_background #464d91
-color0 #919191
-color8 #bdbdbd
-color1 #e17373
-color9 #ffa0a0
-color2 #94b978
-color10 #bddeab
-color3 #ffb97b
-color11 #ffdba0
-color4 #96bddb
-color12 #b1d7f6
-color5 #e1c0fa
-color13 #fbdaff
-color6 #00988e
-color14 #19b2a7
-color7 #dedede
-color15 #ffffff
-selection_foreground #111111
diff --git a/kitty-themes/themes/JetBrains_Darcula.conf b/kitty-themes/themes/JetBrains_Darcula.conf
deleted file mode 100644
index 64ac1462..00000000
--- a/kitty-themes/themes/JetBrains_Darcula.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #202020
-foreground #adadad
-cursor #ffffff
-selection_background #1a3272
-color0 #000000
-color8 #545454
-color1 #fa5355
-color9 #fb7172
-color2 #126e00
-color10 #67ff4f
-color3 #c2c300
-color11 #ffff00
-color4 #4581eb
-color12 #6d9df1
-color5 #fa54ff
-color13 #fb82ff
-color6 #33c2c1
-color14 #60d3d1
-color7 #adadad
-color15 #eeeeee
-selection_foreground #202020
diff --git a/kitty-themes/themes/Kibble.conf b/kitty-themes/themes/Kibble.conf
deleted file mode 100644
index 3b131059..00000000
--- a/kitty-themes/themes/Kibble.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0e100a
-foreground #f7f7f7
-cursor #9fda9c
-selection_background #9ba686
-color0 #4d4d4d
-color8 #5a5a5a
-color1 #c70031
-color9 #f01578
-color2 #29cf13
-color10 #6ce05c
-color3 #d8e30e
-color11 #f3f79e
-color4 #3449d1
-color12 #97a4f7
-color5 #8400ff
-color13 #c495f0
-color6 #0798ab
-color14 #68f2e0
-color7 #e2d1e3
-color15 #ffffff
-selection_foreground #0e100a
diff --git a/kitty-themes/themes/Later_This_Evening.conf b/kitty-themes/themes/Later_This_Evening.conf
deleted file mode 100644
index 13547884..00000000
--- a/kitty-themes/themes/Later_This_Evening.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212121
-foreground #949494
-cursor #424242
-selection_background #424242
-color0 #2b2b2b
-color8 #444747
-color1 #d35a5f
-color9 #d3222e
-color2 #afba66
-color10 #aabb39
-color3 #e5d289
-color11 #e4bd39
-color4 #a0b9d5
-color12 #6599d5
-color5 #bf92d5
-color13 #aa52d5
-color6 #91beb6
-color14 #5fbfad
-color7 #3b3c3c
-color15 #c0c2c2
-selection_foreground #212121
diff --git a/kitty-themes/themes/Lavandula.conf b/kitty-themes/themes/Lavandula.conf
deleted file mode 100644
index 14aa7aaa..00000000
--- a/kitty-themes/themes/Lavandula.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #050014
-foreground #736d7c
-cursor #8b91fa
-selection_background #36323b
-color0 #230045
-color8 #362c45
-color1 #7c1525
-color9 #df5066
-color2 #337e6f
-color10 #52e0c4
-color3 #7f6f49
-color11 #e0c286
-color4 #4f4a7f
-color12 #8e86df
-color5 #593f7e
-color13 #a675df
-color6 #57767f
-color14 #9ad3df
-color7 #736d7c
-color15 #8b91fa
-selection_foreground #050014
diff --git a/kitty-themes/themes/LiquidCarbon.conf b/kitty-themes/themes/LiquidCarbon.conf
deleted file mode 100644
index 2f3a9940..00000000
--- a/kitty-themes/themes/LiquidCarbon.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2f2f2f
-foreground #afc2c2
-cursor #ffffff
-selection_background #7cbeff
-color0 #000000
-color8 #000000
-color1 #ff2f2f
-color9 #ff2f2f
-color2 #549a6f
-color10 #549a6f
-color3 #ccac00
-color11 #ccac00
-color4 #0099cc
-color12 #0099cc
-color5 #cc68c8
-color13 #cc68c8
-color6 #79c4cc
-color14 #79c4cc
-color7 #bccccc
-color15 #bccccc
-selection_foreground #2f2f2f
diff --git a/kitty-themes/themes/LiquidCarbonTransparent.conf b/kitty-themes/themes/LiquidCarbonTransparent.conf
deleted file mode 100644
index 41e5f895..00000000
--- a/kitty-themes/themes/LiquidCarbonTransparent.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #afc2c2
-cursor #ffffff
-selection_background #7cbeff
-color0 #000000
-color8 #000000
-color1 #ff2f2f
-color9 #ff2f2f
-color2 #549a6f
-color10 #549a6f
-color3 #ccac00
-color11 #ccac00
-color4 #0099cc
-color12 #0099cc
-color5 #cc68c8
-color13 #cc68c8
-color6 #79c4cc
-color14 #79c4cc
-color7 #bccccc
-color15 #bccccc
-selection_foreground #000000
diff --git a/kitty-themes/themes/LiquidCarbonTransparentInverse.conf b/kitty-themes/themes/LiquidCarbonTransparentInverse.conf
deleted file mode 100644
index d584624e..00000000
--- a/kitty-themes/themes/LiquidCarbonTransparentInverse.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #afc2c2
-cursor #ffffff
-selection_background #7cbeff
-color0 #bbcbcc
-color8 #ffffff
-color1 #ff2f2f
-color9 #ff2f2f
-color2 #549a6f
-color10 #549a6f
-color3 #ccac00
-color11 #ccac00
-color4 #0099cc
-color12 #0099cc
-color5 #cc68c8
-color13 #cc68c8
-color6 #79c4cc
-color14 #79c4cc
-color7 #000000
-color15 #000000
-selection_foreground #000000
diff --git a/kitty-themes/themes/Man_Page.conf b/kitty-themes/themes/Man_Page.conf
deleted file mode 100644
index 7c8aa387..00000000
--- a/kitty-themes/themes/Man_Page.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #fef49c
-foreground #000000
-cursor #7f7f7f
-selection_background #a4c9cd
-color0 #000000
-color8 #666666
-color1 #cc0000
-color9 #e50000
-color2 #00a600
-color10 #00d900
-color3 #999900
-color11 #e5e500
-color4 #0000b2
-color12 #0000ff
-color5 #b200b2
-color13 #e500e5
-color6 #00a6b2
-color14 #00e5e5
-color7 #cccccc
-color15 #e5e5e5
-selection_foreground #fef49c
diff --git a/kitty-themes/themes/Material.conf b/kitty-themes/themes/Material.conf
deleted file mode 100644
index ba38846f..00000000
--- a/kitty-themes/themes/Material.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #eaeaea
-foreground #222221
-cursor #16aec9
-selection_background #c1c1c1
-color0 #212121
-color8 #424242
-color1 #b7141e
-color9 #e83a3f
-color2 #457b23
-color10 #7aba39
-color3 #f5971d
-color11 #fee92e
-color4 #134eb2
-color12 #53a4f3
-color5 #550087
-color13 #a94dbb
-color6 #0e707c
-color14 #26bad1
-color7 #eeeeee
-color15 #d8d8d8
-selection_foreground #eaeaea
diff --git a/kitty-themes/themes/MaterialDark.conf b/kitty-themes/themes/MaterialDark.conf
deleted file mode 100644
index e599021c..00000000
--- a/kitty-themes/themes/MaterialDark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #222221
-foreground #e4e4e4
-cursor #16aec9
-selection_background #dedede
-color0 #212121
-color8 #424242
-color1 #b7141e
-color9 #e83a3f
-color2 #457b23
-color10 #7aba39
-color3 #f5971d
-color11 #fee92e
-color4 #134eb2
-color12 #53a4f3
-color5 #550087
-color13 #a94dbb
-color6 #0e707c
-color14 #26bad1
-color7 #eeeeee
-color15 #d8d8d8
-selection_foreground #222221
diff --git a/kitty-themes/themes/Mathias.conf b/kitty-themes/themes/Mathias.conf
deleted file mode 100644
index cd1ab142..00000000
--- a/kitty-themes/themes/Mathias.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #bbbbbb
-cursor #bbbbbb
-selection_background #545454
-color0 #000000
-color8 #545454
-color1 #e52222
-color9 #ff5555
-color2 #a6e32d
-color10 #55ff55
-color3 #fc951e
-color11 #ffff55
-color4 #c48dff
-color12 #5555ff
-color5 #fa2573
-color13 #ff55ff
-color6 #67d9f0
-color14 #55ffff
-color7 #f2f2f2
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/Medallion.conf b/kitty-themes/themes/Medallion.conf
deleted file mode 100644
index 0ad88051..00000000
--- a/kitty-themes/themes/Medallion.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1d1808
-foreground #cac296
-cursor #d3b92f
-selection_background #616cab
-color0 #000000
-color8 #5e5118
-color1 #b54c00
-color9 #ff9148
-color2 #7c8a16
-color10 #b1c93a
-color3 #d2bd25
-color11 #ffe449
-color4 #606baf
-color12 #abb8ff
-color5 #8b5990
-color13 #fe9fff
-color6 #906b25
-color14 #ffbb51
-color7 #c9c199
-color15 #fed597
-selection_foreground #1d1808
diff --git a/kitty-themes/themes/Misterioso.conf b/kitty-themes/themes/Misterioso.conf
deleted file mode 100644
index 1960f9bd..00000000
--- a/kitty-themes/themes/Misterioso.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2d3743
-foreground #e1e1e0
-cursor #000000
-selection_background #2d37ff
-color0 #000000
-color8 #545454
-color1 #ff4141
-color9 #ff3241
-color2 #74ae68
-color10 #74cc68
-color3 #ffac28
-color11 #ffb928
-color4 #338e86
-color12 #23d6d6
-color5 #9413e5
-color13 #ff37ff
-color6 #23d6d6
-color14 #00ece1
-color7 #e1e1df
-color15 #ffffff
-selection_foreground #2d3743
diff --git a/kitty-themes/themes/Molokai.conf b/kitty-themes/themes/Molokai.conf
deleted file mode 100644
index cd35d1f3..00000000
--- a/kitty-themes/themes/Molokai.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #121212
-foreground #bbbbbb
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #121212
-color8 #545454
-color1 #fa2573
-color9 #f5669c
-color2 #97e123
-color10 #b0e05e
-color3 #dfd460
-color11 #fef26c
-color4 #0f7fcf
-color12 #00afff
-color5 #8700ff
-color13 #af87ff
-color6 #42a7cf
-color14 #50cdfe
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #121212
diff --git a/kitty-themes/themes/MonaLisa.conf b/kitty-themes/themes/MonaLisa.conf
deleted file mode 100644
index 3b1602db..00000000
--- a/kitty-themes/themes/MonaLisa.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #110b0d
-foreground #f6d56a
-cursor #c36c32
-selection_background #f6d56a
-color0 #341a0d
-color8 #874227
-color1 #9b281b
-color9 #ff4230
-color2 #626132
-color10 #b3b163
-color3 #c26e27
-color11 #ff9565
-color4 #515b5c
-color12 #9eb2b3
-color5 #9b1d29
-color13 #ff5b6a
-color6 #588056
-color14 #89cc8e
-color7 #f6d75c
-color15 #ffe597
-selection_foreground #110b0d
diff --git a/kitty-themes/themes/Monokai.conf b/kitty-themes/themes/Monokai.conf
deleted file mode 100644
index 660c0cc7..00000000
--- a/kitty-themes/themes/Monokai.conf
+++ /dev/null
@@ -1,41 +0,0 @@
-# Monokai
-
-background #272822
-foreground #f8f8f2
-cursor #f8f8f2
-selection_background #f8f8f2
-selection_foreground #272822
-active_tab_background #75715e
-active_tab_foreground #272822
-active_border_color #75715e
-inactive_tab_background #272822
-inactive_tab_foreground #75715e
-inactive_border_color #75715e
-url_color #f8f8f2
-
-# 16 Color Space
-
-# black
-color0 #272822
-color8 #75715e
-# red
-color1 #f92672
-color9 #f92672
-# green
-color2 #a6e22e
-color10 #a6e22e
-# yellow
-color3 #e6db74
-color11 #e6db74
-# blue
-color4 #66d9ef
-color12 #66d9ef
-# magenta
-color5 #fd971f
-color13 #fd971f
-# cyan
-color6 #ae81ff
-color14 #ae81ff
-# white
-color7 #f8f8f2
-color15 #f8f8f2
diff --git a/kitty-themes/themes/Monokai_Classic.conf b/kitty-themes/themes/Monokai_Classic.conf
deleted file mode 100644
index 9c74f402..00000000
--- a/kitty-themes/themes/Monokai_Classic.conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #3b3c35
-foreground #fdfff1
-
-cursor #fdfff1
-cursor_text_color #000000
-selection_foreground #3b3c35
-selection_background #fdfff1
-
-# dull black
-color0 #3b3c35
-# light black
-color8 #6e7066
-
-# dull red
-color1 #f82570
-# light red
-color9 #f82570
-
-# dull green
-color2 #a6e12d
-# light green
-color10 #a6e12d
-
-# yellow
-color3 #e4db73
-# light yellow
-color11 #e4db73
-
-# blue
-color4 #fc961f
-# light blue
-color12 #fc961f
-
-# magenta
-color5 #ae81ff
-# light magenta
-color13 #ae81ff
-
-# cyan
-color6 #66d9ee
-# light cyan
-color14 #66d9ee
-
-# dull white
-color7 #fdfff1
-# bright white
-color15 #fdfff1
diff --git a/kitty-themes/themes/Monokai_Pro.conf b/kitty-themes/themes/Monokai_Pro.conf
deleted file mode 100644
index f5a2e8ca..00000000
--- a/kitty-themes/themes/Monokai_Pro.conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #403e41
-foreground #fcfcfa
-
-cursor #fcfcfa
-cursor_text_color #000000
-selection_foreground #403e41
-selection_background #fcfcfa
-
-# dull black
-color0 #403e41
-# light black
-color8 #727072
-
-# dull red
-color1 #ff6188
-# light red
-color9 #ff6188
-
-# dull green
-color2 #a9dc76
-# light green
-color10 #a9dc76
-
-# yellow
-color3 #ffd866
-# light yellow
-color11 #ffd866
-
-# blue
-color4 #fc9867
-# light blue
-color12 #fc9867
-
-# magenta
-color5 #ab9df2
-# light magenta
-color13 #ab9df2
-
-# cyan
-color6 #78dce8
-# light cyan
-color14 #78dce8
-
-# dull white
-color7 #fcfcfa
-# bright white
-color15 #fcfcfa
diff --git a/kitty-themes/themes/Monokai_Pro_(Filter_Machine).conf b/kitty-themes/themes/Monokai_Pro_(Filter_Machine).conf
deleted file mode 100644
index b46e1d32..00000000
--- a/kitty-themes/themes/Monokai_Pro_(Filter_Machine).conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #3a4449
-foreground #f2fffc
-
-cursor #f2fffc
-cursor_text_color #000000
-selection_foreground #3a4449
-selection_background #f2fffc
-
-# dull black
-color0 #3a4449
-# light black
-color8 #6b7678
-
-# dull red
-color1 #ff6d7e
-# light red
-color9 #ff6d7e
-
-# dull green
-color2 #a2e57b
-# light green
-color10 #a2e57b
-
-# yellow
-color3 #ffed72
-# light yellow
-color11 #ffed72
-
-# blue
-color4 #ffb270
-# light blue
-color12 #ffb270
-
-# magenta
-color5 #baa0f8
-# light magenta
-color13 #baa0f8
-
-# cyan
-color6 #7cd5f1
-# light cyan
-color14 #7cd5f1
-
-# dull white
-color7 #f2fffc
-# bright white
-color15 #f2fffc
diff --git a/kitty-themes/themes/Monokai_Pro_(Filter_Octagon).conf b/kitty-themes/themes/Monokai_Pro_(Filter_Octagon).conf
deleted file mode 100644
index 3aee34a2..00000000
--- a/kitty-themes/themes/Monokai_Pro_(Filter_Octagon).conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #3a3d4b
-foreground #eaf2f1
-
-cursor #eaf2f1
-cursor_text_color #000000
-selection_foreground #3a3d4b
-selection_background #eaf2f1
-
-# dull black
-color0 #3a3d4b
-# light black
-color8 #696d77
-
-# dull red
-color1 #ff657a
-# light red
-color9 #ff657a
-
-# dull green
-color2 #bad761
-# light green
-color10 #bad761
-
-# yellow
-color3 #ffd76d
-# light yellow
-color11 #ffd76d
-
-# blue
-color4 #ff9b5e
-# light blue
-color12 #ff9b5e
-
-# magenta
-color5 #c39ac9
-# light magenta
-color13 #c39ac9
-
-# cyan
-color6 #9cd1bb
-# light cyan
-color14 #9cd1bb
-
-# dull white
-color7 #eaf2f1
-# bright white
-color15 #eaf2f1
diff --git a/kitty-themes/themes/Monokai_Pro_(Filter_Ristretto).conf b/kitty-themes/themes/Monokai_Pro_(Filter_Ristretto).conf
deleted file mode 100644
index 599b0e17..00000000
--- a/kitty-themes/themes/Monokai_Pro_(Filter_Ristretto).conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #403838
-foreground #fff1f3
-
-cursor #fff1f3
-cursor_text_color #000000
-selection_foreground #403838
-selection_background #fff1f3
-
-# dull black
-color0 #403838
-# light black
-color8 #72696a
-
-# dull red
-color1 #fd6883
-# light red
-color9 #fd6883
-
-# dull green
-color2 #adda78
-# light green
-color10 #adda78
-
-# yellow
-color3 #f9cc6c
-# light yellow
-color11 #f9cc6c
-
-# blue
-color4 #f38d70
-# light blue
-color12 #f38d70
-
-# magenta
-color5 #a8a9eb
-# light magenta
-color13 #a8a9eb
-
-# cyan
-color6 #85dacc
-# light cyan
-color14 #85dacc
-
-# dull white
-color7 #fff1f3
-# bright white
-color15 #fff1f3
diff --git a/kitty-themes/themes/Monokai_Pro_(Filter_Spectrum).conf b/kitty-themes/themes/Monokai_Pro_(Filter_Spectrum).conf
deleted file mode 100644
index 5617a6d8..00000000
--- a/kitty-themes/themes/Monokai_Pro_(Filter_Spectrum).conf
+++ /dev/null
@@ -1,47 +0,0 @@
-background #363537
-foreground #f7f1ff
-
-cursor #f7f1ff
-cursor_text_color #000000
-selection_foreground #363537
-selection_background #f7f1ff
-
-# dull black
-color0 #363537
-# light black
-color8 #69676c
-
-# dull red
-color1 #fc618d
-# light red
-color9 #fc618d
-
-# dull green
-color2 #7bd88f
-# light green
-color10 #7bd88f
-
-# yellow
-color3 #fce566
-# light yellow
-color11 #fce566
-
-# blue
-color4 #fd9353
-# light blue
-color12 #fd9353
-
-# magenta
-color5 #948ae3
-# light magenta
-color13 #948ae3
-
-# cyan
-color6 #5ad4e6
-# light cyan
-color14 #5ad4e6
-
-# dull white
-color7 #f7f1ff
-# bright white
-color15 #f7f1ff
diff --git a/kitty-themes/themes/Monokai_Soda.conf b/kitty-themes/themes/Monokai_Soda.conf
deleted file mode 100644
index eb7a24d6..00000000
--- a/kitty-themes/themes/Monokai_Soda.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #191919
-foreground #c4c4b5
-cursor #f6f6ec
-selection_background #343434
-color0 #191919
-color8 #615e4b
-color1 #f3005f
-color9 #f3005f
-color2 #97e023
-color10 #97e023
-color3 #fa8419
-color11 #dfd561
-color4 #9c64fe
-color12 #9c64fe
-color5 #f3005f
-color13 #f3005f
-color6 #57d1ea
-color14 #57d1ea
-color7 #c4c4b5
-color15 #f6f6ee
-selection_foreground #191919
diff --git a/kitty-themes/themes/N0tch2k.conf b/kitty-themes/themes/N0tch2k.conf
deleted file mode 100644
index 3e2bb6cf..00000000
--- a/kitty-themes/themes/N0tch2k.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #222222
-foreground #a0a0a0
-cursor #a99075
-selection_background #4d4d4d
-color0 #383838
-color8 #474747
-color1 #a95551
-color9 #a97775
-color2 #666666
-color10 #8c8c8c
-color3 #a98051
-color11 #a99175
-color4 #657d3e
-color12 #98bd5e
-color5 #767676
-color13 #a3a3a3
-color6 #c9c9c9
-color14 #dcdcdc
-color7 #d0b8a3
-color15 #d8c8bb
-selection_foreground #222222
diff --git a/kitty-themes/themes/Neopolitan.conf b/kitty-themes/themes/Neopolitan.conf
deleted file mode 100644
index 2c6ffce1..00000000
--- a/kitty-themes/themes/Neopolitan.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #271f19
-foreground #ffffff
-cursor #ffffff
-selection_background #253b76
-color0 #000000
-color8 #000000
-color1 #800000
-color9 #800000
-color2 #61ce3c
-color10 #61ce3c
-color3 #fbde2d
-color11 #fbde2d
-color4 #253b76
-color12 #253b76
-color5 #ff0080
-color13 #ff0080
-color6 #8da6ce
-color14 #8da6ce
-color7 #f8f8f8
-color15 #f8f8f8
-selection_foreground #271f19
diff --git a/kitty-themes/themes/Neutron.conf b/kitty-themes/themes/Neutron.conf
deleted file mode 100644
index 9f2fe4de..00000000
--- a/kitty-themes/themes/Neutron.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1d22
-foreground #e6e8ee
-cursor #f6f6ec
-selection_background #2e353d
-color0 #22252b
-color8 #22252b
-color1 #b53f36
-color9 #b53f36
-color2 #5ab977
-color10 #5ab977
-color3 #ddb566
-color11 #ddb566
-color4 #6a7b92
-color12 #6a7b92
-color5 #a3799d
-color13 #a3799d
-color6 #3f93a8
-color14 #3f93a8
-color7 #e6e8ee
-color15 #ebedf2
-selection_foreground #1b1d22
diff --git a/kitty-themes/themes/NightLion_v1.conf b/kitty-themes/themes/NightLion_v1.conf
deleted file mode 100644
index 1021c468..00000000
--- a/kitty-themes/themes/NightLion_v1.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #bbbbbb
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #4c4c4c
-color8 #545454
-color1 #bb0000
-color9 #ff5555
-color2 #5ede8f
-color10 #55ff55
-color3 #f2f067
-color11 #ffff55
-color4 #266ad7
-color12 #5555ff
-color5 #bb00bb
-color13 #ff55ff
-color6 #00d9df
-color14 #55ffff
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/NightLion_v2.conf b/kitty-themes/themes/NightLion_v2.conf
deleted file mode 100644
index aed7f8df..00000000
--- a/kitty-themes/themes/NightLion_v2.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #171717
-foreground #bbbbbb
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #4c4c4c
-color8 #545454
-color1 #bb0000
-color9 #ff5555
-color2 #03f622
-color10 #7df61c
-color3 #f2f067
-color11 #ffff55
-color4 #63d0f0
-color12 #62cae7
-color5 #ce6fda
-color13 #ff9af5
-color6 #00d9df
-color14 #00ccd7
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #171717
diff --git a/kitty-themes/themes/Nova.conf b/kitty-themes/themes/Nova.conf
deleted file mode 100644
index c3f2da1d..00000000
--- a/kitty-themes/themes/Nova.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #3c4c54
-foreground #8798a3
-cursor #7fc1c9
-color0 #8799a4
-color8 #c4d3dc
-color1 #efc08d
-color9 #ef8358
-color2 #a6cb91
-color10 #a8ce93
-color3 #d7d690
-color11 #e5e77f
-color4 #83afe4
-color12 #69c8ff
-color5 #d460da
-color13 #d18ec2
-color6 #7fc1b6
-color14 #00e59f
-color7 #c4d3dc
-color15 #e6eef3
-selection_foreground #3c4c54
-selection_background #7fc1ca
diff --git a/kitty-themes/themes/Novel.conf b/kitty-themes/themes/Novel.conf
deleted file mode 100644
index 03220e37..00000000
--- a/kitty-themes/themes/Novel.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #dfdbc3
-foreground #3b2322
-cursor #73635a
-selection_background #a4a390
-color0 #000000
-color8 #7f7f7f
-color1 #cc0000
-color9 #cc0000
-color2 #009600
-color10 #009600
-color3 #d06b00
-color11 #d06b00
-color4 #0000cc
-color12 #0000cc
-color5 #cc00cc
-color13 #cc00cc
-color6 #0087cc
-color14 #0086cb
-color7 #cccccc
-color15 #ffffff
-selection_foreground #dfdbc3
diff --git a/kitty-themes/themes/Obsidian.conf b/kitty-themes/themes/Obsidian.conf
deleted file mode 100644
index 7265d44c..00000000
--- a/kitty-themes/themes/Obsidian.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #273032
-foreground #cccccc
-cursor #c0cad0
-selection_background #3d4b4e
-color0 #000000
-color8 #545454
-color1 #a50001
-color9 #ff0003
-color2 #00bb00
-color10 #92c763
-color3 #fecc22
-color11 #fef773
-color4 #399bda
-color12 #a0d6ff
-color5 #bb00bb
-color13 #ff55ff
-color6 #00bbbb
-color14 #55ffff
-color7 #bbbbbb
-color15 #ffffff
-selection_foreground #273032
diff --git a/kitty-themes/themes/Ocean.conf b/kitty-themes/themes/Ocean.conf
deleted file mode 100644
index ad6205a6..00000000
--- a/kitty-themes/themes/Ocean.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #214fbc
-foreground #ffffff
-cursor #7f7f7f
-selection_background #216dff
-color0 #000000
-color8 #666666
-color1 #990000
-color9 #e50000
-color2 #00a600
-color10 #00d900
-color3 #999900
-color11 #e5e500
-color4 #0000b2
-color12 #0000ff
-color5 #b200b2
-color13 #e500e5
-color6 #00a6b2
-color14 #00e5e5
-color7 #bebebe
-color15 #e5e5e5
-selection_foreground #214fbc
diff --git a/kitty-themes/themes/OceanicMaterial.conf b/kitty-themes/themes/OceanicMaterial.conf
deleted file mode 100644
index 9403518f..00000000
--- a/kitty-themes/themes/OceanicMaterial.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1c262b
-foreground #c1c8d6
-cursor #b2b8c3
-selection_background #6dc1b8
-color0 #000000
-color8 #767676
-color1 #ee2a29
-color9 #dc5b60
-color2 #3fa33f
-color10 #70be71
-color3 #fee92e
-color11 #fef063
-color4 #1d80ef
-color12 #53a4f3
-color5 #8800a0
-color13 #a94dbb
-color6 #16aec9
-color14 #42c6d9
-color7 #a4a4a4
-color15 #fffefe
-selection_foreground #1c262b
diff --git a/kitty-themes/themes/Ollie.conf b/kitty-themes/themes/Ollie.conf
deleted file mode 100644
index 66eecf58..00000000
--- a/kitty-themes/themes/Ollie.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212024
-foreground #8a8dae
-cursor #5b6da7
-selection_background #1e3965
-color0 #000000
-color8 #5a3625
-color1 #ab2e30
-color9 #ff3d48
-color2 #31ab60
-color10 #3bff99
-color3 #ab4200
-color11 #ff5e1e
-color4 #2c56ab
-color12 #4487ff
-color5 #af8427
-color13 #ffc21c
-color6 #1fa5ab
-color14 #1efaff
-color7 #8a8dab
-color15 #5b6da7
-selection_foreground #212024
diff --git a/kitty-themes/themes/OneDark.conf b/kitty-themes/themes/OneDark.conf
deleted file mode 100644
index ad6cb4e1..00000000
--- a/kitty-themes/themes/OneDark.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-# One Dark by Giuseppe Cesarano, https://github.com/GiuseppeCesarano
-# This work is licensed under the terms of the GPL-2.0 license.
-# For a copy, see https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html.
-
-foreground #979eab
-background #282c34
-cursor #cccccc
-color0 #282c34
-color1 #e06c75
-color2 #98c379
-color3 #e5c07b
-color4 #61afef
-color5 #be5046
-color6 #56b6c2
-color7 #979eab
-color8 #393e48
-color9 #d19a66
-color10 #56b6c2
-color11 #e5c07b
-color12 #61afef
-color13 #be5046
-color14 #56b6c2
-color15 #abb2bf
-selection_foreground #282c34
-selection_background #979eab
diff --git a/kitty-themes/themes/Parasio_Dark.conf b/kitty-themes/themes/Parasio_Dark.conf
deleted file mode 100644
index 4cc1e8d6..00000000
--- a/kitty-themes/themes/Parasio_Dark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2f1d2e
-foreground #a39d9b
-cursor #a39d9b
-selection_background #4f414c
-color0 #2f1d2e
-color8 #776d70
-color1 #ef6154
-color9 #ef6154
-color2 #48b685
-color10 #48b685
-color3 #fec418
-color11 #fec418
-color4 #05b6ef
-color12 #05b6ef
-color5 #805ba3
-color13 #805ba3
-color6 #5bc4be
-color14 #5bc4be
-color7 #a39d9b
-color15 #e6e9da
-selection_foreground #2f1d2e
diff --git a/kitty-themes/themes/PaulMillr.conf b/kitty-themes/themes/PaulMillr.conf
deleted file mode 100644
index 72cc95cb..00000000
--- a/kitty-themes/themes/PaulMillr.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #f1f1f1
-cursor #4c4c4c
-selection_background #414141
-color0 #2a2a2a
-color8 #666666
-color1 #ff0000
-color9 #ff007f
-color2 #78ff0e
-color10 #66ff66
-color3 #e6be00
-color11 #f3d64d
-color4 #396ad6
-color12 #7099ec
-color5 #b348bd
-color13 #da66e5
-color6 #66ccff
-color14 #79def1
-color7 #bababa
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/PencilDark.conf b/kitty-themes/themes/PencilDark.conf
deleted file mode 100644
index 3926f6cb..00000000
--- a/kitty-themes/themes/PencilDark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #202020
-foreground #f0f0f0
-cursor #20bafb
-selection_background #b6d6fc
-color0 #202020
-color8 #414141
-color1 #c30670
-color9 #fb0079
-color2 #10a778
-color10 #5ed6ae
-color3 #a79c14
-color11 #f3e42f
-color4 #008ec4
-color12 #20bafb
-color5 #523b78
-color13 #6854de
-color6 #20a4b9
-color14 #4fb8cc
-color7 #d9d9d9
-color15 #f0f0f0
-selection_foreground #202020
diff --git a/kitty-themes/themes/PencilLight.conf b/kitty-themes/themes/PencilLight.conf
deleted file mode 100644
index 4311c705..00000000
--- a/kitty-themes/themes/PencilLight.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #f0f0f0
-foreground #414141
-cursor #20bafb
-selection_background #b6d6fc
-color0 #202020
-color8 #414141
-color1 #c30670
-color9 #fb0079
-color2 #10a778
-color10 #5ed6ae
-color3 #a79c14
-color11 #f3e42f
-color4 #008ec4
-color12 #20bafb
-color5 #523b78
-color13 #6854de
-color6 #20a4b9
-color14 #4fb8cc
-color7 #d9d9d9
-color15 #f0f0f0
-selection_foreground #f0f0f0
diff --git a/kitty-themes/themes/Piatto_Light.conf b/kitty-themes/themes/Piatto_Light.conf
deleted file mode 100644
index 401fe911..00000000
--- a/kitty-themes/themes/Piatto_Light.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #ffffff
-foreground #414141
-cursor #5e76c7
-selection_background #6f6a4e
-color0 #414141
-color8 #3e3e3e
-color1 #b23670
-color9 #da3365
-color2 #66781d
-color10 #829428
-color3 #cc6e33
-color11 #cc6e33
-color4 #3b5ea7
-color12 #3b5ea7
-color5 #a353b2
-color13 #a353b2
-color6 #66781d
-color14 #829428
-color7 #ffffff
-color15 #f1f1f1
-selection_foreground #ffffff
diff --git a/kitty-themes/themes/Pnevma.conf b/kitty-themes/themes/Pnevma.conf
deleted file mode 100644
index 1dda097c..00000000
--- a/kitty-themes/themes/Pnevma.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1c1c1c
-foreground #d0d0d0
-cursor #e3c8ae
-selection_background #4d4d4d
-color0 #2f2e2d
-color8 #4a4845
-color1 #a36666
-color9 #d78787
-color2 #90a57d
-color10 #afbea2
-color3 #d7af87
-color11 #e4c9af
-color4 #7fa5bd
-color12 #a1bdce
-color5 #c79ec4
-color13 #d7beda
-color6 #8adbb4
-color14 #b1e7dd
-color7 #d0d0d0
-color15 #efefef
-selection_foreground #1c1c1c
diff --git a/kitty-themes/themes/Pro.conf b/kitty-themes/themes/Pro.conf
deleted file mode 100644
index 6ffc047f..00000000
--- a/kitty-themes/themes/Pro.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #000000
-foreground #f2f2f2
-cursor #4d4d4d
-selection_background #414141
-color0 #000000
-color8 #666666
-color1 #990000
-color9 #e50000
-color2 #00a600
-color10 #00d900
-color3 #999900
-color11 #e5e500
-color4 #1f08db
-color12 #0000ff
-color5 #b200b2
-color13 #e500e5
-color6 #00a6b2
-color14 #00e5e5
-color7 #bfbfbf
-color15 #e5e5e5
-selection_foreground #000000
diff --git a/kitty-themes/themes/Red_Alert.conf b/kitty-themes/themes/Red_Alert.conf
deleted file mode 100644
index 020429ac..00000000
--- a/kitty-themes/themes/Red_Alert.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #762423
-foreground #ffffff
-cursor #ffffff
-selection_background #073642
-color0 #000000
-color8 #262626
-color1 #d52e4d
-color9 #e02453
-color2 #71be6b
-color10 #aff08b
-color3 #beb86b
-color11 #dfddb7
-color4 #479bed
-color12 #65a9f0
-color5 #e878d6
-color13 #ddb7df
-color6 #6bbeb8
-color14 #b7dfdd
-color7 #d6d6d6
-color15 #ffffff
-selection_foreground #762423
diff --git a/kitty-themes/themes/Red_Sands.conf b/kitty-themes/themes/Red_Sands.conf
deleted file mode 100644
index a491f3d1..00000000
--- a/kitty-themes/themes/Red_Sands.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-# Theme ported from the Mac Terminal application.
-
-background #79241d
-foreground #d6c8a7
-cursor #ffffff
-selection_background #a4a390
-color0 #000000
-color8 #545454
-color1 #ff3e00
-color9 #ba0000
-color2 #00ba00
-color10 #00ba00
-color3 #e6af00
-color11 #e6af00
-color4 #0071ff
-color12 #0071ae
-color5 #ba00ba
-color13 #ff54ff
-color6 #00baba
-color14 #54ffff
-color7 #bababa
-color15 #ffffff
-selection_foreground #79241d
diff --git a/kitty-themes/themes/Relaxed_Afterglow.conf b/kitty-themes/themes/Relaxed_Afterglow.conf
deleted file mode 100644
index 59887778..00000000
--- a/kitty-themes/themes/Relaxed_Afterglow.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-# Relaxed Afterglow by Michael Kühnel , https://github.com/Relaxed-Theme/relaxed-terminal-themes
-# This work is licensed under the terms of the MIT license.
-# For a copy, see https://opensource.org/licenses/MIT.
-
-background #353a44
-foreground #d9d9d9
-cursor #d9d9d9
-selection_foreground #d8d8d8
-selection_background #6a7984
-color0 #151515
-color1 #bc5653
-color2 #909d63
-color3 #ebc17a
-color4 #6a8799
-color5 #b06698
-color6 #c9dfff
-color7 #d9d9d9
-color8 #636363
-color9 #bc5653
-color10 #a0ac77
-color11 #ebc17a
-color12 #7eaac7
-color13 #b06698
-color14 #acbbd0
-color15 #f7f7f7
diff --git a/kitty-themes/themes/Renault_Style.conf b/kitty-themes/themes/Renault_Style.conf
deleted file mode 100644
index 856818db..00000000
--- a/kitty-themes/themes/Renault_Style.conf
+++ /dev/null
@@ -1,22 +0,0 @@
-background #3a3a3a
-foreground #e9cb7b
-cursor #7f7f7f
-selection_background #958553
-
-color0 #000000
-color1 #990000
-color2 #00A600
-color3 #999900
-color4 #0000B2
-color5 #B200B2
-color6 #00A6B2
-color7 #BFBFBF
-color8 #666666
-color9 #E50000
-color10 #00d800
-color11 #E5E500
-color12 #0000FF
-color13 #E500E5
-color14 #00e5e5
-color15 #E5E5E5
-selection_foreground #3a3a3a
diff --git a/kitty-themes/themes/Renault_Style_Light.conf b/kitty-themes/themes/Renault_Style_Light.conf
deleted file mode 100644
index e08cd6a0..00000000
--- a/kitty-themes/themes/Renault_Style_Light.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #3a3a3a
-foreground #e9cb7b
-cursor #7f7f7f
-selection_background #958553
-color0 #000000
-color8 #323232
-color1 #da4839
-color9 #ff7b6a
-color2 #509f50
-color10 #83d082
-color3 #ffd249
-color11 #ffff7b
-color4 #46657d
-color12 #9fcef0
-color5 #cfcfff
-color13 #ffffff
-color6 #87c1f1
-color14 #a4d4f8
-color7 #ffffff
-color15 #ffffff
-selection_foreground #3a3a3a
diff --git a/kitty-themes/themes/Rippedcasts.conf b/kitty-themes/themes/Rippedcasts.conf
deleted file mode 100644
index 20e98729..00000000
--- a/kitty-themes/themes/Rippedcasts.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2b2b2b
-foreground #ffffff
-cursor #7f7f7f
-selection_background #5a637e
-color0 #000000
-color8 #666666
-color1 #ccae95
-color9 #edcbac
-color2 #a7ff60
-color10 #bced68
-color3 #beba1f
-color11 #e5e500
-color4 #74a4af
-color12 #86bdc8
-color5 #ff73fd
-color13 #e500e5
-color6 #5a637e
-color14 #8b9bc4
-color7 #bebebe
-color15 #e5e5e5
-selection_foreground #2b2b2b
diff --git a/kitty-themes/themes/Royal.conf b/kitty-themes/themes/Royal.conf
deleted file mode 100644
index 0ebd236e..00000000
--- a/kitty-themes/themes/Royal.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #100814
-foreground #504868
-cursor #514965
-selection_background #1e1d2a
-color0 #241f2a
-color8 #312d3c
-color1 #90274b
-color9 #d4346c
-color2 #23801c
-color10 #2cd845
-color3 #b49d27
-color11 #fde83a
-color4 #6480af
-color12 #8fb9f9
-color5 #664d96
-color13 #a479e2
-color6 #8aaabd
-color14 #abd3eb
-color7 #514965
-color15 #9d8bbd
-selection_foreground #100814
diff --git a/kitty-themes/themes/SeaShells.conf b/kitty-themes/themes/SeaShells.conf
deleted file mode 100644
index 60a2968b..00000000
--- a/kitty-themes/themes/SeaShells.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #08131a
-foreground #deb88d
-cursor #fba02f
-selection_background #1e4862
-color0 #17384c
-color8 #424b52
-color1 #d05023
-color9 #d38677
-color2 #027b9b
-color10 #618c98
-color3 #fba02f
-color11 #fdd29e
-color4 #1d4850
-color12 #1abcdd
-color5 #68d3f0
-color13 #bbe3ee
-color6 #50a3b5
-color14 #86abb3
-color7 #deb88d
-color15 #fee3cd
-selection_foreground #08131a
diff --git a/kitty-themes/themes/Seafoam_Pastel.conf b/kitty-themes/themes/Seafoam_Pastel.conf
deleted file mode 100644
index c3e95a76..00000000
--- a/kitty-themes/themes/Seafoam_Pastel.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #243335
-foreground #d3e6d3
-cursor #576379
-selection_background #ffffff
-color0 #747474
-color8 #8a8a8a
-color1 #825d4c
-color9 #cf9279
-color2 #718b62
-color10 #98d9aa
-color3 #aca06d
-color11 #fae69c
-color4 #4c7b82
-color12 #79c3cf
-color5 #8a7166
-color13 #d6b2a0
-color6 #719494
-color14 #acdfdf
-color7 #dfdfdf
-color15 #dfdfdf
-selection_foreground #243335
diff --git a/kitty-themes/themes/Seti.conf b/kitty-themes/themes/Seti.conf
deleted file mode 100644
index 69c4105d..00000000
--- a/kitty-themes/themes/Seti.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #111213
-foreground #cacecd
-cursor #e2be21
-selection_background #303233
-color0 #323232
-color8 #323232
-color1 #c22832
-color9 #c22832
-color2 #8ec43d
-color10 #8ec43d
-color3 #e0c64f
-color11 #e0c64f
-color4 #43a5d5
-color12 #43a5d5
-color5 #8b57b5
-color13 #8b57b5
-color6 #8ec43d
-color14 #8ec43d
-color7 #eeeeee
-color15 #ffffff
-selection_foreground #111213
diff --git a/kitty-themes/themes/Shaman.conf b/kitty-themes/themes/Shaman.conf
deleted file mode 100644
index a316711e..00000000
--- a/kitty-themes/themes/Shaman.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #001014
-foreground #405555
-cursor #49fcd5
-selection_background #415554
-color0 #012026
-color8 #374350
-color1 #b12f2c
-color9 #ff4242
-color2 #00a940
-color10 #2aea5e
-color3 #5d8aa9
-color11 #8dd3fd
-color4 #449985
-color12 #61d4b9
-color5 #00599c
-color13 #1298ff
-color6 #5c7e19
-color14 #98cf28
-color7 #405554
-color15 #58fad6
-selection_foreground #001014
diff --git a/kitty-themes/themes/Slate.conf b/kitty-themes/themes/Slate.conf
deleted file mode 100644
index 650e4abb..00000000
--- a/kitty-themes/themes/Slate.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212121
-foreground #34b0d2
-cursor #87d2c3
-selection_background #0f3754
-color0 #212121
-color8 #ffffff
-color1 #e1a7bf
-color9 #ffccd8
-color2 #80d778
-color10 #bdffa8
-color3 #c4c9bf
-color11 #d0cbc9
-color4 #254a49
-color12 #79afd2
-color5 #a380d3
-color13 #c4a7d8
-color6 #14ab9c
-color14 #8bdee0
-color7 #02c4e0
-color15 #e0e0e0
-selection_foreground #212121
diff --git a/kitty-themes/themes/Smyck.conf b/kitty-themes/themes/Smyck.conf
deleted file mode 100644
index 3c972512..00000000
--- a/kitty-themes/themes/Smyck.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1b1b
-foreground #f7f7f7
-cursor #bbbbbb
-selection_background #207383
-color0 #000000
-color8 #7a7a7a
-color1 #b74131
-color9 #d6837b
-color2 #7da900
-color10 #c4f036
-color3 #c4a400
-color11 #fee14d
-color4 #62a3c4
-color12 #8dcff0
-color5 #b98acc
-color13 #f799ff
-color6 #207383
-color14 #69d9cf
-color7 #a0a0a0
-color15 #f7f7f7
-selection_foreground #1b1b1b
diff --git a/kitty-themes/themes/SoftServer.conf b/kitty-themes/themes/SoftServer.conf
deleted file mode 100644
index e7eb5fe9..00000000
--- a/kitty-themes/themes/SoftServer.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #242626
-foreground #99a3a2
-cursor #d1dfde
-selection_background #7f8786
-color0 #000000
-color8 #666c6b
-color1 #a16869
-color9 #dc5b5f
-color2 #99a569
-color10 #bfde54
-color3 #a29069
-color11 #deb35f
-color4 #6a8fa3
-color12 #62b1df
-color5 #6971a3
-color13 #5f6ede
-color6 #6ba48f
-color14 #64e39c
-color7 #99a3a2
-color15 #d1dfde
-selection_foreground #242626
diff --git a/kitty-themes/themes/Solarized_Darcula.conf b/kitty-themes/themes/Solarized_Darcula.conf
deleted file mode 100644
index 0a845d4c..00000000
--- a/kitty-themes/themes/Solarized_Darcula.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #3d3f41
-foreground #d2d8d9
-cursor #708183
-selection_background #214283
-color0 #25292a
-color8 #25292a
-color1 #f24840
-color9 #f24840
-color2 #629655
-color10 #629655
-color3 #b68800
-color11 #b68800
-color4 #2074c7
-color12 #2074c7
-color5 #797fd4
-color13 #797fd4
-color6 #15968d
-color14 #15968d
-color7 #d2d8d9
-color15 #d2d8d9
-selection_foreground #3d3f41
diff --git a/kitty-themes/themes/Solarized_Dark.conf b/kitty-themes/themes/Solarized_Dark.conf
deleted file mode 100644
index 84194267..00000000
--- a/kitty-themes/themes/Solarized_Dark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #001e26
-foreground #708183
-cursor #708183
-selection_background #002731
-color0 #002731
-color8 #001e26
-color1 #d01b24
-color9 #bd3612
-color2 #728905
-color10 #465a61
-color3 #a57705
-color11 #52676f
-color4 #2075c7
-color12 #708183
-color5 #c61b6e
-color13 #5856b9
-color6 #259185
-color14 #81908f
-color7 #e9e2cb
-color15 #fcf4dc
-selection_foreground #001e26
diff --git a/kitty-themes/themes/Solarized_Dark_-_Patched.conf b/kitty-themes/themes/Solarized_Dark_-_Patched.conf
deleted file mode 100644
index 37c35979..00000000
--- a/kitty-themes/themes/Solarized_Dark_-_Patched.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #001e26
-foreground #708183
-cursor #708183
-selection_background #002731
-color0 #002731
-color8 #465a61
-color1 #d01b24
-color9 #bd3612
-color2 #728905
-color10 #465a61
-color3 #a57705
-color11 #52676f
-color4 #2075c7
-color12 #708183
-color5 #c61b6e
-color13 #5856b9
-color6 #259185
-color14 #81908f
-color7 #e9e2cb
-color15 #fcf4dc
-selection_foreground #001e26
diff --git a/kitty-themes/themes/Solarized_Dark_Higher_Contrast.conf b/kitty-themes/themes/Solarized_Dark_Higher_Contrast.conf
deleted file mode 100644
index 8477ea1f..00000000
--- a/kitty-themes/themes/Solarized_Dark_Higher_Contrast.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #001e26
-foreground #9bc1c2
-cursor #f34a00
-selection_background #003747
-color0 #002731
-color8 #006388
-color1 #d01b24
-color9 #f4153b
-color2 #6bbe6c
-color10 #50ee84
-color3 #a57705
-color11 #b17e28
-color4 #2075c7
-color12 #178dc7
-color5 #c61b6e
-color13 #e14d8e
-color6 #259185
-color14 #00b29e
-color7 #e9e2cb
-color15 #fcf4dc
-selection_foreground #001e26
diff --git a/kitty-themes/themes/Solarized_Light.conf b/kitty-themes/themes/Solarized_Light.conf
deleted file mode 100644
index 36e101b1..00000000
--- a/kitty-themes/themes/Solarized_Light.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #fdf6e3
-foreground #52676f
-cursor #52676f
-selection_background #e9e2cb
-color0 #e4e4e4
-color8 #ffffd7
-color1 #d70000
-color9 #d75f00
-color2 #5f8700
-color10 #585858
-color3 #af8700
-color11 #626262
-color4 #0087ff
-color12 #808080
-color5 #af005f
-color13 #5f5faf
-color6 #00afaf
-color14 #8a8a8a
-color7 #262626
-color15 #1c1c1c
-selection_foreground #fcf4dc
diff --git a/kitty-themes/themes/Source_Code_X.conf b/kitty-themes/themes/Source_Code_X.conf
deleted file mode 100644
index d3aefa36..00000000
--- a/kitty-themes/themes/Source_Code_X.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-foreground #000000
-background #1f1f24
-cursor #7F7F7F
-color0 #4e596b
-color8 #91a0b1
-color1 #fb695d
-color7 #BFBFBF
-color9 #fb695d
-color2 #74b391
-color10 #aef37c
-color3 #fc8e3e
-color11 #fc8e3e
-color4 #9586f4
-color12 #53a4fb
-color5 #fb5ea3
-color13 #fb5ea3
-color6 #79c8b6
-color14 #83d2c0
-color15 #91a0b1
-selection_foreground #1f1f24
-selection_background #000000
diff --git a/kitty-themes/themes/SpaceGray.conf b/kitty-themes/themes/SpaceGray.conf
deleted file mode 100644
index ba9afebf..00000000
--- a/kitty-themes/themes/SpaceGray.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #20232c
-foreground #b2b8c2
-cursor #b2b8c2
-selection_background #15171e
-color0 #000000
-color8 #000000
-color1 #af4b57
-color9 #af4b57
-color2 #87b279
-color10 #87b279
-color3 #e5c078
-color11 #e5c078
-color4 #7c8fa3
-color12 #7c8fa3
-color5 #a37996
-color13 #a37996
-color6 #84a6a4
-color14 #84a6a4
-color7 #b2b8c2
-color15 #fffefe
-selection_foreground #20232c
diff --git a/kitty-themes/themes/SpaceGray_Eighties.conf b/kitty-themes/themes/SpaceGray_Eighties.conf
deleted file mode 100644
index 8249a17c..00000000
--- a/kitty-themes/themes/SpaceGray_Eighties.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212121
-foreground #bdb9ae
-cursor #bbbbbb
-selection_background #262e35
-color0 #15171c
-color8 #545454
-color1 #ec5f67
-color9 #ff6973
-color2 #80a763
-color10 #93d393
-color3 #fdc253
-color11 #ffd156
-color4 #5485c0
-color12 #4d83d0
-color5 #bf83c0
-color13 #ff55ff
-color6 #57c2c0
-color14 #83e8e4
-color7 #eeece7
-color15 #ffffff
-selection_foreground #212121
diff --git a/kitty-themes/themes/SpaceGray_Eighties_Dull.conf b/kitty-themes/themes/SpaceGray_Eighties_Dull.conf
deleted file mode 100644
index eabf1845..00000000
--- a/kitty-themes/themes/SpaceGray_Eighties_Dull.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212121
-foreground #c8c5bc
-cursor #bbbbbb
-selection_background #262e36
-color0 #15171c
-color8 #545454
-color1 #b14956
-color9 #ec5f67
-color2 #91b377
-color10 #88e985
-color3 #c6725a
-color11 #fdc253
-color4 #7b8fa4
-color12 #5485c0
-color5 #a5779e
-color13 #bf83c0
-color6 #7fcccb
-color14 #58c2c0
-color7 #b2b8c2
-color15 #ffffff
-selection_foreground #212121
diff --git a/kitty-themes/themes/Spacedust.conf b/kitty-themes/themes/Spacedust.conf
deleted file mode 100644
index 08b93c4b..00000000
--- a/kitty-themes/themes/Spacedust.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0a1e24
-foreground #ecefc1
-cursor #708183
-selection_background #0a385c
-color0 #6e5246
-color8 #674c31
-color1 #e35a00
-color9 #ff8a39
-color2 #5cab96
-color10 #adcab8
-color3 #e3cd7b
-color11 #ffc777
-color4 #0e548b
-color12 #67a0cd
-color5 #e35a00
-color13 #ff8a39
-color6 #06afc7
-color14 #83a6b3
-color7 #f0f1ce
-color15 #fefff0
-selection_foreground #0a1e24
diff --git a/kitty-themes/themes/Spiderman.conf b/kitty-themes/themes/Spiderman.conf
deleted file mode 100644
index a72bbcad..00000000
--- a/kitty-themes/themes/Spiderman.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1d1e
-foreground #e2e2e2
-cursor #2b3fff
-selection_background #070e4f
-color0 #1b1d1e
-color8 #505354
-color1 #e60712
-color9 #ff0325
-color2 #e22828
-color10 #ff3238
-color3 #e24655
-color11 #fe3935
-color4 #2b3fff
-color12 #1d4fff
-color5 #2435db
-color13 #737bff
-color6 #3255ff
-color14 #6083ff
-color7 #fffef6
-color15 #fefff9
-selection_foreground #1b1d1e
diff --git a/kitty-themes/themes/Spring.conf b/kitty-themes/themes/Spring.conf
deleted file mode 100644
index 4df6d1c2..00000000
--- a/kitty-themes/themes/Spring.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #ffffff
-foreground #4d4d4c
-cursor #4d4d4c
-selection_background #d6d6d6
-color0 #000000
-color8 #000000
-color1 #ff4c83
-color9 #ff0021
-color2 #1f8c3a
-color10 #1fc231
-color3 #1fc95a
-color11 #d4b706
-color4 #1cd2ee
-color12 #15a9fd
-color5 #8959a8
-color13 #8959a8
-color6 #3e999f
-color14 #3e999f
-color7 #fffefe
-color15 #fffefe
-selection_foreground #ffffff
diff --git a/kitty-themes/themes/Square.conf b/kitty-themes/themes/Square.conf
deleted file mode 100644
index e5457a5e..00000000
--- a/kitty-themes/themes/Square.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1a1a1a
-foreground #acacab
-cursor #fbfacc
-selection_background #4d4d4d
-color0 #050505
-color8 #141414
-color1 #e9897c
-color9 #f99286
-color2 #b6377d
-color10 #c3f786
-color3 #ecebbe
-color11 #fcfbcc
-color4 #a9cdeb
-color12 #b6defb
-color5 #75507b
-color13 #ad7fa8
-color6 #c9caec
-color14 #d7d9fc
-color7 #f2f2f2
-color15 #e2e2e2
-selection_foreground #1a1a1a
diff --git a/kitty-themes/themes/Sundried.conf b/kitty-themes/themes/Sundried.conf
deleted file mode 100644
index d5ff99d8..00000000
--- a/kitty-themes/themes/Sundried.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1a1818
-foreground #c8c8c8
-cursor #fffefe
-selection_background #302b2a
-color0 #302b2a
-color8 #4d4d47
-color1 #a6463d
-color9 #aa000c
-color2 #577644
-color10 #128c20
-color3 #9c5f2a
-color11 #fc6a20
-color4 #485a98
-color12 #7898f7
-color5 #854551
-color13 #fc89a0
-color6 #9c814e
-color14 #fad384
-color7 #c8c8c8
-color15 #fffefe
-selection_foreground #1a1818
diff --git a/kitty-themes/themes/Symfonic.conf b/kitty-themes/themes/Symfonic.conf
deleted file mode 100644
index 9b5e0d33..00000000
--- a/kitty-themes/themes/Symfonic.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #ffffff
-cursor #dc322f
-selection_background #073642
-color0 #000000
-color8 #1b1d21
-color1 #dc322f
-color9 #dc322f
-color2 #56db3a
-color10 #56db3a
-color3 #ff8400
-color11 #ff8400
-color4 #0084d4
-color12 #0084d4
-color5 #b729d9
-color13 #b729d9
-color6 #ccccff
-color14 #ccccff
-color7 #ffffff
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/Tango_Dark.conf b/kitty-themes/themes/Tango_Dark.conf
deleted file mode 100644
index 6a4673ae..00000000
--- a/kitty-themes/themes/Tango_Dark.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #ffffff
-cursor #ffffff
-selection_background #b4d5ff
-color0 #000000
-color8 #545753
-color1 #cc0000
-color9 #ef2828
-color2 #4e9a05
-color10 #8ae234
-color3 #c4a000
-color11 #fce94e
-color4 #3464a4
-color12 #719ecf
-color5 #74507a
-color13 #ad7ea7
-color6 #05989a
-color14 #34e2e2
-color7 #d3d7cf
-color15 #ededec
-selection_foreground #000000
diff --git a/kitty-themes/themes/Tango_Light.conf b/kitty-themes/themes/Tango_Light.conf
deleted file mode 100644
index 07f82bf6..00000000
--- a/kitty-themes/themes/Tango_Light.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #ffffff
-foreground #000000
-cursor #000000
-selection_background #b4d5ff
-color0 #000000
-color8 #545753
-color1 #cc0000
-color9 #ef2828
-color2 #4e9a05
-color10 #8ae234
-color3 #c4a000
-color11 #fce94e
-color4 #3464a4
-color12 #719ecf
-color5 #74507a
-color13 #ad7ea7
-color6 #05989a
-color14 #34e2e2
-color7 #d3d7cf
-color15 #ededec
-selection_foreground #ffffff
diff --git a/kitty-themes/themes/Teerb.conf b/kitty-themes/themes/Teerb.conf
deleted file mode 100644
index 9e9cab76..00000000
--- a/kitty-themes/themes/Teerb.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #262626
-foreground #d0d0d0
-cursor #e3c8ae
-selection_background #4d4d4d
-color0 #1c1c1c
-color8 #1c1c1c
-color1 #d68686
-color9 #d68686
-color2 #aed686
-color10 #aed686
-color3 #d7af87
-color11 #e4c9af
-color4 #86aed6
-color12 #86aed6
-color5 #d6aed6
-color13 #d6aed6
-color6 #8adbb4
-color14 #b1e7dd
-color7 #d0d0d0
-color15 #efefef
-selection_foreground #262626
diff --git a/kitty-themes/themes/Thayer_Bright.conf b/kitty-themes/themes/Thayer_Bright.conf
deleted file mode 100644
index b74bc608..00000000
--- a/kitty-themes/themes/Thayer_Bright.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1d1e
-foreground #f8f8f8
-cursor #fc971e
-selection_background #4d4d4d
-color0 #1b1d1e
-color8 #505354
-color1 #f92672
-color9 #ff5995
-color2 #4df740
-color10 #b6e354
-color3 #f3fd21
-color11 #feed6c
-color4 #2656d6
-color12 #3f78ff
-color5 #8c54fe
-color13 #9e6ffe
-color6 #37c8b4
-color14 #23ced4
-color7 #ccccc6
-color15 #f8f8f2
-selection_foreground #1b1d1e
diff --git a/kitty-themes/themes/The_Hulk.conf b/kitty-themes/themes/The_Hulk.conf
deleted file mode 100644
index 81bd962d..00000000
--- a/kitty-themes/themes/The_Hulk.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1d1e
-foreground #b4b4b4
-cursor #15b61a
-selection_background #4d4f4c
-color0 #1b1d1e
-color8 #505354
-color1 #259d1a
-color9 #8dff2a
-color2 #13ce2f
-color10 #48ff76
-color3 #62e456
-color11 #3afe15
-color4 #2424f4
-color12 #4f6a95
-color5 #641e73
-color13 #72579d
-color6 #378ca9
-color14 #3f85a5
-color7 #d8d8d0
-color15 #e5e5e0
-selection_foreground #1b1d1e
diff --git a/kitty-themes/themes/Tomorrow.conf b/kitty-themes/themes/Tomorrow.conf
deleted file mode 100644
index 23a8dfc5..00000000
--- a/kitty-themes/themes/Tomorrow.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #ffffff
-foreground #4c4c4c
-cursor #4c4c4c
-selection_background #d6d6d6
-color0 #000000
-color8 #000000
-color1 #c82828
-color9 #c82828
-color2 #708b00
-color10 #708b00
-color3 #e9b600
-color11 #e9b600
-color4 #4170ae
-color12 #4170ae
-color5 #8958a7
-color13 #8958a7
-color6 #3d999f
-color14 #3d999f
-color7 #fffefe
-color15 #fffefe
-selection_foreground #ffffff
diff --git a/kitty-themes/themes/Tomorrow_Night.conf b/kitty-themes/themes/Tomorrow_Night.conf
deleted file mode 100644
index 0bf3cf7f..00000000
--- a/kitty-themes/themes/Tomorrow_Night.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1d1f21
-foreground #c4c8c5
-cursor #c4c8c5
-selection_background #363a41
-color0 #000000
-color8 #000000
-color1 #cc6666
-color9 #cc6666
-color2 #b5bd68
-color10 #b5bd68
-color3 #f0c574
-color11 #f0c574
-color4 #80a1bd
-color12 #80a1bd
-color5 #b294ba
-color13 #b294ba
-color6 #8abdb6
-color14 #8abdb6
-color7 #fffefe
-color15 #fffefe
-selection_foreground #1d1f21
diff --git a/kitty-themes/themes/Tomorrow_Night_Blue.conf b/kitty-themes/themes/Tomorrow_Night_Blue.conf
deleted file mode 100644
index b32cbfde..00000000
--- a/kitty-themes/themes/Tomorrow_Night_Blue.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #002450
-foreground #fffefe
-cursor #fffefe
-selection_background #003e8e
-color0 #000000
-color8 #000000
-color1 #ff9ca3
-color9 #ff9ca3
-color2 #d0f0a8
-color10 #d0f0a8
-color3 #ffedac
-color11 #ffedac
-color4 #badaff
-color12 #badaff
-color5 #ebbaff
-color13 #ebbaff
-color6 #99ffff
-color14 #99ffff
-color7 #fffefe
-color15 #fffefe
-selection_foreground #002450
diff --git a/kitty-themes/themes/Tomorrow_Night_Bright.conf b/kitty-themes/themes/Tomorrow_Night_Bright.conf
deleted file mode 100644
index 346f3dca..00000000
--- a/kitty-themes/themes/Tomorrow_Night_Bright.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #e9e9e9
-cursor #e9e9e9
-selection_background #424242
-color0 #000000
-color8 #000000
-color1 #d44d53
-color9 #d44d53
-color2 #b9c949
-color10 #b9c949
-color3 #e6c446
-color11 #e6c446
-color4 #79a6da
-color12 #79a6da
-color5 #c396d7
-color13 #c396d7
-color6 #70c0b1
-color14 #70c0b1
-color7 #fffefe
-color15 #fffefe
-selection_foreground #000000
diff --git a/kitty-themes/themes/Tomorrow_Night_Eighties.conf b/kitty-themes/themes/Tomorrow_Night_Eighties.conf
deleted file mode 100644
index 404efc7c..00000000
--- a/kitty-themes/themes/Tomorrow_Night_Eighties.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #2c2c2c
-foreground #cccccc
-cursor #cccccc
-selection_background #505050
-color0 #000000
-color8 #000000
-color1 #f17779
-color9 #f17779
-color2 #99cc99
-color10 #99cc99
-color3 #ffcc66
-color11 #ffcc66
-color4 #6699cc
-color12 #6699cc
-color5 #cc99cc
-color13 #cc99cc
-color6 #66cccc
-color14 #66cccc
-color7 #fffefe
-color15 #fffefe
-selection_foreground #2c2c2c
diff --git a/kitty-themes/themes/ToyChest.conf b/kitty-themes/themes/ToyChest.conf
deleted file mode 100644
index 040cb8d0..00000000
--- a/kitty-themes/themes/ToyChest.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #23364a
-foreground #30cf7b
-cursor #d4d4d4
-selection_background #5f207a
-color0 #2c3f57
-color8 #326889
-color1 #be2d26
-color9 #dd5943
-color2 #199171
-color10 #30cf7b
-color3 #da8e26
-color11 #e7d74b
-color4 #325d96
-color12 #33a5d9
-color5 #8a5ddb
-color13 #ad6bdc
-color6 #35a08f
-color14 #41c3ad
-color7 #23d082
-color15 #d4d4d4
-selection_foreground #23364a
diff --git a/kitty-themes/themes/Treehouse.conf b/kitty-themes/themes/Treehouse.conf
deleted file mode 100644
index 940751e5..00000000
--- a/kitty-themes/themes/Treehouse.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #191919
-foreground #776b53
-cursor #fac814
-selection_background #776b53
-color0 #321200
-color8 #423625
-color1 #b1270e
-color9 #ed5c20
-color2 #44a900
-color10 #55f237
-color3 #a9810b
-color11 #f1b731
-color4 #578499
-color12 #85cfec
-color5 #96363c
-color13 #e04b5a
-color6 #b2591d
-color14 #f07c14
-color7 #776b53
-color15 #ffc800
-selection_foreground #191919
diff --git a/kitty-themes/themes/Twilight.conf b/kitty-themes/themes/Twilight.conf
deleted file mode 100644
index 452a4d26..00000000
--- a/kitty-themes/themes/Twilight.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #141414
-foreground #feffd3
-cursor #ffffff
-selection_background #303030
-color0 #141414
-color8 #262626
-color1 #c06c43
-color9 #dd7c4c
-color2 #afb979
-color10 #cbd88c
-color3 #c2a86c
-color11 #e1c47d
-color4 #444649
-color12 #5a5d61
-color5 #b4be7b
-color13 #d0db8e
-color6 #778284
-color14 #8a989a
-color7 #feffd3
-color15 #feffd3
-selection_foreground #141414
diff --git a/kitty-themes/themes/Ubuntu.conf b/kitty-themes/themes/Ubuntu.conf
deleted file mode 100644
index 96e9d09d..00000000
--- a/kitty-themes/themes/Ubuntu.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #300a24
-foreground #eeeeec
-cursor #bbbbbb
-selection_background #b4d5ff
-color0 #2e3436
-color8 #555753
-color1 #cc0000
-color9 #ef2929
-color2 #4e9a06
-color10 #8ae234
-color3 #c4a000
-color11 #fce94f
-color4 #3465a4
-color12 #729fcf
-color5 #75507b
-color13 #ad7fa8
-color6 #06989a
-color14 #34e2e2
-color7 #d3d7cf
-color15 #eeeeec
-selection_foreground #300a24
diff --git a/kitty-themes/themes/Urple.conf b/kitty-themes/themes/Urple.conf
deleted file mode 100644
index a29dcad7..00000000
--- a/kitty-themes/themes/Urple.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1b1b23
-foreground #86799a
-cursor #a062ea
-selection_background #a062ea
-color0 #000000
-color8 #5c3125
-color1 #af425b
-color9 #ff6387
-color2 #37a315
-color10 #28e51f
-color3 #ac5b41
-color11 #f08061
-color4 #554d9a
-color12 #8579ed
-color5 #6c3ba1
-color13 #a05dee
-color6 #808080
-color14 #eaeaea
-color7 #87799c
-color15 #bfa3ff
-selection_foreground #1b1b23
diff --git a/kitty-themes/themes/Vaughn.conf b/kitty-themes/themes/Vaughn.conf
deleted file mode 100644
index 26d18858..00000000
--- a/kitty-themes/themes/Vaughn.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #25234e
-foreground #dcdccc
-cursor #ff5555
-selection_background #b5d5ff
-color0 #24234f
-color8 #709080
-color1 #705050
-color9 #dca3a3
-color2 #60b48a
-color10 #60b48a
-color3 #dfaf8f
-color11 #f0dfaf
-color4 #5454ff
-color12 #5454ff
-color5 #f08cc3
-color13 #ec93d3
-color6 #8cd0d3
-color14 #93e0e3
-color7 #709080
-color15 #ffffff
-selection_foreground #25234e
diff --git a/kitty-themes/themes/VibrantInk.conf b/kitty-themes/themes/VibrantInk.conf
deleted file mode 100644
index 5fbd281a..00000000
--- a/kitty-themes/themes/VibrantInk.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #ffffff
-cursor #ffffff
-selection_background #b4d5ff
-color0 #868686
-color8 #545454
-color1 #ff6600
-color9 #ff0000
-color2 #ccff04
-color10 #00ff00
-color3 #ffcc00
-color11 #ffff00
-color4 #44b3cc
-color12 #0000ff
-color5 #9933cc
-color13 #ff00ff
-color6 #44b3cc
-color14 #00ffff
-color7 #f4f4f4
-color15 #e5e5e5
-selection_foreground #000000
diff --git a/kitty-themes/themes/WarmNeon.conf b/kitty-themes/themes/WarmNeon.conf
deleted file mode 100644
index 3256f5d8..00000000
--- a/kitty-themes/themes/WarmNeon.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #3f3f3f
-foreground #afdab6
-cursor #2fff24
-selection_background #b0ad21
-color0 #000000
-color8 #fdfcfc
-color1 #e24345
-color9 #e86f71
-color2 #38b139
-color10 #9bc08f
-color3 #dae145
-color11 #ddd979
-color4 #4260c5
-color12 #7a90d5
-color5 #f81ffb
-color13 #f674b9
-color6 #29bad3
-color14 #5ed1e4
-color7 #d0b8a3
-color15 #d8c8bb
-selection_foreground #3f3f3f
diff --git a/kitty-themes/themes/Wez.conf b/kitty-themes/themes/Wez.conf
deleted file mode 100644
index 11706abd..00000000
--- a/kitty-themes/themes/Wez.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #000000
-foreground #b3b3b3
-cursor #52ad70
-selection_background #4c52f8
-color0 #000000
-color8 #555555
-color1 #cc5555
-color9 #ff5555
-color2 #55cc55
-color10 #55ff55
-color3 #cdcd55
-color11 #ffff55
-color4 #5455cb
-color12 #5555ff
-color5 #cc55cc
-color13 #ff55ff
-color6 #7acaca
-color14 #55ffff
-color7 #cccccc
-color15 #ffffff
-selection_foreground #000000
diff --git a/kitty-themes/themes/WildCherry.conf b/kitty-themes/themes/WildCherry.conf
deleted file mode 100644
index a4bfa476..00000000
--- a/kitty-themes/themes/WildCherry.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #1f1626
-foreground #d9faff
-cursor #dd00ff
-selection_background #002731
-color0 #000506
-color8 #009cc9
-color1 #d94085
-color9 #da6bab
-color2 #2ab250
-color10 #f4dba5
-color3 #ffd06e
-color11 #eac066
-color4 #873bdb
-color12 #2f8bb9
-color5 #ececec
-color13 #ae636b
-color6 #c1b8b6
-color14 #ff919d
-color7 #fff8dd
-color15 #e4838d
-selection_foreground #1f1626
diff --git a/kitty-themes/themes/Wombat.conf b/kitty-themes/themes/Wombat.conf
deleted file mode 100644
index d4616fc1..00000000
--- a/kitty-themes/themes/Wombat.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #171717
-foreground #ded9ce
-cursor #bbbbbb
-selection_background #453a39
-color0 #000000
-color8 #313131
-color1 #ff605a
-color9 #f58b7f
-color2 #b1e869
-color10 #dcf88f
-color3 #ead89c
-color11 #eee5b2
-color4 #5da9f6
-color12 #a5c7ff
-color5 #e86aff
-color13 #ddaaff
-color6 #82fff6
-color14 #b6fff9
-color7 #ded9ce
-color15 #fefffe
-selection_foreground #171717
diff --git a/kitty-themes/themes/Wryan.conf b/kitty-themes/themes/Wryan.conf
deleted file mode 100644
index 73d758d3..00000000
--- a/kitty-themes/themes/Wryan.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #101010
-foreground #999993
-cursor #9d9eca
-selection_background #4d4d4d
-color0 #333333
-color8 #3d3d3d
-color1 #8c4665
-color9 #bf4d80
-color2 #287373
-color10 #53a6a6
-color3 #7c7c99
-color11 #9e9ecb
-color4 #395573
-color12 #477ab3
-color5 #5e468c
-color13 #7e62b3
-color6 #31658c
-color14 #6096bf
-color7 #899ca1
-color15 #c0c0c0
-selection_foreground #101010
diff --git a/kitty-themes/themes/Zenburn.conf b/kitty-themes/themes/Zenburn.conf
deleted file mode 100644
index 032b99b4..00000000
--- a/kitty-themes/themes/Zenburn.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-# Zenburn by jnurmine, https://github.com/jnurmine/Zenburn
-# This work is licensed under the terms of the GNU GPL license.
-# For a copy, see http://www.gnu.org/licenses/gpl.html.
-
-background #3f3f3f
-foreground #dcdccc
-cursor #73635a
-selection_background #21322f
-color0 #4d4d4d
-color8 #709080
-color1 #705050
-color9 #dca3a3
-color2 #60b48a
-color10 #c3bf9f
-color3 #f0deae
-color11 #dfcf9f
-color4 #506070
-color12 #94bff3
-color5 #dc8cc3
-color13 #ec93d3
-color6 #8cd0d3
-color14 #93e0e3
-color7 #dcdccc
-color15 #ffffff
-selection_foreground #3f3f3f
diff --git a/kitty-themes/themes/ayu.conf b/kitty-themes/themes/ayu.conf
deleted file mode 100644
index 80302632..00000000
--- a/kitty-themes/themes/ayu.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #0e1419
-foreground #e5e1cf
-cursor #f19618
-selection_background #243340
-color0 #000000
-color8 #323232
-color1 #ff3333
-color9 #ff6565
-color2 #b8cc52
-color10 #e9fe83
-color3 #e6c446
-color11 #fff778
-color4 #36a3d9
-color12 #68d4ff
-color5 #f07078
-color13 #ffa3aa
-color6 #95e5cb
-color14 #c7fffc
-color7 #ffffff
-color15 #ffffff
-selection_foreground #0e1419
diff --git a/kitty-themes/themes/ayu_light.conf b/kitty-themes/themes/ayu_light.conf
deleted file mode 100644
index 35c532ce..00000000
--- a/kitty-themes/themes/ayu_light.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #fafafa
-foreground #5b6673
-cursor #ff6900
-selection_background #f0ede4
-color0 #000000
-color8 #323232
-color1 #ff3333
-color9 #ff6565
-color2 #86b200
-color10 #b8e532
-color3 #f19618
-color11 #ffc849
-color4 #41a6d9
-color12 #73d7ff
-color5 #f07078
-color13 #ffa3aa
-color6 #4cbe99
-color14 #7ff0cb
-color7 #ffffff
-color15 #ffffff
-selection_foreground #fafafa
diff --git a/kitty-themes/themes/ayu_mirage.conf b/kitty-themes/themes/ayu_mirage.conf
deleted file mode 100644
index 19d2e405..00000000
--- a/kitty-themes/themes/ayu_mirage.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #212733
-foreground #d9d7ce
-cursor #ffcc66
-selection_background #343f4c
-color0 #191e2a
-color8 #686868
-color1 #ed8274
-color9 #f28779
-color2 #a6cc70
-color10 #bae67e
-color3 #fad07b
-color11 #ffd580
-color4 #6dcbfa
-color12 #73d0ff
-color5 #cfbafa
-color13 #d4bfff
-color6 #90e1c6
-color14 #95e6cb
-color7 #c7c7c7
-color15 #ffffff
-selection_foreground #212733
diff --git a/kitty-themes/themes/grep b/kitty-themes/themes/grep
deleted file mode 100644
index e69de29b..00000000
diff --git a/kitty-themes/themes/gruvbox_dark.conf b/kitty-themes/themes/gruvbox_dark.conf
deleted file mode 100644
index 369da8d4..00000000
--- a/kitty-themes/themes/gruvbox_dark.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-# gruvbox dark by morhetz, https://github.com/morhetz/gruvbox
-# This work is licensed under the terms of the MIT license.
-# For a copy, see https://opensource.org/licenses/MIT.
-
-background #282828
-foreground #ebdbb2
-
-cursor #928374
-
-selection_foreground #928374
-selection_background #3c3836
-
-color0 #282828
-color8 #928374
-
-# red
-color1 #cc241d
-# light red
-color9 #fb4934
-
-# green
-color2 #98971a
-# light green
-color10 #b8bb26
-
-# yellow
-color3 #d79921
-# light yellow
-color11 #fabd2d
-
-# blue
-color4 #458588
-# light blue
-color12 #83a598
-
-# magenta
-color5 #b16286
-# light magenta
-color13 #d3869b
-
-# cyan
-color6 #689d6a
-# lighy cyan
-color14 #8ec07c
-
-# light gray
-color7 #a89984
-# dark gray
-color15 #928374
diff --git a/kitty-themes/themes/gruvbox_light.conf b/kitty-themes/themes/gruvbox_light.conf
deleted file mode 100644
index 6d8b89b2..00000000
--- a/kitty-themes/themes/gruvbox_light.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-# gruvbox light by morhetz, https://github.com/morhetz/gruvbox
-# This work is licensed under the terms of the MIT license.
-# For a copy, see https://opensource.org/licenses/MIT.
-
-background #fbf1c7
-foreground #3c3836
-
-cursor #928374
-
-selection_foreground #3c3836
-selection_background #928374
-
-color0 #fbf1c7
-color8 #282828
-
-# red
-color1 #cc241d
-# light red
-color9 #9d0006
-
-# green
-color2 #98971a
-# light green
-color10 #79740e
-
-# yellow
-color3 #d79921
-# light yellow
-color11 #b57614
-
-# blue
-color4 #458588
-# light blue
-color12 #076678
-
-# magenta
-color5 #b16286
-# light magenta
-color13 #8f3f71
-
-# cyan
-color6 #689d6a
-# lighy cyan
-color14 #427b58
-
-# light gray
-color7 #7c6f64
-# dark gray
-color15 #928374
diff --git a/kitty-themes/themes/idleToes.conf b/kitty-themes/themes/idleToes.conf
deleted file mode 100644
index 941d4b20..00000000
--- a/kitty-themes/themes/idleToes.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-background #323232
-foreground #ffffff
-cursor #d6d6d6
-selection_background #5b5b5b
-color0 #323232
-color8 #535353
-color1 #d25252
-color9 #f07070
-color2 #7fe173
-color10 #9dff90
-color3 #ffc66d
-color11 #ffe48b
-color4 #4098ff
-color12 #5eb7f7
-color5 #f57fff
-color13 #ff9dff
-color6 #bed6ff
-color14 #dcf4ff
-color7 #eeeeec
-color15 #ffffff
-selection_foreground #323232
diff --git a/kitty-themes/themes/snazzy.conf b/kitty-themes/themes/snazzy.conf
deleted file mode 100644
index 2ed5ff57..00000000
--- a/kitty-themes/themes/snazzy.conf
+++ /dev/null
@@ -1,46 +0,0 @@
-# snazzy by Connor Holyday (holyday.me)
-# This work is licensed under the terms of the MIT license.
-# For a copy, see https://opensource.org/licenses/MIT.
-
-# Snazzy Colorscheme for Kitty
-# Based on https://github.com/sindresorhus/hyper-snazzy
-
-foreground #eff0eb
-background #282a36
-selection_foreground #000000
-selection_background #FFFACD
-url_color #0087BD
-cursor #97979B
-cursor_text_color #282A36
-
-# black
-color0 #282a36
-color8 #686868
-
-# red
-color1 #FF5C57
-color9 #FF5C57
-
-# green
-color2 #5AF78E
-color10 #5AF78E
-
-# yellow
-color3 #F3F99D
-color11 #F3F99D
-
-# blue
-color4 #57C7FF
-color12 #57C7FF
-
-# magenta
-color5 #FF6AC1
-color13 #FF6AC1
-
-# cyan
-color6 #9AEDFE
-color14 #9AEDFE
-
-# white
-color7 #F1F1F0
-color15 #EFF0EB
diff --git a/kitty/kitty-themes b/kitty/kitty-themes
new file mode 160000
index 00000000..b1abdd54
--- /dev/null
+++ b/kitty/kitty-themes
@@ -0,0 +1 @@
+Subproject commit b1abdd54ba655ef34f75a568d78625981bf1722c
diff --git a/kitty.conf b/kitty/kitty.conf
similarity index 86%
rename from kitty.conf
rename to kitty/kitty.conf
index eef1a945..99144f51 100644
--- a/kitty.conf
+++ b/kitty/kitty.conf
@@ -5,3 +5,5 @@ confirm_os_window_close 0
include ./kitty-themes/themes/Neutron.conf
disable_ligatures always
+
+window_padding_width 4
diff --git a/Scripts/MkvToMp4.bash b/scripts/MkvToMp4.bash
similarity index 100%
rename from Scripts/MkvToMp4.bash
rename to scripts/MkvToMp4.bash
diff --git a/Scripts/ReplaceNvChad.bash b/scripts/ReplaceNvChad.bash
similarity index 100%
rename from Scripts/ReplaceNvChad.bash
rename to scripts/ReplaceNvChad.bash
diff --git a/Scripts/Rsync.bash b/scripts/Rsync.bash
similarity index 100%
rename from Scripts/Rsync.bash
rename to scripts/Rsync.bash
diff --git a/Scripts/SyncNvChad.bash b/scripts/SyncNvChad.bash
similarity index 100%
rename from Scripts/SyncNvChad.bash
rename to scripts/SyncNvChad.bash
diff --git a/scripts/manager.bash b/scripts/manager.bash
new file mode 100644
index 00000000..819f1b52
--- /dev/null
+++ b/scripts/manager.bash
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+folders=("hypr" "ags" "tmux" "lazygit" "kitty" "fish")
+
+cd ..
+
+for folder in ${folders[@]}; do
+ echo Moving $folder
+ rsync -a ./$folder/ ~/.config/$folder
+done
+
+# Starship
+mv starship.toml ~/.config/starship.toml
diff --git a/starship.toml b/starship.toml
deleted file mode 100644
index 0efdd068..00000000
--- a/starship.toml
+++ /dev/null
@@ -1,61 +0,0 @@
-format = """
-[ ](bg:#a5aed2)\
-[ ⚛ ](bg:#a3aed2 fg:#090c0c)\
-[](bg:#2969e8 fg:#a3aed2)\
-$directory\
-[](fg:#2969e8 bg:#394260)\
-$git_branch\
-$git_status\
-[](fg:#394260 bg:#212736)\
-$nodejs\
-$rust\
-$golang\
-$php\
-\n$character"""
-
-[directory]
-style = "fg:#e3e5e5 bg:#2969e8"
-format = "[ $path ]($style)"
-truncation_length = 3
-truncation_symbol = "…/"
-
-[directory.substitutions]
-"Documents" = " "
-"Downloads" = " "
-"Music" = " "
-"Pictures" = " "
-
-[git_branch]
-symbol = ""
-style = "bg:#394260"
-format = '[[ $symbol $branch ](fg:#769ff0 bg:#394260)]($style)'
-
-[git_status]
-style = "bg:#394260"
-format = '[[($all_status$ahead_behind )](fg:#769ff0 bg:#394260)]($style)'
-
-[nodejs]
-symbol = ""
-style = "bg:#212736"
-format = '[[ $symbol ($version) ](fg:#769ff0 bg:#212736)]($style)'
-
-[rust]
-symbol = ""
-style = "bg:#212736"
-format = '[[ $symbol ($version) ](fg:#769ff0 bg:#212736)]($style)'
-
-[golang]
-symbol = "ﳑ"
-style = "bg:#212736"
-format = '[[ $symbol ($version) ](fg:#769ff0 bg:#212736)]($style)'
-
-[php]
-symbol = ""
-style = "bg:#212736"
-format = '[[ $symbol ($version) ](fg:#769ff0 bg:#212736)]($style)'
-
-[time]
-disabled = false
-time_format = "%R" # Hour:Minute Format
-style = "bg:#1d2230"
-format = '[[ $time ](fg:#a0a9cb bg:#1d2230)]($style)'
diff --git a/starship_garuda.toml b/starship_garuda.toml
deleted file mode 100644
index 41e2aa94..00000000
--- a/starship_garuda.toml
+++ /dev/null
@@ -1,142 +0,0 @@
-## FIRST LINE/ROW: Info & Status
-# First param ─┌
-[username]
-format = " [╭─$user]($style)@"
-style_user = "bold red"
-style_root = "bold red"
-show_always = true
-
-# Second param
-[hostname]
-format = "[$hostname]($style) in "
-style = "bold dimmed red"
-trim_at = "-"
-ssh_only = false
-disabled = false
-
-# Third param
-[directory]
-style = "purple"
-truncation_length = 0
-truncate_to_repo = true
-truncation_symbol = "repo: "
-
-# Before all the version info (python, nodejs, php, etc.)
-[git_status]
-style = "white"
-ahead = "⇡${count}"
-diverged = "⇕⇡${ahead_count}⇣${behind_count}"
-behind = "⇣${count}"
-deleted = "x"
-
-# Last param in the first line/row
-[cmd_duration]
-min_time = 1
-format = "took [$duration]($style)"
-disabled = false
-
-
-## SECOND LINE/ROW: Prompt
-# Somethere at the beginning
-[battery]
-full_symbol = " "
-charging_symbol = " "
-discharging_symbol = " "
-disabled = true
-
-[[battery.display]] # "bold red" style when capacity is between 0% and 10%
-threshold = 15
-style = "bold red"
-disabled = true
-
-[[battery.display]] # "bold yellow" style when capacity is between 10% and 30%
-threshold = 50
-style = "bold yellow"
-disabled = true
-
-[[battery.display]] # "bold green" style when capacity is between 10% and 30%
-threshold = 80
-style = "bold green"
-disabled = true
-
-# Prompt: optional param 1
-[time]
-format = " 🕙 $time($style)\n"
-time_format = "%T"
-style = "bright-white"
-disabled = true
-
-# Prompt: param 2 └─
-[character]
-success_symbol = " [╰─λ](bold red)"
-error_symbol = " [×](bold red)"
-
-# SYMBOLS
-[status]
-symbol = "🔴"
-format = '[\[$symbol$status_common_meaning$status_signal_name$status_maybe_int\]]($style)'
-map_symbol = true
-disabled = false
-
-[aws]
-symbol = " "
-
-[conda]
-symbol = " "
-
-[dart]
-symbol = " "
-
-[docker_context]
-symbol = " "
-
-[elixir]
-symbol = " "
-
-[elm]
-symbol = " "
-
-[git_branch]
-symbol = " "
-
-[golang]
-symbol = " "
-
-[hg_branch]
-symbol = " "
-
-[java]
-symbol = " "
-
-[julia]
-symbol = " "
-
-[nim]
-symbol = " "
-
-[nix_shell]
-symbol = " "
-
-[nodejs]
-symbol = " "
-
-[package]
-symbol = " "
-
-[perl]
-symbol = " "
-
-[php]
-symbol = " "
-
-[python]
-symbol = " "
-
-[ruby]
-symbol = " "
-
-[rust]
-symbol = " "
-
-[swift]
-symbol = "ﯣ "
diff --git a/.tmux.conf b/tmux/.tmux.conf
similarity index 100%
rename from .tmux.conf
rename to tmux/.tmux.conf