From 68cd145a02bc2e2f8b6c592dd8325389ce87e71d Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 30 Jul 2024 21:12:06 -0400 Subject: [PATCH 01/27] minor changes to help with testing --- usr/share/edamame/UI/main.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/usr/share/edamame/UI/main.py b/usr/share/edamame/UI/main.py index 1254aa8..b041e0d 100755 --- a/usr/share/edamame/UI/main.py +++ b/usr/share/edamame/UI/main.py @@ -2101,8 +2101,12 @@ def keyboard(self, button): self.grid.attach(model_label, 1, 2, 1, 1) self.model_menu = Gtk.ComboBoxText.new() - with open("/etc/edamame/keyboards.json", "r") as file: - keyboards = json.load(file) + try: + with open("/etc/edamame/keyboards.json", "r") as file: + keyboards = json.load(file) + except FileNotFoundError: + with open("/tmp/keyboards.json", "r") as file: + keyboards = json.load(file) layout_list = keyboards["layouts"] model = keyboards["models"] for each8 in model: @@ -2334,7 +2338,11 @@ def make_kbd_names(): data[-1] = "}}" break data = "\n".join(data) - os.chdir("/etc/edamame") + try: + os.chdir("/etc/edamame") + except FileNotFoundError: + common.eprint("WARNING: /etc/edamame not found. In testing?") + os.chdir("/tmp") with open("keyboards.json", "w+") as file: file.write(data) From b211c25985fa6493c13a33fb5c24a7d35867f80b Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 1 Nov 2024 16:48:40 -0400 Subject: [PATCH 02/27] Start work to support multiple UI versions - Start work to support multiple UI versions, depending on UI toolkit - Fixed a bug where dependencies where not changing based on compilation status --- DEBIAN/control | 4 +-- build.sh | 11 +++++-- usr/bin/edamame.cxx | 2 +- usr/share/edamame/UI/GTK_UI/__init__.py | 30 +++++++++++++++++++ .../edamame/UI/GTK_UI/auto_partitioner.py | 1 + usr/share/edamame/UI/GTK_UI/common.py | 1 + usr/share/edamame/UI/{ => GTK_UI}/confirm.py | 0 usr/share/edamame/UI/{ => GTK_UI}/error.py | 2 +- usr/share/edamame/UI/{ => GTK_UI}/main.py | 0 usr/share/edamame/UI/{ => GTK_UI}/progress.py | 0 usr/share/edamame/UI/{ => GTK_UI}/report.py | 0 usr/share/edamame/UI/{ => GTK_UI}/success.py | 2 +- usr/share/edamame/UI/__init__.py | 14 +++++---- usr/share/edamame/UI/auto_partitioner.py | 1 - usr/share/edamame/UI/common.py | 1 - usr/share/edamame/common.py | 20 +++++++++++++ usr/share/edamame/engine.py | 2 ++ usr/share/edamame/installer.py | 1 + usr/share/edamame/progress.py | 1 + usr/share/edamame/success.py | 1 + 20 files changed, 78 insertions(+), 16 deletions(-) create mode 100755 usr/share/edamame/UI/GTK_UI/__init__.py create mode 120000 usr/share/edamame/UI/GTK_UI/auto_partitioner.py create mode 120000 usr/share/edamame/UI/GTK_UI/common.py rename usr/share/edamame/UI/{ => GTK_UI}/confirm.py (100%) rename usr/share/edamame/UI/{ => GTK_UI}/error.py (99%) rename usr/share/edamame/UI/{ => GTK_UI}/main.py (100%) rename usr/share/edamame/UI/{ => GTK_UI}/progress.py (100%) rename usr/share/edamame/UI/{ => GTK_UI}/report.py (100%) rename usr/share/edamame/UI/{ => GTK_UI}/success.py (99%) delete mode 120000 usr/share/edamame/UI/auto_partitioner.py delete mode 120000 usr/share/edamame/UI/common.py diff --git a/DEBIAN/control b/DEBIAN/control index 228e330..725f749 100644 --- a/DEBIAN/control +++ b/DEBIAN/control @@ -1,11 +1,11 @@ Package: edamame -Version: 2.9.0 +Version: 2.9.1 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin Architecture: amd64 Priority: important Replaces: system-installer (<=2.7.4) -Depends: arch-install-scripts, python3.11, bash, gir1.2-gtk-3.0 (>=3.24.12-1ubuntu1), coreutils (>=8.28-1ubuntu1), apt (>=1.6.11), squashfs-tools (>=1:4.3-6ubuntu0.18.04.1), grub2-common (>=2.02-2ubuntu8.13), initramfs-tools (>=0.130ubuntu3.8), systemd (>=237-3ubuntu10.24), locales (>=2.27-3ubuntu1), procps (>=2:3.3.12-3ubuntu1.1), grep (>=3.1-2), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-apt (>=2.0.0), python3-urllib3 (>=1.26.5-1~exp1), python3-gnupg (>=0.4.5), python3-xmltodict (>=0.11.0), python3-dnspython, tzdata, laptop-detect (>=0.16), libpython3.11 +Depends: arch-install-scripts, , bash, gir1.2-gtk-3.0 (>=3.24.12-1ubuntu1), coreutils (>=8.28-1ubuntu1), apt (>=1.6.11), squashfs-tools (>=1:4.3-6ubuntu0.18.04.1), grub2-common (>=2.02-2ubuntu8.13), initramfs-tools (>=0.130ubuntu3.8), systemd (>=237-3ubuntu10.24), locales (>=2.27-3ubuntu1), procps (>=2:3.3.12-3ubuntu1.1), grep (>=3.1-2), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-apt (>=2.0.0), python3-urllib3 (>=1.26.5-1~exp1), python3-gnupg (>=0.4.5), python3-xmltodict (>=0.11.0), python3-dnspython, tzdata, laptop-detect (>=0.16) Description: System Installation utility System Installation utility for Debian-based Operating Systems. diff --git a/build.sh b/build.sh index af1d19f..4fbc269 100755 --- a/build.sh +++ b/build.sh @@ -81,13 +81,15 @@ fi cd ../.. files_to_edit=$(find "." -maxdepth 10 -type f -name '*.py' -print) shebang='\#\!/usr/bin/env' +py_ver="" if [ "$vert" == "dnc" ]; then - shebang="$shebang python3" + py_ver="python3" elif [ "$vert" == "3.11" ]; then - shebang="$shebang python3.11" + py_ver="python3.11" elif [ "$vert" == "3.12" ]; then - shebang="$shebang python3.12" + py_ver="python3.12" fi +shebang="$shebang $py_ver" for each in $files_to_edit; do sed -i "s:\#\!shebang:$shebang:" $each done @@ -146,6 +148,7 @@ if [[ "$vert" != "dnc" ]]; then cd ../"$FOLDER"/ ./nuitka_compile.sh --python-ver=$vert rm -v nuitka_compile.sh compile.conf + sed -i "s/^Depends:/& lib${py_vert},/" DEBIAN/control cd "$base" fi ############################################################## @@ -178,6 +181,8 @@ rm "$FOLDER"/usr/bin/edamame.cxx if [[ "$vert" != "dnc" ]]; then find "$FOLDER" -maxdepth 10 -type d -name __pycache__ -exec rm -rfv {} \; fi +# Insert other deps into control file +sed -i "s/<\!--python_vert-->/$py_vert/g" "$FOLDER/DEBIAN/control" #build the shit dpkg-deb --build "$FOLDER" rm -rf "$FOLDER" diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 64e1cf1..9be6dbf 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -37,7 +37,7 @@ using namespace std; -str VERSION = "2.9.0"; +str VERSION = "2.9.1"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; diff --git a/usr/share/edamame/UI/GTK_UI/__init__.py b/usr/share/edamame/UI/GTK_UI/__init__.py new file mode 100755 index 0000000..56a1c71 --- /dev/null +++ b/usr/share/edamame/UI/GTK_UI/__init__.py @@ -0,0 +1,30 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# __init__.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""UI for Edamame""" +import UI.GTK_UI.confirm as confirm +import UI.GTK_UI.error as error +import UI.GTK_UI.main as main +import UI.GTK_UI.progress as progress +import UI.GTK_UI.report as report +import UI.GTK_UI.success as success diff --git a/usr/share/edamame/UI/GTK_UI/auto_partitioner.py b/usr/share/edamame/UI/GTK_UI/auto_partitioner.py new file mode 120000 index 0000000..957253b --- /dev/null +++ b/usr/share/edamame/UI/GTK_UI/auto_partitioner.py @@ -0,0 +1 @@ +../../auto_partitioner.py \ No newline at end of file diff --git a/usr/share/edamame/UI/GTK_UI/common.py b/usr/share/edamame/UI/GTK_UI/common.py new file mode 120000 index 0000000..349c5f0 --- /dev/null +++ b/usr/share/edamame/UI/GTK_UI/common.py @@ -0,0 +1 @@ +../../common.py \ No newline at end of file diff --git a/usr/share/edamame/UI/confirm.py b/usr/share/edamame/UI/GTK_UI/confirm.py similarity index 100% rename from usr/share/edamame/UI/confirm.py rename to usr/share/edamame/UI/GTK_UI/confirm.py diff --git a/usr/share/edamame/UI/error.py b/usr/share/edamame/UI/GTK_UI/error.py similarity index 99% rename from usr/share/edamame/UI/error.py rename to usr/share/edamame/UI/GTK_UI/error.py index b5502cc..17febb3 100755 --- a/usr/share/edamame/UI/error.py +++ b/usr/share/edamame/UI/GTK_UI/error.py @@ -26,7 +26,7 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from UI import report +from UI.GTK_UI import report class Main(report.Main): diff --git a/usr/share/edamame/UI/main.py b/usr/share/edamame/UI/GTK_UI/main.py similarity index 100% rename from usr/share/edamame/UI/main.py rename to usr/share/edamame/UI/GTK_UI/main.py diff --git a/usr/share/edamame/UI/progress.py b/usr/share/edamame/UI/GTK_UI/progress.py similarity index 100% rename from usr/share/edamame/UI/progress.py rename to usr/share/edamame/UI/GTK_UI/progress.py diff --git a/usr/share/edamame/UI/report.py b/usr/share/edamame/UI/GTK_UI/report.py similarity index 100% rename from usr/share/edamame/UI/report.py rename to usr/share/edamame/UI/GTK_UI/report.py diff --git a/usr/share/edamame/UI/success.py b/usr/share/edamame/UI/GTK_UI/success.py similarity index 99% rename from usr/share/edamame/UI/success.py rename to usr/share/edamame/UI/GTK_UI/success.py index 95e9b6a..5064ea3 100755 --- a/usr/share/edamame/UI/success.py +++ b/usr/share/edamame/UI/GTK_UI/success.py @@ -33,7 +33,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk try: - import UI.report as report + import UI.GTK_UI.report as report except ModuleNotFoundError: import report diff --git a/usr/share/edamame/UI/__init__.py b/usr/share/edamame/UI/__init__.py index 0e76250..476f2e0 100755 --- a/usr/share/edamame/UI/__init__.py +++ b/usr/share/edamame/UI/__init__.py @@ -21,10 +21,12 @@ # MA 02110-1301, USA. # # +import os +import importlib + """UI for Edamame""" -import UI.confirm as confirm -import UI.error as error -import UI.main as main -import UI.progress as progress -import UI.report as report -import UI.success as success +def load_UI(ui_type: str) -> bool: + """Load the specified UI type""" + if os.path.exists(f"UI/{ui_type}_UI"): + return importlib.import_module(f"UI.{ui_type}_UI") + raise ImportError(f"Module {ui_type}_UI is not present!") diff --git a/usr/share/edamame/UI/auto_partitioner.py b/usr/share/edamame/UI/auto_partitioner.py deleted file mode 120000 index 84df7e3..0000000 --- a/usr/share/edamame/UI/auto_partitioner.py +++ /dev/null @@ -1 +0,0 @@ -../auto_partitioner.py \ No newline at end of file diff --git a/usr/share/edamame/UI/common.py b/usr/share/edamame/UI/common.py deleted file mode 120000 index a11703e..0000000 --- a/usr/share/edamame/UI/common.py +++ /dev/null @@ -1 +0,0 @@ -../common.py \ No newline at end of file diff --git a/usr/share/edamame/common.py b/usr/share/edamame/common.py index bf9ba8d..fefe2bb 100644 --- a/usr/share/edamame/common.py +++ b/usr/share/edamame/common.py @@ -85,3 +85,23 @@ def item_in_list(item, array): if item == each: return True return False + + +def determine_toolkit(): + """Determine System UI toolkit""" + UI_by_DE = { + "gnome": "GTK", + "xfce": "GTK", + "lxde": "GTK", + "mate": "GTK", + "unity": "GTK", + "cinnamon": "GTK", + "pantheon": "GTK", + "kde": "Qt", + "lxqt": "Qt", + "lomiri": "Qt", + "dde": "Qt", + "deepin": "Qt" + + } + return UI_by_DE[os.environ["XDG_CURRENT_DESKTOP"].lower()] diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index d1beae1..580bd94 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -43,6 +43,8 @@ common.eprint(f" ### {sys.argv[0]} STARTED ### ") +UI = UI.load_UI("GTK") + def copy_log_to_disk(): """Copy Installation Log to installation location""" diff --git a/usr/share/edamame/installer.py b/usr/share/edamame/installer.py index 143260e..b832c6d 100755 --- a/usr/share/edamame/installer.py +++ b/usr/share/edamame/installer.py @@ -33,6 +33,7 @@ import common import auto_partitioner +UI = UI.load_UI("GTK") def __mount__(device, path_dir): """Mount device at path diff --git a/usr/share/edamame/progress.py b/usr/share/edamame/progress.py index 2600801..7c85eac 100755 --- a/usr/share/edamame/progress.py +++ b/usr/share/edamame/progress.py @@ -22,4 +22,5 @@ # # import UI +UI = UI.load_UI("GTK") UI.progress.show_progress() diff --git a/usr/share/edamame/success.py b/usr/share/edamame/success.py index a8f317b..dab61aa 100755 --- a/usr/share/edamame/success.py +++ b/usr/share/edamame/success.py @@ -24,6 +24,7 @@ from json import loads from sys import argv import UI +UI = UI.load_UI("GTK") SETTINGS = loads(argv[1]) UI.success.show_success(SETTINGS) From d6ab3678f38c2590b127af765541afc577a67025 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Mon, 4 Nov 2024 15:00:57 -0500 Subject: [PATCH 03/27] Increase size of EFI partition to 1GB --- etc/edamame/settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/edamame/settings.json b/etc/edamame/settings.json index 92ef0c3..35162ef 100644 --- a/etc/edamame/settings.json +++ b/etc/edamame/settings.json @@ -18,10 +18,10 @@ "EFI": { "EFI": { "START": 0, - "END": 500 + "END": 1024 }, "ROOT":{ - "START": 501, + "START": 1025, "END": "40%", "fs": "btrfs" }, From f043a5129984b1fabdc04af4e08a00147a8855ca Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 8 Nov 2024 19:13:52 -0500 Subject: [PATCH 04/27] Allow testing of existing code with new UIs This new commit adds the ability to tell Edamame to use a different GUI toolkit than normal. This is useful for testing different versions of Edamame against new GUIs without having to do too much work. --- usr/bin/edamame.cxx | 40 +++++++++++++++++++++++++------------ usr/share/edamame/engine.py | 11 +++++++++- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 9be6dbf..f512790 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -29,15 +29,13 @@ #define float_list vector #define bool_list vector - // import libs - #include - #include - #include - #include +// import libs +#include +#include using namespace std; -str VERSION = "2.9.1"; +str VERSION = "2.9.2"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; @@ -45,6 +43,7 @@ str NC = "\033[0m"; str HELP = "\n" "Edamame, Version " + VERSION + "\n" "\n" +"\t --gui specify the GUI to use. May throw an error if given toolkit is not available." "\t-h, --help print this help dialoge.\n" "\t --boot-time launch Edamame in boot-time mode.\n" "\t-v, --version print current version.\n" @@ -72,7 +71,7 @@ str run(const char* cmd) { // Launch with boot time parameter -void launch(bool boot_time) +void launch(str arg) { str command1 = "/usr/bin/xhost"; str enable = " +si:localuser:root"; @@ -80,7 +79,7 @@ void launch(bool boot_time) str command = "echo 'toor' | sudo -S nice -n -10 /usr/share/edamame/engine.py"; run((command1 + enable).c_str()); cout << Y << "RUNNING LOG LOCATED AT /tmp/edamame.log" << NC << endl; - if (boot_time) + if (arg == "boot_time") { FILE* file = fopen("/tmp/edamame.log", "w"); if (file != NULL) @@ -94,6 +93,10 @@ void launch(bool boot_time) } command = command + " --boot-time"; } + elif (arg != "") + { + command = command + "--gui=" + arg; + } command = command + " 2>/tmp/edamame.log 1>&2"; run(command.c_str()); run((command1 + disable).c_str()); @@ -102,7 +105,7 @@ void launch(bool boot_time) // Launch with no parameter void launch() { - launch(false); + launch(""); } @@ -114,20 +117,31 @@ int main(int argc, char **argv) if ((arg == "-v") || (arg == "--version")) { cout << "\n" << VERSION << "\n" << endl; + return 0; } elif ((arg == "-h") || (arg == "--help")) { cout << HELP << endl; + return 0; } elif (arg == "--boot-time") { - launch(true); + launch("boot_time"); + return 0; } - else + elif (arg == "--gui") { - cerr << "Option " << arg << " not recognized." << endl; - cerr << HELP << endl; + if (argc > 2) + { + launch(argv[2]); + return 0; + } + cout << "No GUI method selected. Please select a GUI method to use." << endl; + return 1; } + cerr << "Option '" << arg << "' not recognized." << endl; + cerr << HELP << endl; + return 1; } else { diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index 580bd94..681abc7 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -43,7 +43,6 @@ common.eprint(f" ### {sys.argv[0]} STARTED ### ") -UI = UI.load_UI("GTK") def copy_log_to_disk(): @@ -91,6 +90,16 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N sys.exit(0) BOOT_TIME = True immerse.enable() + elif "--gui=" in sys.argv[1]: + gui = sys.argv[1].split("=")[-1] + try: + UI = UI.load_UI(gui.upper()) + except ImportError: + common.eprint(f"FATAL ERROR: GUI METHOD '{gui}' DOES NOT EXIST!") + sys.exit(1) +else: + UI = UI.load_UI("GTK") + MEMCHECK = psutil.virtual_memory().total if (MEMCHECK / 1024 ** 2) < 1024: UI.error.show_error("\n\tRAM is less than 1 GB.\t\n") From 1e8b1da6e3e6fec7a11b310111e5f3eb92c30667 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 12 Nov 2024 16:45:02 -0500 Subject: [PATCH 05/27] Minor type hinting improvement --- usr/share/edamame/UI/GTK_UI/confirm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/usr/share/edamame/UI/GTK_UI/confirm.py b/usr/share/edamame/UI/GTK_UI/confirm.py index e8f00c8..97cd652 100755 --- a/usr/share/edamame/UI/GTK_UI/confirm.py +++ b/usr/share/edamame/UI/GTK_UI/confirm.py @@ -24,6 +24,7 @@ """Confirm UI for Edamame""" from __future__ import print_function from sys import argv, stderr +import json import auto_partitioner as ap import gi gi.require_version('Gtk', '3.0') @@ -336,3 +337,8 @@ def show_confirm(settings, boot_time=False): data = window.return_install() window.exit("clicked") return data + + +if __name__ == "__main__": + settings = json.loads(argv[1]) + print(show_confirm(settings)) From c97a3da7561eceaae2e1c9b79c1ae6b4482693e3 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 12 Nov 2024 16:46:55 -0500 Subject: [PATCH 06/27] Initial release of Qt UI This UI still has a few major bugs, some debugging code that needs to be removed, and needs a minor UX polish. However, it is working for the most part. --- etc/edamame/settings.json | 5 +- usr/share/edamame/UI/Qt_UI/__init__.py | 30 + .../edamame/UI/Qt_UI/auto_partitioner.py | 1 + usr/share/edamame/UI/Qt_UI/common.py | 1 + usr/share/edamame/UI/Qt_UI/confirm.py | 361 +++ usr/share/edamame/UI/Qt_UI/error.py | 93 + usr/share/edamame/UI/Qt_UI/main.py | 2297 +++++++++++++++++ usr/share/edamame/UI/Qt_UI/progress.py | 162 ++ usr/share/edamame/UI/Qt_UI/qt_common.py | 35 + usr/share/edamame/UI/Qt_UI/report.py | 681 +++++ usr/share/edamame/UI/Qt_UI/success.py | 395 +++ usr/share/edamame/engine.py | 2 +- 12 files changed, 4061 insertions(+), 2 deletions(-) create mode 100755 usr/share/edamame/UI/Qt_UI/__init__.py create mode 120000 usr/share/edamame/UI/Qt_UI/auto_partitioner.py create mode 120000 usr/share/edamame/UI/Qt_UI/common.py create mode 100755 usr/share/edamame/UI/Qt_UI/confirm.py create mode 100755 usr/share/edamame/UI/Qt_UI/error.py create mode 100755 usr/share/edamame/UI/Qt_UI/main.py create mode 100755 usr/share/edamame/UI/Qt_UI/progress.py create mode 100755 usr/share/edamame/UI/Qt_UI/qt_common.py create mode 100755 usr/share/edamame/UI/Qt_UI/report.py create mode 100755 usr/share/edamame/UI/Qt_UI/success.py diff --git a/etc/edamame/settings.json b/etc/edamame/settings.json index 35162ef..e560f6b 100644 --- a/etc/edamame/settings.json +++ b/etc/edamame/settings.json @@ -51,5 +51,8 @@ "run_post_oem": ["/usr/bin/drauger-welcome"], "kernel_meta_pkg": "linux-drauger", "remove_pkgs": ["edamame", - "persistence-daemon"] + "persistence-daemon"], + "hostname_prepend": "DRAUGER", + "hostname_append_len": 8, + "min_password_length": 4 } diff --git a/usr/share/edamame/UI/Qt_UI/__init__.py b/usr/share/edamame/UI/Qt_UI/__init__.py new file mode 100755 index 0000000..dfbf583 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/__init__.py @@ -0,0 +1,30 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# __init__.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""UI for Edamame""" +import UI.Qt_UI.confirm as confirm +import UI.Qt_UI.error as error +import UI.Qt_UI.main as main +import UI.Qt_UI.progress as progress +import UI.Qt_UI.report as report +import UI.Qt_UI.success as success diff --git a/usr/share/edamame/UI/Qt_UI/auto_partitioner.py b/usr/share/edamame/UI/Qt_UI/auto_partitioner.py new file mode 120000 index 0000000..957253b --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/auto_partitioner.py @@ -0,0 +1 @@ +../../auto_partitioner.py \ No newline at end of file diff --git a/usr/share/edamame/UI/Qt_UI/common.py b/usr/share/edamame/UI/Qt_UI/common.py new file mode 120000 index 0000000..349c5f0 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/common.py @@ -0,0 +1 @@ +../../common.py \ No newline at end of file diff --git a/usr/share/edamame/UI/Qt_UI/confirm.py b/usr/share/edamame/UI/Qt_UI/confirm.py new file mode 100755 index 0000000..7c06399 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/confirm.py @@ -0,0 +1,361 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# confirm.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Confirm UI for Edamame""" +from __future__ import print_function +import sys +import json +from qtpy import QtGui, QtWidgets, QtCore +try: + import UI.Qt_UI.qt_common as QCommon +except ImportError: + import qt_common as QCommon +import auto_partitioner as ap + + +class Main(QtWidgets.QWidget): + """UI Confirmation Class""" + + def __init__(self, settings): + """set up confirmation UI""" + super().__init__() + self.setWindowTitle("Edamame") + self.install = False + self.setWindowIcon(QtGui.QIcon.fromTheme("system-installer")) + self.grid = QtWidgets.QGridLayout() + self.setLayout(self.grid) + + label = QtWidgets.QLabel() + label.setText(""" +# FINAL CONFIRMATION +## Please read the below summary carefully.\n +## This is your final chance to cancel installation.\n +--- + """) + label.setTextFormat(QtCore.Qt.MarkdownText) + label.setAlignment(QtCore.Qt.AlignCenter) + # label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 11) + + label1 = QtWidgets.QLabel() + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1.setText(""" +### PARTITIONS + """) + label1.setAlignment(QtCore.Qt.AlignCenter) + # label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + label5 = QtWidgets.QLabel() + label5.setTextFormat(QtCore.Qt.MarkdownText) + + if settings["AUTO_PART"]: + label = f"""**AUTO PARTITIONING ENABLED**\t + +**INSTALLATION DRIVE:** {settings["ROOT"]}""" + if settings["raid_array"]["raid_type"] not in ("OEM", None): + label = label + f""" + +**RAID Type:** {settings["raid_array"]["raid_type"]} + +**Drive 1:** {settings["raid_array"]["disks"]["1"]} + +**Drive 2:** {settings["raid_array"]["disks"]["2"]}""" + if settings["raid_array"]["raid_type"].lower() == "raid10": + label = label + f""" + +**Drive 3:** {settings["raid_array"]["disks"]["3"]} + +**Drive 4:** {settings["raid_array"]["disks"]["4"]}""" + else: + label = label + f""" +**HOME:** {settings["HOME"]}""" + else: + label = f"""**ROOT:** {settings["ROOT"]} + +**EFI:** {settings["EFI"]} + +**SWAP:** {settings["SWAP"]} + +**HOME:** {settings["HOME"]}""" + + label5.setText(label) + label5.setAlignment(QtCore.Qt.AlignCenter) + # label5 = self._set_default_margins(label5) + self.grid.addWidget(label5, 3, 1, round(len(label.split("\n")) / 2), 2) + + if "OEM" not in settings.values(): + # Attach the same separator in multiple places + # sep = Gtk.Separator.new(Gtk.Orientation.VERTICAL) + # self.grid.addWidget(sep, 3, 2, 1, 6) + + label6 = QtWidgets.QLabel() + label6.setTextFormat(QtCore.Qt.MarkdownText) + label6.setText(""" +### SYSTEM + """) + label6.setAlignment(QtCore.Qt.AlignCenter) + # label6 = self._set_default_margins(label6) + self.grid.addWidget(label6, 2, 4, 1, 2) + + label7 = QtWidgets.QLabel() + label7.setTextFormat(QtCore.Qt.MarkdownText) + label7.setText(""" **Language:** """) + label7.setAlignment(QtCore.Qt.AlignCenter) + # label7 = self._set_default_margins(label7) + self.grid.addWidget(label7, 3, 4, 1, 1) + + label8 = QtWidgets.QLabel() + label8.setTextFormat(QtCore.Qt.MarkdownText) + label8.setText(settings["LANG"]) + label8.setAlignment(QtCore.Qt.AlignCenter) + # label8 = self._set_default_margins(label8) + self.grid.addWidget(label8, 3, 5, 1, 1) + + label9 = QtWidgets.QLabel() + label9.setTextFormat(QtCore.Qt.MarkdownText) + label9.setText(""" **Time Zone:** """) + label9.setAlignment(QtCore.Qt.AlignCenter) + # label9 = self._set_default_margins(label9) + self.grid.addWidget(label9, 4, 4, 1, 1) + + label10 = QtWidgets.QLabel() + label10.setTextFormat(QtCore.Qt.MarkdownText) + label10.setText(settings["TIME_ZONE"]) + label10.setAlignment(QtCore.Qt.AlignCenter) + # label10 = self._set_default_margins(label10) + self.grid.addWidget(label10, 4, 5, 1, 1) + + label11 = QtWidgets.QLabel() + label11.setTextFormat(QtCore.Qt.MarkdownText) + label11.setText(" **Computer Name:** ") + label11.setAlignment(QtCore.Qt.AlignCenter) + # label11 = self._set_default_margins(label11) + self.grid.addWidget(label11, 5, 4, 1, 1) + + label12 = QtWidgets.QLabel() + label12.setTextFormat(QtCore.Qt.MarkdownText) + label12.setText(settings["COMPUTER_NAME"]) + label12.setAlignment(QtCore.Qt.AlignCenter) + # label12 = self._set_default_margins(label12) + self.grid.addWidget(label12, 5, 5, 1, 1) + + if ap.is_EFI(): + label31 = QtWidgets.QLabel() + label31.setTextFormat(QtCore.Qt.MarkdownText) + label31.setText(" **Compatibility Mode:** ") + label31.setAlignment(QtCore.Qt.AlignCenter) + # label31 = self._set_default_margins(label31) + self.grid.addWidget(label31, 6, 4, 1, 1) + + label32 = QtWidgets.QLabel() + label32.setTextFormat(QtCore.Qt.MarkdownText) + label32.setText(str(settings["COMPAT_MODE"])) + label32.setAlignment(QtCore.Qt.AlignCenter) + # label32 = self._set_default_margins(label32) + self.grid.addWidget(label32, 6, 5, 1, 1) + + # sep1 = Gtk.Separator.new(Gtk.Orientation.VERTICAL) + # self.grid.addWidget(sep1, 6, 2, 1, 6) + + label13 = QtWidgets.QLabel() + label13.setTextFormat(QtCore.Qt.MarkdownText) + label13.setText(""" +### USER + """) + label13.setAlignment(QtCore.Qt.AlignCenter) + # label13 = self._set_default_margins(label13) + self.grid.addWidget(label13, 2, 7, 1, 2) + + label14 = QtWidgets.QLabel() + label14.setTextFormat(QtCore.Qt.MarkdownText) + label14.setText(""" **Username:** """) + label14.setAlignment(QtCore.Qt.AlignCenter) + # label14 = self._set_default_margins(label14) + self.grid.addWidget(label14, 3, 7, 1, 1) + + label15 = QtWidgets.QLabel() + label15.setTextFormat(QtCore.Qt.MarkdownText) + label15.setText(settings["USERNAME"]) + label15.setAlignment(QtCore.Qt.AlignCenter) + # label15 = self._set_default_margins(label15) + self.grid.addWidget(label15, 3, 8, 1, 1) + + label16 = QtWidgets.QLabel() + label16.setTextFormat(QtCore.Qt.MarkdownText) + label16.setText(""" **Password:** """) + label16.setAlignment(QtCore.Qt.AlignCenter) + # label16 = self._set_default_margins(label16) + self.grid.addWidget(label16, 4, 7, 1, 1) + + label17 = QtWidgets.QLabel() + label17.setTextFormat(QtCore.Qt.MarkdownText) + label17.setText(settings["PASSWORD"]) + label17.setAlignment(QtCore.Qt.AlignCenter) + # label17 = self._set_default_margins(label17) + self.grid.addWidget(label17, 4, 8, 1, 1) + + label23 = QtWidgets.QLabel() + label23.setTextFormat(QtCore.Qt.MarkdownText) + label23.setText(""" **Auto-Login:** """) + label23.setAlignment(QtCore.Qt.AlignCenter) + # label23 = self._set_default_margins(label23) + self.grid.addWidget(label23, 5, 7, 1, 1) + + label24 = QtWidgets.QLabel() + label24.setTextFormat(QtCore.Qt.MarkdownText) + label24.setText(str(settings["LOGIN"])) + label24.setAlignment(QtCore.Qt.AlignCenter) + # label24 = self._set_default_margins(label24) + self.grid.addWidget(label24, 5, 8, 1, 1) + + # sep2 = Gtk.Separator.new(Gtk.Orientation.VERTICAL) + # self.grid.addWidget(sep2, 9, 2, 1, 6) + + label18 = QtWidgets.QLabel() + label18.setTextFormat(QtCore.Qt.MarkdownText) + label18.setText(""" +### OTHER + """) + label18.setAlignment(QtCore.Qt.AlignCenter) + # label18 = self._set_default_margins(label18) + self.grid.addWidget(label18, 2, 10, 1, 2) + + label19 = QtWidgets.QLabel() + label19.setTextFormat(QtCore.Qt.MarkdownText) + label19.setText(""" **Install Extras:** """) + label19.setAlignment(QtCore.Qt.AlignCenter) + # label19 = self._set_default_margins(label19) + self.grid.addWidget(label19, 3, 10, 1, 1) + + label20 = QtWidgets.QLabel() + label20.setTextFormat(QtCore.Qt.MarkdownText) + label20.setText(str(settings["EXTRAS"])) + label20.setAlignment(QtCore.Qt.AlignCenter) + # label20 = self._set_default_margins(label20) + self.grid.addWidget(label20, 3, 11, 1, 1) + + label21 = QtWidgets.QLabel() + label21.setTextFormat(QtCore.Qt.MarkdownText) + label21.setText(""" **Install Updates:** """) + label21.setAlignment(QtCore.Qt.AlignCenter) + # label21 = self._set_default_margins(label21) + self.grid.addWidget(label21, 4, 10, 1, 1) + + label22 = QtWidgets.QLabel() + label22.setTextFormat(QtCore.Qt.MarkdownText) + label22.setText(str(settings["UPDATES"])) + label22.setAlignment(QtCore.Qt.AlignCenter) + # label22 = self._set_default_margins(label22) + self.grid.addWidget(label22, 4, 11, 1, 1) + + label25 = QtWidgets.QLabel() + label25.setTextFormat(QtCore.Qt.MarkdownText) + label25.setText(""" **Keyboard Model:** """) + label25.setAlignment(QtCore.Qt.AlignCenter) + # label25 = self._set_default_margins(label25) + self.grid.addWidget(label25, 5, 10, 1, 1) + + label26 = QtWidgets.QLabel() + label26.setTextFormat(QtCore.Qt.MarkdownText) + label26.setText(settings["MODEL"]) + label26.setAlignment(QtCore.Qt.AlignCenter) + # label26 = self._set_default_margins(label26) + self.grid.addWidget(label26, 5, 11, 1, 1) + + label27 = QtWidgets.QLabel() + label27.setTextFormat(QtCore.Qt.MarkdownText) + label27.setText(""" **Keyboard Layout:** """) + label27.setAlignment(QtCore.Qt.AlignCenter) + # label27 = self._set_default_margins(label27) + self.grid.addWidget(label27, 6, 10, 1, 1) + + label28 = QtWidgets.QLabel() + label28.setTextFormat(QtCore.Qt.MarkdownText) + label28.setText(settings["LAYOUT"]) + label28.setAlignment(QtCore.Qt.AlignCenter) + # label28 = self._set_default_margins(label28) + self.grid.addWidget(label28, 6, 11, 1, 1) + + label29 = QtWidgets.QLabel() + label29.setTextFormat(QtCore.Qt.MarkdownText) + label29.setText(""" **Keyboard Variant:** """) + label29.setAlignment(QtCore.Qt.AlignCenter) + # label29 = self._set_default_margins(label29) + self.grid.addWidget(label29, 7, 10, 1, 1) + + label30 = QtWidgets.QLabel() + label30.setTextFormat(QtCore.Qt.MarkdownText) + label30.setText(settings["VARIENT"]) + label30.setAlignment(QtCore.Qt.AlignCenter) + # label30 = self._set_default_margins(label30) + self.grid.addWidget(label30, 7, 11, 1, 1) + + button1 = QtWidgets.QPushButton("INSTALL NOW -->") + button1.clicked.connect(self.onnextclicked) + # button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 8, 11, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + # button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 8, 1, 1, 1) + + # self.set_position(Gtk.WindowPosition.CENTER) + + # self.show_all() + + def onnextclicked(self, button): + """set install to True""" + self.install = True + self.exit("clicked") + + # def _set_default_margins(self, widget): + # """Set default margin size""" + # widget.set_margin_start(10) + # widget.set_margin_end(10) + # widget.set_margin_top(10) + # widget.set_margin_bottom(10) + # return widget + + def exit(self, button): + """exit""" + self.close() + print(1) + return 1 + + +def show_confirm(settings, boot_time=False): + """Show confirmation dialog""" + app = QtWidgets.QApplication([sys.argv[0]]) + window = Main(settings) + if boot_time: + window = QCommon.set_window_undecorated(window) + window.show() + app.exec() + return window.install + + +if __name__ == "__main__": + settings = json.loads(sys.argv[1]) + print(show_confirm(settings)) diff --git a/usr/share/edamame/UI/Qt_UI/error.py b/usr/share/edamame/UI/Qt_UI/error.py new file mode 100755 index 0000000..233466e --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/error.py @@ -0,0 +1,93 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# error.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Error dialog for Edamame""" +from sys import argv +from qtpy import QtGui, QtWidgets, QtCore +try: + from UI.Qt_UI import report +except ImportError: + try: + from Qt_UI import report + except ImportError: + import report + + +class Main(report.Main): + """UI Error Class""" + def __init__(self, display, report_setting): + """set up Error UI""" + super().__init__() + self.display = display + self.enable_reporting = report_setting + self.scrolling = False + self.main_menu("clicked") + + def main_menu(self, widget): + """Main Menu""" + self.clear_window() + + self.label = QtWidgets.QLabel("**" + self.display + "**") + self.label.setTextFormat(QtCore.Qt.MarkdownText) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label = self._set_default_margins(self.label) + self.grid.addWidget(self.label, 1, 1, 1, 3) + + if self.enable_reporting: + self.label2 = QtWidgets.QLabel(""" +If you wish to notify the developers of this failed installation,\n +you can send an installation report below. + """) + self.label2.setAlignment(QtCore.Qt.AlignCenter) + self.label2.setTextFormat(QtCore.Qt.MarkdownText) + self.label2 = self._set_default_margins(self.label2) + self.grid.addWidget(self.label2, 2, 1, 1, 3) + + self.button = QtWidgets.QPushButton("Send Installation report") + self.button.clicked.connect(self.main) + self.button = self._set_default_margins(self.button) + self.grid.addWidget(self.button, 3, 3, 1, 1) + + self.button2 = QtWidgets.QPushButton("Exit") + self.button2.clicked.connect(self.exit) + self.button2 = self._set_default_margins(self.button2) + self.grid.addWidget(self.button2, 3, 1, 1, 1) + + +def show_error(display: str, report_setting: bool = True): + """Show Error Dialog + + `display` is displayed to the user as the main error text, + along with instructions on how to send an installation report. + `report` controls whether or not the user can send an installation report""" + app = QtWidgets.QApplication([argv[0]]) + window = Main(display, report_setting) + # window.set_position(Gtk.WindowPosition.CENTER) + # window.clicked.connect("delete-event", Main.exit) + window.show() + app.exec() + + +if __name__ == '__main__': + DISPLAY = str(argv[1]) + show_error(DISPLAY) diff --git a/usr/share/edamame/UI/Qt_UI/main.py b/usr/share/edamame/UI/Qt_UI/main.py new file mode 100755 index 0000000..4305d26 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/main.py @@ -0,0 +1,2297 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# main.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Main Installation UI""" +from __future__ import print_function +import re +import sys +import json +import os +import subprocess +import traceback +import random +from qtpy import QtGui, QtWidgets, QtCore +import common +import auto_partitioner as ap +try: + import UI.Qt_UI.qt_common as QCommon +except ImportError: + import qt_common as QCommon + + +def has_special_character(input_string): + """Check for special characters""" + regex = re.compile(r'[@_!#$%^&*()<>?/\|}{~:]') + if regex.search(input_string) is None: + return False + return True + + +with open("../../../../../etc/edamame/settings.json") as config_file: + SETTINGS = json.loads(config_file.read()) + + +DEFAULT = f""" +# Welcome to the {SETTINGS["distro"]} System Installer!\n +\n\n +A few things before we get started:\n +\n\n +**PARTITIONING**\n +\n\n +The {SETTINGS["distro"]} System Installer uses Gparted to allow the user to set up their\n +partitions manually. It is advised to account for this if installing next to\n +another OS. If using automatic partitoning, it will take up the entirety of\n +the drive told to use. Loss of data from usage of this tool is entirely at the\n +fault of the user. You have been warned.\n +\n""" + +KEYBOARD_COMPLETION = "TO DO" +USER_COMPLETION = "TO DO" +PART_COMPLETION = "TO DO" +LOCALE_COMPLETION = "TO DO" +OPTIONS_COMPLETION = "TO DO" + + +class Main(QtWidgets.QWidget): + """Main UI Window""" + def __init__(self, screen_size): + """Initialize the Window""" + super().__init__() + self.setWindowTitle("Edamame") + self.setWindowIcon(QtGui.QIcon.fromTheme("system-installer")) + self.grid = QtWidgets.QGridLayout() + self.setLayout(self.grid) + + self.screen_size = (screen_size.width(), screen_size.height()) + + # Initialize setting values + self.data = {"AUTO_PART": "", "HOME": "", "ROOT": "", "EFI": "", + "SWAP": "", "LANG": "", "TIME_ZONE": "", "USERNAME": "", + "PASSWORD": "", "COMPUTER_NAME": "", "EXTRAS": "", + "UPDATES": "", "LOGIN": "", "MODEL": "", "LAYOUT": "", + "VARIENT": "", "raid_array": {"raid_type": None, + "disks": {"1": None, + "2": None, + "3": None, + "4": None}}, + "COMPAT_MODE": ""} + + self.langs = {'Afar': "aa", 'Afrikaans': "af", 'Aragonese': "an", + 'Arabic': "ar", 'Asturian': "ast", 'Belarusian': "be", + 'Bulgarian': "bg", 'Breton': "br", 'Bosnian': "bs", + 'Catalan': "ca", 'Czech': "cs", 'Welsh': "cy", + "Danish": 'da', "German": 'de', "Greek": 'el', + "English": 'en', "Esperanto": 'eo', "Spanish": 'es', + "Estonian": 'et', "Basque": 'eu', "Finnish": 'fi', + "Faroese": 'fo', "French": 'fr', "Irish": 'ga', + "Gaelic": 'gd', "Galician": 'gl', "Manx": 'gv', + "Hebrew": 'he', "Croatian": 'hr', "Upper Sorbian": 'hsb', + "Hungarian": 'hu', "Indonesian": 'id', "Icelandic": 'is', + "Italian": 'it', "Japanese": 'ja', "Kashmiri": 'ka', + "Kazakh": 'kk', "Greenlandic": 'kl', "Korean": 'ko', + "Kurdish": 'ku', "Cornish": 'kw', 'Bhili': "bhb", + "Ganda": 'lg', "Lithuanian": 'lt', "Latvian": 'lv', + "Malagasy": 'mg', "Maori": 'mi', "Macedonian": 'mk', + "Malay": 'ms', "Maltese": 'mt', "Min Nan Chinese": 'nan', + "North Ndebele": 'nb', "Dutch": 'nl', + "Norwegian Nynorsk": 'nn', "Occitan": 'oc', "Oromo": 'om', + "Polish": 'pl', "Portuguese": 'pt', "Romanian": 'ro', + "Russian": 'ru', "Slovak": 'sk', "Slovenian": 'sl', + "Northern Sami": 'so', "Albanian": 'sq', "Serbian": 'sr', + "Sotho": 'st', "Swedish": 'sv', "Tulu": 'tcy', + "Tajik": 'tg', "Thai": 'th', "Tagalog": 'tl', + "Turkish": 'tr', "Uighur": 'ug', "Ukrainian": 'uk', + "Uzbek": 'uz', "Walloon": 'wa', "Xhosa": 'xh', + "Yiddish": 'yi', "Chinese": 'zh', "Zulu": 'zu'} + self.raid_def = {"RAID0": {"min_drives": 2, + "desc": "Max performance, Least Reliability", + "raid_num": 0}, + "RAID1": {"min_drives": 2, + "desc": "Least Performance, Max Reliability", + "raid_num": 1}, + "RAID10": {"min_drives": 4, + "desc": "Balanced Performance and Reliability", + "raid_num": 10}} + + # Open initial window + self.reset("clicked") + + def show_all(self): + """Final work to prepare the window to be shown""" + self.resize(self.sizeHint()) + size = (self.size().width(), self.size().height()) + print(f"Window size: {size}") + size_offset = (round(size[0] / 2), round(size[1]/ 2)) + position = (round(self.screen_size[0] / 2), round(self.screen_size[1] / 2)) + self.move(position[0] - size_offset[0], position[1] - size_offset[1]) + # size = (self.size().width(), self.size().height()) + # self.setFixedSize(size[0], size[1]) + + + + def _set_default_margins(self, widget): + """Set default margin size""" + try: + margin = QtCore.QMargins(10, 10, 10, 10) + widget.setContentsMargins(margin) + except AttributeError: + common.eprint("WARNING: QtCore.QMargins() does not exist. Spacing in UI might be a bit wonky.") + return widget + + def quick_install_warning(self, button): + """Quick Install Mode Entry Point""" + self.clear_window() + + label = QtWidgets.QLabel("""\n +# QUICK INSTALL MODE INITIATED\n +\n +You have activated Quick Install mode.\n +\n +This mode allows users to provide the system installation utility with\n +a config file containing their prefrences for installation and set up.\n +\n +An example of one of these can be found at /etc/edamame/quick-install-template.json\n +\n""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label.setAlignment(QtCore.Qt.AlignCenter) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + button4 = QtWidgets.QPushButton("Select Config File") + button4.clicked.connect(self.select_config) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 2, 3, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.reset) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 2, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 2, 1, 1, 1) + + self.show_all() + + def reset(self, button): + """Main Splash Window""" + global DEFAULT + self.clear_window() + + label = QtWidgets.QLabel(DEFAULT) + label.setTextFormat(QtCore.Qt.MarkdownText) + label.setAlignment(QtCore.Qt.AlignLeft) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 4) + + button1 = QtWidgets.QPushButton("Normal Installation") + button1.clicked.connect(self.main_menu) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 2, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 2, 1, 1, 1) + + button3 = QtWidgets.QPushButton("Quick Installation") + button3.clicked.connect(self.quick_install_warning) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 2, 2, 1, 1) + + button4 = QtWidgets.QPushButton("OEM Installation") + button4.clicked.connect(self.oem_startup) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 2, 3, 1, 1) + + self.show_all() + + def oem_startup(self, widget): + """Start up OEM installation""" + self.clear_window() + + # show a confirmation window + + label = QtWidgets.QLabel(""" +## Are you sure you want to do an OEM installation?\n +\n +OEM installation should **ONLY** be used by OEMs, or those installing\n +Drauger OS for other people, ahead of time. It has several limitations\n +over a normal or quick installation:\n +\n +* Takes up the entire drive it is installed to\n +* Locale, keyboard, and password must be set AFTER installation\n +* Hostname and username can not be set by the user\n +* Restricted Extras AND Updates are automatically installed\n +* A Swap file will automatically be generated to enable Hybrid Sleep\n +\n""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label.setAlignment(QtCore.Qt.AlignCenter) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + button1 = QtWidgets.QPushButton("Proceed -->") + button1.clicked.connect(self.oem_run) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 2, 3, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 2, 2, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.reset) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 2, 1, 1, 1) + + self.show_all() + + def oem_run(self, widget): + """Start up OEM installation""" + self.data = "/etc/edamame/oem-install.json" + self.complete() + + def select_config(self, widget): + """Quick Install File Selection Window""" + common.eprint("\t###\tQUICK INSTALL MODE ACTIVATED\t###\t") + dialog = QtWidgets.QFileDialog(self) + + dialog.setMimeTypeFilters(["application/json", "application/x-xz-compressed-tar"]) + + dialog.exec() + response = dialog.selectedFiles()[0] + if response != "": + self.data = response + self.complete() + else: + self.data = 1 + self.exit("clicked") + + def main_menu(self, button): + """Main Menu""" + self.clear_window() + + self.label = QtWidgets.QLabel(""" +### Feel free to complete any of the below segments in any order.\n +### However, all segments must be completed.\n""") + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setTextFormat(QtCore.Qt.MarkdownText) + self.label = self._set_default_margins(self.label) + self.grid.addWidget(self.label, 1, 2, 1, 2) + + completion_label = QtWidgets.QLabel("""**COMPLETION**""") + completion_label.setAlignment(QtCore.Qt.AlignCenter) + completion_label.setTextFormat(QtCore.Qt.MarkdownText) + completion_label = self._set_default_margins(completion_label) + self.grid.addWidget(completion_label, 2, 2, 1, 1) + + button8 = QtWidgets.QPushButton("Keyboard") + button8.clicked.connect(self.keyboard) + button8 = self._set_default_margins(button8) + self.grid.addWidget(button8, 3, 3, 1, 1) + + label_keyboard = QtWidgets.QLabel(KEYBOARD_COMPLETION) + label_keyboard.setAlignment(QtCore.Qt.AlignCenter) + label_keyboard.setTextFormat(QtCore.Qt.MarkdownText) + label_keyboard = self._set_default_margins(label_keyboard) + self.grid.addWidget(label_keyboard, 3, 2, 1, 1) + + button4 = QtWidgets.QPushButton("Locale and Time") + button4.clicked.connect(self.locale) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 4, 3, 1, 1) + + label_locale = QtWidgets.QLabel(LOCALE_COMPLETION) + label_locale.setAlignment(QtCore.Qt.AlignCenter) + label_locale.setTextFormat(QtCore.Qt.MarkdownText) + label_locale = self._set_default_margins(label_locale) + self.grid.addWidget(label_locale, 4, 2, 1, 1) + + button5 = QtWidgets.QPushButton("Options") + button5.clicked.connect(self.options) + button5 = self._set_default_margins(button5) + self.grid.addWidget(button5, 5, 3, 1, 1) + + label_options = QtWidgets.QLabel(OPTIONS_COMPLETION) + label_options.setAlignment(QtCore.Qt.AlignCenter) + label_options.setTextFormat(QtCore.Qt.MarkdownText) + label_options = self._set_default_margins(label_options) + self.grid.addWidget(label_options, 5, 2, 1, 1) + + button6 = QtWidgets.QPushButton("Partitioning") + button6.clicked.connect(self.partitioning) + button6 = self._set_default_margins(button6) + self.grid.addWidget(button6, 6, 3, 1, 1) + + label_part = QtWidgets.QLabel(PART_COMPLETION) + label_part.setAlignment(QtCore.Qt.AlignCenter) + label_part.setTextFormat(QtCore.Qt.MarkdownText) + label_part = self._set_default_margins(label_part) + self.grid.addWidget(label_part, 6, 2, 1, 1) + + button7 = QtWidgets.QPushButton("User Settings") + button7.clicked.connect(self.user) + button7 = self._set_default_margins(button7) + self.grid.addWidget(button7, 7, 3, 1, 1) + + label_user = QtWidgets.QLabel(USER_COMPLETION) + label_user.setAlignment(QtCore.Qt.AlignCenter) + label_user.setTextFormat(QtCore.Qt.MarkdownText) + label_user = self._set_default_margins(label_user) + self.grid.addWidget(label_user, 7, 2, 1, 1) + + button1 = QtWidgets.QPushButton("DONE") + button1.clicked.connect(self.done) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 8, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 8, 1, 1, 1) + + self.show_all() + + def user(self, button): + """User setup Window""" + self.clear_window() + + label = QtWidgets.QLabel("""# Set Up Main User""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + label1 = QtWidgets.QLabel(" Username: ") + label1.setAlignment(QtCore.Qt.AlignCenter) + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 3, 1, 1, 2) + + self.username = QtWidgets.QLineEdit() + self.username.setText(self.data["USERNAME"]) + self.username = self._set_default_margins(self.username) + self.grid.addWidget(self.username, 3, 3, 1, 1) + + label2 = QtWidgets.QLabel(" Computer\'s Name: ") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 4, 1, 1, 2) + + self.compname = QtWidgets.QLineEdit() + self.compname.setText(self.data["COMPUTER_NAME"]) + self.compname = self._set_default_margins(self.compname) + self.grid.addWidget(self.compname, 4, 3, 1, 1) + + auto_gen_hostname = QtWidgets.QPushButton("Auto-Generate Computer Name") + auto_gen_hostname.clicked.connect(self.generate_hostname) + auto_gen_hostname = self._set_default_margins(auto_gen_hostname) + self.grid.addWidget(auto_gen_hostname, 4, 4, 1, 1) + + label3 = QtWidgets.QLabel(" Password: ") + label3.setAlignment(QtCore.Qt.AlignCenter) + label3.setTextFormat(QtCore.Qt.MarkdownText) + label3 = self._set_default_margins(label3) + self.grid.addWidget(label3, 5, 1, 1, 2) + + self.password = QtWidgets.QLineEdit() + self.password.setEchoMode(QtWidgets.QLineEdit.Password) + self.password.setText(self.data["PASSWORD"]) + self.password = self._set_default_margins(self.password) + self.grid.addWidget(self.password, 5, 3, 1, 1) + + label4 = QtWidgets.QLabel(" Confirm Pasword: ") + label4.setAlignment(QtCore.Qt.AlignCenter) + label4.setTextFormat(QtCore.Qt.MarkdownText) + label4 = self._set_default_margins(label4) + self.grid.addWidget(label4, 6, 1, 1, 2) + + self.passconf = QtWidgets.QLineEdit() + self.passconf.setEchoMode(QtWidgets.QLineEdit.Password) + self.passconf.setText(self.data["PASSWORD"]) + self.passconf = self._set_default_margins(self.passconf) + self.grid.addWidget(self.passconf, 6, 3, 1, 1) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.onnext2clicked) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 8, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 8, 2, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.main_menu) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 8, 1, 1, 1) + + self.show_all() + + def generate_hostname(self, widget): + """Generate a hostname that follows these rules: + - 16 characters long + - Starts with "DRAUGER-", no quotation marks + - The remaining characters should be a randomly generated string of uppercase letters and numbers + - Avoid Letters: + - O, I + - Avoid numnbers: + - 0, 1 + - Have no special characters""" + remaining_len = SETTINGS["hostname_append_len"] + allowed_letters = ["A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] + allowed_numbers = [2, 3, 4, 5, 6, 7, 8, 9] + suffix = [] + while remaining_len > 0: + if (random.randint(0, 10) % 2) == 0: + # letter + suffix.append(random.sample(allowed_letters, 1)[0]) + else: + # number + suffix.append(str(random.sample(allowed_numbers, 1)[0])) + remaining_len -= 1 + suffix = "".join(suffix) + output = f"{SETTINGS['hostname_prepend']}-{suffix}" + + self.data["COMPUTER_NAME"] = output + self.compname.setText(output) + + def onnext2clicked(self, button): + """Password, Username, and hostname Checker""" + self.data["PASSWORD"] = self.password.text() + pass2 = self.passconf.text() + self.data["USERNAME"] = self.username.text() + self.data["USERNAME"] = self.data["USERNAME"].lower() + self.data["COMPUTER_NAME"] = self.compname.text() + if self.data["PASSWORD"] != pass2: + label5 = QtWidgets.QLabel("Passwords do not match") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif len(self.data["PASSWORD"]) < SETTINGS["min_password_length"]: + label5 = QtWidgets.QLabel("Password is less than 4 characters") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif has_special_character(self.data["USERNAME"]): + label5 = QtWidgets.QLabel("Username contains special characters") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif " " in self.data["USERNAME"]: + label5 = QtWidgets.QLabel("Username contains space") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif len(self.data["USERNAME"]) < 1: + label5 = QtWidgets.QLabel("Username empty") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif has_special_character(self.data["COMPUTER_NAME"]): + label5 = QtWidgets.QLabel("Computer Name contains non-hyphen special character") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif " " in self.data["COMPUTER_NAME"]: + label5 = QtWidgets.QLabel("Computer Name contains space") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + elif len(self.data["COMPUTER_NAME"]) < 1: + label5 = QtWidgets.QLabel("Computer Name is empty") + label5.setAlignment(QtCore.Qt.AlignCenter) + label5.setTextFormat(QtCore.Qt.MarkdownText) + label5 = self._set_default_margins(label5) + try: + self.grid.itemAtPosition(7, 1).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label5, 7, 1, 1, 3) + else: + global USER_COMPLETION + USER_COMPLETION = "COMPLETED" + self.main_menu("clicked") + self.show_all() + + def partitioning(self, button): + """Partitioning Main Window""" + self.clear_window() + + label = QtWidgets.QLabel(f""" +Would you like to let {SETTINGS["distro"]} automatically partition a drive for installation?\n +Or, would you like to manually partition space for it?\n +\n +**NOTE**\n +Auto partitioning takes up an entire drive. If you are uncomfortable with\n +this, please either manually partition your drive, or abort installation\n +now.\n +\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 7) + + link = QtWidgets.QPushButton("Manual Partitioning") + link.clicked.connect(self.opengparted) + link = self._set_default_margins(link) + self.grid.addWidget(link, 5, 5, 1, 1) + + button1 = QtWidgets.QPushButton("Automatic Partitioning") + button1.clicked.connect(self.auto_partition) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 5, 7, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 5, 3, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.main_menu) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 5, 1, 1, 1) + + self.show_all() + + def auto_partition(self, button): + """Auto Partitioning Settings Window""" + self.clear_window() + self.data["AUTO_PART"] = True + + # Get a list of disks and their capacity + self.devices = json.loads(subprocess.check_output(["lsblk", "-n", "-i", "--json", + "-o", "NAME,SIZE,TYPE,FSTYPE"]).decode()) + self.devices = self.devices["blockdevices"] + dev = [] + for each2 in enumerate(self.devices): + if "loop" in self.devices[each2[0]]["name"]: + continue + dev.append(self.devices[each2[0]]) + devices = [] + for each4 in dev: + devices.append(each4) + devices = [x for x in devices if x != []] + for each4 in devices: + if "sr" in each4["name"]: + devices.remove(each4) + for each4 in enumerate(devices): + del devices[each4[0]]["type"] + for each4 in enumerate(devices): + devices[each4[0]]["name"] = "/dev/%s" % (devices[each4[0]]["name"]) + + # Jesus Christ that's a lot of parsing and formatting. + # At least it's done. + # Now we just need to remove anything that may have been used to make a + # RAID Array + + for each in range(len(devices) - 1, -1, -1): + for each1 in self.data["raid_array"]["disks"]: + if devices[each]["name"] == self.data["raid_array"]["disks"][each1]: + del devices[each] + break + + # Now we have to make a GUI using them . . . + + label = QtWidgets.QLabel("""Which drive would you like to install to?""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 2, 1, 3) + + self.disks = QtWidgets.QComboBox() + for each4 in enumerate(devices): + self.disks.addItem(f"{devices[each4[0]]["name"]} Size: {devices[each4[0]]["size"]}", (devices[each4[0]]["name"])) + if self.data["ROOT"] != "": + self.disks.setCurrentText(self.data["ROOT"]) + self.disks = self._set_default_margins(self.disks) + self.disks.currentTextChanged.connect(self._set_root_part) + self.grid.addWidget(self.disks, 2, 2, 1, 3) + + home_part = QtWidgets.QCheckBox("Separate home partition") + if ((self.data["HOME"] != "") and (self.data["HOME"] != "NULL")): + home_part.setChecked(True) + home_part.toggled.connect(self.auto_home_setup) + home_part = self._set_default_margins(home_part) + self.grid.addWidget(home_part, 3, 3, 1, 2) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.confirm_auto_part) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 5, 1, 1) + + button5 = QtWidgets.QPushButton("Make RAID Array") + button5.clicked.connect(self.define_array) + button5 = self._set_default_margins(button5) + self.grid.addWidget(button5, 6, 4, 1, 1) + + button4 = QtWidgets.QPushButton("Make Space") + button4.clicked.connect(self.make_space) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 6, 3, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 2, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.partitioning) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 6, 1, 1, 1) + + self.show_all() + + def define_array(self, widget, error=None): + """Define btrfs RAID Array settings""" + self.clear_window() + + dev = [] + for each2 in enumerate(self.devices): + if "loop" in self.devices[each2[0]]["name"]: + continue + dev.append(self.devices[each2[0]]) + devices = [] + for each4 in dev: + devices.append(each4) + devices = [x for x in devices if x != []] + for each4 in devices: + if each4["name"] == "sr0": + devices.remove(each4) + + for each in range(len(devices) - 1, -1, -1): + if devices[each]["name"] == self.data["ROOT"]: + del devices[each] + + if self.data["raid_array"]["raid_type"] is None: + loops = 2 + else: + loops = self.raid_def[self.data["raid_array"]["raid_type"]]["min_drives"] + + label = QtWidgets.QLabel("""**Define RAID Array** +RAID Arrays can only be used as your home partition.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + label1 = QtWidgets.QLabel() + if error is None: + label1.setText("""Please Select which RAID Type to use and which +drives to use for the RAID array.""") + elif error == "type_not_set": + label1.setText("""You must select a RAID Type to proceed.""") + elif error == "disk_not_set": + label1.setText("""You do not have enough drives set for this RAID +Type. Minimum drives is: %s""" % (loops)) + label1.setAlignment(QtCore.Qt.AlignCenter) + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 3) + + label2 = QtWidgets.QLabel("RAID Type: ") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 3, 1, 1, 1) + + raid_type = QtWidgets.QComboBox() + for each in self.raid_def: + raid_type.addItem(f"{each}: {self.raid_def[each]["desc"]}", each) + raid_type = self._set_default_margins(raid_type) + raid_type.setItemText(self.data["raid_array"]["raid_type"]) + raid_type.currentTextChanged.connect(self._change_raid_type) + self.grid.addWidget(raid_type, 3, 2, 1, 2) + + for each in range(loops): + label = QtWidgets.QLabel(f"Drive {each + 1}") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 4 + each, 1, 1, 1) + + device_drop_down = QtWidgets.QComboBox() + for each4 in devices: + skip = False + for each1 in self.data["raid_array"]["disks"]: + if each4["name"] == self.data["raid_array"]["disks"][each1]: + if str(each + 1) != each1: + skip = True + if skip: + continue + device_drop_down.addItem(f"{each4["name"]} Size: {each4["size"]}", each4["name"]) + + device_drop_down.setCurrentIndex(self.data["raid_array"]["disks"][str(each + 1)]) + if (each + 1) == 1: + device_drop_down.currentTextChanged.connect(self._assign_raid_disk_1) + elif (each + 1) == 2: + device_drop_down.currentTextChanged.connect(self._assign_raid_disk_2) + elif (each + 1) == 3: + device_drop_down.currentTextChanged.connect(self._assign_raid_disk_3) + elif (each + 1) == 4: + device_drop_down.currentTextChanged.connect(self._assign_raid_disk_4) + device_drop_down = self._set_default_margins(device_drop_down) + self.grid.addWidget(device_drop_down, 4 + each, 2, 1, 2) + + button1 = QtWidgets.QPushButton("Done") + button1.clicked.connect(self.confirm_raid_array) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 9, 3, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 9, 1, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back to Main Menu") + button3.clicked.connect(self.main_menu) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 9, 2, 1, 1) + + self.show_all() + + # I know there is a better way to assign disks than this, or at least a + # Better way to define these functions. But this was the simplest way I can + # Think to do it right now. In the future, I want to add the ability to + # increase RAID Array size, but that will have to include some more dynamic + # programming that I just don't know how to do or care to learn right now + # considering it's 1 AM at time of writing + + def _assign_raid_disk_1(self, widget): + """Assign RAID Disk 1""" + self.data["raid_array"]["disks"]["1"] = widget + self.define_array("clicked") + + def _assign_raid_disk_2(self, widget): + """Assign RAID Disk 2""" + self.data["raid_array"]["disks"]["2"] = widget + self.define_array("clicked") + + def _assign_raid_disk_3(self, widget): + """Assign RAID Disk 3""" + self.data["raid_array"]["disks"]["3"] = widget + self.define_array("clicked") + + def _assign_raid_disk_4(self, widget): + """Assign RAID Disk 4""" + self.data["raid_array"]["disks"]["4"] = widget + self.define_array("clicked") + + def _change_raid_type(self, widget): + """Set RAID type""" + self.data["raid_array"]["raid_type"] = widget + if self.data["raid_array"]["raid_type"].lower() in ("raid0", "raid1"): + self.data["raid_array"]["disks"]["3"] = None + self.data["raid_array"]["disks"]["4"] = None + self.define_array("clicked") + + def confirm_raid_array(self, widget): + """Confirm RAID settings and modify other + installer settings as necessary""" + if self.data["raid_array"]["raid_type"] is None: + self.define_array("clicked", error="type_not_set") + return + count = 0 + for each in self.data["raid_array"]["disks"]: + if self.data["raid_array"]["disks"][each] is not None: + count += 1 + if count < self.raid_def[self.data["raid_array"]["raid_type"]]["min_drives"]: + self.define_array("clicked", error="disk_not_set") + return + + self.clear_window() + + label = QtWidgets.QLabel("**Are you sure you want to make this RAID Array?**") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + label1 = QtWidgets.QLabel(f"**RAID Type:** {self.data["raid_array"]["raid_type"]}") + label1.setAlignment(QtCore.Qt.AlignCenter) + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 3) + + for each in self.data["raid_array"]["disks"]: + if self.data["raid_array"]["disks"][each] is None: + continue + label = QtWidgets.QLabel(f"""**Drive {each}:** {self.data["raid_array"]["disks"][each]}""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2 + int(each), 1, 1, 3) + + button1 = QtWidgets.QPushButton("Confirm") + button1.clicked.connect(self.cement_raid_array) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 9, 3, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 9, 1, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.define_array) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 9, 2, 1, 1) + + self.show_all() + + def cement_raid_array(self, widget): + """Set alternate settings so that the RAID Array can be handled internally""" + self.data["HOME"] = self.data["raid_array"]["disks"]["1"] + self.auto_partition("clicked") + + def make_space(self, widget, drive=None): + """Window for making space on an installed drive""" + self.clear_window() + + label = QtWidgets.QLabel("""Drive to Delete From""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + data = ap.check_disk_state() + devices = QtWidgets.QComboBox() + for each in data: + devices.addItem(f"{each['name']}, size: {int(ap.bytes_to_gb(each['size']))}GB", each["name"]) + devices.currentTextChanged.connect(self.make_space_parts) + devices = self._set_default_margins(devices) + self.grid.addWidget(devices, 2, 1, 1, 3) + + label2 = QtWidgets.QLabel("""Partition to Delete""") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 3, 1, 1, 3) + + self.parts = QtWidgets.QComboBox() + self.parts = self._set_default_margins(self.parts) + self.grid.addWidget(self.parts, 4, 1, 1, 3) + + button1 = QtWidgets.QPushButton("Done") + button1.clicked.connect(self.auto_partition) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 3, 1, 1) + + button3 = QtWidgets.QPushButton("!!! DELETE !!!") + button3.clicked.connect(self.confirm_remove_part) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 6, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 1, 1, 1) + + if drive is not None: + devices.setCurrentText(drive) + self.make_space_parts(devices) + + self.show_all() + + def make_space_parts(self, widget): + """Set partitions to show for make_space()""" + self.parts.remove_all() + data = ap.check_disk_state() + name = widget.currentText() + for each in data: + if each["name"] == name: + if "children" in each: + for each1 in each["children"]: + self.parts.addItem(f"{each1['name']}, filesystem: {each1['fstype']}, size: {int(ap.bytes_to_gb(each1['size']))}GB", + each1["name"]) + + def confirm_remove_part(self, widget): + """Confirm removal of designated partition""" + self.clear_window() + + label = QtWidgets.QLabel("""**Are you sure you want to delete this partition?**""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + label1 = QtWidgets.QLabel(self.parts.currentText()) + label1.setAlignment(QtCore.Qt.AlignCenter) + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 1) + + button1 = QtWidgets.QPushButton("NO") + button1.clicked.connect(self.make_space) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 3, 1, 1) + + button3 = QtWidgets.QPushButton("YES") + button3.clicked.connect(self.remove_part) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 6, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 1, 1, 1) + + self.show_all() + + def remove_part(self, widget): + """Interface for removing partitions""" + part = self.parts.currentText() + ap.delete_part(part) + if "nvme" in part: + self.make_space("clicked", drive=part[:-2]) + else: + self.make_space("clicked", drive=part[:-1]) + + def auto_home_setup(self, widget): + """Handle preexisting vs making a new home directory""" + if widget: + pre_exist = QtWidgets.QCheckBox("Pre-existing") + pre_exist.toggled.connect(self.auto_home_setup2) + pre_exist = self._set_default_margins(pre_exist) + self.grid.addWidget(pre_exist, 4, 1, 1, 2) + + self.data["HOME"] = "MAKE" + else: + try: + self.grid.itemAt((4, 1)).widget().setParent(None) + except TypeError: + pass + self.data["HOME"] = "" + + self.show_all() + + def auto_home_setup2(self, widget): + """Provide options for prexisting home partitions""" + if widget: + dev_list = tuple(self.devices) + new_dev_list = [] # this will be the final list that is displayed for the user + + # todo: account for BTRFS drives that have no partitions + for device in dev_list: # we will iterate through the dev list and add devices to the new list + try: + if device == []: # if the device is empty, we skip + continue + elif 'children' in device: + for child in device['children']: + if "type" not in child.keys(): # if it doesn't have a label, skip + continue + elif not child['type'] == 'part': # if it isn't labeled partition, skip + continue + + test_child = {'name': child['name'], 'size': child['size']} + + if test_child not in new_dev_list: # make sure child object is not already in dev_list + new_dev_list.append(test_child) + elif device["fstype"] is not None: + # if the drive has no partition table, just a file system, + # add it + if device["fstype"] != "squashfs": + # don't add it, beacuse it's a squashfs file system + new_device = {"name": device["name"], "size": device["size"]} + new_dev_list.append(new_device) + elif "type" not in device.keys(): # if it doesn't have a label, skip + continue + elif device['type'] != 'part': + # if it isn't labeled partition, skip + continue + else: + new_device = {'name': device['name'], 'size': device['size']} + + new_dev_list.append(new_device) + except KeyError: + common.eprint(traceback.format_exc()) + print(json.dumps(device, indent=2)) + + # TEMPORARY: Remove the ability to use a home partition on the same + # drive as where the root partition is + for each in range(len(new_dev_list) - 1, -1, -1): + if self.data["ROOT"][5:] in new_dev_list[each]["name"]: + del new_dev_list[each] + + home_cmbbox = QtWidgets.QComboBox() + + # properly format device names and add to combo box + for device in new_dev_list: + if device["name"][:5] != "/dev/": + device['name'] = f"/dev/{device['name']}" + + home_cmbbox.addItem(f"{device['name']} Size: {device['size']}", device['name']) + + if self.data["HOME"] != "": + home_cmbbox.setCurrentText(self.data["HOME"]) + home_cmbbox.currentTextChanged.connect(self.select_home_part) + parts = self._set_default_margins(home_cmbbox) + self.grid.addWidget(home_cmbbox, 5, 1, 1, 2) + else: + self.data["HOME"] = "MAKE" + + self.show_all() + + def select_home_part(self, widget): + """Set pre-existing home partition, based on user input""" + if os.path.exists(widget): + self.data["HOME"] = widget + + def _set_root_part(self, widget): + """set root drive""" + print("HERE!") + print(widget) + self.data["ROOT"] = widget + # self.auto_partition("clicked") + + def confirm_auto_part(self, button): + """Force User to either pick a drive to install to, abort, + or backtrack + """ + if ap.is_EFI(): + self.data["EFI"] = True + else: + self.data["EFI"] = False + if self.data["HOME"] == "": + self.data["HOME"] = "NULL" + self.data["SWAP"] = "FILE" + if self.disks.currentText() is None: + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + label = QtWidgets.QLabel(""" +Which drive would you like to install to?\n +\n +**You must pick a drive to install to or abort installation.**\n +\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + else: + self.data["ROOT"] = self.disks.currentText() + global PART_COMPLETION + PART_COMPLETION = "COMPLETED" + self.main_menu("clicked") + + self.show_all() + + def set_up_partitioner_label(self, additional_message=""): + """prepare top label for display on manual partitioner + + Keyword arguments: + additonal message -- any errors that need to be displayed below original message + + Return value: + label -- the top label ready for additional formatting and display + """ + label = QtWidgets.QLabel + + input_string = """ +What are the mount points for the partitions you wish to be used?\n +Leave empty the partitions you don't want.\n +**ROOT PARTITION MUST BE USED**\n""" + + if additional_message != "": + input_string = input_string + "\n" + additional_message + + label.setText(input_string) + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + + return label + + def input_part(self, button): + """Manual Partitioning Input Window""" + self.clear_window() + + label = self.set_up_partitioner_label() + self.grid.addWidget(label, 1, 1, 1, 3) + + label2 = QtWidgets.QLabel("ROOT Partition (Mounted at /)") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 2, 1, 1, 2) + + self.root = QtWidgets.QComboBox() + self.root = self._set_default_margins(self.root) + self.root.currentTextChanged.connect(self.update_possible_root_parts) + self.grid.addWidget(self.root, 2, 3, 1, 1) + + self.root_parts = QtWidgets.QComboBox() + self.root_parts = self._set_default_margins(self.root_parts) + self.root_parts.currentTextChanged.connect(self.update_possible_home_parts) + self.grid.addWidget(self.root_parts, 3, 3, 1, 1) + + root_info = QtWidgets.QPushButton("Info on Root Partition") + root_info.clicked.connect(self.explain_root) + root_info = self._set_default_margins(root_info) + self.grid.addWidget(root_info, 2, 4, 1, 1) + + if ap.is_EFI(): + label3 = QtWidgets.QLabel("EFI Partition (Mounted at /boot/efi)") + label3.setAlignment(QtCore.Qt.AlignCenter) + label3.setTextFormat(QtCore.Qt.MarkdownText) + label3 = self._set_default_margins(label3) + self.grid.addWidget(label3, 4, 1, 1, 2) + + self.efi = QtWidgets.QComboBox() + self.efi = self._set_default_margins(self.efi) + self.efi.currentTextChanged.connect(self.update_possible_efi_parts) + self.grid.addWidget(self.efi, 4, 3, 1, 1) + + self.efi_parts = QtWidgets.QComboBox() + self.efi_parts = self._set_default_margins(self.efi_parts) + self.grid.addWidget(self.efi_parts, 5, 3, 1, 1) + + efi_info = QtWidgets.QPushButton("Info on EFI Partition") + efi_info.clicked.connect(self.explain_efi) + efi_info = self._set_default_margins(efi_info) + self.grid.addWidget(efi_info, 4, 4, 1, 1) + + label4 = QtWidgets.QLabel("Home Partition (Mounted at /home) (optional)") + label4.setAlignment(QtCore.Qt.AlignCenter) + label4.setTextFormat(QtCore.Qt.MarkdownText) + label4 = self._set_default_margins(label4) + self.grid.addWidget(label4, 6, 1, 1, 2) + + self.home = QtWidgets.QComboBox() + self.home = self._set_default_margins(self.home) + self.home.currentTextChanged.connect(self.update_possible_home_parts) + self.grid.addWidget(self.home, 6, 3, 1, 1) + + self.home_parts = QtWidgets.QComboBox() + self.home_parts = self._set_default_margins(self.home_parts) + self.home_parts.currentTextChanged.connect(self.update_possible_root_parts) + self.grid.addWidget(self.home_parts, 7, 3, 1, 1) + + home_info = QtWidgets.QPushButton("Info on Home Partition") + home_info.clicked.connect(self.explain_home) + home_info = self._set_default_margins(home_info) + self.grid.addWidget(home_info, 6, 4, 1, 1) + + label6 = QtWidgets.QLabel("SWAP") + label6.setAlignment(QtCore.Qt.AlignCenter) + label6.setTextFormat(QtCore.Qt.MarkdownText) + label6 = self._set_default_margins(label6) + self.grid.addWidget(label6, 8, 1, 1, 2) + + self.swap =QtWidgets.QComboBox() + self.swap = self._set_default_margins(self.swap) + self.swap.currentTextChanged.connect(self.update_possible_swap_parts) + self.grid.addWidget(self.swap, 8, 3, 1, 1) + + self.swap_parts = QtWidgets.QComboBox() + self.swap_parts = self._set_default_margins(self.swap_parts) + self.grid.addWidget(self.swap_parts, 9, 3, 1, 1) + + swap_info = QtWidgets.QPushButton("Info on SWAP") + swap_info.clicked.connect(self.explain_swap) + swap_info = self._set_default_margins(swap_info) + self.grid.addWidget(swap_info, 8, 4, 1, 1) + + if ap.is_EFI(): + self.scan_for_usable_drives("clicked", self.root, self.home, + self.swap, self.efi) + else: + self.scan_for_usable_drives("clicked", self.root, self.home, + self.swap) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.check_man_part_settings) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 10, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 10, 3, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.partitioning) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 10, 1, 1, 1) + + button4 = QtWidgets.QPushButton("Refresh Drives") + if ap.is_EFI(): + button4.clicked.connect(self.scan_for_usable_drives) + else: + button4.clicked.connect(self.scan_for_usable_drives) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 10, 2, 1, 1) + + self.show_all() + + def update_possible_root_parts(self, root_drive_dropdown): + """Update possible root partitions based on given drive""" + if self.root.currentText() in ("", None): + print("No drive selected for root. No action necessary") + return + flag = False + drives = ap.check_disk_state() + parts = [] + for each in drives: + if each["name"] == root_drive_dropdown: + if "children" in each: + parts = each["children"] + if parts == []: + return + setting = root_drive_dropdown + self.root_parts.setCurrentText(None) + for each in range(self.root_parts.count() - 1, -1, -1): + self.root_parts.removeItem(each) + for each in parts: + if each["fstype"] in ("ext4", "ext3", "btrfs", "xfs", "f2fs"): + if each["size"] >= ap.LIMITER: + if each["name"] != self.home_parts.currentText(): + self.root_parts.addItem(each["name"], each["name"]) + if each["name"] == setting: + flag = True + self.root_parts.addItem("Root Partition", "Root Partition") + + if flag: + self.root_parts.setCurrentText(setting) + elif self.data["ROOT"] not in ("", None, "NULL"): + self.root_parts.setCurrentText(self.data["ROOT"]) + else: + self.root_parts.setCurrentText("Root Partition") + + self.show_all() + + def update_possible_home_parts(self, home_drive_dropdown): + """Update possible root partitions based on given drive""" + if self.home.currentText() in ("", None): + print("No drive selected for home. No action necessary") + return + flag = False + drives = ap.check_disk_state() + parts = [] + for each in drives: + if each["name"] == home_drive_dropdown: + if "children" in each: + for each1 in each["children"]: + parts.append(each1) + setting = home_drive_dropdown.currentText() + self.home_parts.setCurrentText(None) + for each in range(self.home_parts.count() - 1, -1, -1): + self.home_parts.removeItem(each) + for each in parts: + if each["fstype"] in ("ext4", "ext3", "btrfs", "xfs", "f2fs", + "jfs", "ext2"): + if each["size"] >= ap.gb_to_bytes(8): + if each["name"] != self.root_parts.currentText(): + self.home_parts.addItem(each["name"], each["name"]) + if each["name"] == setting: + flag = True + self.home_parts.addItem("Home Partition", "Home Partition") + + if flag: + self.home_parts.setCurrentText(setting) + elif self.data["HOME"] not in ("", None, "NULL"): + self.home_parts.setCurrentText(self.data["HOME"]) + else: + self.home_parts.setCurrentText("Home Partition") + + self.show_all() + + def update_possible_swap_parts(self, swap_drive_dropdown): + """Update possible root partitions based on given drive""" + if self.swap.currentText() in ("", None): + print("No drive selected for swap. No action necessary") + return + flag = False + drives = ap.check_disk_state() + parts = [] + for each in drives: + if each["name"] == swap_drive_dropdown: + if "children" in each: + parts = each["children"] + setting = swap_drive_dropdown + self.swap_parts.setCurrentText(None) + for each in range(self.swap_parts.count() - 1, -1, -1): + self.swap_parts.removeItem(each) + for each in parts: + if each["fstype"] in ("linux-swap", "swap"): + self.swap_parts.addItem(each["name"], each["name"]) + if each["name"] == setting: + flag = True + self.swap_parts.addItem("Swap Partition", "Swap Partition") + + if flag: + self.swap_parts.setCurrentText(setting) + elif self.data["SWAP"] not in ("", None, "NULL"): + self.swap_parts.setCurrentText(self.data["SWAP"]) + else: + self.swap_parts.setCurrentText("Swap Partition") + + self.show_all() + + def update_possible_efi_parts(self, efi_drive_dropdown): + """Update possible root partitions based on given drive""" + if self.efi.currentText() in ("", None): + print("No drive selected for EFI. No action necessary") + return + flag = False + drives = ap.check_disk_state() + parts = [] + for each in drives: + if each["name"] == efi_drive_dropdown: + if "children" in each: + parts = each["children"] + if parts == []: + return + setting = efi_drive_dropdown + self.efi_parts.setCurrentText(None) + for each in range(self.efi_parts.count() - 1, -1, -1): + self.efi_parts.removeItem(each) + for each in parts: + if each["fstype"] in ("exfat", "vfat", "fat32", "fat16", "fat12"): + if each["size"] >= ap.mb_to_bytes(125): + self.efi_parts.addItem(each["name"], each["name"]) + if each["name"] == setting: + flag = True + self.efi_parts.addItem("EFI Partition", "EFI Partition") + + if flag: + self.efi_parts.setCurrentText(setting) + elif self.data["EFI"] not in ("", None, "NULL"): + self.efi_parts.setCurrentText(self.data["EFI"]) + else: + self.efi_parts.setCurrentText("EFI Partition") + + self.show_all() + + def scan_for_usable_drives(self, widget): + """Add available drives to drive dropdowns""" + root_dropdown = self.root + home_dropdown = self.home + swap_dropdown = self.swap + if hasattr(self, "efi"): + efi_dropdown = self.efi + else: + efi_dropdown = None + # get drive names + drives_dict = ap.check_disk_state() + drives = [] + for each in drives_dict: + if each["type"] == "disk": + drives.append(each["name"]) + + # get previous settings + root_selected = root_dropdown.currentText() + home_selected = home_dropdown.currentText() + swap_selected = swap_dropdown.currentText() + try: + if efi_dropdown is not None: + efi_selected = efi_dropdown.currentText() + except AttributeError: + efi_selected = None + + # confirm previous settings + if root_selected in ("", None): + root_selected = ap.part_to_drive(self.data["ROOT"]) + if home_selected in ("", None): + home_selected = ap.part_to_drive(self.data["HOME"]) + if swap_selected in ("", None): + swap_selected = ap.part_to_drive(self.data["SWAP"]) + try: + if efi_dropdown is not None: + if efi_selected in ("", None): + efi_selected = ap.part_to_drive(self.data["EFI"]) + except AttributeError: + pass + + # wipe current dropdowns + for each in range(self.root.count() - 1, -1, -1): + self.root.removeItem(each) + for each in range(self.home.count() - 1, -1, -1): + self.home.removeItem(each) + for each in range(self.swap.count() - 1, -1, -1): + self.swap.removeItem(each) + try: + if efi_dropdown is not None: + for each in range(self.efi.count() - 1, -1, -1): + self.efi.removeItem(each) + except AttributeError: + pass + + # repopulate + for each in drives: + root_dropdown.addItem(each, each) + home_dropdown.addItem(each, each) + swap_dropdown.addItem(each, each) + try: + if efi_dropdown is not None: + efi_dropdown.addItem(each, each) + except AttributeError: + pass + + # custom attributes + home_dropdown.addItem("(none)", "(none)") + home_dropdown.addItem("Drive with Home Partition", "Drive with Home Partition") + swap_dropdown.addItem("FILE", "FILE") + swap_dropdown.addItem("Drive with Swap Partition", "Drive with Swap Partition") + root_dropdown.addItem("Drive with Root Partition", "Drive with Root Partition") + try: + if efi_dropdown is not None: + efi_dropdown.addItem("Drive with EFI Partition", "Drive with EFI Partition") + except (AttributeError, NameError): + pass + + # re-apply settings + drives = set(drives) + if root_selected in drives: + root_dropdown.setCurrentText(root_selected) + else: + root_dropdown.setCurrentText("Drive with Root Partition") + if home_selected in drives: + home_dropdown.setCurrentText(home_selected) + else: + home_dropdown.setCurrentText("Drive with Home Partition") + if swap_selected in drives: + swap_dropdown.setCurrentText(swap_selected) + else: + swap_dropdown.setCurrentText("Drive with Swap Partition") + try: + if efi_dropdown is not None: + if efi_selected in drives: + efi_dropdown.setCurrentText(efi_selected) + else: + efi_dropdown.setCurrentText("Drive with EFI Partition") + except AttributeError: + pass + self.show_all() + + def explain_root(self, button): + """Explain Root Partition requierments and limitations""" + self.clear_window() + + label = QtWidgets.QLabel("**Info on Root Partition**") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label = QtWidgets.QLabel("What is an Root Partition?") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 1, 1, 1) + + label = QtWidgets.QLabel(""" +The Root Partition is the partition where your operating system is\n +going to be installed, as well as the vast majority of apps you install\n +throughout the lifetime of the OS.\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 1, 1, 1) + + label = QtWidgets.QLabel("Root Partition Requirements") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 2, 1, 1) + + label = QtWidgets.QLabel(""" +Root Partitions are expected to be no smaller than 32 GB, and can be any\n +file system type except FAT32, FAT16, NTFS, or exFAT/vFAT.\n +\n +We suggest having a Root Partition of at least 64 GB, with a btrfs\n +file system. This will provide you with the ability to back up your OS in\n +case of a potentially risky upgrade or configuration change, while also\n +providing great file system performance.\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 2, 1, 1) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.input_part) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 1, 1, 1) + + self.show_all() + + def explain_efi(self, button): + """Explain efi Partition requierments and limitations""" + self.clear_window() + + label = QtWidgets.QLabel("**Info on EFI Partition**") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label = QtWidgets.QLabel("What is an EFI Partition?") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 1, 1, 1) + + label = QtWidgets.QLabel(""" +An EFI or UEFI Partition is a small partition which\n +contains the bootloader and related files for a system\n +using UEFI firmware.\n +\n +Since you booted your system in UEFI mode, you are\n +required to have one of these partitions.\n""") + label4.setAlignment(QtCore.Qt.AlignCenter) + label4.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 1, 1, 1) + + label = QtWidgets.QLabel("EFI Partition Requirements") + label4.setAlignment(QtCore.Qt.AlignCenter) + label4.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 2, 1, 1) + + label = QtWidgets.QLabel(""" +EFI Partitions are expected to be no smaller than 200 MB,\n +(although we suggest about 1 GB) and use a FAT32 or FAT16\n +file system. We suggest using a FAT32 file system as it\n +is the most widely supported.\n +\n +This partition must also have the \"boot\" and \"esp\" flags set.\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 2, 1, 1) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.input_part) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 1, 1, 1) + + self.show_all() + + def explain_home(self, button): + """Explain home Partition requierments and limitations""" + self.clear_window() + + label = QtWidgets.QLabel("**Info on Home Partition**") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label = QtWidgets.QLabel("What is a Home Partition?") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 1, 1, 1) + + label = QtWidgets.QLabel(""" +A Home Partition is a partition which contains all or\n +most of your user info. Having one of these is completely\n +optional. If you do opt for one, it can help keep your data\n +safe from data loss, or if the partition is on another drive,\n +it can ensure quick access times to data in your home\n +directory.\n""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 1, 1, 1) + + label = QtWidgets.QLabel("Home Partition Requirements") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 2, 1, 1) + + label = QtWidgets.QLabel(""" +Home Partitions are expected to be no smaller than 500 MB,\n +and can be any file system except FAT32, FAT16, exFAT/vFAT, or NTFS.\n +We suggest using a btrfs file system as it has features useful for\n +backing up your data, as well as is capable of optimizing itself for\n +solid-state drives.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 2, 1, 1) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.input_part) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 1, 1, 1) + + self.show_all() + + def explain_swap(self, button): + """Explain swap partitions and files""" + self.clear_window() + + label = QtWidgets.QLabel("**Info on SWAP Partition**") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label = QtWidgets.QLabel("What is an SWAP Partition?") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 1, 1, 1) + + label = QtWidgets.QLabel(""" +A SWAP Partition is a partition which your system may use to\n +extend system memory. It is useful for when your system memory is\n +extremely low.\n + +Because it is on your internal drive, it is capable of retaining\n +data between reboots and even total powerloss events. Thanks to this,\n +it also enables the usage of the Hibernate and Hybrid Suspend features.\n + +SWAP can also be used as a file. This can allow you to easily create more\n +SWAP later, should you deem it necessary.\n + +Having SWAP is mandatory on this operating system. As such, if you do not\n +create a SWAP partition, a SWAP file will be created for you.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 1, 1, 1) + + label = QtWidgets.QLabel("SWAP Partition Requirements") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 2, 2, 1, 1) + + label = QtWidgets.QLabel(""" +SWAP Partitions are expected to be no smaller than 100 MB,\n +and use linux-swap file system.\n + +If you are not sure how big you should make your SWAP partition,\n +simply leave that field empty and a SWAP file of the appropriate +size will be generated for you.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 3, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 2, 1, 1) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.input_part) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 1, 1, 1) + + self.show_all() + + def check_man_part_settings(self, button): + """Check device paths provided for manual partitioner""" + try: + efi = self.efi_parts.currentText() + except (AttributeError, NameError): + efi = "" + try: + swap = self.swap_parts.currentText() + except (AttributeError, NameError): + swap = "" + if self.root_parts.currentText() in ("", None): + label = self.set_up_partitioner_label("ERROR: / NOT SET") + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label, 1, 1, 1, 3) + return + elif (efi in ("", None)) and ap.is_EFI(): + label = self.set_up_partitioner_label( + "ERROR: System is running EFI. An EFI partition must be set.") + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label, 1, 1, 1, 3) + return + if ((swap.upper() == "FILE") or (swap == "")): + if ap.size_of_part(self.root_parts.currentText()) < ap.get_min_root_size(bytes=False): + label_string = \ + f"""/ is too small. Minimum Root Partition size is { round(ap.get_min_root_size(bytes=False)) } GB +Make a swap partition to reduce this minimum to { round(ap.get_min_root_size(swap=False, bytes=False)) } GB""" + label = self.set_up_partitioner_label(label_string) + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label, 1, 1, 1, 3) + return + else: + if ap.size_of_part(self.root_parts.currentText()) < ap.get_min_root_size(swap=False, bytes=False): + label_string = f"/ is too small. Minimum Root Partition size is { round(ap.get_min_root_size(swap=False, bytes=False)) } GB" + label = self.set_up_partitioner_label(label_string) + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label, 1, 1, 1, 3) + return + label = self.set_up_partitioner_label() + try: + self.grid.itemAt((1, 1)).widget().setParent(None) + except TypeError: + pass + self.grid.addWidget(label, 1, 1, 1, 3) + self.data["ROOT"] = self.root_parts.currentText() + + if efi in ("", " ", None): + self.data["EFI"] = "NULL" + else: + self.data["EFI"] = efi + if self.home_parts.currentText() in ("", " ", None): + self.data["HOME"] = "NULL" + else: + self.data["HOME"] = self.home_parts.currentText() + if ((swap in ("", " ", None)) or (swap.upper() == "FILE")): + self.data["SWAP"] = "FILE" + else: + self.data["SWAP"] = swap + global PART_COMPLETION + PART_COMPLETION = "COMPLETED" + self.main_menu("clicked") + + def opengparted(self, button): + """Open GParted""" + subprocess.Popen("gparted", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + self.data["AUTO_PART"] = False + self.input_part("clicked") + + def options(self, button): + """Extraneous options menu""" + self.clear_window() + + label = QtWidgets.QLabel("""**Extra Options**\n +The below options require a network connection, unless otherwise stated.\n +Please ensure you are connected before selecting any of these options.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtWidgets.QLabel("""Install third-party packages, such as NVIDIA drivers, if necessary\t\t""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + self.extras = QtWidgets.QCheckBox("Install Restricted Extras") + if self.data["EXTRAS"]: + self.extras.setChecked(True) + self.extras = self._set_default_margins(self.extras) + self.grid.addWidget(self.extras, 3, 1, 1, 2) + + label2 = QtWidgets.QLabel("""Update the system during installation""") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 4, 1, 1, 2) + + self.updates = QtWidgets.QCheckBox("Update during Installation") + if self.data["UPDATES"]: + self.updates.setChecked(True) + self.updates = self._set_default_margins(self.updates) + self.grid.addWidget(self.updates, 5, 1, 1, 2) + + label2 = QtWidgets.QLabel("""Automatically login upon boot up. Does **NOT** require internet.""") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 6, 1, 1, 2) + + self.login = QtWidgets.QCheckBox("Enable Auto-Login") + if self.data["LOGIN"]: + self.login.setChecked(True) + self.login = self._set_default_margins(self.login) + self.grid.addWidget(self.login, 7, 1, 1, 2) + + self.compat_mode = QtWidgets.QCheckBox("Enable Bootloader Compatibility Mode") + if self.data["COMPAT_MODE"]: + self.compat_mode.setChecked(True) + self.compat_mode = self._set_default_margins(self.compat_mode) + + if ap.is_EFI(): + label2 = QtWidgets.QLabel("""Enable compatibility mode to improve installation reliability\n +with some UEFI systems. Does **NOT** require internet.""") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 8, 1, 1, 2) + + self.grid.addWidget(self.compat_mode, 9, 1, 1, 2) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.options_next) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 10, 2, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.main_menu) + buton3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 10, 1, 1, 1) + + self.show_all() + + def options_next(self, button): + """Set update and extras settings""" + self.data["EXTRAS"] = self.extras.isChecked() + self.data["UPDATES"] = self.updates.isChecked() + self.data["LOGIN"] = self.login.isChecked() + self.data["COMPAT_MODE"] = self.compat_mode.isChecked() + global OPTIONS_COMPLETION + OPTIONS_COMPLETION = "COMPLETED" + self.main_menu("clicked") + + def locale(self, button): + """Language and Time Zone settings menu""" + self.clear_window() + + label = QtWidgets.QLabel("""**Choose your Language and Time Zone**""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + label2 = QtWidgets.QLabel("""Language""") + label2.setAlignment(QtCore.Qt.AlignCenter) + label2.setTextFormat(QtCore.Qt.MarkdownText) + label2 = self._set_default_margins(label2) + self.grid.addWidget(label2, 2, 2, 1, 1) + + self.lang_menu = QtWidgets.QComboBox() + + for each in self.langs: + self.lang_menu.addItem(each, self.langs[each]) + self.lang_menu.addItem("Other, User will need to set up manually.", "other") + if self.data["LANG"] != "": + self.lang_menu.setCurrentText(self.data["LANG"]) + self.lang_menu = self._set_default_margins(self.lang_menu) + self.grid.addWidget(self.lang_menu, 3, 2, 1, 1) + + label3 = QtWidgets.QLabel("""Region""") + label3.setAlignment(QtCore.Qt.AlignCenter) + label3.setTextFormat(QtCore.Qt.MarkdownText) + label3 = self._set_default_margins(label3) + self.grid.addWidget(label3, 4, 2, 1, 1) + + time_zone = self.data["TIME_ZONE"].split("/") + self.time_menu = QtWidgets.QComboBox() + zones_pre = os.listdir("/usr/share/zoneinfo") + zones_pre.sort() + zones = [] + for each in zones_pre: + if os.path.isdir(f"/usr/share/zoneinfo/{each}"): + if each not in ("right", "posix"): + self.time_menu.addItem(each, each) + if len(time_zone) > 0: + self.time_menu.setCurrentText(time_zone[0]) + self.time_menu.currentTextChanged.connect(self.update_subregion) + self.time_menu = self._set_default_margins(self.time_menu) + self.grid.addWidget(self.time_menu, 5, 2, 1, 1) + + label4 = QtWidgets.QLabel("""Sub-Region""") + label4.setAlignment(QtCore.Qt.AlignCenter) + label4.setTextFormat(QtCore.Qt.MarkdownText) + label4 = self._set_default_margins(label4) + self.grid.addWidget(label4, 6, 2, 1, 1) + + self.sub_region = QtWidgets.QComboBox() + self.sub_region = self._set_default_margins(self.sub_region) + self.grid.addWidget(self.sub_region, 7, 2, 1, 1) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.on_locale_completed) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 8, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 8, 2, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.main_menu) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 8, 1, 1, 1) + + self.update_subregion(self.time_menu) + + self.show_all() + + def update_subregion(self, widget): + """Narrow subregions to possible areas + It makes no sense to be in New York, China, when New York is in the + USA + """ + if widget is None: + return + try: + zones = sorted(os.listdir("/usr/share/zoneinfo/" + widget)) + except TypeError: + zones = sorted(os.listdir("/usr/share/zoneinfo/" + widget.currentText())) + for each in range(self.sub_region.count() - 1, -1, -1): + self.sub_region.removeItem(each) + zones.sort() + for each7 in zones: + self.sub_region.addItem(each7, each7) + time_zone = self.data["TIME_ZONE"].split("/") + if len(time_zone) > 1: + self.sub_region.setCurrentText(time_zone[1]) + + def on_locale_completed(self, button): + """Set default language and time zone if user did not set them""" + if self.lang_menu.currentText() is not None: + self.data["LANG"] = self.lang_menu.currentText() + else: + self.data["LANG"] = "en" + + if ((self.time_menu.currentText() is not None) and ( + self.sub_region.currentText() is not None)): + self.data["TIME_ZONE"] = self.time_menu.currentText() + self.data["TIME_ZONE"] = self.data["TIME_ZONE"] + "/" + self.data["TIME_ZONE"] = self.data["TIME_ZONE"] + self.sub_region.currentText() + else: + self.data["TIME_ZONE"] = "America/New_York" + + global LOCALE_COMPLETION + LOCALE_COMPLETION = "COMPLETED" + self.main_menu("clicked") + + def keyboard(self, button): + """Keyboard Settings Dialog""" + self.clear_window() + + label = QtWidgets.QLabel("""**Choose your Keyboard layout**""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 4) + + model_label = QtWidgets.QLabel("""Model: """) + model_label.setAlignment(QtCore.Qt.AlignCenter) + model_label.setTextFormat(QtCore.Qt.MarkdownText) + model_label = self._set_default_margins(model_label) + self.grid.addWidget(model_label, 2, 1, 1, 1) + + self.model_menu = QtWidgets.QComboBox() + with open("/etc/edamame/keyboards.json", "r") as file: + keyboards = json.load(file) + layout_list = keyboards["layouts"] + model = keyboards["models"] + for each8 in model: + self.model_menu.addItem(each8, model[each8]) + if self.data["MODEL"] != "": + index = self.model_menu.findText(self.data["MODEL"], QtCore.Qt.MatchFixedString) + if index >= 0: + self.model_menu.setCurrentIndex(index) + else: + index = self.model_menu.findText("pc105", QtCore.Qt.MatchFixedString) + if index >= 0: + self.model_menu.setCurrentText("pc105") + self.model_menu = self._set_default_margins(self.model_menu) + self.grid.addWidget(self.model_menu, 2, 2, 1, 3) + + layout_label = QtWidgets.QLabel("""Layout: """) + layout_label.setAlignment(QtCore.Qt.AlignCenter) + layout_label.setTextFormat(QtCore.Qt.MarkdownText) + layout_label = self._set_default_margins(layout_label) + self.grid.addWidget(layout_label, 3, 1, 1, 1) + + self.layout_menu = QtWidgets.QComboBox() + for each8 in layout_list: + self.layout_menu.addItem(each8, layout_list[each8]) + if self.data["LAYOUT"] != "": + index = self.layout_menu.findText(self.data["LAYOUT"], QtCore.Qt.MatchFixedString) + if index >= 0: + self.layout_menu.setCurrentIndex(index) + self.layout_menu.currentTextChanged.connect(self.varient_narrower) + self.layout_menu = self._set_default_margins(self.layout_menu) + self.grid.addWidget(self.layout_menu, 3, 2, 1, 3) + + varient_label = QtWidgets.QLabel("""Variant: """) + varient_label.setAlignment(QtCore.Qt.AlignCenter) + varient_label.setTextFormat(QtCore.Qt.MarkdownText) + varient_label = self._set_default_margins(varient_label) + self.grid.addWidget(varient_label, 4, 1, 1, 1) + + self.varient_menu = QtWidgets.QComboBox() + self.varients = keyboards["varints"] + for each8 in self.varients: + for each9 in self.varients[each8]: + self.varient_menu.addItem(each9, self.varients[each8][each9]) + if self.data["VARIENT"] != "": + index = self.varient_menu.findText(self.data["VARIENT"], QtCore.Qt.MatchFixedString) + if index >= 0: + self.varient_menu.setCurrentIndex(index) + self.varient_menu = self._set_default_margins(self.varient_menu) + self.grid.addWidget(self.varient_menu, 4, 2, 1, 3) + + button1 = QtWidgets.QPushButton("Okay -->") + button1.clicked.connect(self.on_keyboard_completed) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 3, 1, 1) + + button3 = QtWidgets.QPushButton("<-- Back") + button3.clicked.connect(self.main_menu) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 6, 1, 1, 1) + + self.show_all() + + def varient_narrower(self, widget): + """Narrow down possible keyboard varients""" + term = self.layout_menu.currentData() + for each in range(self.varient_menu.count() - 1, -1, -1): + self.varient_menu.removeItem(each) + + for each9 in self.varients[term]: + self.varient_menu.addItem(each9, self.varients[term][each9]) + if self.data["VARIENT"] != "": + self.varient_menu.setCurrentText(self.data["VARIENT"]) + self.varient_menu = self._set_default_margins(self.varient_menu) + self.show_all() + + def on_keyboard_completed(self, button): + """Set default keyboard layout if user did not specify one""" + if self.model_menu.currentText() is not None: + self.data["MODEL"] = self.model_menu.currentText() + else: + self.data["MODEL"] = "Generic 105-key PC (intl.)" + if self.layout_menu.currentText() is not None: + self.data["LAYOUT"] = self.layout_menu.currentText() + elif "kernel keymap" in self.data["MODEL"]: + self.data["LAYOUT"] = "" + else: + self.data["LAYOUT"] = "English (US)" + if self.varient_menu.currentText() is not None: + self.data["VARIENT"] = self.varient_menu.currentText() + elif "kernel keymap" in self.data["MODEL"]: + self.data["VARIENT"] = "" + else: + self.data["VARIENT"] = "euro" + global KEYBOARD_COMPLETION + KEYBOARD_COMPLETION = "COMPLETED" + + self.main_menu("clicked") + + def done(self, button): + """Check to see if each segment has been completed + If it hasn't, print a warning, else + Print out the value of stuffs and exit + """ + global KEYBOARD_COMPLETION + global LOCALE_COMPLETION + global OPTIONS_COMPLETION + global PART_COMPLETION + global USER_COMPLETION + if "COMPLETED" not in (KEYBOARD_COMPLETION, LOCALE_COMPLETION, + OPTIONS_COMPLETION, PART_COMPLETION, + USER_COMPLETION): + self.label.setText("""Feel free to complete any of the below segments in any order.\n +However, all segments must be completed.\n +\n +**One or more segments have not been completed**\n +Please complete these segments, then try again.\n +Or, exit installation.\n""") + else: + self.complete() + + def complete(self): + """Set settings var""" + self.close() + if isinstance(self.data, str): + if os.path.isfile(self.data): + return + else: + self.data = 1 + elif isinstance(self.data, dict): + if "" in self.data.values(): + self.data = 1 + else: + self.data["EXTRAS"] = bool(self.data["EXTRAS"]) + self.data["UPDATES"] = bool(self.data["UPDATES"]) + self.data["LOGIN"] = bool(self.data["LOGIN"]) + else: + self.data = 1 + + def exit(self, button): + """Exit dialog""" + self.clear_window() + + label = QtWidgets.QLabel("""\n**Are you sure you want to exit?** + +Exiting now will cause all your settings to be lost.""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + yes = QtWidgets.QPushButton("Exit") + yes.clicked.connect(self._exit) + yes = self._set_default_margins(yes) + self.grid.addWidget(yes, 2, 1, 1, 1) + + no = QtWidgets.QPushButton("Return") + no.clicked.connect(self.main_menu) + no = self._set_default_margins(no) + self.grid.addWidget(no, 2, 2, 1, 1) + + self.show_all() + + def _exit(self, button): + """Exit""" + self.close + self.data = 1 + return 1 + + def clear_window(self): + """Clear Window""" + for i in range(self.grid.count() - 1, -1, -1): + self.grid.itemAt(i).widget().setParent(None) + self.resize(0, 0) + + +def show_main(boot_time=False): + """Show Main UI""" + make_kbd_names() + app = QtWidgets.QApplication([sys.argv[0]]) + window = Main(app.primaryScreen().size()) + if boot_time: + window = QCommon.set_window_undecorated(window) + # window.set_resizable(False) + window.show() + app.exec() + window.complete() + return window.data + + +def make_kbd_names(): + """Get Keyboard Names faster""" + if os.path.isfile("/etc/edamame/keyboards.json"): + # Keyboards file already made. Nothing to do. + return + with open("/usr/share/console-setup/KeyboardNames.pl") as file: + data = file.read() + data = data.split("\n") + for each in range(len(data) - 1, -1, -1): + data[each] = data[each].replace(" =>", ":") + data[each] = data[each].replace(");", "},") + data[each] = data[each].replace("'", '"') + data[each] = data[each].replace("\\", "") + if "%variants" in data[each]: + data[each] = '"varints": {' + elif "%layouts" in data[each]: + data[each] = '"layouts": {' + elif "%models" in data[each]: + data[each] = '"models": {' + elif (("package" in data[each]) or ("1;" in data[each])): + del data[each] + elif "#!/" in data[each]: + data[each] = "{" + elif "(" in data[each]: + if data[each][-1] == "(": + data[each] = data[each].replace("(", "{") + if "}" in data[each]: + data[each - 1] = data[each - 1][:-1] + while True: + if data[-1] == "": + del data[-1] + else: + data[-1] = "}}" + break + data = "\n".join(data) + os.chdir("/etc/edamame") + with open("keyboards.json", "w+") as file: + file.write(data) + + +if __name__ == '__main__': + print(show_main()) diff --git a/usr/share/edamame/UI/Qt_UI/progress.py b/usr/share/edamame/UI/Qt_UI/progress.py new file mode 100755 index 0000000..618a899 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/progress.py @@ -0,0 +1,162 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# progress.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Progress Window GUI""" +import sys +import signal +import json +from os import remove +from qtpy import QtCore, QtWidgets, QtGui +try: + import UI.Qt_UI.qt_common as QCommon +except ImportError: + import qt_common as QCommon + +window = None + + +class Main(QtWidgets.QWidget): + """Progress UI Window""" + signal = QtCore.Signal() + + def __init__(self, app, distro="Linux"): + """Progress UI main set-up""" + super().__init__() + self.setWindowTitle("Edamame") + self.install = False + self.setWindowIcon(QtGui.QIcon.fromTheme("system-installer")) + self.grid = QtWidgets.QGridLayout() + self.setLayout(self.grid) + self.distro = distro + + self.label = QtWidgets.QLabel() + self.label.setText(f""" +# Installing {self.distro} to your internal hard drive. +This may take some time. If you have an error, please send +the log file (located at /tmp/edamame.log) to: contact@draugeros.org""") + self.label.setTextFormat(QtCore.Qt.MarkdownText) + self.label.setAlignment(QtCore.Qt.AlignCenter) + # self.label = self._set_default_margins(self.label) + self.grid.addWidget(self.label, 1, 1, 1, 1) + + self.progress = QtWidgets.QProgressBar() + self.progress.setTextVisible(True) + self.progress.setMaximum(100) + self.progress.setMinimum(0) + self.progress.setValue(0) + # self.progress = self._set_default_margins(self.progress) + self.grid.addWidget(self.progress, 3, 1, 1, 1) + + self.file_contents = QtWidgets.QTextEdit() + self.file_contents.setReadOnly(True) + self.file_contents.setCursorWidth(0) + self.file_contents.setCurrentFont(QtGui.QFont("Ubuntu Mono")) + # self.text = self._set_default_margins(self.text) + self.grid.addWidget(self.file_contents, 5, 1, 1, 1) + + # self.set_position(Gtk.WindowPosition.CENTER) + + self.signal.connect(self.pulse) + self.startTimer(33) + + # def _set_default_margins(self, widget): + # """Set default margin size""" + # widget.set_margin_start(10) + # widget.set_margin_end(10) + # widget.set_margin_top(10) + # widget.set_margin_bottom(10) + # return widget + + def read_file(self): + """Read Progress log""" + text = "" + try: + with open("/tmp/edamame.log", "r") as read_file: + text = read_file.read() + if len(text.split("\n")) > 10: + text = text.split("\n") + text = text[-9:] + for each in enumerate(text): + if len(text[each[0]]) > 90: + text[each[0]] = text[each[0]][:90] + elif len(text[each[0]]) < 90: + multiplyer = 90 - len(text[each[0]]) + text[each[0]] = text[each[0]] + (" " * multiplyer) + text = "\n".join(text) + self.file_contents.setText(text) + except FileNotFoundError: + self.file_contents.setText("") + return True + + + def pulse(self): + """Update progress indicator and log output in GUI""" + fraction = "" + try: + with open("/tmp/edamame-progress.log", "r") as prog_file: + fraction = int(prog_file.read()) + except FileNotFoundError: + fraction = 0 + try: + self.progress.setValue(fraction) + except ValueError: + self.progress.setValue(0) + if fraction == 100: + remove("/tmp/edamame-progress.log") + remove("/mnt/tmp/edamame-progress.log") + self.close() + self.source_id = None + return False + + self.read_file() + return True + + def timerEvent(self, *args, **kwargs): + self.signal.emit() + + +def show_progress(): + """Show Progress UI""" + try: + with open("/etc/edamame/settings.json", "r") as file: + distro = json.load(file)["distro"] + except (FileNotFoundError, KeyError): + distro = "Linux" + signal.signal(signal.SIGTERM, handle_sig_term) + app = QtWidgets.QApplication([sys.argv[0]]) + global window + window = Main(distro) + window = QCommon.set_window_undecorated(window) + window.show() + app.exec() + + +def handle_sig_term(signum, frame): + global window + window.close() + print("progress.py received SIGTERM! Installation is likely complete...") + sys.exit() + + +if __name__ == '__main__': + show_progress() diff --git a/usr/share/edamame/UI/Qt_UI/qt_common.py b/usr/share/edamame/UI/Qt_UI/qt_common.py new file mode 100755 index 0000000..e178b50 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/qt_common.py @@ -0,0 +1,35 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# qt_common.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Common Qt Functions""" +from qtpy import QtCore, QtWidgets +def set_window_nonresizeable(qt_window): + """Set a Qt Window resizable or not""" + qt_window.setFixedSize(qt_window.size()) + return qt_window + + +def set_window_undecorated(qt_window: QtWidgets.QWidget) -> QtWidgets.QWidget: + """Set a Qt Window decorated or not""" + qt_window.setWindowFlag(QtCore.Qt.FramelessWindowHint) + return qt_window diff --git a/usr/share/edamame/UI/Qt_UI/report.py b/usr/share/edamame/UI/Qt_UI/report.py new file mode 100755 index 0000000..76da1fe --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/report.py @@ -0,0 +1,681 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# report.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Installation Reporting UI""" +from subprocess import check_output, CalledProcessError +from os import getenv, path +from shutil import copyfile +import time +import json +import gnupg +from qtpy import QtGui, QtWidgets, QtCore +import urllib3 +import common + +try: + gpg = gnupg.GPG(gnupghome="/home/live/.gnupg") +except ValueError: + try: + gpg = gnupg.GPG(gnupghome=getenv("HOME") + "/.gnupg") + except ValueError: + gpg = gnupg.GPG(gnupghome="/home/drauger-user/.gnupg") + + +class Main(QtWidgets.QWidget): + """Main UI and tools for reporting installations""" + def __init__(self): + super().__init__() + self.setWindowTitle("Edamame") + self.grid = QtWidgets.QGridLayout() + self.setLayout(self.grid) + self.setWindowIcon(QtGui.QIcon.fromTheme("system-installer")) + self.opt_setting = False + self.cpu_setting = False + self.gpu_setting = False + self.ram_setting = False + self.disk_setting = False + self.log_setting = False + self.custom_setting = False + + self.default_message = """ +Write a custom message to our developers and contributors! +If you would like a response, please leave: +* Your name (if this is not left we will use your username) +* A way to get in contact with you through one or more of: +* Email +* Telegram +* Discord +* Mastodon +* Twitter +""" + + def clear_window(self): + """Clear Window""" + for i in range(self.grid.count() - 1, -1, -1): + self.grid.itemAt(i).widget().setParent(None) + + def _set_default_margins(self, widget): + """Set default margin size""" + try: + margin = QtCore.QMargins(10, 10, 10, 10) + widget.setContentsMargins(margin) + except AttributeError: + common.eprint("WARNING: QtCore.QMargins() does not exist. Spacing in UI might be a bit wonky.") + return widget + + def cpu_toggle(self, widget): + """Toggle sending CPU info""" + if widget: + self.cpu_setting = True + else: + self.cpu_setting = False + + def cpu_explaination(self, widget): + """Explain why we need CPU info""" + self.clear_window() + + label = QtWidgets.QLabel() + label.setText(""" + **Why to report CPU info**\t""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtWidgets.QLabel(""" + Knowing what CPUs most of our users use helps us to optimize Drauger OS. + It allows us to know if we have more or less CPU cores to take + advantage of, or if we need to focus on becoming even lighter weight. + + It also gives us valuable information such as CPU vulnerabilities that are + common among our users. Knowing this helps us decide if we need to keep \t + certain security measures enabled, or if we can disable some for better + performance with little to no risk to security. + \t""") + label1.setAlignment(QtCore.Qt.AlignCenter) + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + def gpu_toggle(self, widget): + """Toggle sending GPU/PCIe info""" + if widget: + self.gpu_setting = True + else: + self.gpu_setting = False + + def gpu_explaination(self, widget): + """Explain why we need GPU/PCIe info""" + self.clear_window() + + label = QtWidgets.QLabel(""" + **Why to report GPU / PCIe info**\t""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtWidgets.QLabel(""" + Knowing what GPUs most of our users use helps us to optimize Drauger OS. + It can help us know if we need to put more work into Nvidia and/or AMD + support. + + It can also help us know if we need to lighten the grpahical load on our + users GPUs based on the age and/or power of these GPUs. Or, if we can + afford a little eye candy. + + As for PCIe info, this can help us ensure support for common Wi-Fi cards is + built into the kernel, and drivers that aren't needed aren't included. This + can save space on your system, as well as speed up updates and increase \t + hardware support. + \t""") + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + def ram_toggle(self, widget): + """Toggle sending RAM info""" + if widget: + self.ram_setting = True + else: + self.ram_setting = False + + def ram_explaination(self, widget): + """Explain why we need RAM info""" + self.clear_window() + + label = QtWidgets.QLabel(""" + **Why to report RAM/SWAP info**\t""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtGui.QLabel(""" + Knowing how much RAM our users systems have helps us determine if\t + Drauger OS is using too much RAM. + + Knowing how much SWAP our users have helps us understand if users + understand the neccessity of SWAP, and also what kind of device + they may be using. That way, we can optimize to run better on laptops\t + or desktops as needed. + + Right now, all this gives us is the AMOUNT of RAM and SWAP that you + have. In the future, we do plan to get RAM type (DDR2, DDR3, etc.), + and RAM speed. This will help us better understand the age of your + system, how responsive it is, and how well lower end systems can + handle eye candy. + \t""") + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + def disk_toggle(self, widget): + """Toggle Sending Disk Info""" + if widget: + self.disk_setting = True + else: + self.disk_setting = False + + def disk_explaination(self, widget): + """Explain why we need Disk Info""" + self.clear_window() + + label = QtWidgets.QLabel(""" + **Why to report Disk and Partitioning info**\t""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtWidgets.QLabel(""" + Understanding our users partitioning and disk setups helps us know + if our users are dual-booting Drauger OS. This, in turn with the added + benefit of knowing immediatly whether our users are using the automatic\t + or manual partitioning systems tells us where to focus our effort. + + This can mean we are more likely to catch bugs or add new features + in one area or another. + \t""") + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + def log_toggle(self, widget): + """Toggle sending the log""" + if widget: + self.log_setting = True + else: + self.log_setting = False + + def log_explaination(self, widget): + """Explaination for why we need the installation log""" + self.clear_window() + + label = QtWidgets.QLabel(""" + **Why to send the Installation Log**\t""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + label1 = QtWidgets.QLabel(""" + As soon as the installation of Drauger OS completed, the installation log + was copied to your internal drive. + + Normally, if you have a bug that might be related to the installer, we + would ask you to send us that log. By sending it now, we don't have to do + that. Instead, we can give you a command to run in your terminal. The + output of that command will tell us which installation log is yours + so we can immediatly access it and track down bugs. + + If you send nothing else, please send this. + \t""") + label1.setTextFormat(QtCore.Qt.MarkdownText) + label1 = self._set_default_margins(label1) + self.grid.addWidget(label1, 2, 1, 1, 2) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + def exit(self, button): + """Exit""" + self.close() + return 1 + + def message_handler(self, widget): + """Generate Message then show it""" + self.generate_message() + self.preview_message("clicked") + + def send_report(self, widget): + """Send installation Report""" + self.clear_window() + + label = QtWidgets.QLabel("\n\n\t\tSending Report. Please Wait . . .\t\t\n\n") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 1) + + + try: + copyfile(self.path, "/mnt/var/log/installation_report.txt") + except: + pass + + try: + # Get keys + http = urllib3.PoolManager() + with open("/etc/edamame/settings.json", + "r") as config: + URL = json.load(config)["report"] + data = http.request("GET", URL["recv_keys"]).data + key = data.decode() + # Import keys + result = gpg.import_keys(key) + # Encrypt file using newly imported keys + with open(self.path, "rb") as signing: + signed_data = gpg.encrypt_file(signing, result.fingerprints, + always_trust=True) + with open(self.path, "w") as signed: + signed.write(str(signed_data)) + # Upload newly encrypted file + check_output(["rsync", self.path, + URL["upload"]]) + + self.clear_window() + + label = QtWidgets.QLabel("\n\n\t\tReport Sent Successfully!\t\t\n\n") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + button1 = QtWidgets.QPushButton("Okay!") + button1.clicked.connect(self.main_menu) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 2, 2, 1, 1) + + except: + self.clear_window() + + label = QtWidgets.QLabel(""" + +\t\tReport Failed to Send! +\t\tPlease make sure you have a working internet connection.\t\t + +""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 2) + + button1 = QtWidgets.QPushButton("Okay") + button1.clicked.connect(self.main_menu) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 2, 2, 1, 1) + + def preview_message(self, widget): + """Preview Installation Report""" + self.clear_window() + try: + with open(self.path, "r") as mail: + text = mail.read() + except FileNotFoundError: + # this is handled during file generation, so this should never be used + path = getenv("HOME") + "/installation_report.txt" + with open(path) as mail: + text = mail.read() + + self.custom_message = QtWidgets.QTextEdit() + self.custom_message.setPlainText(json.dumps(json.loads(text), indent=2)) + self.custom_message.setReadOnly(True) + self.custom_message.setAcceptRichText(False) + self.custom_message.setCursorWidth(0) + self.custom_message = self._set_default_margins(self.custom_message) + self.grid.addWidget(self.custom_message, 1, 1, 4, 4) + + button1 = QtWidgets.QPushButton("Send Report") + button1.clicked.connect(self.send_report) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 5, 4, 1, 1) + + button2 = QtWidgets.QPushButton("Abort") + button2.clicked.connect(self.main_menu) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 5, 1, 1, 1) + + def generate_message(self): + """write installation report to disk""" + report_code = time.time() + output = {} + self.path = f"/tmp/installation_report-{ report_code }.dosir" + output['Installation Report Code'] = report_code + try: + ver = check_output(["edamame", "-v"]).decode().split("\n") + ver = [each for each in ver if each != ""][0] + output['edamame Version'] = ver + except (FileNotFoundError, CalledProcessError): + output['edamame Version'] = "VERSION UNKNOWN. LIKELY TESTING OR MAJOR ERROR." + output['OS'] = get_info(["lsb_release", "-ds"])[0] + if self.cpu.isChecked(): + output['CPU INFO'] = cpu_info() + else: + output['CPU INFO'] = 'OPT OUT' + if self.gpu.isChecked(): + output['PCIe / GPU INFO'] = get_info(["lspci", "-nnq"]) + else: + output['PCIe / GPU INFO'] = 'OPT OUT' + if self.ram.isChecked(): + output['RAM / SWAP INFO'] = ram_info() + else: + output['RAM / SWAP INFO'] = 'OPT OUT' + if self.disk.isChecked(): + output['DISK SETUP'] = disk_info() + else: + output['DISK SETUP'] = 'OPT OUT' + if self.log.isChecked(): + try: + with open("/tmp/edamame.log", "r") as log: + output['INSTALLATION LOG'] = log.read().split("\n") + except FileNotFoundError: + output['INSTALLATION LOG'] = 'Log does not exist.' + else: + output['INSTALLATION LOG'] = 'OPT OUT' + if self.custom.isChecked(): + custom = self.text_buffer.toPlainText() + # Make sure that custom messages are not the default message. + # if they are, just put none so we don't see a bunch of + # trash custom messages + if custom == self.default_message: + output['CUSTOM MESSAGE'] = "NONE" + else: + output['CUSTOM MESSAGE'] = custom.split("\n") + else: + output['CUSTOM MESSAGE'] = "NONE" + try: + with open(self.path, "w+") as message: + json.dump(output, message, indent=1) + except PermissionError: + self.path = getenv("HOME") + "/installation_report.txt" + with open(self.path, "w+") as message: + json.dump(output, message, indent=1) + + def message_accept(self, widget): + """Accept Message Input in GUI""" + if self.custom.isChecked(): + self.custom_setting = True + self.text_buffer = QtWidgets.QTextEdit() + self.text_buffer.setText(self.default_message) + self.text_buffer.setReadOnly(False) + self.text_buffer.setTabChangesFocus(False) + self.text_buffer = self._set_default_margins(self.text_buffer) + self.grid.addWidget(self.text_buffer, 8, 1, 4, 8) + + else: + self.grid.remove(self.text_buffer) + self.custom_setting = False + del self.text_buffer + + def main(self, widget): + """UI to show if user opts in""" + self.clear_window() + + label = QtWidgets.QLabel(""" +Send installation and hardware report\t""") + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + self.cpu = QtWidgets.QCheckBox("CPU Info") + self.cpu.setChecked(self.cpu_setting) + self.cpu.toggled.connect(self.cpu_toggle) + self.cpu = self._set_default_margins(self.cpu) + self.grid.addWidget(self.cpu, 2, 2, 1, 2) + + cpu_explain = QtWidgets.QPushButton() + cpu_explain.setIcon(QtGui.QIcon.fromTheme("help-about")) + # cpu_explain.setIconSize(QtCore.QSize(3, 3)) + cpu_explain.clicked.connect(self.cpu_explaination) + cpu_explain = self._set_default_margins(cpu_explain) + self.grid.addWidget(cpu_explain, 2, 4, 1, 1) + + self.gpu = QtWidgets.QCheckBox("GPU/PCIe Info") + self.gpu.setChecked(self.gpu_setting) + self.gpu.toggled.connect(self.gpu_toggle) + self.gpu = self._set_default_margins(self.gpu) + self.grid.addWidget(self.gpu, 3, 2, 1, 2) + + gpu_explain = QtWidgets.QPushButton() + gpu_explain.setIcon(QtGui.QIcon.fromTheme("help-about")) + # gpu_explain.setIconSize(QtCore.QSize(3, 3)) + gpu_explain.clicked.connect(self.gpu_explaination) + gpu_explain = self._set_default_margins(gpu_explain) + self.grid.addWidget(gpu_explain, 3, 4, 1, 1) + + self.ram = QtWidgets.QCheckBox("RAM/SWAP Info") + self.ram.setChecked(self.ram_setting) + self.ram.toggled.connect(self.ram_toggle) + self.ram = self._set_default_margins(self.ram) + self.grid.addWidget(self.ram, 4, 2, 1, 2) + + ram_explain = QtWidgets.QPushButton() + ram_explain.setIcon(QtGui.QIcon.fromTheme("help-about")) + # ram_explain.setIconSize(QtCore.QSize(3, 3)) + ram_explain.clicked.connect(self.ram_explaination) + ram_explain = self._set_default_margins(ram_explain) + self.grid.addWidget(ram_explain, 4, 4, 1, 1) + + self.disk = QtWidgets.QCheckBox("Disk/Partitioning Info") + self.disk.setChecked(self.disk_setting) + self.disk.toggled.connect(self.disk_toggle) + self.disk = self._set_default_margins(self.disk) + self.grid.addWidget(self.disk, 5, 2, 1, 2) + + disk_explain = QtWidgets.QPushButton() + disk_explain.setIcon(QtGui.QIcon.fromTheme("help-about")) + # disk_explain.setIconSize(QtCore.QSize(3, 3)) + disk_explain.clicked.connect(self.disk_explaination) + disk_explain = self._set_default_margins(disk_explain) + self.grid.addWidget(disk_explain, 5, 4, 1, 1) + + self.log = QtWidgets.QCheckBox("Installation Log") + self.log.setChecked(self.log_setting) + self.log.toggled.connect(self.log_toggle) + self.log = self._set_default_margins(self.log) + self.grid.addWidget(self.log, 6, 2, 1, 2) + + log_explain = QtWidgets.QPushButton() + log_explain.setIcon(QtGui.QIcon.fromTheme("help-about")) + # log_explain.setIconSize(QtCore.QSize(3, 3)) + log_explain.clicked.connect(self.log_explaination) + log_explain = self._set_default_margins(log_explain) + self.grid.addWidget(log_explain, 6, 4, 1, 1) + + self.custom = QtWidgets.QCheckBox("Custom Message") + self.custom.setChecked(self.custom_setting) + self.custom.toggled.connect(self.message_accept) + self.custom = self._set_default_margins(self.custom) + self.grid.addWidget(self.custom, 7, 2, 1, 2) + + if hasattr(self, 'text_buffer'): + self.grid.addWidget(self.text_buffer, 8, 1, 4, 8) + + button2 = QtWidgets.QPushButton("Preview Message") + button2.clicked.connect(self.message_handler) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 12, 5, 1, 4) + + button1 = QtWidgets.QPushButton("<-- Back") + button1.clicked.connect(self.main_menu) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 12, 1, 1, 1) + + +def cpu_info(): + """get CPU info""" + info = check_output("lscpu").decode().split("\n") + # We need to create a more intelligent parser for this data as positions can + # change depending on the system that is being used. + sentenal = 0 + output = {} + backup_speed = None + count = 0 + while sentenal < 7: + for each in info: + if sentenal == 0: + if "Model name:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = add[1] + sentenal += 1 + elif sentenal == 1: + if "Thread(s) per core:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = int(add[1]) + sentenal += 1 + elif sentenal == 2: + if "Core(s) per socket:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = int(add[1]) + sentenal += 1 + elif sentenal == 3: + if "CPU max MHz:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = float(add[1]) + sentenal += 1 + count = 0 + elif count == len(info): + count = 0 + sentenal += 1 + output["CPU max MHz"] = "Unknown" + else: + count += 1 + elif sentenal == 4: + if "L2 cache:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = add[1] + sentenal += 1 + count = 0 + elif count == len(info): + count = 0 + sentenal += 1 + output["L2 cache"] = "Unknown" + else: + count += 1 + elif sentenal == 5: + if "L3 cache:" in each: + add = [each1 for each1 in each.split(" ") if each1 != ""] + if add[0][-1] == ":": + add[0] = add[0][:-1] + output[add[0]] = add[1] + sentenal += 1 + count = 0 + elif count == len(info): + count = 0 + sentenal += 1 + output["L3 cache"] = "Unknown" + else: + count += 1 + elif sentenal == 6: + if "CPU MHz:" in each: + backup_speed = each + sentenal += 1 + count = 0 + elif count == len(info): + count = 0 + sentenal += 1 + backup_speed = "Unknown" + else: + count += 1 + speed_dir = "/sys/devices/system/cpu/cpu0/cpufreq/" + if path.exists(speed_dir): + if path.exists(speed_dir + "bios_limit"): + with open(speed_dir + "bios_limit", "r") as file: + speed = int(file.read()) / 1000 + else: + with open(speed_dir + "scaling_max_freq", "r") as file: + speed = int(file.read()) / 1000 + else: + speed = backup_speed + # speed = float(speed) + output["CPU base MHz"] = speed + return output + + +def ram_info(): + """Get RAM info""" + ram_capacity = check_output(["lsmem", "--summary=only"]).decode().split("\n") + for each in enumerate(ram_capacity): + ram_capacity[each[0]] = [each1 for each1 in each[1].split(" ") if each1 != ""] + for each in range(len(ram_capacity) - 1, -1, -1): + if ram_capacity[each] == []: + del ram_capacity[each] + swap_capacity = check_output(["swapon", "--show"]).decode().split("\n") + return {"RAM": dict(ram_capacity), "SWAP": swap_capacity} + + +def disk_info(): + """Get disk info""" + info = json.loads(check_output(["lsblk", "--json", "--output", + "name,size,type,mountpoint"]).decode()) + for each in range(len(info["blockdevices"]) - 1, -1, -1): + if "loop" == info["blockdevices"][each]["type"]: + del info["blockdevices"][each] + return info["blockdevices"] + + +def get_info(cmd): + """Get arbitrary info from commands""" + info = check_output(cmd).decode() + if info[-1] == "\n": + info = list(info) + del info[-1] + info = "".join(info) + info = info.split("\n") + return info diff --git a/usr/share/edamame/UI/Qt_UI/success.py b/usr/share/edamame/UI/Qt_UI/success.py new file mode 100755 index 0000000..210b583 --- /dev/null +++ b/usr/share/edamame/UI/Qt_UI/success.py @@ -0,0 +1,395 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# success.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Success Reporting UI""" +from shutil import rmtree, copytree, move, copyfile +import subprocess +import os +import sys +import json +import pathlib +import xmltodict +import tarfile as tar +import urllib.parse as urlp +from qtpy import QtGui, QtWidgets, QtCore +try: + from UI.Qt_UI import report +except ImportError: + try: + from Qt_UI import report + except ImportError: + import report +import common + + +class Main(report.Main): + """Success UI Class""" + def __init__(self, settings): + """Initialize data""" + super().__init__() + self.settings = settings + + try: + with open("/etc/edamame/settings.json", "r") as file: + self.distro = json.load(file)["distro"] + except (FileNotFoundError, KeyError): + self.distro = "Linux" + self.main_menu("clicked") + + def _set_default_margins(self, widget): + """Set default margin size""" + try: + margin = QtCore.QMargins(10, 10, 10, 10) + widget.setContentsMargins(margin) + except AttributeError: + common.eprint("WARNING: QtCore.QMargins() does not exist. Spacing in UI might be a bit wonky.") + return widget + + def main_menu(self, widget): + """Main Success Window""" + self.clear_window() + + text = """ +## %s has been successfully installed on your computer!\n +""" % (self.distro) + if "OEM" not in self.settings.values(): + text = text + """Please consider sending an installation report to our team,\n +using the "Send Installation Report" button below.\t\n\n""" + label = QtWidgets.QLabel(text) + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 4) + + if "OEM" in self.settings.values(): + button1 = QtWidgets.QPushButton("Power Off System") + button1.clicked.connect(__poweroff__) + else: + button1 = QtWidgets.QPushButton("Restart System") + button1.clicked.connect(__reboot__) + button1 = self._set_default_margins(button1) + if "OEM" in self.settings.values(): + self.grid.addWidget(button1, 6, 4, 1, 1) + else: + self.grid.addWidget(button1, 6, 2, 1, 1) + + button2 = QtWidgets.QPushButton("Exit") + button2.clicked.connect(self.exit) + button2 = self._set_default_margins(button2) + if "OEM" in self.settings.values(): + self.grid.addWidget(button2, 6, 1, 1, 2) + else: + self.grid.addWidget(button2, 6, 1, 1, 1) + + if "OEM" not in self.settings.values(): + button3 = QtWidgets.QPushButton("Advanced") + button3.clicked.connect(self.onadvclicked) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 6, 3, 1, 1) + + button4 = QtWidgets.QPushButton("Send Installation Report") + button4.clicked.connect(self.main) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 6, 4, 1, 1) + + def onadvclicked(self, button): + """Advanced Settings and Functions""" + self.clear_window() + + label = QtWidgets.QLabel(""" +The below options are meant exclusivly for advanced users.\n\n +**User discretion is advised.**\n\n + """) + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + button1 = QtWidgets.QPushButton("Dump Settings to File") + button1.clicked.connect(self.dump_settings_dialog) + button1 = self._set_default_margins(button1) + self.grid.addWidget(button1, 6, 3, 1, 1) + + button2 = QtWidgets.QPushButton("Delete Installation") + button2.clicked.connect(self.ondeletewarn) + button2 = self._set_default_margins(button2) + self.grid.addWidget(button2, 6, 1, 1, 1) + + button4 = QtWidgets.QPushButton("Exit") + button4.clicked.connect(self.exit) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 7, 3, 1, 1) + + button5 = QtWidgets.QPushButton("<-- Back") + button5.clicked.connect(self.main_menu) + button5 = self._set_default_margins(button5) + self.grid.addWidget(button5, 7, 1, 1, 1) + + def ondeletewarn(self, button): + """Warning about Deleting the installation""" + self.clear_window() + + label = QtWidgets.QLabel(""" +\tAre you sure you wish to delete the new installation?\t\n +\tNo data will be recoverable.\t +""") + label.setAlignment(QtCore.Qt.AlignCenter) + label.setTextFormat(QtCore.Qt.MarkdownText) + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 3) + + button5 = QtWidgets.QPushButton("DELETE") + button5.clicked.connect(self.delete_install) + button5 = self._set_default_margins(button5) + self.grid.addWidget(button5, 2, 2, 1, 1) + + button4 = QtWidgets.QPushButton("Exit") + button4.clicked.connect(self.exit) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 2, 3, 1, 1) + + button6 = QtWidgets.QPushButton("<-- Back") + button6.clicked.connect(self.onadvclicked) + button6 = self._set_default_margins(button6) + self.grid.addWidget(button6, 2, 1, 1, 1) + + def delete_install(self, button): + """Delete Installation from Drive + This code is dangerous. Be wary + """ + delete = os.listdir("/mnt") + for each in delete: + try: + os.remove("/mnt/" + each) + except IsADirectoryError: + rmtree("/mnt/" + each) + self.exit("clicked") + + def exit(self, button): + """Exit""" + self.close() + return 0 + + def clear_window(self): + """Clear Window""" + for i in range(self.grid.count() - 1, -1, -1): + self.grid.itemAt(i).widget().setParent(None) + + + def dump_settings_dialog(self, button): + """Get what to dump and what not to dump""" + self.clear_window() + + label = QtWidgets.QLabel(""" +\tSelect what you would like included in your Quick Install file.\t +""") + label = self._set_default_margins(label) + self.grid.addWidget(label, 1, 1, 1, 4) + + self.settings_toggle = QtWidgets.QCheckBox("Installation Settings") + self.settings_toggle.setChecked(True) + self.settings_toggle = self._set_default_margins(self.settings_toggle) + self.grid.addWidget(self.settings_toggle, 2, 1, 1, 4) + + self.network_toggle = QtWidgets.QCheckBox("Network Settings") + self.network_toggle.setChecked(True) + self.network_toggle = self._set_default_margins(self.network_toggle) + self.grid.addWidget(self.network_toggle, 3, 1, 1, 4) + + self.wall_toggle = QtWidgets.QCheckBox("Wallpaper") + self.wall_toggle.setChecked(False) + self.wall_toggle = self._set_default_margins(self.wall_toggle) + self.grid.addWidget(self.wall_toggle, 4, 1, 1, 4) + + button4 = QtWidgets.QPushButton("Exit") + button4.clicked.connect(self.exit) + button4 = self._set_default_margins(button4) + self.grid.addWidget(button4, 6, 4, 1, 1) + + button5 = QtWidgets.QPushButton("<-- Back") + button5.clicked.connect(self.onadvclicked) + button5 = self._set_default_margins(button5) + self.grid.addWidget(button5, 6, 1, 1, 1) + + button3 = QtWidgets.QPushButton("DUMP") + button3.clicked.connect(self.dump_settings_file_dialog) + button3 = self._set_default_margins(button3) + self.grid.addWidget(button3, 5, 1, 1, 4) + + def dump_settings_file_dialog(self, button): + """Dump Settings File Dialog""" + filter = None + try: + if (self.network_toggle.isChecked() or self.wall_toggle.isChecked()): + filter = ["application/x-tar"] + else: + filter = ["application/json"] + except NameError: + filter = ["application/json"] + + + dialog = QtWidgets.QFileDialog(self) + dialog.setDirectory(os.getenv("HOME")) + dialog.setMimeTypeFilters(filter) + dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + dialog.exec() + response = dialog.selectedFiles()[0] + + if response != "": + if self.network_toggle.isChecked(): + if response[-3:] != ".xz": + response = f"{response}.xz" + adv_dump_settings(self.settings, response, + copy_net=self.network_toggle.isChecked(), + copy_set=self.settings_toggle.isChecked(), + copy_wall=self.wall_toggle.isChecked()) + else: + dump_settings(self.settings, response) + + # dialog.close() + self.onadvclicked("clicked") + + +def dump_settings(settings, path): + """Dump Settings to File""" + with open(path, "w+") as dump_file: + json.dump(settings, dump_file, indent=2) + + +def adv_dump_settings(settings, dump_path, copy_net=True, copy_set=True, + copy_wall=False): + """Compress Install settings and + Network settings to tar bar for later use + """ + # Make our directory layout + try: + os.mkdir("/tmp/working_dir") + except FileExistsError: + pass + try: + os.mkdir("/tmp/working_dir/settings") + except FileExistsError: + pass + try: + os.mkdir("/tmp/working_dir/assets") + except FileExistsError: + pass + # dump our installation settings and grab our network settings too + if copy_set: + dump_settings(settings, + "/tmp/working_dir/settings/installation-settings.json") + if copy_net: + # /etc/NetworkManager/system-connections has perms 755, so we can see the files in it + # but those files have permissions 600, and we don't own them, so we can't read them. + # Must temporarily change them to 644 so we can read them + net_connections = "/etc/NetworkManager/system-connections" + if len(os.listdir(net_connections)) > 0: + subprocess.check_call("echo 'toor' | sudo -S chmod 644 " + net_connections + "/*", + shell=True) + copytree("/etc/NetworkManager/system-connections", + "/tmp/working_dir/settings/network-settings") + subprocess.check_call("echo 'toor' | sudo -S chmod 600 " + net_connections + "/*", + shell=True) + if copy_wall: + # Grab wallpaper + home = os.getenv("HOME") + wall_path = [] + monitors = [] + with open(home + "/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml", "r") as fb: + xml = xmltodict.parse(fb.read()) + for each in xml["channel"]["property"][0]["property"][0]["property"]: + monitors.append(each["@name"]) + for each1 in each["property"][0]["property"]: + try: + if each1["@name"] == "last-image": + wall_path.append(each1["@value"]) + except (AttributeError, TypeError): + pass + wall_path_unique = common.unique(wall_path) + # Copy designated files into "assets" + if len(wall_path_unique) == 1: + # we only have one wallpaper, so copy that into assets/master + # then, dump the list of screens to assets/screens.list + # when read in, this will make it so that the wallpaper is used on + # all the same screens as before + try: + os.mkdir("/tmp/working_dir/assets/master") + except FileExistsError: + pass + with open("/tmp/working_dir/assets/screens.list", w) as screens_list: + for each in monitors: + screens_list.write(each + "\n") + file_type = wall_path[0].split("/")[-1].split(".")[-1] + copyfile(wall_path[0], + "/tmp/working_dir/assets/master/wallpaper." + file_type) + else: + # We have different wallpapers on different screens + # This has a trade-off of taking up more disk space in the tar ball + # But, it should work out okay + for each in range(len(wall_path)): + try: + os.mkdir("/tmp/working_dir/assets/" + monitors[each]) + except FileExistsError: + pass + file_type = wall_path[each].split("/")[-1].split(".")[-1] + copyfile(wall_path[each], + "/tmp/working_dir/assets/" + monitors[each] + "/wallpaper." + file_type) + # make our tar ball, with XZ compression + os.chdir("/tmp/working_dir") + tar_file = tar.open(name=dump_path.split("/")[-1], mode="w:xz") + tar_file.add(name="settings") + tar_file.add(name="assets") + tar_file.close() + # copy it to the desired location + move(dump_path.split("/")[-1], dump_path) + os.chmod(dump_path, 0o740) + os.chown(dump_path, 1000, 1000) + # clean up + rmtree("/tmp/working_dir") + + +def show_success(settings): + """Show Success UI""" + app = QtWidgets.QApplication([sys.argv[0]]) + window = Main(settings) + # window.set_resizable(False) + # window.set_position(Gtk.WindowPosition.CENTER) + window.show() + app.exec() + + +def __reboot__(button): + """Reboot the system""" + subprocess.Popen(["/sbin/reboot"]) + sys.exit(0) + + +def __poweroff__(button): + """Shutdown the system""" + subprocess.Popen(["/sbin/poweroff"]) + sys.exit(0) + + +if __name__ == '__main__': + settings = json.loads(sys.argv[1]) + show_success(settings) diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index 681abc7..a397620 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -45,7 +45,7 @@ -def copy_log_to_disk(): +def copy_log_to_disk() -> None: """Copy Installation Log to installation location""" try: shutil.copyfile("/tmp/edamame.log", From 46b7f67743faa5a3a474642435ac7c29feb6b813 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 12 Nov 2024 18:20:46 -0500 Subject: [PATCH 07/27] Multiple bug fixes for Qt UI --- usr/share/edamame/UI/Qt_UI/main.py | 141 ++++++------------------ usr/share/edamame/UI/Qt_UI/qt_common.py | 2 +- 2 files changed, 35 insertions(+), 108 deletions(-) diff --git a/usr/share/edamame/UI/Qt_UI/main.py b/usr/share/edamame/UI/Qt_UI/main.py index 4305d26..4ed82eb 100755 --- a/usr/share/edamame/UI/Qt_UI/main.py +++ b/usr/share/edamame/UI/Qt_UI/main.py @@ -136,19 +136,6 @@ def __init__(self, screen_size): # Open initial window self.reset("clicked") - def show_all(self): - """Final work to prepare the window to be shown""" - self.resize(self.sizeHint()) - size = (self.size().width(), self.size().height()) - print(f"Window size: {size}") - size_offset = (round(size[0] / 2), round(size[1]/ 2)) - position = (round(self.screen_size[0] / 2), round(self.screen_size[1] / 2)) - self.move(position[0] - size_offset[0], position[1] - size_offset[1]) - # size = (self.size().width(), self.size().height()) - # self.setFixedSize(size[0], size[1]) - - - def _set_default_margins(self, widget): """Set default margin size""" try: @@ -192,8 +179,6 @@ def quick_install_warning(self, button): button2 = self._set_default_margins(button2) self.grid.addWidget(button2, 2, 1, 1, 1) - self.show_all() - def reset(self, button): """Main Splash Window""" global DEFAULT @@ -225,8 +210,6 @@ def reset(self, button): button4 = self._set_default_margins(button4) self.grid.addWidget(button4, 2, 3, 1, 1) - self.show_all() - def oem_startup(self, widget): """Start up OEM installation""" self.clear_window() @@ -266,8 +249,6 @@ def oem_startup(self, widget): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 2, 1, 1, 1) - self.show_all() - def oem_run(self, widget): """Start up OEM installation""" self.data = "/etc/edamame/oem-install.json" @@ -372,8 +353,6 @@ def main_menu(self, button): button2 = self._set_default_margins(button2) self.grid.addWidget(button2, 8, 1, 1, 1) - self.show_all() - def user(self, button): """User setup Window""" self.clear_window() @@ -450,8 +429,6 @@ def user(self, button): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 8, 1, 1, 1) - self.show_all() - def generate_hostname(self, widget): """Generate a hostname that follows these rules: - 16 characters long @@ -494,7 +471,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif len(self.data["PASSWORD"]) < SETTINGS["min_password_length"]: @@ -504,7 +481,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif has_special_character(self.data["USERNAME"]): @@ -514,7 +491,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif " " in self.data["USERNAME"]: @@ -524,7 +501,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif len(self.data["USERNAME"]) < 1: @@ -534,7 +511,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif has_special_character(self.data["COMPUTER_NAME"]): @@ -544,7 +521,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif " " in self.data["COMPUTER_NAME"]: @@ -554,7 +531,7 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) elif len(self.data["COMPUTER_NAME"]) < 1: @@ -564,14 +541,13 @@ def onnext2clicked(self, button): label5 = self._set_default_margins(label5) try: self.grid.itemAtPosition(7, 1).widget().setParent(None) - except TypeError: + except (TypeError, AttributeError): pass self.grid.addWidget(label5, 7, 1, 1, 3) else: global USER_COMPLETION USER_COMPLETION = "COMPLETED" self.main_menu("clicked") - self.show_all() def partitioning(self, button): """Partitioning Main Window""" @@ -611,8 +587,6 @@ def partitioning(self, button): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 5, 1, 1, 1) - self.show_all() - def auto_partition(self, button): """Auto Partitioning Settings Window""" self.clear_window() @@ -699,8 +673,6 @@ def auto_partition(self, button): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 6, 1, 1, 1) - self.show_all() - def define_array(self, widget, error=None): """Define btrfs RAID Array settings""" self.clear_window() @@ -807,8 +779,6 @@ def define_array(self, widget, error=None): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 9, 2, 1, 1) - self.show_all() - # I know there is a better way to assign disks than this, or at least a # Better way to define these functions. But this was the simplest way I can # Think to do it right now. In the future, I want to add the ability to @@ -896,8 +866,6 @@ def confirm_raid_array(self, widget): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 9, 2, 1, 1) - self.show_all() - def cement_raid_array(self, widget): """Set alternate settings so that the RAID Array can be handled internally""" self.data["HOME"] = self.data["raid_array"]["disks"]["1"] @@ -950,8 +918,6 @@ def make_space(self, widget, drive=None): devices.setCurrentText(drive) self.make_space_parts(devices) - self.show_all() - def make_space_parts(self, widget): """Set partitions to show for make_space()""" self.parts.remove_all() @@ -995,8 +961,6 @@ def confirm_remove_part(self, widget): button2 = self._set_default_margins(button2) self.grid.addWidget(button2, 6, 1, 1, 1) - self.show_all() - def remove_part(self, widget): """Interface for removing partitions""" part = self.parts.currentText() @@ -1017,13 +981,11 @@ def auto_home_setup(self, widget): self.data["HOME"] = "MAKE" else: try: - self.grid.itemAt((4, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(4, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.data["HOME"] = "" - self.show_all() - def auto_home_setup2(self, widget): """Provide options for prexisting home partitions""" if widget: @@ -1089,8 +1051,6 @@ def auto_home_setup2(self, widget): else: self.data["HOME"] = "MAKE" - self.show_all() - def select_home_part(self, widget): """Set pre-existing home partition, based on user input""" if os.path.exists(widget): @@ -1098,8 +1058,6 @@ def select_home_part(self, widget): def _set_root_part(self, widget): """set root drive""" - print("HERE!") - print(widget) self.data["ROOT"] = widget # self.auto_partition("clicked") @@ -1116,8 +1074,8 @@ def confirm_auto_part(self, button): self.data["SWAP"] = "FILE" if self.disks.currentText() is None: try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass label = QtWidgets.QLabel(""" Which drive would you like to install to?\n @@ -1135,8 +1093,6 @@ def confirm_auto_part(self, button): PART_COMPLETION = "COMPLETED" self.main_menu("clicked") - self.show_all() - def set_up_partitioner_label(self, additional_message=""): """prepare top label for display on manual partitioner @@ -1146,7 +1102,7 @@ def set_up_partitioner_label(self, additional_message=""): Return value: label -- the top label ready for additional formatting and display """ - label = QtWidgets.QLabel + label = QtWidgets.QLabel() input_string = """ What are the mount points for the partitions you wish to be used?\n @@ -1253,12 +1209,7 @@ def input_part(self, button): swap_info = self._set_default_margins(swap_info) self.grid.addWidget(swap_info, 8, 4, 1, 1) - if ap.is_EFI(): - self.scan_for_usable_drives("clicked", self.root, self.home, - self.swap, self.efi) - else: - self.scan_for_usable_drives("clicked", self.root, self.home, - self.swap) + self.scan_for_usable_drives("clicked") button1 = QtWidgets.QPushButton("Okay -->") button1.clicked.connect(self.check_man_part_settings) @@ -1283,8 +1234,6 @@ def input_part(self, button): button4 = self._set_default_margins(button4) self.grid.addWidget(button4, 10, 2, 1, 1) - self.show_all() - def update_possible_root_parts(self, root_drive_dropdown): """Update possible root partitions based on given drive""" if self.root.currentText() in ("", None): @@ -1319,8 +1268,6 @@ def update_possible_root_parts(self, root_drive_dropdown): else: self.root_parts.setCurrentText("Root Partition") - self.show_all() - def update_possible_home_parts(self, home_drive_dropdown): """Update possible root partitions based on given drive""" if self.home.currentText() in ("", None): @@ -1334,7 +1281,7 @@ def update_possible_home_parts(self, home_drive_dropdown): if "children" in each: for each1 in each["children"]: parts.append(each1) - setting = home_drive_dropdown.currentText() + setting = home_drive_dropdown self.home_parts.setCurrentText(None) for each in range(self.home_parts.count() - 1, -1, -1): self.home_parts.removeItem(each) @@ -1355,8 +1302,6 @@ def update_possible_home_parts(self, home_drive_dropdown): else: self.home_parts.setCurrentText("Home Partition") - self.show_all() - def update_possible_swap_parts(self, swap_drive_dropdown): """Update possible root partitions based on given drive""" if self.swap.currentText() in ("", None): @@ -1387,8 +1332,6 @@ def update_possible_swap_parts(self, swap_drive_dropdown): else: self.swap_parts.setCurrentText("Swap Partition") - self.show_all() - def update_possible_efi_parts(self, efi_drive_dropdown): """Update possible root partitions based on given drive""" if self.efi.currentText() in ("", None): @@ -1422,8 +1365,6 @@ def update_possible_efi_parts(self, efi_drive_dropdown): else: self.efi_parts.setCurrentText("EFI Partition") - self.show_all() - def scan_for_usable_drives(self, widget): """Add available drives to drive dropdowns""" root_dropdown = self.root @@ -1523,7 +1464,6 @@ def scan_for_usable_drives(self, widget): efi_dropdown.setCurrentText("Drive with EFI Partition") except AttributeError: pass - self.show_all() def explain_root(self, button): """Explain Root Partition requierments and limitations""" @@ -1579,8 +1519,6 @@ def explain_root(self, button): button1 = self._set_default_margins(button1) self.grid.addWidget(button1, 6, 1, 1, 1) - self.show_all() - def explain_efi(self, button): """Explain efi Partition requierments and limitations""" self.clear_window() @@ -1637,8 +1575,6 @@ def explain_efi(self, button): button1 = self._set_default_margins(button1) self.grid.addWidget(button1, 6, 1, 1, 1) - self.show_all() - def explain_home(self, button): """Explain home Partition requierments and limitations""" self.clear_window() @@ -1694,8 +1630,6 @@ def explain_home(self, button): button1 = self._set_default_margins(button1) self.grid.addWidget(button1, 6, 1, 1, 1) - self.show_all() - def explain_swap(self, button): """Explain swap partitions and files""" self.clear_window() @@ -1759,8 +1693,6 @@ def explain_swap(self, button): button1 = self._set_default_margins(button1) self.grid.addWidget(button1, 6, 1, 1, 1) - self.show_all() - def check_man_part_settings(self, button): """Check device paths provided for manual partitioner""" try: @@ -1774,8 +1706,8 @@ def check_man_part_settings(self, button): if self.root_parts.currentText() in ("", None): label = self.set_up_partitioner_label("ERROR: / NOT SET") try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.grid.addWidget(label, 1, 1, 1, 3) return @@ -1783,8 +1715,8 @@ def check_man_part_settings(self, button): label = self.set_up_partitioner_label( "ERROR: System is running EFI. An EFI partition must be set.") try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.grid.addWidget(label, 1, 1, 1, 3) return @@ -1795,8 +1727,8 @@ def check_man_part_settings(self, button): Make a swap partition to reduce this minimum to { round(ap.get_min_root_size(swap=False, bytes=False)) } GB""" label = self.set_up_partitioner_label(label_string) try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.grid.addWidget(label, 1, 1, 1, 3) return @@ -1805,15 +1737,15 @@ def check_man_part_settings(self, button): label_string = f"/ is too small. Minimum Root Partition size is { round(ap.get_min_root_size(swap=False, bytes=False)) } GB" label = self.set_up_partitioner_label(label_string) try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.grid.addWidget(label, 1, 1, 1, 3) return label = self.set_up_partitioner_label() try: - self.grid.itemAt((1, 1)).widget().setParent(None) - except TypeError: + self.grid.itemAtPosition(1, 1).widget().setParent(None) + except (TypeError, AttributeError): pass self.grid.addWidget(label, 1, 1, 1, 3) self.data["ROOT"] = self.root_parts.currentText() @@ -1822,7 +1754,7 @@ def check_man_part_settings(self, button): self.data["EFI"] = "NULL" else: self.data["EFI"] = efi - if self.home_parts.currentText() in ("", " ", None): + if self.home_parts.currentText() in ("", " ", None, "Home Partition"): self.data["HOME"] = "NULL" else: self.data["HOME"] = self.home_parts.currentText() @@ -1913,8 +1845,6 @@ def options(self, button): buton3 = self._set_default_margins(button3) self.grid.addWidget(button3, 10, 1, 1, 1) - self.show_all() - def options_next(self, button): """Set update and extras settings""" self.data["EXTRAS"] = self.extras.isChecked() @@ -1999,8 +1929,6 @@ def locale(self, button): self.update_subregion(self.time_menu) - self.show_all() - def update_subregion(self, widget): """Narrow subregions to possible areas It makes no sense to be in New York, China, when New York is in the @@ -2124,8 +2052,6 @@ def keyboard(self, button): button3 = self._set_default_margins(button3) self.grid.addWidget(button3, 6, 1, 1, 1) - self.show_all() - def varient_narrower(self, widget): """Narrow down possible keyboard varients""" term = self.layout_menu.currentData() @@ -2137,7 +2063,6 @@ def varient_narrower(self, widget): if self.data["VARIENT"] != "": self.varient_menu.setCurrentText(self.data["VARIENT"]) self.varient_menu = self._set_default_margins(self.varient_menu) - self.show_all() def on_keyboard_completed(self, button): """Set default keyboard layout if user did not specify one""" @@ -2224,19 +2149,20 @@ def exit(self, button): no = self._set_default_margins(no) self.grid.addWidget(no, 2, 2, 1, 1) - self.show_all() - def _exit(self, button): """Exit""" - self.close + self.close() self.data = 1 return 1 def clear_window(self): """Clear Window""" - for i in range(self.grid.count() - 1, -1, -1): - self.grid.itemAt(i).widget().setParent(None) - self.resize(0, 0) + for i in range(self.grid.rowCount() - 1, -1, -1): + for i2 in range(self.grid.columnCount() - 1, -1, -1): + try: + self.grid.itemAtPosition(i, i2).widget().setParent(None) + except (TypeError, AttributeError): + pass def show_main(boot_time=False): @@ -2246,6 +2172,7 @@ def show_main(boot_time=False): window = Main(app.primaryScreen().size()) if boot_time: window = QCommon.set_window_undecorated(window) + window = QCommon.set_window_nonresizeable(window) # window.set_resizable(False) window.show() app.exec() diff --git a/usr/share/edamame/UI/Qt_UI/qt_common.py b/usr/share/edamame/UI/Qt_UI/qt_common.py index e178b50..c24ca02 100755 --- a/usr/share/edamame/UI/Qt_UI/qt_common.py +++ b/usr/share/edamame/UI/Qt_UI/qt_common.py @@ -25,7 +25,7 @@ from qtpy import QtCore, QtWidgets def set_window_nonresizeable(qt_window): """Set a Qt Window resizable or not""" - qt_window.setFixedSize(qt_window.size()) + qt_window.grid.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) return qt_window From 30ee9b7b2f8933eebd9777a2736053d22ea767c2 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 12 Nov 2024 18:54:45 -0500 Subject: [PATCH 08/27] Minor UX bug fix --- usr/share/edamame/UI/Qt_UI/confirm.py | 1 + usr/share/edamame/UI/Qt_UI/error.py | 7 +++++-- usr/share/edamame/UI/Qt_UI/progress.py | 1 + usr/share/edamame/UI/Qt_UI/qt_common.py | 2 ++ usr/share/edamame/UI/Qt_UI/success.py | 5 +++++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/usr/share/edamame/UI/Qt_UI/confirm.py b/usr/share/edamame/UI/Qt_UI/confirm.py index 7c06399..358fecf 100755 --- a/usr/share/edamame/UI/Qt_UI/confirm.py +++ b/usr/share/edamame/UI/Qt_UI/confirm.py @@ -351,6 +351,7 @@ def show_confirm(settings, boot_time=False): window = Main(settings) if boot_time: window = QCommon.set_window_undecorated(window) + window = QCommon.set_window_nonresizeable(window) window.show() app.exec() return window.install diff --git a/usr/share/edamame/UI/Qt_UI/error.py b/usr/share/edamame/UI/Qt_UI/error.py index 233466e..fe82ced 100755 --- a/usr/share/edamame/UI/Qt_UI/error.py +++ b/usr/share/edamame/UI/Qt_UI/error.py @@ -31,6 +31,10 @@ from Qt_UI import report except ImportError: import report +try: + import UI.Qt_UI.qt_common as QCommon +except ImportError: + import qt_common as QCommon class Main(report.Main): @@ -82,8 +86,7 @@ def show_error(display: str, report_setting: bool = True): `report` controls whether or not the user can send an installation report""" app = QtWidgets.QApplication([argv[0]]) window = Main(display, report_setting) - # window.set_position(Gtk.WindowPosition.CENTER) - # window.clicked.connect("delete-event", Main.exit) + window = QCommon.set_window_nonresizeable(window) window.show() app.exec() diff --git a/usr/share/edamame/UI/Qt_UI/progress.py b/usr/share/edamame/UI/Qt_UI/progress.py index 618a899..0de1dc1 100755 --- a/usr/share/edamame/UI/Qt_UI/progress.py +++ b/usr/share/edamame/UI/Qt_UI/progress.py @@ -147,6 +147,7 @@ def show_progress(): global window window = Main(distro) window = QCommon.set_window_undecorated(window) + window = QCommon.set_window_nonresizeable(window) window.show() app.exec() diff --git a/usr/share/edamame/UI/Qt_UI/qt_common.py b/usr/share/edamame/UI/Qt_UI/qt_common.py index c24ca02..a7dd397 100755 --- a/usr/share/edamame/UI/Qt_UI/qt_common.py +++ b/usr/share/edamame/UI/Qt_UI/qt_common.py @@ -23,6 +23,8 @@ # """Common Qt Functions""" from qtpy import QtCore, QtWidgets + + def set_window_nonresizeable(qt_window): """Set a Qt Window resizable or not""" qt_window.grid.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) diff --git a/usr/share/edamame/UI/Qt_UI/success.py b/usr/share/edamame/UI/Qt_UI/success.py index 210b583..b6f650f 100755 --- a/usr/share/edamame/UI/Qt_UI/success.py +++ b/usr/share/edamame/UI/Qt_UI/success.py @@ -40,6 +40,10 @@ except ImportError: import report import common +try: + import UI.Qt_UI.qt_common as QCommon +except ImportError: + import qt_common as QCommon class Main(report.Main): @@ -372,6 +376,7 @@ def show_success(settings): """Show Success UI""" app = QtWidgets.QApplication([sys.argv[0]]) window = Main(settings) + window = QCommon.set_window_nonresizeable(window) # window.set_resizable(False) # window.set_position(Gtk.WindowPosition.CENTER) window.show() From 2d3590fdcc431f7b7d54c92475a509070cfbe66e Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Tue, 12 Nov 2024 18:57:01 -0500 Subject: [PATCH 09/27] Minor version bump to reflect new Qt UI --- DEBIAN/control | 2 +- usr/bin/edamame.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEBIAN/control b/DEBIAN/control index 725f749..2619369 100644 --- a/DEBIAN/control +++ b/DEBIAN/control @@ -1,5 +1,5 @@ Package: edamame -Version: 2.9.1 +Version: 2.9.3 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index f512790..3cf0dbe 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -35,7 +35,7 @@ using namespace std; -str VERSION = "2.9.2"; +str VERSION = "2.9.3"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; From 8731299cfb3e88abda184f1ce48cd9463895aacb Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 19:09:46 -0500 Subject: [PATCH 10/27] Minor changes Mostly marking files as execuatable --- usr/lib/python3/dist-packages/de_control/__init__.py | 0 usr/lib/python3/dist-packages/de_control/immersion.py | 0 usr/lib/python3/dist-packages/de_control/modify.py | 0 usr/share/edamame/common.py | 0 usr/share/edamame/modules/install_extras.py | 0 usr/share/edamame/tests/test_auto_partitioner.py | 2 +- usr/share/edamame/tests/test_common.py | 2 +- 7 files changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 usr/lib/python3/dist-packages/de_control/__init__.py mode change 100644 => 100755 usr/lib/python3/dist-packages/de_control/immersion.py mode change 100644 => 100755 usr/lib/python3/dist-packages/de_control/modify.py mode change 100644 => 100755 usr/share/edamame/common.py mode change 100644 => 100755 usr/share/edamame/modules/install_extras.py diff --git a/usr/lib/python3/dist-packages/de_control/__init__.py b/usr/lib/python3/dist-packages/de_control/__init__.py old mode 100644 new mode 100755 diff --git a/usr/lib/python3/dist-packages/de_control/immersion.py b/usr/lib/python3/dist-packages/de_control/immersion.py old mode 100644 new mode 100755 diff --git a/usr/lib/python3/dist-packages/de_control/modify.py b/usr/lib/python3/dist-packages/de_control/modify.py old mode 100644 new mode 100755 diff --git a/usr/share/edamame/common.py b/usr/share/edamame/common.py old mode 100644 new mode 100755 diff --git a/usr/share/edamame/modules/install_extras.py b/usr/share/edamame/modules/install_extras.py old mode 100644 new mode 100755 diff --git a/usr/share/edamame/tests/test_auto_partitioner.py b/usr/share/edamame/tests/test_auto_partitioner.py index 28cf4a7..1e98e8e 100755 --- a/usr/share/edamame/tests/test_auto_partitioner.py +++ b/usr/share/edamame/tests/test_auto_partitioner.py @@ -1,4 +1,4 @@ -#!shebang +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # test_auto_partitioner.py diff --git a/usr/share/edamame/tests/test_common.py b/usr/share/edamame/tests/test_common.py index 515f5ba..3236aa4 100755 --- a/usr/share/edamame/tests/test_common.py +++ b/usr/share/edamame/tests/test_common.py @@ -1,4 +1,4 @@ -#!shebang +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # test_common.py From f8c89417d881031bd1707a590c0079106a216c48 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 19:24:18 -0500 Subject: [PATCH 11/27] Update build system to split edamame into 3 packages Edamame now consists of 3 packages: - edamame-common - The common core of all 3 packages - edamame-gtk - The GTK+ UI - edamame-qt - The Qt UI Builds will now be placed in an auto-generated folder named `build`. --- .gitignore | 1 + DEBIAN/control | 11 -- DEBIAN/edamame-common.control | 13 +++ DEBIAN/edamame-common.install | 17 +++ DEBIAN/edamame-gtk.control | 13 +++ DEBIAN/edamame-gtk.install | 1 + DEBIAN/edamame-qt.control | 13 +++ DEBIAN/edamame-qt.install | 1 + build-common.sh | 144 +++++++++++++++++++++++++ build-gtk.sh | 71 ++++++++++++ build-qt.sh | 71 ++++++++++++ build.sh | 197 ++-------------------------------- 12 files changed, 352 insertions(+), 201 deletions(-) delete mode 100644 DEBIAN/control create mode 100644 DEBIAN/edamame-common.control create mode 100644 DEBIAN/edamame-common.install create mode 100644 DEBIAN/edamame-gtk.control create mode 100644 DEBIAN/edamame-gtk.install create mode 100644 DEBIAN/edamame-qt.control create mode 100644 DEBIAN/edamame-qt.install create mode 100755 build-common.sh create mode 100755 build-gtk.sh create mode 100755 build-qt.sh diff --git a/.gitignore b/.gitignore index c98494f..1d87f94 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ changelog.gz changelog *.o *.so +build/ diff --git a/DEBIAN/control b/DEBIAN/control deleted file mode 100644 index 2619369..0000000 --- a/DEBIAN/control +++ /dev/null @@ -1,11 +0,0 @@ -Package: edamame -Version: 2.9.3 -Maintainer: Thomas Castleman -Homepage: https://github.com/drauger-os-development/edamame -Section: admin -Architecture: amd64 -Priority: important -Replaces: system-installer (<=2.7.4) -Depends: arch-install-scripts, , bash, gir1.2-gtk-3.0 (>=3.24.12-1ubuntu1), coreutils (>=8.28-1ubuntu1), apt (>=1.6.11), squashfs-tools (>=1:4.3-6ubuntu0.18.04.1), grub2-common (>=2.02-2ubuntu8.13), initramfs-tools (>=0.130ubuntu3.8), systemd (>=237-3ubuntu10.24), locales (>=2.27-3ubuntu1), procps (>=2:3.3.12-3ubuntu1.1), grep (>=3.1-2), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-apt (>=2.0.0), python3-urllib3 (>=1.26.5-1~exp1), python3-gnupg (>=0.4.5), python3-xmltodict (>=0.11.0), python3-dnspython, tzdata, laptop-detect (>=0.16) -Description: System Installation utility - System Installation utility for Debian-based Operating Systems. diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control new file mode 100644 index 0000000..34990bf --- /dev/null +++ b/DEBIAN/edamame-common.control @@ -0,0 +1,13 @@ +Package: edamame-common +Version: 2.9.4 +Maintainer: Thomas Castleman +Homepage: https://github.com/drauger-os-development/edamame +Section: admin +Architecture: amd64 +Priority: important +Replaces: edamame (<=2.9.3) +Depends: arch-install-scripts, , bash, coreutils (>=8.28-1ubuntu1), apt (>=1.6.11), squashfs-tools (>=1:4.3-6ubuntu0.18.04.1), grub2-common (>=2.02-2ubuntu8.13), initramfs-tools (>=0.130ubuntu3.8), systemd (>=237-3ubuntu10.24), locales (>=2.27-3ubuntu1), procps (>=2:3.3.12-3ubuntu1.1), grep (>=3.1-2), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-apt (>=2.0.0), python3-urllib3 (>=1.26.5-1~exp1), python3-dnspython, tzdata, laptop-detect (>=0.16), edamame-gtk (>=2.9.4) | edamame-qt (>=2.9.4) +Description: Edamame System Installation utility - Common Files + Edamame System Installation utility for Debian-based Operating Systems. + . + This is the common core files for Edamame. diff --git a/DEBIAN/edamame-common.install b/DEBIAN/edamame-common.install new file mode 100644 index 0000000..5b0285a --- /dev/null +++ b/DEBIAN/edamame-common.install @@ -0,0 +1,17 @@ +etc/* +usr/bin/* +usr/lib/* +usr/share/applications/* +usr/share/doc/* +usr/share/polkit-1/* +usr/share/edamame/auto_partitoner.py +usr/share/edamame/check_internet.py +usr/share/edamame/check_kernel_versions.py +usr/share/edamame/chroot.py +usr/share/edamame/common.py +usr/share/edamame/engine.py +usr/share/edamame/installer.py +usr/share/edamame/progress.py +usr/share/edamame/success.py +usr/share/edamame/UI/__init__.py +usr/share/edamame/modules/* diff --git a/DEBIAN/edamame-gtk.control b/DEBIAN/edamame-gtk.control new file mode 100644 index 0000000..e41ddca --- /dev/null +++ b/DEBIAN/edamame-gtk.control @@ -0,0 +1,13 @@ +Package: edamame-gtk +Version: 2.9.4 +Maintainer: Thomas Castleman +Homepage: https://github.com/drauger-os-development/edamame +Section: admin +Architecture: amd64 +Priority: important +Breaks: edamame (<=2.9.3) +Depends: , gir1.2-gtk-3.0 (>=3.24.12-1ubuntu1), locales (>=2.27-3ubuntu1), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-urllib3 (>=1.26.5-1~exp1), python3-gnupg (>=0.4.5), python3-xmltodict (>=0.11.0), python3-dnspython, tzdata, edamame-common (>=2.9.4) +Description: Edamame System Installation utility - GTK+ Front End + Edamame System Installation utility for Debian-based Operating Systems. + . + This is the GTK 3.0-based front end. diff --git a/DEBIAN/edamame-gtk.install b/DEBIAN/edamame-gtk.install new file mode 100644 index 0000000..4d3187f --- /dev/null +++ b/DEBIAN/edamame-gtk.install @@ -0,0 +1 @@ +usr/share/edamame/UI/GTK_UI/* diff --git a/DEBIAN/edamame-qt.control b/DEBIAN/edamame-qt.control new file mode 100644 index 0000000..7a1fa46 --- /dev/null +++ b/DEBIAN/edamame-qt.control @@ -0,0 +1,13 @@ +Package: edamame-qt +Version: 2.9.4 +Maintainer: Thomas Castleman +Homepage: https://github.com/drauger-os-development/edamame +Section: admin +Architecture: amd64 +Priority: important +Breaks: edamame (<=2.9.3) +Depends: , python3-qtpy (>=2.4.1-2), locales (>=2.27-3ubuntu1), keyboard-configuration, util-linux (>=2.34-0.1ubuntu2), python3-parted (>=3.11.2), python3-psutil (>=5.5.0), python3-urllib3 (>=1.26.5-1~exp1), python3-gnupg (>=0.4.5), python3-xmltodict (>=0.11.0), python3-dnspython, tzdata, edamame-common (>=2.9.4) +Description: Edamame System Installation utility - Qt Front End + Edamame System Installation utility for Debian-based Operating Systems. + . + This is the Qt 5-based front end. diff --git a/DEBIAN/edamame-qt.install b/DEBIAN/edamame-qt.install new file mode 100644 index 0000000..6efad7c --- /dev/null +++ b/DEBIAN/edamame-qt.install @@ -0,0 +1 @@ +usr/share/edamame/UI/Qt_UI/* diff --git a/build-common.sh b/build-common.sh new file mode 100755 index 0000000..f25faa1 --- /dev/null +++ b/build-common.sh @@ -0,0 +1,144 @@ +#!/bin/bash +VERSION=$(cat DEBIAN/edamame-common.control | grep 'Version: ' | sed 's/Version: //g') +PAK=$(cat DEBIAN/edamame-common.control | grep 'Package: ' | sed 's/Package: //g') +ARCH=$(cat DEBIAN/edamame-common.control | grep 'Architecture: '| sed 's/Architecture: //g') +FOLDER="$PAK\_$VERSION\_$ARCH" +FOLDER=$(echo "$FOLDER" | sed 's/\\//g') +OPTIONS="$1" +SETTINGS=$(grep -v "^#" build.conf | sed 's/=/ /g') +META_URL=$(echo "$SETTINGS" | grep "META_URL" | awk '{print $2}') +PACK_URL=$(echo "$SETTINGS" | grep "PACK_URL" | awk '{print $2}') +mkdir ../"$FOLDER" +############################################################## +# # +# # +# COMPILE ANYTHING NECSSARY HERE # +# # +# # +############################################################## + +# Instead of compiling, we are building a tar.xz archive of the latest kernel package +# Don't make the archive if --pool passed +if [ "$OPTIONS" != "--pool" ]; then + cd usr/share/edamame + echo -e "\t###\tDOWNLOADING\t###\t" + rsync -vr "$PACK_URL" kernel + rsync -vr "$META_URL" kernel + echo -e "\t###\tDELETING CRUFT\t###\t" + list=$(ls kernel) + for each in $list; do + remove=$(ls kernel/$each | grep -v 'amd64.deb$') + for each2 in $remove; do + rm -rfv kernel/$each/$each2 + done + done + meta=$(echo kernel/linux-meta/$(ls kernel/linux-meta | sort -Vr | head -1)) + dep=$(dpkg-deb --field $meta Depends | sed 's/, /\\|/g') + cd kernel/linux-upstream + rm -rfv $(ls | sed "/\($dep\)/d") + dep=$(echo "$dep" | sed 's/\\|/ /g' | awk '{print $1}' | sed 's/\(image-\|headers-\)/xanmod_/g') + rm -rfv $(ls | grep "edge") + rm -rfv $(ls | grep "cacule") + cd ../linux-meta + rm -rfv $(ls | grep -v "$dep") + rm -rfv $(ls | grep "edge") + rm -rfv $(ls | grep "cacule") + cd .. + dep=$(echo "$dep" | sed 's/xanmod_//g') + mv linux-upstream "$dep" + cd .. + # delete empty folders + find . -type d -empty -print -delete + echo -e "\t###\tCOMPRESSING\t###\t" + tar --verbose --create --xz -f kernel.tar.xz kernel + echo -e "\t###\tCLEANING\t###\t" + rm -rfv kernel + cd ../../.. +fi + +# Pshyc - we're compiling shit now +cd usr/bin +echo "Would you like to build with Python 3.11, or 3.12?" +read -p "Exit [0], Do Not Compile [1], Python 3.11 [2], Python 3.12 [3], : " ans +if $(echo "${ans,,}" | grep -qE "1|one|first"); then + vert="dnc" +elif $(echo "${ans,,}" | grep -qE "2|two|second|3.11"); then + vert="3.11" +elif $(echo "${ans,,}" | grep -qE "3|three|third|3.12"); then + vert="3.12" +elif $(echo "${ans,,}" | grep -qE "exit|quit|leave|e|q|x|0|no|zero"); then + echo "Exiting as requested..." + exit 1 +else + echo "Input not recognized. Defaulting to Python 3.11" +fi +{ + g++ -fPIE -m64 -o edamame edamame.cxx +} || { + echo "Build failed. Try making sure you have 'python${vert}-dev' and 'libpython${vert}-dev' installed" 1>&2 + exit 2 +} +cd ../.. +files_to_edit=$(find "." -maxdepth 10 -type f -name '*.py' -print) +shebang='\#\!/usr/bin/env' +py_ver="" +if [ "$vert" == "dnc" ]; then + py_ver="python3" +elif [ "$vert" == "3.11" ]; then + py_ver="python3.11" +elif [ "$vert" == "3.12" ]; then + py_ver="python3.12" +fi +shebang="$shebang $py_ver" +for each in $files_to_edit; do + sed -i "s:\#\!shebang:$shebang:" $each +done +############################################################## +# # +# # +# REMEMBER TO DELETE SOURCE FILES FROM TMP # +# FOLDER BEFORE BUILD # +# # +# # +############################################################## + +# COPY FILES TO "$FOLDER" +copy=$( usr/share/doc/$PAK/changelog +cd usr/share/doc/$PAK +tar --verbose --create --xz -f changelog.gz changelog 1>/dev/null +rm -v changelog +cd ../../../.. +base="$PWD" +cp -Rv usr/share/doc/$PAK ../"$FOLDER"/usr/share/doc/$PAK +cd .. + +# Clean up +# delete binary files from repo +rm -v "$base"/usr/bin/edamame +# delete C++ source from package +rm -v "$FOLDER"/usr/bin/edamame.cxx +# delete Python cache files +if [[ "$vert" != "dnc" ]]; then + find "$FOLDER" -maxdepth 10 -type d -name __pycache__ -exec rm -rfv {} \; +fi +# Insert other deps into control file +sed -i "s/<\!--python_vert-->/$py_vert/g" "$FOLDER/DEBIAN/edamame-common.control" +sed -i "s/ , //g" "$FOLDER/DEBIAN/edamame-common.control" +#build the shit +mv "$FOLDER/DEBIAN/edamame-common.control" "$FOLDER/DEBIAN/control" +# mv "$FOLDER/DEBIAN/edamame-common.install" "$FOLDER/DEBIAN/install" +dpkg-deb --build "$FOLDER" +rm -rfv "$FOLDER" + +echo "$PAK Version: $VERSION built!" diff --git a/build-gtk.sh b/build-gtk.sh new file mode 100755 index 0000000..cfd2df3 --- /dev/null +++ b/build-gtk.sh @@ -0,0 +1,71 @@ +#!/bin/bash +VERSION=$(cat DEBIAN/edamame-gtk.control | grep 'Version: ' | sed 's/Version: //g') +PAK=$(cat DEBIAN/edamame-gtk.control | grep 'Package: ' | sed 's/Package: //g') +ARCH=$(cat DEBIAN/edamame-gtk.control | grep 'Architecture: '| sed 's/Architecture: //g') +FOLDER="$PAK\_$VERSION\_$ARCH" +FOLDER=$(echo "$FOLDER" | sed 's/\\//g') +SETTINGS=$(grep -v "^#" build.conf | sed 's/=/ /g') +mkdir ../"$FOLDER" +############################################################## +# # +# # +# COMPILE ANYTHING NECSSARY HERE # +# # +# # +############################################################## + +files_to_edit=$(find "$PWD" -maxdepth 10 -type f -name '*.py' -print) +shebang='\#\!/usr/bin/env' +py_ver="python3" +shebang="$shebang $py_ver" +for each in $files_to_edit; do + sed -i "s:\#\!shebang:$shebang:" $each +done +############################################################## +# # +# # +# REMEMBER TO DELETE SOURCE FILES FROM TMP # +# FOLDER BEFORE BUILD # +# # +# # +############################################################## + +# COPY FILES TO "$FOLDER" +copy=$( usr/share/doc/$PAK/changelog +cd usr/share/doc/$PAK +tar --verbose --create --xz -f changelog.gz changelog 1>/dev/null +rm -v changelog +cd ../../../.. +base="$PWD" +cp -Rv usr/share/doc/$PAK ../"$FOLDER"/usr/share/doc/$PAK +cd .. + +# Clean up +# delete Python cache files +if [[ "$vert" != "dnc" ]]; then + find "$FOLDER" -maxdepth 10 -type d -name __pycache__ -exec rm -rfv {} \; +fi +# Insert other deps into control file +sed -i "s/<\!--python_vert-->/$py_vert/g" "$FOLDER/DEBIAN/edamame-gtk.control" +sed -i "s/ , //g" "$FOLDER/DEBIAN/edamame-gtk.control" +#build the shit +mv "$FOLDER/DEBIAN/edamame-gtk.control" "$FOLDER/DEBIAN/control" +# mv "$FOLDER/DEBIAN/edamame-common.install" "$FOLDER/DEBIAN/install" +dpkg-deb --build "$FOLDER" +rm -rfv "$FOLDER" +cd "$base" +for each in $files_to_edit; do + sed -i "s:$shebang:\#\!shebang:" $each +done +echo "$PAK Version: $VERSION built!" diff --git a/build-qt.sh b/build-qt.sh new file mode 100755 index 0000000..0db6b58 --- /dev/null +++ b/build-qt.sh @@ -0,0 +1,71 @@ +#!/bin/bash +VERSION=$(cat DEBIAN/edamame-qt.control | grep 'Version: ' | sed 's/Version: //g') +PAK=$(cat DEBIAN/edamame-qt.control | grep 'Package: ' | sed 's/Package: //g') +ARCH=$(cat DEBIAN/edamame-qt.control | grep 'Architecture: '| sed 's/Architecture: //g') +FOLDER="$PAK\_$VERSION\_$ARCH" +FOLDER=$(echo "$FOLDER" | sed 's/\\//g') +SETTINGS=$(grep -v "^#" build.conf | sed 's/=/ /g') +mkdir ../"$FOLDER" +############################################################## +# # +# # +# COMPILE ANYTHING NECSSARY HERE # +# # +# # +############################################################## + +files_to_edit=$(find "$PWD" -maxdepth 10 -type f -name '*.py' -print) +shebang='\#\!/usr/bin/env' +py_ver="python3" +shebang="$shebang $py_ver" +for each in $files_to_edit; do + sed -i "s:\#\!shebang:$shebang:" $each +done +############################################################## +# # +# # +# REMEMBER TO DELETE SOURCE FILES FROM TMP # +# FOLDER BEFORE BUILD # +# # +# # +############################################################## + +# COPY FILES TO "$FOLDER" +copy=$( usr/share/doc/$PAK/changelog +cd usr/share/doc/$PAK +tar --verbose --create --xz -f changelog.gz changelog 1>/dev/null +rm -v changelog +cd ../../../.. +base="$PWD" +cp -Rv usr/share/doc/$PAK ../"$FOLDER"/usr/share/doc/$PAK +cd .. + +# Clean up +# delete Python cache files +if [[ "$vert" != "dnc" ]]; then + find "$FOLDER" -maxdepth 10 -type d -name __pycache__ -exec rm -rfv {} \; +fi +# Insert other deps into control file +sed -i "s/<\!--python_vert-->/$py_vert/g" "$FOLDER/DEBIAN/edamame-qt.control" +sed -i "s/ , //g" "$FOLDER/DEBIAN/edamame-qt.control" +#build the shit +mv "$FOLDER/DEBIAN/edamame-qt.control" "$FOLDER/DEBIAN/control" +# mv "$FOLDER/DEBIAN/edamame-common.install" "$FOLDER/DEBIAN/install" +dpkg-deb --build "$FOLDER" +rm -rfv "$FOLDER" +cd "$base" +for each in $files_to_edit; do + sed -i "s:$shebang:\#\!shebang:" $each +done +echo "$PAK Version: $VERSION built!" diff --git a/build.sh b/build.sh index 4fbc269..3171c4b 100755 --- a/build.sh +++ b/build.sh @@ -1,192 +1,9 @@ #!/bin/bash -VERSION=$(cat DEBIAN/control | grep 'Version: ' | sed 's/Version: //g') -PAK=$(cat DEBIAN/control | grep 'Package: ' | sed 's/Package: //g') -ARCH=$(cat DEBIAN/control | grep 'Architecture: '| sed 's/Architecture: //g') -FOLDER="$PAK\_$VERSION\_$ARCH" -FOLDER=$(echo "$FOLDER" | sed 's/\\//g') -OPTIONS="$1" -SETTINGS=$(grep -v "^#" build.conf | sed 's/=/ /g') -META_URL=$(echo "$SETTINGS" | grep "META_URL" | awk '{print $2}') -PACK_URL=$(echo "$SETTINGS" | grep "PACK_URL" | awk '{print $2}') -mkdir ../"$FOLDER" -############################################################## -# # -# # -# COMPILE ANYTHING NECSSARY HERE # -# # -# # -############################################################## +echo "Building all Edamame packages!" +./build-common.sh "$1" +./build-qt.sh +./build-gtk.sh +mkdir build +cp ../edamame*.deb build/ -# Instead of compiling, we are building a tar.xz archive of the latest kernel package -# Don't make the archive if --pool passed -if [ "$OPTIONS" != "--pool" ]; then - cd usr/share/edamame - echo -e "\t###\tDOWNLOADING\t###\t" - rsync -vr "$PACK_URL" kernel - rsync -vr "$META_URL" kernel - echo -e "\t###\tDELETING CRUFT\t###\t" - list=$(ls kernel) - for each in $list; do - remove=$(ls kernel/$each | grep -v 'amd64.deb$') - for each2 in $remove; do - rm -rfv kernel/$each/$each2 - done - done - meta=$(echo kernel/linux-meta/$(ls kernel/linux-meta | sort -Vr | head -1)) - dep=$(dpkg-deb --field $meta Depends | sed 's/, /\\|/g') - cd kernel/linux-upstream - rm -rfv $(ls | sed "/\($dep\)/d") - dep=$(echo "$dep" | sed 's/\\|/ /g' | awk '{print $1}' | sed 's/\(image-\|headers-\)/xanmod_/g') - rm -rfv $(ls | grep "edge") - rm -rfv $(ls | grep "cacule") - cd ../linux-meta - rm -rfv $(ls | grep -v "$dep") - rm -rfv $(ls | grep "edge") - rm -rfv $(ls | grep "cacule") - cd .. - dep=$(echo "$dep" | sed 's/xanmod_//g') - mv linux-upstream "$dep" - cd .. - # delete empty folders - find . -type d -empty -print -delete - echo -e "\t###\tCOMPRESSING\t###\t" - tar --verbose --create --xz -f kernel.tar.xz kernel - echo -e "\t###\tCLEANING\t###\t" - rm -rfv kernel - cd ../../.. -fi - -# Pshyc - we're compiling shit now -cd usr/bin -echo "Would you like to build with Python 3.11, or 3.12?" -read -p "Exit [0], Do Not Compile [1], Python 3.11 [2], Python 3.12 [3], : " ans -if $(echo "${ans,,}" | grep -qE "1|one|first"); then - vert="dnc" -elif $(echo "${ans,,}" | grep -qE "2|two|second|3.11"); then - vert="3.11" -elif $(echo "${ans,,}" | grep -qE "3|three|third|3.12"); then - vert="3.12" -elif $(echo "${ans,,}" | grep -qE "exit|quit|leave|e|q|x|0|no|zero"); then - echo "Exiting as requested..." - exit 1 -else - echo "Input not recognized. Defaulting to Python 3.11" -fi -{ - g++ -fPIE -m64 -o edamame edamame.cxx -} || { - echo "Build failed. Try making sure you have 'python${vert}-dev' and 'libpython${vert}-dev' installed" 1>&2 - exit 2 -} -cd ../.. -files_to_edit=$(find "." -maxdepth 10 -type f -name '*.py' -print) -shebang='\#\!/usr/bin/env' -py_ver="" -if [ "$vert" == "dnc" ]; then - py_ver="python3" -elif [ "$vert" == "3.11" ]; then - py_ver="python3.11" -elif [ "$vert" == "3.12" ]; then - py_ver="python3.12" -fi -shebang="$shebang $py_ver" -for each in $files_to_edit; do - sed -i "s:\#\!shebang:$shebang:" $each -done -############################################################## -# # -# # -# REMEMBER TO DELETE SOURCE FILES FROM TMP # -# FOLDER BEFORE BUILD # -# # -# # -############################################################## -if [ -d bin ]; then - cp -R bin ../"$FOLDER"/bin -fi -if [ -d etc ]; then - cp -R etc ../"$FOLDER"/etc -fi -if [ -d usr ]; then - cp -R usr ../"$FOLDER"/usr -fi -if [ -d lib ]; then - cp -R lib ../"$FOLDER"/lib -fi -if [ -d lib32 ]; then - cp -R lib32 ../"$FOLDER"/lib32 -fi -if [ -d lib64 ]; then - cp -R lib64 ../"$FOLDER"/lib64 -fi -if [ -d libx32 ]; then - cp -R libx32 ../"$FOLDER"/libx32 -fi -if [ -d sbin ]; then - cp -R sbin ../"$FOLDER"/sbin -fi -if [ -d var ]; then - cp -R var ../"$FOLDER"/var -fi -if [ -d opt ]; then - cp -R opt ../"$FOLDER"/opt -fi -if [ -d srv ]; then - cp -R srv ../"$FOLDER"/srv -fi -############################################################## -# # -# # -# COMPILE ANYTHING NECSSARY HERE # -# # -# # -############################################################## -if [[ "$vert" != "dnc" ]]; then - cp nuitka_compile.sh ../"$FOLDER"/ - cp compile.conf ../"$FOLDER"/ - base="$PWD" - cd ../"$FOLDER"/ - ./nuitka_compile.sh --python-ver=$vert - rm -v nuitka_compile.sh compile.conf - sed -i "s/^Depends:/& lib${py_vert},/" DEBIAN/control - cd "$base" -fi -############################################################## -# # -# # -# REMEMBER TO DELETE SOURCE FILES FROM TMP # -# FOLDER BEFORE BUILD # -# # -# # -############################################################## -cp -R DEBIAN ../"$FOLDER"/DEBIAN -mkdir -p usr/share/doc/$PAK -git log > usr/share/doc/$PAK/changelog -cd usr/share/doc/$PAK -tar --verbose --create --xz -f changelog.gz changelog 1>/dev/null -rm changelog -cd ../../../.. -base="$PWD" -cp -R usr/share/doc/$PAK ../"$FOLDER"/usr/share/doc/$PAK -cd .. -#DELETE STUFF HERE -if [ "$OPTIONS" != "--pool" ]; then - rm "$base"/usr/share/edamame/kernel.tar.xz -fi -# delete binary files from repo -rm "$base"/usr/bin/edamame -# delete C++ source from package -rm "$FOLDER"/usr/bin/edamame.cxx -# delete Python cache files -if [[ "$vert" != "dnc" ]]; then - find "$FOLDER" -maxdepth 10 -type d -name __pycache__ -exec rm -rfv {} \; -fi -# Insert other deps into control file -sed -i "s/<\!--python_vert-->/$py_vert/g" "$FOLDER/DEBIAN/control" -#build the shit -dpkg-deb --build "$FOLDER" -rm -rf "$FOLDER" -cd "$base" -for each in $files_to_edit; do - sed -i "s:$shebang:\#\!shebang:" $each -done +echo "All Edamame packages build! Please check \`build' for build artifacts and distributables." From fb92ccd34d2815f5eb94238fa8ce16010f80d3a0 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 20:51:12 -0500 Subject: [PATCH 12/27] Fix typo --- DEBIAN/edamame-common.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEBIAN/edamame-common.install b/DEBIAN/edamame-common.install index 5b0285a..9c5b5e4 100644 --- a/DEBIAN/edamame-common.install +++ b/DEBIAN/edamame-common.install @@ -4,7 +4,7 @@ usr/lib/* usr/share/applications/* usr/share/doc/* usr/share/polkit-1/* -usr/share/edamame/auto_partitoner.py +usr/share/edamame/auto_partitioner.py usr/share/edamame/check_internet.py usr/share/edamame/check_kernel_versions.py usr/share/edamame/chroot.py From e62773446256a3ff79e10bee7653fb9772be7259 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 21:40:30 -0500 Subject: [PATCH 13/27] Change working directory before running Edamame --- DEBIAN/edamame-common.control | 2 +- usr/bin/edamame.cxx | 4 +++- usr/share/edamame/UI/__init__.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control index 34990bf..bd86e9e 100644 --- a/DEBIAN/edamame-common.control +++ b/DEBIAN/edamame-common.control @@ -1,5 +1,5 @@ Package: edamame-common -Version: 2.9.4 +Version: 2.9.5 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 3cf0dbe..cb3fc2d 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -32,10 +32,11 @@ // import libs #include #include +#include using namespace std; -str VERSION = "2.9.3"; +str VERSION = "2.9.5"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; @@ -98,6 +99,7 @@ void launch(str arg) command = command + "--gui=" + arg; } command = command + " 2>/tmp/edamame.log 1>&2"; + chdir("/usr/share/edamame"); run(command.c_str()); run((command1 + disable).c_str()); } diff --git a/usr/share/edamame/UI/__init__.py b/usr/share/edamame/UI/__init__.py index 476f2e0..e1de2e0 100755 --- a/usr/share/edamame/UI/__init__.py +++ b/usr/share/edamame/UI/__init__.py @@ -21,10 +21,11 @@ # MA 02110-1301, USA. # # +"""UI for Edamame""" import os import importlib -"""UI for Edamame""" + def load_UI(ui_type: str) -> bool: """Load the specified UI type""" if os.path.exists(f"UI/{ui_type}_UI"): From 3a64ff275ffcdbfd72427756ebe6bf7e8f83c696 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 22:05:22 -0500 Subject: [PATCH 14/27] Fix typo preventing running with Qt --- usr/bin/edamame.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index cb3fc2d..6cc305b 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -96,7 +96,7 @@ void launch(str arg) } elif (arg != "") { - command = command + "--gui=" + arg; + command = command + " --gui=" + arg; } command = command + " 2>/tmp/edamame.log 1>&2"; chdir("/usr/share/edamame"); From 073070da2ffabd3636d436a360a7ec7deb811344 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 22:19:21 -0500 Subject: [PATCH 15/27] Minor change to help with testing This might also help the code actually run too. --- usr/share/edamame/UI/Qt_UI/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/usr/share/edamame/UI/Qt_UI/main.py b/usr/share/edamame/UI/Qt_UI/main.py index 4ed82eb..62580f1 100755 --- a/usr/share/edamame/UI/Qt_UI/main.py +++ b/usr/share/edamame/UI/Qt_UI/main.py @@ -47,8 +47,12 @@ def has_special_character(input_string): return True -with open("../../../../../etc/edamame/settings.json") as config_file: - SETTINGS = json.loads(config_file.read()) +try: + with open("../../../../../etc/edamame/settings.json") as config_file: + SETTINGS = json.loads(config_file.read()) +except FileNotFoundError: + with open("../../../etc/edamame/settings.json") as config_file: + SETTINGS = json.loads(config_file.read()) DEFAULT = f""" From 66f6e7bf66ff954d656b34024c0a6e08068c8e60 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Fri, 15 Nov 2024 22:31:19 -0500 Subject: [PATCH 16/27] Fix minor import error --- .../edamame/UI/{Qt_UI => QT_UI}/__init__.py | 12 +- .../edamame/UI/QT_UI/auto_partitioner.py | 765 ++++++++++++++++++ usr/share/edamame/UI/QT_UI/common.py | 107 +++ .../edamame/UI/{Qt_UI => QT_UI}/confirm.py | 2 +- .../edamame/UI/{Qt_UI => QT_UI}/error.py | 6 +- usr/share/edamame/UI/{Qt_UI => QT_UI}/main.py | 2 +- .../edamame/UI/{Qt_UI => QT_UI}/progress.py | 2 +- .../edamame/UI/{Qt_UI => QT_UI}/qt_common.py | 0 .../edamame/UI/{Qt_UI => QT_UI}/report.py | 0 .../edamame/UI/{Qt_UI => QT_UI}/success.py | 6 +- .../edamame/UI/Qt_UI/auto_partitioner.py | 1 - usr/share/edamame/UI/Qt_UI/common.py | 1 - usr/share/edamame/UI/__init__.py | 7 +- 13 files changed, 891 insertions(+), 20 deletions(-) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/__init__.py (81%) create mode 100755 usr/share/edamame/UI/QT_UI/auto_partitioner.py create mode 100755 usr/share/edamame/UI/QT_UI/common.py rename usr/share/edamame/UI/{Qt_UI => QT_UI}/confirm.py (99%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/error.py (96%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/main.py (99%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/progress.py (99%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/qt_common.py (100%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/report.py (100%) rename usr/share/edamame/UI/{Qt_UI => QT_UI}/success.py (99%) delete mode 120000 usr/share/edamame/UI/Qt_UI/auto_partitioner.py delete mode 120000 usr/share/edamame/UI/Qt_UI/common.py diff --git a/usr/share/edamame/UI/Qt_UI/__init__.py b/usr/share/edamame/UI/QT_UI/__init__.py similarity index 81% rename from usr/share/edamame/UI/Qt_UI/__init__.py rename to usr/share/edamame/UI/QT_UI/__init__.py index dfbf583..73f4fff 100755 --- a/usr/share/edamame/UI/Qt_UI/__init__.py +++ b/usr/share/edamame/UI/QT_UI/__init__.py @@ -22,9 +22,9 @@ # # """UI for Edamame""" -import UI.Qt_UI.confirm as confirm -import UI.Qt_UI.error as error -import UI.Qt_UI.main as main -import UI.Qt_UI.progress as progress -import UI.Qt_UI.report as report -import UI.Qt_UI.success as success +import UI.QT_UI.confirm as confirm +import UI.QT_UI.error as error +import UI.QT_UI.main as main +import UI.QT_UI.progress as progress +import UI.QT_UI.report as report +import UI.QT_UI.success as success diff --git a/usr/share/edamame/UI/QT_UI/auto_partitioner.py b/usr/share/edamame/UI/QT_UI/auto_partitioner.py new file mode 100755 index 0000000..d5e5f49 --- /dev/null +++ b/usr/share/edamame/UI/QT_UI/auto_partitioner.py @@ -0,0 +1,765 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# auto_partitioner.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Auto-partition Drive selected for installation""" +import json +import time +import os +import sys +import subprocess +import parted +import psutil +import common + + +def gb_to_bytes(gb): + """Convert GB to Bytes""" + return gb * (10 ** 9) + + +def bytes_to_gb(b): + """Convert Bytes to GB""" + return b / (10 ** 9) + + +def mb_to_bytes(mb): + """Convert MB to Bytes""" + return mb * (10 ** 6) + + +def is_EFI(): + """Get if the current system is using EFI""" + return os.path.isdir("/sys/firmware/efi") + + +def part_to_drive(part): + """Get a drive from a griven partition + This is just an alias for get_drive_path()""" + return get_drive_path(part) + + +# GET DEFAULT CONFIG +LIMITER = gb_to_bytes(32) +PARTITIONING_ENABLED = True + +# get configuration for partitioning +config = { + "partitioning": { + "EFI": { + "EFI": { + "START": 0, + "END": 500 + }, + "ROOT": { + "START": 501, + "END": "40%", + "fs": "btrfs" + }, + "HOME": { + "START": "40%", + "END": "100%", + "fs": "btrfs" + } + }, + "BIOS": { + "ROOT": { + "START": 0, + "END": "40%", + "fs": "ext4" + }, + "HOME": { + "START": "40%", + "END": "100%", + "fs": "btrfs" + } + }, + "GENERAL": { + "min root size": 23000, + "mdswh": 128 + } + } + } + +try: + with open("/etc/edamame/settings.json", "r") as config_file: + config_data = json.load(config_file) +except FileNotFoundError: + config_data = config + + +# check to make sure packager left this block in +if "partitioning" in config_data: + new_config = config_data["partitioning"] +else: + common.eprint("Partitioning settings not found. Cannot partition drives automatically") + PARTITIONING_ENABLED = False + + +if is_EFI(): + try: + new_config = new_config["EFI"] | new_config["GENERAL"] + config = config["partitioning"]["EFI"] | config["partitioning"]["GENERAL"] + except KeyError: + common.eprint("EFI or General partitioning details not defined. Falling back to defaults") + print("EFI or General partitioning details not defined. Falling back to defaults") + new_config = config["partitioning"]["EFI"] | config["partitioning"]["GENERAL"] +else: + try: + new_config = new_config["BIOS"] | new_config["GENERAL"] + config = config["partitioning"]["BIOS"] | config["partitioning"]["GENERAL"] + except KeyError: + common.eprint("BIOS or General partitioning details not defined. Falling back to defaults") + print("BIOS or General partitioning details not defined. Falling back to defaults") + new_config = config["partitioning"]["BIOS"] | config["partitioning"]["GENERAL"] + + + + +# make sure everything is there. If not, substitute in defaults +# we don't use config["partitioning"][] syntax here because +# if we enter any of +# these if-statements, then we are not using the built-in settings. +# In which case, because of the above +# block, if we are using externally sourced settings, then config will +# be pared down for us. And if we +# are using the built-in settings, then we won't enter any of +if "ROOT" not in new_config: + new_config["ROOT"] = config["ROOT"] +if "HOME" not in new_config: + new_config["HOME"] = config["HOME"] +if is_EFI(): + if "EFI" not in new_config: + new_config["EFI"] = config["EFI"] +if "min root size" not in new_config: + new_config["min root size"] = config["min root size"] +if "mdswh" not in new_config: + new_config["mdswh"] = config["mdswh"] +config = new_config + # if not, fall back to internal default + + +def size_of_part(part_path, bytes=False): + """Get the size of the partition at `part_path` + + If `bytes` is True, return size in bytes. + Else, return size in gigabytes. + """ + # Get the root Drive + root = get_drive_path(part_path) + # connect to that drive's partition table + device = parted.getDevice(root) + try: + disk = parted.Disk(device) + except parted._ped.DiskLabelException: + raise OSError(f"NO PARTITION TABLE EXISTS ON { root } ") + # Grab the right partiton + part = disk.getPartitionByPath(part_path) + # get size + size = part.getSize(unit="b") + # size conversion, if necessary + if not bytes: + size = bytes_to_gb(size) + return size + + +def get_drive_path(part_path): + """Get drive path from partition path""" + if ("nvme" in part_path) or ("mmc" in part_path): + try: + output = part_path[:part_path.index("p")] + except ValueError: + # this might be a device with no partitions. + return part_path + else: + count = 0 + for each in part_path: + if not each.isnumeric(): + count+=1 + else: + break + output = part_path[:count] + return output + + +def get_min_root_size(swap=True, ram_size=False, ram_size_unit=True, + bytes=True): + """Get minimum root partition size as bytes + + When `swap' == True, factor in the ideal size of swap file for + the current system's RAM. + + If `ram_size' is not an int or float, RAM of the + current system will be used. + + if `ram_size_unit' is True, `ram_size' should be in GB. When `ram_size_unit' + is False, `ram_size' should be in bytes. + + If `bytes` is True, return size in bytes. + Else, return size in gigabytes. + """ + if swap: + if type(ram_size) not in (int, float): + mem = psutil.virtual_memory().total + else: + if ram_size_unit: + mem = gb_to_bytes(ram_size) + else: + mem = ram_size + swap_amount = round((mem + ((mem / 1024 ** 3) ** 0.5) * 1024 ** 3)) + else: + swap_amount = 0 + min_root_size = swap_amount + (config["min root size"] * (1000 ** 2)) + if not bytes: + min_root_size = bytes_to_gb(min_root_size) + return min_root_size + + +def check_disk_state(): + """Check disk state as registered with lsblk + + Returns data as dictionary + """ + try: + subprocess.check_call(["partprobe"]) + except subprocess.CalledProcessError: + print("`partprobe` failed. Provided info may not be up-to-date.") + time.sleep(0.2) + command = ["lsblk", "--json", "--paths", "--bytes", "--output", + "name,size,type,fstype"] + data = json.loads(subprocess.check_output(command))["blockdevices"] + for each in range(len(data) - 1, -1, -1): + if data[each]["type"] == "loop": + del data[each] + return data + + +def get_fs(part_name: str): + """Get filesystem type for given partition""" + disk = check_disk_state() + for each in disk: + if each["name"] == part_name: + return each["fstype"] + if "children" in each: + for each1 in each["children"]: + if each1["name"] == part_name: + return each1["fstype"] + + +def __mkfs__(device, fs): + """Set partition filesystem""" + # pre-define command + if "ext" in fs: + force = "-F" + else: + force = "-f" + command = ["mkfs", "-t", fs, force, str(device)] + try: + try: + data = subprocess.check_output(command).decode() + except UnicodeDecodeError: + return "" + except subprocess.CalledProcessError as error: + data = error.output.decode() + return data + + +if is_EFI(): + def __mkfs_fat__(device): + """Set partition filesystem to FAT32""" + # pre-define command + command = ["mkfs.fat", "-F", "32", str(device)] + try: + data = subprocess.check_output(command).decode() + except subprocess.CalledProcessError as error: + data = error.output.decode() + return data + + + def __make_efi__(device, start=config["EFI"]["START"], + end=config["EFI"]["END"]): + """Make EFI partition""" + disk = parted.Disk(device) + start_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(start, + "MB", + device.sectorSize), + end=parted.sizeToSectors(start + 10, + "MB", + device.sectorSize)) + end_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(common.real_number(end - 20), + "MB", + device.sectorSize), + end=parted.sizeToSectors(end + 10, + "MB", + device.sectorSize)) + min_size = parted.sizeToSectors(common.real_number((end - start) - 25), + "MB", + device.sectorSize) + max_size = parted.sizeToSectors(common.real_number((end - start) + 20), + "MB", + device.sectorSize) + const = parted.Constraint(startAlign=device.optimumAlignment, + endAlign=device.optimumAlignment, + startRange=start_geo, endRange=end_geo, + minSize=min_size, maxSize=max_size) + geometry = parted.geometry.Geometry(start=start, + length=parted.sizeToSectors(end - start, + "MB", + device.sectorSize), + device=device) + new_part = parted.Partition(disk=disk, + type=parted.PARTITION_NORMAL, + geometry=geometry) + new_part.setFlag(parted.PARTITION_BOOT) + disk.addPartition(partition=new_part, constraint=const) + disk.commit() + time.sleep(0.1) + __mkfs_fat__(new_part.path) + return new_part.path + + +def sectors_to_size(sectors, sector_size): + """Convert number of sectors to sector size""" + return (sectors * sector_size) / 1000 ** 2 + + +def __make_root__(device, start=config["ROOT"]["START"], + end=config["ROOT"]["END"], fs=config["ROOT"]["fs"]): + """Make root partition""" + # __parted__(device, ["mkpart", name, fs, str(start), str(end)]) + size = sectors_to_size(device.length, device.sectorSize) + try: + if start[-1] == "%": + start = int(start[:-1]) / 100 + start = int(size * start) + except TypeError: + pass + try: + if end[-1] == "%": + end = int(end[:-1]) / 100 + end = int(size * end) + except TypeError: + pass + disk = parted.Disk(device) + s_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(common.real_number(start - 20), + "MB", + device.sectorSize), + end=parted.sizeToSectors(start + 20, + "MB", + device.sectorSize)) + e_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(common.real_number(end - 100), + "MB", + device.sectorSize), + end=parted.sizeToSectors(end, "MB", + device.sectorSize)) + min_size = parted.sizeToSectors(common.real_number((end - start) - 250), + "MB", + device.sectorSize) + max_size = parted.sizeToSectors(common.real_number((end - start) + 250), + "MB", + device.sectorSize) + const = parted.Constraint(startAlign=device.optimumAlignment, + endAlign=device.optimumAlignment, + startRange=s_geo, endRange=e_geo, + minSize=min_size, + maxSize=max_size) + geo = parted.geometry.Geometry(start=parted.sizeToSectors(start, "MB", + device.sectorSize), + length=parted.sizeToSectors((end - start), + "MB", + device.sectorSize), + device=device) + new_part = parted.Partition(disk=disk, + type=parted.PARTITION_NORMAL, + geometry=geo) + try: + disk.addPartition(partition=new_part, constraint=const) + except parted._ped.PartitionException: + # Simply use the geometry of the first free space region, if it is big enough + data = disk.getFreeSpaceRegions() + sizes = {} + for each in data: + sizes[each.getSize(unit="b")] = each + sizes_sorted = sorted(sizes) + made = False + for each in range(len(sizes_sorted) - 1, -1, -1): + if sizes[sizes_sorted[each]].getSize(unit="b") >= get_min_root_size(): + s_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(common.real_number(sizes[sizes_sorted[each]].start - 2000), + "MB", + device.sectorSize), + end=parted.sizeToSectors(sizes[sizes_sorted[each]].start + 2000, + "MB", + device.sectorSize)) + e_geo = parted.geometry.Geometry(device=device, + start=parted.sizeToSectors(common.real_number(sizes[sizes_sorted[each]].end - 2000), + "MB", + device.sectorSize), + end=parted.sizeToSectors(sizes[sizes_sorted[each]].end + 2000, "MB", + device.sectorSize)) + min_size = parted.sizeToSectors(common.real_number((sizes[sizes_sorted[each]].end - sizes[sizes_sorted[each]].start) - 2000), + "MB", + device.sectorSize) + max_size = parted.sizeToSectors(common.real_number((sizes[sizes_sorted[each]].end - sizes[sizes_sorted[each]].start) + 2000), + "MB", + device.sectorSize) + const = parted.Constraint(startAlign=device.optimumAlignment, + endAlign=device.optimumAlignment, + startRange=s_geo, endRange=e_geo, + minSize=min_size, + maxSize=max_size) + new_part = parted.Partition(disk=disk, + type=parted.PARTITION_NORMAL, + geometry=sizes[sizes_sorted[each]]) + try: + disk.addPartition(partition=new_part, constraint=const) + except: + break + made = True + break + if not made: + common.eprint("WAS NOT ABLE TO CREATE ROOT PARTITION. LIKELY NOT ENOUGH SPACE FOR ONE.") + common.eprint("INSTALLATION WILL FAIL") + + disk.commit() + time.sleep(0.1) + __mkfs__(new_part.path, fs) + return new_part.path + + +def __make_home__(device, new_start=config["HOME"]["START"], + new_end=config["HOME"]["END"], new_fs=config["HOME"]["fs"]): + """Easy sorta-macro to make a home partiton""" + return __make_root__(device, start=new_start, end=new_end, fs=new_fs) + + +def __generate_return_data__(home, efi, part1, part2, part3): + """Generate return data for wherever we are in the code""" + parts = {} + if efi: + parts["EFI"] = part1 + parts["ROOT"] = part2 + else: + parts["EFI"] = None + parts["ROOT"] = part1 + if home != "MAKE": + parts["HOME"] = home + else: + if efi: + parts["HOME"] = part3 + else: + parts["HOME"] = part2 + return parts + + +def __make_root_boot__(device): + """Make Root partition bootable. + +This ONLY works if the root partition is the only partition on the drive +""" + disk = parted.Disk(device) + partitions = disk.getPrimaryPartitions() + partitions[0].setFlag(parted.PARTITION_BOOT) + disk.commit() + + +def make_part_boot(part_path): + """Make a partition bootable. + + This is useful for ensuring that users make their EFI partiton + (or root partition in the case of BIOS systems) bootable. + + part_path should be set to the path to the partition device file. + So, if a user's EFI partition is the first partition on a SATA or USB + interface, part_path should be: + + /dev/sda1 + + If the user's EFI partition is the 5th partition on the first NVMe drive on + the first NVMe bus: + + /dev/nvme0n1p5 + + etc... + """ + # Get root drive + root = get_drive_path(part_path) + # get Device + device = parted.getDevice(root) + # get entire partition table + disk = parted.Disk(device) + # narrow down to just primary partitions + partitions = disk.getPrimaryPartitions() + # mark designated partition as bootable + try: + if ("nvme" in part_path) or ("mmc" in part_path): + flags = partitions[int(part_path[part_path.index("p") + 1:])].getFlagsAsString().split(", ") + if "boot" not in flags: + partitions[int(part_path[part_path.index("p") + 1:])].setFlag(parted.PARTITION_BOOT) + else: + common.eprint(f"{ part_path } already marked as boot. Not re-marking.") + return + else: + flags = partitions[int(part_path[8:])].getFlagsAsString().split(", ") + if "boot" not in flags: + partitions[int(part_path[8:])].setFlag(parted.PARTITION_BOOT) + else: + common.eprint(f"{ part_path } already marked as boot. Not re-marking.") + return + except IndexError: + return + # We don't have commitment issues here! + disk.commit() + + +def clobber_disk(device): + """Reset drive""" + common.eprint("DELETING PARTITIONS.") + device.clobber() + disk = parted.freshDisk(device, "gpt") + disk.commit() + return disk + + +def delete_part(part_path): + """Delete partiton indicated by path""" + device = parted.getDevice(get_drive_path(part_path)) + disk = parted.Disk(device) + part = disk.getPartitionByPath(part_path) + disk.deletePartition(part) + disk.commit() + + +def partition(root, efi, home, raid_array): + """Partition drive 'root' for Linux installation + +root: needs to be path to installation drive (i.e.: /dev/sda, /dev/nvme0n1) +efi: booleen indicated whether system was booted with UEFI +home: whether to make a home partition, or if one already exists + +Possible values: + None, 'NULL': Do not make a home partition, and one does not exist + 'MAKE': Make a home partition on the installation drive + (some partition path): path to a partition to be used as home directory +""" + # Initial set up for partitioning + common.eprint("\t###\tauto_partioner.py STARTED\t###\t") + part1 = None + part2 = None + part3 = None + if raid_array["raid_type"] not in (None, "OEM"): + if raid_array["raid_type"].lower() == "raid0": + raid_array["raid_type"] = 0 + elif raid_array["raid_type"].lower() == "raid1": + raid_array["raid_type"] = 1 + elif raid_array["raid_type"].lower() == "raid10": + raid_array["raid_type"] = 10 + for each in raid_array["disks"]: + if each in ("1", "2"): + if raid_array["disks"][each] is None: + # Invalid RAID array. Do not create. + raid_array["raid_type"] = None + # We double check this to ensure we are working with valid RAID arrays + if raid_array["raid_type"] is not None: + disks = [] + for each in raid_array["disks"]: + if raid_array["disks"][each] is not None: + disks.append(raid_array["disks"][each]) + raid_array["disks"] = disks + device = parted.getDevice(root) + try: + disk = parted.Disk(device) + except parted._ped.DiskLabelException: + common.eprint("NO PARTITION TABLE EXISTS. MAKING NEW ONE . . .") + disk = parted.freshDisk(device, "gpt") + # sectors_to_size() returns size in MBs, multiply by 1 million to convert to bytes + size = sectors_to_size(device.length, device.sectorSize) * 1000000 + if ((home in ("NULL", "null", + None, "MAKE")) and (raid_array["raid_type"] is None)): + disk = clobber_disk(device) + elif raid_array["raid_type"] is not None: + disk = clobber_disk(device) + common.eprint("CREATING RAID ARRAY") + common.eprint(f"RAID TYPE: {raid_array['raid_type']}") + if not make_raid_array(raid_array["disks"], raid_array["raid_type"]): + common.eprint("INITIAL RAID ARRAY CREATION FAILED. FORCING . . .") + if not make_raid_array(raid_array["disks"], raid_array["raid_type"], + force=True): + common.eprint("FORCED RAID ARRAY CREATION FAILED. BAD DRIVE?") + common.eprint("FALLING BACK TO NO HOME PARTITION.") + home = None + else: + # we know there is a pre-existing home partition + # determine if it is on the same drive and act accordingly + home_drive = get_drive_path(home) + if home_drive == root: + common.eprint("HOME PARTITION EXISTS. NOT DELETING PARTITIONS.") + else: + disk = clobber_disk(device) + if size <= LIMITER: + if efi: + part1 = __make_efi__(device) + part2 = __make_root__(device, end="100%") + else: + part1 = __make_root__(device, start="0%", end="100%") + __make_root_boot__(device) + common.eprint("\t###\tauto_partioner.py CLOSED\t###\t") + return __generate_return_data__(home, efi, part1, part2, part3) + # Handled 16GB drives + # From here until 64GB drives, we want our root partition to be AT LEAST + # 16GB + if home == "MAKE": + # If home == "MAKE", we KNOW there are no partitons because we made a + # new partition table + if size >= gb_to_bytes(config["mdswh"]): + root_end = int((size * 0.35) / (1000 ** 2)) + else: + root_end = get_min_root_size() + if (efi and (part1 is None)): + part1 = __make_efi__(device) + part2 = __make_root__(device, end=root_end) + part3 = __make_home__(device, new_start=root_end) + elif part1 is None: + part1 = __make_root__(device, start="0%", end=root_end) + __make_root_boot__(device) + part2 = __make_home__(device, new_start=root_end) + common.eprint("\t###\tauto_partioner.py CLOSED\t###\t") + return __generate_return_data__(home, efi, part1, part2, part3) + if home in ("NULL", "null", None, "Home Partition", "home partition"): + # If home == any possible 'null' value, + # we KNOW there are no partitons because we made a + # new partition table + if efi: + part1 = __make_efi__(device) + part2 = __make_root__(device, end="100%") + else: + part1 = __make_root__(device, start="0%", end="100%") + __make_root_boot__(device) + common.eprint("\t###\tauto_partioner.py CLOSED\t###\t") + return __generate_return_data__(home, efi, part1, part2, part3) + # This one we need to figure out if the home partiton is on the drive + # we are working on or elsewhere + if root == get_drive_path(home): + # It IS on the same drive. We need to figure out where at and work + # around it + # NOTE: WE NEED TO WORK IN MB ONLY IN THIS SECTION + disk = parted.Disk(device) + data = disk.getFreeSpaceRegions() + sizes = {} + for each in data: + sizes[each.length] = each + sizes_sorted = sorted(sizes) + # Lets make some partitons! + if efi: + for each in sizes_sorted: + if sizes[each].getSize() >= 200: + end = sizes[each].start + parted.sizeToSectors(200, "MB", + device.sectorSize) + end = sectors_to_size(end, device.sectorSize) + part1 = __make_efi__(device, start=sizes[each].start, + end=end) + part2 = __make_root__(device, start=end, + end=sizes[each].end) + break + else: + for each in sizes_sorted: + if sizes[each].getSize() >= 200: + part1 = __make_root__(device, start=sizes[each].start + 1, + end=sizes[each].end - 1) + __make_root_boot__(device) + break + common.eprint("\t###\tauto_partioner.py CLOSED\t###\t") + return __generate_return_data__(home, efi, part1, part2, part3) + # it's elsewhere. We're good. + if efi: + part1 = __make_efi__(device) + part2 = __make_root__(device, end="100%") + else: + part1 = __make_root__(device, start="0%", end="100%") + __make_root_boot__(device) + part3 = home + # Figure out what parts are for what + # Return that data as a dictonary + common.eprint("\t###\tauto_partioner.py CLOSED\t###\t") + return __generate_return_data__(home, efi, part1, part2, part3) + + +def make_raid_array(disks: list, raid_type: int, force=False) -> bool: + """Make BTRFS RAID Array + Supported RAID Types: + RAID0: Minimum 2 drives, max performance, no resiliancey + RAID1: Minimum 2 drives, max resiliancey, minimum performance + RAID5: 3-16 drives, poor resiliancey, great read performance, poor write performance + RAID6: Minimum 4 drives. Medium resiliancey, great read performance, worse write performance + RAID10: Minimum 4 drives, Medium resiliancey, Great performance + + raid_type should be an int indicating the RAID type desired so: + raid_type == 0: use RAID0 + raid_type == 1: use RAID1 + etc. + + Any ints other than 0, 1, 5, 6, and 10 will throw a ValueError + + disks should be a list of the disks desired in the RAID array. A ValueError + will be thrown if the list is too short or too long. + + Returns True if array was successfully completed. False otherwise. + You can then mount the array by calling `mount' on any of the devices in the + disks list. + """ + raid_types_dict = {0: "raid0", + 1: "raid1", + 5: "raid5", + 6: "raid6", + 10: "raid10"} + command = ["mkfs.btrfs", "-d"] + if force: + command.insert(1, "-f") + if raid_type not in raid_types_dict: + raise ValueError(f"'{raid_type}' not a valid BTRFS RAID type") + if raid_type in (0, 1): + if len(disks) < 2: + raise ValueError(f"Not enough disks for RAID{raid_type}") + elif raid_type == 5: + if not 3 <= len(disks) <= 16: + raise ValueError("Not enough/Too many disks for RAID5") + elif raid_type in (6, 10): + if len(disks) < 4: + raise ValueError(f"Not enough disks for RAID{raid_type}") + for each in disks: + if not os.path.exists(each): + raise FileNotFoundError(f"Device not found: {each}") + command.append(raid_types_dict[raid_type]) + if raid_type not in (0, 5, 6): + command.append("-m") + command.append(raid_types_dict[raid_type]) + command = command + disks + try: + subprocess.check_call(command, stderr=sys.stderr.buffer, + stdout=sys.stderr.buffer) + return True + except subprocess.CalledProcessError: + return False diff --git a/usr/share/edamame/UI/QT_UI/common.py b/usr/share/edamame/UI/QT_UI/common.py new file mode 100755 index 0000000..fefe2bb --- /dev/null +++ b/usr/share/edamame/UI/QT_UI/common.py @@ -0,0 +1,107 @@ +#!shebang +# -*- coding: utf-8 -*- +# +# common.py +# +# Copyright 2024 Thomas Castleman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +"""Common functions and other data for edamame""" +import sys +import os + + +def unique(starting_list): + """Function to get a list down to only unique elements""" + # initialize a null list + # unique_list = [] + # traverse for all elements + # for each in starting_list: + # check if exists in unique_list or not + # if each not in unique_list: + # unique_list.append(each) + # return unique_list + return list(set(starting_list)) + + +def eprint(*args, **kwargs): + """Make it easier for us to print to stderr""" + print(*args, file=sys.stderr, **kwargs) + + +def real_number(num): + """Take an int or float and return an int that is 0 or higher + + This DOES NOT return absolute value. Any negative numbers will return 0. + Passing in anything other than an int or float will raise a TypeError + Valid floats that are passed are truncated, not rounded. + """ + if not isinstance(num, (int, float)): + raise TypeError("Not a valid int or float") + if num >= 0: + return int(num) + return 0 + + +def recursive_mkdir(path): + """ Recursively make directories down a file path + + This function is functionally equivallent to: `mkdir -p {path}' + """ + path = path.split("/") + for each in enumerate(path): + dir = "/".join(path[:each[0] + 1]) + # prevent calling mkdir() on an empty string + if dir != "": + try: + os.mkdir(dir) + except FileExistsError: + pass + +def item_in_list(item, array): + """Check if an item is in a list. This is supposed to be faster than: + + 'item' in 'array' + + This only speeds things up in situations with more than about 10 items in a list + """ + new_arr = set(array) + for each in array: + if item == each: + return True + return False + + +def determine_toolkit(): + """Determine System UI toolkit""" + UI_by_DE = { + "gnome": "GTK", + "xfce": "GTK", + "lxde": "GTK", + "mate": "GTK", + "unity": "GTK", + "cinnamon": "GTK", + "pantheon": "GTK", + "kde": "Qt", + "lxqt": "Qt", + "lomiri": "Qt", + "dde": "Qt", + "deepin": "Qt" + + } + return UI_by_DE[os.environ["XDG_CURRENT_DESKTOP"].lower()] diff --git a/usr/share/edamame/UI/Qt_UI/confirm.py b/usr/share/edamame/UI/QT_UI/confirm.py similarity index 99% rename from usr/share/edamame/UI/Qt_UI/confirm.py rename to usr/share/edamame/UI/QT_UI/confirm.py index 358fecf..7d33623 100755 --- a/usr/share/edamame/UI/Qt_UI/confirm.py +++ b/usr/share/edamame/UI/QT_UI/confirm.py @@ -27,7 +27,7 @@ import json from qtpy import QtGui, QtWidgets, QtCore try: - import UI.Qt_UI.qt_common as QCommon + import UI.QT_UI.qt_common as QCommon except ImportError: import qt_common as QCommon import auto_partitioner as ap diff --git a/usr/share/edamame/UI/Qt_UI/error.py b/usr/share/edamame/UI/QT_UI/error.py similarity index 96% rename from usr/share/edamame/UI/Qt_UI/error.py rename to usr/share/edamame/UI/QT_UI/error.py index fe82ced..8773f13 100755 --- a/usr/share/edamame/UI/Qt_UI/error.py +++ b/usr/share/edamame/UI/QT_UI/error.py @@ -25,14 +25,14 @@ from sys import argv from qtpy import QtGui, QtWidgets, QtCore try: - from UI.Qt_UI import report + from UI.QT_UI import report except ImportError: try: - from Qt_UI import report + from QT_UI import report except ImportError: import report try: - import UI.Qt_UI.qt_common as QCommon + import UI.QT_UI.qt_common as QCommon except ImportError: import qt_common as QCommon diff --git a/usr/share/edamame/UI/Qt_UI/main.py b/usr/share/edamame/UI/QT_UI/main.py similarity index 99% rename from usr/share/edamame/UI/Qt_UI/main.py rename to usr/share/edamame/UI/QT_UI/main.py index 62580f1..eb12dac 100755 --- a/usr/share/edamame/UI/Qt_UI/main.py +++ b/usr/share/edamame/UI/QT_UI/main.py @@ -34,7 +34,7 @@ import common import auto_partitioner as ap try: - import UI.Qt_UI.qt_common as QCommon + import UI.QT_UI.qt_common as QCommon except ImportError: import qt_common as QCommon diff --git a/usr/share/edamame/UI/Qt_UI/progress.py b/usr/share/edamame/UI/QT_UI/progress.py similarity index 99% rename from usr/share/edamame/UI/Qt_UI/progress.py rename to usr/share/edamame/UI/QT_UI/progress.py index 0de1dc1..282c6fe 100755 --- a/usr/share/edamame/UI/Qt_UI/progress.py +++ b/usr/share/edamame/UI/QT_UI/progress.py @@ -28,7 +28,7 @@ from os import remove from qtpy import QtCore, QtWidgets, QtGui try: - import UI.Qt_UI.qt_common as QCommon + import UI.QT_UI.qt_common as QCommon except ImportError: import qt_common as QCommon diff --git a/usr/share/edamame/UI/Qt_UI/qt_common.py b/usr/share/edamame/UI/QT_UI/qt_common.py similarity index 100% rename from usr/share/edamame/UI/Qt_UI/qt_common.py rename to usr/share/edamame/UI/QT_UI/qt_common.py diff --git a/usr/share/edamame/UI/Qt_UI/report.py b/usr/share/edamame/UI/QT_UI/report.py similarity index 100% rename from usr/share/edamame/UI/Qt_UI/report.py rename to usr/share/edamame/UI/QT_UI/report.py diff --git a/usr/share/edamame/UI/Qt_UI/success.py b/usr/share/edamame/UI/QT_UI/success.py similarity index 99% rename from usr/share/edamame/UI/Qt_UI/success.py rename to usr/share/edamame/UI/QT_UI/success.py index b6f650f..327c896 100755 --- a/usr/share/edamame/UI/Qt_UI/success.py +++ b/usr/share/edamame/UI/QT_UI/success.py @@ -33,15 +33,15 @@ import urllib.parse as urlp from qtpy import QtGui, QtWidgets, QtCore try: - from UI.Qt_UI import report + from UI.QT_UI import report except ImportError: try: - from Qt_UI import report + from QT_UI import report except ImportError: import report import common try: - import UI.Qt_UI.qt_common as QCommon + import UI.QT_UI.qt_common as QCommon except ImportError: import qt_common as QCommon diff --git a/usr/share/edamame/UI/Qt_UI/auto_partitioner.py b/usr/share/edamame/UI/Qt_UI/auto_partitioner.py deleted file mode 120000 index 957253b..0000000 --- a/usr/share/edamame/UI/Qt_UI/auto_partitioner.py +++ /dev/null @@ -1 +0,0 @@ -../../auto_partitioner.py \ No newline at end of file diff --git a/usr/share/edamame/UI/Qt_UI/common.py b/usr/share/edamame/UI/Qt_UI/common.py deleted file mode 120000 index 349c5f0..0000000 --- a/usr/share/edamame/UI/Qt_UI/common.py +++ /dev/null @@ -1 +0,0 @@ -../../common.py \ No newline at end of file diff --git a/usr/share/edamame/UI/__init__.py b/usr/share/edamame/UI/__init__.py index e1de2e0..0dd4478 100755 --- a/usr/share/edamame/UI/__init__.py +++ b/usr/share/edamame/UI/__init__.py @@ -28,6 +28,7 @@ def load_UI(ui_type: str) -> bool: """Load the specified UI type""" - if os.path.exists(f"UI/{ui_type}_UI"): - return importlib.import_module(f"UI.{ui_type}_UI") - raise ImportError(f"Module {ui_type}_UI is not present!") + ui = ui_type.upper() + if os.path.exists(f"UI/{ui}_UI"): + return importlib.import_module(f"UI.{ui}_UI") + raise ImportError(f"Module {ui}_UI is not present!") From ece1d8fbb42549efe388d35df78f5da613ba8144 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sat, 16 Nov 2024 09:47:24 -0500 Subject: [PATCH 17/27] Fix Qt install path --- DEBIAN/edamame-qt.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEBIAN/edamame-qt.install b/DEBIAN/edamame-qt.install index 6efad7c..ebbdd45 100644 --- a/DEBIAN/edamame-qt.install +++ b/DEBIAN/edamame-qt.install @@ -1 +1 @@ -usr/share/edamame/UI/Qt_UI/* +usr/share/edamame/UI/QT_UI/* From 56abf6f3eb595f52f8301bc1db679aab61b4b13d Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sat, 16 Nov 2024 14:55:48 -0500 Subject: [PATCH 18/27] Ensure success and progress Windows use chosen GUI --- DEBIAN/edamame-common.control | 2 +- usr/bin/edamame.cxx | 2 +- usr/share/edamame/engine.py | 11 ++++++++--- usr/share/edamame/progress.py | 9 ++++++++- usr/share/edamame/success.py | 10 +++++++++- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control index bd86e9e..86fa9f8 100644 --- a/DEBIAN/edamame-common.control +++ b/DEBIAN/edamame-common.control @@ -1,5 +1,5 @@ Package: edamame-common -Version: 2.9.5 +Version: 2.9.6 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 6cc305b..87c5344 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -36,7 +36,7 @@ using namespace std; -str VERSION = "2.9.5"; +str VERSION = "2.9.6"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index a397620..b392b1e 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -206,15 +206,20 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N if INSTALL: try: # Run the progress bar in the background - process = subprocess.Popen("/usr/share/edamame/progress.py") + command = ["/usr/share/edamame/progress.py"] + if "--gui=" in sys.argv[1]: + command.append(sys.argv[1]) + process = subprocess.Popen(command) pid = process.pid SETTINGS["INTERNET"] = check_internet.has_internet() installer.install(SETTINGS, CONFIG["local_repo"]) shutil.rmtree("/mnt/repo") common.eprint(f" ### {sys.argv[0]} CLOSED ### ") copy_log_to_disk() - subprocess.Popen(["su", "live", "-c", - f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"]) + command = ["su", "live", "-c", f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"] + if "--gui=" in sys.argv[1]: + command.append(sys.argv[1]) + subprocess.Popen(command) os.kill(pid, 15) except Exception as error: os.kill(pid, 15) diff --git a/usr/share/edamame/progress.py b/usr/share/edamame/progress.py index 7c85eac..14f8a22 100755 --- a/usr/share/edamame/progress.py +++ b/usr/share/edamame/progress.py @@ -22,5 +22,12 @@ # # import UI -UI = UI.load_UI("GTK") +import sys + +if "--gui=" in sys.argv[1]: + gui = sys.argv[1].split("=")[-1] + UI = UI.load_UI(gui) +else: + UI = UI.load_UI("GTK") + UI.progress.show_progress() diff --git a/usr/share/edamame/success.py b/usr/share/edamame/success.py index dab61aa..32502ea 100755 --- a/usr/share/edamame/success.py +++ b/usr/share/edamame/success.py @@ -24,7 +24,15 @@ from json import loads from sys import argv import UI -UI = UI.load_UI("GTK") + +for each in argv: + if "--gui=" in each: + gui = each.split("=")[-1] + UI = UI.load_UI(gui) + break + +if "load_UI" in dir(UI): + UI = UI.load_UI("GTK") SETTINGS = loads(argv[1]) UI.success.show_success(SETTINGS) From 1ec6b07416da98869a6390893086d2dd436111d7 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sat, 16 Nov 2024 15:25:35 -0500 Subject: [PATCH 19/27] Fix error in Qt GUI --- DEBIAN/edamame-qt.control | 2 +- usr/share/edamame/UI/QT_UI/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEBIAN/edamame-qt.control b/DEBIAN/edamame-qt.control index 7a1fa46..99d8761 100644 --- a/DEBIAN/edamame-qt.control +++ b/DEBIAN/edamame-qt.control @@ -1,5 +1,5 @@ Package: edamame-qt -Version: 2.9.4 +Version: 2.9.5 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/share/edamame/UI/QT_UI/main.py b/usr/share/edamame/UI/QT_UI/main.py index eb12dac..c77aa8e 100755 --- a/usr/share/edamame/UI/QT_UI/main.py +++ b/usr/share/edamame/UI/QT_UI/main.py @@ -1092,7 +1092,7 @@ def confirm_auto_part(self, button): self.grid.addWidget(label, 1, 1, 1, 3) else: - self.data["ROOT"] = self.disks.currentText() + self.data["ROOT"] = self.disks.currentText().split()[0] global PART_COMPLETION PART_COMPLETION = "COMPLETED" self.main_menu("clicked") From 05798bbfe6ed059f78b0f4632a2cdbbb4955b09a Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:20:18 -0500 Subject: [PATCH 20/27] Fix success window, bug fix for progress window --- DEBIAN/edamame-common.control | 2 +- DEBIAN/edamame-qt.control | 2 +- usr/bin/edamame.cxx | 2 +- usr/share/edamame/UI/QT_UI/progress.py | 1 + usr/share/edamame/engine.py | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control index 86fa9f8..5a3e202 100644 --- a/DEBIAN/edamame-common.control +++ b/DEBIAN/edamame-common.control @@ -1,5 +1,5 @@ Package: edamame-common -Version: 2.9.6 +Version: 2.9.7 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/DEBIAN/edamame-qt.control b/DEBIAN/edamame-qt.control index 99d8761..d387e7c 100644 --- a/DEBIAN/edamame-qt.control +++ b/DEBIAN/edamame-qt.control @@ -1,5 +1,5 @@ Package: edamame-qt -Version: 2.9.5 +Version: 2.9.6 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 87c5344..083e91e 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -36,7 +36,7 @@ using namespace std; -str VERSION = "2.9.6"; +str VERSION = "2.9.7"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; diff --git a/usr/share/edamame/UI/QT_UI/progress.py b/usr/share/edamame/UI/QT_UI/progress.py index 282c6fe..bc0c120 100755 --- a/usr/share/edamame/UI/QT_UI/progress.py +++ b/usr/share/edamame/UI/QT_UI/progress.py @@ -71,6 +71,7 @@ def __init__(self, app, distro="Linux"): self.file_contents.setReadOnly(True) self.file_contents.setCursorWidth(0) self.file_contents.setCurrentFont(QtGui.QFont("Ubuntu Mono")) + self.file_contents.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) # self.text = self._set_default_margins(self.text) self.grid.addWidget(self.file_contents, 5, 1, 1, 1) diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index b392b1e..a2aaa5f 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -218,7 +218,7 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N copy_log_to_disk() command = ["su", "live", "-c", f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"] if "--gui=" in sys.argv[1]: - command.append(sys.argv[1]) + command[-1] = command[-1] + " " + sys.argv[1] subprocess.Popen(command) os.kill(pid, 15) except Exception as error: From f14e491af088d24b34eef62cea02029539480237 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:23:50 -0500 Subject: [PATCH 21/27] Make sure all builds end up in build/ --- build-common.sh | 4 +++- build-gtk.sh | 3 +++ build-qt.sh | 3 +++ build.sh | 2 -- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/build-common.sh b/build-common.sh index f25faa1..a3d8255 100755 --- a/build-common.sh +++ b/build-common.sh @@ -140,5 +140,7 @@ mv "$FOLDER/DEBIAN/edamame-common.control" "$FOLDER/DEBIAN/control" # mv "$FOLDER/DEBIAN/edamame-common.install" "$FOLDER/DEBIAN/install" dpkg-deb --build "$FOLDER" rm -rfv "$FOLDER" - +cd "$base" +mkdir build +mv -v ../edamame-common*.deb ./build/ echo "$PAK Version: $VERSION built!" diff --git a/build-gtk.sh b/build-gtk.sh index cfd2df3..bceafff 100755 --- a/build-gtk.sh +++ b/build-gtk.sh @@ -68,4 +68,7 @@ cd "$base" for each in $files_to_edit; do sed -i "s:$shebang:\#\!shebang:" $each done +cd "$base" +mkdir build +mv -v ../edamame-gtk*.deb ./build/ echo "$PAK Version: $VERSION built!" diff --git a/build-qt.sh b/build-qt.sh index 0db6b58..3ad9d89 100755 --- a/build-qt.sh +++ b/build-qt.sh @@ -68,4 +68,7 @@ cd "$base" for each in $files_to_edit; do sed -i "s:$shebang:\#\!shebang:" $each done +cd "$base" +mkdir build +mv -v ../edamame-qt*.deb ./build/ echo "$PAK Version: $VERSION built!" diff --git a/build.sh b/build.sh index 3171c4b..d6079e3 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,5 @@ echo "Building all Edamame packages!" ./build-common.sh "$1" ./build-qt.sh ./build-gtk.sh -mkdir build -cp ../edamame*.deb build/ echo "All Edamame packages build! Please check \`build' for build artifacts and distributables." From ebd7d46f9c6e2c327a5ffb9defcfddde56e88df2 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:37:51 -0500 Subject: [PATCH 22/27] Bug fix for Qt UI --- DEBIAN/edamame-qt.control | 2 +- usr/share/edamame/UI/QT_UI/progress.py | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/DEBIAN/edamame-qt.control b/DEBIAN/edamame-qt.control index d387e7c..a560a42 100644 --- a/DEBIAN/edamame-qt.control +++ b/DEBIAN/edamame-qt.control @@ -1,5 +1,5 @@ Package: edamame-qt -Version: 2.9.6 +Version: 2.9.7 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/share/edamame/UI/QT_UI/progress.py b/usr/share/edamame/UI/QT_UI/progress.py index bc0c120..f4f3eb4 100755 --- a/usr/share/edamame/UI/QT_UI/progress.py +++ b/usr/share/edamame/UI/QT_UI/progress.py @@ -94,15 +94,12 @@ def read_file(self): try: with open("/tmp/edamame.log", "r") as read_file: text = read_file.read() - if len(text.split("\n")) > 10: + if len(text.split("\n")) > 9: text = text.split("\n") - text = text[-9:] + text = text[-8:] for each in enumerate(text): - if len(text[each[0]]) > 90: - text[each[0]] = text[each[0]][:90] - elif len(text[each[0]]) < 90: - multiplyer = 90 - len(text[each[0]]) - text[each[0]] = text[each[0]] + (" " * multiplyer) + if len(each[1]) > 90: + text[each[0]] = each[1][:90] text = "\n".join(text) self.file_contents.setText(text) except FileNotFoundError: From cc40efecb27d20e4e00ceaf417cd70ccd903a270 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:39:36 -0500 Subject: [PATCH 23/27] Prevent line wrapping in Qt UI progress window --- usr/share/edamame/UI/QT_UI/progress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usr/share/edamame/UI/QT_UI/progress.py b/usr/share/edamame/UI/QT_UI/progress.py index f4f3eb4..c1e7730 100755 --- a/usr/share/edamame/UI/QT_UI/progress.py +++ b/usr/share/edamame/UI/QT_UI/progress.py @@ -98,8 +98,8 @@ def read_file(self): text = text.split("\n") text = text[-8:] for each in enumerate(text): - if len(each[1]) > 90: - text[each[0]] = each[1][:90] + if len(each[1]) > 80: + text[each[0]] = each[1][:80] text = "\n".join(text) self.file_contents.setText(text) except FileNotFoundError: From ffc99e82c1175068b9d5e669ea38e2e2d4689857 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:46:03 -0500 Subject: [PATCH 24/27] Fix for success UI Apparently, we need to be in a certain folder to run the success UI now --- DEBIAN/edamame-common.control | 2 +- usr/share/edamame/engine.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control index 5a3e202..5a51947 100644 --- a/DEBIAN/edamame-common.control +++ b/DEBIAN/edamame-common.control @@ -1,5 +1,5 @@ Package: edamame-common -Version: 2.9.7 +Version: 2.9.8 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index a2aaa5f..fae100b 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -206,6 +206,7 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N if INSTALL: try: # Run the progress bar in the background + cwd = os.getcwd() command = ["/usr/share/edamame/progress.py"] if "--gui=" in sys.argv[1]: command.append(sys.argv[1]) @@ -219,6 +220,8 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N command = ["su", "live", "-c", f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"] if "--gui=" in sys.argv[1]: command[-1] = command[-1] + " " + sys.argv[1] + # we need to be in a certain directory when we run the success window code + os.chdir(cwd) subprocess.Popen(command) os.kill(pid, 15) except Exception as error: From bc0bffc3f8742329954ebc672f1b4c662a038645 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 00:50:23 -0500 Subject: [PATCH 25/27] missed version bump --- usr/bin/edamame.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/edamame.cxx b/usr/bin/edamame.cxx index 083e91e..5e7c636 100644 --- a/usr/bin/edamame.cxx +++ b/usr/bin/edamame.cxx @@ -36,7 +36,7 @@ using namespace std; -str VERSION = "2.9.7"; +str VERSION = "2.9.8"; str R = "\033[0;31m"; str G = "\033[0;32m"; str Y = "\033[1;33m"; From 1931ef559add9ad63eacb15de9762741d5b1b6e0 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 15:05:46 -0500 Subject: [PATCH 26/27] PEP8 improvements --- usr/share/edamame/engine.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index fae100b..dc9d84d 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -44,12 +44,11 @@ common.eprint(f" ### {sys.argv[0]} STARTED ### ") - def copy_log_to_disk() -> None: """Copy Installation Log to installation location""" try: shutil.copyfile("/tmp/edamame.log", - "/mnt/var/log/edamame.log") + "/mnt/var/log/edamame.log") except FileNotFoundError: common.eprint(" ### Log Not Found. Testing? ### ") with open("/tmp/edamame.log", "w+") as log: @@ -57,13 +56,15 @@ def copy_log_to_disk() -> None: This is a stand-in file. """) shutil.copyfile("/tmp/edamame.log", - "/mnt/var/log/edamame.log") + "/mnt/var/log/edamame.log") + with open("/etc/edamame/settings.json") as config_file: CONFIG = json.loads(config_file.read()) -def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> None: +def shutdown(boot_time: bool, immersion_obj: dec.Immersion, + exit_code: int) -> None: if boot_time: immersion_obj.disable() sys.exit(exit_code) @@ -179,7 +180,7 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N net_settings = os.listdir(work_dir + "/settings/network-settings") if len(net_settings) > 0: shutil.copytree(net_settings + "/settings/network-settings", - "/etc/NetworkManager/system-connections") + "/etc/NetworkManager/system-connections") common.eprint("\t###\tNOTE: NETWORK SETTINGS COPIED TO LIVE SYSTEM\t###\t") except FileNotFoundError: pass @@ -187,13 +188,15 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N # Parse out just the data. Ignore everything else. SETTINGS = SETTINGS["DATA"] if "OEM" in SETTINGS.values(): - # This is an OEM installation. Parts will be skipped now and handled later. + # This is an OEM installation. Parts will be skipped + # now and handled later. # Other parts will be automated additional_settings = oem.pre_install.show_main() for each in additional_settings: SETTINGS[each] = additional_settings[each] if "COMPAT_MODE" not in SETTINGS: - # this is an old quick install file. It has likely worked for the user before. + # this is an old quick install file. It has likely worked + # for the user before. # honor the file, but offer to update it # TODO: Add offer to update Quick Install File SETTINGS["COMPAT_MODE"] = False @@ -217,10 +220,12 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, exit_code: int) -> N shutil.rmtree("/mnt/repo") common.eprint(f" ### {sys.argv[0]} CLOSED ### ") copy_log_to_disk() - command = ["su", "live", "-c", f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"] + command = ["su", "live", "-c", + f"/usr/share/edamame/success.py \'{json.dumps(SETTINGS)}\'"] if "--gui=" in sys.argv[1]: command[-1] = command[-1] + " " + sys.argv[1] - # we need to be in a certain directory when we run the success window code + # we need to be in a certain directory when we run the + # success window code os.chdir(cwd) subprocess.Popen(command) os.kill(pid, 15) From 30aafe9c5bd175180d45987c764aa621770def58 Mon Sep 17 00:00:00 2001 From: Thomas Castleman Date: Sun, 17 Nov 2024 15:06:25 -0500 Subject: [PATCH 27/27] Fix occasional bugs when running Success & Progress Windows --- DEBIAN/edamame-common.control | 4 ++-- usr/share/edamame/engine.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DEBIAN/edamame-common.control b/DEBIAN/edamame-common.control index 5a51947..da6adc9 100644 --- a/DEBIAN/edamame-common.control +++ b/DEBIAN/edamame-common.control @@ -1,5 +1,5 @@ Package: edamame-common -Version: 2.9.8 +Version: 2.9.9 Maintainer: Thomas Castleman Homepage: https://github.com/drauger-os-development/edamame Section: admin @@ -10,4 +10,4 @@ Depends: arch-install-scripts, , bash, coreutils (>=8.28-1ubun Description: Edamame System Installation utility - Common Files Edamame System Installation utility for Debian-based Operating Systems. . - This is the common core files for Edamame. + These are the common core files for Edamame. diff --git a/usr/share/edamame/engine.py b/usr/share/edamame/engine.py index dc9d84d..348ae0a 100755 --- a/usr/share/edamame/engine.py +++ b/usr/share/edamame/engine.py @@ -209,7 +209,10 @@ def shutdown(boot_time: bool, immersion_obj: dec.Immersion, if INSTALL: try: # Run the progress bar in the background - cwd = os.getcwd() + # we need to be in a certain directory when we run the + # progress window code + cwd = "/".join(sys.argv[0].split("/")[:-1]) + os.chdir(cwd) command = ["/usr/share/edamame/progress.py"] if "--gui=" in sys.argv[1]: command.append(sys.argv[1])