Compare commits
34 Commits
03-05-2025
...
10-08-2025
Author | SHA1 | Date | |
---|---|---|---|
f68b3291b3 | |||
f5768d2118 | |||
65271a50d7 | |||
1b4d4ad815 | |||
052ed0a828 | |||
8fcad26789 | |||
fa0fb7ae31 | |||
c509fdbd18 | |||
fd957d9742 | |||
4e1c3a8e9c | |||
7b8ec10e6c | |||
47aa3ac749 | |||
ddae246d46 | |||
c8d439d428 | |||
2463d63c12 | |||
70d973e9d7 | |||
54b62fd5d5 | |||
794dda346a | |||
298a3da73b | |||
f682858051 | |||
68580d0ded | |||
b764547d16 | |||
7f4fabe856 | |||
79f6fc0265 | |||
5ac37ad9ad | |||
4cdcfadbac | |||
55f2119bc3 | |||
d6c20b81f9 | |||
0c4d000d96 | |||
3da54642a0 | |||
fb0158d1cd | |||
6604650adf | |||
a903666a26 | |||
d0adaa76e4 |
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
debug.log
|
debug.log
|
||||||
.venv
|
.venv
|
||||||
|
.venv.bak
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
__pycache__
|
__pycache__
|
||||||
|
80
Changelog
@@ -7,23 +7,95 @@ My standard System: Linux Mint 22 Cinnamon
|
|||||||
- If Wire-Py already runs, prevent further start
|
- If Wire-Py already runs, prevent further start
|
||||||
- for loops with lists replaced by List Comprehensions
|
- for loops with lists replaced by List Comprehensions
|
||||||
|
|
||||||
|
### Added
|
||||||
|
10.08.2025
|
||||||
|
|
||||||
|
- Wirepy divided into several scripts for better overview and maintenance
|
||||||
|
|
||||||
|
- Own file dialogue is now used to open Wireguard files for the import.
|
||||||
|
|
||||||
|
- New update notification. A canvas ring flashes when updates are available
|
||||||
|
|
||||||
|
- UI size change now grow all widgets with.
|
||||||
|
|
||||||
|
- Update Search at start of the app now starts in a thread to not stop the GUI
|
||||||
|
|
||||||
|
- Logviewer replaces with new button in the menu bar,
|
||||||
|
a new window opens in which the necessary infos stand when errors occur.
|
||||||
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
13-04-0725
|
01.08.2025
|
||||||
|
|
||||||
|
- Replace Imagemanager with IconManager and remove ImageManager from wp_app_config.py
|
||||||
|
|
||||||
|
- Logging improved
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Added
|
||||||
|
02-07-2025
|
||||||
|
|
||||||
|
- Complete code for faulty f" string configuration dur addicted and fixed
|
||||||
|
|
||||||
|
- updater replace Downloadbutton with Lx Tools installer
|
||||||
|
|
||||||
|
- remove logviewer icon
|
||||||
|
|
||||||
|
- add settings.png icon for update button
|
||||||
|
|
||||||
|
|
||||||
|
### Added
|
||||||
|
27-06-2025
|
||||||
|
|
||||||
|
- Header added for more modern desing
|
||||||
|
|
||||||
|
- Sizes adjust the frames and labels improve
|
||||||
|
|
||||||
|
- More modern desing for listbox, Address, Dns and Endpoint
|
||||||
|
|
||||||
|
- ui works now better with rename button
|
||||||
|
|
||||||
|
- add Image class for manage Images
|
||||||
|
|
||||||
|
### Added
|
||||||
|
23-06-2025
|
||||||
|
|
||||||
|
- all msg_window with MassageDialog replaced
|
||||||
|
|
||||||
|
### Added
|
||||||
|
14-06-2025
|
||||||
|
|
||||||
|
- replace msg_window with MassageDialog
|
||||||
|
- vpn stop icon corrected
|
||||||
|
|
||||||
|
### Added
|
||||||
|
07-06-2025
|
||||||
|
|
||||||
|
- ssl_decrypt now directly checks whether encrypted files are located in the specified directory.
|
||||||
|
- This method has been removed from common_tools. The path has been adjusted ssl_decrypt likely
|
||||||
|
had an incorrect path. No files were decrypted. This has been fixed.
|
||||||
|
- Key generation has been removed from ssl_decrypt, as it is only needed in ssl_encrypt.
|
||||||
|
- Logviewer now in the settings menu. Moduls now as libs in share_libs.
|
||||||
|
- The Lx Tools installer is there to ensure that everything required for the selected app is installed.
|
||||||
|
|
||||||
|
|
||||||
|
### Added
|
||||||
|
13-04-20255
|
||||||
|
|
||||||
- Installer update for Open Suse Tumbleweed and Leap
|
- Installer update for Open Suse Tumbleweed and Leap
|
||||||
- add symbolic link wirepy.py
|
- add symbolic link wirepy.py
|
||||||
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
09-04-0725
|
09-04-2025
|
||||||
|
|
||||||
- Installer now with query and remove
|
- Installer now with query and remove
|
||||||
- Icons merged
|
- Icons merged
|
||||||
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
07-04-0725
|
07-04-2025
|
||||||
|
|
||||||
- Installers will support other systems again
|
- Installers will support other systems again
|
||||||
- Installer is now finished clean with wrong password
|
- Installer is now finished clean with wrong password
|
||||||
@@ -46,12 +118,14 @@ My standard System: Linux Mint 22 Cinnamon
|
|||||||
- Tunnels are now read from the directory to view them in the list.
|
- Tunnels are now read from the directory to view them in the list.
|
||||||
To display only own tunnels, and read errors are minimized.
|
To display only own tunnels, and read errors are minimized.
|
||||||
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
10-11-2024
|
10-11-2024
|
||||||
|
|
||||||
- Fix Checkbutton Autostart when first install Wire-Py
|
- Fix Checkbutton Autostart when first install Wire-Py
|
||||||
- Update Translate Files
|
- Update Translate Files
|
||||||
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
10-11-2024
|
10-11-2024
|
||||||
|
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
|
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
|
||||||
|
|
||||||
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
||||||
|
# Update add New Style TButton.Borderless for Buttons without edge and without rounded corners by
|
||||||
|
# Désire Werner Menrath 28-07-2025
|
||||||
|
# Add Update Progressbars Round ends on Horizontal and Vertikal + add small bars
|
||||||
|
# Désire Werner Menrath 30-07-2025
|
||||||
|
|
||||||
package require Tk 8.6
|
package require Tk 8.6
|
||||||
|
|
||||||
@@ -117,6 +121,14 @@ namespace eval ttk::theme::water-dark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ttk::style layout TEntry {
|
||||||
|
Combobox.field -sticky nswe -children {
|
||||||
|
Combobox.padding -expand true -sticky nswe -children {
|
||||||
|
Combobox.textarea -sticky nswe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ttk::style layout TCombobox {
|
ttk::style layout TCombobox {
|
||||||
Combobox.field -sticky nswe -children {
|
Combobox.field -sticky nswe -children {
|
||||||
Combobox.padding -expand true -sticky nswe -children {
|
Combobox.padding -expand true -sticky nswe -children {
|
||||||
@@ -199,15 +211,23 @@ namespace eval ttk::theme::water-dark {
|
|||||||
# Button
|
# Button
|
||||||
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
|
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
|
||||||
|
|
||||||
ttk::style element create Button.button image \
|
ttk::style element create Button.button image [list $I(box-basic) {selected disabled} $I(box-basic) disabled $I(box-basic) pressed $I(box-basic) selected $I(box-basic) active $I(button-hover) focus $I(box-basic) ] -border 4 -sticky ewns
|
||||||
[list $I(box-basic) \
|
|
||||||
{selected disabled} $I(box-basic) \
|
# Borderless Button
|
||||||
disabled $I(box-basic) \
|
ttk::style configure TButton.Borderless -padding 0 -width -10 -anchor center
|
||||||
pressed $I(box-basic) \
|
ttk::style element create BorderlessButton.button image \
|
||||||
selected $I(box-basic) \
|
[list $I(empty) \
|
||||||
active $I(button-hover) \
|
active $I(button-borderless-hover) \
|
||||||
focus $I(button-hover) \
|
pressed $I(button-borderless-hover) \
|
||||||
] -border 4 -sticky ewns
|
focus $I(button-borderless-hover) \
|
||||||
|
] -border 0 -sticky ewns
|
||||||
|
ttk::style layout TButton.Borderless {
|
||||||
|
BorderlessButton.button -children {
|
||||||
|
Button.padding -children {
|
||||||
|
Button.label -side left -expand true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Toolbutton
|
# Toolbutton
|
||||||
ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center
|
ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center
|
||||||
@@ -221,6 +241,24 @@ namespace eval ttk::theme::water-dark {
|
|||||||
active $I(rect-basic) \
|
active $I(rect-basic) \
|
||||||
] -border 4 -sticky ewns
|
] -border 4 -sticky ewns
|
||||||
|
|
||||||
|
# TButton Borderless Round
|
||||||
|
ttk::style configure TButton.Borderless.Round -padding 0 -width -10 -anchor center
|
||||||
|
ttk::style element create TButton.Borderless.Round.button image \
|
||||||
|
[list $I(empty) \
|
||||||
|
{selected disabled} $I(empty) \
|
||||||
|
disabled $I(empty) \
|
||||||
|
selected $I(rect-basic) \
|
||||||
|
pressed $I(rect-basic) \
|
||||||
|
active $I(rect-basic) \
|
||||||
|
] -border 4 -sticky ewns
|
||||||
|
ttk::style layout TButton.Borderless.Round {
|
||||||
|
TButton.Borderless.Round.button -children {
|
||||||
|
Button.padding -children {
|
||||||
|
Button.label -side left -expand true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Menubutton
|
# Menubutton
|
||||||
ttk::style configure TMenubutton -padding {8 4 4 4}
|
ttk::style configure TMenubutton -padding {8 4 4 4}
|
||||||
|
|
||||||
@@ -265,7 +303,7 @@ namespace eval ttk::theme::water-dark {
|
|||||||
pressed $I(rect-accent) \
|
pressed $I(rect-accent) \
|
||||||
selected $I(rect-accent) \
|
selected $I(rect-accent) \
|
||||||
active $I(rect-accent-hover) \
|
active $I(rect-accent-hover) \
|
||||||
focus $I(rect-accent-hover) \
|
focus $I(rect-accent) \
|
||||||
] -border 4 -sticky ewns
|
] -border 4 -sticky ewns
|
||||||
|
|
||||||
# Checkbutton
|
# Checkbutton
|
||||||
@@ -394,17 +432,52 @@ namespace eval ttk::theme::water-dark {
|
|||||||
] -sticky {}
|
] -sticky {}
|
||||||
|
|
||||||
# Progressbar
|
# Progressbar
|
||||||
ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \
|
ttk::style layout Horizontal.TProgressbar {
|
||||||
-sticky ew
|
Horizontal.Progressbar.trough -children {
|
||||||
|
Horizontal.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ttk::style layout Vertical.TProgressbar {
|
||||||
|
Vertical.Progressbar.trough -children {
|
||||||
|
Vertical.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \
|
ttk::style element create Horizontal.Progressbar.trough image $I(hor-trough) \
|
||||||
-sticky ew
|
-border 3 -padding 0 -sticky ew
|
||||||
|
|
||||||
ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \
|
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-pbar) \
|
||||||
-sticky ns
|
-border 3 -padding 0 -sticky ew
|
||||||
|
|
||||||
ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \
|
ttk::style element create Vertical.Progressbar.trough image $I(vert-trough) \
|
||||||
-sticky ns
|
-border 3 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
ttk::style element create Vertical.Progressbar.pbar image $I(vert-pbar) \
|
||||||
|
-border 3 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
# Small Progressbar
|
||||||
|
ttk::style layout Small.Horizontal.TProgressbar {
|
||||||
|
Small.Horizontal.Progressbar.trough -children {
|
||||||
|
Small.Horizontal.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ttk::style layout Small.Vertical.TProgressbar {
|
||||||
|
Small.Vertical.Progressbar.trough -children {
|
||||||
|
Small.Vertical.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ttk::style element create Small.Horizontal.Progressbar.trough image $I(hor-trough-small) \
|
||||||
|
-border 1 -padding 0 -sticky ew
|
||||||
|
|
||||||
|
ttk::style element create Small.Horizontal.Progressbar.pbar image $I(hor-pbar-small) \
|
||||||
|
-border 1 -padding 0 -sticky ew
|
||||||
|
|
||||||
|
ttk::style element create Small.Vertical.Progressbar.trough image $I(vert-trough-small) \
|
||||||
|
-border 1 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
ttk::style element create Small.Vertical.Progressbar.pbar image $I(vert-pbar-small) \
|
||||||
|
-border 1 -padding 0 -sticky ns
|
||||||
|
|
||||||
# Entry
|
# Entry
|
||||||
ttk::style element create Entry.field \
|
ttk::style element create Entry.field \
|
||||||
@@ -532,8 +605,17 @@ namespace eval ttk::theme::water-dark {
|
|||||||
-background [list selected $colors(-selectbg)] \
|
-background [list selected $colors(-selectbg)] \
|
||||||
-foreground [list selected $colors(-selectfg)]
|
-foreground [list selected $colors(-selectfg)]
|
||||||
|
|
||||||
|
ttk::style configure TEntry -foreground $colors(-fg) -fieldbackground $colors(-bg)
|
||||||
|
ttk::style map TLabelframe -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TLabel -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TButton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TCheckbutton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TRadiobutton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TEntry -foreground [list disabled $colors(-disabledfg)] -fieldbackground [list disabled $colors(-disabledbg) readonly $colors(-disabledbg)]
|
||||||
|
|
||||||
# Panedwindow
|
# Panedwindow
|
||||||
# Insane hack to remove clam's ugly sash
|
# Insane hack to remove clam's ugly sash
|
||||||
ttk::style configure Sash -gripcount 0
|
ttk::style configure Sash -gripcount 0
|
||||||
|
ttk::style configure TPanedwindow -padding 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
TK-Themes/theme/dark/button-borderless-hover.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 4.0 KiB |
BIN
TK-Themes/theme/dark/hor-pbar-small.png
Normal file
After Width: | Height: | Size: 104 B |
BIN
TK-Themes/theme/dark/hor-pbar.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
TK-Themes/theme/dark/hor-trough-small.png
Normal file
After Width: | Height: | Size: 106 B |
BIN
TK-Themes/theme/dark/hor-trough.png
Normal file
After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 2.4 KiB |
BIN
TK-Themes/theme/dark/vert-pbar-small.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
TK-Themes/theme/dark/vert-pbar.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
TK-Themes/theme/dark/vert-trough-small.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
TK-Themes/theme/dark/vert-trough.png
Normal file
After Width: | Height: | Size: 228 B |
@@ -1,6 +1,10 @@
|
|||||||
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
|
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
|
||||||
|
|
||||||
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
||||||
|
# Update add New Style TButton.Borderless for Buttons without edge and without rounded corners by
|
||||||
|
# Désire Werner Menrath 28-07-2025
|
||||||
|
# Add Update Progressbars Round ends on Horizontal and Vertikal + add small bars
|
||||||
|
# Désire Werner Menrath 30-07-2025
|
||||||
|
|
||||||
package require Tk 8.6
|
package require Tk 8.6
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
-fg "#000000"
|
-fg "#000000"
|
||||||
-bg "#ffffff"
|
-bg "#ffffff"
|
||||||
-disabledfg "#737373"
|
-disabledfg "#737373"
|
||||||
-disabledbg "#ffffff"
|
-disabledbg "#f0f0f0"
|
||||||
-selectfg "#ffffff"
|
-selectfg "#ffffff"
|
||||||
-selectbg "#007fff"
|
-selectbg "#007fff"
|
||||||
}
|
}
|
||||||
@@ -44,6 +48,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ttk::style layout TMenubutton {
|
ttk::style layout TMenubutton {
|
||||||
Menubutton.button -children {
|
Menubutton.button -children {
|
||||||
Menubutton.padding -children {
|
Menubutton.padding -children {
|
||||||
@@ -117,6 +122,14 @@ namespace eval ttk::theme::water-light {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ttk::style layout TEntry {
|
||||||
|
Combobox.field -sticky nswe -children {
|
||||||
|
Combobox.padding -expand true -sticky nswe -children {
|
||||||
|
Combobox.textarea -sticky nswe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ttk::style layout TCombobox {
|
ttk::style layout TCombobox {
|
||||||
Combobox.field -sticky nswe -children {
|
Combobox.field -sticky nswe -children {
|
||||||
Combobox.padding -expand true -sticky nswe -children {
|
Combobox.padding -expand true -sticky nswe -children {
|
||||||
@@ -199,15 +212,23 @@ namespace eval ttk::theme::water-light {
|
|||||||
# Button
|
# Button
|
||||||
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
|
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
|
||||||
|
|
||||||
ttk::style element create Button.button image \
|
ttk::style element create Button.button image [list $I(box-basic) {selected disabled} $I(box-basic) disabled $I(box-basic) selected $I(box-basic) pressed $I(box-basic) active $I(button-hover) focus $I(box-basic) ] -border 4 -sticky ewns
|
||||||
[list $I(box-basic) \
|
|
||||||
{selected disabled} $I(box-basic) \
|
# Borderless Button
|
||||||
disabled $I(box-basic) \
|
ttk::style configure TButton.Borderless -padding 0 -width -10 -anchor center
|
||||||
selected $I(box-basic) \
|
ttk::style element create BorderlessButton.button image \
|
||||||
pressed $I(box-basic) \
|
[list $I(empty) \
|
||||||
active $I(button-hover) \
|
active $I(button-borderless-hover) \
|
||||||
focus $I(button-hover) \
|
pressed $I(button-borderless-hover) \
|
||||||
] -border 4 -sticky ewns
|
focus $I(button-borderless-hover) \
|
||||||
|
] -border 0 -sticky ewns
|
||||||
|
ttk::style layout TButton.Borderless {
|
||||||
|
BorderlessButton.button -children {
|
||||||
|
Button.padding -children {
|
||||||
|
Button.label -side left -expand true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Toolbutton
|
# Toolbutton
|
||||||
ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center
|
ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center
|
||||||
@@ -221,6 +242,25 @@ namespace eval ttk::theme::water-light {
|
|||||||
active $I(rect-basic) \
|
active $I(rect-basic) \
|
||||||
] -border 4 -sticky ewns
|
] -border 4 -sticky ewns
|
||||||
|
|
||||||
|
# TButton Borderless Round
|
||||||
|
ttk::style configure TButton.Borderless.Round -padding 0 -width -10 -anchor center
|
||||||
|
ttk::style element create TButton.Borderless.Round.button image \
|
||||||
|
[list $I(empty) \
|
||||||
|
{selected disabled} $I(empty) \
|
||||||
|
disabled $I(empty) \
|
||||||
|
selected $I(rect-basic) \
|
||||||
|
pressed $I(rect-basic) \
|
||||||
|
active $I(rect-basic) \
|
||||||
|
] -border 4 -sticky ewns
|
||||||
|
ttk::style layout TButton.Borderless.Round {
|
||||||
|
TButton.Borderless.Round.button -children {
|
||||||
|
Button.padding -children {
|
||||||
|
Button.label -side left -expand true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Menubutton
|
# Menubutton
|
||||||
ttk::style configure TMenubutton -padding {8 4 4 4}
|
ttk::style configure TMenubutton -padding {8 4 4 4}
|
||||||
|
|
||||||
@@ -265,7 +305,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
selected $I(rect-accent) \
|
selected $I(rect-accent) \
|
||||||
pressed $I(rect-accent) \
|
pressed $I(rect-accent) \
|
||||||
active $I(rect-accent-hover) \
|
active $I(rect-accent-hover) \
|
||||||
focus $I(rect-accent-hover) \
|
focus $I(rect-accent) \
|
||||||
] -border 4 -sticky ewns
|
] -border 4 -sticky ewns
|
||||||
|
|
||||||
# Checkbutton
|
# Checkbutton
|
||||||
@@ -394,17 +434,52 @@ namespace eval ttk::theme::water-light {
|
|||||||
] -sticky {}
|
] -sticky {}
|
||||||
|
|
||||||
# Progressbar
|
# Progressbar
|
||||||
ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \
|
ttk::style layout Horizontal.TProgressbar {
|
||||||
-sticky ew
|
Horizontal.Progressbar.trough -children {
|
||||||
|
Horizontal.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ttk::style layout Vertical.TProgressbar {
|
||||||
|
Vertical.Progressbar.trough -children {
|
||||||
|
Vertical.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \
|
ttk::style element create Horizontal.Progressbar.trough image $I(hor-trough) \
|
||||||
-sticky ew
|
-border 3 -padding 0 -sticky ew
|
||||||
|
|
||||||
ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \
|
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-pbar) \
|
||||||
-sticky ns
|
-border 3 -padding 0 -sticky ew
|
||||||
|
|
||||||
ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \
|
ttk::style element create Vertical.Progressbar.trough image $I(vert-trough) \
|
||||||
-sticky ns
|
-border 3 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
ttk::style element create Vertical.Progressbar.pbar image $I(vert-pbar) \
|
||||||
|
-border 3 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
# Small Progressbar
|
||||||
|
ttk::style layout Small.Horizontal.TProgressbar {
|
||||||
|
Small.Horizontal.Progressbar.trough -children {
|
||||||
|
Small.Horizontal.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ttk::style layout Small.Vertical.TProgressbar {
|
||||||
|
Small.Vertical.Progressbar.trough -children {
|
||||||
|
Small.Vertical.Progressbar.pbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ttk::style element create Small.Horizontal.Progressbar.trough image $I(hor-trough-small) \
|
||||||
|
-border 1 -padding 0 -sticky ew
|
||||||
|
|
||||||
|
ttk::style element create Small.Horizontal.Progressbar.pbar image $I(hor-pbar-small) \
|
||||||
|
-border 1 -padding 0 -sticky ew
|
||||||
|
|
||||||
|
ttk::style element create Small.Vertical.Progressbar.trough image $I(vert-trough-small) \
|
||||||
|
-border 1 -padding 0 -sticky ns
|
||||||
|
|
||||||
|
ttk::style element create Small.Vertical.Progressbar.pbar image $I(vert-pbar-small) \
|
||||||
|
-border 1 -padding 0 -sticky ns
|
||||||
|
|
||||||
# Entry
|
# Entry
|
||||||
ttk::style element create Entry.field \
|
ttk::style element create Entry.field \
|
||||||
@@ -448,7 +523,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
image [list $I(combo-button-basic) \
|
image [list $I(combo-button-basic) \
|
||||||
{!readonly focus} $I(combo-button-focus) \
|
{!readonly focus} $I(combo-button-focus) \
|
||||||
{readonly focus} $I(combo-button-hover) \
|
{readonly focus} $I(combo-button-hover) \
|
||||||
{readonly hover} $I(combo-button-hover)
|
{readonly hover} $I(combo-button-hover)\
|
||||||
] -border 5 -padding {2 6 6 6}
|
] -border 5 -padding {2 6 6 6}
|
||||||
|
|
||||||
ttk::style element create Combobox.arrow image $I(down) \
|
ttk::style element create Combobox.arrow image $I(down) \
|
||||||
@@ -481,7 +556,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
image [list $I(combo-button-basic) \
|
image [list $I(combo-button-basic) \
|
||||||
{!readonly focus} $I(combo-button-focus) \
|
{!readonly focus} $I(combo-button-focus) \
|
||||||
{readonly focus} $I(combo-button-hover) \
|
{readonly focus} $I(combo-button-hover) \
|
||||||
{readonly hover} $I(combo-button-hover)
|
{readonly hover} $I(combo-button-hover)\
|
||||||
] -border 5 -padding {2 6 6 6}
|
] -border 5 -padding {2 6 6 6}
|
||||||
|
|
||||||
# Sizegrip
|
# Sizegrip
|
||||||
@@ -517,7 +592,7 @@ namespace eval ttk::theme::water-light {
|
|||||||
|
|
||||||
ttk::style element create Treeheading.cell \
|
ttk::style element create Treeheading.cell \
|
||||||
image [list $I(tree-basic) \
|
image [list $I(tree-basic) \
|
||||||
pressed $I(tree-pressed)
|
pressed $I(tree-pressed)\
|
||||||
] -border 5 -padding 4 -sticky ewns
|
] -border 5 -padding 4 -sticky ewns
|
||||||
|
|
||||||
ttk::style element create Treeitem.indicator \
|
ttk::style element create Treeitem.indicator \
|
||||||
@@ -532,8 +607,17 @@ namespace eval ttk::theme::water-light {
|
|||||||
-background [list selected #ccc] \
|
-background [list selected #ccc] \
|
||||||
-foreground [list selected $colors(-fg)]
|
-foreground [list selected $colors(-fg)]
|
||||||
|
|
||||||
|
ttk::style map TLabelframe -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TLabel -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TButton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TCheckbutton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TRadiobutton -foreground [list disabled $colors(-disabledfg)]
|
||||||
|
ttk::style map TEntry -foreground [list disabled $colors(-disabledfg)] -fieldbackground [list disabled $colors(-disabledbg) readonly $colors(-disabledbg)]
|
||||||
|
ttk::style configure TEntry -foreground $colors(-fg) -fieldbackground $colors(-bg)
|
||||||
|
|
||||||
# Panedwindow
|
# Panedwindow
|
||||||
# Insane hack to remove clam's ugly sash
|
# Insane hack to remove clam's ugly sash
|
||||||
ttk::style configure Sash -gripcount 0
|
ttk::style configure Sash -gripcount 0
|
||||||
|
ttk::style configure TPanedwindow -padding 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
TK-Themes/theme/light/button-borderless-hover.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 247 B After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 3.9 KiB |
BIN
TK-Themes/theme/light/hor-pbar-small.png
Normal file
After Width: | Height: | Size: 107 B |
BIN
TK-Themes/theme/light/hor-pbar.png
Normal file
After Width: | Height: | Size: 185 B |
BIN
TK-Themes/theme/light/hor-trough-small.png
Normal file
After Width: | Height: | Size: 106 B |
BIN
TK-Themes/theme/light/hor-trough.png
Normal file
After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 3.8 KiB |
BIN
TK-Themes/theme/light/vert-pbar-small.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
TK-Themes/theme/light/vert-pbar.png
Normal file
After Width: | Height: | Size: 251 B |
BIN
TK-Themes/theme/light/vert-trough-small.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
TK-Themes/theme/light/vert-trough.png
Normal file
After Width: | Height: | Size: 228 B |
@@ -1,5 +1,10 @@
|
|||||||
# Copyright © 2021 rdbende <rdbende@gmail.com>
|
# Copyright © 2021 rdbende <rdbende@gmail.com>
|
||||||
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024
|
||||||
|
# Update add New Style TButton.Borderless for Buttons without edge and without rounded corners by
|
||||||
|
# Désire Werner Menrath 28-07-2025
|
||||||
|
# Add Update Progressbars Round ends on Horizontal and Vertikal + add small bars
|
||||||
|
# Désire Werner Menrath 30-07-2025
|
||||||
|
|
||||||
|
|
||||||
source [file join [file dirname [info script]] theme light.tcl]
|
source [file join [file dirname [info script]] theme light.tcl]
|
||||||
source [file join [file dirname [info script]] theme dark.tcl]
|
source [file join [file dirname [info script]] theme dark.tcl]
|
||||||
@@ -13,7 +18,7 @@ proc set_theme {mode} {
|
|||||||
array set colors {
|
array set colors {
|
||||||
-fg "#ffffff"
|
-fg "#ffffff"
|
||||||
-bg "#333333"
|
-bg "#333333"
|
||||||
-disabledfg "#ffffff"
|
-disabledfg "#aaaaaa"
|
||||||
-disabledbg "#737373"
|
-disabledbg "#737373"
|
||||||
-selectfg "#000000"
|
-selectfg "#000000"
|
||||||
-selectbg "#00c4ff"
|
-selectbg "#00c4ff"
|
||||||
@@ -53,7 +58,7 @@ proc set_theme {mode} {
|
|||||||
-fg "#000000"
|
-fg "#000000"
|
||||||
-bg "#ffffff"
|
-bg "#ffffff"
|
||||||
-disabledfg "#737373"
|
-disabledfg "#737373"
|
||||||
-disabledbg "#ffffff"
|
-disabledbg "#f0f0f0"
|
||||||
-selectfg "#000000"
|
-selectfg "#000000"
|
||||||
-selectbg "#00c4ff"
|
-selectbg "#00c4ff"
|
||||||
}
|
}
|
||||||
|
970
animated_icon.py
Normal file
@@ -0,0 +1,970 @@
|
|||||||
|
"""
|
||||||
|
A Tkinter widget for displaying animated icons.
|
||||||
|
|
||||||
|
This module provides the AnimatedIcon class, a custom Tkinter Canvas widget
|
||||||
|
that can display various types of animations. It supports both native Tkinter
|
||||||
|
drawing and Pillow (PIL) for anti-aliased graphics if available.
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from math import sin, cos, pi
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw, ImageTk
|
||||||
|
PIL_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
PIL_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
def _hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
|
||||||
|
"""Converts a hex color string to an RGB tuple."""
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
|
||||||
|
class AnimatedIcon(tk.Canvas):
|
||||||
|
"""A custom Tkinter Canvas widget for displaying animations."""
|
||||||
|
|
||||||
|
def __init__(self, master: tk.Misc, width: int = 20, height: int = 20, animation_type: str = "counter_arc", color: str = "#2a6fde", highlight_color: str = "#5195ff", use_pillow: bool = False, bg: Optional[str] = None) -> None:
|
||||||
|
"""
|
||||||
|
Initializes the AnimatedIcon widget.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
master: The parent widget.
|
||||||
|
width (int): The width of the icon.
|
||||||
|
height (int): The height of the icon.
|
||||||
|
animation_type (str): The type of animation to display.
|
||||||
|
Options: "counter_arc", "double_arc", "line", "blink".
|
||||||
|
color (str): The primary color of the icon.
|
||||||
|
highlight_color (str): The highlight color of the icon.
|
||||||
|
use_pillow (bool): Whether to use Pillow for drawing if available.
|
||||||
|
bg (str): The background color of the canvas.
|
||||||
|
"""
|
||||||
|
if bg is None:
|
||||||
|
try:
|
||||||
|
bg = master.cget("background")
|
||||||
|
except tk.TclError:
|
||||||
|
bg = "#f0f0f0" # Fallback color
|
||||||
|
super().__init__(master, width=width, height=height, bg=bg, highlightthickness=0)
|
||||||
|
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.animation_type = animation_type
|
||||||
|
self.color = color
|
||||||
|
self.highlight_color = highlight_color
|
||||||
|
self.use_pillow = use_pillow and PIL_AVAILABLE
|
||||||
|
self.running = False
|
||||||
|
self.angle = 0
|
||||||
|
self.pulse_animation = False
|
||||||
|
|
||||||
|
self.color_rgb = _hex_to_rgb(self.color)
|
||||||
|
self.highlight_color_rgb = _hex_to_rgb(self.highlight_color)
|
||||||
|
|
||||||
|
if self.use_pillow:
|
||||||
|
self.image = Image.new(
|
||||||
|
"RGBA", (width * 4, height * 4), (0, 0, 0, 0))
|
||||||
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
|
self.photo_image = None
|
||||||
|
|
||||||
|
def _draw_frame(self) -> None:
|
||||||
|
"""Draws a single frame of the animation."""
|
||||||
|
if self.use_pillow:
|
||||||
|
self._draw_pillow_frame()
|
||||||
|
else:
|
||||||
|
self._draw_canvas_frame()
|
||||||
|
|
||||||
|
def _draw_canvas_frame(self) -> None:
|
||||||
|
"""Draws a frame using native Tkinter canvas methods."""
|
||||||
|
self.delete("all")
|
||||||
|
if self.pulse_animation:
|
||||||
|
self._draw_canvas_pulse()
|
||||||
|
elif self.animation_type == "line":
|
||||||
|
self._draw_canvas_line()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_canvas_double_arc()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_canvas_counter_arc()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_canvas_blink()
|
||||||
|
|
||||||
|
def _draw_canvas_pulse(self) -> None:
|
||||||
|
"""Draws the pulse animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
pulse_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
if self.animation_type == "line":
|
||||||
|
for i in range(8):
|
||||||
|
angle = i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
self.create_line(start_x, start_y, end_x,
|
||||||
|
end_y, fill=pulse_color, width=2)
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.create_arc(bbox, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=pulse_color, width=2)
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=pulse_color, width=2)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_line(self) -> None:
|
||||||
|
"""Draws the line animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
for i in range(8):
|
||||||
|
angle = self.angle + i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
alpha = (cos(self.angle * 2 + i * (pi / 4)) + 1) / 2
|
||||||
|
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
self.create_line(start_x, start_y, end_x,
|
||||||
|
end_y, fill=color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_double_arc(self) -> None:
|
||||||
|
"""Draws the double arc animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
|
||||||
|
start_angle1 = -self.angle * 180 / pi
|
||||||
|
extent1 = 120 + 60 * sin(-self.angle)
|
||||||
|
self.create_arc(bbox, start=start_angle1, extent=extent1,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
start_angle2 = (-self.angle + pi) * 180 / pi
|
||||||
|
extent2 = 120 + 60 * sin(-self.angle + pi / 2)
|
||||||
|
self.create_arc(bbox, start=start_angle2, extent=extent2,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_counter_arc(self) -> None:
|
||||||
|
"""Draws the counter arc animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
start_angle1 = -self.angle * 180 / pi
|
||||||
|
self.create_arc(bbox_outer, start=start_angle1, extent=150,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
start_angle2 = self.angle * 180 / pi + 60
|
||||||
|
self.create_arc(bbox_inner, start=start_angle2, extent=150,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_blink(self) -> None:
|
||||||
|
"""Draws the blink animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
blink_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||||
|
radius, start=0, extent=359.9, style=tk.ARC, outline=blink_color, width=4)
|
||||||
|
|
||||||
|
def _draw_pillow_frame(self) -> None:
|
||||||
|
"""Draws a frame using Pillow for anti-aliased graphics."""
|
||||||
|
self.draw.rectangle(
|
||||||
|
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||||
|
if self.pulse_animation:
|
||||||
|
self._draw_pillow_pulse()
|
||||||
|
elif self.animation_type == "line":
|
||||||
|
self._draw_pillow_line()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_pillow_double_arc()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_pillow_counter_arc()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_pillow_blink()
|
||||||
|
|
||||||
|
resized_image = self.image.resize(
|
||||||
|
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||||
|
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||||
|
self.delete("all")
|
||||||
|
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||||
|
|
||||||
|
def _draw_pillow_pulse(self) -> None:
|
||||||
|
"""Draws the pulse animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
pulse_color = (r, g, b)
|
||||||
|
|
||||||
|
if self.animation_type == "line":
|
||||||
|
for i in range(12):
|
||||||
|
angle = i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=pulse_color, width=6, joint="curve")
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.draw.arc(bbox, start=0, end=360, fill=pulse_color, width=5)
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.draw.arc(bbox_outer, start=0, end=360,
|
||||||
|
fill=pulse_color, width=7)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.draw.arc(bbox_inner, start=0, end=360,
|
||||||
|
fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_line(self) -> None:
|
||||||
|
"""Draws the line animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
for i in range(12):
|
||||||
|
angle = self.angle + i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
alpha = (cos(self.angle * 2.5 + i * (pi / 6)) + 1) / 2
|
||||||
|
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
color = (r, g, b)
|
||||||
|
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=color, width=6, joint="curve")
|
||||||
|
|
||||||
|
def _draw_pillow_double_arc(self) -> None:
|
||||||
|
"""Draws the double arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
|
||||||
|
start_angle1 = self.angle * 180 / pi
|
||||||
|
extent1 = 120 + 60 * sin(self.angle)
|
||||||
|
self.draw.arc(bbox, start=start_angle1, end=start_angle1 +
|
||||||
|
extent1, fill=self.highlight_color_rgb, width=5)
|
||||||
|
|
||||||
|
start_angle2 = (self.angle + pi) * 180 / pi
|
||||||
|
extent2 = 120 + 60 * sin(self.angle + pi / 2)
|
||||||
|
self.draw.arc(bbox, start=start_angle2, end=start_angle2 +
|
||||||
|
extent2, fill=self.color_rgb, width=5)
|
||||||
|
|
||||||
|
def _draw_pillow_counter_arc(self) -> None:
|
||||||
|
"""Draws the counter arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
start_angle1 = self.angle * 180 / pi
|
||||||
|
self.draw.arc(bbox_outer, start=start_angle1, end=start_angle1 +
|
||||||
|
150, fill=self.highlight_color_rgb, width=7)
|
||||||
|
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
start_angle2 = -self.angle * 180 / pi + 60
|
||||||
|
self.draw.arc(bbox_inner, start=start_angle2,
|
||||||
|
end=start_angle2 + 150, fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_blink(self) -> None:
|
||||||
|
"""Draws the blink animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
blink_color = (r, g, b)
|
||||||
|
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||||
|
center_y + radius), start=0, end=360, fill=blink_color, width=10)
|
||||||
|
|
||||||
|
def _draw_stopped_frame(self) -> None:
|
||||||
|
"""Draws the icon in its stopped (static) state."""
|
||||||
|
self.delete("all")
|
||||||
|
if self.use_pillow:
|
||||||
|
self._draw_pillow_stopped_frame()
|
||||||
|
else:
|
||||||
|
self._draw_canvas_stopped_frame()
|
||||||
|
|
||||||
|
def _draw_canvas_stopped_frame(self) -> None:
|
||||||
|
"""Draws the stopped state using canvas methods."""
|
||||||
|
if self.animation_type == "line":
|
||||||
|
self._draw_canvas_line_stopped()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_canvas_double_arc_stopped()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_canvas_counter_arc_stopped()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_canvas_blink_stopped()
|
||||||
|
|
||||||
|
def _draw_canvas_line_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the line animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
for i in range(8):
|
||||||
|
angle = i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
self.create_line(start_x, start_y, end_x, end_y,
|
||||||
|
fill=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_double_arc_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the double arc animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.create_arc(bbox, start=0, extent=359.9, style=tk.ARC,
|
||||||
|
outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_counter_arc_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the counter arc animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_blink_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the blink animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||||
|
radius, start=0, extent=359.9, style=tk.ARC, outline=self.highlight_color, width=4)
|
||||||
|
|
||||||
|
def _draw_pillow_stopped_frame(self) -> None:
|
||||||
|
"""Draws the stopped state using Pillow."""
|
||||||
|
self.draw.rectangle(
|
||||||
|
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||||
|
if self.animation_type == "line":
|
||||||
|
self._draw_pillow_line_stopped()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_pillow_double_arc_stopped()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_pillow_counter_arc_stopped()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_pillow_blink_stopped()
|
||||||
|
|
||||||
|
resized_image = self.image.resize(
|
||||||
|
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||||
|
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||||
|
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||||
|
|
||||||
|
def _draw_pillow_line_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the line animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
for i in range(12):
|
||||||
|
angle = i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=self.highlight_color_rgb, width=6, joint="curve")
|
||||||
|
|
||||||
|
def _draw_pillow_double_arc_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the double arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.draw.arc(bbox, start=0, end=360,
|
||||||
|
fill=self.highlight_color_rgb, width=5)
|
||||||
|
|
||||||
|
def _draw_pillow_counter_arc_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the counter arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.draw.arc(bbox_outer, start=0, end=360,
|
||||||
|
fill=self.highlight_color_rgb, width=7)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.draw.arc(bbox_inner, start=0, end=360,
|
||||||
|
fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_blink_stopped(self) -> None:
|
||||||
|
"""Draws the stopped state for the blink animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||||
|
center_y + radius), start=0, end=360, fill=self.highlight_color_rgb, width=10)
|
||||||
|
|
||||||
|
def _animate(self) -> None:
|
||||||
|
"""The main animation loop."""
|
||||||
|
if self.running:
|
||||||
|
self.angle += 0.1
|
||||||
|
if self.angle > 2 * pi:
|
||||||
|
self.angle -= 2 * pi
|
||||||
|
self._draw_frame()
|
||||||
|
self.after(30, self._animate)
|
||||||
|
|
||||||
|
def start(self, pulse: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Starts the animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
||||||
|
"""
|
||||||
|
if not self.running:
|
||||||
|
self.pulse_animation = pulse
|
||||||
|
self.running = True
|
||||||
|
self._animate()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Stops the animation and shows the static 'stopped' frame."""
|
||||||
|
self.running = False
|
||||||
|
self.pulse_animation = False
|
||||||
|
self._draw_stopped_frame()
|
||||||
|
|
||||||
|
def hide(self) -> None:
|
||||||
|
"""Stops the animation and clears the canvas."""
|
||||||
|
self.running = False
|
||||||
|
self.pulse_animation = False
|
||||||
|
self.delete("all")
|
||||||
|
|
||||||
|
def show_full_circle(self) -> None:
|
||||||
|
"""Shows the static 'stopped' frame without starting the animation."""
|
||||||
|
if not self.running:
|
||||||
|
self._draw_stopped_frame()
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw, ImageTk
|
||||||
|
PIL_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
PIL_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
def _hex_to_rgb(hex_color):
|
||||||
|
"""Converts a hex color string to an RGB tuple."""
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
|
||||||
|
class AnimatedIcon(tk.Canvas):
|
||||||
|
"""A custom Tkinter Canvas widget for displaying animations."""
|
||||||
|
|
||||||
|
def __init__(self, master, width=20, height=20, animation_type="counter_arc", color="#2a6fde", highlight_color="#5195ff", use_pillow=False, bg=None):
|
||||||
|
"""
|
||||||
|
Initializes the AnimatedIcon widget.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
master: The parent widget.
|
||||||
|
width (int): The width of the icon.
|
||||||
|
height (int): The height of the icon.
|
||||||
|
animation_type (str): The type of animation to display.
|
||||||
|
Options: "counter_arc", "double_arc", "line", "blink".
|
||||||
|
color (str): The primary color of the icon.
|
||||||
|
highlight_color (str): The highlight color of the icon.
|
||||||
|
use_pillow (bool): Whether to use Pillow for drawing if available.
|
||||||
|
bg (str): The background color of the canvas.
|
||||||
|
"""
|
||||||
|
if bg is None:
|
||||||
|
try:
|
||||||
|
bg = master.cget("background")
|
||||||
|
except tk.TclError:
|
||||||
|
bg = "#f0f0f0" # Fallback color
|
||||||
|
super().__init__(master, width=width, height=height, bg=bg, highlightthickness=0)
|
||||||
|
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.animation_type = animation_type
|
||||||
|
self.color = color
|
||||||
|
self.highlight_color = highlight_color
|
||||||
|
self.use_pillow = use_pillow and PIL_AVAILABLE
|
||||||
|
self.running = False
|
||||||
|
self.angle = 0
|
||||||
|
self.pulse_animation = False
|
||||||
|
|
||||||
|
self.color_rgb = _hex_to_rgb(self.color)
|
||||||
|
self.highlight_color_rgb = _hex_to_rgb(self.highlight_color)
|
||||||
|
|
||||||
|
if self.use_pillow:
|
||||||
|
self.image = Image.new(
|
||||||
|
"RGBA", (width * 4, height * 4), (0, 0, 0, 0))
|
||||||
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
|
self.photo_image = None
|
||||||
|
|
||||||
|
def _draw_frame(self):
|
||||||
|
"""Draws a single frame of the animation."""
|
||||||
|
if self.use_pillow:
|
||||||
|
self._draw_pillow_frame()
|
||||||
|
else:
|
||||||
|
self._draw_canvas_frame()
|
||||||
|
|
||||||
|
def _draw_canvas_frame(self):
|
||||||
|
"""Draws a frame using native Tkinter canvas methods."""
|
||||||
|
self.delete("all")
|
||||||
|
if self.pulse_animation:
|
||||||
|
self._draw_canvas_pulse()
|
||||||
|
elif self.animation_type == "line":
|
||||||
|
self._draw_canvas_line()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_canvas_double_arc()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_canvas_counter_arc()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_canvas_blink()
|
||||||
|
|
||||||
|
def _draw_canvas_pulse(self):
|
||||||
|
"""Draws the pulse animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
pulse_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
if self.animation_type == "line":
|
||||||
|
for i in range(8):
|
||||||
|
angle = i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
self.create_line(start_x, start_y, end_x,
|
||||||
|
end_y, fill=pulse_color, width=2)
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.create_arc(bbox, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=pulse_color, width=2)
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=pulse_color, width=2)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_line(self):
|
||||||
|
"""Draws the line animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
for i in range(8):
|
||||||
|
angle = self.angle + i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
alpha = (cos(self.angle * 2 + i * (pi / 4)) + 1) / 2
|
||||||
|
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
self.create_line(start_x, start_y, end_x,
|
||||||
|
end_y, fill=color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_double_arc(self):
|
||||||
|
"""Draws the double arc animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
|
||||||
|
start_angle1 = -self.angle * 180 / pi
|
||||||
|
extent1 = 120 + 60 * sin(-self.angle)
|
||||||
|
self.create_arc(bbox, start=start_angle1, extent=extent1,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
start_angle2 = (-self.angle + pi) * 180 / pi
|
||||||
|
extent2 = 120 + 60 * sin(-self.angle + pi / 2)
|
||||||
|
self.create_arc(bbox, start=start_angle2, extent=extent2,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_counter_arc(self):
|
||||||
|
"""Draws the counter arc animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
start_angle1 = -self.angle * 180 / pi
|
||||||
|
self.create_arc(bbox_outer, start=start_angle1, extent=150,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
start_angle2 = self.angle * 180 / pi + 60
|
||||||
|
self.create_arc(bbox_inner, start=start_angle2, extent=150,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_blink(self):
|
||||||
|
"""Draws the blink animation using canvas methods."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
blink_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||||
|
radius, start=0, extent=359.9, style=tk.ARC, outline=blink_color, width=4)
|
||||||
|
|
||||||
|
def _draw_pillow_frame(self):
|
||||||
|
"""Draws a frame using Pillow for anti-aliased graphics."""
|
||||||
|
self.draw.rectangle(
|
||||||
|
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||||
|
if self.pulse_animation:
|
||||||
|
self._draw_pillow_pulse()
|
||||||
|
elif self.animation_type == "line":
|
||||||
|
self._draw_pillow_line()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_pillow_double_arc()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_pillow_counter_arc()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_pillow_blink()
|
||||||
|
|
||||||
|
resized_image = self.image.resize(
|
||||||
|
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||||
|
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||||
|
self.delete("all")
|
||||||
|
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||||
|
|
||||||
|
def _draw_pillow_pulse(self):
|
||||||
|
"""Draws the pulse animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
pulse_color = (r, g, b)
|
||||||
|
|
||||||
|
if self.animation_type == "line":
|
||||||
|
for i in range(12):
|
||||||
|
angle = i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=pulse_color, width=6, joint="curve")
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.draw.arc(bbox, start=0, end=360, fill=pulse_color, width=5)
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.draw.arc(bbox_outer, start=0, end=360,
|
||||||
|
fill=pulse_color, width=7)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.draw.arc(bbox_inner, start=0, end=360,
|
||||||
|
fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_line(self):
|
||||||
|
"""Draws the line animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
for i in range(12):
|
||||||
|
angle = self.angle + i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
alpha = (cos(self.angle * 2.5 + i * (pi / 6)) + 1) / 2
|
||||||
|
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
color = (r, g, b)
|
||||||
|
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=color, width=6, joint="curve")
|
||||||
|
|
||||||
|
def _draw_pillow_double_arc(self):
|
||||||
|
"""Draws the double arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
|
||||||
|
start_angle1 = self.angle * 180 / pi
|
||||||
|
extent1 = 120 + 60 * sin(self.angle)
|
||||||
|
self.draw.arc(bbox, start=start_angle1, end=start_angle1 +
|
||||||
|
extent1, fill=self.highlight_color_rgb, width=5)
|
||||||
|
|
||||||
|
start_angle2 = (self.angle + pi) * 180 / pi
|
||||||
|
extent2 = 120 + 60 * sin(self.angle + pi / 2)
|
||||||
|
self.draw.arc(bbox, start=start_angle2, end=start_angle2 +
|
||||||
|
extent2, fill=self.color_rgb, width=5)
|
||||||
|
|
||||||
|
def _draw_pillow_counter_arc(self):
|
||||||
|
"""Draws the counter arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
start_angle1 = self.angle * 180 / pi
|
||||||
|
self.draw.arc(bbox_outer, start=start_angle1, end=start_angle1 +
|
||||||
|
150, fill=self.highlight_color_rgb, width=7)
|
||||||
|
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
start_angle2 = -self.angle * 180 / pi + 60
|
||||||
|
self.draw.arc(bbox_inner, start=start_angle2,
|
||||||
|
end=start_angle2 + 150, fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_blink(self):
|
||||||
|
"""Draws the blink animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||||
|
r = int(
|
||||||
|
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||||
|
g = int(
|
||||||
|
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||||
|
b = int(
|
||||||
|
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||||
|
blink_color = (r, g, b)
|
||||||
|
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||||
|
center_y + radius), start=0, end=360, fill=blink_color, width=10)
|
||||||
|
|
||||||
|
def _draw_stopped_frame(self):
|
||||||
|
"""Draws the icon in its stopped (static) state."""
|
||||||
|
self.delete("all")
|
||||||
|
if self.use_pillow:
|
||||||
|
self._draw_pillow_stopped_frame()
|
||||||
|
else:
|
||||||
|
self._draw_canvas_stopped_frame()
|
||||||
|
|
||||||
|
def _draw_canvas_stopped_frame(self):
|
||||||
|
"""Draws the stopped state using canvas methods."""
|
||||||
|
if self.animation_type == "line":
|
||||||
|
self._draw_canvas_line_stopped()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_canvas_double_arc_stopped()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_canvas_counter_arc_stopped()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_canvas_blink_stopped()
|
||||||
|
|
||||||
|
def _draw_canvas_line_stopped(self):
|
||||||
|
"""Draws the stopped state for the line animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
for i in range(8):
|
||||||
|
angle = i * (pi / 4)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||||
|
self.create_line(start_x, start_y, end_x, end_y,
|
||||||
|
fill=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_double_arc_stopped(self):
|
||||||
|
"""Draws the stopped state for the double arc animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.create_arc(bbox, start=0, extent=359.9, style=tk.ARC,
|
||||||
|
outline=self.highlight_color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_counter_arc_stopped(self):
|
||||||
|
"""Draws the stopped state for the counter arc animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||||
|
style=tk.ARC, outline=self.color, width=2)
|
||||||
|
|
||||||
|
def _draw_canvas_blink_stopped(self):
|
||||||
|
"""Draws the stopped state for the blink animation."""
|
||||||
|
center_x, center_y = self.width / 2, self.height / 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||||
|
radius, start=0, extent=359.9, style=tk.ARC, outline=self.highlight_color, width=4)
|
||||||
|
|
||||||
|
def _draw_pillow_stopped_frame(self):
|
||||||
|
"""Draws the stopped state using Pillow."""
|
||||||
|
self.draw.rectangle(
|
||||||
|
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||||
|
if self.animation_type == "line":
|
||||||
|
self._draw_pillow_line_stopped()
|
||||||
|
elif self.animation_type == "double_arc":
|
||||||
|
self._draw_pillow_double_arc_stopped()
|
||||||
|
elif self.animation_type == "counter_arc":
|
||||||
|
self._draw_pillow_counter_arc_stopped()
|
||||||
|
elif self.animation_type == "blink":
|
||||||
|
self._draw_pillow_blink_stopped()
|
||||||
|
|
||||||
|
resized_image = self.image.resize(
|
||||||
|
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||||
|
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||||
|
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||||
|
|
||||||
|
def _draw_pillow_line_stopped(self):
|
||||||
|
"""Draws the stopped state for the line animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
for i in range(12):
|
||||||
|
angle = i * (pi / 6)
|
||||||
|
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||||
|
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||||
|
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||||
|
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||||
|
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||||
|
fill=self.highlight_color_rgb, width=6, joint="curve")
|
||||||
|
|
||||||
|
def _draw_pillow_double_arc_stopped(self):
|
||||||
|
"""Draws the stopped state for the double arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
bbox = (center_x - radius, center_y - radius,
|
||||||
|
center_x + radius, center_y + radius)
|
||||||
|
self.draw.arc(bbox, start=0, end=360,
|
||||||
|
fill=self.highlight_color_rgb, width=5)
|
||||||
|
|
||||||
|
def _draw_pillow_counter_arc_stopped(self):
|
||||||
|
"""Draws the stopped state for the counter arc animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius_outer = min(center_x, center_y) * 0.8
|
||||||
|
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||||
|
center_x + radius_outer, center_y + radius_outer)
|
||||||
|
self.draw.arc(bbox_outer, start=0, end=360,
|
||||||
|
fill=self.highlight_color_rgb, width=7)
|
||||||
|
radius_inner = min(center_x, center_y) * 0.6
|
||||||
|
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||||
|
center_x + radius_inner, center_y + radius_inner)
|
||||||
|
self.draw.arc(bbox_inner, start=0, end=360,
|
||||||
|
fill=self.color_rgb, width=7)
|
||||||
|
|
||||||
|
def _draw_pillow_blink_stopped(self):
|
||||||
|
"""Draws the stopped state for the blink animation using Pillow."""
|
||||||
|
center_x, center_y = self.width * 2, self.height * 2
|
||||||
|
radius = min(center_x, center_y) * 0.8
|
||||||
|
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||||
|
center_y + radius), start=0, end=360, fill=self.highlight_color_rgb, width=10)
|
||||||
|
|
||||||
|
def _animate(self):
|
||||||
|
"""The main animation loop."""
|
||||||
|
if self.running:
|
||||||
|
self.angle += 0.1
|
||||||
|
if self.angle > 2 * pi:
|
||||||
|
self.angle -= 2 * pi
|
||||||
|
self._draw_frame()
|
||||||
|
self.after(30, self._animate)
|
||||||
|
|
||||||
|
def start(self, pulse=False):
|
||||||
|
"""
|
||||||
|
Starts the animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
||||||
|
"""
|
||||||
|
if not self.running:
|
||||||
|
self.pulse_animation = pulse
|
||||||
|
self.running = True
|
||||||
|
self._animate()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stops the animation and shows the static 'stopped' frame."""
|
||||||
|
self.running = False
|
||||||
|
self.pulse_animation = False
|
||||||
|
self._draw_stopped_frame()
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
"""Stops the animation and clears the canvas."""
|
||||||
|
self.running = False
|
||||||
|
self.pulse_animation = False
|
||||||
|
self.delete("all")
|
||||||
|
|
||||||
|
def show_full_circle(self):
|
||||||
|
"""Shows the static 'stopped' frame without starting the animation."""
|
||||||
|
if not self.running:
|
||||||
|
self._draw_stopped_frame()
|
858
common_tools.py
@@ -1,858 +0,0 @@
|
|||||||
""" Classes Method and Functions for lx Apps """
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tkinter as tk
|
|
||||||
from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List
|
|
||||||
import zipfile
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from subprocess import check_call, CompletedProcess
|
|
||||||
from tkinter import ttk, Toplevel
|
|
||||||
from wp_app_config import AppConfig, Msg
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Translate
|
|
||||||
_ = AppConfig.setup_translations()
|
|
||||||
|
|
||||||
|
|
||||||
class Create:
|
|
||||||
"""
|
|
||||||
This class is for the creation of the folders and files
|
|
||||||
required by Wire-Py, as well as for decryption
|
|
||||||
the tunnel from the user's home directory
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def dir_and_files() -> None:
|
|
||||||
"""
|
|
||||||
check and create folders and files if not present
|
|
||||||
"""
|
|
||||||
|
|
||||||
pth: Path = Path.home() / ".config/wire_py"
|
|
||||||
pth.mkdir(parents=True, exist_ok=True)
|
|
||||||
sett: Path = Path.home() / ".config/wire_py/settings"
|
|
||||||
AppConfig.KEYS_FILE
|
|
||||||
|
|
||||||
if sett.exists():
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
sett.touch()
|
|
||||||
sett.write_text(
|
|
||||||
"[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
if AppConfig.KEYS_FILE.exists():
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
AppConfig.KEYS_FILE.touch()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def files_for_autostart() -> None:
|
|
||||||
"""
|
|
||||||
check and create a file for auto start if not present and enable the service
|
|
||||||
"""
|
|
||||||
|
|
||||||
pth2: Path = Path.home() / ".config/systemd/user"
|
|
||||||
pth2.mkdir(parents=True, exist_ok=True)
|
|
||||||
wg_ser: Path = Path.home() / ".config/systemd/user/wg_start.service"
|
|
||||||
|
|
||||||
if wg_ser.exists():
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
wg_ser.touch()
|
|
||||||
wg_ser.write_text(
|
|
||||||
"[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target\n\n[Service]\n"
|
|
||||||
"Type=oneshot\nExecStartPre=/bin/sleep 5\nExecStart=/usr/local/bin/start_wg.py\n[Install]"
|
|
||||||
"\nWantedBy=default.target"
|
|
||||||
)
|
|
||||||
check_call(["systemctl", "--user", "enable", "wg_start.service"])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_dir() -> None:
|
|
||||||
"""Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard"""
|
|
||||||
|
|
||||||
if AppConfig.TEMP_DIR.exists():
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
AppConfig.TEMP_DIR.mkdir()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decrypt() -> None:
|
|
||||||
"""
|
|
||||||
Starts SSL dencrypt
|
|
||||||
"""
|
|
||||||
process: CompletedProcess[str] = subprocess.run(
|
|
||||||
["pkexec", "/usr/local/bin/ssl_decrypt.py"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
path: Path = Path.home() / ".config/wire_py/"
|
|
||||||
file_in_path: list[Path] = list(path.rglob("*.dat"))
|
|
||||||
if file_in_path:
|
|
||||||
if process.returncode == 0:
|
|
||||||
print("File successfully decrypted...")
|
|
||||||
|
|
||||||
else:
|
|
||||||
print(f"Error with the following code... {process.returncode}")
|
|
||||||
else:
|
|
||||||
print(_("Ready for import"))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encrypt() -> None:
|
|
||||||
"""
|
|
||||||
Starts SSL encryption
|
|
||||||
"""
|
|
||||||
process: CompletedProcess[str] = subprocess.run(
|
|
||||||
["pkexec", "/usr/local/bin/ssl_encrypt.py"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
print(process.stdout)
|
|
||||||
if process.returncode == 0:
|
|
||||||
print("All Files successfully encrypted...")
|
|
||||||
else:
|
|
||||||
print(f"Error with the following code... {process.returncode}")
|
|
||||||
|
|
||||||
|
|
||||||
class LxTools(tk.Tk):
|
|
||||||
"""
|
|
||||||
Class LinuxTools methods that can also be used for other apps
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def center_window_cross_platform(window, width, height):
|
|
||||||
"""
|
|
||||||
Centers a window on the primary monitor in a way that works on both X11 and Wayland
|
|
||||||
|
|
||||||
Args:
|
|
||||||
window: The tkinter window to center
|
|
||||||
width: Window width
|
|
||||||
height: Window height
|
|
||||||
"""
|
|
||||||
# Calculate the position before showing the window
|
|
||||||
|
|
||||||
# First attempt: Try to use GDK if available (works on both X11 and Wayland)
|
|
||||||
try:
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gdk", "3.0")
|
|
||||||
from gi.repository import Gdk
|
|
||||||
|
|
||||||
display = Gdk.Display.get_default()
|
|
||||||
monitor = display.get_primary_monitor() or display.get_monitor(0)
|
|
||||||
geometry = monitor.get_geometry()
|
|
||||||
scale_factor = monitor.get_scale_factor()
|
|
||||||
|
|
||||||
# Calculate center position on primary monitor
|
|
||||||
x = geometry.x + (geometry.width - width // scale_factor) // 2
|
|
||||||
y = geometry.y + (geometry.height - height // scale_factor) // 2
|
|
||||||
|
|
||||||
# Set window geometry
|
|
||||||
window.geometry(f"{width}x{height}+{x}+{y}")
|
|
||||||
return
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Second attempt: Try xrandr for X11
|
|
||||||
try:
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
output = subprocess.check_output(
|
|
||||||
["xrandr", "--query"], universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse the output to find the primary monitor
|
|
||||||
primary_info = None
|
|
||||||
for line in output.splitlines():
|
|
||||||
if "primary" in line:
|
|
||||||
parts = line.split()
|
|
||||||
for part in parts:
|
|
||||||
if "x" in part and "+" in part:
|
|
||||||
primary_info = part
|
|
||||||
break
|
|
||||||
break
|
|
||||||
|
|
||||||
if primary_info:
|
|
||||||
# Parse the geometry: WIDTHxHEIGHT+X+Y
|
|
||||||
geometry = primary_info.split("+")
|
|
||||||
dimensions = geometry[0].split("x")
|
|
||||||
primary_width = int(dimensions[0])
|
|
||||||
primary_height = int(dimensions[1])
|
|
||||||
primary_x = int(geometry[1])
|
|
||||||
primary_y = int(geometry[2])
|
|
||||||
|
|
||||||
# Calculate center position on primary monitor
|
|
||||||
x = primary_x + (primary_width - width) // 2
|
|
||||||
y = primary_y + (primary_height - height) // 2
|
|
||||||
|
|
||||||
# Set window geometry
|
|
||||||
window.geometry(f"{width}x{height}+{x}+{y}")
|
|
||||||
return
|
|
||||||
except (subprocess.SubprocessError, ImportError, IndexError, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Final fallback: Use standard Tkinter method
|
|
||||||
screen_width = window.winfo_screenwidth()
|
|
||||||
screen_height = window.winfo_screenheight()
|
|
||||||
|
|
||||||
# Try to make an educated guess for multi-monitor setups
|
|
||||||
# If screen width is much larger than height, assume multiple monitors side by side
|
|
||||||
if (
|
|
||||||
screen_width > screen_height * 1.8
|
|
||||||
): # Heuristic for detecting multiple monitors
|
|
||||||
# Assume primary monitor is on the left half
|
|
||||||
screen_width = screen_width // 2
|
|
||||||
|
|
||||||
x = (screen_width - width) // 2
|
|
||||||
y = (screen_height - height) // 2
|
|
||||||
window.geometry(f"{width}x{height}+{x}+{y}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_file_name(path: Path, i: int = 5) -> List[str]:
|
|
||||||
"""
|
|
||||||
Recursively searches the specified path for files and returns a list of filenames,
|
|
||||||
with the last 'i' characters of each filename removed.
|
|
||||||
|
|
||||||
This method is useful for obtaining filenames without specific file extensions,
|
|
||||||
e.g., to remove '.conf' from Wireguard configuration files.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (Path): The directory path to search
|
|
||||||
i (int, optional): Number of characters to remove from the end of each filename.
|
|
||||||
Default is 5, which typically corresponds to the length of '.conf'.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: A list of filenames without the last 'i' characters
|
|
||||||
|
|
||||||
Example:
|
|
||||||
If path contains files like 'tunnel1.conf', 'tunnel2.conf' and i=5,
|
|
||||||
the method returns ['tunnel1', 'tunnel2'].
|
|
||||||
"""
|
|
||||||
lists_file = list(path.rglob("*"))
|
|
||||||
lists_file = [conf_file.name[:-i] for conf_file in lists_file]
|
|
||||||
return lists_file
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_username() -> str:
|
|
||||||
"""
|
|
||||||
Returns the username of the logged-in user,
|
|
||||||
even if the script is running with root privileges.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["logname"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
print(result.stdout.strip())
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None:
|
|
||||||
"""
|
|
||||||
method that can be added after need to delete a folder and a file when quitting.
|
|
||||||
Args:
|
|
||||||
:param file: default None
|
|
||||||
:param AppConfig.TEMP_DIR: default None
|
|
||||||
"""
|
|
||||||
if AppConfig.TEMP_DIR is not None:
|
|
||||||
shutil.rmtree(AppConfig.TEMP_DIR)
|
|
||||||
try:
|
|
||||||
if file is not None:
|
|
||||||
Path.unlink(file)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def msg_window(
|
|
||||||
image_path: Path,
|
|
||||||
image_path2: Path,
|
|
||||||
w_title: str,
|
|
||||||
w_txt: str,
|
|
||||||
txt2: Optional[str] = None,
|
|
||||||
com: Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Creates message windows
|
|
||||||
|
|
||||||
:argument AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text
|
|
||||||
:argument AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon
|
|
||||||
:argument w_title = Windows Title
|
|
||||||
:argument w_txt = Text for Tk Window
|
|
||||||
:argument txt2 = Text for Button two
|
|
||||||
:argument com = function for Button command
|
|
||||||
"""
|
|
||||||
msg: tk.Toplevel = tk.Toplevel()
|
|
||||||
msg.resizable(width=False, height=False)
|
|
||||||
msg.title(w_title)
|
|
||||||
msg.configure(pady=15, padx=15)
|
|
||||||
|
|
||||||
# Lade das erste Bild für das Fenster
|
|
||||||
try:
|
|
||||||
msg.img = tk.PhotoImage(file=image_path)
|
|
||||||
msg.i_window = tk.Label(msg, image=msg.img)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Fehler beim Laden des Fensterbildes: {e}")
|
|
||||||
msg.i_window = tk.Label(msg, text="Bild nicht gefunden")
|
|
||||||
|
|
||||||
label: tk.Label = tk.Label(msg, text=w_txt)
|
|
||||||
label.grid(column=1, row=0)
|
|
||||||
|
|
||||||
if txt2 is not None and com is not None:
|
|
||||||
label.config(font=("Ubuntu", 11), padx=15, justify="left")
|
|
||||||
msg.i_window.grid(column=0, row=0, sticky="nw")
|
|
||||||
button2: ttk.Button = ttk.Button(
|
|
||||||
msg, text=f"{txt2}", command=com, padding=4
|
|
||||||
)
|
|
||||||
button2.grid(column=0, row=1, sticky="e", columnspan=2)
|
|
||||||
button: ttk.Button = ttk.Button(
|
|
||||||
msg, text="OK", command=msg.destroy, padding=4
|
|
||||||
)
|
|
||||||
button.grid(column=0, row=1, sticky="w", columnspan=2)
|
|
||||||
else:
|
|
||||||
label.config(font=("Ubuntu", 11), padx=15)
|
|
||||||
msg.i_window.grid(column=0, row=0)
|
|
||||||
button: ttk.Button = ttk.Button(
|
|
||||||
msg, text="OK", command=msg.destroy, padding=4
|
|
||||||
)
|
|
||||||
button.grid(column=0, columnspan=2, row=1)
|
|
||||||
|
|
||||||
# Lade das Icon für das Fenster
|
|
||||||
try:
|
|
||||||
icon = tk.PhotoImage(file=image_path2)
|
|
||||||
msg.iconphoto(True, icon)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Fehler beim Laden des Fenstericons: {e}")
|
|
||||||
|
|
||||||
msg.columnconfigure(0, weight=1)
|
|
||||||
msg.rowconfigure(0, weight=1)
|
|
||||||
msg.winfo_toplevel()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None:
|
|
||||||
"""
|
|
||||||
Function for cleanup after a program interruption
|
|
||||||
|
|
||||||
:param file: Optional - File to be deleted
|
|
||||||
:param file_path: Optional - Directory to be deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
def signal_handler(signum: int, frame: Any) -> NoReturn:
|
|
||||||
"""
|
|
||||||
Determines clear text names for signal numbers and handles signals
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signum: The signal number
|
|
||||||
frame: The current stack frame
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
NoReturn since the function either exits the program or continues execution
|
|
||||||
"""
|
|
||||||
|
|
||||||
signals_to_names_dict: Dict[int, str] = dict(
|
|
||||||
(getattr(signal, n), n)
|
|
||||||
for n in dir(signal)
|
|
||||||
if n.startswith("SIG") and "_" not in n
|
|
||||||
)
|
|
||||||
|
|
||||||
signal_name: str = signals_to_names_dict.get(
|
|
||||||
signum, f"Unnamed signal: {signum}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# End program for certain signals, report to others only reception
|
|
||||||
if signum in (signal.SIGINT, signal.SIGTERM):
|
|
||||||
exit_code: int = 1
|
|
||||||
print(
|
|
||||||
f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}."
|
|
||||||
)
|
|
||||||
LxTools.clean_files(file_path, file)
|
|
||||||
print("Breakdown by user...")
|
|
||||||
sys.exit(exit_code)
|
|
||||||
else:
|
|
||||||
print(f"Signal {signum} received and ignored.")
|
|
||||||
LxTools.clean_files(file_path, file)
|
|
||||||
print("Process unexpectedly ended...")
|
|
||||||
|
|
||||||
# Register signal handlers for various signals
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
signal.signal(signal.SIGHUP, signal_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class Tunnel:
|
|
||||||
"""
|
|
||||||
Class of Methods for Wire-Py
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_files_to_dictionary() -> Dict[str, List[str]]:
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
if not AppConfig.TEMP_DIR.exists() or not AppConfig.TEMP_DIR.is_dir():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Get a list of all files in the directorys
|
|
||||||
files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()]
|
|
||||||
if not files:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Search for the string in the files
|
|
||||||
for file in files:
|
|
||||||
try:
|
|
||||||
with open(file, "r") as f:
|
|
||||||
content = f.read()
|
|
||||||
# Hier parsen wir die relevanten Zeilen aus dem Inhalt
|
|
||||||
address_line = next(
|
|
||||||
line
|
|
||||||
for line in content.splitlines()
|
|
||||||
if line.startswith("Address")
|
|
||||||
)
|
|
||||||
dns_line = next(
|
|
||||||
line for line in content.splitlines() if line.startswith("DNS")
|
|
||||||
)
|
|
||||||
endpoint_line = next(
|
|
||||||
line
|
|
||||||
for line in content.splitlines()
|
|
||||||
if line.startswith("Endpoint")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extrahiere die Werte
|
|
||||||
address = address_line.split("=")[1].strip()
|
|
||||||
dns = dns_line.split("=")[1].strip()
|
|
||||||
endpoint = endpoint_line.split("=")[1].strip()
|
|
||||||
|
|
||||||
# Speichere im Dictionary
|
|
||||||
data[file.stem] = {
|
|
||||||
"Address": address,
|
|
||||||
"DNS": dns,
|
|
||||||
"Endpoint": endpoint,
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
# Ignore errors and continue to the next file
|
|
||||||
continue
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]:
|
|
||||||
"""
|
|
||||||
Returns tuple of (address, dns, endpoint, pre_key)
|
|
||||||
"""
|
|
||||||
|
|
||||||
dictlist: List[str] = []
|
|
||||||
for lines in file.readlines():
|
|
||||||
line_plit: List[str] = lines.split()
|
|
||||||
dictlist = dictlist + line_plit
|
|
||||||
dictlist.remove("[Interface]")
|
|
||||||
dictlist.remove("[Peer]")
|
|
||||||
for items in dictlist:
|
|
||||||
if items == "=":
|
|
||||||
dictlist.remove(items)
|
|
||||||
if items == "::/0":
|
|
||||||
dictlist.remove(items)
|
|
||||||
|
|
||||||
# Here is the beginning (Loop) of convert List to Dictionary
|
|
||||||
for _ in dictlist:
|
|
||||||
a: List[str] = [dictlist[0], dictlist[1]]
|
|
||||||
b: List[str] = [dictlist[2], dictlist[3]]
|
|
||||||
c: List[str] = [dictlist[4], dictlist[5]]
|
|
||||||
d: List[str] = [dictlist[6], dictlist[7]]
|
|
||||||
e: List[str] = [dictlist[8], dictlist[9]]
|
|
||||||
f: List[str] = [dictlist[10], dictlist[11]]
|
|
||||||
g: List[str] = [dictlist[12], dictlist[13]]
|
|
||||||
h: List[str] = [dictlist[14], dictlist[15]]
|
|
||||||
new_list: List[List[str]] = [a, b, c, d, e, f, g, h]
|
|
||||||
final_dict: Dict[str, str] = {}
|
|
||||||
for elements in new_list:
|
|
||||||
final_dict[elements[0]] = elements[1]
|
|
||||||
|
|
||||||
# end... result a Dictionary
|
|
||||||
|
|
||||||
address: str = final_dict["Address"]
|
|
||||||
dns: str = final_dict["DNS"]
|
|
||||||
if "," in dns:
|
|
||||||
dns = dns[:-1]
|
|
||||||
endpoint: str = final_dict["Endpoint"]
|
|
||||||
pre_key: Optional[str] = final_dict.get("PresharedKey")
|
|
||||||
if pre_key is None:
|
|
||||||
pre_key: Optional[str] = final_dict.get("PreSharedKey")
|
|
||||||
return address, dns, endpoint, pre_key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def active() -> str:
|
|
||||||
"""
|
|
||||||
Shows the Active Tunnel
|
|
||||||
"""
|
|
||||||
active = (
|
|
||||||
os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"')
|
|
||||||
.read()
|
|
||||||
.split()
|
|
||||||
)
|
|
||||||
if not active:
|
|
||||||
active = ""
|
|
||||||
else:
|
|
||||||
active = active[0]
|
|
||||||
|
|
||||||
return active
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def list() -> List[str]:
|
|
||||||
"""
|
|
||||||
Returns a list of Wireguard tunnel names
|
|
||||||
"""
|
|
||||||
AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/")
|
|
||||||
wg_s: List[str] = os.listdir(AppConfig.TEMP_DIR)
|
|
||||||
|
|
||||||
return wg_s
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def export(
|
|
||||||
image_path: Path = None,
|
|
||||||
image_path2: Path = None,
|
|
||||||
image_path3: Path = None,
|
|
||||||
image_path4: Path = None,
|
|
||||||
title: Dict = None,
|
|
||||||
window_msg: Dict = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
This will export the tunnels.
|
|
||||||
A zipfile with the current date and time is created
|
|
||||||
in the user's home directory with the correct right
|
|
||||||
Args:
|
|
||||||
AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text
|
|
||||||
AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon
|
|
||||||
AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon
|
|
||||||
"""
|
|
||||||
now_time: datetime = datetime.now()
|
|
||||||
now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M")
|
|
||||||
tl: List[str] = Tunnel.list()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if len(tl) != 0:
|
|
||||||
wg_tar: str = f"{Path.home()}/{now_datetime}"
|
|
||||||
shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True)
|
|
||||||
source: Path = Path("/tmp/wire_py")
|
|
||||||
shutil.make_archive(wg_tar, "zip", source)
|
|
||||||
shutil.rmtree(source)
|
|
||||||
with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
|
|
||||||
if len(zf.namelist()) != 0:
|
|
||||||
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_info"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_vpn"],
|
|
||||||
Msg.STR["exp_succ"],
|
|
||||||
Msg.STR["exp_in_home"],
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_error"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"],
|
|
||||||
Msg.STR["exp_err"],
|
|
||||||
Msg.STR["exp_try"],
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_info"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"],
|
|
||||||
Msg.STR["sel_tl"],
|
|
||||||
Msg.STR["tl_first"],
|
|
||||||
)
|
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# ConfigManager with caching
|
|
||||||
class ConfigManager:
|
|
||||||
"""
|
|
||||||
Universal class for managing configuration files with caching.
|
|
||||||
Can be reused in different projects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_config = None
|
|
||||||
_config_file = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def init(cls, config_file):
|
|
||||||
"""Initial the Configmanager with the given config file"""
|
|
||||||
cls._config_file = config_file
|
|
||||||
cls._config = None # Reset the cache
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls):
|
|
||||||
"""Load the config file and return the config as dict"""
|
|
||||||
if not cls._config:
|
|
||||||
try:
|
|
||||||
lines = Path(cls._config_file).read_text(encoding="utf-8").splitlines()
|
|
||||||
cls._config = {
|
|
||||||
"updates": lines[1].strip(),
|
|
||||||
"theme": lines[3].strip(),
|
|
||||||
"tooltips": lines[5].strip()
|
|
||||||
== "True", # is converted here to boolean!!!
|
|
||||||
"autostart": lines[7].strip() if len(lines) > 7 else "off",
|
|
||||||
}
|
|
||||||
except (IndexError, FileNotFoundError):
|
|
||||||
# DeDefault values in case of error
|
|
||||||
cls._config = {
|
|
||||||
"updates": "on",
|
|
||||||
"theme": "light",
|
|
||||||
"tooltips": "True", # Default Value as string !
|
|
||||||
"autostart": "off",
|
|
||||||
}
|
|
||||||
return cls._config
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def save(cls):
|
|
||||||
"""Save the config to the config file"""
|
|
||||||
if cls._config:
|
|
||||||
lines = [
|
|
||||||
"# Configuration\n",
|
|
||||||
f"{cls._config['updates']}\n",
|
|
||||||
"# Theme\n",
|
|
||||||
f"{cls._config['theme']}\n",
|
|
||||||
"# Tooltips\n",
|
|
||||||
f"{str(cls._config['tooltips'])}\n",
|
|
||||||
"# Autostart\n",
|
|
||||||
f"{cls._config['autostart']}\n",
|
|
||||||
]
|
|
||||||
Path(cls._config_file).write_text("".join(lines), encoding="utf-8")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set(cls, key, value):
|
|
||||||
"""Sets a configuration value and saves the change"""
|
|
||||||
cls.load()
|
|
||||||
cls._config[key] = value
|
|
||||||
cls.save()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, key, default=None):
|
|
||||||
"""Returns a configuration value"""
|
|
||||||
config = cls.load()
|
|
||||||
return config.get(key, default)
|
|
||||||
|
|
||||||
|
|
||||||
class ThemeManager:
|
|
||||||
@staticmethod
|
|
||||||
def change_theme(root, theme_in_use, theme_name=None):
|
|
||||||
"""Change application theme centrally"""
|
|
||||||
root.tk.call("set_theme", theme_in_use)
|
|
||||||
if theme_in_use == theme_name:
|
|
||||||
ConfigManager.set("theme", theme_in_use)
|
|
||||||
|
|
||||||
|
|
||||||
class GiteaUpdate:
|
|
||||||
"""
|
|
||||||
Calling download requests the download URL of the running script,
|
|
||||||
the taskbar image for the “Download OK” window, the taskbar image for the
|
|
||||||
“Download error” window and the variable res
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def api_down(update_api_url: str, version: str, update_setting: str = None) -> str:
|
|
||||||
"""
|
|
||||||
Checks for updates via API
|
|
||||||
|
|
||||||
Args:
|
|
||||||
update_api_url: Update API URL
|
|
||||||
version: Current version
|
|
||||||
update_setting: Update setting from ConfigManager (on/off)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
New version or status message
|
|
||||||
"""
|
|
||||||
# If updates are disabled, return immediately
|
|
||||||
if update_setting != "on":
|
|
||||||
return "False"
|
|
||||||
|
|
||||||
try:
|
|
||||||
response: requests.Response = requests.get(update_api_url, timeout=10)
|
|
||||||
response.raise_for_status() # Raise exception for HTTP errors
|
|
||||||
|
|
||||||
response_data = response.json()
|
|
||||||
if not response_data:
|
|
||||||
return "No Updates"
|
|
||||||
|
|
||||||
latest_version = response_data[0].get("tag_name")
|
|
||||||
if not latest_version:
|
|
||||||
return "Invalid API Response"
|
|
||||||
|
|
||||||
# Compare versions (strip 'v. ' prefix if present)
|
|
||||||
current_version = version[3:] if version.startswith("v. ") else version
|
|
||||||
|
|
||||||
if current_version != latest_version:
|
|
||||||
return latest_version
|
|
||||||
else:
|
|
||||||
return "No Updates"
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException:
|
|
||||||
return "No Internet Connection!"
|
|
||||||
except (ValueError, KeyError, IndexError):
|
|
||||||
return "Invalid API Response"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def download(
|
|
||||||
urld: str,
|
|
||||||
res: str,
|
|
||||||
image_path: Path = None,
|
|
||||||
image_path2: Path = None,
|
|
||||||
image_path3: Path = None,
|
|
||||||
image_path4: Path = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Downloads new version of wirepy
|
|
||||||
|
|
||||||
Args:
|
|
||||||
urld: Download URL
|
|
||||||
res: Result filename
|
|
||||||
AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text
|
|
||||||
AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon
|
|
||||||
AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
to_down: str = f"wget -qP {Path.home()} {" "} {urld}"
|
|
||||||
result: int = subprocess.call(to_down, shell=True)
|
|
||||||
if result == 0:
|
|
||||||
shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000)
|
|
||||||
|
|
||||||
wt: str = _("Download Successful")
|
|
||||||
msg_t: str = _("Your zip file is in home directory")
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_info"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_vpn"],
|
|
||||||
wt,
|
|
||||||
msg_t,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
wt: str = _("Download error")
|
|
||||||
msg_t: str = _("Download failed! Please try again")
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_error"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"],
|
|
||||||
wt,
|
|
||||||
msg_t,
|
|
||||||
)
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
|
|
||||||
wt: str = _("Download error")
|
|
||||||
msg_t: str = _("Download failed! No internet connection!")
|
|
||||||
LxTools.msg_window(
|
|
||||||
AppConfig.IMAGE_PATHS["icon_error"],
|
|
||||||
AppConfig.IMAGE_PATHS["icon_msg"],
|
|
||||||
wt,
|
|
||||||
msg_t,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Tooltip:
|
|
||||||
"""Class for Tooltip
|
|
||||||
from common_tools.py import Tooltip
|
|
||||||
example: Tooltip(label, "Show tooltip on label")
|
|
||||||
example: Tooltip(button, "Show tooltip on button")
|
|
||||||
example: Tooltip(widget, "Text", state_var=tk.BooleanVar())
|
|
||||||
example: Tooltip(widget, "Text", state_var=tk.BooleanVar(), x_offset=20, y_offset=10)
|
|
||||||
|
|
||||||
info: label and button are parent widgets.
|
|
||||||
NOTE: When using with state_var, pass the tk.BooleanVar object directly,
|
|
||||||
NOT its value. For example: use state_var=my_bool_var, NOT state_var=my_bool_var.get()
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
widget: Any,
|
|
||||||
text: str,
|
|
||||||
state_var: Optional[tk.BooleanVar] = None,
|
|
||||||
x_offset: int = 65,
|
|
||||||
y_offset: int = 40,
|
|
||||||
) -> None:
|
|
||||||
"""Tooltip Class"""
|
|
||||||
self.widget: Any = widget
|
|
||||||
self.text: str = text
|
|
||||||
self.tooltip_window: Optional[Toplevel] = None
|
|
||||||
self.state_var = state_var
|
|
||||||
self.x_offset = x_offset
|
|
||||||
self.y_offset = y_offset
|
|
||||||
|
|
||||||
# Initial binding based on current state
|
|
||||||
self.update_bindings()
|
|
||||||
|
|
||||||
# Add trace to the state_var if provided
|
|
||||||
if self.state_var is not None:
|
|
||||||
self.state_var.trace_add("write", self.update_bindings)
|
|
||||||
|
|
||||||
def update_bindings(self, *args) -> None:
|
|
||||||
"""Updates the bindings based on the current state"""
|
|
||||||
# Remove existing bindings first
|
|
||||||
self.widget.unbind("<Enter>")
|
|
||||||
self.widget.unbind("<Leave>")
|
|
||||||
|
|
||||||
# Add new bindings if tooltips are enabled
|
|
||||||
if self.state_var is None or self.state_var.get():
|
|
||||||
self.widget.bind("<Enter>", self.show_tooltip)
|
|
||||||
self.widget.bind("<Leave>", self.hide_tooltip)
|
|
||||||
|
|
||||||
def show_tooltip(self, event: Optional[Any] = None) -> None:
|
|
||||||
"""Shows the tooltip"""
|
|
||||||
if self.tooltip_window or not self.text:
|
|
||||||
return
|
|
||||||
|
|
||||||
x: int
|
|
||||||
y: int
|
|
||||||
cx: int
|
|
||||||
cy: int
|
|
||||||
|
|
||||||
x, y, cx, cy = self.widget.bbox("insert")
|
|
||||||
x += self.widget.winfo_rootx() + self.x_offset
|
|
||||||
y += self.widget.winfo_rooty() + self.y_offset
|
|
||||||
|
|
||||||
self.tooltip_window = tw = tk.Toplevel(self.widget)
|
|
||||||
tw.wm_overrideredirect(True)
|
|
||||||
tw.wm_geometry(f"+{x}+{y}")
|
|
||||||
|
|
||||||
label: tk.Label = tk.Label(
|
|
||||||
tw,
|
|
||||||
text=self.text,
|
|
||||||
background="lightgreen",
|
|
||||||
foreground="black",
|
|
||||||
relief="solid",
|
|
||||||
borderwidth=1,
|
|
||||||
padx=5,
|
|
||||||
pady=5,
|
|
||||||
)
|
|
||||||
label.grid()
|
|
||||||
|
|
||||||
self.tooltip_window.after(2200, lambda: tw.destroy())
|
|
||||||
|
|
||||||
def hide_tooltip(self, event: Optional[Any] = None) -> None:
|
|
||||||
"""Hides the tooltip"""
|
|
||||||
if self.tooltip_window:
|
|
||||||
self.tooltip_window.destroy()
|
|
||||||
self.tooltip_window = None
|
|
228
install
@@ -1,228 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
NORMAL='\033[0m'
|
|
||||||
GREEN='\033[1;32m'
|
|
||||||
RED='\033[31;1;42m'
|
|
||||||
BLUE='\033[30;1;34m'
|
|
||||||
|
|
||||||
install_file_with(){
|
|
||||||
clear
|
|
||||||
mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
|
|
||||||
mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
|
|
||||||
systemctl --user enable wg_start.service >/dev/null 2>&1
|
|
||||||
sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
systemctl --user disable wg_start.service
|
|
||||||
rm -r ~/.config/wire_py && rm -r ~/.config/systemd
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
sudo apt install python3-tk && \
|
|
||||||
sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
|
|
||||||
sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
|
|
||||||
sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
|
|
||||||
sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
|
|
||||||
sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
||||||
sudo mkdir -p /usr/local/etc/ssl
|
|
||||||
if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
|
|
||||||
then
|
|
||||||
sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_arch_d(){
|
|
||||||
clear
|
|
||||||
mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
|
|
||||||
mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
|
|
||||||
systemctl --user enable wg_start.service >/dev/null 2>&1
|
|
||||||
sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
systemctl --user disable wg_start.service
|
|
||||||
rm -r ~/.config/wire_py && rm -r ~/.config/systemd
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
sudo pacman -S --noconfirm tk python3 python-requests && \
|
|
||||||
sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
|
|
||||||
sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
|
|
||||||
sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
|
|
||||||
sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
|
|
||||||
sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
||||||
sudo mkdir -p /usr/local/etc/ssl
|
|
||||||
if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
|
|
||||||
then
|
|
||||||
sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install(){
|
|
||||||
if grep -i 'debian' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
groups > /tmp/isgroup
|
|
||||||
if grep 'sudo' /tmp/isgroup
|
|
||||||
then
|
|
||||||
install_file_with
|
|
||||||
else
|
|
||||||
echo -e "$BLUE"The installer found that they are not in the group sudo.""
|
|
||||||
echo -e "with "$RED"su -"$BLUE" "they can enter the root shell in which they then""
|
|
||||||
echo -e "enter "$GREEN""usermod -aG sudo $USER.""$BLUE""
|
|
||||||
echo -e ""after logging in from the system, they can then run Wire-Py install again." $NORMAL"
|
|
||||||
read -n 1 -s -r -p $"Press Enter to exit"
|
|
||||||
clear
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
elif grep -i 'mint\|ubuntu\|pop|' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
install_file_with
|
|
||||||
|
|
||||||
elif grep -i 'arch' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
groups > /tmp/isgroup
|
|
||||||
clear
|
|
||||||
if grep 'wheel' /tmp/isgroup
|
|
||||||
then
|
|
||||||
install_arch_d
|
|
||||||
else
|
|
||||||
echo "The installer found that they are not in the group sudo."
|
|
||||||
echo "The sudoers file must be edited with"
|
|
||||||
echo -e "$RED""su -""$NORMAL"
|
|
||||||
echo -e "$GREEN"""EDITOR=nano visudo"""$NORMAL"
|
|
||||||
echo "Find the line:"
|
|
||||||
echo "## Uncomment to allow members of group wheel to execute any command"
|
|
||||||
echo "remove '#' on # %wheel ALL=(ALL) ALL and save the file"
|
|
||||||
echo -e "then enter "$GREEN"gpasswd -a $USER wheel.""$NORMAL"
|
|
||||||
echo "after logging in from the system, they can then run Wire-Py install again."
|
|
||||||
read -n 1 -s -r -p $"Press Enter to exit"
|
|
||||||
clear
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
elif grep -i '|manjaro\|garuda\|endeavour|' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
install_arch_d
|
|
||||||
|
|
||||||
elif grep -i 'fedora' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
clear
|
|
||||||
mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
|
|
||||||
mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
|
|
||||||
systemctl --user enable wg_start.service >/dev/null 2>&1
|
|
||||||
sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
systemctl --user disable wg_start.service
|
|
||||||
rm -r ~/.config/wire_py && rm -r ~/.config/systemd
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
sudo dnf install python3-tkinter -y
|
|
||||||
sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
|
|
||||||
sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
|
|
||||||
sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
|
|
||||||
sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
|
|
||||||
sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
||||||
sudo mkdir -p /usr/local/etc/ssl
|
|
||||||
if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
|
|
||||||
then
|
|
||||||
sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
elif grep -i 'suse' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
clear
|
|
||||||
mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
|
|
||||||
mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
|
|
||||||
systemctl --user enable wg_start.service >/dev/null 2>&1
|
|
||||||
sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
systemctl --user disable wg_start.service
|
|
||||||
rm -r ~/.config/wire_py && rm -r ~/.config/systemd
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
|
|
||||||
sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
|
|
||||||
sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
|
|
||||||
sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
|
|
||||||
sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
||||||
sudo mkdir -p /usr/local/etc/ssl
|
|
||||||
if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
|
|
||||||
then
|
|
||||||
sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
|
|
||||||
fi
|
|
||||||
if grep -i 'Tumbleweed' /etc/os-release > /dev/null 2>&1
|
|
||||||
then
|
|
||||||
sudo zypper install python313-tk
|
|
||||||
else
|
|
||||||
sudo zypper install python36-tk
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
clear
|
|
||||||
echo $"Your System could not be determined."
|
|
||||||
echo
|
|
||||||
read -n 1 -s -r -p $"Press Enter to exit"
|
|
||||||
clear
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
fi
|
|
||||||
#clear
|
|
||||||
read -n 1 -s -r -p $"Press Enter to exit"
|
|
||||||
clear
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(){
|
|
||||||
sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \
|
|
||||||
/usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
systemctl --user disable wg_start.service
|
|
||||||
rm -r ~/.config/wire_py && rm -r ~/.config/systemd
|
|
||||||
sudo rm /usr/share/applications/Wire-Py.desktop
|
|
||||||
sudo rm /usr/share/locale/de/LC_MESSAGES/languages/de/wirepy.mo
|
|
||||||
sudo rm -r /usr/local/etc/ssl
|
|
||||||
which syncpy >/dev/null
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
sudo rm -r /usr/share/icons/lx-icons && sudo rm -r /usr/share/TK-Themes
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
read -p "Press Enter to exit..."
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
which wirepy >/dev/null
|
|
||||||
if [ $? -eq 0 ]
|
|
||||||
then
|
|
||||||
echo "Do you want to update/reinstall or uninstall wirepy?"
|
|
||||||
echo
|
|
||||||
echo "Update/reinstall: press y, uninstall press r"
|
|
||||||
echo
|
|
||||||
read -n 1 -s -r -p "Cancel with any other key..." result
|
|
||||||
case $result in
|
|
||||||
[y]* ) clear; install; exit;;
|
|
||||||
[Y]* ) clear; install; exit;;
|
|
||||||
[j]* ) clear; install; exit;;
|
|
||||||
[J]* ) clear; install; exit;;
|
|
||||||
[r]* ) clear; remove; exit;;
|
|
||||||
[R]* ) clear; remove; exit;;
|
|
||||||
esac
|
|
||||||
clear
|
|
||||||
else
|
|
||||||
install
|
|
||||||
|
|
||||||
fi
|
|
13
logger.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class Logger:
|
||||||
|
def __init__(self):
|
||||||
|
self._log_func = print # Default to print if not initialized
|
||||||
|
|
||||||
|
def init_logger(self, log_func):
|
||||||
|
self._log_func = log_func
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
self._log_func(message)
|
||||||
|
|
||||||
|
|
||||||
|
# Global instance
|
||||||
|
app_logger = Logger()
|
BIN
lx-icons/128/Lunix_Tools.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
lx-icons/128/download.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
lx-icons/128/download_error.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
lx-icons/128/log.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
lx-icons/128/question_mark.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
lx-icons/128/settings-2.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
lx-icons/128/settings.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
lx-icons/128/warning.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 10 KiB |
BIN
lx-icons/16/settings.png
Normal file
After Width: | Height: | Size: 697 B |
BIN
lx-icons/256/Lunix_Tools.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
lx-icons/256/download.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
lx-icons/256/download_error.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.8 KiB |
BIN
lx-icons/256/log.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
lx-icons/256/question_mark.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
lx-icons/256/settings-2.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
lx-icons/256/settings.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
lx-icons/256/trash.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
lx-icons/256/warning.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
BIN
lx-icons/32/Lunix_Tools.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
lx-icons/32/arrow-left.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
lx-icons/32/arrow-right.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
lx-icons/32/arrow-up.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
lx-icons/32/audio.png
Normal file
After Width: | Height: | Size: 969 B |
BIN
lx-icons/32/carrel.png
Normal file
After Width: | Height: | Size: 665 B |
BIN
lx-icons/32/computer.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/device.png
Normal file
After Width: | Height: | Size: 1014 B |
BIN
lx-icons/32/document.png
Normal file
After Width: | Height: | Size: 524 B |
BIN
lx-icons/32/download.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
lx-icons/32/download_error.png
Normal file
After Width: | Height: | Size: 657 B |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
lx-icons/32/file-python.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
lx-icons/32/folder-water-documents.png
Normal file
After Width: | Height: | Size: 939 B |
BIN
lx-icons/32/folder-water-download.png
Normal file
After Width: | Height: | Size: 995 B |
BIN
lx-icons/32/folder-water-music.png
Normal file
After Width: | Height: | Size: 1021 B |
BIN
lx-icons/32/folder-water-pictures.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
lx-icons/32/folder-water-video.png
Normal file
After Width: | Height: | Size: 971 B |
BIN
lx-icons/32/folder-water.png
Normal file
After Width: | Height: | Size: 766 B |
BIN
lx-icons/32/hide.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/home.png
Normal file
After Width: | Height: | Size: 546 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
lx-icons/32/list.png
Normal file
After Width: | Height: | Size: 801 B |
BIN
lx-icons/32/log.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/lxtools_key.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
lx-icons/32/media-optical.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
lx-icons/32/new-document.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/new-folder.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/pdf.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
lx-icons/32/picture.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
lx-icons/32/question_mark.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
lx-icons/32/recursive.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/search.png
Normal file
After Width: | Height: | Size: 978 B |
BIN
lx-icons/32/settings-2.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
lx-icons/32/settings.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
lx-icons/32/tar.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
lx-icons/32/trash.png
Normal file
After Width: | Height: | Size: 1.0 KiB |