diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0b46ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +debug.log +.venv +.venv.bak +.idea +.vscode +__pycache__ diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..1df4d59 --- /dev/null +++ b/Changelog @@ -0,0 +1,11 @@ +Changelog for LXTools installer + +## [Unreleased] + + - + + ### Added +4-06-2025 + + - Create LXTools installer for simple install Apps by git.ilunix.de + diff --git a/TK-Themes/LICENSE b/TK-Themes/LICENSE new file mode 100644 index 0000000..0212030 --- /dev/null +++ b/TK-Themes/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 rdbende + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/TK-Themes/theme/dark.tcl b/TK-Themes/theme/dark.tcl new file mode 100644 index 0000000..2ad09db --- /dev/null +++ b/TK-Themes/theme/dark.tcl @@ -0,0 +1,539 @@ +# Copyright (c) 2021 rdbende + +# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024 + +package require Tk 8.6 + +namespace eval ttk::theme::water-dark { + variable version 2.0 + package provide ttk::theme::water-dark $version + + ttk::style theme create water-dark -parent clam -settings { + proc load_images {imgdir} { + variable I + foreach file [glob -directory $imgdir *.png] { + set img [file tail [file rootname $file]] + set I($img) [image create photo -file $file -format png] + } + } + + load_images [file join [file dirname [info script]] dark] + + array set colors { + -fg "#ffffff" + -bg "#333333" + -disabledfg "#aaaaaa" + -disabledbg "#737373" + -selectfg "#ffffff" + -selectbg "#007fff" + } + + ttk::style layout TButton { + Button.button -children { + Button.padding -children { + Button.label -side left -expand true + } + } + } + + ttk::style layout Toolbutton { + Toolbutton.button -children { + Toolbutton.padding -children { + Toolbutton.label -side left -expand true + } + } + } + + ttk::style layout TMenubutton { + Menubutton.button -children { + Menubutton.padding -children { + Menubutton.indicator -side right + Menubutton.label -side right -expand true + } + } + } + + ttk::style layout TOptionMenu { + OptionMenu.button -children { + OptionMenu.padding -children { + OptionMenu.indicator -side right + OptionMenu.label -side right -expand true + } + } + } + + ttk::style layout Accent.TButton { + AccentButton.button -children { + AccentButton.padding -children { + AccentButton.label -side left -expand true + } + } + } + + ttk::style layout TCheckbutton { + Checkbutton.button -children { + Checkbutton.padding -children { + Checkbutton.indicator -side left + Checkbutton.label -side right -expand true + } + } + } + + ttk::style layout Switch.TCheckbutton { + Switch.button -children { + Switch.padding -children { + Switch.indicator -side left + Switch.label -side right -expand true + } + } + } + + ttk::style layout Toggle.TButton { + ToggleButton.button -children { + ToggleButton.padding -children { + ToggleButton.label -side left -expand true + } + } + } + + ttk::style layout TRadiobutton { + Radiobutton.button -children { + Radiobutton.padding -children { + Radiobutton.indicator -side left + Radiobutton.label -side right -expand true + } + } + } + + ttk::style layout Vertical.TScrollbar { + Vertical.Scrollbar.trough -sticky ns -children { + Vertical.Scrollbar.thumb -expand true + } + } + + ttk::style layout Horizontal.TScrollbar { + Horizontal.Scrollbar.trough -sticky ew -children { + Horizontal.Scrollbar.thumb -expand true + } + } + + ttk::style layout TCombobox { + Combobox.field -sticky nswe -children { + Combobox.padding -expand true -sticky nswe -children { + Combobox.textarea -sticky nswe + } + } + Combobox.button -side right -sticky ns -children { + Combobox.arrow -sticky nsew + } + } + + ttk::style layout TSpinbox { + Spinbox.field -sticky nsew -children { + Spinbox.padding -expand true -sticky nswe -children { + Spinbox.textarea -sticky nswe + } + + } + Spinbox.button -side right -sticky ns -children { + null -side right -children { + Spinbox.uparrow -side top + Spinbox.downarrow -side bottom + } + } + } + + ttk::style layout Horizontal.TSeparator { + Horizontal.separator -sticky nswe + } + + ttk::style layout Vertical.TSeparator { + Vertical.separator -sticky nswe + } + + ttk::style layout Horizontal.Tick.TScale { + Horizontal.TickScale.trough -sticky ew -children { + Horizontal.TickScale.slider -sticky w + } + } + + ttk::style layout Vertical.Tick.TScale { + Vertical.TickScale.trough -sticky ns -children { + Vertical.TickScale.slider -sticky n + } + } + + ttk::style layout Card.TFrame { + Card.field { + Card.padding -expand 1 + } + } + + ttk::style layout TLabelframe { + Labelframe.border { + Labelframe.padding -expand 1 -children { + Labelframe.label -side right + } + } + } + + ttk::style layout TNotebook.Tab { + Notebook.tab -children { + Notebook.padding -side top -children { + Notebook.label -side top -sticky {} + } + } + } + + ttk::style layout Treeview.Item { + Treeitem.padding -sticky nswe -children { + Treeitem.indicator -side left -sticky {} + Treeitem.image -side left -sticky {} + Treeitem.text -side left -sticky {} + } + } + + + # Elements + + # Button + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center + + 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(button-hover) \ + ] -border 4 -sticky ewns + + # Toolbutton + ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center + + ttk::style element create Toolbutton.button image \ + [list $I(empty) \ + {selected disabled} $I(empty) \ + disabled $I(empty) \ + pressed $I(rect-basic) \ + selected $I(rect-basic) \ + active $I(rect-basic) \ + ] -border 4 -sticky ewns + + # Menubutton + ttk::style configure TMenubutton -padding {8 4 4 4} + + ttk::style element create Menubutton.button \ + image [list $I(rect-basic) \ + disabled $I(rect-basic) \ + pressed $I(rect-basic) \ + active $I(button-hover) \ + ] -border 4 -sticky ewns + + ttk::style element create Menubutton.indicator \ + image [list $I(down) \ + active $I(down) \ + pressed $I(down) \ + disabled $I(down) \ + ] -width 15 -sticky e + + # OptionMenu + ttk::style configure TOptionMenu -padding {8 4 4 4} + + ttk::style element create OptionMenu.button \ + image [list $I(rect-basic) \ + disabled $I(rect-basic) \ + pressed $I(rect-basic) \ + active $I(button-hover) \ + ] -border 4 -sticky ewns + + ttk::style element create OptionMenu.indicator \ + image [list $I(down) \ + active $I(down) \ + pressed $I(down) \ + disabled $I(down) \ + ] -width 15 -sticky e + + # AccentButton + ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style element create AccentButton.button image \ + [list $I(rect-accent) \ + {selected disabled} $I(rect-accent-hover) \ + disabled $I(rect-accent-hover) \ + pressed $I(rect-accent) \ + selected $I(rect-accent) \ + active $I(rect-accent-hover) \ + focus $I(rect-accent-hover) \ + ] -border 4 -sticky ewns + + # Checkbutton + ttk::style configure TCheckbutton -padding 4 + + ttk::style element create Checkbutton.indicator image \ + [list $I(box-basic) \ + {alternate disabled} $I(check-tri-basic) \ + {selected disabled} $I(check-basic) \ + disabled $I(box-basic) \ + {pressed alternate} $I(check-tri-hover) \ + {active alternate} $I(check-tri-hover) \ + alternate $I(check-tri-accent) \ + {pressed selected} $I(check-hover) \ + {active selected} $I(check-hover) \ + selected $I(check-accent) \ + {pressed !selected} $I(rect-hover) \ + active $I(box-hover) \ + ] -width 26 -sticky w + + # Switch + ttk::style element create Switch.indicator image \ + [list $I(off-basic) \ + {selected disabled} $I(on-basic) \ + disabled $I(off-basic) \ + {pressed selected} $I(on-accent) \ + {active selected} $I(on-accent) \ + selected $I(on-accent) \ + {pressed !selected} $I(off-basic) \ + active $I(off-basic) \ + ] -width 46 -sticky w + + # ToggleButton + ttk::style configure Toggle.TButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style element create ToggleButton.button image \ + [list $I(rect-basic) \ + {selected disabled} $I(rect-accent-hover) \ + disabled $I(rect-basic) \ + {pressed selected} $I(rect-basic) \ + {active selected} $I(rect-accent) \ + selected $I(rect-accent) \ + {pressed !selected} $I(rect-accent) \ + active $I(rect-basic) \ + ] -border 4 -sticky ewns + + # Radiobutton + ttk::style configure TRadiobutton -padding 4 + + ttk::style element create Radiobutton.indicator image \ + [list $I(outline-basic) \ + {alternate disabled} $I(radio-tri-basic) \ + {selected disabled} $I(radio-basic) \ + disabled $I(outline-basic) \ + {pressed alternate} $I(radio-tri-hover) \ + {active alternate} $I(radio-tri-hover) \ + alternate $I(radio-tri-accent) \ + {pressed selected} $I(radio-hover) \ + {active selected} $I(radio-hover) \ + selected $I(radio-accent) \ + {pressed !selected} $I(circle-hover) \ + active $I(outline-hover) \ + ] -width 26 -sticky w + + # Scrollbar + ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \ + -sticky ew + + ttk::style element create Horizontal.Scrollbar.thumb \ + image [list $I(hor-accent) \ + disabled $I(hor-basic) \ + pressed $I(hor-hover) \ + active $I(hor-hover) \ + ] -sticky ew + + ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \ + -sticky ns + + ttk::style element create Vertical.Scrollbar.thumb \ + image [list $I(vert-accent) \ + disabled $I(vert-basic) \ + pressed $I(vert-hover) \ + active $I(vert-hover) \ + ] -sticky ns + + # Scale + ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \ + -border 5 -padding 0 + + ttk::style element create Horizontal.Scale.slider \ + image [list $I(circle-accent) \ + disabled $I(circle-basic) \ + pressed $I(circle-hover) \ + active $I(circle-hover) \ + ] -sticky {} + + ttk::style element create Vertical.Scale.trough image $I(scale-vert) \ + -border 5 -padding 0 + + ttk::style element create Vertical.Scale.slider \ + image [list $I(circle-accent) \ + disabled $I(circle-basic) \ + pressed $I(circle-hover) \ + active $I(circle-hover) \ + ] -sticky {} + + # Tickscale + ttk::style element create Horizontal.TickScale.trough image $I(scale-hor) \ + -border 5 -padding 0 + + ttk::style element create Horizontal.TickScale.slider \ + image [list $I(tick-hor-accent) \ + disabled $I(tick-hor-basic) \ + pressed $I(tick-hor-hover) \ + active $I(tick-hor-hover) \ + ] -sticky {} + + ttk::style element create Vertical.TickScale.trough image $I(scale-vert) \ + -border 5 -padding 0 + + ttk::style element create Vertical.TickScale.slider \ + image [list $I(tick-vert-accent) \ + disabled $I(tick-vert-basic) \ + pressed $I(tick-vert-hover) \ + active $I(tick-vert-hover) \ + ] -sticky {} + + # Progressbar + ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \ + -sticky ew + + ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \ + -sticky ew + + ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \ + -sticky ns + + ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \ + -sticky ns + + # Entry + ttk::style element create Entry.field \ + image [list $I(box-basic) \ + {focus hover} $I(box-accent) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} -sticky news + + # Combobox + ttk::style map TCombobox -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg) \ + ] + + ttk::style map TCombobox -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg) \ + ] + + ttk::style element create Combobox.field \ + image [list $I(box-basic) \ + {readonly disabled} $I(rect-basic) \ + {readonly pressed} $I(rect-basic) \ + {readonly focus hover} $I(button-hover) \ + {readonly focus} $I(button-hover) \ + {readonly hover} $I(button-hover) \ + {focus hover} $I(box-accent) \ + readonly $I(rect-basic) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} + + ttk::style element create Combobox.button \ + image [list $I(combo-button-basic) \ + {!readonly focus} $I(combo-button-focus) \ + {readonly focus} $I(combo-button-hover) \ + {readonly hover} $I(combo-button-hover) + ] -border 5 -padding {2 6 6 6} + + ttk::style element create Combobox.arrow image $I(down) \ + -width 15 -sticky e + + # Spinbox + ttk::style element create Spinbox.field \ + image [list $I(box-basic) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} -sticky news + + ttk::style element create Spinbox.uparrow \ + image [list $I(up) \ + disabled $I(up) \ + pressed $I(up-accent) \ + active $I(up-accent) \ + ] -border 4 -width 15 -sticky e + + ttk::style element create Spinbox.downarrow \ + image [list $I(down) \ + disabled $I(down) \ + pressed $I(down-accent) \ + active $I(down-accent) \ + ] -border 4 -width 15 -sticky e + + ttk::style element create Spinbox.button \ + image [list $I(combo-button-basic) \ + {!readonly focus} $I(combo-button-focus) \ + {readonly focus} $I(combo-button-hover) \ + {readonly hover} $I(combo-button-hover) + ] -border 5 -padding {2 6 6 6} + + # Sizegrip + ttk::style element create Sizegrip.sizegrip image $I(size) \ + -sticky ewns + + # Separator + ttk::style element create Horizontal.separator image $I(separator) + + ttk::style element create Vertical.separator image $I(separator) + + # Card + ttk::style element create Card.field image $I(card) \ + -border 10 -padding 4 -sticky news + + # Labelframe + ttk::style element create Labelframe.border image $I(card) \ + -border 5 -padding 4 -sticky news + + # Notebook + ttk::style element create Notebook.client \ + image $I(notebook) -border 5 + + ttk::style element create Notebook.tab \ + image [list $I(tab-disabled) \ + selected $I(tab-basic) \ + active $I(tab-hover) \ + ] -border 5 -padding {14 4} + + # Treeview + ttk::style element create Treeview.field image $I(card) \ + -border 5 + + ttk::style element create Treeheading.cell \ + image [list $I(tree-basic) \ + pressed $I(tree-pressed) + ] -border 5 -padding 4 -sticky ewns + + ttk::style element create Treeitem.indicator \ + image [list $I(right) \ + user2 $I(empty) \ + user1 $I(down) \ + ] -width 26 -sticky {} + + ttk::style configure Treeview -background $colors(-bg) + ttk::style configure Treeview.Item -padding {2 0 0 0} + ttk::style map Treeview \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] + + # Panedwindow + # Insane hack to remove clam's ugly sash + ttk::style configure Sash -gripcount 0 + } +} diff --git a/TK-Themes/theme/dark/box-accent.png b/TK-Themes/theme/dark/box-accent.png new file mode 100644 index 0000000..767a81b Binary files /dev/null and b/TK-Themes/theme/dark/box-accent.png differ diff --git a/TK-Themes/theme/dark/box-basic.png b/TK-Themes/theme/dark/box-basic.png new file mode 100644 index 0000000..0b28fed Binary files /dev/null and b/TK-Themes/theme/dark/box-basic.png differ diff --git a/TK-Themes/theme/dark/box-hover.png b/TK-Themes/theme/dark/box-hover.png new file mode 100644 index 0000000..3ca7c19 Binary files /dev/null and b/TK-Themes/theme/dark/box-hover.png differ diff --git a/TK-Themes/theme/dark/box-invalid.png b/TK-Themes/theme/dark/box-invalid.png new file mode 100644 index 0000000..f180e94 Binary files /dev/null and b/TK-Themes/theme/dark/box-invalid.png differ diff --git a/TK-Themes/theme/dark/button-hover.png b/TK-Themes/theme/dark/button-hover.png new file mode 100644 index 0000000..300eb09 Binary files /dev/null and b/TK-Themes/theme/dark/button-hover.png differ diff --git a/TK-Themes/theme/dark/card.png b/TK-Themes/theme/dark/card.png new file mode 100644 index 0000000..3978e9f Binary files /dev/null and b/TK-Themes/theme/dark/card.png differ diff --git a/TK-Themes/theme/dark/check-accent.png b/TK-Themes/theme/dark/check-accent.png new file mode 100644 index 0000000..7cabf6a Binary files /dev/null and b/TK-Themes/theme/dark/check-accent.png differ diff --git a/TK-Themes/theme/dark/check-basic.png b/TK-Themes/theme/dark/check-basic.png new file mode 100644 index 0000000..529718e Binary files /dev/null and b/TK-Themes/theme/dark/check-basic.png differ diff --git a/TK-Themes/theme/dark/check-hover.png b/TK-Themes/theme/dark/check-hover.png new file mode 100644 index 0000000..c96c80c Binary files /dev/null and b/TK-Themes/theme/dark/check-hover.png differ diff --git a/TK-Themes/theme/dark/check-tri-accent.png b/TK-Themes/theme/dark/check-tri-accent.png new file mode 100644 index 0000000..1d0c255 Binary files /dev/null and b/TK-Themes/theme/dark/check-tri-accent.png differ diff --git a/TK-Themes/theme/dark/check-tri-basic.png b/TK-Themes/theme/dark/check-tri-basic.png new file mode 100644 index 0000000..d0188f5 Binary files /dev/null and b/TK-Themes/theme/dark/check-tri-basic.png differ diff --git a/TK-Themes/theme/dark/check-tri-hover.png b/TK-Themes/theme/dark/check-tri-hover.png new file mode 100644 index 0000000..ae3e19f Binary files /dev/null and b/TK-Themes/theme/dark/check-tri-hover.png differ diff --git a/TK-Themes/theme/dark/circle-accent.png b/TK-Themes/theme/dark/circle-accent.png new file mode 100644 index 0000000..1b25b16 Binary files /dev/null and b/TK-Themes/theme/dark/circle-accent.png differ diff --git a/TK-Themes/theme/dark/circle-basic.png b/TK-Themes/theme/dark/circle-basic.png new file mode 100644 index 0000000..85f2bac Binary files /dev/null and b/TK-Themes/theme/dark/circle-basic.png differ diff --git a/TK-Themes/theme/dark/circle-hover.png b/TK-Themes/theme/dark/circle-hover.png new file mode 100644 index 0000000..b439cd6 Binary files /dev/null and b/TK-Themes/theme/dark/circle-hover.png differ diff --git a/TK-Themes/theme/dark/combo-button-basic.png b/TK-Themes/theme/dark/combo-button-basic.png new file mode 100644 index 0000000..4aef1b2 Binary files /dev/null and b/TK-Themes/theme/dark/combo-button-basic.png differ diff --git a/TK-Themes/theme/dark/combo-button-focus.png b/TK-Themes/theme/dark/combo-button-focus.png new file mode 100644 index 0000000..e37db17 Binary files /dev/null and b/TK-Themes/theme/dark/combo-button-focus.png differ diff --git a/TK-Themes/theme/dark/combo-button-hover.png b/TK-Themes/theme/dark/combo-button-hover.png new file mode 100644 index 0000000..493cf52 Binary files /dev/null and b/TK-Themes/theme/dark/combo-button-hover.png differ diff --git a/TK-Themes/theme/dark/down-accent.png b/TK-Themes/theme/dark/down-accent.png new file mode 100644 index 0000000..5bb987d Binary files /dev/null and b/TK-Themes/theme/dark/down-accent.png differ diff --git a/TK-Themes/theme/dark/down.png b/TK-Themes/theme/dark/down.png new file mode 100644 index 0000000..d83f92d Binary files /dev/null and b/TK-Themes/theme/dark/down.png differ diff --git a/TK-Themes/theme/dark/empty.png b/TK-Themes/theme/dark/empty.png new file mode 100644 index 0000000..202e3de Binary files /dev/null and b/TK-Themes/theme/dark/empty.png differ diff --git a/TK-Themes/theme/dark/hor-accent.png b/TK-Themes/theme/dark/hor-accent.png new file mode 100644 index 0000000..ab4b182 Binary files /dev/null and b/TK-Themes/theme/dark/hor-accent.png differ diff --git a/TK-Themes/theme/dark/hor-basic.png b/TK-Themes/theme/dark/hor-basic.png new file mode 100644 index 0000000..bbc2e50 Binary files /dev/null and b/TK-Themes/theme/dark/hor-basic.png differ diff --git a/TK-Themes/theme/dark/hor-hover.png b/TK-Themes/theme/dark/hor-hover.png new file mode 100644 index 0000000..56216f4 Binary files /dev/null and b/TK-Themes/theme/dark/hor-hover.png differ diff --git a/TK-Themes/theme/dark/notebook.png b/TK-Themes/theme/dark/notebook.png new file mode 100644 index 0000000..5b937c1 Binary files /dev/null and b/TK-Themes/theme/dark/notebook.png differ diff --git a/TK-Themes/theme/dark/off-basic.png b/TK-Themes/theme/dark/off-basic.png new file mode 100644 index 0000000..2ca539a Binary files /dev/null and b/TK-Themes/theme/dark/off-basic.png differ diff --git a/TK-Themes/theme/dark/on-accent.png b/TK-Themes/theme/dark/on-accent.png new file mode 100644 index 0000000..9d48eb6 Binary files /dev/null and b/TK-Themes/theme/dark/on-accent.png differ diff --git a/TK-Themes/theme/dark/on-basic.png b/TK-Themes/theme/dark/on-basic.png new file mode 100644 index 0000000..9dce6a5 Binary files /dev/null and b/TK-Themes/theme/dark/on-basic.png differ diff --git a/TK-Themes/theme/dark/outline-basic.png b/TK-Themes/theme/dark/outline-basic.png new file mode 100644 index 0000000..2f76a1b Binary files /dev/null and b/TK-Themes/theme/dark/outline-basic.png differ diff --git a/TK-Themes/theme/dark/outline-hover.png b/TK-Themes/theme/dark/outline-hover.png new file mode 100644 index 0000000..7ce5290 Binary files /dev/null and b/TK-Themes/theme/dark/outline-hover.png differ diff --git a/TK-Themes/theme/dark/radio-accent.png b/TK-Themes/theme/dark/radio-accent.png new file mode 100644 index 0000000..6535a97 Binary files /dev/null and b/TK-Themes/theme/dark/radio-accent.png differ diff --git a/TK-Themes/theme/dark/radio-basic.png b/TK-Themes/theme/dark/radio-basic.png new file mode 100644 index 0000000..f9b55a6 Binary files /dev/null and b/TK-Themes/theme/dark/radio-basic.png differ diff --git a/TK-Themes/theme/dark/radio-hover.png b/TK-Themes/theme/dark/radio-hover.png new file mode 100644 index 0000000..4f3eab8 Binary files /dev/null and b/TK-Themes/theme/dark/radio-hover.png differ diff --git a/TK-Themes/theme/dark/radio-tri-accent.png b/TK-Themes/theme/dark/radio-tri-accent.png new file mode 100644 index 0000000..b09b6ae Binary files /dev/null and b/TK-Themes/theme/dark/radio-tri-accent.png differ diff --git a/TK-Themes/theme/dark/radio-tri-basic.png b/TK-Themes/theme/dark/radio-tri-basic.png new file mode 100644 index 0000000..def9e27 Binary files /dev/null and b/TK-Themes/theme/dark/radio-tri-basic.png differ diff --git a/TK-Themes/theme/dark/radio-tri-hover.png b/TK-Themes/theme/dark/radio-tri-hover.png new file mode 100644 index 0000000..86f1b59 Binary files /dev/null and b/TK-Themes/theme/dark/radio-tri-hover.png differ diff --git a/TK-Themes/theme/dark/rect-accent-hover.png b/TK-Themes/theme/dark/rect-accent-hover.png new file mode 100644 index 0000000..bb49129 Binary files /dev/null and b/TK-Themes/theme/dark/rect-accent-hover.png differ diff --git a/TK-Themes/theme/dark/rect-accent.png b/TK-Themes/theme/dark/rect-accent.png new file mode 100644 index 0000000..a4821d1 Binary files /dev/null and b/TK-Themes/theme/dark/rect-accent.png differ diff --git a/TK-Themes/theme/dark/rect-basic.png b/TK-Themes/theme/dark/rect-basic.png new file mode 100644 index 0000000..8ab4d10 Binary files /dev/null and b/TK-Themes/theme/dark/rect-basic.png differ diff --git a/TK-Themes/theme/dark/rect-hover.png b/TK-Themes/theme/dark/rect-hover.png new file mode 100644 index 0000000..b9e4c35 Binary files /dev/null and b/TK-Themes/theme/dark/rect-hover.png differ diff --git a/TK-Themes/theme/dark/right.png b/TK-Themes/theme/dark/right.png new file mode 100644 index 0000000..bc840c2 Binary files /dev/null and b/TK-Themes/theme/dark/right.png differ diff --git a/TK-Themes/theme/dark/scale-hor.png b/TK-Themes/theme/dark/scale-hor.png new file mode 100644 index 0000000..570530e Binary files /dev/null and b/TK-Themes/theme/dark/scale-hor.png differ diff --git a/TK-Themes/theme/dark/scale-vert.png b/TK-Themes/theme/dark/scale-vert.png new file mode 100644 index 0000000..c6fcf6f Binary files /dev/null and b/TK-Themes/theme/dark/scale-vert.png differ diff --git a/TK-Themes/theme/dark/separator.png b/TK-Themes/theme/dark/separator.png new file mode 100644 index 0000000..411c970 Binary files /dev/null and b/TK-Themes/theme/dark/separator.png differ diff --git a/TK-Themes/theme/dark/size.png b/TK-Themes/theme/dark/size.png new file mode 100644 index 0000000..51c682e Binary files /dev/null and b/TK-Themes/theme/dark/size.png differ diff --git a/TK-Themes/theme/dark/tab-basic.png b/TK-Themes/theme/dark/tab-basic.png new file mode 100644 index 0000000..8c6d008 Binary files /dev/null and b/TK-Themes/theme/dark/tab-basic.png differ diff --git a/TK-Themes/theme/dark/tab-disabled.png b/TK-Themes/theme/dark/tab-disabled.png new file mode 100644 index 0000000..736c438 Binary files /dev/null and b/TK-Themes/theme/dark/tab-disabled.png differ diff --git a/TK-Themes/theme/dark/tab-hover.png b/TK-Themes/theme/dark/tab-hover.png new file mode 100644 index 0000000..213e82b Binary files /dev/null and b/TK-Themes/theme/dark/tab-hover.png differ diff --git a/TK-Themes/theme/dark/tick-hor-accent.png b/TK-Themes/theme/dark/tick-hor-accent.png new file mode 100644 index 0000000..3a90f28 Binary files /dev/null and b/TK-Themes/theme/dark/tick-hor-accent.png differ diff --git a/TK-Themes/theme/dark/tick-hor-basic.png b/TK-Themes/theme/dark/tick-hor-basic.png new file mode 100644 index 0000000..ee4b441 Binary files /dev/null and b/TK-Themes/theme/dark/tick-hor-basic.png differ diff --git a/TK-Themes/theme/dark/tick-hor-hover.png b/TK-Themes/theme/dark/tick-hor-hover.png new file mode 100644 index 0000000..bc859f4 Binary files /dev/null and b/TK-Themes/theme/dark/tick-hor-hover.png differ diff --git a/TK-Themes/theme/dark/tick-vert-accent.png b/TK-Themes/theme/dark/tick-vert-accent.png new file mode 100644 index 0000000..0c0ff98 Binary files /dev/null and b/TK-Themes/theme/dark/tick-vert-accent.png differ diff --git a/TK-Themes/theme/dark/tick-vert-basic.png b/TK-Themes/theme/dark/tick-vert-basic.png new file mode 100644 index 0000000..9e0a5ee Binary files /dev/null and b/TK-Themes/theme/dark/tick-vert-basic.png differ diff --git a/TK-Themes/theme/dark/tick-vert-hover.png b/TK-Themes/theme/dark/tick-vert-hover.png new file mode 100644 index 0000000..e9ff3a3 Binary files /dev/null and b/TK-Themes/theme/dark/tick-vert-hover.png differ diff --git a/TK-Themes/theme/dark/tree-basic.png b/TK-Themes/theme/dark/tree-basic.png new file mode 100644 index 0000000..c71808d Binary files /dev/null and b/TK-Themes/theme/dark/tree-basic.png differ diff --git a/TK-Themes/theme/dark/tree-pressed.png b/TK-Themes/theme/dark/tree-pressed.png new file mode 100644 index 0000000..41b65c6 Binary files /dev/null and b/TK-Themes/theme/dark/tree-pressed.png differ diff --git a/TK-Themes/theme/dark/up-accent.png b/TK-Themes/theme/dark/up-accent.png new file mode 100644 index 0000000..54a20f8 Binary files /dev/null and b/TK-Themes/theme/dark/up-accent.png differ diff --git a/TK-Themes/theme/dark/up.png b/TK-Themes/theme/dark/up.png new file mode 100644 index 0000000..069d440 Binary files /dev/null and b/TK-Themes/theme/dark/up.png differ diff --git a/TK-Themes/theme/dark/vert-accent.png b/TK-Themes/theme/dark/vert-accent.png new file mode 100644 index 0000000..495f804 Binary files /dev/null and b/TK-Themes/theme/dark/vert-accent.png differ diff --git a/TK-Themes/theme/dark/vert-basic.png b/TK-Themes/theme/dark/vert-basic.png new file mode 100644 index 0000000..4f6c46e Binary files /dev/null and b/TK-Themes/theme/dark/vert-basic.png differ diff --git a/TK-Themes/theme/dark/vert-hover.png b/TK-Themes/theme/dark/vert-hover.png new file mode 100644 index 0000000..142ccce Binary files /dev/null and b/TK-Themes/theme/dark/vert-hover.png differ diff --git a/TK-Themes/theme/light.tcl b/TK-Themes/theme/light.tcl new file mode 100644 index 0000000..dbd6dad --- /dev/null +++ b/TK-Themes/theme/light.tcl @@ -0,0 +1,539 @@ +# Copyright (c) 2021 rdbende + +# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024 + +package require Tk 8.6 + +namespace eval ttk::theme::water-light { + variable version 2.0 + package provide ttk::theme::water-light $version + + ttk::style theme create water-light -parent clam -settings { + proc load_images {imgdir} { + variable I + foreach file [glob -directory $imgdir *.png] { + set img [file tail [file rootname $file]] + set I($img) [image create photo -file $file -format png] + } + } + + load_images [file join [file dirname [info script]] light] + + array set colors { + -fg "#000000" + -bg "#ffffff" + -disabledfg "#737373" + -disabledbg "#ffffff" + -selectfg "#ffffff" + -selectbg "#007fff" + } + + ttk::style layout TButton { + Button.button -children { + Button.padding -children { + Button.label -side left -expand true + } + } + } + + ttk::style layout Toolbutton { + Toolbutton.button -children { + Toolbutton.padding -children { + Toolbutton.label -side left -expand true + } + } + } + + ttk::style layout TMenubutton { + Menubutton.button -children { + Menubutton.padding -children { + Menubutton.indicator -side right + Menubutton.label -side right -expand true + } + } + } + + ttk::style layout TOptionMenu { + OptionMenu.button -children { + OptionMenu.padding -children { + OptionMenu.indicator -side right + OptionMenu.label -side right -expand true + } + } + } + + ttk::style layout Accent.TButton { + AccentButton.button -children { + AccentButton.padding -children { + AccentButton.label -side left -expand true + } + } + } + + ttk::style layout TCheckbutton { + Checkbutton.button -children { + Checkbutton.padding -children { + Checkbutton.indicator -side left + Checkbutton.label -side right -expand true + } + } + } + + ttk::style layout Switch.TCheckbutton { + Switch.button -children { + Switch.padding -children { + Switch.indicator -side left + Switch.label -side right -expand true + } + } + } + + ttk::style layout Toggle.TButton { + ToggleButton.button -children { + ToggleButton.padding -children { + ToggleButton.label -side left -expand true + } + } + } + + ttk::style layout TRadiobutton { + Radiobutton.button -children { + Radiobutton.padding -children { + Radiobutton.indicator -side left + Radiobutton.label -side right -expand true + } + } + } + + ttk::style layout Vertical.TScrollbar { + Vertical.Scrollbar.trough -sticky ns -children { + Vertical.Scrollbar.thumb -expand true + } + } + + ttk::style layout Horizontal.TScrollbar { + Horizontal.Scrollbar.trough -sticky ew -children { + Horizontal.Scrollbar.thumb -expand true + } + } + + ttk::style layout TCombobox { + Combobox.field -sticky nswe -children { + Combobox.padding -expand true -sticky nswe -children { + Combobox.textarea -sticky nswe + } + } + Combobox.button -side right -sticky ns -children { + Combobox.arrow -sticky nsew + } + } + + ttk::style layout TSpinbox { + Spinbox.field -sticky nsew -children { + Spinbox.padding -expand true -sticky nswe -children { + Spinbox.textarea -sticky nswe + } + + } + Spinbox.button -side right -sticky ns -children { + null -side right -children { + Spinbox.uparrow -side top + Spinbox.downarrow -side bottom + } + } + } + + ttk::style layout Horizontal.TSeparator { + Horizontal.separator -sticky nswe + } + + ttk::style layout Vertical.TSeparator { + Vertical.separator -sticky nswe + } + + ttk::style layout Horizontal.Tick.TScale { + Horizontal.TickScale.trough -sticky ew -children { + Horizontal.TickScale.slider -sticky w + } + } + + ttk::style layout Vertical.Tick.TScale { + Vertical.TickScale.trough -sticky ns -children { + Vertical.TickScale.slider -sticky n + } + } + + ttk::style layout Card.TFrame { + Card.field { + Card.padding -expand 1 + } + } + + ttk::style layout TLabelframe { + Labelframe.border { + Labelframe.padding -expand 1 -children { + Labelframe.label -side right + } + } + } + + ttk::style layout TNotebook.Tab { + Notebook.tab -children { + Notebook.padding -side top -children { + Notebook.label -side top -sticky {} + } + } + } + + ttk::style layout Treeview.Item { + Treeitem.padding -sticky nswe -children { + Treeitem.indicator -side left -sticky {} + Treeitem.image -side left -sticky {} + Treeitem.text -side left -sticky {} + } + } + + + # Elements + + # Button + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center + + 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(button-hover) \ + ] -border 4 -sticky ewns + + # Toolbutton + ttk::style configure Toolbutton -padding {8 4 8 4} -width -5 -anchor center + + ttk::style element create Toolbutton.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 + + # Menubutton + ttk::style configure TMenubutton -padding {8 4 4 4} + + ttk::style element create Menubutton.button \ + image [list $I(rect-basic) \ + disabled $I(rect-basic) \ + pressed $I(rect-basic) \ + active $I(button-hover) \ + ] -border 4 -sticky ewns + + ttk::style element create Menubutton.indicator \ + image [list $I(down) \ + active $I(down) \ + pressed $I(down) \ + disabled $I(down) \ + ] -width 15 -sticky e + + # OptionMenu + ttk::style configure TOptionMenu -padding {8 4 4 4} + + ttk::style element create OptionMenu.button \ + image [list $I(rect-basic) \ + disabled $I(rect-basic) \ + pressed $I(rect-basic) \ + active $I(button-hover) \ + ] -border 4 -sticky ewns + + ttk::style element create OptionMenu.indicator \ + image [list $I(down) \ + active $I(down) \ + pressed $I(down) \ + disabled $I(down) \ + ] -width 15 -sticky e + + # AccentButton + ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style element create AccentButton.button image \ + [list $I(rect-accent) \ + {selected disabled} $I(rect-accent-hover) \ + disabled $I(rect-accent-hover) \ + selected $I(rect-accent) \ + pressed $I(rect-accent) \ + active $I(rect-accent-hover) \ + focus $I(rect-accent-hover) \ + ] -border 4 -sticky ewns + + # Checkbutton + ttk::style configure TCheckbutton -padding 4 + + ttk::style element create Checkbutton.indicator image \ + [list $I(box-basic) \ + {alternate disabled} $I(check-tri-basic) \ + {selected disabled} $I(check-basic) \ + disabled $I(box-basic) \ + {pressed alternate} $I(check-tri-hover) \ + {active alternate} $I(check-tri-hover) \ + alternate $I(check-tri-accent) \ + {pressed selected} $I(check-hover) \ + {active selected} $I(check-hover) \ + selected $I(check-accent) \ + {pressed !selected} $I(rect-hover) \ + active $I(box-hover) \ + ] -width 26 -sticky w + + # Switch + ttk::style element create Switch.indicator image \ + [list $I(off-basic) \ + {selected disabled} $I(on-basic) \ + disabled $I(off-basic) \ + {pressed selected} $I(on-hover) \ + {active selected} $I(on-hover) \ + selected $I(on-accent) \ + {pressed !selected} $I(off-hover) \ + active $I(off-hover) \ + ] -width 46 -sticky w + + # ToggleButton + ttk::style configure Toggle.TButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style element create ToggleButton.button image \ + [list $I(rect-basic) \ + {selected disabled} $I(rect-accent-hover) \ + disabled $I(rect-basic) \ + {pressed selected} $I(rect-basic) \ + {active selected} $I(rect-accent) \ + selected $I(rect-accent) \ + {pressed !selected} $I(rect-accent) \ + active $I(rect-basic) \ + ] -border 4 -sticky ewns + + # Radiobutton + ttk::style configure TRadiobutton -padding 4 + + ttk::style element create Radiobutton.indicator image \ + [list $I(outline-basic) \ + {alternate disabled} $I(radio-tri-basic) \ + {selected disabled} $I(radio-basic) \ + disabled $I(outline-basic) \ + {pressed alternate} $I(radio-tri-hover) \ + {active alternate} $I(radio-tri-hover) \ + alternate $I(radio-tri-accent) \ + {pressed selected} $I(radio-hover) \ + {active selected} $I(radio-hover) \ + selected $I(radio-accent) \ + {pressed !selected} $I(circle-hover) \ + active $I(outline-hover) \ + ] -width 26 -sticky w + + # Scrollbar + ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \ + -sticky ew + + ttk::style element create Horizontal.Scrollbar.thumb \ + image [list $I(hor-accent) \ + disabled $I(hor-basic) \ + pressed $I(hor-hover) \ + active $I(hor-hover) \ + ] -sticky ew + + ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \ + -sticky ns + + ttk::style element create Vertical.Scrollbar.thumb \ + image [list $I(vert-accent) \ + disabled $I(vert-basic) \ + pressed $I(vert-hover) \ + active $I(vert-hover) \ + ] -sticky ns + + # Scale + ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \ + -border 5 -padding 0 + + ttk::style element create Horizontal.Scale.slider \ + image [list $I(circle-accent) \ + disabled $I(circle-basic) \ + pressed $I(circle-hover) \ + active $I(circle-hover) \ + ] -sticky {} + + ttk::style element create Vertical.Scale.trough image $I(scale-vert) \ + -border 5 -padding 0 + + ttk::style element create Vertical.Scale.slider \ + image [list $I(circle-accent) \ + disabled $I(circle-basic) \ + pressed $I(circle-hover) \ + active $I(circle-hover) \ + ] -sticky {} + + # Tickscale + ttk::style element create Horizontal.TickScale.trough image $I(scale-hor) \ + -border 5 -padding 0 + + ttk::style element create Horizontal.TickScale.slider \ + image [list $I(tick-hor-accent) \ + disabled $I(tick-hor-basic) \ + pressed $I(tick-hor-hover) \ + active $I(tick-hor-hover) \ + ] -sticky {} + + ttk::style element create Vertical.TickScale.trough image $I(scale-vert) \ + -border 5 -padding 0 + + ttk::style element create Vertical.TickScale.slider \ + image [list $I(tick-vert-accent) \ + disabled $I(tick-vert-basic) \ + pressed $I(tick-vert-hover) \ + active $I(tick-vert-hover) \ + ] -sticky {} + + # Progressbar + ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \ + -sticky ew + + ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \ + -sticky ew + + ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \ + -sticky ns + + ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \ + -sticky ns + + # Entry + ttk::style element create Entry.field \ + image [list $I(box-basic) \ + {focus hover} $I(box-accent) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} -sticky news + + # Combobox + ttk::style map TCombobox -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg) \ + ] + + ttk::style map TCombobox -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg) \ + ] + + ttk::style element create Combobox.field \ + image [list $I(box-basic) \ + {readonly disabled} $I(rect-basic) \ + {readonly pressed} $I(rect-basic) \ + {readonly focus hover} $I(button-hover) \ + {readonly focus} $I(button-hover) \ + {readonly hover} $I(button-hover) \ + {focus hover} $I(box-accent) \ + readonly $I(rect-basic) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} + + ttk::style element create Combobox.button \ + image [list $I(combo-button-basic) \ + {!readonly focus} $I(combo-button-focus) \ + {readonly focus} $I(combo-button-hover) \ + {readonly hover} $I(combo-button-hover) + ] -border 5 -padding {2 6 6 6} + + ttk::style element create Combobox.arrow image $I(down) \ + -width 15 -sticky e + + # Spinbox + ttk::style element create Spinbox.field \ + image [list $I(box-basic) \ + invalid $I(box-invalid) \ + disabled $I(box-basic) \ + focus $I(box-accent) \ + hover $I(box-hover) \ + ] -border 5 -padding {8} -sticky news + + ttk::style element create Spinbox.uparrow \ + image [list $I(up) \ + disabled $I(up) \ + pressed $I(up-accent) \ + active $I(up-accent) \ + ] -border 4 -width 15 -sticky e + + ttk::style element create Spinbox.downarrow \ + image [list $I(down) \ + disabled $I(down) \ + pressed $I(down-accent) \ + active $I(down-accent) \ + ] -border 4 -width 15 -sticky e + + ttk::style element create Spinbox.button \ + image [list $I(combo-button-basic) \ + {!readonly focus} $I(combo-button-focus) \ + {readonly focus} $I(combo-button-hover) \ + {readonly hover} $I(combo-button-hover) + ] -border 5 -padding {2 6 6 6} + + # Sizegrip + ttk::style element create Sizegrip.sizegrip image $I(size) \ + -sticky ewns + + # Separator + ttk::style element create Horizontal.separator image $I(separator) + + ttk::style element create Vertical.separator image $I(separator) + + # Card + ttk::style element create Card.field image $I(card) \ + -border 10 -padding 4 -sticky news + + # Labelframe + ttk::style element create Labelframe.border image $I(card) \ + -border 5 -padding 4 -sticky news + + # Notebook + ttk::style element create Notebook.client \ + image $I(notebook) -border 5 + + ttk::style element create Notebook.tab \ + image [list $I(tab-disabled) \ + selected $I(tab-basic) \ + active $I(tab-hover) \ + ] -border 5 -padding {14 4} + + # Treeview + ttk::style element create Treeview.field image $I(card) \ + -border 5 + + ttk::style element create Treeheading.cell \ + image [list $I(tree-basic) \ + pressed $I(tree-pressed) + ] -border 5 -padding 4 -sticky ewns + + ttk::style element create Treeitem.indicator \ + image [list $I(right) \ + user2 $I(empty) \ + user1 $I(down) \ + ] -width 26 -sticky {} + + ttk::style configure Treeview -background $colors(-bg) + ttk::style configure Treeview.Item -padding {2 0 0 0} + ttk::style map Treeview \ + -background [list selected #ccc] \ + -foreground [list selected $colors(-fg)] + + # Panedwindow + # Insane hack to remove clam's ugly sash + ttk::style configure Sash -gripcount 0 + } +} diff --git a/TK-Themes/theme/light/box-accent.png b/TK-Themes/theme/light/box-accent.png new file mode 100644 index 0000000..70c1e38 Binary files /dev/null and b/TK-Themes/theme/light/box-accent.png differ diff --git a/TK-Themes/theme/light/box-basic.png b/TK-Themes/theme/light/box-basic.png new file mode 100644 index 0000000..090a00b Binary files /dev/null and b/TK-Themes/theme/light/box-basic.png differ diff --git a/TK-Themes/theme/light/box-hover.png b/TK-Themes/theme/light/box-hover.png new file mode 100644 index 0000000..e691da4 Binary files /dev/null and b/TK-Themes/theme/light/box-hover.png differ diff --git a/TK-Themes/theme/light/box-invalid.png b/TK-Themes/theme/light/box-invalid.png new file mode 100644 index 0000000..1f16f5c Binary files /dev/null and b/TK-Themes/theme/light/box-invalid.png differ diff --git a/TK-Themes/theme/light/button-hover.png b/TK-Themes/theme/light/button-hover.png new file mode 100644 index 0000000..13366eb Binary files /dev/null and b/TK-Themes/theme/light/button-hover.png differ diff --git a/TK-Themes/theme/light/card.png b/TK-Themes/theme/light/card.png new file mode 100644 index 0000000..09152f5 Binary files /dev/null and b/TK-Themes/theme/light/card.png differ diff --git a/TK-Themes/theme/light/check-accent.png b/TK-Themes/theme/light/check-accent.png new file mode 100644 index 0000000..67de0a6 Binary files /dev/null and b/TK-Themes/theme/light/check-accent.png differ diff --git a/TK-Themes/theme/light/check-basic.png b/TK-Themes/theme/light/check-basic.png new file mode 100644 index 0000000..0c00612 Binary files /dev/null and b/TK-Themes/theme/light/check-basic.png differ diff --git a/TK-Themes/theme/light/check-hover.png b/TK-Themes/theme/light/check-hover.png new file mode 100644 index 0000000..3811696 Binary files /dev/null and b/TK-Themes/theme/light/check-hover.png differ diff --git a/TK-Themes/theme/light/check-tri-accent.png b/TK-Themes/theme/light/check-tri-accent.png new file mode 100644 index 0000000..c02f75d Binary files /dev/null and b/TK-Themes/theme/light/check-tri-accent.png differ diff --git a/TK-Themes/theme/light/check-tri-basic.png b/TK-Themes/theme/light/check-tri-basic.png new file mode 100644 index 0000000..e92bea5 Binary files /dev/null and b/TK-Themes/theme/light/check-tri-basic.png differ diff --git a/TK-Themes/theme/light/check-tri-hover.png b/TK-Themes/theme/light/check-tri-hover.png new file mode 100644 index 0000000..d611d76 Binary files /dev/null and b/TK-Themes/theme/light/check-tri-hover.png differ diff --git a/TK-Themes/theme/light/circle-accent.png b/TK-Themes/theme/light/circle-accent.png new file mode 100644 index 0000000..b2202e2 Binary files /dev/null and b/TK-Themes/theme/light/circle-accent.png differ diff --git a/TK-Themes/theme/light/circle-basic.png b/TK-Themes/theme/light/circle-basic.png new file mode 100644 index 0000000..b16202e Binary files /dev/null and b/TK-Themes/theme/light/circle-basic.png differ diff --git a/TK-Themes/theme/light/circle-hover.png b/TK-Themes/theme/light/circle-hover.png new file mode 100644 index 0000000..6ea1803 Binary files /dev/null and b/TK-Themes/theme/light/circle-hover.png differ diff --git a/TK-Themes/theme/light/combo-button-basic.png b/TK-Themes/theme/light/combo-button-basic.png new file mode 100644 index 0000000..b7daa04 Binary files /dev/null and b/TK-Themes/theme/light/combo-button-basic.png differ diff --git a/TK-Themes/theme/light/combo-button-focus.png b/TK-Themes/theme/light/combo-button-focus.png new file mode 100644 index 0000000..521aef0 Binary files /dev/null and b/TK-Themes/theme/light/combo-button-focus.png differ diff --git a/TK-Themes/theme/light/combo-button-hover.png b/TK-Themes/theme/light/combo-button-hover.png new file mode 100644 index 0000000..1d06c57 Binary files /dev/null and b/TK-Themes/theme/light/combo-button-hover.png differ diff --git a/TK-Themes/theme/light/down-accent.png b/TK-Themes/theme/light/down-accent.png new file mode 100644 index 0000000..5bb987d Binary files /dev/null and b/TK-Themes/theme/light/down-accent.png differ diff --git a/TK-Themes/theme/light/down.png b/TK-Themes/theme/light/down.png new file mode 100644 index 0000000..1fd7e4f Binary files /dev/null and b/TK-Themes/theme/light/down.png differ diff --git a/TK-Themes/theme/light/empty.png b/TK-Themes/theme/light/empty.png new file mode 100644 index 0000000..202e3de Binary files /dev/null and b/TK-Themes/theme/light/empty.png differ diff --git a/TK-Themes/theme/light/hor-accent.png b/TK-Themes/theme/light/hor-accent.png new file mode 100644 index 0000000..20ea079 Binary files /dev/null and b/TK-Themes/theme/light/hor-accent.png differ diff --git a/TK-Themes/theme/light/hor-basic.png b/TK-Themes/theme/light/hor-basic.png new file mode 100644 index 0000000..eb18d1e Binary files /dev/null and b/TK-Themes/theme/light/hor-basic.png differ diff --git a/TK-Themes/theme/light/hor-hover.png b/TK-Themes/theme/light/hor-hover.png new file mode 100644 index 0000000..4d6c0f1 Binary files /dev/null and b/TK-Themes/theme/light/hor-hover.png differ diff --git a/TK-Themes/theme/light/notebook.png b/TK-Themes/theme/light/notebook.png new file mode 100644 index 0000000..430d9f2 Binary files /dev/null and b/TK-Themes/theme/light/notebook.png differ diff --git a/TK-Themes/theme/light/off-basic.png b/TK-Themes/theme/light/off-basic.png new file mode 100644 index 0000000..cf383f0 Binary files /dev/null and b/TK-Themes/theme/light/off-basic.png differ diff --git a/TK-Themes/theme/light/off-hover.png b/TK-Themes/theme/light/off-hover.png new file mode 100644 index 0000000..893b39c Binary files /dev/null and b/TK-Themes/theme/light/off-hover.png differ diff --git a/TK-Themes/theme/light/on-accent.png b/TK-Themes/theme/light/on-accent.png new file mode 100644 index 0000000..31ee2fc Binary files /dev/null and b/TK-Themes/theme/light/on-accent.png differ diff --git a/TK-Themes/theme/light/on-basic.png b/TK-Themes/theme/light/on-basic.png new file mode 100644 index 0000000..23ec72f Binary files /dev/null and b/TK-Themes/theme/light/on-basic.png differ diff --git a/TK-Themes/theme/light/on-hover.png b/TK-Themes/theme/light/on-hover.png new file mode 100644 index 0000000..d55f7ae Binary files /dev/null and b/TK-Themes/theme/light/on-hover.png differ diff --git a/TK-Themes/theme/light/outline-basic.png b/TK-Themes/theme/light/outline-basic.png new file mode 100644 index 0000000..2e79874 Binary files /dev/null and b/TK-Themes/theme/light/outline-basic.png differ diff --git a/TK-Themes/theme/light/outline-hover.png b/TK-Themes/theme/light/outline-hover.png new file mode 100644 index 0000000..502915d Binary files /dev/null and b/TK-Themes/theme/light/outline-hover.png differ diff --git a/TK-Themes/theme/light/radio-accent.png b/TK-Themes/theme/light/radio-accent.png new file mode 100644 index 0000000..4daef1d Binary files /dev/null and b/TK-Themes/theme/light/radio-accent.png differ diff --git a/TK-Themes/theme/light/radio-basic.png b/TK-Themes/theme/light/radio-basic.png new file mode 100644 index 0000000..793531e Binary files /dev/null and b/TK-Themes/theme/light/radio-basic.png differ diff --git a/TK-Themes/theme/light/radio-hover.png b/TK-Themes/theme/light/radio-hover.png new file mode 100644 index 0000000..d6faa74 Binary files /dev/null and b/TK-Themes/theme/light/radio-hover.png differ diff --git a/TK-Themes/theme/light/radio-tri-accent.png b/TK-Themes/theme/light/radio-tri-accent.png new file mode 100644 index 0000000..806d575 Binary files /dev/null and b/TK-Themes/theme/light/radio-tri-accent.png differ diff --git a/TK-Themes/theme/light/radio-tri-basic.png b/TK-Themes/theme/light/radio-tri-basic.png new file mode 100644 index 0000000..da85d03 Binary files /dev/null and b/TK-Themes/theme/light/radio-tri-basic.png differ diff --git a/TK-Themes/theme/light/radio-tri-hover.png b/TK-Themes/theme/light/radio-tri-hover.png new file mode 100644 index 0000000..ded14a6 Binary files /dev/null and b/TK-Themes/theme/light/radio-tri-hover.png differ diff --git a/TK-Themes/theme/light/rect-accent-hover.png b/TK-Themes/theme/light/rect-accent-hover.png new file mode 100644 index 0000000..5daa96a Binary files /dev/null and b/TK-Themes/theme/light/rect-accent-hover.png differ diff --git a/TK-Themes/theme/light/rect-accent.png b/TK-Themes/theme/light/rect-accent.png new file mode 100644 index 0000000..8b3f822 Binary files /dev/null and b/TK-Themes/theme/light/rect-accent.png differ diff --git a/TK-Themes/theme/light/rect-basic.png b/TK-Themes/theme/light/rect-basic.png new file mode 100644 index 0000000..239ca31 Binary files /dev/null and b/TK-Themes/theme/light/rect-basic.png differ diff --git a/TK-Themes/theme/light/rect-hover.png b/TK-Themes/theme/light/rect-hover.png new file mode 100644 index 0000000..9252c4f Binary files /dev/null and b/TK-Themes/theme/light/rect-hover.png differ diff --git a/TK-Themes/theme/light/right.png b/TK-Themes/theme/light/right.png new file mode 100644 index 0000000..8122cc9 Binary files /dev/null and b/TK-Themes/theme/light/right.png differ diff --git a/TK-Themes/theme/light/scale-hor.png b/TK-Themes/theme/light/scale-hor.png new file mode 100644 index 0000000..d11f508 Binary files /dev/null and b/TK-Themes/theme/light/scale-hor.png differ diff --git a/TK-Themes/theme/light/scale-vert.png b/TK-Themes/theme/light/scale-vert.png new file mode 100644 index 0000000..f78595d Binary files /dev/null and b/TK-Themes/theme/light/scale-vert.png differ diff --git a/TK-Themes/theme/light/separator.png b/TK-Themes/theme/light/separator.png new file mode 100644 index 0000000..7bffc9a Binary files /dev/null and b/TK-Themes/theme/light/separator.png differ diff --git a/TK-Themes/theme/light/size.png b/TK-Themes/theme/light/size.png new file mode 100644 index 0000000..bde3ade Binary files /dev/null and b/TK-Themes/theme/light/size.png differ diff --git a/TK-Themes/theme/light/tab-basic.png b/TK-Themes/theme/light/tab-basic.png new file mode 100644 index 0000000..365fdff Binary files /dev/null and b/TK-Themes/theme/light/tab-basic.png differ diff --git a/TK-Themes/theme/light/tab-disabled.png b/TK-Themes/theme/light/tab-disabled.png new file mode 100644 index 0000000..eeee518 Binary files /dev/null and b/TK-Themes/theme/light/tab-disabled.png differ diff --git a/TK-Themes/theme/light/tab-hover.png b/TK-Themes/theme/light/tab-hover.png new file mode 100644 index 0000000..5003806 Binary files /dev/null and b/TK-Themes/theme/light/tab-hover.png differ diff --git a/TK-Themes/theme/light/tick-hor-accent.png b/TK-Themes/theme/light/tick-hor-accent.png new file mode 100644 index 0000000..aa32534 Binary files /dev/null and b/TK-Themes/theme/light/tick-hor-accent.png differ diff --git a/TK-Themes/theme/light/tick-hor-basic.png b/TK-Themes/theme/light/tick-hor-basic.png new file mode 100644 index 0000000..ba727d7 Binary files /dev/null and b/TK-Themes/theme/light/tick-hor-basic.png differ diff --git a/TK-Themes/theme/light/tick-hor-hover.png b/TK-Themes/theme/light/tick-hor-hover.png new file mode 100644 index 0000000..cb66b59 Binary files /dev/null and b/TK-Themes/theme/light/tick-hor-hover.png differ diff --git a/TK-Themes/theme/light/tick-vert-accent.png b/TK-Themes/theme/light/tick-vert-accent.png new file mode 100644 index 0000000..53c102d Binary files /dev/null and b/TK-Themes/theme/light/tick-vert-accent.png differ diff --git a/TK-Themes/theme/light/tick-vert-basic.png b/TK-Themes/theme/light/tick-vert-basic.png new file mode 100644 index 0000000..a58440d Binary files /dev/null and b/TK-Themes/theme/light/tick-vert-basic.png differ diff --git a/TK-Themes/theme/light/tick-vert-hover.png b/TK-Themes/theme/light/tick-vert-hover.png new file mode 100644 index 0000000..18cbec5 Binary files /dev/null and b/TK-Themes/theme/light/tick-vert-hover.png differ diff --git a/TK-Themes/theme/light/tree-basic.png b/TK-Themes/theme/light/tree-basic.png new file mode 100644 index 0000000..755062b Binary files /dev/null and b/TK-Themes/theme/light/tree-basic.png differ diff --git a/TK-Themes/theme/light/tree-pressed.png b/TK-Themes/theme/light/tree-pressed.png new file mode 100644 index 0000000..ee28416 Binary files /dev/null and b/TK-Themes/theme/light/tree-pressed.png differ diff --git a/TK-Themes/theme/light/up-accent.png b/TK-Themes/theme/light/up-accent.png new file mode 100644 index 0000000..54a20f8 Binary files /dev/null and b/TK-Themes/theme/light/up-accent.png differ diff --git a/TK-Themes/theme/light/up.png b/TK-Themes/theme/light/up.png new file mode 100644 index 0000000..16568f2 Binary files /dev/null and b/TK-Themes/theme/light/up.png differ diff --git a/TK-Themes/theme/light/vert-accent.png b/TK-Themes/theme/light/vert-accent.png new file mode 100644 index 0000000..58b0c32 Binary files /dev/null and b/TK-Themes/theme/light/vert-accent.png differ diff --git a/TK-Themes/theme/light/vert-basic.png b/TK-Themes/theme/light/vert-basic.png new file mode 100644 index 0000000..d5f61ec Binary files /dev/null and b/TK-Themes/theme/light/vert-basic.png differ diff --git a/TK-Themes/theme/light/vert-hover.png b/TK-Themes/theme/light/vert-hover.png new file mode 100644 index 0000000..bfdc9d0 Binary files /dev/null and b/TK-Themes/theme/light/vert-hover.png differ diff --git a/TK-Themes/water.tcl b/TK-Themes/water.tcl new file mode 100755 index 0000000..ac96081 --- /dev/null +++ b/TK-Themes/water.tcl @@ -0,0 +1,88 @@ +# Copyright © 2021 rdbende +# inspired by rdbende modified azure to water by Désire Werner Menrath polunga40@unity-mail.de 2024 + +source [file join [file dirname [info script]] theme light.tcl] +source [file join [file dirname [info script]] theme dark.tcl] + +option add *tearOff 0 + +proc set_theme {mode} { + if {$mode == "dark"} { + ttk::style theme use "water-dark" + + array set colors { + -fg "#ffffff" + -bg "#333333" + -disabledfg "#ffffff" + -disabledbg "#737373" + -selectfg "#000000" + -selectbg "#00c4ff" + } + + ttk::style configure . \ + -background $colors(-bg) \ + -foreground $colors(-fg) \ + -troughcolor $colors(-bg) \ + -focuscolor $colors(-selectbg) \ + -selectbackground $colors(-selectbg) \ + -selectforeground $colors(-selectfg) \ + -insertcolor $colors(-fg) \ + -insertwidth 1 \ + -fieldbackground $colors(-selectbg) \ + -font {"Segoe Ui" 10} \ + -borderwidth 1 \ + -relief flat + + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + + ttk::style map . -foreground [list disabled $colors(-disabledfg)] + + option add *font [ttk::style lookup . -font] + option add *Menu.selectcolor $colors(-fg) + + } elseif {$mode == "light"} { + ttk::style theme use "water-light" + + array set colors { + -fg "#000000" + -bg "#ffffff" + -disabledfg "#737373" + -disabledbg "#ffffff" + -selectfg "#000000" + -selectbg "#00c4ff" + } + + ttk::style configure . \ + -background $colors(-bg) \ + -foreground $colors(-fg) \ + -troughcolor $colors(-bg) \ + -focuscolor $colors(-selectbg) \ + -selectbackground $colors(-selectbg) \ + -selectforeground $colors(-selectfg) \ + -insertcolor $colors(-fg) \ + -insertwidth 1 \ + -fieldbackground $colors(-selectbg) \ + -font {"Segoe Ui" 10} \ + -borderwidth 1 \ + -relief flat + + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + + ttk::style map . -foreground [list disabled $colors(-disabledfg)] + + option add *font [ttk::style lookup . -font] + option add *Menu.selectcolor $colors(-fg) + } +} diff --git a/lx-icons/128/download.png b/lx-icons/128/download.png new file mode 100644 index 0000000..1589350 Binary files /dev/null and b/lx-icons/128/download.png differ diff --git a/lx-icons/128/download_error.png b/lx-icons/128/download_error.png new file mode 100644 index 0000000..011c99e Binary files /dev/null and b/lx-icons/128/download_error.png differ diff --git a/lx-icons/128/error.png b/lx-icons/128/error.png new file mode 100644 index 0000000..ae580f8 Binary files /dev/null and b/lx-icons/128/error.png differ diff --git a/lx-icons/128/info.png b/lx-icons/128/info.png new file mode 100644 index 0000000..ccba611 Binary files /dev/null and b/lx-icons/128/info.png differ diff --git a/lx-icons/128/log.png b/lx-icons/128/log.png new file mode 100644 index 0000000..06de63c Binary files /dev/null and b/lx-icons/128/log.png differ diff --git a/lx-icons/128/wg_export.png b/lx-icons/128/wg_export.png new file mode 100644 index 0000000..787e894 Binary files /dev/null and b/lx-icons/128/wg_export.png differ diff --git a/lx-icons/128/wg_import.png b/lx-icons/128/wg_import.png new file mode 100644 index 0000000..a65f8b2 Binary files /dev/null and b/lx-icons/128/wg_import.png differ diff --git a/lx-icons/128/wg_msg.png b/lx-icons/128/wg_msg.png new file mode 100644 index 0000000..dcc04cd Binary files /dev/null and b/lx-icons/128/wg_msg.png differ diff --git a/lx-icons/128/wg_trash.png b/lx-icons/128/wg_trash.png new file mode 100644 index 0000000..16b06b1 Binary files /dev/null and b/lx-icons/128/wg_trash.png differ diff --git a/lx-icons/128/wg_vpn-start.png b/lx-icons/128/wg_vpn-start.png new file mode 100644 index 0000000..2555e35 Binary files /dev/null and b/lx-icons/128/wg_vpn-start.png differ diff --git a/lx-icons/128/wg_vpn-stop.png b/lx-icons/128/wg_vpn-stop.png new file mode 100644 index 0000000..ff02259 Binary files /dev/null and b/lx-icons/128/wg_vpn-stop.png differ diff --git a/lx-icons/128/wg_vpn.png b/lx-icons/128/wg_vpn.png new file mode 100644 index 0000000..e800c31 Binary files /dev/null and b/lx-icons/128/wg_vpn.png differ diff --git a/lx-icons/256/download.png b/lx-icons/256/download.png new file mode 100644 index 0000000..614bf40 Binary files /dev/null and b/lx-icons/256/download.png differ diff --git a/lx-icons/256/download_error.png b/lx-icons/256/download_error.png new file mode 100644 index 0000000..e75bb67 Binary files /dev/null and b/lx-icons/256/download_error.png differ diff --git a/lx-icons/256/error.png b/lx-icons/256/error.png new file mode 100644 index 0000000..7734e3a Binary files /dev/null and b/lx-icons/256/error.png differ diff --git a/lx-icons/256/info.png b/lx-icons/256/info.png new file mode 100644 index 0000000..8c1ca4f Binary files /dev/null and b/lx-icons/256/info.png differ diff --git a/lx-icons/256/log.png b/lx-icons/256/log.png new file mode 100644 index 0000000..3921edb Binary files /dev/null and b/lx-icons/256/log.png differ diff --git a/lx-icons/256/wg_export.png b/lx-icons/256/wg_export.png new file mode 100644 index 0000000..49fd9b2 Binary files /dev/null and b/lx-icons/256/wg_export.png differ diff --git a/lx-icons/256/wg_import.png b/lx-icons/256/wg_import.png new file mode 100644 index 0000000..03cc502 Binary files /dev/null and b/lx-icons/256/wg_import.png differ diff --git a/lx-icons/256/wg_msg.png b/lx-icons/256/wg_msg.png new file mode 100644 index 0000000..76f84f4 Binary files /dev/null and b/lx-icons/256/wg_msg.png differ diff --git a/lx-icons/256/wg_trash.png b/lx-icons/256/wg_trash.png new file mode 100644 index 0000000..6e21206 Binary files /dev/null and b/lx-icons/256/wg_trash.png differ diff --git a/lx-icons/256/wg_vpn-start.png b/lx-icons/256/wg_vpn-start.png new file mode 100644 index 0000000..17f7bf0 Binary files /dev/null and b/lx-icons/256/wg_vpn-start.png differ diff --git a/lx-icons/256/wg_vpn-stop.png b/lx-icons/256/wg_vpn-stop.png new file mode 100644 index 0000000..a99bc5b Binary files /dev/null and b/lx-icons/256/wg_vpn-stop.png differ diff --git a/lx-icons/256/wg_vpn.png b/lx-icons/256/wg_vpn.png new file mode 100644 index 0000000..2aa3c20 Binary files /dev/null and b/lx-icons/256/wg_vpn.png differ diff --git a/lx-icons/32/download.png b/lx-icons/32/download.png new file mode 100644 index 0000000..e209fbd Binary files /dev/null and b/lx-icons/32/download.png differ diff --git a/lx-icons/32/download_error.png b/lx-icons/32/download_error.png new file mode 100644 index 0000000..595d04d Binary files /dev/null and b/lx-icons/32/download_error.png differ diff --git a/lx-icons/32/error.png b/lx-icons/32/error.png new file mode 100644 index 0000000..18e6c64 Binary files /dev/null and b/lx-icons/32/error.png differ diff --git a/lx-icons/32/info.png b/lx-icons/32/info.png new file mode 100644 index 0000000..f11ca97 Binary files /dev/null and b/lx-icons/32/info.png differ diff --git a/lx-icons/32/log.png b/lx-icons/32/log.png new file mode 100644 index 0000000..ebc7be1 Binary files /dev/null and b/lx-icons/32/log.png differ diff --git a/lx-icons/32/wg_export.png b/lx-icons/32/wg_export.png new file mode 100644 index 0000000..81bb65b Binary files /dev/null and b/lx-icons/32/wg_export.png differ diff --git a/lx-icons/32/wg_import.png b/lx-icons/32/wg_import.png new file mode 100644 index 0000000..dbaa7e7 Binary files /dev/null and b/lx-icons/32/wg_import.png differ diff --git a/lx-icons/32/wg_msg.png b/lx-icons/32/wg_msg.png new file mode 100644 index 0000000..5bd0115 Binary files /dev/null and b/lx-icons/32/wg_msg.png differ diff --git a/lx-icons/32/wg_trash.png b/lx-icons/32/wg_trash.png new file mode 100644 index 0000000..57f604d Binary files /dev/null and b/lx-icons/32/wg_trash.png differ diff --git a/lx-icons/32/wg_vpn-start.png b/lx-icons/32/wg_vpn-start.png new file mode 100644 index 0000000..149142f Binary files /dev/null and b/lx-icons/32/wg_vpn-start.png differ diff --git a/lx-icons/32/wg_vpn-stop.png b/lx-icons/32/wg_vpn-stop.png new file mode 100644 index 0000000..f29cbe6 Binary files /dev/null and b/lx-icons/32/wg_vpn-stop.png differ diff --git a/lx-icons/32/wg_vpn.png b/lx-icons/32/wg_vpn.png new file mode 100644 index 0000000..55df4dd Binary files /dev/null and b/lx-icons/32/wg_vpn.png differ diff --git a/lx-icons/48/download.png b/lx-icons/48/download.png new file mode 100644 index 0000000..4302a7c Binary files /dev/null and b/lx-icons/48/download.png differ diff --git a/lx-icons/48/download_error.png b/lx-icons/48/download_error.png new file mode 100644 index 0000000..96ec900 Binary files /dev/null and b/lx-icons/48/download_error.png differ diff --git a/lx-icons/48/error.png b/lx-icons/48/error.png new file mode 100644 index 0000000..92a731a Binary files /dev/null and b/lx-icons/48/error.png differ diff --git a/lx-icons/48/info.png b/lx-icons/48/info.png new file mode 100644 index 0000000..52206e6 Binary files /dev/null and b/lx-icons/48/info.png differ diff --git a/lx-icons/48/log.png b/lx-icons/48/log.png new file mode 100644 index 0000000..971a013 Binary files /dev/null and b/lx-icons/48/log.png differ diff --git a/lx-icons/48/wg_export.png b/lx-icons/48/wg_export.png new file mode 100644 index 0000000..73fb2aa Binary files /dev/null and b/lx-icons/48/wg_export.png differ diff --git a/lx-icons/48/wg_import.png b/lx-icons/48/wg_import.png new file mode 100644 index 0000000..dd888b9 Binary files /dev/null and b/lx-icons/48/wg_import.png differ diff --git a/lx-icons/48/wg_msg.png b/lx-icons/48/wg_msg.png new file mode 100644 index 0000000..732eabb Binary files /dev/null and b/lx-icons/48/wg_msg.png differ diff --git a/lx-icons/48/wg_trash.png b/lx-icons/48/wg_trash.png new file mode 100644 index 0000000..3e1538c Binary files /dev/null and b/lx-icons/48/wg_trash.png differ diff --git a/lx-icons/48/wg_vpn-start.png b/lx-icons/48/wg_vpn-start.png new file mode 100644 index 0000000..e3f078d Binary files /dev/null and b/lx-icons/48/wg_vpn-start.png differ diff --git a/lx-icons/48/wg_vpn-stop.png b/lx-icons/48/wg_vpn-stop.png new file mode 100644 index 0000000..30aa9cc Binary files /dev/null and b/lx-icons/48/wg_vpn-stop.png differ diff --git a/lx-icons/48/wg_vpn.png b/lx-icons/48/wg_vpn.png new file mode 100644 index 0000000..cec4506 Binary files /dev/null and b/lx-icons/48/wg_vpn.png differ diff --git a/lx-icons/64/download.png b/lx-icons/64/download.png new file mode 100644 index 0000000..cc12d8a Binary files /dev/null and b/lx-icons/64/download.png differ diff --git a/lx-icons/64/download_error.png b/lx-icons/64/download_error.png new file mode 100644 index 0000000..0cd4161 Binary files /dev/null and b/lx-icons/64/download_error.png differ diff --git a/lx-icons/64/error.png b/lx-icons/64/error.png new file mode 100644 index 0000000..5ac3858 Binary files /dev/null and b/lx-icons/64/error.png differ diff --git a/lx-icons/64/info.png b/lx-icons/64/info.png new file mode 100644 index 0000000..4917814 Binary files /dev/null and b/lx-icons/64/info.png differ diff --git a/lx-icons/64/log.png b/lx-icons/64/log.png new file mode 100644 index 0000000..e1eb8dc Binary files /dev/null and b/lx-icons/64/log.png differ diff --git a/lx-icons/64/wg_export.png b/lx-icons/64/wg_export.png new file mode 100644 index 0000000..7d14d17 Binary files /dev/null and b/lx-icons/64/wg_export.png differ diff --git a/lx-icons/64/wg_import.png b/lx-icons/64/wg_import.png new file mode 100644 index 0000000..c9b0c85 Binary files /dev/null and b/lx-icons/64/wg_import.png differ diff --git a/lx-icons/64/wg_msg.png b/lx-icons/64/wg_msg.png new file mode 100644 index 0000000..d821d13 Binary files /dev/null and b/lx-icons/64/wg_msg.png differ diff --git a/lx-icons/64/wg_trash.png b/lx-icons/64/wg_trash.png new file mode 100644 index 0000000..1655e17 Binary files /dev/null and b/lx-icons/64/wg_trash.png differ diff --git a/lx-icons/64/wg_vpn-start.png b/lx-icons/64/wg_vpn-start.png new file mode 100644 index 0000000..3eeb910 Binary files /dev/null and b/lx-icons/64/wg_vpn-start.png differ diff --git a/lx-icons/64/wg_vpn-stop.png b/lx-icons/64/wg_vpn-stop.png new file mode 100644 index 0000000..03a5044 Binary files /dev/null and b/lx-icons/64/wg_vpn-stop.png differ diff --git a/lx-icons/64/wg_vpn.png b/lx-icons/64/wg_vpn.png new file mode 100644 index 0000000..d69ef68 Binary files /dev/null and b/lx-icons/64/wg_vpn.png differ diff --git a/lxtools_installerv3.py b/lxtools_installerv3.py new file mode 100755 index 0000000..f531367 --- /dev/null +++ b/lxtools_installerv3.py @@ -0,0 +1,1855 @@ +#!/usr/bin/python3 +import gettext +import locale +import tkinter as tk +from tkinter import messagebox, ttk +import os +import socket +import subprocess +import tempfile +import urllib.request +import zipfile +import json +import stat +from pathlib import Path +from datetime import datetime + + +class LXToolsAppConfig: + VERSION = "1.1.4" + APP_NAME = "Lunix Tools Installer" + WINDOW_WIDTH = 450 + WINDOW_HEIGHT = 580 + + # Working directory + WORK_DIR = os.getcwd() + ICONS_DIR = os.path.join(WORK_DIR, "lx-icons") + THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes") + + # Locale settings + LOCALE_DIR = Path("/usr/share/locale/") + + # Download URLs + WIREPY_URL = "https://git.ilunix.de/punix/Wire-Py/archive/main.zip" + SHARED_LIBS_URL = "https://git.ilunix.de/punix/shared_libs/archive/main.zip" + + # API URLs for version checking + WIREPY_API_URL = "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases" + SHARED_LIBS_API_URL = ( + "https://git.ilunix.de/api/v1/repos/punix/shared_libs/releases" + ) + + # Project configurations + PROJECTS = { + "wirepy": { + "name": "Wire-Py", + "description": "WireGuard VPN Manager with GUI", + "download_url": WIREPY_URL, + "api_url": WIREPY_API_URL, + "icon_key": "icon_vpn", + "main_executable": "wirepy.py", + "symlink_name": "wirepy", + "config_file": "wp_app_config.py", + "desktop_file": "Wire-Py.desktop", + "policy_file": "org.sslcrypt.policy", + "requires_ssl": True, + }, + "logviewer": { + "name": "LogViewer", + "description": "System Log Viewer with GUI", + "download_url": SHARED_LIBS_URL, + "api_url": SHARED_LIBS_API_URL, + "icon_key": "icon_log", + "main_executable": "logviewer.py", + "symlink_name": "logviewer", + "config_file": "logview_app_config.py", + "desktop_file": "LogViewer.desktop", + "policy_file": None, + "requires_ssl": False, + }, + } + + # OS Detection List (order matters - specific first, generic last) + OS_DETECTION = [ + ("mint", "Linux Mint"), + ("pop", "Pop!_OS"), + ("manjaro", "Manjaro"), + ("garuda", "Garuda Linux"), + ("endeavouros", "EndeavourOS"), + ("fedora", "Fedora"), + ("tumbleweed", "SUSE Tumbleweed"), + ("leap", "SUSE Leap"), + ("arch", "Arch Linux"), + ("ubuntu", "Ubuntu"), + ("debian", "Debian"), + ] + + # Package manager commands for TKinter installation + TKINTER_INSTALL_COMMANDS = { + "Ubuntu": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], + "Debian": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], + "Linux Mint": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], + "Pop!_OS": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], + "Fedora": ["dnf", "install", "-y", "tkinter"], + "Arch Linux": ["pacman", "-S", "--noconfirm", "tk"], + "Manjaro": ["pacman", "-S", "--noconfirm", "tk"], + "Garuda Linux": ["pacman", "-S", "--noconfirm", "tk"], + "EndeavourOS": ["pacman", "-S", "--noconfirm", "tk"], + "SUSE Tumbleweed": ["zypper", "install", "-y", "python314-tk"], + "SUSE Leap": ["zypper", "install", "-y", "python312-tk"], + } + + @staticmethod + def setup_translations(): + """Initialize translations and set the translation function""" + try: + locale.bindtextdomain( + LXToolsAppConfig.APP_NAME, LXToolsAppConfig.LOCALE_DIR + ) + gettext.bindtextdomain( + LXToolsAppConfig.APP_NAME, LXToolsAppConfig.LOCALE_DIR + ) + gettext.textdomain(LXToolsAppConfig.APP_NAME) + except: + pass + return gettext.gettext + + +# Initialize translations +_ = LXToolsAppConfig.setup_translations() + + +class ColorManager: + @staticmethod + def get_system_colors(root): + """Get system colors that work across different themes""" + try: + # Versuche system-spezifische Farben zu ermitteln + default_bg = root.cget("bg") + + # Teste verschiedene Farbnamen + test_colors = ["#f0f0f0", "#e1e1e1", "#d9d9d9", "lightgray"] + working_bg = default_bg + + for color in test_colors: + try: + # Teste ob die Farbe funktioniert + test_label = tk.Label(root, bg=color) + working_bg = color + test_label.destroy() + break + except tk.TclError: + continue + + return { + "default_bg": working_bg, + "card_bg": "#f8f9fa", # Hellerer Hintergrund für Cards + "hover_bg": "#e6f3ff", + "selected_bg": "#cce7ff", + "border_color": "#cccccc", + "header_bg": "#2c3e50", # Dunkler Header + "header_fg": "white", # Weiße Schrift im Header + "progress_bg": "#f1f3f4", # Wie Progress Label + } + except: + # Fallback Farben + return { + "default_bg": "#f0f0f0", + "card_bg": "#f8f9fa", + "hover_bg": "#e6f3ff", + "selected_bg": "#cce7ff", + "border_color": "#cccccc", + "header_bg": "#2c3e50", + "header_fg": "white", + "progress_bg": "#f1f3f4", + } + + +class ThemeManager: + @staticmethod + def apply_light_theme(root): + """Apply light theme""" + try: + theme_dir = LXToolsAppConfig.THEMES_DIR + water_theme_path = os.path.join(theme_dir, "water.tcl") + + if os.path.exists(water_theme_path): + try: + root.tk.call("source", water_theme_path) + root.tk.call("set_theme", "light") + return True + except tk.TclError: + pass + + # System theme fallback + try: + style = ttk.Style() + if "clam" in style.theme_names(): + style.theme_use("clam") + return True + except: + pass + + except Exception: + pass + + return False + + +class OSDetector: + @staticmethod + def detect_os(): + """Detect operating system using ordered list""" + try: + with open("/etc/os-release", "r") as f: + content = f.read().lower() + + # Check each OS in order (specific first) + for keyword, os_name in LXToolsAppConfig.OS_DETECTION: + if keyword in content: + return os_name + + return "Unknown System" + except FileNotFoundError: + return "File not found" + + @staticmethod + def check_tkinter_available(): + """Check if tkinter is available""" + try: + import tkinter + + return True + except ImportError: + return False + + @staticmethod + def install_tkinter(): + """Install tkinter based on detected OS""" + detected_os = OSDetector.detect_os() + + if detected_os in LXToolsAppConfig.TKINTER_INSTALL_COMMANDS: + commands = LXToolsAppConfig.TKINTER_INSTALL_COMMANDS[detected_os] + + print(f"Installing tkinter for {detected_os}...") + print(f"Command: {' '.join(commands)}") + + try: + # Use pkexec for privilege escalation + full_command = ["pkexec", "bash", "-c", " ".join(commands)] + result = subprocess.run( + full_command, capture_output=True, text=True, timeout=300 + ) + + if result.returncode == 0: + print("TKinter installation completed successfully!") + return True + else: + print(f"TKinter installation failed: {result.stderr}") + return False + + except subprocess.TimeoutExpired: + print("TKinter installation timed out") + return False + except Exception as e: + print(f"Error installing tkinter: {e}") + return False + else: + print(f"No tkinter installation command defined for {detected_os}") + return False + + +class SystemManager: + @staticmethod + def create_directories(directories): + """Create system directories using pkexec""" + for directory in directories: + subprocess.run(["pkexec", "mkdir", "-p", directory], check=True) + + @staticmethod + def copy_file(src, dest, make_executable=False): + """Copy file using pkexec""" + subprocess.run(["pkexec", "cp", src, dest], check=True) + if make_executable: + subprocess.run(["pkexec", "chmod", "755", dest], check=True) + + @staticmethod + def copy_directory(src, dest): + """Copy directory using pkexec""" + subprocess.run(["pkexec", "cp", "-r", src, dest], check=True) + + @staticmethod + def remove_file(path): + """Remove file using pkexec""" + subprocess.run(["pkexec", "rm", "-f", path], check=False) + + @staticmethod + def remove_directory(path): + """Remove directory using pkexec""" + subprocess.run(["pkexec", "rm", "-rf", path], check=False) + + @staticmethod + def create_symlink(target, link_name): + """Create symbolic link using pkexec""" + subprocess.run(["pkexec", "rm", "-f", link_name], check=False) + subprocess.run(["pkexec", "ln", "-sf", target, link_name], check=True) + + @staticmethod + def create_ssl_key(pem_file): + """Create SSL key using pkexec""" + try: + subprocess.run( + ["pkexec", "openssl", "genrsa", "-out", pem_file, "4096"], check=True + ) + subprocess.run(["pkexec", "chmod", "600", pem_file], check=True) + return True + except subprocess.CalledProcessError: + return False + + +class ImageManager: + def __init__(self): + self.images = {} + + def load_image(self, image_key, fallback_paths=None): + """Load PNG image using tk.PhotoImage with fallback options""" + if image_key in self.images: + return self.images[image_key] + + # Define image paths based on key + image_paths = { + "app_icon": [ + "./lx-icons/48/wg_vpn.png", + "/usr/share/icons/lx-icons/48/wg_vpn.png", + ], + "download_icon": [ + "./lx-icons/32/download.png", + "/usr/share/icons/lx-icons/32/download.png", + ], + "download_error_icon": [ + "./lx-icons/32/download_error.png", + "/usr/share/icons/lx-icons/32/download_error.png", + ], + "success_icon": [ + "./lx-icons/32/download.png", + "/usr/share/icons/lx-icons/32/download.png", + ], + "icon_vpn": [ + "./lx-icons/48/wg_vpn.png", + "/usr/share/icons/lx-icons/48/wg_vpn.png", + ], + "icon_log": [ + "./lx-icons/48/log.png", + "/usr/share/icons/lx-icons/48/log.png", + ], + } + + # Get paths to try + paths_to_try = image_paths.get(image_key, []) + + # Add fallback paths if provided + if fallback_paths: + paths_to_try.extend(fallback_paths) + + # Try to load image from paths + for path in paths_to_try: + try: + if os.path.exists(path): + photo = tk.PhotoImage(file=path) + self.images[image_key] = photo + return photo + except tk.TclError as e: + print(f"Failed to load image from {path}: {e}") + continue + + # Return None if no image found + return None + + +class GiteaUpdate: + @staticmethod + def api_down(url, current_version=""): + """Get latest version from Gitea API""" + try: + with urllib.request.urlopen(url, timeout=10) as response: + data = json.loads(response.read().decode()) + if data and len(data) > 0: + latest_version = data[0].get("tag_name", "Unknown") + return latest_version.lstrip("v") # Remove 'v' prefix if present + return "Unknown" + except Exception as e: + print(f"API Error: {e}") + return "Unknown" + + +class NetworkChecker: + @staticmethod + def check_internet_connection(host="8.8.8.8", port=53, timeout=3): + """Check if internet connection is available""" + try: + socket.setdefaulttimeout(timeout) + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + return True + except socket.error: + return False + + @staticmethod + def check_repository_access(url="https://git.ilunix.de", timeout=5): + """Check if repository is accessible""" + try: + urllib.request.urlopen(url, timeout=timeout) + return True + except: + return False + + +class DownloadManager: + @staticmethod + def download_and_extract(url, extract_to, progress_callback=None): + """Download and extract ZIP file""" + try: + if progress_callback: + progress_callback(f"Downloading from {url}...") + + with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file: + urllib.request.urlretrieve(url, tmp_file.name) + + if progress_callback: + progress_callback("Extracting files...") + + with zipfile.ZipFile(tmp_file.name, "r") as zip_ref: + zip_ref.extractall(extract_to) + + os.unlink(tmp_file.name) + return True + + except Exception as e: + if progress_callback: + progress_callback(f"Download failed: {str(e)}") + return False + + +class AppManager: + def __init__(self): + self.projects = LXToolsAppConfig.PROJECTS + + def get_project_info(self, project_key): + """Get project information by key""" + return self.projects.get(project_key) + + def get_all_projects(self): + """Get all project configurations""" + return self.projects + + def is_installed(self, project_key): + """Check if project is installed with better detection""" + if project_key == "wirepy": + # Check for wirepy symlink + return os.path.exists("/usr/local/bin/wirepy") and os.path.islink( + "/usr/local/bin/wirepy" + ) + + elif project_key == "logviewer": + # Check for logviewer symlink AND executable file + symlink_exists = os.path.exists("/usr/local/bin/logviewer") + executable_exists = os.path.exists( + "/usr/lib/python3/dist-packages/shared_libs/logviewer.py" + ) + executable_is_executable = False + + if executable_exists: + try: + # Check if file is executable + file_stat = os.stat( + "/usr/lib/python3/dist-packages/shared_libs/logviewer.py" + ) + executable_is_executable = bool(file_stat.st_mode & stat.S_IEXEC) + except: + executable_is_executable = False + + # LogViewer is installed if symlink exists AND executable file exists AND is executable + is_installed = ( + symlink_exists and executable_exists and executable_is_executable + ) + + # Debug logging + print(f"LogViewer installation check:") + print(f" Symlink exists: {symlink_exists}") + print(f" Executable exists: {executable_exists}") + print(f" Is executable: {executable_is_executable}") + print(f" Final result: {is_installed}") + + return is_installed + + return False + + def get_installed_version(self, project_key): + """Get installed version from config file""" + try: + if project_key == "wirepy": + config_file = ( + "/usr/lib/python3/dist-packages/shared_libs/wp_app_config.py" + ) + elif project_key == "logviewer": + config_file = ( + "/usr/lib/python3/dist-packages/shared_libs/logview_app_config.py" + ) + else: + return "Unknown" + + if os.path.exists(config_file): + with open(config_file, "r") as f: + content = f.read() + for line in content.split("\n"): + if "VERSION" in line and "=" in line: + version = line.split("=")[1].strip().strip("\"'") + return version + return "Unknown" + except Exception as e: + print(f"Error getting version for {project_key}: {e}") + return "Unknown" + + def get_latest_version(self, project_key): + """Get latest version from API""" + project_info = self.get_project_info(project_key) + if not project_info: + return "Unknown" + + return GiteaUpdate.api_down(project_info["api_url"]) + + def check_other_apps_installed(self, exclude_key): + """Check if other apps are still installed""" + return any( + self.is_installed(key) for key in self.projects.keys() if key != exclude_key + ) + + +class InstallationManager: + def __init__( + self, app_manager, progress_callback=None, icon_callback=None, log_callback=None + ): + self.app_manager = app_manager + self.progress_callback = progress_callback + self.icon_callback = icon_callback + self.log_callback = log_callback + self.system_manager = SystemManager() + self.download_manager = DownloadManager() + + def install_project(self, project_key): + """Install or update project""" + project_info = self.app_manager.get_project_info(project_key) + if not project_info: + raise Exception(f"Unknown project: {project_key}") + + self.update_progress(f"Starting installation of {project_info['name']}...") + self.log(f"=== Installing {project_info['name']} ===") + + try: + # Create installation script + script_content = self._create_install_script(project_key) + + # Execute installation + self._execute_install_script(script_content) + + self.update_progress( + f"{project_info['name']} installation completed successfully!" + ) + self.log(f"=== {project_info['name']} installed successfully ===") + + # Set success icon + self.update_icon("success") + + except Exception as e: + self.log(f"ERROR: Installation failed: {e}") + self.update_icon("error") + raise Exception(f"Installation failed: {e}") + + def _create_install_script(self, project_key): + """Create installation script based on project""" + if project_key == "wirepy": + return self._create_wirepy_install_script() + elif project_key == "logviewer": + return self._create_logviewer_install_script() + else: + raise Exception(f"Unknown project: {project_key}") + + def _create_wirepy_install_script(self): + """Create Wire-Py installation script""" + script = f"""#!/bin/bash +set -e + +echo "=== Wire-Py Installation ===" + +# Create necessary directories +mkdir -p /usr/lib/python3/dist-packages/shared_libs +mkdir -p /usr/share/icons/lx-icons +mkdir -p /usr/share/locale/de/LC_MESSAGES +mkdir -p /usr/share/applications +mkdir -p /usr/local/etc/ssl +mkdir -p /usr/share/polkit-1/actions +mkdir -p /usr/share/TK-Themes + +# Download and extract Wire-Py +cd /tmp +rm -rf wirepy_install +mkdir wirepy_install +cd wirepy_install + +echo "Downloading Wire-Py..." +wget -q "{LXToolsAppConfig.WIREPY_URL}" -O wirepy.zip +unzip -q wirepy.zip +WIREPY_DIR=$(find . -name "wire-py" -type d | head -1) + +echo "Downloading shared libraries..." +wget -q "{LXToolsAppConfig.SHARED_LIBS_URL}" -O shared.zip +unzip -q shared.zip +SHARED_DIR=$(find . -name "shared_libs" -type d | head -1) + +# Install Wire-Py files +echo "Installing Wire-Py executables..." +for file in wirepy.py start_wg.py ssl_encrypt.py ssl_decrypt.py match_found.py tunnel.py; do + if [ -f "$WIREPY_DIR/$file" ]; then + cp -f "$WIREPY_DIR/$file" /usr/local/bin/ + chmod 755 /usr/local/bin/$file + echo "Installed $file" + fi +done + +# Install config +if [ -f "$WIREPY_DIR/wp_app_config.py" ]; then + cp -f "$WIREPY_DIR/wp_app_config.py" /usr/lib/python3/dist-packages/shared_libs/ + echo "Installed wp_app_config.py" +fi + +# Install shared libraries +echo "Installing shared libraries..." +for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do + if [ -f "$SHARED_DIR/$file" ]; then + cp -f "$SHARED_DIR/$file" /usr/lib/python3/dist-packages/shared_libs/ + echo "Installed shared lib: $file" + fi +done + +# Install LogViewer executable +if [ -f "$SHARED_DIR/logviewer.py" ]; then + cp -f "$SHARED_DIR/logviewer.py" /usr/lib/python3/dist-packages/shared_libs/ + chmod 755 /usr/lib/python3/dist-packages/shared_libs/logviewer.py + echo "Installed logviewer.py (executable)" +fi + +# Install icons +if [ -d "$WIREPY_DIR/lx-icons" ]; then + echo "Installing icons..." + cp -rf "$WIREPY_DIR/lx-icons"/* /usr/share/icons/lx-icons/ +fi + +# Install TK-Themes +if [ -d "$WIREPY_DIR/TK-Themes" ]; then + echo "Installing TK-Themes..." + cp -rf "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/ +fi + +# Install desktop file +if [ -f "$WIREPY_DIR/Wire-Py.desktop" ]; then + cp -f "$WIREPY_DIR/Wire-Py.desktop" /usr/share/applications/ + echo "Installed desktop file" +fi + +# Install language files +if [ -d "$WIREPY_DIR/languages/de" ]; then + echo "Installing language files..." + cp -f "$WIREPY_DIR/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true +fi + +# Install policy file +if [ -f "$WIREPY_DIR/org.sslcrypt.policy" ]; then + cp -f "$WIREPY_DIR/org.sslcrypt.policy" /usr/share/polkit-1/actions/ + echo "Installed policy file" +fi + +# Create symlink for Wirepy +ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy +# Create symlink for LogViewer +ln -sf /usr/lib/python3/dist-packages/shared_libs/logviewer.py /usr/local/bin/logviewer +echo "Created Wirepy and LogViewer symlink" + +# Install language files if available +if [ -d "$SHARED_DIR/languages/de" ]; then + echo "Installing language files..." + cp "$SHARED_DIR/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true +fi +echo "Created symlink" + +# Create SSL key if not exists +if [ ! -f /usr/local/etc/ssl/pwgk.pem ]; then + echo "Creating SSL key..." + openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 + chmod 600 /usr/local/etc/ssl/pwgk.pem +fi + +# Cleanup +cd /tmp +rm -rf wirepy_install + +echo "Wire-Py installation completed!" +""" + return script + + def _create_logviewer_install_script(self): + """Create LogViewer installation script""" + script = f"""#!/bin/bash +set -e + +echo "=== LogViewer Installation ===" + +# Create necessary directories +mkdir -p /usr/lib/python3/dist-packages/shared_libs +mkdir -p /usr/share/icons/lx-icons +mkdir -p /usr/share/locale/de/LC_MESSAGES +mkdir -p /usr/share/applications +mkdir -p /usr/share/TK-Themes + +# Download and extract shared libraries (contains LogViewer) +cd /tmp +rm -rf logviewer_install +mkdir logviewer_install +cd logviewer_install + +echo "Downloading LogViewer and shared libraries..." +wget -q "{LXToolsAppConfig.SHARED_LIBS_URL}" -O shared.zip +unzip -q shared.zip +SHARED_DIR=$(find . -name "shared_libs" -type d | head -1) + +# Check if TK-Themes exists, if not download Wire-Py for themes +if [ ! -d "/usr/share/TK-Themes" ] || [ -z "$(ls -A /usr/share/TK-Themes 2>/dev/null)" ]; then + echo "TK-Themes not found, downloading from Wire-Py..." + wget -q "{LXToolsAppConfig.WIREPY_URL}" -O wirepy.zip + unzip -q wirepy.zip + WIREPY_DIR=$(find . -name "Wire-Py" -type d | head -1) + + if [ -d "$WIREPY_DIR/TK-Themes" ]; then + echo "Installing TK-Themes..." + cp -rf "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/ + fi + + # Also install icons from Wire-Py if not present + if [ -d "$WIREPY_DIR/lx-icons" ] && [ ! -d "/usr/share/icons/lx-icons" ]; then + echo "Installing icons from Wire-Py..." + cp -rf "$WIREPY_DIR/lx-icons"/* /usr/share/icons/lx-icons/ + fi +fi + +# Install shared libraries +echo "Installing shared libraries..." +for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do + if [ -f "$SHARED_DIR/$file" ]; then + cp -f "$SHARED_DIR/$file" /usr/lib/python3/dist-packages/shared_libs/ + echo "Installed shared lib: $file" + fi +done + +# Install LogViewer executable +if [ -f "$SHARED_DIR/logviewer.py" ]; then + cp -f "$SHARED_DIR/logviewer.py" /usr/lib/python3/dist-packages/shared_libs/ + chmod 755 /usr/lib/python3/dist-packages/shared_libs/logviewer.py + echo "Installed logviewer.py (executable)" +fi + +# Create LogViewer desktop file +cat > /usr/share/applications/LogViewer.desktop << 'EOF' +[Desktop Entry] +Version=1.0 +Type=Application +Name=LogViewer +Comment=System Log Viewer +Exec=/usr/local/bin/logviewer +Icon=/usr/share/icons/lx-icons/48/log.png +Terminal=false +Categories=System;Utility; +StartupNotify=true +EOF + +echo "Created LogViewer desktop file" + +# Create symlink for LogViewer +ln -sf /usr/lib/python3/dist-packages/shared_libs/logviewer.py /usr/local/bin/logviewer +echo "Created LogViewer symlink" + +# Install language files if available +if [ -d "$SHARED_DIR/languages/de" ]; then + echo "Installing language files..." + cp "$SHARED_DIR/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true +fi + +# Cleanup +cd /tmp +rm -rf logviewer_install + +echo "LogViewer installation completed!" +""" + return script + + def _execute_install_script(self, script_content): + """Execute installation script with pkexec""" + try: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".sh", delete=False + ) as script_file: + script_file.write(script_content) + script_file.flush() + + # Make script executable + os.chmod(script_file.name, 0o755) + self.log(f"Created install script: {script_file.name}") + + # Execute with pkexec + result = subprocess.run( + ["pkexec", "bash", script_file.name], + capture_output=True, + text=True, + timeout=300, # 5 minutes timeout + ) + + # Log output + if result.stdout: + self.log(f"STDOUT: {result.stdout}") + if result.stderr: + self.log(f"STDERR: {result.stderr}") + + # Clean up + os.unlink(script_file.name) + + if result.returncode != 0: + raise Exception(f"Installation script failed: {result.stderr}") + + except subprocess.TimeoutExpired: + raise Exception("Installation timed out") + except subprocess.CalledProcessError as e: + raise Exception(f"Installation script failed: {e}") + + def update_progress(self, message): + if self.progress_callback: + self.progress_callback(message) + + def update_icon(self, status): + if self.icon_callback: + self.icon_callback(status) + + def log(self, message): + if self.log_callback: + self.log_callback(message) + + +class UninstallationManager: + def __init__(self, app_manager, progress_callback=None, log_callback=None): + self.app_manager = app_manager + self.progress_callback = progress_callback + self.log_callback = log_callback + + def uninstall_project(self, project_key): + """Uninstall project""" + project_info = self.app_manager.get_project_info(project_key) + if not project_info: + raise Exception(f"Unknown project: {project_key}") + + if not self.app_manager.is_installed(project_key): + raise Exception(f"{project_info['name']} is not installed.") + + self.update_progress(f"Uninstalling {project_info['name']}...") + self.log(f"=== Uninstalling {project_info['name']} ===") + + try: + # Create uninstallation script + script_content = self._create_uninstall_script(project_key) + + # Execute uninstallation + self._execute_uninstall_script(script_content) + + self.update_progress(f"{project_info['name']} uninstalled successfully!") + self.log(f"=== {project_info['name']} uninstalled successfully ===") + + except Exception as e: + self.log(f"ERROR: Uninstallation failed: {e}") + raise Exception(f"Uninstallation failed: {e}") + + def _create_uninstall_script(self, project_key): + """Create uninstallation script based on project""" + if project_key == "wirepy": + return self._create_wirepy_uninstall_script() + elif project_key == "logviewer": + return self._create_logviewer_uninstall_script() + else: + raise Exception(f"Unknown project: {project_key}") + + def _create_wirepy_uninstall_script(self): + """Create Wire-Py uninstallation script""" + script = """#!/bin/bash +set -e + +echo "=== Wire-Py Uninstallation ===" + +# Remove Wire-Py executables +echo "Removing Wire-Py executables..." +for file in wirepy.py start_wg.py ssl_encrypt.py ssl_decrypt.py match_found.py tunnel.py; do + rm -f /usr/local/bin/$file + echo "Removed $file" +done + +# Remove symlink +rm -f /usr/local/bin/wirepy +echo "Removed wirepy symlink" + +# Remove config +rm -f /usr/lib/python3/dist-packages/shared_libs/wp_app_config.py +echo "Removed wp_app_config.py" + +# Remove desktop file +rm -f /usr/share/applications/Wire-Py.desktop +echo "Removed desktop file" + +# Remove policy file +rm -f /usr/share/polkit-1/actions/org.sslcrypt.policy +echo "Removed policy file" + +# Remove language files +rm -f /usr/share/locale/de/LC_MESSAGES/wirepy.mo +echo "Removed language files" + +# Remove user config directory +if [ -d "$HOME/.config/wire_py" ]; then + rm -rf "$HOME/.config/wire_py" + echo "Removed user config directory" +fi + +# Remove log file +rm -f "$HOME/.local/share/lxlogs/wirepy.log" +echo "Removed log file" + +# Check if LogViewer is still installed before removing shared resources +if [ ! -f /usr/local/bin/logviewer ] || [ ! -L /usr/local/bin/logviewer ]; then + echo "No other LX apps found, removing shared resources..." + rm -rf /usr/share/icons/lx-icons + rm -rf /usr/share/TK-Themes + rm -rf /usr/local/etc/ssl + + # Remove shared libs + for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py logviewer.py; do + rm -f /usr/lib/python3/dist-packages/shared_libs/$file + done + + # Try to remove shared_libs directory if empty + rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true +else + echo "LogViewer still installed, keeping shared resources" +fi + +echo "Wire-Py uninstallation completed!" +""" + return script + + def _create_logviewer_uninstall_script(self): + """Create LogViewer uninstallation script""" + script = """#!/bin/bash +set -e + +echo "=== LogViewer Uninstallation ===" + +# Remove LogViewer symlink +rm -f /usr/local/bin/logviewer +echo "Removed logviewer symlink" + +# Remove desktop file +rm -f /usr/share/applications/LogViewer.desktop +echo "Removed desktop file" + +# Remove language files +rm -f /usr/share/locale/de/LC_MESSAGES/logviewer.mo +echo "Removed language files" + +# Remove user config directory +if [ -d "$HOME/.config/logviewer" ]; then + rm -rf "$HOME/.config/logviewer" + echo "Removed user config directory" +fi + +# Remove log file +rm -f "$HOME/.local/share/lxlogs/logviewer.log" +echo "Removed log file" + +# Check if Wire-Py is still installed before removing shared resources +if [ ! -f /usr/local/bin/wirepy ] || [ ! -L /usr/local/bin/wirepy ]; then + echo "No other LX apps found, removing shared resources..." + rm -rf /usr/share/icons/lx-icons + rm -rf /usr/share/TK-Themes + + # Remove shared libs (but keep logviewer.py if we're only uninstalling logviewer) + for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do + rm -f /usr/lib/python3/dist-packages/shared_libs/$file + done + + # Remove logviewer.py last + rm -f /usr/lib/python3/dist-packages/shared_libs/logviewer.py + + # Try to remove shared_libs directory if empty + rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true +else + echo "Wire-Py still installed, keeping shared resources" + # Only remove logviewer-specific files + rm -f /usr/lib/python3/dist-packages/shared_libs/logview_app_config.py + rm -f /usr/lib/python3/dist-packages/shared_libs/logviewer.py +fi + +echo "LogViewer uninstallation completed!" +""" + return script + + def _execute_uninstall_script(self, script_content): + """Execute uninstallation script with pkexec""" + try: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".sh", delete=False + ) as script_file: + script_file.write(script_content) + script_file.flush() + + # Make script executable + os.chmod(script_file.name, 0o755) + self.log(f"Created uninstall script: {script_file.name}") + + # Execute with pkexec + result = subprocess.run( + ["pkexec", "bash", script_file.name], + capture_output=True, + text=True, + timeout=120, + ) + + # Log output + if result.stdout: + self.log(f"STDOUT: {result.stdout}") + if result.stderr: + self.log(f"STDERR: {result.stderr}") + + # Clean up + os.unlink(script_file.name) + + if result.returncode != 0: + raise Exception(f"Uninstallation script failed: {result.stderr}") + + except subprocess.TimeoutExpired: + raise Exception("Uninstallation timed out") + except subprocess.CalledProcessError as e: + raise Exception(f"Uninstallation script failed: {e}") + + def update_progress(self, message): + if self.progress_callback: + self.progress_callback(message) + + def log(self, message): + if self.log_callback: + self.log_callback(message) + + +class LXToolsGUI: + def __init__(self): + self.root = None + self.notebook = None + self.progress_label = None + self.download_icon_label = None + self.log_text = None + self.selected_project = None + self.project_frames = {} + self.status_labels = {} + self.version_labels = {} + + # Managers + self.app_manager = AppManager() + self.installation_manager = InstallationManager( + self.app_manager, + self.update_progress, + self.update_download_icon, + self.log_message, + ) + self.uninstallation_manager = UninstallationManager( + self.app_manager, self.update_progress, self.log_message + ) + self.image_manager = ImageManager() + + # Detect OS + self.detected_os = OSDetector.detect_os() + + # Color scheme + self.colors = { + "bg": "#f8f9fa", + "card_bg": "#ffffff", + "hover_bg": "#e3f2fd", + "selected_bg": "#bbdefb", + "progress_bg": "#f8f9fa", + "text": "#2c3e50", + "accent": "#3498db", + } + + def create_gui(self): + """Create the main GUI""" + self.root = tk.Tk() + self.root.title(f"{LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION}") + self.root.geometry( + f"{LXToolsAppConfig.WINDOW_WIDTH}x{LXToolsAppConfig.WINDOW_HEIGHT}" + ) + + self.root.configure(bg=self.colors["bg"]) + + # Try to set icon + try: + icon = self.image_manager.load_image("download_icon") + if icon: + self.root.iconphoto(False, icon) + except: + pass + self.root.minsize(LXToolsAppConfig.WINDOW_WIDTH, LXToolsAppConfig.WINDOW_HEIGHT) + ThemeManager.apply_light_theme(self.root) + # Create header + self._create_header() + + # Create notebook (tabs) + self.notebook = ttk.Notebook(self.root, height=300) + self.notebook.pack(fill="both", expand=True, padx=15, pady=(10, 10)) + + # Create tabs + self._create_projects_tab() + self._create_log_tab() + + # Create progress section + self._create_progress_section() + + # Create buttons + self._create_modern_buttons() + + # Initial status refresh + self.root.after(100, self.refresh_status) + + return self.root + + def _create_header(self): + """Create clean header""" + # HEADER + header_frame = tk.Frame(self.root, bg="#2c3e50", height=70) + header_frame.pack(fill="x", pady=(0, 0)) + header_frame.pack_propagate(False) + + # Content + content = tk.Frame(header_frame, bg="#2c3e50") + content.pack(fill="both", expand=True, padx=15, pady=12) + + # LINKE SEITE: Icon + App Info + left_side = tk.Frame(content, bg="#2c3e50") + left_side.pack(side="left", anchor="w") + + icon_text_frame = tk.Frame(left_side, bg="#2c3e50") + icon_text_frame.pack(anchor="w") + + # Werkzeug-Icon + tk.Label( + icon_text_frame, text="🔧", font=("Helvetica", 18), bg="#2c3e50", fg="white" + ).pack(side="left", padx=(0, 8)) + + # App Name und Version + text_frame = tk.Frame(icon_text_frame, bg="#2c3e50") + text_frame.pack(side="left") + + tk.Label( + text_frame, + text="Lx Tools Installer", + font=("Helvetica", 14, "bold"), + fg="white", + bg="#2c3e50", + pady=4, + ).pack(anchor="w") + + tk.Label( + text_frame, + text=f"v {LXToolsAppConfig.VERSION} • Linux Application Installer", + font=("Helvetica", 9), + fg="#bdc3c7", + bg="#2c3e50", + ).pack(anchor="w") + + # RECHTE SEITE: System + Dynamischer Status + right_side = tk.Frame(content, bg="#2c3e50") + right_side.pack(side="right", anchor="e") + + tk.Label( + right_side, + text=f"System: {self.detected_os}", + font=("Helvetica", 11), + fg="#ecf0f1", + bg="#2c3e50", + ).pack(anchor="e") + + # DYNAMISCHER Status (anfangs leer) + self.header_status_label = tk.Label( + right_side, text="", font=("Helvetica", 10), bg="#2c3e50" # Anfangs leer + ) + self.header_status_label.pack(anchor="e", pady=(2, 0)) + + # Trennlinie + separator = tk.Frame(self.root, height=1, bg="#34495e") + separator.pack(fill="x", pady=0) + + def update_header_status(self, message="", color="#1abc9c"): + """Update status in header""" + if hasattr(self, "header_status_label"): + self.header_status_label.config(text=message, fg=color) + + def check_ready_status(self): + """Check if system is ready for installation""" + # Prüfungen: + internet_ok = NetworkChecker.check_internet_connection() + repo_ok = NetworkChecker.check_repository_access() + + if internet_ok and repo_ok: + self.update_header_status("Ready for installation", "#1abc9c") # Grün + elif not internet_ok: + self.update_header_status("No internet connection", "#e74c3c") # Rot + elif not repo_ok: + self.update_header_status("Repository unavailable", "#f39c12") # Orange + else: + self.update_header_status("System checking...", "#3498db") # Blau + + def _create_projects_tab(self): + """Create projects tab with project cards""" + projects_frame = ttk.Frame(self.notebook) + self.notebook.add(projects_frame, text="Projects") + + # Scrollable frame + canvas = tk.Canvas(projects_frame, bg=self.colors["bg"]) + scrollbar = ttk.Scrollbar( + projects_frame, orient="vertical", command=canvas.yview + ) + scrollable_frame = tk.Frame(canvas, bg=self.colors["bg"]) + + scrollable_frame.bind( + "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) + ) + + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Create project cards + for project_key, project_info in self.app_manager.get_all_projects().items(): + self._create_project_card(scrollable_frame, project_key, project_info) + + def _create_project_card(self, parent, project_key, project_info): + """Create a project card""" + # Main card frame + card_frame = tk.Frame(parent, bg=self.colors["bg"]) + card_frame.pack(fill="x", padx=10, pady=5) + + # Content frame (the actual card) + content_frame = tk.Frame( + card_frame, bg=self.colors["card_bg"], relief="solid", bd=1 + ) + content_frame.pack(fill="x", padx=5, pady=2) + + # Store frame reference + self.project_frames[project_key] = card_frame + + # Make entire card clickable + self._make_clickable(content_frame, content_frame, project_key) + + # Header with icon and title + header_frame = tk.Frame(content_frame, bg=self.colors["card_bg"]) + header_frame.pack(fill="x", padx=15, pady=(15, 5)) + + # Icon + icon_label = tk.Label(header_frame, bg=self.colors["card_bg"]) + icon_label.pack(side="left", padx=(0, 10)) + + # Try to load project icon + icon = self.image_manager.load_image( + project_info.get("icon_key", "default_icon") + ) + if icon: + icon_label.config(image=icon) + icon_label.image = icon + else: + # Use emoji based on project + if project_key == "wirepy": + icon_label.config(text="🔒", font=("Helvetica", 24)) + elif project_key == "logviewer": + icon_label.config(text="📋", font=("Helvetica", 24)) + else: + icon_label.config(text="📦", font=("Helvetica", 24)) + + # Title and description + title_frame = tk.Frame(header_frame, bg=self.colors["card_bg"]) + title_frame.pack(side="left", fill="x", expand=True) + + title_label = tk.Label( + title_frame, + text=project_info["name"], + font=("Helvetica", 14, "bold"), + bg=self.colors["card_bg"], + fg=self.colors["text"], + anchor="w", + ) + title_label.pack(fill="x") + + desc_label = tk.Label( + title_frame, + text=project_info["description"], + font=("Helvetica", 10), + bg=self.colors["card_bg"], + fg="#7f8c8d", + anchor="w", + wraplength=300, + ) + desc_label.pack(fill="x") + + # Status section + status_frame = tk.Frame(content_frame, bg=self.colors["card_bg"]) + status_frame.pack(fill="x", padx=15, pady=(5, 15)) + + # Status label + status_label = tk.Label( + status_frame, + text="❓ Checking...", + font=("Helvetica", 10), + bg=self.colors["card_bg"], + fg="#95a5a6", + anchor="w", + ) + status_label.pack(fill="x") + + # Version label + version_label = tk.Label( + status_frame, + text="Version: Checking...", + font=("Helvetica", 9), + bg=self.colors["card_bg"], + fg="#95a5a6", + anchor="w", + ) + version_label.pack(fill="x") + + # Store label references + self.status_labels[project_key] = status_label + self.version_labels[project_key] = version_label + + # Make all elements clickable + for widget in [ + header_frame, + title_frame, + title_label, + desc_label, + status_frame, + status_label, + version_label, + ]: + self._make_clickable(widget, content_frame, project_key) + + # Make icon clickable too + self._make_clickable(icon_label, content_frame, project_key) + + def _make_clickable(self, widget, main_frame, project_key): + """Make widget clickable with hover effects""" + + def on_click(event): + self.select_project(project_key) + + def on_enter(event): + if self.selected_project == project_key: + main_frame.config(bg=self.colors["selected_bg"]) + self._update_frame_children_bg(main_frame, self.colors["selected_bg"]) + else: + main_frame.config(bg=self.colors["hover_bg"]) + self._update_frame_children_bg(main_frame, self.colors["hover_bg"]) + + def on_leave(event): + if self.selected_project == project_key: + main_frame.config(bg=self.colors["selected_bg"]) + self._update_frame_children_bg(main_frame, self.colors["selected_bg"]) + else: + main_frame.config(bg=self.colors["card_bg"]) + self._update_frame_children_bg(main_frame, self.colors["card_bg"]) + + widget.bind("", on_click) + widget.bind("", on_enter) + widget.bind("", on_leave) + + def _update_frame_children_bg(self, frame, bg_color): + """Recursively update background color of all children""" + try: + for child in frame.winfo_children(): + if isinstance(child, (tk.Frame, tk.Label)): + child.config(bg=bg_color) + if isinstance(child, tk.Frame): + self._update_frame_children_bg(child, bg_color) + except tk.TclError: + # Ignore color errors + pass + + def select_project(self, project_key): + """Select a project""" + # Reset previous selection + if self.selected_project and self.selected_project in self.project_frames: + old_frame = self.project_frames[self.selected_project] + old_content = old_frame.winfo_children()[0] # content_frame + old_content.config(bg=self.colors["card_bg"]) + self._update_frame_children_bg(old_content, self.colors["card_bg"]) + + # Set new selection + self.selected_project = project_key + if project_key in self.project_frames: + new_frame = self.project_frames[project_key] + new_content = new_frame.winfo_children()[0] # content_frame + new_content.config(bg=self.colors["selected_bg"]) + self._update_frame_children_bg(new_content, self.colors["selected_bg"]) + + project_info = self.app_manager.get_project_info(project_key) + self.log_message(f"Selected project: {project_info['name']}") + + def _create_log_tab(self): + """Create log tab""" + log_frame = ttk.Frame(self.notebook) + self.notebook.add(log_frame, text="Installation Log") + + # Log text with scrollbar + log_container = tk.Frame(log_frame) + log_container.pack(fill="both", expand=True, padx=10, pady=10) + + self.log_text = tk.Text( + log_container, + wrap=tk.WORD, + font=("Consolas", 9), + bg="#1e1e1e", + fg="#d4d4d4", + insertbackground="white", + selectbackground="#264f78", + ) + + log_scrollbar = ttk.Scrollbar( + log_container, orient="vertical", command=self.log_text.yview + ) + self.log_text.configure(yscrollcommand=log_scrollbar.set) + + self.log_text.pack(side="left", fill="both", expand=True) + log_scrollbar.pack(side="right", fill="y") + + # Log controls + log_controls = tk.Frame(log_frame) + log_controls.pack(fill="x", padx=10, pady=(0, 0)) + + # Clear log button + clear_log_btn = ttk.Button( + log_controls, text="Clear Log", command=self.clear_log + ) + clear_log_btn.pack(side="right") + + # Initial log message + self.log_message( + f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===" + ) + self.log_message(f"Working directory: {LXToolsAppConfig.WORK_DIR}") + self.log_message(f"Icons directory: {LXToolsAppConfig.ICONS_DIR}") + self.log_message(f"Detected OS: {self.detected_os}") + self.log_message("Ready for installation...") + + def _create_progress_section(self): + """Create progress section with download icon""" + progress_frame = ttk.LabelFrame(self.root, text="Progress", padding=10) + progress_frame.pack(fill="x", padx=15, pady=10) + + # Container for Icon and Progress + progress_container = tk.Frame(progress_frame) + progress_container.pack(fill="x") + + # Download Icon (left) + self.download_icon_label = tk.Label(progress_container, text="", width=50) + self.download_icon_label.pack(side="left", padx=(0, 10)) + + # Progress Text (right, expandable) + self.progress_label = tk.Label( + progress_container, + text="Ready for installation...", + font=("Helvetica", 10), + fg="blue", + anchor="w", + ) + self.progress_label.pack(side="left", fill="x", expand=True) + + # Initial icon load (neutral) + self._reset_download_icon() + + def _create_modern_buttons(self): + """Create modern styled buttons""" + button_frame = tk.Frame(self.root, bg=self.colors["bg"]) + button_frame.pack(fill="x", padx=15, pady=(5, 10)) + + # Button style configuration + style = ttk.Style() + + # Install button (green) + style.configure("Install.TButton", foreground="#27ae60", font=("Helvetica", 11)) + style.map( + "Install.TButton", + foreground=[("active", "#14542f"), ("pressed", "#1e8449")], + ) + + # Uninstall button (red) + style.configure( + "Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 11) + ) + style.map( + "Uninstall.TButton", + foreground=[("active", "#7d3b34"), ("pressed", "#c0392b")], + ) + + # Refresh button (blue) + style.configure("Refresh.TButton", foreground="#3498db", font=("Helvetica", 11)) + style.map( + "Refresh.TButton", + foreground=[("active", "#1e3747"), ("pressed", "#2980b9")], + ) + + # Create buttons + install_btn = ttk.Button( + button_frame, + text="Install/Update", + command=self.install_selected, + style="Install.TButton", + padding=8, + ) + install_btn.pack(side="left", padx=(0, 10)) + + uninstall_btn = ttk.Button( + button_frame, + text="Uninstall", + command=self.uninstall_selected, + style="Uninstall.TButton", + padding=8, + ) + uninstall_btn.pack(side="left", padx=(0, 10)) + + refresh_btn = ttk.Button( + button_frame, + text="Refresh Status", + command=self.refresh_status, + style="Refresh.TButton", + padding=8, + ) + refresh_btn.pack(side="right") + + def update_download_icon(self, status): + """Update download icon based on status""" + if not self.download_icon_label: + return + + if status == "downloading": + icon = self.image_manager.load_image("download_icon") + if icon: + self.download_icon_label.config(image=icon, text="") + self.download_icon_label.image = icon + else: + self.download_icon_label.config(text="⬇️", font=("Helvetica", 16)) + + elif status == "error": + icon = self.image_manager.load_image("download_error_icon") + if icon: + self.download_icon_label.config(image=icon, text="") + self.download_icon_label.image = icon + else: + self.download_icon_label.config(text="❌", font=("Helvetica", 16)) + + elif status == "success": + icon = self.image_manager.load_image("success_icon") + if icon: + self.download_icon_label.config(image=icon, text="") + self.download_icon_label.image = icon + else: + self.download_icon_label.config(text="✅", font=("Helvetica", 16)) + + self.download_icon_label.update() + + def _reset_download_icon(self): + """Reset download icon to neutral state""" + icon = self.image_manager.load_image("download_icon") + if icon: + self.download_icon_label.config(image=icon, text="") + self.download_icon_label.image = icon + else: + self.download_icon_label.config(text="📥", font=("Helvetica", 16)) + + def refresh_status(self): + """Refresh application status and version information""" + self.update_progress("Refreshing status and checking versions...") + self._reset_download_icon() + self.log_message("=== Refreshing Status ===") + self.root.focus_set() + for project_key, project_info in self.app_manager.get_all_projects().items(): + status_label = self.status_labels[project_key] + version_label = self.version_labels[project_key] + + self.log_message(f"Checking {project_info['name']}...") + + if self.app_manager.is_installed(project_key): + installed_version = self.app_manager.get_installed_version(project_key) + status_label.config( + text=f"✅ Installed ({installed_version})", fg="green" + ) + self.log_message( + f"{project_info['name']}: Installed {installed_version}" + ) + + # Get latest version from API + try: + latest_version = self.app_manager.get_latest_version(project_key) + if latest_version != "Unknown": + if installed_version != f"v. {latest_version}": + version_label.config( + text=f"Latest: v. {latest_version} (Update available)", + fg="orange", + ) + self.log_message( + f"{project_info['name']}: Update available v. {latest_version}" + ) + else: + version_label.config( + text=f"Latest: v. {latest_version} (Up to date)", + fg="green", + ) + self.log_message(f"{project_info['name']}: Up to date") + else: + version_label.config(text="Latest: Unknown", fg="gray") + self.log_message( + f"{project_info['name']}: Could not check latest version" + ) + except Exception as e: + version_label.config(text="Latest: Check failed", fg="gray") + self.log_message( + f"{project_info['name']}: Version check failed: {e}" + ) + else: + status_label.config(text="❌ Not installed", fg="red") + self.log_message(f"{project_info['name']}: Not installed") + + # Still show latest available version + try: + latest_version = self.app_manager.get_latest_version(project_key) + if latest_version != "Unknown": + version_label.config( + text=f"Available: v {latest_version}", fg="blue" + ) + self.log_message( + f"{project_info['name']}: Available v {latest_version}" + ) + else: + version_label.config(text="Available: Unknown", fg="gray") + except Exception as e: + version_label.config(text="Available: Check failed", fg="gray") + self.log_message( + f"{project_info['name']}: Version check failed: {e}" + ) + + self.update_progress("Status refresh completed.") + self.log_message("=== Status refresh completed ===") + self.check_ready_status() + + def install_selected(self): + """Handle install button click""" + if not self.selected_project: + messagebox.showwarning("Warning", "Please select a project to install.") + return + + # Check internet connection + if not NetworkChecker.check_internet_connection(): + self.update_download_icon("error") + messagebox.showerror( + "Network Error", + "No internet connection available.\nPlease check your network connection.", + ) + return + + if not NetworkChecker.check_repository_access(): + self.update_download_icon("error") + messagebox.showerror( + "Repository Error", "Cannot access repository.\nPlease try again later." + ) + return + self.root.focus_set() + + # Reset download icon + self._reset_download_icon() + project_info = self.app_manager.get_project_info(self.selected_project) + + # Check if already installed + if self.app_manager.is_installed(self.selected_project): + installed_version = self.app_manager.get_installed_version( + self.selected_project + ) + latest_version = self.app_manager.get_latest_version(self.selected_project) + + dialog_text = ( + f"{project_info['name']} is already installed.\n\n" + f"Installed version: {installed_version}\n" + f"Latest version: v {latest_version}\n\n" + f"YES = Update (reinstall all files)\n" + f"NO = Uninstall\n" + f"Cancel = Do nothing" + ) + + result = messagebox.askyesnocancel( + f"{project_info['name']} already installed", dialog_text + ) + + if result is None: # Cancel + self.update_progress("Installation cancelled.") + return + elif not result: # Uninstall + self.uninstall_selected() + return + else: # Update + self.update_progress("Updating application...") + + try: + self.update_download_icon("downloading") + self.installation_manager.install_project(self.selected_project) + self.update_download_icon("success") + messagebox.showinfo( + "Success", + f"{project_info['name']} has been successfully installed/updated.", + ) + self.refresh_status() + except Exception as e: + self.update_download_icon("error") + messagebox.showerror("Error", f"Installation failed: {e}") + + def uninstall_selected(self): + """Handle uninstall button click""" + if not self.selected_project: + messagebox.showwarning("Warning", "Please select a project to uninstall.") + return + + project_info = self.app_manager.get_project_info(self.selected_project) + + if not self.app_manager.is_installed(self.selected_project): + messagebox.showinfo("Info", f"{project_info['name']} is not installed.") + return + + result = messagebox.askyesno( + "Confirm Uninstall", + f"Are you sure you want to uninstall {project_info['name']}?\n\n" + f"This will remove all application files and user configurations.", + ) + if not result: + return + + try: + self.uninstallation_manager.uninstall_project(self.selected_project) + messagebox.showinfo( + "Success", f"{project_info['name']} has been successfully uninstalled." + ) + self.refresh_status() + except Exception as e: + messagebox.showerror("Error", f"Uninstallation failed: {e}") + self.root.focus_set() + + def update_progress(self, message): + """Update progress message""" + if self.progress_label: + self.progress_label.config(text=message) + self.progress_label.update() + print(f"Progress: {message}") + + def log_message(self, message): + """Add message to log""" + if self.log_text: + timestamp = datetime.now().strftime("%H:%M:%S") + log_entry = f"[{timestamp}] {message}\n" + self.log_text.insert(tk.END, log_entry) + self.log_text.see(tk.END) + self.log_text.update() + print(f"Log: {message}") + + def clear_log(self): + """Clear the log""" + if self.log_text: + self.log_text.delete(1.0, tk.END) + self.log_message("Log cleared") + + def run(self): + """Start the GUI application""" + root = self.create_gui() + root.mainloop() + + +def check_and_install_tkinter(): + """Check if tkinter is available and install if needed""" + if not OSDetector.check_tkinter_available(): + print("TKinter is not available on this system.") + detected_os = OSDetector.detect_os() + print(f"Detected OS: {detected_os}") + + response = input("Would you like to install TKinter? (y/n): ").lower().strip() + if response in ["y", "yes"]: + print("Installing TKinter...") + if OSDetector.install_tkinter(): + print("TKinter installed successfully!") + print("Please restart the application.") + return False + else: + print("Failed to install TKinter.") + print("Please install TKinter manually:") + + if detected_os in ["Ubuntu", "Debian", "Linux Mint", "Pop!_OS"]: + print("sudo apt update && sudo apt install python3-tk") + elif detected_os == "Fedora": + print("sudo dnf install tkinter") + elif detected_os in [ + "Arch Linux", + "Manjaro", + "Garuda Linux", + "EndeavourOS", + ]: + print("sudo pacman -S tk") + elif "SUSE" in detected_os or "openSUSE" in detected_os: + print("sudo zypper install python3-tk") + else: + print("Please check your distribution's package manager.") + + return False + else: + print("TKinter is required to run this application.") + return False + + return True + + +def main(): + """Main function to start the application""" + print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===") + print(f"Working directory: {os.getcwd()}") + + # Check and install tkinter if needed + if not check_and_install_tkinter(): + return + + try: + # Create and run the GUI + app = LXToolsGUI() + app.run() + except KeyboardInterrupt: + print("\nApplication interrupted by user.") + except Exception as e: + print(f"Fatal error: {e}") + try: + messagebox.showerror("Fatal Error", f"Application failed to start: {e}") + except: + pass + + +if __name__ == "__main__": + main()