From 3845ba4135c7da62ea82f9a6a38a749cb6948bde Mon Sep 17 00:00:00 2001 From: "Eshan Roy (Eshanized)" Date: Fri, 19 Apr 2024 09:03:31 +0530 Subject: [PATCH] =?UTF-8?q?=E2=8F=B3=20@eshanized=20updated=20the=20reposi?= =?UTF-8?q?tory!!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usr/share/blackbox/Functions.py | 2671 ----------------- .../blackbox/Functions_Ref_DO_NOT_MODIFY.py | 523 ---- usr/share/blackbox/Package.py | 19 - usr/share/blackbox/Settings.py | 132 - usr/share/blackbox/blackbox.css | 61 - usr/share/blackbox/blackbox.py | 1290 -------- usr/share/blackbox/default/blackbox.yml | 11 - usr/share/blackbox/images/blackbox.png | Bin 12126 -> 0 bytes .../images/snigdhaos-blackbox copy.png | Bin 12126 -> 0 bytes .../blackbox/images/snigdhaos-splash.png | Bin 62606 -> 0 bytes .../scripts/get-the-keys-and-repos.sh | 34 - usr/share/blackbox/ui/AboutDialog.py | 192 -- usr/share/blackbox/ui/AppFrameGUI.py | 523 ---- usr/share/blackbox/ui/GUI.py | 726 ----- usr/share/blackbox/ui/ISOPackagesWindow.py | 429 --- usr/share/blackbox/ui/MessageDialog.py | 118 - usr/share/blackbox/ui/PackageListDialog.py | 283 -- usr/share/blackbox/ui/PackageSearchWindow.py | 543 ---- usr/share/blackbox/ui/PackagesImportDialog.py | 228 -- usr/share/blackbox/ui/PacmanLogWindow.py | 71 - usr/share/blackbox/ui/ProgressBarWindow.py | 87 - usr/share/blackbox/ui/ProgressDialog.py | 400 --- usr/share/blackbox/ui/SplashScreen.py | 30 - .../blackbox/yaml/netinstall-fuzzer.yaml | 227 -- .../blackbox/yaml/netinstall-scanner.yaml | 664 ---- .../blackbox/yaml/netinstall-webapp.yaml | 444 --- 26 files changed, 9706 deletions(-) delete mode 100644 usr/share/blackbox/Functions.py delete mode 100644 usr/share/blackbox/Functions_Ref_DO_NOT_MODIFY.py delete mode 100644 usr/share/blackbox/Package.py delete mode 100644 usr/share/blackbox/Settings.py delete mode 100644 usr/share/blackbox/blackbox.css delete mode 100644 usr/share/blackbox/blackbox.py delete mode 100644 usr/share/blackbox/default/blackbox.yml delete mode 100644 usr/share/blackbox/images/blackbox.png delete mode 100644 usr/share/blackbox/images/snigdhaos-blackbox copy.png delete mode 100644 usr/share/blackbox/images/snigdhaos-splash.png delete mode 100644 usr/share/blackbox/scripts/get-the-keys-and-repos.sh delete mode 100644 usr/share/blackbox/ui/AboutDialog.py delete mode 100644 usr/share/blackbox/ui/AppFrameGUI.py delete mode 100644 usr/share/blackbox/ui/GUI.py delete mode 100644 usr/share/blackbox/ui/ISOPackagesWindow.py delete mode 100644 usr/share/blackbox/ui/MessageDialog.py delete mode 100644 usr/share/blackbox/ui/PackageListDialog.py delete mode 100644 usr/share/blackbox/ui/PackageSearchWindow.py delete mode 100644 usr/share/blackbox/ui/PackagesImportDialog.py delete mode 100644 usr/share/blackbox/ui/PacmanLogWindow.py delete mode 100644 usr/share/blackbox/ui/ProgressBarWindow.py delete mode 100644 usr/share/blackbox/ui/ProgressDialog.py delete mode 100644 usr/share/blackbox/ui/SplashScreen.py delete mode 100644 usr/share/blackbox/yaml/netinstall-fuzzer.yaml delete mode 100644 usr/share/blackbox/yaml/netinstall-scanner.yaml delete mode 100644 usr/share/blackbox/yaml/netinstall-webapp.yaml diff --git a/usr/share/blackbox/Functions.py b/usr/share/blackbox/Functions.py deleted file mode 100644 index 6570b06..0000000 --- a/usr/share/blackbox/Functions.py +++ /dev/null @@ -1,2671 +0,0 @@ -# ================================================================= -# = Author: Cameron Percival = -# ================================================================= - -import os -import sys -import psutil -import time -import datetime -from datetime import datetime, timedelta -import subprocess -import threading -import gi -import logging -from logging.handlers import TimedRotatingFileHandler -import shutil -from threading import Thread -from Package import Package -from Settings import Settings -from ui.MessageDialog import MessageDialog -from distro import id -from os import makedirs -import Functions as fn - -gi.require_version("Gtk", "3.0") -from gi.repository import GLib, Gtk - -# ===================================================== -# Base Directory -# ===================================================== - -base_dir = os.path.dirname(os.path.realpath(__file__)) - -# ===================================================== -# Global Variables -# ===================================================== -sudo_username = os.getlogin() -home = "/home/" + str(sudo_username) -path_dir_cache = base_dir + "/cache/" -packages = [] -distr = id() -blackbox_lockfile = "/tmp/blackbox.lock" -blackbox_pidfile = "/tmp/blackbox.pid" -# 10m timeout -process_timeout = 600 - -snigdhaos_mirrorlist = "/etc/pacman.d/snigdhaos-mirrorlist" -pacman_conf = "/etc/pacman.conf" -pacman_conf_backup = "/etc/pacman.conf.bak" -pacman_logfile = "/var/log/pacman.log" -pacman_lockfile = "/var/lib/pacman/db.lck" -pacman_cache_dir = "/var/cache/pacman/pkg/" - -snigdhaos_core = [ - "[snigdhaos-core]", - "SigLevel = PackageRequired DatabaseNever", - "Include = /etc/pacman.d/snigdhaos-mirrorlist", -] -snigdhaos_extra = [ - "[snigdhaos-extra]", - "SigLevel = PackageRequired DatabaseNever", - "Include = /etc/pacman.d/snigdhaos-mirrorlist", -] - -log_dir = "/var/log/blackbox/" -config_dir = "%s/.config/blackbox" % home -config_file = "%s/blackbox.yaml" % config_dir - -event_log_file = "%s/event.log" % log_dir -export_dir = "%s/blackbox-exports" % home - - -# ===================================================== -# PERMISSIONS -# ===================================================== - - -def permissions(dst): - try: - groups = subprocess.run( - ["sh", "-c", "id " + sudo_username], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - for x in groups.stdout.decode().split(" "): - if "gid" in x: - g = x.split("(")[1] - group = g.replace(")", "").strip() - subprocess.call(["chown", "-R", sudo_username + ":" + group, dst], shell=False) - - except Exception as e: - logger.error(e) - - -# Create log, export, conf directory -try: - if not os.path.exists(log_dir): - makedirs(log_dir) - - if not os.path.exists(export_dir): - makedirs(export_dir) - - if not os.path.exists(config_dir): - makedirs(config_dir) - - permissions(export_dir) - permissions(config_dir) - - print("[INFO] Log directory = %s" % log_dir) - print("[INFO] Export directory = %s" % export_dir) - print("[INFO] Config directory = %s" % config_dir) - - -except os.error as oe: - print("[ERROR] Exception in setup log/export directory: %s" % oe) - sys.exit(1) - -# read in conf file from $HOME/.config/blackbox/blackbox.yaml -# initialize logger -try: - settings = Settings(False, False) - settings_config = settings.read_config_file() - - logger = logging.getLogger("logger") - - # create console handler and set level to debug - ch = logging.StreamHandler() - - # rotate the events log every Friday - tfh = TimedRotatingFileHandler( - event_log_file, encoding="utf-8", delay=False, when="W4" - ) - - if settings_config: - debug_logging_enabled = None - debug_logging_enabled = settings_config["Debug Logging"] - - if debug_logging_enabled is not None and debug_logging_enabled is True: - logger.setLevel(logging.DEBUG) - ch.setLevel(logging.DEBUG) - tfh.setLevel(level=logging.DEBUG) - - else: - logger.setLevel(logging.INFO) - ch.setLevel(logging.INFO) - tfh.setLevel(level=logging.INFO) - else: - logger.setLevel(logging.INFO) - ch.setLevel(logging.INFO) - tfh.setLevel(level=logging.INFO) - - # create formatter - formatter = logging.Formatter( - "%(asctime)s:%(levelname)s > %(message)s", "%Y-%m-%d %H:%M:%S" - ) - # add formatter to ch - ch.setFormatter(formatter) - tfh.setFormatter(formatter) - - # add ch to logger - logger.addHandler(ch) - - # add fh to logger - logger.addHandler(tfh) - -except Exception as e: - print("[ERROR] Failed to setup logger, exception: %s" % e) - - -# on app close create file of installed packages -def _on_close_create_packages_file(): - try: - logger.info("App closing saving currently installed packages to file") - packages_file = "%s-packages.txt" % datetime.now().strftime("%Y-%m-%d-%H-%M-%S") - logger.info("Saving in %s%s" % (log_dir, packages_file)) - cmd = ["pacman", "-Q"] - - with subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - with open("%s/%s" % (log_dir, packages_file), "w") as f: - for line in process.stdout: - f.write("%s" % line) - except Exception as e: - logger.error("Exception in on_close_create_packages_file(): %s" % e) - - -# ===================================================== -# GLOBAL FUNCTIONS -# ===================================================== - - -def _get_position(lists, value): - data = [string for string in lists if value in string] - position = lists.index(data[0]) - return position - - -def is_file_stale(filepath, stale_days, stale_hours, stale_minutes): - # first, lets obtain the datetime of the day that we determine data to be "stale" - now = datetime.now() - # For the purposes of this, we are assuming that one would have the app open longer than 5 minutes if installing. - stale_datetime = now - timedelta( - days=stale_days, hours=stale_hours, minutes=stale_minutes - ) - # Check to see if the file path is in existence. - if os.path.exists(filepath): - # if the file exists, when was it made? - file_created = datetime.fromtimestamp(os.path.getctime(filepath)) - # file is older than the time delta identified above - if file_created < stale_datetime: - return True - return False - - -# ===================================================== -# PACMAN SYNC PACKAGE DB -# ===================================================== -def sync_package_db(): - try: - sync_str = ["pacman", "-Sy"] - logger.info("Synchronizing pacman package databases") - process_sync = subprocess.run( - sync_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - if process_sync.returncode == 0: - return None - else: - if process_sync.stdout: - out = str(process_sync.stdout.decode("utf-8")) - logger.error(out) - - return out - - except Exception as e: - logger.error("Exception in sync_package_db(): %s" % e) - - -# ===================================================== -# PACMAN SYNC FILES DB -# ===================================================== - - -def sync_file_db(): - try: - sync_str = ["pacman", "-Fy"] - logger.info("Synchronizing pacman file database") - process_sync = subprocess.run( - sync_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - if process_sync.returncode == 0: - return None - else: - if process_sync.stdout: - out = str(process_sync.stdout.decode("utf-8")) - logger.error(out) - - return out - - except Exception as e: - logger.error("Exception in sync_file_db(): %s" % e) - - -# ===================================================== -# PACMAN INSTALL/UNINSTALL PROCESS -# ===================================================== - - -# this is run inside a separate thread -def start_subprocess(self, cmd, progress_dialog, action, pkg, widget): - try: - self.switch_package_version.set_sensitive(False) - self.switch_snigdhaos_keyring.set_sensitive(False) - self.switch_snigdhaos_mirrorlist.set_sensitive(False) - - widget.set_sensitive(False) - - # store process std out into a list, if there are errors display to user once the process completes - process_stdout_lst = [] - process_stdout_lst.append("Command = %s\n\n" % " ".join(cmd)) - - with subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - if progress_dialog is not None: - progress_dialog.pkg_dialog_closed = False - - self.in_progress = True - - if ( - progress_dialog is not None - and progress_dialog.pkg_dialog_closed is False - ): - line = ( - "Pacman is processing the %s of package %s \n\nCommand running = %s\n\n" - % (action, pkg.name, " ".join(cmd)) - ) - GLib.idle_add( - update_progress_textview, - self, - line, - progress_dialog, - priority=GLib.PRIORITY_DEFAULT, - ) - - logger.debug("Pacman is now processing the request") - - # poll for the process to complete - # read stdout as it comes in, update the progress textview - - # poll() Check if child process has terminated. - # Set and return returncode attribute. Otherwise, returns None. - - while True: - if process.poll() is not None: - break - - if ( - progress_dialog is not None - and progress_dialog.pkg_dialog_closed is False - ): - for line in process.stdout: - GLib.idle_add( - update_progress_textview, - self, - line, - progress_dialog, - priority=GLib.PRIORITY_DEFAULT, - ) - process_stdout_lst.append(line) - - time.sleep(0.3) - else: - # increase wait time to reduce cpu load, no textview updates required since dialog is closed - # since the progress dialog window is closed, capture errors and then later display it - for line in process.stdout: - process_stdout_lst.append(line) - time.sleep(1) - - returncode = None - returncode = process.poll() - - # logger.debug("Pacman process return code = %s" % returncode) - - if returncode is not None: - logger.info("Pacman process completed running = [ %s ]" % " ".join(cmd)) - - GLib.idle_add( - refresh_ui, - self, - action, - widget, - pkg, - progress_dialog, - process_stdout_lst, - priority=GLib.PRIORITY_DEFAULT, - ) - else: - # an error happened during the pacman transaction - logger.error( - "Pacman process failed when running = [ %s ]" % " ".join(cmd) - ) - - except TimeoutError as t: - logger.error("TimeoutError in %s start_subprocess(): %s" % (action, t)) - process.terminate() - if progress_dialog is not None: - progress_dialog.btn_package_progress_close.set_sensitive(True) - self.switch_package_version.set_sensitive(True) - self.switch_snigdhaos_keyring.set_sensitive(True) - self.switch_snigdhaos_mirrorlist.set_sensitive(True) - - except SystemError as s: - logger.error("SystemError in %s start_subprocess(): %s" % (action, s)) - process.terminate() - if progress_dialog is not None: - progress_dialog.btn_package_progress_close.set_sensitive(True) - self.switch_package_version.set_sensitive(True) - self.switch_snigdhaos_keyring.set_sensitive(True) - self.switch_snigdhaos_mirrorlist.set_sensitive(True) - - -# refresh ui components, once the process completes -# show notification dialog to user if errors are encountered during package install/uninstall -def refresh_ui(self, action, switch, pkg, progress_dialog, process_stdout_lst): - self.switch_package_version.set_sensitive(True) - self.switch_snigdhaos_keyring.set_sensitive(True) - self.switch_snigdhaos_mirrorlist.set_sensitive(True) - - logger.debug("Checking if package %s is installed" % pkg.name) - installed = check_package_installed(pkg.name) - - if progress_dialog is not None: - progress_dialog.btn_package_progress_close.set_sensitive(True) - - if installed is True and action == "install": - logger.debug("Toggle switch state = True") - switch.set_sensitive(True) - switch.set_state(True) - switch.set_active(True) - - if progress_dialog is not None: - if progress_dialog.pkg_dialog_closed is False: - progress_dialog.set_title("Package install for %s completed" % pkg.name) - - progress_dialog.infobar.set_name("infobar_info") - - content = progress_dialog.infobar.get_content_area() - if content is not None: - for widget in content.get_children(): - content.remove(widget) - - lbl_install = Gtk.Label(xalign=0, yalign=0) - lbl_install.set_markup("Package %s installed" % pkg.name) - - content.add(lbl_install) - - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - self.timeout_id = GLib.timeout_add( - 100, reveal_infobar, self, progress_dialog - ) - - if installed is False and action == "install": - logger.debug("Toggle switch state = False") - - if progress_dialog is not None: - # install failed/terminated - switch.set_sensitive(True) - switch.set_state(False) - switch.set_active(False) - - if progress_dialog.pkg_dialog_closed is False: - progress_dialog.set_title("Package install for %s failed" % pkg.name) - - progress_dialog.infobar.set_name("infobar_error") - - content = progress_dialog.infobar.get_content_area() - if content is not None: - for widget in content.get_children(): - content.remove(widget) - - lbl_install = Gtk.Label(xalign=0, yalign=0) - lbl_install.set_markup( - "Package %s install failed" % pkg.name - ) - - content.add(lbl_install) - - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - self.timeout_id = GLib.timeout_add( - 100, reveal_infobar, self, progress_dialog - ) - else: - logger.debug(" ".join(process_stdout_lst)) - - message_dialog = MessageDialog( - "Errors occurred during install", - "Errors occurred install for %s failed" % pkg.name, - "Pacman failed to install package %s\n" % pkg.name, - " ".join(process_stdout_lst), - "error", - True, - ) - - message_dialog.show_all() - result = message_dialog.run() - message_dialog.destroy() - elif progress_dialog is None or progress_dialog.pkg_dialog_closed is True: - # the package progress dialog has been closed, but notify user package failed to install - - if ( - "error: failed to init transaction (unable to lock database)\n" - in process_stdout_lst - ): - # at this point the package install is stuck behind another pacman transaction so put this onto the holding queue - - # logger.debug(" ".join(process_stdout_lst)) - - if progress_dialog is None: - logger.debug("Adding package to holding queue") - if self.display_package_progress is False: - inst_str = [ - "pacman", - "-S", - pkg.name, - "--needed", - "--noconfirm", - ] - self.pkg_holding_queue.put( - ( - pkg, - action, - switch, - inst_str, - progress_dialog, - ), - ) - else: - logger.debug(" ".join(process_stdout_lst)) - switch.set_sensitive(True) - switch.set_state(False) - switch.set_active(False) - - proc = fn.get_pacman_process() - - message_dialog = MessageDialog( - "Warning", - "BlackBox cannot proceed pacman lockfile found", - "Pacman cannot lock the db, a lockfile is found inside %s" - % fn.pacman_lockfile, - "Pacman is running: %s" % proc, - "warning", - False, - ) - - message_dialog.show_all() - result = message_dialog.run() - message_dialog.destroy() - - # progress dialog is closed show message dialog with error - elif "error: target not found: %s\n" % pkg.name in process_stdout_lst: - switch.set_sensitive(True) - switch.set_state(False) - switch.set_active(False) - - message_dialog = MessageDialog( - "Error", - "Pacman repository error: package '%s' was not found" % pkg.name, - "BlackBox cannot process the request", - "Are the correct pacman mirrorlists configured ?", - "error", - False, - ) - - message_dialog.show_all() - result = message_dialog.run() - message_dialog.destroy() - - else: - # logger.debug(" ".join(process_stdout_lst)) - - switch.set_sensitive(True) - switch.set_state(False) - switch.set_active(False) - - message_dialog = MessageDialog( - "Errors occurred during install", - "Errors occurred install for %s failed" % pkg.name, - "Pacman failed to install package %s\n" % pkg.name, - " ".join(process_stdout_lst), - "error", - True, - ) - - message_dialog.show_all() - result = message_dialog.run() - message_dialog.destroy() - - if installed is False and action == "uninstall": - logger.debug("Toggle switch state = False") - switch.set_sensitive(True) - switch.set_state(False) - switch.set_active(False) - - if progress_dialog is not None: - if progress_dialog.pkg_dialog_closed is False: - progress_dialog.set_title( - "Package uninstall for %s completed" % pkg.name - ) - progress_dialog.infobar.set_name("infobar_info") - content = progress_dialog.infobar.get_content_area() - if content is not None: - for widget in content.get_children(): - content.remove(widget) - - lbl_install = Gtk.Label(xalign=0, yalign=0) - lbl_install.set_markup("Package %s uninstalled" % pkg.name) - - content.add(lbl_install) - - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - self.timeout_id = GLib.timeout_add( - 100, reveal_infobar, self, progress_dialog - ) - - if installed is True and action == "uninstall": - # uninstall failed/terminated - switch.set_sensitive(True) - switch.set_state(True) - switch.set_active(True) - - if progress_dialog is not None: - if progress_dialog.pkg_dialog_closed is False: - progress_dialog.set_title("Package uninstall for %s failed" % pkg.name) - progress_dialog.infobar.set_name("infobar_error") - - content = progress_dialog.infobar.get_content_area() - if content is not None: - for widget in content.get_children(): - content.remove(widget) - - lbl_install = Gtk.Label(xalign=0, yalign=0) - lbl_install.set_markup( - "Package %s uninstall failed" % pkg.name - ) - - content.add(lbl_install) - - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - self.timeout_id = GLib.timeout_add( - 100, reveal_infobar, self, progress_dialog - ) - - elif progress_dialog is None or progress_dialog.pkg_dialog_closed is True: - # the package progress dialog has been closed, but notify user package failed to uninstall - - if ( - "error: failed to init transaction (unable to lock database)\n" - in process_stdout_lst - ): - logger.error(" ".join(process_stdout_lst)) - - else: - message_dialog = MessageDialog( - "Errors occurred during uninstall", - "Errors occurred uninstall of %s failed" % pkg.name, - "Pacman failed to uninstall package %s\n" % pkg.name, - " ".join(process_stdout_lst), - "error", - True, - ) - - message_dialog.show_all() - result = message_dialog.run() - message_dialog.destroy() - - -# update progress textview using stdout from the pacman process running - - -def update_progress_textview(self, line, progress_dialog): - if ( - progress_dialog is not None - and progress_dialog.pkg_dialog_closed is False - and self.in_progress is True - ): - buffer = progress_dialog.package_progress_textview.get_buffer() - if len(line) > 0 or buffer is None: - buffer.insert(buffer.get_end_iter(), "%s" % line, len("%s" % line)) - - text_mark_end = buffer.create_mark("\nend", buffer.get_end_iter(), False) - - # scroll to the end of the textview - progress_dialog.package_progress_textview.scroll_mark_onscreen( - text_mark_end - ) - else: - # dialog window is closed - line = None - return False - - -# ===================================================== -# APP INSTALLATION -# ===================================================== -def install(self): - pkg, action, widget, inst_str, progress_dialog = self.pkg_queue.get() - - try: - if action == "install": - # path = base_dir + "/cache/installed.lst" - logger.debug("Running inside install thread") - logger.info("Installing package %s" % pkg.name) - logger.debug(inst_str) - - # run pacman process inside a thread - - th_subprocess_install = Thread( - name="thread_subprocess", - target=start_subprocess, - args=( - self, - inst_str, - progress_dialog, - action, - pkg, - widget, - ), - daemon=True, - ) - - th_subprocess_install.start() - - logger.debug("Thread: subprocess install started") - - except Exception as e: - # deactivate switch widget, install failed - widget.set_state(False) - if progress_dialog is not None: - progress_dialog.btn_package_progress_close.set_sensitive(True) - logger.error("Exception in install(): %s" % e) - finally: - # task completed - self.pkg_queue.task_done() - - -# ===================================================== -# APP UNINSTALLATION -# ===================================================== -def uninstall(self): - pkg, action, widget, uninst_str, progress_dialog = self.pkg_queue.get() - - try: - if action == "uninstall": - # path = base_dir + "/cache/installed.lst" - logger.debug("Running inside uninstall thread") - logger.info("Uninstalling package %s" % pkg.name) - logger.debug(uninst_str) - - # run pacman process inside a thread - - th_subprocess_uninstall = Thread( - name="thread_subprocess", - target=start_subprocess, - args=( - self, - uninst_str, - progress_dialog, - action, - pkg, - widget, - ), - daemon=True, - ) - - # is there a pacman lockfile, wait for it before uninstalling - # while check_pacman_lockfile(): - # time.sleep(0.2) - - th_subprocess_uninstall.start() - - logger.debug("Thread: subprocess uninstall started") - - except Exception as e: - widget.set_state(True) - if progress_dialog is not None: - progress_dialog.btn_package_progress_close.set_sensitive(True) - logger.error("Exception in uninstall(): %s" % e) - finally: - self.pkg_queue.task_done() - - -# ===================================================== -# SEARCH INDEXING -# ===================================================== - - -# store a list of package metadata into memory for fast retrieval -def store_packages(): - path = base_dir + "/yaml/" - cache = base_dir + "/cache/yaml-packages.lst" - yaml_files = [] - packages = [] - - category_dict = {} - - try: - # get latest package version info - - package_metadata = get_all_package_info() - - # get a list of yaml files - for file in os.listdir(path): - if file.endswith(".yaml"): - yaml_files.append(file) - - if len(yaml_files) > 0: - for yaml_file in yaml_files: - cat_desc = "" - package_name = "" - package_cat = "" - - category_name = yaml_file[11:-5].strip().capitalize() - - # read contents of each yaml file - - with open(path + yaml_file, "r") as yaml: - content = yaml.readlines() - for line in content: - if line.startswith(" packages:"): - continue - elif line.startswith(" description: "): - # Set the label text for the description line - subcat_desc = ( - line.strip(" description: ") - .strip() - .strip('"') - .strip("\n") - .strip() - ) - elif line.startswith("- name:"): - # category - - subcat_name = ( - line.strip("- name: ") - .strip() - .strip('"') - .strip("\n") - .strip() - ) - elif line.startswith(" - "): - # add the package to the packages list - - package_name = line.strip(" - ").strip() - - # get the package description - # package_desc = obtain_pkg_description(package_name) - - package_version = "Unknown" - package_description = "Unknown" - - for data in package_metadata: - if data["name"] == package_name: - package_version = data["version"] - package_description = data["description"] - - break - - if package_description == "Unknown": - package_description = obtain_pkg_description(package_name) - - package = Package( - package_name, - package_description, - category_name, - subcat_name, - subcat_desc, - package_version, - ) - - packages.append(package) - - # filter the results so that each category holds a list of package - - category_name = None - packages_cat_lst = [] - for pkg in packages: - if category_name == pkg.category: - packages_cat_lst.append(pkg) - category_dict[category_name] = packages_cat_lst - elif category_name is None: - packages_cat_lst.append(pkg) - category_dict[pkg.category] = packages_cat_lst - else: - # reset packages, new category - packages_cat_lst = [] - - packages_cat_lst.append(pkg) - - category_dict[pkg.category] = packages_cat_lst - - category_name = pkg.category - - # Print dictionary for debugging - - # for key in category_dict.keys(): - # print("Category = %s" % key) - # pkg_list = category_dict[key] - # - # for pkg in pkg_list: - # print("%s" % pkg.name) - - sorted_dict = None - - sorted_dict = dict(sorted(category_dict.items())) - - if sorted_dict is None: - logger.error( - "An error occurred during sort of stored packages in store_packages()" - ) - else: - with open(cache, "w", encoding="UTF-8") as f: - for key in category_dict.keys(): - pkg_list = category_dict[key] - - for pkg in pkg_list: - f.write("%s\n" % pkg.name) - - return sorted_dict - except Exception as e: - logger.error("Exception in store_packages() : %s" % e) - sys.exit(1) - - -def get_package_description(package): - query_str = ["pacman", "-Si", package] - - try: - with subprocess.Popen( - query_str, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - while True: - if process.poll() is not None: - break - - returncode = None - returncode = process.poll() - - if returncode is not None: - for line in process.stdout: - if "Description :" in line.strip(): - return line.replace(" ", "").split("Description:")[1].strip() - - except Exception as e: - logger.error("Exception in get_package_description(): %s" % e) - - -# ===================================================== -# PACKAGE VERSIONS -# ===================================================== - - -# get live package name, version info and repo name -# used in store_packages() and get_installed_package_data() -def get_all_package_info(): - query_str = ["pacman", "-Si"] - - try: - process_pkg_query = subprocess.Popen( - query_str, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - - out, err = process_pkg_query.communicate(timeout=process_timeout) - - if process_pkg_query.returncode == 0: - if out: - package_data = [] - package_name = "Unknown" - package_version = "Unknown" - package_description = "Unknown" - package_repository = "Unknown" - - for line in out.decode("utf-8").splitlines(): - package_dict = {} - if "Name :" in line.strip(): - package_name = line.replace(" ", "").split("Name:")[1].strip() - - if "Version :" in line.strip(): - package_version = ( - line.replace(" ", "").split("Version:")[1].strip() - ) - - if "Description :" in line.strip(): - package_description = line.split("Description :")[1].strip() - - if "Repository :" in line.strip(): - package_repository = line.split("Repository :")[1].strip() - - package_dict["name"] = package_name - package_dict["version"] = package_version - package_dict["description"] = package_description - package_dict["repository"] = package_repository - - package_data.append(package_dict) - - return package_data - else: - logger.error("Failed to extract package version information.") - - except Exception as e: - logger.error("Exception in get_all_package_info() : %s" % e) - - -# get installed package version, installed date, name to be displayed inside PackageListDialog -# for export and later import -def get_installed_package_data(self): - # to capture the latest package version - latest_package_data = get_all_package_info() - - # query_str = ["pacman", "-Qi"] - # query_str = ["pacman", "-Qien"] - - try: - installed_packages_list = [] - pkg_name = None - pkg_version = None - pkg_install_date = None - pkg_installed_size = None - pkg_latest_version = None - - with subprocess.Popen( - self.pacman_export_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - for line in process.stdout: - if "Name :" in line.strip(): - pkg_name = line.replace(" ", "").split("Name:")[1].strip() - - if "Version :" in line.strip(): - pkg_version = line.replace(" ", "").split("Version:")[1].strip() - - if "Installed Size :" in line.strip(): - pkg_installed_size = line.split("Installed Size :")[1].strip() - - if "Install Date :" in line.strip(): - pkg_install_date = line.split("Install Date :")[1].strip() - - # get the latest version lookup dictionary - - found = False - pkg_latest_version = None - - for i in latest_package_data: - if i["name"] == pkg_name: - pkg_latest_version = i["version"] - break - - installed_packages_list.append( - ( - pkg_name, - pkg_version, - pkg_latest_version, - pkg_installed_size, - pkg_install_date, - ) - ) - - self.pkg_export_queue.put(installed_packages_list) - - # return installed_packages_list - - except Exception as e: - logger.error("Exception in get_installed_package_data() : %s" % e) - - -# get list of files installed by a package -def get_package_files(package_name): - try: - query_str = ["pacman", "-Fl", package_name] - process = subprocess.run( - query_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - if process.returncode == 0: - package_files = [] - for line in process.stdout.decode("utf-8").splitlines(): - package_files.append((line.split(" ")[1], None)) - - return package_files - else: - return None - except Exception as e: - logger.error("Exception in get_package_files(): %s" % e) - - -# get key package information which is to be shown inside ProgressDialog - - -def get_package_information(package_name): - logger.info("Fetching package information for %s" % package_name) - - try: - pkg_name = "Unknown" - pkg_version = "Unknown" - pkg_repository = "Unknown / pacman mirrorlist not configured" - pkg_description = "Unknown" - pkg_arch = "Unknown" - pkg_url = "Unknown" - pkg_depends_on = [] - pkg_conflicts_with = [] - pkg_download_size = "Unknown" - pkg_installed_size = "Unknown" - pkg_build_date = "Unknown" - pkg_packager = "Unknown" - package_metadata = {} - - query_local_cmd = ["pacman", "-Qi", package_name] - query_remote_cmd = ["pacman", "-Si", package_name] - - process_query_remote = subprocess.run( - query_remote_cmd, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - # added validation on process result - if process_query_remote.returncode == 0: - for line in process_query_remote.stdout.decode("utf-8").splitlines(): - if "Name :" in line.strip(): - pkg_name = line.replace(" ", "").split("Name:")[1].strip() - - if "Version :" in line.strip(): - pkg_version = line.replace(" ", "").split("Version:")[1].strip() - - if "Repository :" in line.strip(): - pkg_repository = line.split("Repository :")[1].strip() - - if "Description :" in line.strip(): - pkg_description = line.split("Description :")[1].strip() - - if "Architecture :" in line.strip(): - pkg_arch = line.split("Architecture :")[1].strip() - - if "URL :" in line.strip(): - pkg_url = line.split("URL :")[1].strip() - - if "Depends On :" in line.strip(): - if line.split("Depends On :")[1].strip() != "None": - pkg_depends_on_str = line.split("Depends On :")[1].strip() - - for pkg_dep in pkg_depends_on_str.split(" "): - pkg_depends_on.append((pkg_dep, None)) - else: - pkg_depends_on = [] - - if "Conflicts With :" in line.strip(): - if line.split("Conflicts With :")[1].strip() != "None": - pkg_conflicts_with_str = line.split("Conflicts With :")[ - 1 - ].strip() - - for pkg_con in pkg_conflicts_with_str.split(" "): - pkg_conflicts_with.append((pkg_con, None)) - else: - pkg_conflicts_with = [] - - if "Download Size :" in line.strip(): - pkg_download_size = line.split("Download Size :")[1].strip() - - if "Installed Size :" in line.strip(): - pkg_installed_size = line.split("Installed Size :")[1].strip() - - if "Build Date :" in line.strip(): - pkg_build_date = line.split("Build Date :")[1].strip() - - if "Packager :" in line.strip(): - pkg_packager = line.split("Packager :")[1].strip() - - package_metadata["name"] = pkg_name - package_metadata["version"] = pkg_version - package_metadata["repository"] = pkg_repository - package_metadata["description"] = pkg_description - package_metadata["arch"] = pkg_arch - package_metadata["url"] = pkg_url - package_metadata["depends_on"] = pkg_depends_on - package_metadata["conflicts_with"] = pkg_conflicts_with - package_metadata["download_size"] = pkg_download_size - package_metadata["installed_size"] = pkg_installed_size - package_metadata["build_date"] = pkg_build_date - package_metadata["packager"] = pkg_packager - - return package_metadata - - elif ( - "error: package '%s' was not found\n" % package_name - in process_query_remote.stdout.decode("utf-8") - ): - return "error: package '%s' was not found" % package_name - else: - process_query_local = subprocess.run( - query_local_cmd, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - # added validation on process result - if process_query_local.returncode == 0: - for line in process_query_local.stdout.decode("utf-8").splitlines(): - if "Name :" in line.strip(): - pkg_name = line.replace(" ", "").split("Name:")[1].strip() - - if "Version :" in line.strip(): - pkg_version = line.replace(" ", "").split("Version:")[1].strip() - - if "Repository :" in line.strip(): - pkg_repository = line.split("Repository :")[1].strip() - - if "Description :" in line.strip(): - pkg_description = line.split("Description :")[1].strip() - - if "Architecture :" in line.strip(): - pkg_arch = line.split("Architecture :")[1].strip() - - if "URL :" in line.strip(): - pkg_url = line.split("URL :")[1].strip() - - if "Depends On :" in line.strip(): - if line.split("Depends On :")[1].strip() != "None": - pkg_depends_on_str = line.split("Depends On :")[ - 1 - ].strip() - - for pkg_dep in pkg_depends_on_str.split(" "): - pkg_depends_on.append((pkg_dep, None)) - else: - pkg_depends_on = [] - - if "Conflicts With :" in line.strip(): - if line.split("Conflicts With :")[1].strip() != "None": - pkg_conflicts_with_str = line.split("Conflicts With :")[ - 1 - ].strip() - - for pkg_con in pkg_conflicts_with_str.split(" "): - pkg_conflicts_with.append((pkg_con, None)) - else: - pkg_conflicts_with = [] - - if "Download Size :" in line.strip(): - pkg_download_size = line.split("Download Size :")[1].strip() - - if "Installed Size :" in line.strip(): - pkg_installed_size = line.split("Installed Size :")[1].strip() - - if "Build Date :" in line.strip(): - pkg_build_date = line.split("Build Date :")[1].strip() - - if "Packager :" in line.strip(): - pkg_packager = line.split("Packager :")[1].strip() - - package_metadata["name"] = pkg_name - package_metadata["version"] = pkg_version - package_metadata["repository"] = pkg_repository - package_metadata["description"] = pkg_description - package_metadata["arch"] = pkg_arch - package_metadata["url"] = pkg_url - package_metadata["depends_on"] = pkg_depends_on - package_metadata["conflicts_with"] = pkg_conflicts_with - package_metadata["download_size"] = pkg_download_size - package_metadata["installed_size"] = pkg_installed_size - package_metadata["build_date"] = pkg_build_date - package_metadata["packager"] = pkg_packager - - return package_metadata - else: - return None - except Exception as e: - logger.error("Exception in get_package_information(): %s" % e) - - -# ===================================================== -# APP QUERY -# ===================================================== - - -def get_current_installed(): - logger.debug("Get currently installed packages") - path = base_dir + "/cache/installed.lst" - - # query_str = "pacman -Q > " + path - query_str = ["pacman", "-Q"] - - # run the query - using Popen because it actually suits this use case a bit better. - - subprocess_query = subprocess.Popen( - query_str, - shell=False, - stdout=subprocess.PIPE, - ) - - out, err = subprocess_query.communicate(timeout=process_timeout) - - # added validation on process result - if subprocess_query.returncode == 0: - file = open(path, "w") - for line in out.decode("utf-8"): - file.write(line) - file.close() - else: - logger.warning("Failed to run %s" % query_str) - - -def query_pkg(package): - try: - package = package.strip() - path = base_dir + "/cache/installed.lst" - - pacman_localdb = base_dir + "/cache/pacman-localdb" - - if os.path.exists(path): - if is_file_stale(path, 0, 0, 30): - get_current_installed() - # file does NOT exist; - else: - get_current_installed() - # then, open the resulting list in read mode - with open(path, "r") as f: - # first we need to strip the new line escape sequence to ensure we don't get incorrect outcome - pkg = package.strip("\n") - - # If the pkg name appears in the list, then it is installed - for line in f: - installed = line.split(" ") - # We only compare against the name of the package, NOT the version number. - if pkg == installed[0]: - # file.close() - return True - # We will only hit here, if the pkg does not match anything in the file. - # file.close() - - return False - except Exception as e: - logger.error("Exception in query_pkg(): %s " % e) - - -# ===================================================== -# PACKAGE DESCRIPTION CACHE AND SEARCH -# ===================================================== - - -def cache(package, path_dir_cache): - try: - # first we need to strip the new line escape sequence to ensure we don't get incorrect outcome - pkg = package.strip() - # create the query - query_str = ["pacman", "-Si", pkg, " --noconfirm"] - - # run the query - using Popen because it actually suits this use case a bit better. - - process = subprocess.Popen( - query_str, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - out, err = process.communicate() - - # validate the process result - if process.returncode == 0: - # out, err = process.communicate() - - output = out.decode("utf-8") - - if len(output) > 0: - split = output.splitlines() - - # Currently the output of the pacman command above always puts the description on the 4th line. - desc = str(split[3]) - # Ok, so this is a little fancy: there is formatting from the output which we wish to ignore (ends at 19th character) - # and there is a remenant of it as the last character - usually a single or double quotation mark, which we also need to ignore - description = desc[18:] - # writing to a caching file with filename matching the package name - filename = path_dir_cache + pkg - - file = open(filename, "w") - file.write(description) - file.close() - - return description - # There are several packages that do not return a valid process return code - # Cathing those manually via corrections folder - if process.returncode != 0: - exceptions = [ - "florence", - "mintstick-bin", - "ttf-hack", - "ttf-roboto-mono", - "aisleriot", - "mailspring", - "linux-rt", - "linux-rt-headers", - "linux-rt-lts", - "linux-rt-lts-headers", - "kodi-x11", - "kodi-addons", - "sardi-icons", - ] - if pkg in exceptions: - description = file_lookup(pkg, path_dir_cache + "corrections/") - return description - return "No Description Found" - - except Exception as e: - logger.error("Exception in cache(): %s " % e) - - -# Creating an over-load so that we can use the same function, with slightly different code to get the results we need -def cache_btn(): - # fraction = 1 / len(packages) - # Non Multithreaded version. - packages.sort() - number = 1 - for pkg in packages: - logger.debug(str(number) + "/" + str(len(packages)) + ": Caching " + pkg) - cache(pkg, path_dir_cache) - number = number + 1 - # progressbar.timeout_id = GLib.timeout_add(50, progressbar.update, fraction) - - logger.debug("Caching applications finished") - - # This will need to be coded to be running multiple processes eventually, since it will be manually invoked. - # process the file list - # for each file in the list, open the file - # process the file ignoring what is not what we need - # for each file line processed, we need to invoke the cache function that is not over-ridden. - - -def file_lookup(package, path): - # first we need to strip the new line escape sequence to ensure we don't get incorrect outcome - pkg = package.strip("\n") - output = "" - if os.path.exists(path + "corrections/" + pkg): - filename = path + "corrections/" + pkg - else: - filename = path + pkg - file = open(filename, "r") - output = file.read() - file.close() - if len(output) > 0: - return output - return "No Description Found" - - -def obtain_pkg_description(package): - # This is a pretty simple function now, decide how to get the information, then get it. - # processing variables. - output = "" - path = base_dir + "/cache/" - - # First we need to determine whether to pull from cache or pacman. - if os.path.exists(path + package.strip("\n")): - output = file_lookup(package, path) - - # file doesn't exist, so create a blank copy - else: - output = cache(package, path) - # Add the package in question to the global variable, in case recache is needed - packages.append(package) - return output - - -def restart_program(): - os.unlink("/tmp/blackbox.lock") - python = sys.executable - os.execl(python, python, *sys.argv) - - -# ===================================================== -# MONITOR PACMAN LOG FILE -# ===================================================== - - -# write lines from the pacman log onto a queue, this is called from a non-blocking thread -def add_pacmanlog_queue(self): - try: - lines = [] - with open(pacman_logfile, "r", encoding="utf-8") as f: - while True: - line = f.readline() - if line: - lines.append(line.encode("utf-8")) - self.pacmanlog_queue.put(lines) - else: - time.sleep(0.5) - - except Exception as e: - logger.error("Exception in add_pacmanlog_queue() : %s" % e) - finally: - logger.debug("No new lines found inside the pacman log file") - - -# start log timer to update the textview called from a non-blocking thread -def start_log_timer(self, window_pacmanlog): - while True: - if window_pacmanlog.start_logtimer is False: - logger.debug("Stopping Pacman log monitoring timer") - return False - - GLib.idle_add(update_textview_pacmanlog, self, priority=GLib.PRIORITY_DEFAULT) - time.sleep(2) - - -# update the textview component with new lines from the pacman log file - - -# To fix: Gtk-CRITICAL **: gtk_text_buffer_emit_insert: assertion 'g_utf8_validate (text, len, NULL)' failed -# Make sure the line read from the pacman log file is encoded in utf-8 -# Then decode the line when inserting inside the buffer - - -def update_textview_pacmanlog(self): - lines = self.pacmanlog_queue.get() - - try: - buffer = self.textbuffer_pacmanlog - if len(lines) > 0: - end_iter = buffer.get_end_iter() - for line in lines: - if len(line) > 0: - buffer.insert( - end_iter, - line.decode("utf-8"), - len(line), - ) - - except Exception as e: - logger.error("Exception in update_textview_pacmanlog() : %s" % e) - finally: - self.pacmanlog_queue.task_done() - - if len(lines) > 0: - text_mark_end = buffer.create_mark("end", buffer.get_end_iter(), False) - # auto-scroll the textview to the bottom as new content is added - - self.textview_pacmanlog.scroll_mark_onscreen(text_mark_end) - - lines.clear() - - -# ===================================================== -# USER SEARCH -# ===================================================== - - -def search(self, term): - try: - logger.info('Searching for: "%s"' % term) - - pkg_matches = [] - - category_dict = {} - - whitespace = False - - if term.strip(): - whitespace = True - - for pkg_list in self.packages.values(): - for pkg in pkg_list: - if whitespace: - for te in term.split(" "): - if ( - te.lower() in pkg.name.lower() - or te.lower() in pkg.description.lower() - ): - # only unique name matches - if pkg not in pkg_matches: - pkg_matches.append( - pkg, - ) - else: - if ( - term.lower() in pkg.name.lower() - or term.lower() in pkg.description.lower() - ): - pkg_matches.append( - pkg, - ) - - # filter the results so that each category holds a list of package - - category_name = None - packages_cat = [] - for pkg_match in pkg_matches: - if category_name == pkg_match.category: - packages_cat.append(pkg_match) - category_dict[category_name] = packages_cat - elif category_name is None: - packages_cat.append(pkg_match) - category_dict[pkg_match.category] = packages_cat - else: - # reset packages, new category - packages_cat = [] - - packages_cat.append(pkg_match) - - category_dict[pkg_match.category] = packages_cat - - category_name = pkg_match.category - - # debug console output to display package info - """ - # print out number of results found from each category - print("[DEBUG] %s Search results.." % datetime.now().strftime("%H:%M:%S")) - - for category in sorted(category_dict): - category_res_len = len(category_dict[category]) - print("[DEBUG] %s %s = %s" %( - datetime.now().strftime("%H:%M:%S"), - category, - category_res_len, - ) - ) - """ - - # sort dictionary so the category names are displayed in alphabetical order - sorted_dict = None - - if len(category_dict) > 0: - sorted_dict = dict(sorted(category_dict.items())) - self.search_queue.put( - sorted_dict, - ) - else: - self.search_queue.put( - None, - ) - - except Exception as e: - logger.error("Exception in search(): %s", e) - - -# ===================================================== -# ARCOLINUX REPOS, KEYS AND MIRRORS -# ===================================================== - - -def append_repo(text): - """Append a new repo""" - try: - with open(pacman_conf, "a", encoding="utf-8") as f: - f.write("\n\n") - f.write(text) - except Exception as e: - logger.error("Exception in append_repo(): %s" % e) - - -def repo_exist(value): - """check repo_exists""" - with open(pacman_conf, "r", encoding="utf-8") as f: - lines = f.readlines() - f.close() - - for line in lines: - if value in line: - return True - return False - - -def install_snigdhaos_keyring(): - try: - keyring = base_dir + "/packages/snigdhaos-keyring/" - file = os.listdir(keyring) - cmd_str = [ - "pacman", - "-U", - keyring + str(file).strip("[]'"), - "--noconfirm", - ] - - logger.debug("%s" % " ".join(cmd_str)) - - with subprocess.Popen( - cmd_str, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - process.wait(process_timeout) - - output = [] - - for line in process.stdout: - output.append(line) - - if process.returncode == 0: - return 0 - - else: - if len(output) == 0: - output.append("Error: install of Snigdha OS keyring failed") - - logger.error(" ".join(output)) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = output - - return result_err - except Exception as e: - logger.error("Exception in install_snigdhaos_keyring(): %s" % e) - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = e - - return result_err - - -def remove_snigdhaos_keyring(): - try: - cmd_str = ["pacman", "-Rdd", "snigdhaos-keyring", "--noconfirm"] - with subprocess.Popen( - cmd_str, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - process.wait(process_timeout) - - output = [] - - for line in process.stdout: - output.append(line) - - if process.returncode == 0: - return 0 - - else: - if len(output) == 0: - output.append("Error: removal of Snigdha OS keyring failed") - - logger.error(" ".join(output)) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = output - - return result_err - - except Exception as e: - logger.error("Exception in remove_snigdhaos_keyring(): %s" % e) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = e - - return result_err - - -def install_snigdhaos_mirrorlist(): - try: - mirrorlist = base_dir + "/packages/snigdhaos-mirrorlist/" - file = os.listdir(mirrorlist) - cmd_str = [ - "pacman", - "-U", - mirrorlist + str(file).strip("[]'"), - "--noconfirm", - ] - - logger.debug("%s" % " ".join(cmd_str)) - with subprocess.Popen( - cmd_str, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - process.wait(process_timeout) - - output = [] - - for line in process.stdout: - output.append(line) - - if process.returncode == 0: - return 0 - - else: - if len(output) == 0: - output.append("Error: install of Snigdha OS mirrorlist failed") - - logger.error(" ".join(output)) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = output - - return result_err - except Exception as e: - logger.error("Exception in install_snigdhaos_mirrorlist(): %s" % e) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = output - - return result_err - - -def remove_snigdhaos_mirrorlist(): - try: - cmd_str = ["pacman", "-Rdd", "snigdhaos-mirrorlist-git", "--noconfirm"] - logger.debug("%s" % " ".join(cmd_str)) - with subprocess.Popen( - cmd_str, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - process.wait(process_timeout) - - output = [] - - for line in process.stdout: - output.append(line) - - if process.returncode == 0: - return 0 - - else: - if len(output) == 0: - output.append("Error: removal of Snigdha OS mirrorlist failed") - - logger.error(" ".join(output)) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = output - - return result_err - - except Exception as e: - logger.error("Exception in remove_snigdhaos_mirrorlist(): %s" % e) - - result_err = {} - - result_err["cmd_str"] = cmd_str - result_err["output"] = e - - return result_err - - -def add_snigdhaos_repos(): - logger.info("Adding Snigdha OS repos on %s" % distr) - try: - # first check if snigdhaos repos are already inside pacman conf file - - if verify_snigdhaos_pacman_conf() is False: - # take backup of existing pacman.conf file - - if os.path.exists(pacman_conf): - shutil.copy(pacman_conf, pacman_conf_backup) - - # read existing contents from pacman.conf file - - logger.info("Reading from %s" % pacman_conf) - - lines = [] - - with open(pacman_conf, "r", encoding="utf-8") as r: - lines = r.readlines() - - # check for existing Snigdha OS entries - if len(lines) > 0: - snigdhaos_core_found = False - snigdhaos_extra_found = False - - for line in lines: - if "#" in line.strip(): - if snigdhaos_core[0].replace("#", "") in line.strip(): - snigdhaos_core_found = True - index = lines.index(line) - - del lines[index] - lines.insert(index, snigdhaos_core[0]) - - index += 1 - - del lines[index] - lines.insert(index, snigdhaos_core[1]) - - index += 1 - - del lines[index] - lines.insert(index, snigdhaos_core[2]) - - if snigdhaos_extra[0].replace("#", "") in line.strip(): - snigdhaos_extra_found = True - index = lines.index(line) - - del lines[index] - lines.insert(index, snigdhaos_extra[0]) - - index += 1 - - del lines[index] - lines.insert(index, snigdhaos_extra[1]) - - index += 1 - - del lines[index] - lines.insert(index, snigdhaos_extra[2]) - - if line.strip() == snigdhaos_core[0]: - snigdhaos_core_found = True - - if line.strip() == snigdhaos_extra[0]: - snigdhaos_extra_found = True - - if snigdhaos_core_found is False: - lines.append("\n") - - for snigdhaos_core_line in snigdhaos_core: - lines.append(snigdhaos_core_line) - - if snigdhaos_extra_found is False: - lines.append("\n") - - for snigdhaos_extra_line in snigdhaos_extra: - lines.append(snigdhaos_extra_line) - - logger.info("[Add Snigdha OS repos] Writing to %s" % pacman_conf) - - if len(lines) > 0: - with open(pacman_conf, "w", encoding="utf-8") as w: - for l in lines: - w.write(l.strip() + "\n") - - w.flush() - - return 0 - - else: - logger.error("Failed to process %s" % pacman_conf) - - else: - logger.error("Failed to read %s" % pacman_conf) - else: - logger.info("Snigdha OS repos already setup inside pacman conf file") - return 0 - - except Exception as e: - logger.error("Exception in add_snigdhaos_repos(): %s" % e) - return e - - -def remove_snigdhaos_repos(): - # remove the Snigdha OS repos in /etc/pacman.conf - try: - # check for existing Snigdha OS entries and remove - if verify_snigdhaos_pacman_conf() is True: - if os.path.exists(pacman_conf): - shutil.copy(pacman_conf, pacman_conf_backup) - - logger.info("Reading from %s" % pacman_conf) - - lines = [] - - with open(pacman_conf, "r", encoding="utf-8") as r: - lines = r.readlines() - - if len(lines) > 0: - index = 0 - - for line in lines: - if "%s\n" % snigdhaos_core[0] == line: - index = lines.index("%s\n" % snigdhaos_core[0]) - - if index > 0: - if distr != "snigdhaos": - del lines[index] - del lines[index] - del lines[index] - else: - lines[index] = "#%s\n" % snigdhaos_core[0] - lines[index + 1] = "#%s\n" % snigdhaos_core[1] - lines[index + 2] = "#%s\n" % snigdhaos_core[2] - elif ( - "#" in line.strip() - and snigdhaos_core[0] == line.replace("#", "").strip() - and distr != "snigdhaos" - ): - # check if already commented - - index = lines.index(line) - del lines[index] - del lines[index] - del lines[index] - - if "%s\n" % snigdhaos_extra[0] == line: - index = lines.index("%s\n" % snigdhaos_extra[0]) - - if index > 0: - if distr != "snigdhaos": - del lines[index] - del lines[index] - del lines[index] - else: - lines[index] = "#%s\n" % snigdhaos_extra[0] - lines[index + 1] = "#%s\n" % snigdhaos_extra[1] - lines[index + 2] = "#%s\n" % snigdhaos_extra[2] - elif ( - "#" in line.strip() - and snigdhaos_extra[0] == line.replace("#", "").strip() - and distr != "snigdhaos" - ): - # check if already commented - - index = lines.index(line) - del lines[index] - del lines[index] - del lines[index] - - # remove any white spaces from end of the file only if on non snigdhaos system - # on any non snigdhaos distro lines are deleted which leaves empty lines in the file - # causing the file to grow in size - if distr != "snigdhaos": - if lines[-1] == "\n": - del lines[-1] - - if lines[-2] == "\n": - del lines[-2] - - if lines[-3] == "\n": - del lines[-3] - - if lines[-4] == "\n": - del lines[-4] - - logger.info("[Remove Snigdha OS Repos] Writing to %s" % pacman_conf) - - if len(lines) > 0: - with open(pacman_conf, "w") as w: - w.writelines(lines) - - w.flush() - - return 0 - - else: - logger.error("Failed to process %s" % pacman_conf) - - else: - logger.error("Failed to read %s" % pacman_conf) - else: - logger.info("No Snigdha OS repos setup inside pacman conf file") - return 0 - - except Exception as e: - logger.error("Exception in remove_snigdhaos_repos(): %s" % e) - return e - - -# check if pacman.conf has snigdhaos repos setup - - -def verify_snigdhaos_pacman_conf(): - try: - lines = None - snigdhaos_core_setup = False - snigdhaos_extra_setup = False - with open(pacman_conf, "r") as r: - lines = r.readlines() - - if lines is not None: - for line in lines: - if snigdhaos_core[0] in line.strip(): - if "#" not in line.strip(): - snigdhaos_core_setup = True - else: - return False - - if snigdhaos_extra[0] in line.strip(): - if "#" not in line.strip(): - snigdhaos_extra_setup = True - else: - return False - - if ( - snigdhaos_core_setup is True - and snigdhaos_extra_setup is True - ): - return True - else: - return False - except Exception as e: - logger.error("Exception in check_snigdhaos_pacman(): %s" % e) - - -# ===================================================== -# CHECK IF PACKAGE IS INSTALLED -# ===================================================== - - -# check if package is installed or not -def check_package_installed(package_name): - # query_str = ["pacman", "-Qi", package] - query_str = ["pacman", "-Qq"] - try: - process_pkg_installed = subprocess.run( - query_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - universal_newlines=True, - ) - - if package_name in process_pkg_installed.stdout.splitlines(): - return True - else: - # check if the package is in the local pacman db - if check_pacman_localdb(package_name): - return True - else: - return False - - except subprocess.CalledProcessError: - # package is not installed - return False - - -# ===================================================== -# QUERY THE LOCAL PACMAN DB FOR PACKAGE -# ===================================================== - -# This is used to validate a package install/uninstall - - -# check if package is installed or not -def check_pacman_localdb(package_name): - query_str = ["pacman", "-Qi", package_name] - - try: - process_pkg_installed = subprocess.run( - query_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=process_timeout, - ) - - if process_pkg_installed.returncode == 0: - for line in process_pkg_installed.stdout.decode("utf-8").splitlines(): - if line.startswith("Name :"): - if line.replace(" ", "").split("Name:")[1].strip() == package_name: - return True - - if line.startswith("Replaces :"): - replaces = line.split("Replaces :")[1].strip() - if len(replaces) > 0: - if package_name in replaces: - return True - - else: - return False - - except subprocess.CalledProcessError: - # package is not installed - return False - - -# ===================================================== -# CHECK RUNNING PROCESS -# ===================================================== - - -def check_if_process_running(process_name): - for proc in psutil.process_iter(): - try: - pinfo = proc.as_dict(attrs=["pid", "name", "create_time"]) - if process_name == pinfo["pid"]: - return True - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - pass - return False - - -# ===================================================== -# NOTIFICATIONS -# ===================================================== - - -def show_in_app_notification(self, message, err): - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - if err is True: - self.notification_label.set_markup( - '' + message + "" - ) - else: - self.notification_label.set_markup( - '' + message + "" - ) - self.notification_revealer.set_reveal_child(True) - self.timeout_id = GLib.timeout_add(3000, timeout, self) - - -def timeout(self): - close_in_app_notification(self) - - -def close_in_app_notification(self): - self.notification_revealer.set_reveal_child(False) - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - -def reveal_infobar(self, progress_dialog): - progress_dialog.infobar.set_revealed(True) - progress_dialog.infobar.show_all() - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - -""" - Since the app could be quit/terminated at any time during a pacman transaction. - The pacman process spawned by the install/uninstall threads, needs to be terminated too. - Otherwise the app may hang waiting for pacman to complete its transaction. -""" -# ===================================================== -# PACMAN -# ===================================================== - - -def terminate_pacman(): - try: - process_found = False - for proc in psutil.process_iter(): - try: - pinfo = proc.as_dict(attrs=["pid", "name", "create_time"]) - if pinfo["name"] == "pacman": - process_found = True - logger.debug("Killing pacman process = %s" % pinfo["name"]) - - proc.kill() - - except (psutil.NoSuchProcess, psutil.AccessDenied): - continue - - if process_found is True: - if check_pacman_lockfile(): - os.unlink(pacman_lockfile) - except Exception as e: - logger.error("Exception in terminate_pacman() : %s" % e) - - -def is_thread_alive(thread_name): - for thread in threading.enumerate(): - if thread.name == thread_name and thread.is_alive(): - return True - - return False - - -def print_running_threads(): - threads_alive = [] - for thread in threading.enumerate(): - if thread.is_alive(): - threads_alive.append(thread.name) - - for th in threads_alive: - logger.debug("Thread = %s status = alive" % th) - - -# this keeps monitoring for items on the package holding queue -# items are added to the queue if a package install is stuck behind another pacman transaction -def check_holding_queue(self): - while True: - ( - package, - action, - widget, - cmd_str, - progress_dialog, - ) = self.pkg_holding_queue.get() - - try: - # logger.debug("Enqueued package = %s" % package.name) - - while check_pacman_lockfile() is True: - # logger.debug("Pacman is processing a transaction") - time.sleep(0.2) - - th_subprocess = Thread( - name="thread_subprocess", - target=start_subprocess, - args=( - self, - cmd_str, - progress_dialog, - action, - package, - widget, - ), - daemon=True, - ) - - th_subprocess.start() - - finally: - self.pkg_holding_queue.task_done() - - -# check if pacman lock file exists -def check_pacman_lockfile(): - try: - if os.path.exists(pacman_lockfile): - # logger.debug("Pacman lockfile found inside %s" % pacman_lockfile) - # logger.debug("Another pacman process is running") - return True - else: - # logger.info("No pacman lockfile found, OK to proceed") - return False - except Exception as e: - logger.error("Exception in check_pacman_lockfile() : %s" % e) - - -# this gets info on the pacman process currently running -def get_pacman_process(): - try: - for proc in psutil.process_iter(): - try: - pinfo = proc.as_dict(attrs=["pid", "name", "create_time"]) - if pinfo["name"] == "pacman": - return " ".join(proc.cmdline()) - - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - pass - - except Exception as e: - logger.error("Exception in get_pacman_process() : %s" % e) - - -# used inside PackageImportDialog to display package installation progress -def update_package_import_textview(self, line): - try: - if len(line) > 0: - self.msg_buffer.insert( - self.msg_buffer.get_end_iter(), - " %s" % line, - len(" %s" % line), - ) - - except Exception as e: - logger.error("Exception in update_progress_textview(): %s" % e) - finally: - self.pkg_import_queue.task_done() - text_mark_end = self.msg_buffer.create_mark( - "end", self.msg_buffer.get_end_iter(), False - ) - # scroll to the end of the textview - self.textview.scroll_mark_onscreen(text_mark_end) - - -def monitor_package_import(self): - while True: - if self.stop_thread is True: - break - message = self.pkg_import_queue.get() - GLib.idle_add( - update_package_import_textview, - self, - message, - priority=GLib.PRIORITY_DEFAULT, - ) - - # time.sleep(0.2) - - -# update the package install status label called from outside the main thread -def update_package_status_label(label, text): - label.set_markup(text) - - -def import_packages(self): - try: - packages_status_list = [] - package_failed = False - package_err = {} - - count = 0 - - # clean pacman cache - - if os.path.exists(pacman_cache_dir): - query_pacman_clean_cache_str = ["pacman", "-Sc", "--noconfirm"] - - logger.info("Cleaning Pacman cache directory = %s" % pacman_cache_dir) - - event = "%s [INFO]: Cleaning pacman cache\n" % datetime.now().strftime( - "%Y-%m-%d-%H-%M-%S" - ) - - self.pkg_import_queue.put(event) - - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: Cleaning pacman cache", - ) - - # clean the pacman cache, so we don't run into any invalid/corrupt package errors during install - process_pacman_cc = subprocess.Popen( - query_pacman_clean_cache_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) - - out, err = process_pacman_cc.communicate(timeout=process_timeout) - - self.pkg_import_queue.put(out) - - if process_pacman_cc.returncode == 0: - logger.info("Pacman cache directory cleaned") - else: - logger.error("Failed to clean Pacman cache directory") - - logger.info("Running full system upgrade") - # run full system upgrade, Arch does not allow partial package updates - query_str = ["pacman", "-Syu", "--noconfirm"] - # query_str = ["pacman", "-Qqen"] - logger.info("Running %s" % " ".join(query_str)) - - event = "%s [INFO]:Running full system upgrade\n" % datetime.now().strftime( - "%Y-%m-%d-%H-%M-%S" - ) - - self.pkg_import_queue.put(event) - - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: Performing full system upgrade - do not power off your system", - ) - - output = [] - - with subprocess.Popen( - query_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - while True: - if process.poll() is not None: - break - - for line in process.stdout: - # print(line.strip()) - - self.pkg_import_queue.put(line) - - output.append(line) - - # time.sleep(0.2) - - if process.returncode == 0: - logger.info("Pacman system upgrade completed") - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: Full system upgrade - completed", - ) - else: - if len(output) > 0: - if "there is nothing to do" not in output: - logger.error("Pacman system upgrade failed") - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: Full system upgrade - failed", - ) - - print("%s" % " ".join(output)) - - event = "%s [ERROR]: Installation of packages aborted due to errors\n" % datetime.now().strftime( - "%Y-%m-%d-%H-%M-%S" - ) - - self.pkg_import_queue.put(event) - - logger.error("Installation of packages aborted due to errors") - - return - - # do not proceed with package installs if system upgrade fails - else: - return - - # iterate through list of packages, calling pacman -S on each one - for package in self.packages_list: - process_output = [] - package = package.strip() - if len(package) > 0: - if "#" not in package: - query_str = ["pacman", "-S", package, "--needed", "--noconfirm"] - - count += 1 - - logger.info("Running %s" % " ".join(query_str)) - - event = "%s [INFO]: Running %s\n" % ( - datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), - " ".join(query_str), - ) - - self.pkg_import_queue.put(event) - - with subprocess.Popen( - query_str, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - universal_newlines=True, - ) as process: - while True: - if process.poll() is not None: - break - for line in process.stdout: - process_output.append(line.strip()) - - self.pkg_import_queue.put(line) - - # time.sleep(0.2) - - if process.returncode == 0: - # since this is being run in another thread outside of main, use GLib to update UI component - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: %s -> Installed" % package, - ) - - GLib.idle_add( - update_package_status_label, - self.label_package_count, - "Progress: %s/%s" - % (count, len(self.packages_list)), - ) - - packages_status_list.append("%s -> Installed" % package) - - else: - logger.error("%s --> Install failed" % package) - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Status: %s -> Install failed" % package, - ) - - GLib.idle_add( - update_package_status_label, - self.label_package_count, - "Progress: %s/%s" - % (count, len(self.packages_list)), - ) - - if len(process_output) > 0: - if "there is nothing to do" not in process_output: - logger.error("%s" % " ".join(process_output)) - # store package error in dict - package_err[package] = " ".join(process_output) - - package_failed = True - - packages_status_list.append("%s -> Failed" % package) - - if len(packages_status_list) > 0: - self.pkg_status_queue.put(packages_status_list) - - if package_failed is True: - GLib.idle_add( - update_package_status_label, - self.label_package_status, - "Some packages have failed to install see %s" % self.logfile, - ) - - # end - event = "%s [INFO]: Completed, check the logfile for any errors\n" % ( - datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), - ) - - self.pkg_import_queue.put(event) - - except Exception as e: - logger.error("Exception in import_packages(): %s" % e) - finally: - self.pkg_err_queue.put(package_err) - - -# package install completed now log status to log file -def log_package_status(self): - logger.info("Logging package status") - packages_status_list = None - package_err = None - while True: - try: - time.sleep(0.2) - packages_status_list = self.pkg_status_queue.get() - package_err = self.pkg_err_queue.get() - - finally: - self.pkg_status_queue.task_done() - self.pkg_err_queue.task_done() - with open(self.logfile, "w") as f: - f.write( - "# This file was auto-generated by BlackBox on %s at %s\n" - % ( - datetime.today().date(), - datetime.now().strftime("%H:%M:%S"), - ), - ) - if packages_status_list is not None: - for package in packages_status_list: - if package.split("->")[0].strip() in package_err: - f.write("%s\n" % package) - f.write( - "\tERROR: %s\n" - % package_err[package.split("->")[0].strip()] - ) - else: - f.write("%s\n" % package) - - break - - -# open blackbox log directory -def open_log_dir(): - try: - subprocess.Popen( - ["sudo", "-u", sudo_username, "xdg-open", log_dir], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - except Exception as e: - logger.error("Exception in open_log_dir(): %s" % e) - - -# ANYTHING UNDER THIS LINE IS CURRENTLY UNUSED! diff --git a/usr/share/blackbox/Functions_Ref_DO_NOT_MODIFY.py b/usr/share/blackbox/Functions_Ref_DO_NOT_MODIFY.py deleted file mode 100644 index 8ccb443..0000000 --- a/usr/share/blackbox/Functions_Ref_DO_NOT_MODIFY.py +++ /dev/null @@ -1,523 +0,0 @@ -# ================================================================= -# = Author: Cameron Percival = -# ================================================================= - - -import os -import sys -import shutil -import psutil -import datetime - -# import time -import subprocess -import threading # noqa -import gi - -# import configparser -gi.require_version("Gtk", "3.0") -from gi.repository import GLib, Gtk # noqa - - -# ===================================================== -# Create log file -# ===================================================== - -log_dir = "/var/log/snigdhaos/" -aai_log_dir = "/var/log/snigdhaos/aai/" - - -def create_log(self): - print("Making log in /var/log/snigdhaos") - now = datetime.datetime.now() - time = now.strftime("%Y-%m-%d-%H-%M-%S") - destination = aai_log_dir + "aai-log-" + time - command = "sudo pacman -Q > " + destination - subprocess.call( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - # GLib.idle_add(show_in_app_notification, self, "Log file created") - - -# ===================================================== -# GLOBAL FUNCTIONS -# ===================================================== - - -def _get_position(lists, value): - data = [string for string in lists if value in string] - position = lists.index(data[0]) - return position - - -# ===================================================== -# PERMISSIONS -# ===================================================== - - -def permissions(dst): - try: - groups = subprocess.run( - ["sh", "-c", "id " + sudo_username], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - for x in groups.stdout.decode().split(" "): - if "gid" in x: - g = x.split("(")[1] - group = g.replace(")", "").strip() - subprocess.call(["chown", "-R", sudo_username + ":" + group, dst], shell=False) - - except Exception as e: - print(e) - - -#######ANYTHING UNDER THIS LINE IS CURRENTLY UNUSED! - -sudo_username = os.getlogin() -home = "/home/" + str(sudo_username) - -sddm_default = "/etc/sddm.conf" -sddm_default_original = "/usr/local/share/snigdhaos/sddm/sddm.conf" - -sddm_default_d1 = "/etc/sddm.conf" -sddm_default_d2 = "/etc/sddm.conf.d/kde_settings.conf" -sddm_default_d2_dir = "/etc/sddm.conf.d/" -sddm_default_d_sddm_original_1 = "/usr/local/share/snigdhaos/sddm.conf.d/sddm.conf" -sddm_default_d_sddm_original_2 = ( - "/usr/local/share/snigdhaos/sddm.conf.d/kde_settings.conf" -) - -if os.path.exists("/etc/sddm.conf.d/kde_settings.conf"): - sddm_conf = "/etc/sddm.conf.d/kde_settings.conf" -else: - sddm_conf = "/etc/sddm.conf" - -snigdhaos_mirrorlist = "/etc/pacman.d/snigdhaos-mirrorlist" -snigdhaos_mirrorlist_original = "/usr/local/share/snigdhaos/snigdhaos-mirrorlist" -pacman = "/etc/pacman.conf" -oblogout_conf = "/etc/oblogout.conf" -# oblogout_conf = home + "/oblogout.conf" -gtk3_settings = home + "/.config/gtk-3.0/settings.ini" -gtk2_settings = home + "/.gtkrc-2.0" -grub_theme_conf = "/boot/grub/themes/snigdhaos-grub-theme/theme.txt" -xfce_config = home + "/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml" -slimlock_conf = "/etc/slim.conf" -termite_config = home + "/.config/termite/config" -neofetch_config = home + "/.config/neofetch/config.conf" -lightdm_conf = "/etc/lightdm/lightdm.conf" -bd = ".att_backups" -config = home + "/.config/archlinux-tweak-tool/settings.ini" -config_dir = home + "/.config/archlinux-tweak-tool/" -polybar = home + "/.config/polybar/" -desktop = "" -autostart = home + "/.config/autostart/" -zsh_config = home + "/.zshrc" -account_list = ["Standard", "Administrator"] -i3wm_config = home + "/.config/i3/config" -awesome_config = home + "/.config/awesome/rc.lua" -qtile_config = home + "/.config/qtile/config.py" - -arepo = "[snigdhaos-core]\n\ -SigLevel = Required DatabaseOptional\n\ -Include = /etc/pacman.d/snigdhaos-mirrorlist" - -a3drepo = "[snigdhaos-extra]\n\ -SigLevel = Required DatabaseOptional\n\ -Include = /etc/pacman.d/snigdhaos-mirrorlist" - -# ===================================================== -# NOTIFICATIONS -# ===================================================== - - -def show_in_app_notification(self, message): - if self.timeout_id is not None: - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - self.notification_label.set_markup( - '' + message + "" - ) - self.notification_revealer.set_reveal_child(True) - self.timeout_id = GLib.timeout_add(3000, timeOut, self) - - -def timeOut(self): - close_in_app_notification(self) - - -def close_in_app_notification(self): - self.notification_revealer.set_reveal_child(False) - GLib.source_remove(self.timeout_id) - self.timeout_id = None - - -# ===================================================== -# PERMISSIONS -# ===================================================== - - -def test(dst): - for root, dirs, filesr in os.walk(dst): - # print(root) - for folder in dirs: - pass - # print(dst + "/" + folder) - for file in filesr: - pass - # print(dst + "/" + folder + "/" + file) - for file in filesr: - pass - # print(dst + "/" + file) - - -# ===================================================== -# COPY FUNCTION -# ===================================================== - - -def copy_func(src, dst, isdir=False): - if isdir: - subprocess.run(["cp", "-Rp", src, dst], shell=False) - else: - subprocess.run(["cp", "-p", src, dst], shell=False) - # permissions(dst) - - -# ===================================================== -# SOURCE -# ===================================================== - - -def source_shell(self): - process = subprocess.run(["sh", "-c", 'echo "$SHELL"'], stdout=subprocess.PIPE) - - output = process.stdout.decode().strip() - print(output) - if output == "/bin/bash": - subprocess.run( - [ - "bash", - "-c", - "su - " + sudo_username + ' -c "source ' + home + '/.bashrc"', - ], - stdout=subprocess.PIPE, - ) - elif output == "/bin/zsh": - subprocess.run( - ["zsh", "-c", "su - " + sudo_username + ' -c "source ' + home + '/.zshrc"'], - stdout=subprocess.PIPE, - ) - - -def run_as_user(script): - subprocess.call(["su - " + sudo_username + " -c " + script], shell=False) - - -# ===================================================== -# MESSAGEBOX -# ===================================================== - - -def MessageBox(self, title, message): - md2 = Gtk.MessageDialog( - parent=self, - flags=0, - message_type=Gtk.MessageType.INFO, - buttons=Gtk.ButtonsType.OK, - text=title, - ) - md2.format_secondary_markup(message) - md2.run() - md2.destroy() - - -# ===================================================== -# CONVERT COLOR -# ===================================================== - - -def rgb_to_hex(rgb): - if "rgb" in rgb: - rgb = rgb.replace("rgb(", "").replace(")", "") - vals = rgb.split(",") - return "#{0:02x}{1:02x}{2:02x}".format( - clamp(int(vals[0])), clamp(int(vals[1])), clamp(int(vals[2])) - ) - return rgb - - -def clamp(x): - return max(0, min(x, 255)) - - -# ===================================================== -# GLOBAL FUNCTIONS -# ===================================================== - - -def _get_variable(lists, value): - data = [string for string in lists if value in string] - - if len(data) >= 1: - - data1 = [string for string in data if "#" in string] - - for i in data1: - if i[:4].find("#") != -1: - data.remove(i) - if data: - data_clean = [data[0].strip("\n").replace(" ", "")][0].split("=") - return data_clean - - -# Check value exists - - -def check_value(list, value): - data = [string for string in list if value in string] - if len(data) >= 1: - data1 = [string for string in data if "#" in string] - for i in data1: - if i[:4].find("#") != -1: - data.remove(i) - return data - - -def check_backups(now): - if not os.path.exists(home + "/" + bd + "/Backup-" + now.strftime("%Y-%m-%d %H")): - os.makedirs(home + "/" + bd + "/Backup-" + now.strftime("%Y-%m-%d %H"), 0o777) - permissions(home + "/" + bd + "/Backup-" + now.strftime("%Y-%m-%d %H")) - - -# ===================================================== -# Check if File Exists -# ===================================================== - - -def file_check(file): - if os.path.isfile(file): - return True - - return False - - -def path_check(path): - if os.path.isdir(path): - return True - - return False - - -# ===================================================== -# GTK3 CONF -# ===================================================== - - -def gtk_check_value(my_list, value): - data = [string for string in my_list if value in string] - if len(data) >= 1: - data1 = [string for string in data if "#" in string] - for i in data1: - if i[:4].find("#") != -1: - data.remove(i) - return data - - -def gtk_get_position(my_list, value): - data = [string for string in my_list if value in string] - position = my_list.index(data[0]) - return position - - -# ===================================================== -# OBLOGOUT CONF -# ===================================================== -# Get shortcuts index - - -def get_shortcuts(conflist): - sortcuts = _get_variable(conflist, "shortcuts") - shortcuts_index = _get_position(conflist, sortcuts[0]) - return int(shortcuts_index) - - -# Get commands index - - -def get_commands(conflist): - commands = _get_variable(conflist, "commands") - commands_index = _get_position(conflist, commands[0]) - return int(commands_index) - - -# ===================================================== -# LIGHTDM CONF -# ===================================================== - - -def check_lightdm_value(list, value): - data = [string for string in list if value in string] - # if len(data) >= 1: - # data1 = [string for string in data if "#" in string] - - return data - - -# ===================================================== -# SDDM CONF -# ===================================================== - - -def check_sddm_value(list, value): - data = [string for string in list if value in string] - return data - - -# ===================================================== -# HBLOCK CONF -# ===================================================== - - -def hblock_get_state(self): - lines = int( - subprocess.check_output("wc -l /etc/hosts", shell=True).strip().split()[0] - ) - if os.path.exists("/usr/local/bin/hblock") and lines > 100: - return True - - self.firstrun = False - return False - - -def do_pulse(data, prog): - prog.pulse() - return True - - -# ===================================================== -# UBLOCK ORIGIN -# ===================================================== - - -def ublock_get_state(self): - if os.path.exists( - "/usr/lib/firefox/browser/extensions/uBlock0@raymondhill.net.xpi" - ): - return True - return False - - -def set_firefox_ublock(self, toggle, state): - GLib.idle_add(toggle.set_sensitive, False) - GLib.idle_add(self.label7.set_text, "Run..") - GLib.idle_add(self.progress.set_fraction, 0.2) - - timeout_id = None - timeout_id = GLib.timeout_add(100, do_pulse, None, self.progress) - - try: - - install_ublock = "pacman -S firefox-ublock-origin --needed --noconfirm" - uninstall_ublock = "pacman -Rs firefox-ublock-origin --noconfirm" - - if state: - GLib.idle_add(self.label7.set_text, "Installing ublock Origin...") - subprocess.call( - install_ublock.split(" "), - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - else: - GLib.idle_add(self.label7.set_text, "Removing ublock Origin...") - subprocess.call( - uninstall_ublock.split(" "), - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - - GLib.idle_add(self.label7.set_text, "Complete") - GLib.source_remove(timeout_id) - timeout_id = None - GLib.idle_add(self.progress.set_fraction, 0) - - GLib.idle_add(toggle.set_sensitive, True) - if state: - GLib.idle_add(self.label7.set_text, "uBlock Origin installed") - else: - GLib.idle_add(self.label7.set_text, "uBlock Origin removed") - - except Exception as e: - MessageBox(self, "ERROR!!", str(e)) - print(e) - - -# ===================================================== -# ALACRITTY -# ===================================================== - - -def install_alacritty(self): - install = "pacman -S alacritty --needed --noconfirm" - - if os.path.exists("/usr/bin/alacritty"): - pass - else: - subprocess.call( - install.split(" "), - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - -def copytree(self, src, dst, symlinks=False, ignore=None): # noqa - - if not os.path.exists(dst): - os.makedirs(dst) - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.exists(d): - try: - shutil.rmtree(d) - except Exception as e: - print(e) - os.unlink(d) - if os.path.isdir(s): - try: - shutil.copytree(s, d, symlinks, ignore) - except Exception as e: - print(e) - print("ERROR2") - self.ecode = 1 - else: - try: - shutil.copy2(s, d) - except: # noqa - print("ERROR3") - self.ecode = 1 - - -# ===================================================== -# CHECK RUNNING PROCESS -# ===================================================== - - -def checkIfProcessRunning(processName): - for proc in psutil.process_iter(): - try: - pinfo = proc.as_dict(attrs=["pid", "name", "create_time"]) - if processName == pinfo["pid"]: - return True - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - pass - return False - - -def restart_program(): - python = sys.executable - os.execl(python, python, *sys.argv) diff --git a/usr/share/blackbox/Package.py b/usr/share/blackbox/Package.py deleted file mode 100644 index 1c2bcf5..0000000 --- a/usr/share/blackbox/Package.py +++ /dev/null @@ -1,19 +0,0 @@ -# This class is to encapsulate package metadata, taken from the yaml files stored inside the blackbox github repository - - -class Package(object): - def __init__( - self, - name, - description, - category, - subcategory, - subcategory_description, - version, - ): - self.name = name - self.description = description - self.category = category - self.subcategory = subcategory - self.subcategory_description = subcategory_description - self.version = version \ No newline at end of file diff --git a/usr/share/blackbox/Settings.py b/usr/share/blackbox/Settings.py deleted file mode 100644 index ddc4714..0000000 --- a/usr/share/blackbox/Settings.py +++ /dev/null @@ -1,132 +0,0 @@ -# This class is used to process configuration data for the app - -import os -import Functions as fn -from string import Template - -base_dir = os.path.dirname(os.path.realpath(__file__)) -# a default configuration file if one doesn't exist is copied over from /usr/share/blackbox/defaults to $HOME/.config -default_file = "%s/defaults/blackbox.yaml" % base_dir - - -class Settings(object): - def __init__(self, display_versions, display_package_progress): - self.display_versions = display_versions - self.display_package_progress = display_package_progress - - def write_config_file(self): - try: - content = [] - with open(fn.config_file, "r", encoding="UTF-8") as f: - contents = f.readlines() - - if len(contents) > 0: - self.read(contents) - - conf_settings = {} - - conf_settings["Display Package Versions"] = self.display_versions - - conf_settings[ - "Display Package Progress" - ] = self.display_package_progress - - index = 0 - for line in contents: - if line.startswith("- name:"): - if ( - line.strip("- name: ") - .strip() - .strip('"') - .strip("\n") - .strip() - == "Display Package Versions" - ): - index = contents.index(line) - - index += 2 - - if contents[index].startswith(" enabled: "): - del contents[index] - contents.insert( - index, - " enabled: %s\n" - % conf_settings["Display Package Versions"], - ) - - if ( - line.strip("- name: ") - .strip() - .strip('"') - .strip("\n") - .strip() - == "Display Package Progress" - ): - index += 4 - if contents[index].startswith(" enabled: "): - del contents[index] - contents.insert( - index, - " enabled: %s\n" - % conf_settings["Display Package Progress"], - ) - - if len(contents) > 0: - with open(fn.config_file, "w", encoding="UTF-8") as f: - f.writelines(contents) - - fn.permissions(fn.config_dir) - - except Exception as e: - fn.logger.error("Exception in write_config_file(): %s" % e) - - def read_config_file(self): - try: - if os.path.exists(fn.config_file): - contents = [] - with open(fn.config_file, "r", encoding="UTF-8") as f: - contents = f.readlines() - - # file is empty, string replace template file - if len(contents) == 0: - fn.shutil.copy(default_file, fn.config_file) - fn.permissions(fn.config_dir) - else: - return self.read(contents) - - else: - # config file doesn't exist, string replace template file - fn.shutil.copy(default_file, fn.config_file) - fn.permissions(fn.config_dir) - - with open(fn.config_file, "r", encoding="UTF-8") as f: - contents = f.readlines() - - return self.read(contents) - - except Exception as e: - print("Exception in read_config_file(): %s" % e) - - def read(self, contents): - setting_name = None - setting_value_enabled = None - conf_settings = {} - for line in contents: - if line.startswith("- name:"): - setting_name = ( - line.strip("- name: ").strip().strip('"').strip("\n").strip() - ) - elif line.startswith(" enabled: "): - setting_value_enabled = ( - line.strip(" enabled: ").strip().strip('"').strip("\n").strip() - ) - - if setting_value_enabled == "False": - conf_settings[setting_name] = False - else: - conf_settings[setting_name] = True - - if len(conf_settings) > 0: - return conf_settings - else: - print("[ERROR] Failed to read settings into memory") \ No newline at end of file diff --git a/usr/share/blackbox/blackbox.css b/usr/share/blackbox/blackbox.css deleted file mode 100644 index 515a880..0000000 --- a/usr/share/blackbox/blackbox.css +++ /dev/null @@ -1,61 +0,0 @@ -frame#awesome * { - padding-left: 20px; - padding-right: 20px; - padding-bottom: 20px; - padding-top: 10px; - border-color: transparent; - } - - box#vbox { - padding-right: 20px; - } - - #sidebar label { - font-size: 14px; - font-weight: 500; - padding-left: 15px; - padding-right: 15px; - } - - label#title { - font-size: 20px; - font-weight: 600; - } - - label#lbl_package_version { - color: white; - background-color: #5481e5; - border: 3px solid #5481e5; - border-radius: 100px; - font-size: 80%; - font-weight: bold; - padding-left: 5px; - padding-right: 5px; - } - - infobar#infobar_info { - background-color: #5481e5; - color: white; - } - - infobar#infobar_error { - background-color: #9d261d; - color: white; - } - - infobar#infobar_warning { - background-color: #ffcc00; - color: black; - } - - modelbutton#modelbtn_popover{ - padding-left: 0; - padding-right: 0; - padding-top: 1px; - padding-bottom: 1px; - } - - - modelbutton#modelbtn_popover:hover{ - font-weight: bold; - } \ No newline at end of file diff --git a/usr/share/blackbox/blackbox.py b/usr/share/blackbox/blackbox.py deleted file mode 100644 index 3815d2e..0000000 --- a/usr/share/blackbox/blackbox.py +++ /dev/null @@ -1,1290 +0,0 @@ -#!/usr/bin/env python3 - -import gi -import os - -from requests.packages import package - -import Functions as fn -import signal - -import subprocess -from Functions import os -from queue import Queue -from time import sleep -import sys -import time - -# UI modules -from ui.GUI import GUI -from ui.SplashScreen import SplashScreen -from ui.ProgressBarWindow import ProgressBarWindow -from ui.AppFrameGUI import AppFrameGUI -from ui.AboutDialog import AboutDialog -from ui.MessageDialog import MessageDialog -from ui.PacmanLogWindow import PacmanLogWindow -from ui.PackageListDialog import PackageListDialog -from ui.ProgressDialog import ProgressDialog -from ui.ISOPackagesWindow import ISOPackagesWindow -from ui.PackageSearchWindow import PackageSearchWindow -from ui.PackagesImportDialog import PackagesImportDialog - -# Configuration module -from Settings import Settings - -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -# #============================================================ -# #= Authors: Erik Dubois - Cameron Percival - Fennec = -# #============================================================ - -# Folder structure - -# cache contains descriptions - inside we have corrections for manual intervention -# + installed applications list -# yaml is the folder that is used to create the application -# yaml-awesome is a copy/paste from Calamares to meld manually - not used in the app - -base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class Main(Gtk.Window): - # Create a queue, for worker communication (Multithreading - used in GUI layer) - queue = Queue() - - # Create a queue to handle package install/removal - pkg_queue = Queue() - - # Create a queue for storing search results - search_queue = Queue() - - # Create a queue for storing Pacman log file contents - pacmanlog_queue = Queue() - - # Create a queue for storing packages waiting behind an in-progress pacman install transaction - pkg_holding_queue = Queue() - - def __init__(self): - try: - super(Main, self).__init__(title="BLACKBOX") - - self.set_border_width(10) - self.connect("delete-event", self.on_close) - self.set_position(Gtk.WindowPosition.CENTER) - self.set_icon_from_file(os.path.join(base_dir, "images/snigdhaos-blackbox.png")) - self.set_default_size(1100, 900) - - # ctrl+f give focus to search entry - self.connect("key-press-event", self.on_keypress_event) - - # used for notifications - self.timeout_id = None - - # default: displaying versions are disabled - self.display_versions = False - - # initial app load search_activated is set to False - self.search_activated = False - - # initial app load show the progress dialog window when a package is installed/uninstalled - self.display_package_progress = False - - print( - "---------------------------------------------------------------------------" - ) - print("If you have errors, report it on Snigdha OS Issue") - print( - "---------------------------------------------------------------------------" - ) - print("To report Bug go to https://github.com/Snigdha-OS/snigdhaos-issues") - print( - "---------------------------------------------------------------------------" - ) - print( - "Many applications are coming from the Arch Linux repos and can be installed" - ) - print( - "without any issues. Other applications are available from third party repos" - ) - print("like Chaotic repo, Snigdha OS repo and others.") - print( - "---------------------------------------------------------------------------" - ) - print("We do NOT build packages from AUR.") - print( - "---------------------------------------------------------------------------" - ) - print("Some packages are only available on the Snigdha OS repos.") - print( - "---------------------------------------------------------------------------" - ) - - if os.path.exists(fn.blackbox_lockfile): - running = fn.check_if_process_running("blackbox") - if running is True: - fn.logger.error( - "BlackBox lock file found in %s" % fn.blackbox_lockfile - ) - fn.logger.error("Is there another BlackBox instance running ?") - - sys.exit(1) - - else: - splash_screen = SplashScreen() - - while Gtk.events_pending(): - Gtk.main_iteration() - - sleep(1.5) - splash_screen.destroy() - - # test there is no pacman lock file on the system - if fn.check_pacman_lockfile(): - message_dialog = MessageDialog( - "Error", - "BlackBox cannot proceed pacman lockfile found", - "Pacman cannot lock the db, a lockfile is found inside %s" - % fn.pacman_lockfile, - "Is there another Pacman process running ?", - "error", - False, - ) - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - sys.exit(1) - - fn.logger.info("pkgver = pkgversion") - fn.logger.info("pkgrel = pkgrelease") - print( - "---------------------------------------------------------------------------" - ) - fn.logger.info("Distro = " + fn.distr) - print( - "---------------------------------------------------------------------------" - ) - - # start making sure blackbox starts next time with dark or light theme - if os.path.isdir(fn.home + "/.config/gtk-3.0"): - try: - if not os.path.islink("/root/.config/gtk-3.0"): - if os.path.exists("/root/.config/gtk-3.0"): - fn.shutil.rmtree("/root/.config/gtk-3.0") - - fn.shutil.copytree( - fn.home + "/.config/gtk-3.0", "/root/.config/gtk-3.0" - ) - except Exception as e: - fn.logger.warning("GTK config: %s" % e) - - if os.path.isdir("/root/.config/xsettingsd/xsettingsd.conf"): - try: - if not os.path.islink("/root/.config/xsettingsd/"): - if os.path.exists("/root/.config/xsettingsd/"): - fn.shutil.rmtree("/root/.config/xsettingsd/") - if fn.path.isdir(fn.home + "/.config/xsettingsd/"): - fn.shutil.copytree( - fn.home + "/.config/xsettingsd/", - "/root/.config/xsettingsd/", - ) - except Exception as e: - fn.logger.warning("xsettingsd config: %s" % e) - - # store package information into memory, and use the dictionary returned to search in for quicker retrieval - fn.logger.info("Storing package metadata started") - - self.packages = fn.store_packages() - fn.logger.info("Storing package metadata completed") - - fn.logger.info("Categories = %s" % len(self.packages.keys())) - - total_packages = 0 - - for category in self.packages: - total_packages += len(self.packages[category]) - - fn.logger.info("Total packages = %s" % total_packages) - - fn.logger.info("Setting up GUI") - - GUI.setup_gui( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - fn.settings_config, - ) - - # Create installed.lst file for first time - - fn.get_current_installed() - installed_lst_file = "%s/cache/installed.lst" % base_dir - packages_app_start_file = "%s/%s-packages.txt" % ( - fn.log_dir, - fn.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), - ) - if os.path.exists(installed_lst_file): - fn.logger.info("Created installed.lst") - # Keep log of installed packages before the app makes changes - # fn.shutil.copy(installed_lst_file, packages_app_start_file) - - # pacman sync db and also tests network connectivity - - thread_pacman_sync_db = fn.threading.Thread( - name="thread_pacman_sync_db", - target=self.pacman_db_sync, - daemon=True, - ) - thread_pacman_sync_db.start() - # if self.pacman_db_sync() is False: - # sys.exit(1) - - except Exception as e: - fn.logger.error("Exception in Main() : %s" % e) - - # ===================================================== - # PACMAN DB SYNC - # ===================================================== - - def pacman_db_sync(self): - sync_err = fn.sync_package_db() - - if sync_err is not None: - fn.logger.error("Pacman db synchronization failed") - - print( - "---------------------------------------------------------------------------" - ) - - GLib.idle_add( - self.show_sync_db_message_dialog, - sync_err, - priority=GLib.PRIORITY_DEFAULT, - ) - - else: - fn.logger.info("Pacman db synchronization completed") - - return True - - def show_sync_db_message_dialog(self, sync_err): - message_dialog = MessageDialog( - "Error", - "Pacman db synchronization failed", - "Failed to run command = pacman -Sy\nPacman db synchronization failed\nCheck the synchronization logs, and verify you can connect to the appropriate mirrors\n\n", - sync_err, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - # ===================================================== - # WINDOW KEY EVENT CTRL + F - # ===================================================== - - # sets focus on the search entry - def on_keypress_event(self, widget, event): - shortcut = Gtk.accelerator_get_label(event.keyval, event.state) - - if shortcut in ("Ctrl+F", "Ctrl+Mod2+F"): - # set focus on text entry, select all text if any - self.searchentry.grab_focus() - - if shortcut in ("Ctrl+I", "Ctrl+Mod2+I"): - fn.show_package_info(self) - - # ===================================================== - # SEARCH ENTRY - # ===================================================== - - def on_search_activated(self, searchentry): - if searchentry.get_text_length() == 0 and self.search_activated: - GUI.setup_gui( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - None, - ) - self.search_activated = False - - if searchentry.get_text_length() == 0: - self.search_activated = False - - search_term = searchentry.get_text() - # if the string is completely whitespace ignore searching - if not search_term.isspace(): - try: - if len(search_term.rstrip().lstrip()) > 0: - # test if the string entered by the user is in the package name - # results is a dictionary, which holds a list of packages - # results[category]=pkg_list - - # searching is processed inside a thread - - th_search = fn.threading.Thread( - name="thread_search", - target=fn.search, - args=( - self, - search_term.rstrip().lstrip(), - ), - ) - fn.logger.info("Starting search") - - th_search.start() - - # get the search_results from the queue - results = self.search_queue.get() - - if results is not None: - fn.logger.info("Search complete") - - if len(results) > 0: - total = 0 - for val in results.values(): - total += len(val) - - fn.logger.info("Search found %s results" % total) - # make sure the gui search only displays the pkgs inside the results - - GUI.setup_gui_search( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - results, - search_term, - None, - ) - - self.search_activated = True - else: - fn.logger.info("Search found %s results" % 0) - self.searchentry.grab_focus() - - message_dialog = MessageDialog( - "Info", - "Search returned 0 results", - "Failed to find search term inside the package name or description.", - "Try to search again using another term", - "info", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - elif self.search_activated == True: - GUI.setup_gui( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - None, - ) - self.search_activated = False - except Exception as err: - fn.logger.error("Exception in on_search_activated(): %s" % err) - - finally: - if self.search_activated == True: - self.search_queue.task_done() - - def on_search_cleared(self, searchentry, icon_pos, event): - if self.search_activated: - GUI.setup_gui( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - None, - ) - - self.searchentry.set_placeholder_text("Search...") - - self.search_activated = False - - # ===================================================== - # RESTART/QUIT BUTTON - # ===================================================== - - def on_close(self, widget, data): - # to preserve settings, save current options to conf file inside $HOME/.config/blackbox/blackbox.yaml - - settings = Settings(self.display_versions, self.display_package_progress) - settings.write_config_file() - - # make a final installed packages file inside /var/log/blackbox/ - # this allows a before/after comparison - # fn.on_close_create_packages_file() - - if os.path.exists(fn.blackbox_lockfile): - os.unlink(fn.blackbox_lockfile) - - if os.path.exists(fn.blackbox_pidfile): - os.unlink(fn.blackbox_pidfile) - - # see the comment in fn.terminate_pacman() - fn.terminate_pacman() - - Gtk.main_quit() - print( - "---------------------------------------------------------------------------" - ) - print("Thanks for using BlackBox") - print("Report issues to make it even better") - print( - "---------------------------------------------------------------------------" - ) - print("You can report issues on https://discord.gg/stBhS4taje") - print( - "---------------------------------------------------------------------------" - ) - - # ==================================================================== - # Button Functions - # ==================================================================== - # Given what this function does, it might be worth considering making it a - # thread so that the app doesn't block while installing/uninstalling is happening. - - def app_toggle(self, widget, active, package): - # switch widget is currently toggled off - - if widget.get_state() == False and widget.get_active() == True: - if len(package.name) > 0: - inst_str = [ - "pacman", - "-S", - package.name, - "--needed", - "--noconfirm", - ] - - if self.display_package_progress is True: - if fn.check_pacman_lockfile(): - widget.set_state(False) - widget.set_active(False) - proc = fn.get_pacman_process() - - message_dialog = MessageDialog( - "Warning", - "BlackBox cannot proceed pacman lockfile found", - "Pacman cannot lock the db, a lockfile is found inside %s" - % fn.pacman_lockfile, - "Pacman is running: %s" % proc, - "warning", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - return True - else: - package_metadata = fn.get_package_information(package.name) - - if ( - type(package_metadata) is str - and package_metadata.strip() - == "error: package '%s' was not found" % package.name - ): - self.package_found = False - fn.logger.warning( - "The package %s was not found in any configured Pacman repositories" - % package.name - ) - fn.logger.warning("Package install cannot continue") - - message_dialog = MessageDialog( - "Error", - "Pacman repository error: package '%s' was not found" - % package.name, - "BlackBox cannot process the request", - "Are the correct pacman mirrorlists configured ?", - "error", - False, - ) - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_state(False) - widget.set_active(False) - - return True - else: - widget.set_state(True) - widget.set_active(True) - - progress_dialog = ProgressDialog( - "install", - package, - " ".join(inst_str), - package_metadata, - ) - - progress_dialog.show_all() - - self.pkg_queue.put( - ( - package, - "install", - widget, - inst_str, - progress_dialog, - ), - ) - - th = fn.threading.Thread( - name="thread_pkginst", - target=fn.install, - args=(self,), - daemon=True, - ) - - th.start() - fn.logger.debug("Package-install thread started") - - else: - progress_dialog = None - widget.set_sensitive(False) - - widget.set_active(True) - widget.set_state(True) - - fn.logger.info("Package to install : %s" % package.name) - - # another pacman transaction is running, add items to the holding queue - if ( - fn.check_pacman_lockfile() is True - and self.display_package_progress is False - ): - self.pkg_holding_queue.put( - ( - package, - "install", - widget, - inst_str, - progress_dialog, - ), - ) - - if fn.is_thread_alive("thread_check_holding_queue") is False: - th = fn.threading.Thread( - target=fn.check_holding_queue, - name="thread_check_holding_queue", - daemon=True, - args=(self,), - ) - - th.start() - fn.logger.debug("Check-holding-queue thread started") - elif self.display_package_progress is False: - self.pkg_queue.put( - ( - package, - "install", - widget, - inst_str, - progress_dialog, - ), - ) - - th = fn.threading.Thread( - name="thread_pkginst", - target=fn.install, - args=(self,), - daemon=True, - ) - - th.start() - fn.logger.debug("Package-install thread started") - - # switch widget is currently toggled on - if widget.get_state() == True and widget.get_active() == False: - # Uninstall the package - - if len(package.name) > 0: - uninst_str = ["pacman", "-Rs", package.name, "--noconfirm"] - - fn.logger.info("Package to remove : %s" % package.name) - - if fn.check_pacman_lockfile(): - widget.set_state(True) - widget.set_active(True) - - fn.logger.info("Pacman lockfile found, uninstall aborted") - - GLib.idle_add( - self.show_lockfile_message_dialog, - priority=GLib.PRIORITY_DEFAULT, - ) - - return True - - if self.display_package_progress is True: - package_metadata = fn.get_package_information(package.name) - - progress_dialog = ProgressDialog( - "uninstall", - package, - " ".join(uninst_str), - package_metadata, - ) - - progress_dialog.show_all() - else: - progress_dialog = None - - widget.set_active(False) - widget.set_state(False) - - self.pkg_queue.put( - ( - package, - "uninstall", - widget, - uninst_str, - progress_dialog, - ), - ) - - th = fn.threading.Thread( - name="thread_pkgrem", - target=fn.uninstall, - args=(self,), - daemon=True, - ) - - th.start() - fn.logger.debug("Package-uninstall thread started") - - # fn.print_running_threads() - - # return True to prevent the default handler from running - return True - - # App_Frame_GUI.GUI(self, Gtk, vboxStack1, fn, category, package_file) - # widget.get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().queue_redraw() - # self.gui.hide() - # self.gui.queue_redraw() - # self.gui.show_all() - - def show_lockfile_message_dialog(self): - proc = fn.get_pacman_process() - message_dialog = MessageDialog( - "Warning", - "BlackBox cannot proceed pacman lockfile found", - "Pacman cannot lock the db, a lockfile is found inside %s" - % fn.pacman_lockfile, - "Process running = %s" % proc, - "warning", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - message_dialog.destroy() - - def recache_clicked(self, widget): - # Check if cache is out of date. If so, run the re-cache, if not, don't. - # pb = ProgressBarWindow() - # pb.show_all() - # pb.set_text("Updating Cache") - # pb.reset_timer() - - fn.logger.info("Recache applications - start") - - fn.cache_btn() - - # ================================================================ - # SETTINGS - # ================================================================ - - def on_package_search_clicked(self, widget): - fn.logger.debug("Showing Package Search window") - self.toggle_popover() - - package_search_win = PackageSearchWindow() - package_search_win.show_all() - - def on_snigdhaos_iso_packages_clicked(self, widget): - fn.logger.debug("Showing Snigdha OS ISO Packages window") - snigdhaos_iso_packages_window = ISOPackagesWindow() - snigdhaos_iso_packages_window.show() - - def on_about_app_clicked(self, widget): - fn.logger.debug("Showing About dialog") - self.toggle_popover() - - about = AboutDialog() - about.run() - about.hide() - about.destroy() - - def on_packages_export_clicked(self, widget): - self.toggle_popover() - - dialog_packagelist = PackageListDialog() - dialog_packagelist.show_all() - - def on_packages_import_clicked(self, widget): - self.toggle_popover() - try: - if not os.path.exists(fn.pacman_lockfile): - package_file = "%s/packages-x86_64.txt" % (fn.export_dir,) - package_import_logfile = "%spackages-install-status-%s-%s.log" % ( - fn.log_dir, - fn.datetime.today().date(), - fn.datetime.today().time().strftime("%H-%M-%S"), - ) - - if os.path.exists(package_file): - # check we have a valid file - lines = None - with open(package_file, encoding="utf-8", mode="r") as f: - lines = f.readlines() - - if lines is not None: - if ( - "# This file was auto-generated by the ArchLinux Tweak Tool on" - in lines[0] - or "# This file was auto-generated by BlackBox on" - in lines[0] - ): - fn.logger.info("Package list file is valid") - packages_list = [] - for line in lines: - if not line.startswith("#"): - packages_list.append(line.strip()) - - if len(packages_list) > 0: - dialog_package_import = PackagesImportDialog( - package_file, - packages_list, - package_import_logfile, - ) - dialog_package_import.show_all() - - else: - message_dialog = MessageDialog( - "Error", - "Package file is not valid %s" % package_file, - "Export a list of packages first using the Show Installed Packages button", - "", - "error", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - else: - message_dialog = MessageDialog( - "Warning", - "Cannot locate export package file %s" % package_file, - "Export a list of packages first using the Show Installed Packages button", - "", - "warning", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - else: - message_dialog = MessageDialog( - "Error", - "Pacman lock file found %s" % fn.pacman_lockfile, - "Cannot proceed, another pacman process is running", - "", - "error", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - except Exception as e: - fn.logger.error("Exception in on_packages_import_clicked(): %s" % e) - - # show/hide popover - def toggle_popover(self): - if self.popover.get_visible(): - self.popover.hide() - else: - self.popover.show_all() - - def on_settings_clicked(self, widget): - self.toggle_popover() - - # Snigdha OS keys, mirrors setup - - def snigdhaos_keyring_toggle(self, widget, data): - # toggle is currently off, add keyring - if widget.get_state() == False and widget.get_active() == True: - fn.logger.info("Installing Snigdha OS keyring") - install_keyring = fn.install_snigdhaos_keyring() - - if install_keyring == 0: - fn.logger.info("Installation of Snigdha OS keyring = OK") - rc = fn.add_snigdhaos_repos() - if rc == 0: - fn.logger.info("Snigdha OS repos added into %s" % fn.pacman_conf) - widget.set_active(True) - else: - message_dialog = MessageDialog( - "Error", - "Failed to update pacman conf", - "Errors occurred during update of the pacman config file", - rc, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - - else: - message_dialog = MessageDialog( - "Error", - "Failed to install Snigdha OS keyring", - "Errors occurred during install of the Snigdha OS keyring", - "Command run = %s\n\n Error = %s" - % (install_keyring["cmd_str"], install_keyring["output"]), - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - # toggle is currently on - if widget.get_state() == True and widget.get_active() == False: - remove_keyring = fn.remove_snigdhaos_keyring() - - if remove_keyring == 0: - fn.logger.info("Removing Snigdha OS keyring OK") - - rc = fn.remove_snigdhaos_repos() - if rc == 0: - fn.logger.info("Snigdha OS repos removed from %s" % fn.pacman_conf) - widget.set_active(False) - else: - message_dialog = MessageDialog( - "Error", - "Failed to update pacman conf", - "Errors occurred during update of the pacman config file", - rc, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(True) - widget.set_state(True) - - return True - else: - fn.logger.error("Failed to remove Snigdha OS keyring") - - message_dialog = MessageDialog( - "Error", - "Failed to remove Snigdha OS keyring", - "Errors occurred during removal of the Snigdha OS keyring", - "Command run = %s\n\n Error = %s" - % (remove_keyring["cmd_str"], remove_keyring["output"]), - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - - def snigdhaos_mirrorlist_toggle(self, widget, data): - # self.toggle_popover() - - # toggle is currently off - - if widget.get_state() == False and widget.get_active() == True: - widget.set_active(True) - widget.set_state(True) - - # before installing the mirrorlist make sure the pacman.conf file does not have any references to /etc/pacman.d/snigdhaos-mirrorlist - # otherwise the mirrorlist package will not install - rc_remove = fn.remove_snigdhaos_repos() - if rc_remove == 0: - install_mirrorlist = fn.install_snigdhaos_mirrorlist() - - if install_mirrorlist == 0: - fn.logger.info("Installation of Snigdha OS mirrorlist = OK") - - rc_add = fn.add_snigdhaos_repos() - if rc_add == 0: - fn.logger.info("Snigdha OS repos added into %s" % fn.pacman_conf) - self.pacman_db_sync() - - else: - message_dialog = MessageDialog( - "Error", - "Failed to update pacman conf", - "Errors occurred during update of the pacman config file", - rc_add, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - - else: - fn.logger.error("Failed to install Snigdha OS mirrorlist") - - message_dialog = MessageDialog( - "Error", - "Failed to install Snigdha OS mirrorlist", - "Errors occurred during install of the Snigdha OS mirrorlist", - "Command run = %s\n\n Error = %s" - % (install_mirrorlist["cmd_str"], install_mirrorlist["output"]), - "error", - True, - ) - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - else: - message_dialog = MessageDialog( - "Error", - "Failed to update pacman conf", - "Errors occurred during update of the pacman config file", - rc, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(False) - widget.set_state(False) - - return True - # toggle is currently on - if widget.get_state() == True and widget.get_active() == False: - widget.set_active(False) - widget.set_state(False) - - fn.logger.info("Removing Snigdha OS mirrorlist") - - remove_mirrorlist = fn.remove_snigdhaos_mirrorlist() - - if remove_mirrorlist == 0: - fn.logger.info("Removing Snigdha OS mirrorlist OK") - - rc = fn.remove_snigdhaos_repos() - if rc == 0: - fn.logger.info("Snigdha OS repos removed from %s" % fn.pacman_conf) - widget.set_active(False) - else: - message_dialog = MessageDialog( - "Error", - "Failed to update pacman conf", - "Errors occurred during update of the pacman config file", - rc, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(True) - widget.set_state(True) - - return True - else: - fn.logger.error("Failed to remove Snigdha OS mirrorlist") - - message_dialog = MessageDialog( - "Error", - "Failed to remove Snigdha OS mirrorlist", - "Errors occurred during removal of the Snigdha OS mirrorlist", - "Command run = %s\n\n Error = %s" - % (remove_mirrorlist["cmd_str"], remove_mirrorlist["output"]), - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - widget.set_active(True) - widget.set_state(True) - - return True - - return True - - def version_toggle(self, widget, data): - if widget.get_active() == True: - fn.logger.debug("Showing package versions") - - self.display_versions = True - GLib.idle_add( - self.refresh_main_gui, - priority=GLib.PRIORITY_DEFAULT, - ) - else: - fn.logger.debug("Hiding package versions") - self.display_versions = False - GLib.idle_add( - self.refresh_main_gui, - priority=GLib.PRIORITY_DEFAULT, - ) - - def refresh_main_gui(self): - self.remove(self.vbox) - GUI.setup_gui(self, Gtk, Gdk, GdkPixbuf, base_dir, os, Pango, None) - self.show_all() - - def on_pacman_log_clicked(self, widget): - try: - self.toggle_popover() - - thread_addlog = "thread_addPacmanLogQueue" - self.thread_add_pacmanlog_alive = fn.is_thread_alive(thread_addlog) - - if self.thread_add_pacmanlog_alive == False: - fn.logger.info("Starting thread to monitor Pacman Log file") - - th_add_pacmanlog_queue = fn.threading.Thread( - name=thread_addlog, - target=fn.add_pacmanlog_queue, - args=(self,), - daemon=True, - ) - th_add_pacmanlog_queue.start() - - if self.thread_add_pacmanlog_alive is True: - # need to recreate the textview, can't use existing reference as it throws a seg fault - - self.textview_pacmanlog = Gtk.TextView() - self.textview_pacmanlog.set_property("editable", False) - self.textview_pacmanlog.set_property("monospace", True) - self.textview_pacmanlog.set_border_width(10) - self.textview_pacmanlog.set_vexpand(True) - self.textview_pacmanlog.set_hexpand(True) - - # use the reference to the text buffer initialized before the logtimer thread started - self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog) - - window_pacmanlog = PacmanLogWindow( - self.textview_pacmanlog, - self.modelbtn_pacmanlog, - ) - window_pacmanlog.show_all() - - self.start_logtimer = window_pacmanlog.start_logtimer - - else: - # keep a handle on the textbuffer, this is needed again later, if the pacman log file dialog is closed - # since the textbuffer will already hold textdata at that point - - # textview is used inside another thread to update as the pacmanlog file is read into memory - self.textbuffer_pacmanlog = Gtk.TextBuffer() - - self.textview_pacmanlog = Gtk.TextView() - self.textview_pacmanlog.set_property("editable", False) - self.textview_pacmanlog.set_property("monospace", True) - self.textview_pacmanlog.set_border_width(10) - self.textview_pacmanlog.set_vexpand(True) - self.textview_pacmanlog.set_hexpand(True) - - self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog) - - window_pacmanlog = PacmanLogWindow( - self.textview_pacmanlog, - self.modelbtn_pacmanlog, - ) - window_pacmanlog.show_all() - - thread_logtimer = "thread_startLogTimer" - thread_logtimer_alive = False - - thread_logtimer_alive = fn.is_thread_alive(thread_logtimer) - - # a flag to indicate that the textview will need updating, used inside fn.start_log_timer - self.start_logtimer = True - - if thread_logtimer_alive == False: - th_logtimer = fn.threading.Thread( - name=thread_logtimer, - target=fn.start_log_timer, - args=(self, window_pacmanlog), - daemon=True, - ) - th_logtimer.start() - - self.thread_add_pacmanlog_alive = True - self.modelbtn_pacmanlog.set_sensitive(False) - - except Exception as e: - fn.logger.error("Exception in on_pacman_log_clicked() : %s" % e) - - def package_progress_toggle(self, widget, data): - if widget.get_active() is True: - self.display_package_progress = True - if widget.get_active() is False: - self.display_package_progress = False - - -# ==================================================================== -# MAIN -# ==================================================================== - - -def signal_handler(sig, frame): - fn.logger.info("BlackBox is closing.") - if os.path.exists("/tmp/blackbox.lock"): - os.unlink("/tmp/blackbox.lock") - - if os.path.exists("/tmp/blackbox.pid"): - os.unlink("/tmp/blackbox.pid") - Gtk.main_quit(0) - - -# These should be kept as it ensures that multiple installation instances can't be run concurrently. -if __name__ == "__main__": - try: - signal.signal(signal.SIGINT, signal_handler) - - if not os.path.isfile("/tmp/blackbox.lock"): - with open("/tmp/blackbox.pid", "w") as f: - f.write(str(os.getpid())) - - style_provider = Gtk.CssProvider() - style_provider.load_from_path(base_dir + "/blackbox.css") - - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - style_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, - ) - w = Main() - w.show_all() - - fn.logger.info("App Started") - - Gtk.main() - else: - fn.logger.info("BlackBox lock file found") - - md = Gtk.MessageDialog( - parent=Main(), - flags=0, - message_type=Gtk.MessageType.INFO, - buttons=Gtk.ButtonsType.YES_NO, - text="BlackBox Lock File Found", - ) - md.format_secondary_markup( - "A BlackBox lock file has been found. This indicates there is already an instance of BlackBox running.\n\ - Click 'Yes' to remove the lock file and try running again" - ) # noqa - - result = md.run() - md.destroy() - - if result in (Gtk.ResponseType.OK, Gtk.ResponseType.YES): - pid = "" - if os.path.exists(fn.blackbox_pidfile): - with open("/tmp/blackbox.pid", "r") as f: - line = f.read() - pid = line.rstrip().lstrip() - - if fn.check_if_process_running(int(pid)): - # needs to be fixed - todo - - # md2 = Gtk.MessageDialog( - # parent=Main, - # flags=0, - # message_type=Gtk.MessageType.INFO, - # buttons=Gtk.ButtonsType.OK, - # title="Application Running!", - # text="You first need to close the existing application", - # ) - # md2.format_secondary_markup( - # "You first need to close the existing application" - # ) - # md2.run() - fn.logger.info( - "You first need to close the existing application" - ) - else: - os.unlink("/tmp/blackbox.lock") - sys.exit(1) - else: - # in the rare event that the lock file is present, but the pid isn't - os.unlink("/tmp/blackbox.lock") - sys.exit(1) - else: - sys.exit(1) - except Exception as e: - fn.logger.error("Exception in __main__: %s" % e) \ No newline at end of file diff --git a/usr/share/blackbox/default/blackbox.yml b/usr/share/blackbox/default/blackbox.yml deleted file mode 100644 index 004dcd5..0000000 --- a/usr/share/blackbox/default/blackbox.yml +++ /dev/null @@ -1,11 +0,0 @@ -- name: "Display Package Versions" - description: "Show package version labels alongside the package description " - enabled: False - -- name: "Display Package Progress" - description: "Show the package install/uninstall progress window " - enabled: False - -- name: "Debug Logging" - description: "Enable debug logging for more verbose app logging " - enabled: False \ No newline at end of file diff --git a/usr/share/blackbox/images/blackbox.png b/usr/share/blackbox/images/blackbox.png deleted file mode 100644 index ea9dc48445b3f27671b1a06a519d0612b06615b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12126 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 bNB(c`*~Gt(m^U*pFfe$!`njxgN@xNAH)M2X diff --git a/usr/share/blackbox/scripts/get-the-keys-and-repos.sh b/usr/share/blackbox/scripts/get-the-keys-and-repos.sh deleted file mode 100644 index 018d625..0000000 --- a/usr/share/blackbox/scripts/get-the-keys-and-repos.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -###################################################################################################################### - -sudo pacman -S wget --noconfirm --needed - -echo "Getting the Snigdha OS keys from the Snigdha PS repo - report if link is broken" -sudo wget https://github.com/arcolinux/arcolinux_repo/raw/main/x86_64/arcolinux-keyring-20251209-3-any.pkg.tar.zst -O /tmp/arcolinux-keyring-20251209-3-any.pkg.tar.zst -sudo pacman -U --noconfirm --needed /tmp/arcolinux-keyring-20251209-3-any.pkg.tar.zst - -echo "Getting the latest arcolinux mirrors file - report if link is broken" -sudo wget https://github.com/arcolinux/arcolinux_repo/raw/main/x86_64/arcolinux-mirrorlist-git-23.06-01-any.pkg.tar.zst -O /tmp/arcolinux-mirrorlist-git-23.06-01-any.pkg.tar.zst -sudo pacman -U --noconfirm --needed /tmp/arcolinux-mirrorlist-git-23.06-01-any.pkg.tar.zst - -###################################################################################################################### - -if grep -q snigdhaos-core /etc/pacman.conf; then - - echo "Snigdha OS repos are already in /etc/pacman.conf" - -else - -echo ' -[snigdhaos-core] -SigLevel = PackageRequired DatabaseNever -Include = /etc/pacman.d/snigdhaos-mirrorlist - -[snigdhaos-extra] -SigLevel = PackageRequired DatabaseNever -Include = /etc/pacman.d/snigdhaos-mirrorlist' | sudo tee --append /etc/pacman.conf - -fi - -echo "DONE - UPDATE NOW" \ No newline at end of file diff --git a/usr/share/blackbox/ui/AboutDialog.py b/usr/share/blackbox/ui/AboutDialog.py deleted file mode 100644 index 3c2c215..0000000 --- a/usr/share/blackbox/ui/AboutDialog.py +++ /dev/null @@ -1,192 +0,0 @@ -# This class stores static information about the app, and is displayed in the about dialog -import os -import gi - -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class AboutDialog(Gtk.Dialog): - def __init__(self): - Gtk.Dialog.__init__(self) - - app_name = "BlackBox" - app_title = "About BlackBox" - app_main_description = "%s - %s" % (app_name, "Software Installer Remover") - app_secondary_message = "Install or remove software from your ArcoLinux system" - app_secondary_description = "Report issues to make it even better" - app_version = "pkgversion-pkgrelease" - app_discord = "https://discord.gg/stBhS4taje" - app_website = "https://arcolinux.info" - app_github = "https://github.com/Snigdha-OS/blackbox" - app_authors = [] - app_authors.append(("Cameron Percival", None)) - app_authors.append(("Fennec", None)) - app_authors.append(("Erik Dubois", None)) - - pixbuf = GdkPixbuf.Pixbuf().new_from_file_at_size( - os.path.join(base_dir, "images/blackbox.png"), 100, 100 - ) - app_image = Gtk.Image().new_from_pixbuf(pixbuf) - - self.set_resizable(False) - self.set_size_request(560, 350) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - self.set_border_width(10) - - headerbar = Gtk.HeaderBar() - headerbar.set_show_close_button(True) - headerbar.set_title(app_title) - self.set_titlebar(headerbar) - - btn_about_close = Gtk.Button(label="OK") - btn_about_close.connect("clicked", self.on_response, "response") - - stack = Gtk.Stack() - stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) - stack.set_transition_duration(350) - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - stack_switcher = Gtk.StackSwitcher() - stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) - stack_switcher.set_stack(stack) - stack_switcher.set_homogeneous(True) - - lbl_main_description = Gtk.Label(xalign=0, yalign=0) - lbl_main_description.set_markup( - " %s" % app_main_description - ) - - lbl_secondary_message = Gtk.Label(xalign=0, yalign=0) - lbl_secondary_message.set_text( - " %s" % app_secondary_message - ) - - lbl_secondary_description = Gtk.Label(xalign=0, yalign=0) - lbl_secondary_description.set_text( - " %s" % app_secondary_description - ) - - lbl_version = Gtk.Label(xalign=0, yalign=0) - lbl_version.set_markup( - " Version: %s" % app_version - ) - - ivbox_about = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - ivbox_about.pack_start(app_image, True, True, 0) - ivbox_about.pack_start(lbl_main_description, True, True, 0) - ivbox_about.pack_start(lbl_version, True, True, 0) - ivbox_about.pack_start(lbl_secondary_message, True, True, 0) - ivbox_about.pack_start(lbl_secondary_description, True, True, 0) - - stack.add_titled(ivbox_about, "About BlackBox", "About") - - grid_support = Gtk.Grid() - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text(" ") - - grid_support.attach(lbl_padding1, 0, 1, 1, 1) - - lbl_support_title = Gtk.Label(xalign=0, yalign=0) - lbl_support_title.set_markup("Discord ") - - lbl_support_value = Gtk.Label(xalign=0, yalign=0) - lbl_support_value.set_markup("%s" % (app_discord, app_discord)) - - lbl_website_title = Gtk.Label(xalign=0, yalign=0) - lbl_website_title.set_markup("ArcoLinux website ") - - lbl_website_value = Gtk.Label(xalign=0, yalign=0) - lbl_website_value.set_markup("%s" % (app_website, app_website)) - - lbl_github_title = Gtk.Label(xalign=0, yalign=0) - lbl_github_title.set_markup("GitHub ") - - lbl_github_value = Gtk.Label(xalign=0, yalign=0) - lbl_github_value.set_markup("%s" % (app_github, app_github)) - - grid_support.attach(lbl_support_title, 0, 2, 1, 1) - - grid_support.attach_next_to( - lbl_support_value, lbl_support_title, Gtk.PositionType.RIGHT, 20, 1 - ) - - grid_support.attach(lbl_website_title, 0, 3, 1, 1) - grid_support.attach_next_to( - lbl_website_value, lbl_website_title, Gtk.PositionType.RIGHT, 20, 1 - ) - - grid_support.attach(lbl_github_title, 0, 4, 1, 1) - grid_support.attach_next_to( - lbl_github_value, lbl_github_title, Gtk.PositionType.RIGHT, 20, 1 - ) - - stack.add_titled(grid_support, "Support", "Support") - - box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - box_outer.set_border_width(10) - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text(" ") - - lbl_padding3 = Gtk.Label(xalign=0, yalign=0) - lbl_padding3.set_text(" ") - - lbl_authors_title = Gtk.Label(xalign=0, yalign=0) - lbl_authors_title.set_text( - "The following people have contributed to the development of %s" % app_name - ) - - listbox = Gtk.ListBox() - listbox.set_selection_mode(Gtk.SelectionMode.NONE) - - box_outer.pack_start(lbl_authors_title, True, True, 0) - box_outer.pack_start(listbox, True, True, 0) - - treestore_authors = Gtk.TreeStore(str, str) - for item in app_authors: - treestore_authors.append(None, list(item)) - - treeview_authors = Gtk.TreeView(model=treestore_authors) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn(None, renderer, text=0) - - treeview_authors.append_column(column) - - path = Gtk.TreePath.new_from_indices([0]) - - selection = treeview_authors.get_selection() - - selection.select_path(path) - - treeview_authors.expand_all() - treeview_authors.columns_autosize() - - row_authors = Gtk.ListBoxRow() - vbox_authors = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - row_authors.add(vbox_authors) - - vbox_authors.pack_start(treeview_authors, True, True, 0) - - listbox.add(row_authors) - - stack.add_titled(box_outer, "Authors", "Authors") - - self.connect("response", self.on_response) - - self.vbox.add(stack_switcher) - self.vbox.add(stack) - - self.show_all() - - def on_response(self, dialog, response): - self.hide() - self.destroy() \ No newline at end of file diff --git a/usr/share/blackbox/ui/AppFrameGUI.py b/usr/share/blackbox/ui/AppFrameGUI.py deleted file mode 100644 index ada506f..0000000 --- a/usr/share/blackbox/ui/AppFrameGUI.py +++ /dev/null @@ -1,523 +0,0 @@ -# ================================================================= -# = Author: Cameron Percival = -# ================================================================= -from socket import TIPC_ADDR_NAME -from urllib.parse import scheme_chars -import Functions as fn - - -class AppFrameGUI: - def build_ui_frame(self, Gtk, vbox_stack, category, packages_list): - try: - # Lets set some variables that we know we will need later - # hboxes and items to make the page look sensible - cat_name = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - seperator = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - lbl1 = Gtk.Label(xalign=0) - lbl1.set_text(category) - lbl1.set_name("title") - hseparator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - seperator.pack_start(hseparator, True, True, 0) - cat_name.pack_start(lbl1, False, False, 0) - - # Stack for the different subcategories - I like crossfade as a transition, but you choose - stack = Gtk.Stack() - stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) - stack.set_transition_duration(350) - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - # Stack needs a stack switcher to allow the user to make different choices - stack_switcher = Gtk.StackSwitcher() - stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) - stack_switcher.set_stack(stack) - stack_switcher.set_homogeneous(True) - - # We will need a vbox later for storing the stack and stack switcher together at the end - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - # create scroller for when/if these items go "off the page" - scrolled_switch = Gtk.ScrolledWindow() - scrolled_switch.add(stack_switcher) - - # These lists will ensure that we can keep track of the individual windows and their names - # stack of vboxes - vbox_stacks = [] - # name of each vbox - derived from the sub category name - vbox_stacknames = [] - sep_text = " " - subcats = {} - # index for the grid - index = 0 - - """ - Store a list of unique sub-categories - e.g. - - category --> applications - sub category --> Accessories - sub category --> Conky - - """ - - sub_catlabels = [] - - # store unique subcategory names into a dictionary - - for package in packages_list: - subcats[package.subcategory] = package - - # we now iterate across the dictionary keys - # each Stack has an associated subcategory - - for subcat in subcats.keys(): - vbox_stacks.append( - Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - ) - # for the sub-cat page title - sub_catlabels.append(Gtk.Label(xalign=0)) - - vbox_stacknames.append(subcat) - # iterate across a list of packages - - for package in packages_list: - if package.subcategory == subcat: - page = vbox_stacks.pop() - - if len(sub_catlabels) > 0: - lbl_title = sub_catlabels.pop() - lbl_desc = Gtk.Label(xalign=0) - lbl_desc.set_markup( - "Description: " - + package.subcategory_description - + "" - ) - lbl_title.set_markup("" + package.subcategory + "") - - lbl_padding_page1 = Gtk.Label(xalign=0) - lbl_padding_page1.set_text("") - - page.pack_start(lbl_title, False, False, 0) - page.pack_start(lbl_desc, False, False, 0) - page.pack_start(lbl_padding_page1, False, False, 0) - - grid = Gtk.Grid() - - grid.insert_row(index) - - lbl_sep1 = Gtk.Label(xalign=0, yalign=0) - lbl_sep1.set_text(sep_text) - grid.attach(lbl_sep1, 0, index, 1, 1) - lbl_package = Gtk.Label(xalign=0, yalign=0) # was in for loop - - lbl_package.set_markup("%s" % package.name) - - ###### switch widget starts ###### - - # construct new switch - switch = Gtk.Switch() - switch.set_valign(Gtk.Align.CENTER) - - """ - Changed to use signal state-set for switch widget. - set_state(boolean) allows the switch state to be enabled/disabled. - When a pkg install/uninstall fails, the switch widget is enabled/disabled inside a thread. - - Changing the switch using set_active(bool), and using the signal notify::active - caused a never-ending loop which would call app_toggle. - - """ - switch.set_state(fn.query_pkg(package.name)) - switch.connect( - "state-set", - self.app_toggle, - package, - ) - - # add switch widget to grid - - # attach_next_to(child, sibling, side, width, height) - - grid.attach_next_to( - switch, lbl_sep1, Gtk.PositionType.LEFT, 1, 1 - ) - - # add space seperator next to switch - - lbl_sep_switch = Gtk.Label(xalign=0, yalign=0) - lbl_sep_switch.set_text(sep_text) - - grid.attach_next_to( - lbl_sep_switch, switch, Gtk.PositionType.LEFT, 1, 1 - ) - - ###### switch widget ends ###### - - ###### pkg name label widget starts ###### - - lbl_sep_package1 = Gtk.Label(xalign=0, yalign=0) - lbl_sep_package1.set_text(sep_text) - - # add space seperator next to switch for extra padding - - grid.attach_next_to( - lbl_sep_package1, switch, Gtk.PositionType.RIGHT, 1, 1 - ) - - lbl_sep_package2 = Gtk.Label(xalign=0, yalign=0) - lbl_sep_package2.set_text(sep_text) - - # add pkg name label widget to grid - - grid.attach_next_to( - lbl_package, lbl_sep_package1, Gtk.PositionType.RIGHT, 1, 1 - ) - - ###### pkg name label widget ends - - ###### pkg desc label widget starts ###### - - lbl_sep_package_desc = Gtk.Label(xalign=0, yalign=0) - lbl_sep_package_desc.set_text(sep_text) - - # add space seperator next to pkg name for extra padding - - grid.attach_next_to( - lbl_sep_package_desc, - lbl_package, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - lbl_package_desc = Gtk.Label(xalign=0, yalign=0) - lbl_package_desc.set_text(package.description) - - # add pkg desc label widget to grid - - grid.attach_next_to( - lbl_package_desc, - lbl_sep_package_desc, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - ###### pkg desc label widget ends - - ##### add pkg version label widget starts ##### - - if self.display_versions is True: - lbl_package_version = Gtk.Label(xalign=0, yalign=0) - lbl_package_version.set_text(package.version) - lbl_package_version.set_name("lbl_package_version") - - lbl_sep_package_version = Gtk.Label(xalign=0, yalign=0) - lbl_sep_package_version.set_text(sep_text) - - grid.attach_next_to( - lbl_sep_package_version, - lbl_package_desc, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid.attach_next_to( - lbl_package_version, - lbl_sep_package_version, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - ##### pkg version ends ##### - - # make the page scrollable - grid_sc = Gtk.ScrolledWindow() - - # hide the horizontal scrollbar showing on each grid row if the window width is resized - grid_sc.set_policy( - Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC - ) - grid_sc.add(grid) - - grid_sc.set_propagate_natural_height(True) - # pack the grid to the page. - - page.pack_start(grid_sc, True, True, 0) - # save the page - put it back (now populated) - - """ - UI note. - To remove the extra padding around the switch buttons - Comment out the references to grid_sc - Then just have page.pack_start(grid,True, True, 0) - """ - vbox_stacks.append(page) - - # reset the things that we need to. - # packages.clear() - grid = Gtk.Grid() - - index += 1 - - # Now we pack the stack - item_num = 0 - - for item in vbox_stacks: - stack.add_titled( - item, - "stack" + str(item_num), - vbox_stacknames[item_num], - ) - item_num += 1 - - # Place the stack switcher and the stack together into a vbox - vbox.pack_start(scrolled_switch, False, False, 0) - - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_propagate_natural_height(True) - scrolled_window.add(stack) - vbox.pack_start(scrolled_window, True, True, 0) - - # Stuff the vbox with the title and seperator to create the page - vbox_stack.pack_start(cat_name, False, False, 0) - vbox_stack.pack_start(seperator, False, False, 0) - vbox_stack.pack_start(vbox, False, False, 0) - - except Exception as e: - fn.logger.error("Exception in App_Frame_GUI.GUI(): %s" % e) - - -########## PREVIOUS GUI CODE START ########## -""" -def GUI(self, Gtk, vboxStack1, category, package_file): - try: - # Lets set some variables that we know we will need later - # hboxes and items to make the page look sensible - cat_name = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - seperator = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - lbl1 = Gtk.Label(xalign=0) - lbl1.set_text(category) - lbl1.set_name("title") - hseparator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - seperator.pack_start(hseparator, True, True, 0) - cat_name.pack_start(lbl1, False, False, 0) - - # Stack for the different subcategories - I like crossfade as a transition, but you choose - stack = Gtk.Stack() - stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) - stack.set_transition_duration(350) - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - # Stack needs a stack switcher to allow the user to make different choices - stack_switcher = Gtk.StackSwitcher() - stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) - stack_switcher.set_stack(stack) - stack_switcher.set_homogeneous(True) - - # We will need a vbox later for storing the stack and stack switcher together at the end - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - # create scroller for when/if these items go "off the page" - scrolledSwitch = Gtk.ScrolledWindow() - scrolledSwitch.add(stack_switcher) - - # These lists will ensure that we can keep track of the individual windows and their names - # stack of vboxes - vboxStacks = [] - # name of each vbox - derived from the sub category name - vboxStackNames = [] - - # page variables - reset these when making multiple subcategories: - # List of packages for any given subcategory - packages = [] - # labels: - name = "" - description = "" - # Lets start by reading in the package list and saving it as a file - with open(package_file, "r") as f: - content = f.readlines() - # f.close() - - # Add a line to the end of content to force the page to be packed. - content.append( - "pack now" - ) # Really, this can be any string, as long as it doesn't match the if statement below. - # Now lets read the file, and use some logic to drive what goes where - # Optomised for runspeed: the line most commonly activated appears first. - for line in content: - # this line will handle code in the yaml that we simply don't need or care about - # MAINTENANCE; if the structure of the .yaml file ever changes, this WILL likely need to be updated - if line.startswith(" packages:"): - continue - elif line.startswith(" - "): - # add the package to the packages list - package = line.strip(" - ") - packages.append(package) - # TODO: Add list and function to obtain package description from pacman and store it (maybe? Maybe the yaml file has what we need?) - elif line.startswith(" description: "): - # Set the label text for the description line - description = ( - line.strip(" description: ").strip().strip('"').strip("\n") - ) - else: - # We will only hit here for category changes, or to pack the page, or if the yaml is changed. - # Yaml changes are handled in the first if statement. - # Pack page; - - if len(packages) > 0: - # Pack the page - # Packing list: - # vbox to pack into - pop it off the - page = vboxStacks.pop() - # grid it - grid = Gtk.Grid() - # Subcat - lblName = Gtk.Label(xalign=0) - lblName.set_markup("" + name + "") - page.pack_start(lblName, False, False, 0) - # description - lblDesc = Gtk.Label(xalign=0) - lblDesc.set_markup("Description: " + description + "") - page.pack_start(lblDesc, False, False, 0) - # packages - sep_text = " " - for i in range(len(packages)): - grid.insert_row(i) - # hbox_pkg = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - lblSep1 = Gtk.Label(xalign=0, yalign=0) - lblSep1.set_text(sep_text) - grid.attach(lblSep1, 0, i, 1, 1) - lblPkg = Gtk.Label(xalign=0, yalign=0) # was in for loop - - lblPkg.set_markup("%s" % packages[i].strip()) # was in for loop - # hbox_pkg.pack_start(lblPkg, False, False, 100) - ###### switch widget starts ###### - - - # construct new switch - switch = Gtk.Switch() - - switch.set_active(Functions.query_pkg(packages[i])) - switch.connect( - "notify::active", - self.app_toggle, - packages[i], - Gtk, - vboxStack1, - Functions, - category, - ) - - # add switch widget to grid - - # attach_next_to(child, sibling, side, width, height) - - grid.attach_next_to( - switch, lblSep1, Gtk.PositionType.LEFT, 1, 1 - ) - - # add space seperator next to switch - - lblSepSwitch = Gtk.Label(xalign=0, yalign=0) - lblSepSwitch.set_text(sep_text) - - grid.attach_next_to( - lblSepSwitch, switch, Gtk.PositionType.LEFT, 1, 1 - ) - - ###### switch widget ends ###### - - - ###### pkg name label widget starts ###### - - lblSepPkg1 = Gtk.Label(xalign=0, yalign=0) - lblSepPkg1.set_text(sep_text) - - - # add space seperator next to switch for extra padding - - grid.attach_next_to( - lblSepPkg1, switch, Gtk.PositionType.RIGHT, 1, 1 - ) - - lblSepPkg2 = Gtk.Label(xalign=0, yalign=0) - lblSepPkg2.set_text(sep_text) - - # add pkg name label widget to grid - - grid.attach_next_to( - lblPkg, lblSepPkg1, Gtk.PositionType.RIGHT, 1, 1 - ) - - ###### pkg name label widget ends - - - ###### pkg desc label widget starts ###### - - lblSepPkgDesc = Gtk.Label(xalign=0, yalign=0) - lblSepPkgDesc.set_text(sep_text) - - # add space seperator next to pkg name for extra padding - - grid.attach_next_to( - lblSepPkgDesc, lblPkg, Gtk.PositionType.RIGHT, 1, 1 - ) - - lblPkgDesc = Gtk.Label(xalign=0, yalign=0) - lblPkgDesc.set_text(Functions.obtain_pkg_description(packages[i])) - - # add pkg desc label widget to grid - - grid.attach_next_to( - lblPkgDesc, lblSepPkgDesc, Gtk.PositionType.RIGHT, 1, 1 - ) - - - - - ###### pkg desc label widget ends - - # make the page scrollable - grid_sc = Gtk.ScrolledWindow() - grid_sc.add(grid) - - grid_sc.set_propagate_natural_height(True) - # pack the grid to the page. - page.pack_start(grid_sc, False, False, 0) - # save the page - put it back (now populated) - vboxStacks.append(page) - # reset the things that we need to. - packages.clear() - grid = Gtk.Grid() - # category change - if line.startswith("- name: "): - # Generate the vboxStack item and name for use later (and in packing) - name = line.strip("- name: ").strip().strip('"') - vboxStackNames.append(name) - vboxStacks.append( - Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - ) - - # Now we pack the stack - item_num = 0 - for item in vboxStacks: - stack.add_titled(item, "stack" + str(item_num), vboxStackNames[item_num]) - item_num += 1 - - # Place the stack switcher and the stack together into a vbox - vbox.pack_start(scrolledSwitch, False, False, 0) - vbox.pack_start(stack, True, True, 0) - - # Stuff the vbox with the title and seperator to create the page - vboxStack1.pack_start(cat_name, False, False, 0) - vboxStack1.pack_start(seperator, False, False, 0) - vboxStack1.pack_start(vbox, False, False, 0) - - except Exception as e: - print("Exception in App_Frame_GUI.GUI(): %s" % e) -""" -########## PREVIOUS GUI CODE END ########## \ No newline at end of file diff --git a/usr/share/blackbox/ui/GUI.py b/usr/share/blackbox/ui/GUI.py deleted file mode 100644 index f449a15..0000000 --- a/usr/share/blackbox/ui/GUI.py +++ /dev/null @@ -1,726 +0,0 @@ -# ================================================================= -# = Author: Cameron Percival = -# ================================================================= - - -# ============Functions============ -import Functions as fn -from ui.AppFrameGUI import AppFrameGUI -from multiprocessing import cpu_count -from queue import Queue -from threading import Thread - -base_dir = fn.os.path.abspath(fn.os.path.join(fn.os.path.dirname(__file__), "..")) -# base_dir = fn.os.path.dirname(fn.os.path.realpath(__file__)) - - -class GUI_Worker(Thread): - def __init__(self, queue): - Thread.__init__(self) - self.queue = queue - - def run(self): - while True: - # pull what we need from the queue so we can process properly. - items = self.queue.get() - - try: - # make sure we have the required number of items on the queue - if items is not None: - # self, Gtk, vboxStack1, category, package_file = items - - self, Gtk, vbox_stack, category, packages = items - - AppFrameGUI.build_ui_frame( - self, - Gtk, - vbox_stack, - category, - packages, - ) - - except Exception as e: - fn.logger.error("Exception in GUI_Worker(): %s" % e) - finally: - if items is None: - fn.logger.debug("Stopping GUI Worker thread") - self.queue.task_done() - return False - self.queue.task_done() - - -class GUI: - def setup_gui_search( - self, - Gtk, - Gdk, - GdkPixbuf, - base_dir, - os, - Pango, - search_results, - search_term, - settings, - ): - try: - # remove previous vbox - if self.search_activated == False: - self.remove(self.vbox) - else: - self.remove(self.vbox_search) - - # lets quickly create the latest installed list. - fn.get_current_installed() - - # ======================================================= - # HeaderBar - # ======================================================= - - setup_headerbar(self, Gtk, settings) - - # ======================================================= - # App Notifications - # ======================================================= - - hbox0 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - - self.notification_revealer = Gtk.Revealer() - self.notification_revealer.set_reveal_child(False) - - self.notification_label = Gtk.Label() - - pb_panel = GdkPixbuf.Pixbuf().new_from_file(base_dir + "/images/panel.png") - panel = Gtk.Image().new_from_pixbuf(pb_panel) - - overlay_frame = Gtk.Overlay() - overlay_frame.add(panel) - overlay_frame.add_overlay(self.notification_label) - - self.notification_revealer.add(overlay_frame) - - hbox0.pack_start(self.notification_revealer, True, False, 0) - - # ========================================================== - # CONTAINER - # ========================================================== - - self.vbox_search = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) - - self.vbox_search.pack_start(hbox, True, True, 0) - self.add(self.vbox_search) - - # ========================================================== - # PREP WORK - # ========================================================== - - # This section sets up the tabs, and the array for dealing with the tab content - - # ========================================================== - # GENERATE STACK - # ========================================================== - stack = Gtk.Stack() - # stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) - stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) - stack.set_transition_duration(350) - - vbox_stack = [] - stack_item = 0 - - # Max Threads - """ - Fatal Python error: Segmentation fault - This error happens randomly, due to the for loop iteration on the cpu_count - old code: for x in range(cpu_count()): - """ - - # spawn only 1 GUI_Worker threads, as any number greater causes a Segmentation fault - - search_worker = GUI_Worker(self.queue) - search_worker.name = "thread_GUI_search_worker" - # Set the worker to be True to allow processing, and avoid Blocking - # search_worker.daemon = True - search_worker.start() - - # This code section might look a little weird. It is because it was - # derived from another function before this version was required. - - for category in search_results: - # NOTE: IF the yaml file name standard changes, be sure to update this, or weirdness will follow. - - # subcategory = search_results[category][0].subcategory - vbox_stack.append( - Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) - ) - stack.add_titled( - vbox_stack[stack_item], - str("stack" + str(len(vbox_stack))), - category, - ) - - # subcategory_desc = search_results[category][0].subcategory_description - search_res_lst = search_results[category] - - # Multithreading! - - self.queue.put( - ( - self, - Gtk, - vbox_stack[stack_item], - category, - search_res_lst, - ) - ) - - stack_item += 1 - - # send a signal that no further items are to be put on the queue - self.queue.put(None) - # safety to ensure that we finish threading before we continue on. - self.queue.join() - fn.logger.debug("GUI Worker thread completed") - - stack_switcher = Gtk.StackSidebar() - stack_switcher.set_name("sidebar") - stack_switcher.set_stack(stack) - - # ===================================================== - # LOGO - # ===================================================== - - ivbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) - pixbuf = GdkPixbuf.Pixbuf().new_from_file_at_size( - os.path.join(base_dir, "images/blackbox.png"), 45, 45 - ) - image = Gtk.Image().new_from_pixbuf(pixbuf) - - # remove the focus on startup from search entry - ivbox.set_property("can-focus", True) - Gtk.Window.grab_focus(ivbox) - - # ===================================================== - # RECACHE BUTTON - # ===================================================== - - btn_recache = Gtk.Button(label="Recache Applications") - btn_recache.connect("clicked", self.recache_clicked) - # btnReCache.set_property("has-tooltip", True) - # btnReCache.connect("query-tooltip", self.tooltip_callback, - # "Refresh the application cache") - - # ===================================================== - # REPOS - # ===================================================== - - # if not ( - # fn.check_package_installed("arcolinux-keyring") - # or fn.check_package_installed("arcolinux-mirrorlist-git") - # ): - # self.btnRepos = Gtk.Button(label="Add ArcoLinux Repo") - # self.btnRepos._value = 1 - # else: - # self.btnRepos = Gtk.Button(label="Remove ArcoLinux Repo") - # self.btnRepos._value = 2 - # - # self.btnRepos.set_size_request(100, 30) - # self.btnRepos.connect("clicked", self.on_repos_clicked) - - # ===================================================== - # QUIT BUTTON - # ===================================================== - - btn_quit_app = Gtk.Button(label="Quit") - btn_quit_app.set_size_request(100, 30) - btn_quit_app.connect("clicked", self.on_close, "delete-event") - btn_context = btn_quit_app.get_style_context() - btn_context.add_class("destructive-action") - - # ===================================================== - # SEARCH BOX - # ===================================================== - - self.searchentry = Gtk.SearchEntry() - self.searchentry.set_text(search_term) - self.searchentry.connect("activate", self.on_search_activated) - self.searchentry.connect("icon-release", self.on_search_cleared) - - iv_searchbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - - # ===================================================== - # PACKS - # ===================================================== - - # hbox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) - # hbox2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) - # hbox3 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) - - # hbox3.pack_start(btnReCache, False, False, 0) - - iv_searchbox.pack_start(self.searchentry, False, False, 0) - - ivbox.pack_start(image, False, False, 0) - ivbox.pack_start(iv_searchbox, False, False, 0) - ivbox.pack_start(stack_switcher, True, True, 0) - - ivbox.pack_start(btn_quit_app, False, False, 0) - - vbox1.pack_start(hbox0, False, False, 0) - vbox1.pack_start(stack, True, True, 0) - - hbox.pack_start(ivbox, False, True, 0) - hbox.pack_start(vbox1, True, True, 0) - - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - self.show_all() - - except Exception as err: - fn.logger.error("Exception in GUISearch(): %s" % err) - - def setup_gui(self, Gtk, Gdk, GdkPixbuf, base_dir, os, Pango, settings): # noqa - try: - # reset back to main box - if self.search_activated: - # remove the search vbox - self.remove(self.vbox_search) - self.show_all() - - # lets quickly create the latest installed list. - fn.get_current_installed() - - # ======================================================= - # HeaderBar - # ======================================================= - - setup_headerbar(self, Gtk, settings) - - # ======================================================= - # App Notifications - # ======================================================= - - hbox0 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - - self.notification_revealer = Gtk.Revealer() - self.notification_revealer.set_reveal_child(False) - - self.notification_label = Gtk.Label() - - pb_panel = GdkPixbuf.Pixbuf().new_from_file(base_dir + "/images/panel.png") - panel = Gtk.Image().new_from_pixbuf(pb_panel) - - overlay_frame = Gtk.Overlay() - overlay_frame.add(panel) - overlay_frame.add_overlay(self.notification_label) - - self.notification_revealer.add(overlay_frame) - - hbox0.pack_start(self.notification_revealer, True, False, 0) - - # ========================================================== - # CONTAINER - # ========================================================== - - self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) - - self.vbox.pack_start(hbox, True, True, 0) - self.add(self.vbox) - - # ========================================================== - # PREP WORK - # ========================================================== - - # This section sets up the tabs, and the array for dealing with the tab content - """ - yaml_files_unsorted = [] - path = base_dir + "/yaml/" - for file in os.listdir(path): - if file.endswith(".yaml"): - yaml_files_unsorted.append(file) - else: - print( - "Unsupported configuration file type. Please contact Arcolinux Support." - ) - # Need to sort the list (Or do we? I choose to) - yaml_files = sorted(yaml_files_unsorted) - """ - - # Check github for updated files - # fn.check_github(yaml_files) - # ========================================================== - # GENERATE STACK - # ========================================================== - stack = Gtk.Stack() - # stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) - stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) - stack.set_transition_duration(350) - - vbox_stack = [] - stack_item = 0 - - # Max Threads - """ - Fatal Python error: Segmentation fault - This error happens randomly, due to the for loop iteration on the cpu_count - old code: for x in range(cpu_count()): - """ - - # spawn only 1 GUI_Worker threads, as any number greater causes a Segmentation fault - - worker = GUI_Worker(self.queue) - worker.name = "thread_GUI_Worker" - # Set the worker to be True to allow processing, and avoid Blocking - # worker.daemon = True - worker.start() - - for category in self.packages: - # NOTE: IF the yaml file name standard changes, be sure to update this, or weirdness will follow. - - # this is the side stack listing all categories - vbox_stack.append( - Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) - ) - stack.add_titled( - vbox_stack[stack_item], - str("stack" + str(len(vbox_stack))), - category, - ) - - packages_lst = self.packages[category] - - # Multithreading! - self.queue.put( - ( - self, - Gtk, - vbox_stack[stack_item], - category, - packages_lst, - ) - ) - stack_item += 1 - - # send a signal that no further items are to be put on the queue - self.queue.put(None) - # safety to ensure that we finish threading before we continue on. - - self.queue.join() - fn.logger.debug("GUI Worker thread completed") - - stack_switcher = Gtk.StackSidebar() - stack_switcher.set_name("sidebar") - stack_switcher.set_stack(stack) - - # ===================================================== - # LOGO - # ===================================================== - - ivbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) - pixbuf = GdkPixbuf.Pixbuf().new_from_file_at_size( - os.path.join(base_dir, "images/blackbox.png"), 45, 45 - ) - image = Gtk.Image().new_from_pixbuf(pixbuf) - - # remove the focus on startup from search entry - ivbox.set_property("can-focus", True) - Gtk.Window.grab_focus(ivbox) - - # ===================================================== - # RECACHE BUTTON - # ===================================================== - - # btnReCache = Gtk.Button(label="Recache Applications") - # btnReCache.connect("clicked", self.recache_clicked) - # btnReCache.set_property("has-tooltip", True) - # btnReCache.connect("query-tooltip", self.tooltip_callback, - # "Refresh the application cache") - - # ===================================================== - # REPOS - # ===================================================== - - # ===================================================== - # QUIT BUTTON - # ===================================================== - btn_quit_app = Gtk.Button(label="Quit") - btn_quit_app.set_size_request(100, 30) - btn_quit_app.connect("clicked", self.on_close, "delete-event") - btn_context = btn_quit_app.get_style_context() - btn_context.add_class("destructive-action") - # ===================================================== - # SEARCH BOX - # ===================================================== - self.searchentry = Gtk.SearchEntry() - self.searchentry.set_placeholder_text("Search...") - self.searchentry.connect("activate", self.on_search_activated) - self.searchentry.connect("icon-release", self.on_search_cleared) - - ivsearchbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - - ivsearchbox.pack_start(self.searchentry, False, False, 0) - - ivbox.pack_start(image, False, False, 0) - ivbox.pack_start(ivsearchbox, False, False, 0) - ivbox.pack_start(stack_switcher, True, True, 0) - ivbox.pack_start(btn_quit_app, False, False, 0) - - vbox1.pack_start(hbox0, False, False, 0) - vbox1.pack_start(stack, True, True, 0) - - hbox.pack_start(ivbox, False, True, 0) - hbox.pack_start(vbox1, True, True, 0) - - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - if self.search_activated: - self.show_all() - - except Exception as e: - fn.logger.error("Exception in GUI(): %s" % e) - - -# setup headerbar including popover settings -def setup_headerbar(self, Gtk, settings): - try: - header_bar_title = "BlackBox" - headerbar = Gtk.HeaderBar() - headerbar.set_title(header_bar_title) - headerbar.set_show_close_button(True) - - self.set_titlebar(headerbar) - - toolbutton = Gtk.ToolButton() - # icon-name open-menu-symbolic / open-menu-symbolic.symbolic - toolbutton.set_icon_name("open-menu-symbolic") - - toolbutton.connect("clicked", self.on_settings_clicked) - - headerbar.pack_end(toolbutton) - - self.popover = Gtk.Popover() - self.popover.set_relative_to(toolbutton) - - vbox = Gtk.Box(spacing=0, orientation=Gtk.Orientation.VERTICAL) - vbox.set_border_width(15) - - # switches - - # switch to display package versions - self.switch_package_version = Gtk.Switch() - - if settings != None: - if settings["Display Package Versions"]: - self.display_versions = settings["Display Package Versions"] - - if self.display_versions == True: - self.switch_package_version.set_active(True) - else: - self.switch_package_version.set_active(False) - - self.switch_package_version.connect("notify::active", self.version_toggle) - - # switch to import snigdhaos keyring - self.switch_snigdhaos_keyring = Gtk.Switch() - - if ( - fn.check_package_installed("snigdhaos-keyring") is False - or fn.verify_snigdhaos_pacman_conf() is False - ): - self.switch_snigdhaos_keyring.set_state(False) - - else: - self.switch_snigdhaos_keyring.set_state(True) - - self.switch_snigdhaos_keyring.connect("state-set", self.snigdhaos_keyring_toggle) - - # switch to import snigdhaos mirrorlist - self.switch_snigdhaos_mirrorlist = Gtk.Switch() - - if ( - fn.check_package_installed("snigdhaos-mirrorlist") is False - or fn.verify_snigdhaos_pacman_conf() is False - ): - self.switch_snigdhaos_mirrorlist.set_state(False) - - else: - self.switch_snigdhaos_mirrorlist.set_state(True) - - self.switch_snigdhaos_mirrorlist.connect("state-set", self.snigdhaos_mirrorlist_toggle) - - # switch to display package progress window - self.switch_package_progress = Gtk.Switch() - - if settings != None: - if settings["Display Package Progress"]: - self.display_package_progress = settings["Display Package Progress"] - - if self.display_package_progress == True: - self.switch_package_progress.set_active(True) - else: - self.switch_package_progress.set_active(False) - self.switch_package_progress.connect( - "notify::active", self.package_progress_toggle - ) - - # modalbuttons - - # button to open the pacman log monitoring dialog - self.modelbtn_pacmanlog = Gtk.ModelButton() - self.modelbtn_pacmanlog.connect("clicked", self.on_pacman_log_clicked) - self.modelbtn_pacmanlog.set_name("modelbtn_popover") - self.modelbtn_pacmanlog.props.centered = False - self.modelbtn_pacmanlog.props.text = "Open Pacman Log File" - - # button to display installed packages window - modelbtn_packages_export = Gtk.ModelButton() - modelbtn_packages_export.connect("clicked", self.on_packages_export_clicked) - modelbtn_packages_export.set_name("modelbtn_popover") - modelbtn_packages_export.props.centered = False - modelbtn_packages_export.props.text = "Show Installed Packages" - - # button to display import packages window - modelbtn_packages_import = Gtk.ModelButton() - modelbtn_packages_import.connect("clicked", self.on_packages_import_clicked) - modelbtn_packages_import.set_name("modelbtn_popover") - modelbtn_packages_import.props.centered = False - modelbtn_packages_import.props.text = "Import Packages" - - # button to show about dialog - modelbtn_about_app = Gtk.ModelButton() - modelbtn_about_app.connect("clicked", self.on_about_app_clicked) - modelbtn_about_app.set_name("modelbtn_popover") - modelbtn_about_app.props.centered = False - modelbtn_about_app.props.text = "About BlackBox" - - # button to show iso package lists window - modelbtn_iso_packages_list = Gtk.ModelButton() - modelbtn_iso_packages_list.connect( - "clicked", self.on_arcolinux_iso_packages_clicked - ) - modelbtn_iso_packages_list.set_name("modelbtn_popover") - modelbtn_iso_packages_list.props.centered = False - modelbtn_iso_packages_list.props.text = "Explore ArcoLinux ISO Packages" - - # button to show package search window - modelbtn_package_search = Gtk.ModelButton() - modelbtn_package_search.connect("clicked", self.on_package_search_clicked) - modelbtn_package_search.set_name("modelbtn_popover") - modelbtn_package_search.props.centered = False - modelbtn_package_search.props.text = "Open Package Search" - - # grid for the switch options - grid_switches = Gtk.Grid() - grid_switches.set_row_homogeneous(True) - - lbl_package_version = Gtk.Label(xalign=0) - lbl_package_version.set_text("Display Package Versions") - - lbl_package_version_padding = Gtk.Label(xalign=0) - lbl_package_version_padding.set_text(" ") - - lbl_package_progress = Gtk.Label(xalign=0) - lbl_package_progress.set_text("Display Package Progress") - - lbl_package_progress_padding = Gtk.Label(xalign=0) - lbl_package_progress_padding.set_text(" ") - - lbl_arco_keyring = Gtk.Label(xalign=0) - lbl_arco_keyring.set_text("Import ArcoLinux Keyring") - - lbl_arco_keyring_padding = Gtk.Label(xalign=0) - lbl_arco_keyring_padding.set_text(" ") - - lbl_arco_mirrorlist = Gtk.Label(xalign=0) - lbl_arco_mirrorlist.set_text("Import ArcoLinux Mirrorlist") - - lbl_arco_mirrorlist_padding = Gtk.Label(xalign=0) - lbl_arco_mirrorlist_padding.set_text(" ") - - grid_switches.attach(lbl_package_version, 0, 1, 1, 1) - grid_switches.attach_next_to( - lbl_package_version_padding, - lbl_package_version, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach_next_to( - self.switch_package_version, - lbl_package_version_padding, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach(lbl_package_progress, 0, 2, 1, 1) - grid_switches.attach_next_to( - lbl_package_progress_padding, - lbl_package_progress, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach_next_to( - self.switch_package_progress, - lbl_package_progress_padding, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach(lbl_arco_keyring, 0, 3, 1, 1) - grid_switches.attach_next_to( - lbl_arco_keyring_padding, - lbl_arco_keyring, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach_next_to( - self.switch_arco_keyring, - lbl_arco_keyring_padding, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach(lbl_arco_mirrorlist, 0, 4, 1, 1) - grid_switches.attach_next_to( - lbl_arco_mirrorlist_padding, - lbl_arco_mirrorlist, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - grid_switches.attach_next_to( - self.switch_arco_mirrorlist, - lbl_arco_mirrorlist_padding, - Gtk.PositionType.RIGHT, - 1, - 1, - ) - - vbox_buttons = Gtk.Box(spacing=1, orientation=Gtk.Orientation.VERTICAL) - vbox_buttons.pack_start(self.modelbtn_pacmanlog, False, True, 0) - vbox_buttons.pack_start(modelbtn_packages_export, False, True, 0) - vbox_buttons.pack_start(modelbtn_packages_import, False, True, 0) - vbox_buttons.pack_start(modelbtn_iso_packages_list, False, True, 0) - vbox_buttons.pack_start(modelbtn_package_search, False, True, 0) - vbox_buttons.pack_start(modelbtn_about_app, False, True, 0) - - vbox.pack_start(grid_switches, False, False, 0) - vbox.pack_start(vbox_buttons, False, False, 0) - - self.popover.add(vbox) - self.popover.set_position(Gtk.PositionType.BOTTOM) - except Exception as e: - fn.logger.error("Exception in setup_headerbar(): %s" % e) \ No newline at end of file diff --git a/usr/share/blackbox/ui/ISOPackagesWindow.py b/usr/share/blackbox/ui/ISOPackagesWindow.py deleted file mode 100644 index 2778dbd..0000000 --- a/usr/share/blackbox/ui/ISOPackagesWindow.py +++ /dev/null @@ -1,429 +0,0 @@ -# This class is used to create a window showing a list of packages available for a given ArcoLinux ISO - -import os -import gi -import requests -import Functions as fn -from ui.MessageDialog import MessageDialog - -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, GLib - - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - - -arcolinux_isos = [ - "arcolinuxs-xanmod-iso", - "arcolinuxs-zen-iso", - "arcolinuxs-lts-iso", - "arcolinuxs-iso", - "arcolinuxl-iso", - "arcolinuxd-iso", -] - -arcolinuxb_isos = [ - "arco-sway", - "arco-plasma", - "arco-hyprland", - "arco-chadwm", - "arco-dusk", - "arco-dwm", - "arco-berry", - "arco-hypr", - "arco-enlightenment", - "arco-xtended", - "arco-pantheon", - "arco-awesome", - "arco-bspwm", - "arco-cinnamon", - "arco-budgie", - "arco-cutefish", - "arco-cwm", - "arco-deepin", - "arco-gnome", - "arco-fvwm3", - "arco-herbstluftwm", - "arco-i3", - "arco-icewm", - "arco-jwm", - "arco-leftwm", - "arco-lxqt", - "arco-mate", - "arco-openbox", - "arco-qtile", - "arco-spectrwm", - "arco-ukui", - "arco-wmderland", - "arco-xfce", - "arco-xmonad", -] - -github_arcolinux_packagelist = "https://raw.githubusercontent.com/${ARCOLINUX}/${ISO}/master/archiso/packages.x86_64" -headers = {"Content-Type": "text/plain;charset=UTF-8"} - - -class ISOPackagesWindow(Gtk.Window): - def __init__(self): - Gtk.Window.__init__(self) - - headerbar = Gtk.HeaderBar() - headerbar.set_title("ArcoLinux ISO Package Explorer") - headerbar.set_show_close_button(True) - - # remove the focus on startup from search entry - headerbar.set_property("can-focus", True) - Gtk.Window.grab_focus(headerbar) - - self.set_resizable(True) - self.set_size_request(500, 600) - self.set_border_width(10) - self.set_titlebar(headerbar) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - self.treeview_loaded = False - self.build_gui() - - def get_packagelist(self): - try: - # make request to get the package list from github - url = None - - self.package_list = [] - - if "-iso" in self.selected_iso: - url = github_arcolinux_packagelist.replace( - "${ARCOLINUX}", "arcolinux" - ).replace("${ISO}", self.selected_iso) - github_arcolinux = [ - "https://github.com/arcolinux/", - self.selected_iso, - "/blob/master/archiso/packages.x86_64", - ] - - self.github_source = "".join(github_arcolinux) - else: - url = github_arcolinux_packagelist.replace( - "${ARCOLINUX}", "arcolinuxb" - ).replace("${ISO}", self.selected_iso) - - github_arcolinuxb = [ - "https://github.com/arcolinuxb/", - self.selected_iso, - "/blob/master/archiso/packages.x86_64", - ] - - self.github_source = "".join(github_arcolinuxb) - - r = requests.get(url, headers=headers, allow_redirects=True) - - # read the package list ignore any commented lines - if r.status_code == 200: - if len(r.text) > 0: - for line in r.text.splitlines(): - if "#" not in line.strip() and len(line.strip()) > 0: - self.package_list.append((line.strip(), None)) - else: - fn.logger.error("Request for %s returned %s" % (url, r.status_code)) - - message_dialog = MessageDialog( - "Error", - "Request failed", - "Failed to request package list", - "Request for %s returned status code = %s" % (url, r.status_code), - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - message_dialog.destroy() - - except Exception as e: - message_dialog = MessageDialog( - "Error", - "Request failed", - "Failed to request package list", - e, - "error", - True, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - message_dialog.destroy() - - def on_combo_iso_changed(self, combo): - try: - iso = combo.get_active_text() - if iso is not None: - self.selected_iso = iso - self.get_packagelist() - - if len(self.package_list) > 0: - lbl_github_source_title = Gtk.Label(xalign=0, yalign=0) - lbl_github_source_title.set_markup("Package list source") - - lbl_github_source_value = Gtk.Label(xalign=0, yalign=0) - lbl_github_source_value.set_markup( - "%s" % (self.github_source, self.github_source) - ) - - lbl_package_count_title = Gtk.Label(xalign=0, yalign=0) - lbl_package_count_title.set_markup("Activated packages") - - lbl_package_count_value = Gtk.Label(xalign=0, yalign=0) - lbl_package_count_value.set_text(str(len(self.package_list))) - - self.filename = "%s/blackbox-exports/%s-%s-packages.x86_64.txt" % ( - fn.home, - self.selected_iso, - fn.datetime.now().strftime("%Y-%m-%d"), - ) - - lbl_export_desc_title = Gtk.Label(xalign=0, yalign=0) - lbl_export_desc_title.set_markup("Export destination") - - lbl_export_desc_value = Gtk.Label(xalign=0, yalign=0) - lbl_export_desc_value.set_text(self.filename) - - if self.treeview_loaded is True: - self.vbox_package_data.destroy() - - search_entry = Gtk.SearchEntry() - search_entry.set_placeholder_text("Search...") - search_entry.set_size_request(450, 0) - - grid_package_data = Gtk.Grid() - - treestore_packages_explorer = Gtk.TreeStore(str, str) - - for item in sorted(self.package_list): - treestore_packages_explorer.append(None, list(item)) - - treeview_packages_explorer = Gtk.TreeView() - treeview_packages_explorer.set_search_entry(search_entry) - - treeview_packages_explorer.set_model(treestore_packages_explorer) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Packages", renderer, text=0) - - treeview_packages_explorer.append_column(column) - - path = Gtk.TreePath.new_from_indices([0]) - - selection = treeview_packages_explorer.get_selection() - - selection.select_path(path) - - treeview_packages_explorer.expand_all() - treeview_packages_explorer.columns_autosize() - - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_vexpand(True) - scrolled_window.set_hexpand(True) - - scrolled_window.add(treeview_packages_explorer) - - grid_treeview = Gtk.Grid() - grid_treeview.set_column_homogeneous(True) - - self.vbox_package_data = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - - self.vbox_package_data.pack_start( - lbl_github_source_title, False, True, 1 - ) - - self.vbox_package_data.pack_start( - lbl_github_source_value, False, True, 1 - ) - - self.vbox_package_data.pack_start( - lbl_package_count_title, False, True, 1 - ) - - self.vbox_package_data.pack_start( - lbl_package_count_value, False, True, 1 - ) - - self.vbox_package_data.pack_start( - lbl_export_desc_title, False, True, 1 - ) - self.vbox_package_data.pack_start( - lbl_export_desc_value, False, True, 1 - ) - - lbl_padding_search_entry1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding_search_entry1.set_text("") - - lbl_padding_search_entry2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding_search_entry2.set_text("") - - grid_search_entry = Gtk.Grid() - - grid_search_entry.attach(lbl_padding_search_entry1, 0, 1, 1, 1) - grid_search_entry.attach(search_entry, 0, 2, 1, 1) - grid_search_entry.attach(lbl_padding_search_entry2, 0, 3, 1, 1) - - self.vbox_package_data.pack_start( - grid_search_entry, False, False, 1 - ) - - self.vbox_package_data.pack_start(scrolled_window, False, True, 1) - - self.vbox_combo.pack_start(self.vbox_package_data, False, True, 1) - - self.show_all() - - self.treeview_loaded = True - - except Exception as e: - fn.logger.error("Exception in on_combo_iso_changed(): %s" % e) - - def on_iso_package_list_export(self, widget): - # export the package list to a file inside $HOME/blackbox-exports - fn.logger.debug("Exporting ArcoLinux ISO package list") - try: - if self.filename is not None: - with open(self.filename, "w", encoding="utf-8") as f: - f.write( - "# Created by BlackBox on %s\n" - % fn.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - ) - f.write("# %s\n" % self.github_source) - for line in sorted(self.package_list): - f.write("%s\n" % line[0]) - - if os.path.exists(self.filename): - message_dialog = MessageDialog( - "Info", - "Package export complete", - "Package list exported to %s" % self.filename, - "", - "info", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - else: - message_dialog = MessageDialog( - "Error", - "Package export failed", - "Package list export failed", - "", - "error", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - # file is created by root, update the permissions to the sudo username - fn.permissions(self.filename) - else: - message_dialog = MessageDialog( - "Warning", - "Select an ISO", - "An ArcoLinux ISO needs to be selected before exporting", - "", - "warning", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - except Exception as e: - fn.logger.error("Exception in on_iso_package_list_export(): %s" % e) - - def on_close(self, widget): - self.hide() - self.destroy() - - def populate_combo_iso(self): - for arco_iso in arcolinux_isos: - self.combo_iso.append_text(arco_iso) - - for arco_isob in sorted(arcolinuxb_isos): - self.combo_iso.append_text(arco_isob) - - def build_gui(self): - try: - lbl_select_iso = Gtk.Label(xalign=0, yalign=0) - lbl_select_iso.set_markup("Select ArcoLinux ISO") - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text("") - - self.combo_iso = Gtk.ComboBoxText() - self.combo_iso.set_wrap_width(3) - self.combo_iso.set_entry_text_column(0) - self.combo_iso.connect("changed", self.on_combo_iso_changed) - - self.populate_combo_iso() - - self.filename = None - - grid_top = Gtk.Grid() - - grid_top.attach(lbl_select_iso, 0, 1, 1, 1) - grid_top.attach_next_to( - lbl_padding1, lbl_select_iso, Gtk.PositionType.BOTTOM, 1, 1 - ) - grid_top.attach(self.combo_iso, 0, 2, 1, 1) - grid_top.attach(lbl_padding2, 0, 3, 1, 1) - - btn_ok = Gtk.Button(label="OK") - btn_ok.set_size_request(100, 30) - btn_ok.connect("clicked", self.on_close) - btn_ok.set_halign(Gtk.Align.END) - - btn_export = Gtk.Button(label="Export") - btn_export.set_size_request(100, 30) - btn_export.connect("clicked", self.on_iso_package_list_export) - btn_export.set_halign(Gtk.Align.END) - - grid_bottom = Gtk.Grid() - grid_bottom.attach(btn_ok, 0, 1, 1, 1) - - lbl_padding3 = Gtk.Label(xalign=0, yalign=0) - lbl_padding3.set_text(" ") - - grid_bottom.attach_next_to( - lbl_padding3, btn_ok, Gtk.PositionType.RIGHT, 1, 1 - ) - - grid_bottom.attach_next_to( - btn_export, lbl_padding3, Gtk.PositionType.RIGHT, 1, 1 - ) - - grid_bottom.set_halign(Gtk.Align.END) - - vbox_bottom = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - - lbl_padding_bottom = Gtk.Label(xalign=0, yalign=0) - lbl_padding_bottom.set_text("") - - vbox_bottom.pack_start(lbl_padding_bottom, False, True, 0) - vbox_bottom.pack_start(grid_bottom, False, True, 0) - - self.vbox_combo = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - self.vbox_combo.pack_start(grid_top, False, True, 0) - self.vbox_combo.pack_end(vbox_bottom, False, True, 0) - - self.add(self.vbox_combo) - - self.show_all() - - except Exception as e: - fn.logger.error("Exception in build_gui(): %s" % e) \ No newline at end of file diff --git a/usr/share/blackbox/ui/MessageDialog.py b/usr/share/blackbox/ui/MessageDialog.py deleted file mode 100644 index 382ce4f..0000000 --- a/usr/share/blackbox/ui/MessageDialog.py +++ /dev/null @@ -1,118 +0,0 @@ -# This class is used to create a modal dialog window showing detailed information about an event - -import os -import gi -import Functions as fn - -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class MessageDialog(Gtk.Dialog): - # message_type is a string, either one of "info", "warning", "error" to show which infobar to display - # extended argument when set to true shows a textview inside the dialog - # extended argument when set to false only shows a standard dialog - def __init__( - self, title, subtitle, first_msg, secondary_msg, message_type, extended - ): - Gtk.Dialog.__init__(self) - - headerbar = Gtk.HeaderBar() - headerbar.set_title(title) - headerbar.set_show_close_button(True) - - self.set_resizable(True) - - self.set_border_width(10) - - self.set_titlebar(headerbar) - - btn_ok = Gtk.Button(label="OK") - btn_ok.set_size_request(100, 30) - btn_ok.connect("clicked", on_message_dialog_ok_response, self) - btn_ok.set_halign(Gtk.Align.END) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - - infobar = Gtk.InfoBar() - - if message_type == "info": - infobar.set_name("infobar_info") - if message_type == "error": - infobar.set_name("infobar_error") - if message_type == "warning": - infobar.set_name("infobar_warning") - - lbl_title_message = Gtk.Label(xalign=0, yalign=0) - lbl_title_message.set_markup("%s" % subtitle) - content = infobar.get_content_area() - content.add(lbl_title_message) - - infobar.set_revealed(True) - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text("") - - grid_message = Gtk.Grid() - - grid_message.attach(infobar, 0, 0, 1, 1) - grid_message.attach(lbl_padding1, 0, 1, 1, 1) - - if extended is True: - scrolled_window = Gtk.ScrolledWindow() - textview = Gtk.TextView() - textview.set_property("editable", False) - textview.set_property("monospace", True) - textview.set_border_width(10) - textview.set_vexpand(True) - textview.set_hexpand(True) - - msg_buffer = textview.get_buffer() - msg_buffer.insert( - msg_buffer.get_end_iter(), - "Event timestamp = %s\n" - % fn.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ) - msg_buffer.insert(msg_buffer.get_end_iter(), "%s\n" % first_msg) - msg_buffer.insert(msg_buffer.get_end_iter(), "%s\n" % secondary_msg) - - # move focus away from the textview, to hide the cursor at load - headerbar.set_property("can-focus", True) - Gtk.Window.grab_focus(headerbar) - - scrolled_window.add(textview) - - grid_message.attach(scrolled_window, 0, 2, 1, 1) - grid_message.attach(lbl_padding2, 0, 3, 1, 1) - - self.set_default_size(800, 600) - - else: - # do not display textview - lbl_first_message = Gtk.Label(xalign=0, yalign=0) - lbl_first_message.set_text(first_msg) - - lbl_second_message = Gtk.Label(xalign=0, yalign=0) - lbl_second_message.set_markup("%s" % secondary_msg) - - grid_message.attach(lbl_first_message, 0, 2, 1, 1) - grid_message.attach(lbl_second_message, 0, 3, 1, 1) - - self.set_default_size(600, 100) - self.set_resizable(False) - - vbox_close = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - vbox_close.pack_start(btn_ok, True, True, 1) - - self.vbox.add(grid_message) - self.vbox.add(vbox_close) - - -def on_message_dialog_ok_response(self, widget): - # widget.hide() - widget.destroy() \ No newline at end of file diff --git a/usr/share/blackbox/ui/PackageListDialog.py b/usr/share/blackbox/ui/PackageListDialog.py deleted file mode 100644 index 95a8f5c..0000000 --- a/usr/share/blackbox/ui/PackageListDialog.py +++ /dev/null @@ -1,283 +0,0 @@ -# This class is used to create a modal dialog window to display currently installed packages - -import os -import gi -import Functions as fn -from ui.MessageDialog import MessageDialog -from queue import Queue -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class PackageListDialog(Gtk.Dialog): - def __init__(self): - Gtk.Dialog.__init__(self) - - # Create a queue for storing package list exports to display inside PackageListDialog - self.pkg_export_queue = Queue() - - self.filename = "%s/packages-x86_64.txt" % (fn.export_dir,) - - self.set_resizable(True) - self.set_size_request(1050, 700) - self.set_modal(True) - - self.set_border_width(10) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - - self.connect("delete-event", self.on_close) - - self.installed_packages_list = None - - self.headerbar = Gtk.HeaderBar() - self.headerbar.set_title("Loading please wait ..") - self.headerbar.set_show_close_button(True) - - self.set_titlebar(self.headerbar) - - self.grid_packageslst = Gtk.Grid() - self.grid_packageslst.set_column_homogeneous(True) - - self.lbl_info = Gtk.Label(xalign=0, yalign=0) - self.lbl_info.set_text("Export destination %s" % self.filename) - - # get a list of installed packages on the system - - self.pacman_export_cmd = ["pacman", "-Qien"] - - fn.Thread( - target=fn.get_installed_package_data, - args=(self,), - daemon=True, - ).start() - - fn.Thread(target=self.check_queue, daemon=True).start() - - def setup_gui(self): - if len(self.installed_packages_list) > 0: - self.set_title( - "Showing %s installed packages" % len(self.installed_packages_list) - ) - - search_entry = Gtk.SearchEntry() - search_entry.set_placeholder_text("Search...") - - # remove the focus on startup from search entry - self.headerbar.set_property("can-focus", True) - Gtk.Window.grab_focus(self.headerbar) - - treestore_packages = Gtk.TreeStore(str, str, str, str, str) - for item in sorted(self.installed_packages_list): - treestore_packages.append(None, list(item)) - - treeview_packages = Gtk.TreeView() - treeview_packages.set_search_entry(search_entry) - - treeview_packages.set_model(treestore_packages) - - for i, col_title in enumerate( - [ - "Name", - "Installed Version", - "Latest Version", - "Installed Size", - "Installed Date", - ] - ): - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(col_title, renderer, text=i) - treeview_packages.append_column(col) - - # allow sorting by installed date - - col_installed_date = treeview_packages.get_column(4) - col_installed_date.set_sort_column_id(4) - - treestore_packages.set_sort_func(4, self.compare_install_date, None) - - path = Gtk.TreePath.new_from_indices([0]) - - selection = treeview_packages.get_selection() - selection.select_path(path) - - treeview_packages.expand_all() - treeview_packages.columns_autosize() - - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_vexpand(True) - scrolled_window.set_hexpand(True) - - self.grid_packageslst.attach(scrolled_window, 0, 0, 8, 10) - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - self.grid_packageslst.attach_next_to( - lbl_padding1, scrolled_window, Gtk.PositionType.BOTTOM, 1, 1 - ) - - btn_dialog_export = Gtk.Button(label="Export") - btn_dialog_export.connect("clicked", self.on_dialog_export_clicked) - btn_dialog_export.set_size_request(100, 30) - btn_dialog_export.set_halign(Gtk.Align.END) - - btn_dialog_export_close = Gtk.Button(label="Close") - btn_dialog_export_close.connect("clicked", self.on_close, "delete-event") - btn_dialog_export_close.set_size_request(100, 30) - btn_dialog_export_close.set_halign(Gtk.Align.END) - - scrolled_window.add(treeview_packages) - - grid_btn = Gtk.Grid() - grid_btn.attach(btn_dialog_export, 0, 1, 1, 1) - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text(" ") - - grid_btn.attach_next_to( - lbl_padding2, btn_dialog_export, Gtk.PositionType.RIGHT, 1, 1 - ) - - grid_btn.attach_next_to( - btn_dialog_export_close, lbl_padding2, Gtk.PositionType.RIGHT, 1, 1 - ) - - grid_btn.set_halign(Gtk.Align.END) - - vbox_btn = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - vbox_btn.pack_start(grid_btn, True, True, 1) - - lbl_padding3 = Gtk.Label(xalign=0, yalign=0) - lbl_padding3.set_text("") - - self.vbox.add(search_entry) - self.vbox.add(lbl_padding3) - self.vbox.add(self.grid_packageslst) - self.vbox.add(self.lbl_info) - self.vbox.add(vbox_btn) - - self.show_all() - - def check_queue(self): - while True: - self.installed_packages_list = self.pkg_export_queue.get() - - if self.installed_packages_list is not None: - break - - self.pkg_export_queue.task_done() - - GLib.idle_add(self.setup_gui, priority=GLib.PRIORITY_DEFAULT) - - def on_close(self, dialog, event): - self.hide() - self.destroy() - - def on_dialog_export_clicked(self, dialog): - try: - if not os.path.exists(fn.export_dir): - fn.makedirs(fn.export_dir) - fn.permissions(fn.export_dir) - - with open(self.filename, "w", encoding="utf-8") as f: - f.write( - "# This file was auto-generated by BlackBox on %s at %s\n" - % ( - fn.datetime.today().date(), - fn.datetime.now().strftime("%H:%M:%S"), - ) - ) - - f.write( - "# Exported explicitly installed packages using %s\n" - % " ".join(self.pacman_export_cmd) - ) - - for package in sorted(self.installed_packages_list): - f.write("%s\n" % (package[0])) - - if os.path.exists(self.filename): - fn.logger.info("Export completed") - - # fix permissions, file is owned by root - fn.permissions(self.filename) - - message_dialog = MessageDialog( - "Info", - "Package export complete", - "Package list exported to %s" % self.filename, - "", - "info", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - message_dialog.destroy() - - else: - fn.logger.error("Export failed") - - message_dialog = MessageDialog( - "Error", - "Package export failed", - "Failed to export package list to %s." % self.filename, - "", - "error", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - message_dialog.destroy() - - except Exception as e: - fn.logger.error("Exception in on_dialog_export_clicked(): %s" % e) - - # noqa: any locales other than en_GB.UTF-8 / en_US.UTF-8 are untested - def compare_install_date(self, model, row1, row2, user_data): - try: - sort_column, _ = model.get_sort_column_id() - str_value1 = model.get_value(row1, sort_column) - str_value2 = model.get_value(row2, sort_column) - - datetime_value1 = None - datetime_value2 = None - - # convert string into datetime object, check if time format is 12H format with AM/PM - if str_value1.lower().find("am") > 0 or str_value1.lower().find("pm") > 0: - # 12H format - datetime_value1 = fn.datetime.strptime( - str_value1, "%a %d %b %Y %I:%M:%S %p %Z" - ).replace(tzinfo=None) - datetime_value2 = fn.datetime.strptime( - str_value2, "%a %d %b %Y %I:%M:%S %p %Z" - ).replace(tzinfo=None) - else: - # 24H format - datetime_value1 = fn.datetime.strptime( - str_value1, "%a %d %b %Y %H:%M:%S %Z" - ).replace(tzinfo=None) - datetime_value2 = fn.datetime.strptime( - str_value2, "%a %d %b %Y %H:%M:%S %Z" - ).replace(tzinfo=None) - - if datetime_value1 is not None and datetime_value2 is not None: - if datetime_value1 < datetime_value2: - return -1 - elif datetime_value1 == datetime_value2: - return 0 - else: - return 1 - except ValueError as ve: - # fn.logger.error("ValueError in compare_install_date: %s" % ve) - # compare fails due to the format of the datetime string, which hasn't been tested - pass - except Exception as e: - fn.logger.error("Exception in compare_install_date: %s" % e) \ No newline at end of file diff --git a/usr/share/blackbox/ui/PackageSearchWindow.py b/usr/share/blackbox/ui/PackageSearchWindow.py deleted file mode 100644 index 2334987..0000000 --- a/usr/share/blackbox/ui/PackageSearchWindow.py +++ /dev/null @@ -1,543 +0,0 @@ -# This class is used to create a window for package name searches and to display package information - -import os -import gi - -import Functions as fn -from ui.MessageDialog import MessageDialog - -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - - -class PackageSearchWindow(Gtk.Window): - def __init__(self): - Gtk.Window.__init__(self) - - self.headerbar = Gtk.HeaderBar() - self.headerbar.set_title("Package Search") - self.headerbar.set_show_close_button(True) - - # remove the focus on startup from search entry - self.headerbar.set_property("can-focus", True) - Gtk.Window.grab_focus(self.headerbar) - - self.set_resizable(True) - self.set_size_request(700, 500) - self.set_border_width(10) - self.set_titlebar(self.headerbar) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - self.search_package_activated = False - self.build_gui() - - def build_gui(self): - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) - self.stack.set_transition_duration(350) - self.stack.set_hhomogeneous(False) - self.stack.set_vhomogeneous(False) - - stack_switcher = Gtk.StackSwitcher() - stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) - stack_switcher.set_stack(self.stack) - stack_switcher.set_homogeneous(True) - - searchentry = Gtk.SearchEntry() - searchentry.set_placeholder_text("Search using package name...") - searchentry.set_size_request(400, 0) - searchentry.connect("activate", self.on_search_package_activated) - searchentry.connect("icon-release", self.on_search_package_cleared) - - btn_ok = Gtk.Button(label="OK") - btn_ok.set_size_request(100, 30) - btn_ok.connect("clicked", self.on_close) - btn_ok.set_halign(Gtk.Align.END) - - grid_bottom = Gtk.Grid() - grid_bottom.attach(btn_ok, 0, 1, 1, 1) - grid_bottom.set_halign(Gtk.Align.END) - - vbox_bottom = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - lbl_padding_bottom = Gtk.Label(xalign=0, yalign=0) - lbl_padding_bottom.set_text("") - - vbox_bottom.pack_start(lbl_padding_bottom, False, True, 0) - vbox_bottom.pack_start(grid_bottom, False, True, 0) - - self.stack.add_titled(searchentry, "Package Search", "Package Search") - - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) - vbox.set_border_width(10) - - vbox.pack_start(stack_switcher, False, False, 0) - vbox.pack_start(self.stack, False, False, 0) - vbox.pack_end(vbox_bottom, False, True, 0) - - self.add(vbox) - self.show_all() - - thread_pacman_sync_file_db = fn.threading.Thread( - name="thread_pacman_sync_file_db", - target=fn.sync_file_db, - daemon=True, - ) - thread_pacman_sync_file_db.start() - - def on_close(self, widget): - self.hide() - self.destroy() - - def on_search_package_activated(self, searchentry): - if searchentry.get_text_length() == 0 and self.search_package_activated: - self.search_package_activated = False - self.stack.get_child_by_name("Package Information").destroy() - - self.stack.get_child_by_name("Package Files").destroy() - Gtk.Window.grab_focus(self.headerbar) - else: - self.perform_search(searchentry) - - def on_search_package_cleared(self, searchentry, icon_pos, event): - searchentry.set_placeholder_text("Search using package name...") - if self.search_package_activated is True: - self.search_package_activated = False - self.stack.get_child_by_name("Package Information").destroy() - - self.stack.get_child_by_name("Package Files").destroy() - - Gtk.Window.grab_focus(self.headerbar) - - def perform_search(self, searchentry): - try: - if ( - len(searchentry.get_text().rstrip().lstrip()) > 0 - and not searchentry.get_text().isspace() - ): - term = searchentry.get_text().rstrip().lstrip() - - if len(term) > 0: - fn.logger.info("Searching pacman file database") - - package_metadata = fn.get_package_information(term) - - if package_metadata is not None: - # package information - - if self.search_package_activated is True: - self.stack.get_child_by_name( - "Package Information" - ).destroy() - - self.stack.get_child_by_name("Package Files").destroy() - - box_outer = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=5 - ) - - listbox = Gtk.ListBox() - listbox.set_selection_mode(Gtk.SelectionMode.NONE) - box_outer.pack_start(listbox, True, True, 0) - - # package name - row_package_title = Gtk.ListBoxRow() - vbox_package_title = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_title.add(vbox_package_title) - lbl_package_name_title = Gtk.Label(xalign=0) - lbl_package_name_title.set_markup("Package Name") - - lbl_package_name_value = Gtk.Label(xalign=0) - lbl_package_name_value.set_text(package_metadata["name"]) - vbox_package_title.pack_start( - lbl_package_name_title, True, True, 0 - ) - vbox_package_title.pack_start( - lbl_package_name_value, True, True, 0 - ) - - listbox.add(row_package_title) - - # repository - - row_package_repo = Gtk.ListBoxRow() - vbox_package_repo = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_repo.add(vbox_package_repo) - lbl_package_repo_title = Gtk.Label(xalign=0) - lbl_package_repo_title.set_markup("Repository") - - lbl_package_repo_value = Gtk.Label(xalign=0) - lbl_package_repo_value.set_text(package_metadata["repository"]) - vbox_package_repo.pack_start( - lbl_package_repo_title, True, True, 0 - ) - vbox_package_repo.pack_start( - lbl_package_repo_value, True, True, 0 - ) - - listbox.add(row_package_repo) - - # description - - row_package_description = Gtk.ListBoxRow() - vbox_package_description = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_description.add(vbox_package_description) - lbl_package_description_title = Gtk.Label(xalign=0) - lbl_package_description_title.set_markup("Description") - - lbl_package_description_value = Gtk.Label(xalign=0) - lbl_package_description_value.set_text( - package_metadata["description"] - ) - vbox_package_description.pack_start( - lbl_package_description_title, True, True, 0 - ) - vbox_package_description.pack_start( - lbl_package_description_value, True, True, 0 - ) - - listbox.add(row_package_description) - - # arch - - row_package_arch = Gtk.ListBoxRow() - vbox_package_arch = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_arch.add(vbox_package_arch) - lbl_package_arch_title = Gtk.Label(xalign=0) - lbl_package_arch_title.set_markup("Architecture") - - lbl_package_arch_value = Gtk.Label(xalign=0) - lbl_package_arch_value.set_text(package_metadata["arch"]) - vbox_package_arch.pack_start( - lbl_package_arch_title, True, True, 0 - ) - vbox_package_arch.pack_start( - lbl_package_arch_value, True, True, 0 - ) - - listbox.add(row_package_arch) - - # url - - row_package_url = Gtk.ListBoxRow() - vbox_package_url = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_url.add(vbox_package_url) - lbl_package_url_title = Gtk.Label(xalign=0) - lbl_package_url_title.set_markup("URL") - - lbl_package_url_value = Gtk.Label(xalign=0) - lbl_package_url_value.set_markup( - "%s" - % (package_metadata["url"], package_metadata["url"]) - ) - vbox_package_url.pack_start( - lbl_package_url_title, True, True, 0 - ) - vbox_package_url.pack_start( - lbl_package_url_value, True, True, 0 - ) - - listbox.add(row_package_url) - - # download size - - row_package_size = Gtk.ListBoxRow() - vbox_package_size = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_size.add(vbox_package_size) - lbl_package_size_title = Gtk.Label(xalign=0) - lbl_package_size_title.set_markup("Download size") - - lbl_package_size_value = Gtk.Label(xalign=0) - lbl_package_size_value.set_text( - package_metadata["download_size"] - ) - vbox_package_size.pack_start( - lbl_package_size_title, True, True, 0 - ) - vbox_package_size.pack_start( - lbl_package_size_value, True, True, 0 - ) - - listbox.add(row_package_size) - - # installed size - - row_package_installed_size = Gtk.ListBoxRow() - vbox_package_installed_size = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_installed_size.add(vbox_package_installed_size) - lbl_package_installed_size_title = Gtk.Label(xalign=0) - lbl_package_installed_size_title.set_markup( - "Installed size" - ) - - lbl_package_installed_size_value = Gtk.Label(xalign=0) - lbl_package_installed_size_value.set_text( - package_metadata["installed_size"] - ) - vbox_package_installed_size.pack_start( - lbl_package_installed_size_title, True, True, 0 - ) - vbox_package_installed_size.pack_start( - lbl_package_installed_size_value, True, True, 0 - ) - - listbox.add(row_package_installed_size) - - # build date - - row_package_build_date = Gtk.ListBoxRow() - vbox_package_build_date = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_build_date.add(vbox_package_build_date) - lbl_package_build_date_title = Gtk.Label(xalign=0) - lbl_package_build_date_title.set_markup("Build date") - - lbl_package_build_date_value = Gtk.Label(xalign=0) - lbl_package_build_date_value.set_text( - package_metadata["build_date"] - ) - vbox_package_build_date.pack_start( - lbl_package_build_date_title, True, True, 0 - ) - vbox_package_build_date.pack_start( - lbl_package_build_date_value, True, True, 0 - ) - - listbox.add(row_package_build_date) - - # packager - - row_package_maintainer = Gtk.ListBoxRow() - vbox_package_maintainer = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_maintainer.add(vbox_package_maintainer) - lbl_package_maintainer_title = Gtk.Label(xalign=0) - lbl_package_maintainer_title.set_markup("Packager") - - lbl_package_maintainer_value = Gtk.Label(xalign=0) - lbl_package_maintainer_value.set_text( - package_metadata["packager"] - ) - vbox_package_maintainer.pack_start( - lbl_package_maintainer_title, True, True, 0 - ) - vbox_package_maintainer.pack_start( - lbl_package_maintainer_value, True, True, 0 - ) - - listbox.add(row_package_maintainer) - - # depends on - - expander_depends_on = Gtk.Expander() - expander_depends_on.set_expanded(True) - expander_depends_on.set_use_markup(True) - expander_depends_on.set_resize_toplevel(True) - expander_depends_on.set_label("Depends on") - - row_package_depends_on = Gtk.ListBoxRow() - expander_depends_on.add(row_package_depends_on) - vbox_package_depends_on = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_depends_on.add(vbox_package_depends_on) - - if len(package_metadata["depends_on"]) > 0: - treestore_depends = Gtk.TreeStore(str, str) - - for item in package_metadata["depends_on"]: - treestore_depends.append(None, list(item)) - - treeview_depends = Gtk.TreeView(model=treestore_depends) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Package", renderer, text=0) - - treeview_depends.append_column(column) - - vbox_package_depends_on.pack_start( - treeview_depends, True, True, 0 - ) - - else: - lbl_package_depends_value = Gtk.Label(xalign=0, yalign=0) - lbl_package_depends_value.set_text("None") - - vbox_package_depends_on.pack_start( - lbl_package_depends_value, True, True, 0 - ) - - listbox.add(expander_depends_on) - - # conflicts with - - expander_conflicts_with = Gtk.Expander() - expander_conflicts_with.set_use_markup(True) - expander_conflicts_with.set_expanded(True) - expander_conflicts_with.set_resize_toplevel(True) - expander_conflicts_with.set_label("Conflicts with") - - row_package_conflicts_with = Gtk.ListBoxRow() - expander_conflicts_with.add(row_package_conflicts_with) - vbox_package_conflicts_with = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_conflicts_with.add(vbox_package_conflicts_with) - - if len(package_metadata["conflicts_with"]) > 0: - treestore_conflicts = Gtk.TreeStore(str, str) - - for item in package_metadata["conflicts_with"]: - treestore_conflicts.append(None, list(item)) - - treeview_conflicts = Gtk.TreeView(model=treestore_conflicts) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Package", renderer, text=0) - - treeview_conflicts.append_column(column) - - vbox_package_conflicts_with.pack_start( - treeview_conflicts, True, True, 0 - ) - - else: - lbl_package_conflicts_with_value = Gtk.Label( - xalign=0, yalign=0 - ) - lbl_package_conflicts_with_value.set_text("None") - - vbox_package_conflicts_with.pack_start( - lbl_package_conflicts_with_value, True, True, 0 - ) - - listbox.add(expander_conflicts_with) - - checkbtn_installed = Gtk.CheckButton(label="Installed") - checkbtn_installed.set_active(False) - checkbtn_installed.set_sensitive(False) - - # is the package installed - installed = fn.check_package_installed(term) - - if installed is True: - checkbtn_installed.set_active(True) - - # box_outer.pack_start(checkbtn_installed, True, True, 0) - - scrolled_window_package_info = Gtk.ScrolledWindow() - scrolled_window_package_info.set_propagate_natural_height(True) - scrolled_window_package_info.add(box_outer) - - vbox_package_info = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - - lbl_padding_vbox = Gtk.Label(xalign=0, yalign=0) - lbl_padding_vbox.set_text("") - - vbox_package_info.pack_start( - scrolled_window_package_info, True, True, 0 - ) - vbox_package_info.pack_start(lbl_padding_vbox, True, True, 0) - vbox_package_info.pack_start(checkbtn_installed, True, True, 0) - - self.stack.add_titled( - vbox_package_info, - "Package Information", - "Package Information", - ) - - # package files - - package_files = fn.get_package_files(term) - if package_files is not None: - lbl_package_title = Gtk.Label(xalign=0, yalign=0) - lbl_package_title.set_markup("Package") - - lbl_package_title_value = Gtk.Label(xalign=0, yalign=0) - - lbl_package_title_value.set_text(package_metadata["name"]) - - treestore_filelist = Gtk.TreeStore(str, str) - - for file in package_files: - treestore_filelist.append(None, list(file)) - - treeview_files = Gtk.TreeView(model=treestore_filelist) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Files", renderer, text=0) - - treeview_files.append_column(column) - - vbox_package_files = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - - vbox_package_files.pack_start( - lbl_package_title, True, True, 0 - ) - vbox_package_files.pack_start( - lbl_package_title_value, True, True, 0 - ) - - lbl_padding_package_files = Gtk.Label(xalign=0, yalign=0) - lbl_padding_package_files.set_text("") - - vbox_package_files.pack_start( - lbl_padding_package_files, True, True, 0 - ) - - scrolled_window_package_files = Gtk.ScrolledWindow() - scrolled_window_package_files.set_propagate_natural_height( - True - ) - scrolled_window_package_files.add(treeview_files) - - vbox_package_files.pack_start( - scrolled_window_package_files, True, True, 0 - ) - - self.stack.add_titled( - vbox_package_files, - "Package Files", - "Package Files", - ) - - self.search_package_activated = True - self.show_all() - - else: - message_dialog = MessageDialog( - "Info", - "Search returned 0 results", - "Failed to find package name", - "Are the correct pacman mirrorlists configured ?\nOr try to search again using the exact package name", - "info", - False, - ) - - message_dialog.show_all() - message_dialog.run() - message_dialog.hide() - - except Exception as e: - fn.logger.error("Exception in perform_search(): %s" % e) \ No newline at end of file diff --git a/usr/share/blackbox/ui/PackagesImportDialog.py b/usr/share/blackbox/ui/PackagesImportDialog.py deleted file mode 100644 index 5e41c14..0000000 --- a/usr/share/blackbox/ui/PackagesImportDialog.py +++ /dev/null @@ -1,228 +0,0 @@ -# ============================================================ -# Authors: Brad Heffernan - Erik Dubois - Cameron Percival -# ============================================================ - -import os -import gi -import Functions as fn -from queue import Queue -from ui.MessageDialog import MessageDialog - -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - - -class PackagesImportDialog(Gtk.Dialog): - """create a gui""" - - def __init__(self, package_file, packages_list, logfile): - Gtk.Dialog.__init__(self) - - # Create a queue for storing package import messages from pacman - self.pkg_import_queue = Queue() - - # Create a queue for storing package install errors - self.pkg_err_queue = Queue() - - # Create a queue for storing package install status - self.pkg_status_queue = Queue() - - self.package_file = package_file - self.packages_list = packages_list - self.logfile = logfile - - self.stop_thread = False - - self.set_resizable(True) - self.set_border_width(10) - self.set_size_request(800, 700) - self.set_modal(True) - - headerbar = Gtk.HeaderBar() - headerbar.set_title("Import packages") - headerbar.set_show_close_button(True) - - self.set_titlebar(headerbar) - - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - - hbox_title = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - lbl_packages_title = Gtk.Label(xalign=0) - lbl_packages_title.set_name("title") - lbl_packages_title.set_text("Packages") - - hbox_title.pack_start(lbl_packages_title, False, False, 0) - - hbox_title_install = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - label_install_title = Gtk.Label(xalign=0) - label_install_title.set_markup(" Install Packages") - - hbox_title_install.pack_start(label_install_title, False, False, 0) - - hbox_sep = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - hsep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - hbox_sep.pack_start(hsep, True, True, 0) - - frame_install = Gtk.Frame(label="") - frame_install_label = frame_install.get_label_widget() - frame_install_label.set_markup("Install Packages") - - hbox_install = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - label_install_desc = Gtk.Label(xalign=0, yalign=0) - label_install_desc.set_markup( - f"" - f" WARNING: Proceed with caution this will install packages onto your system!\n" - f" Packages from the AUR are not supported \n" - f" This also performs a full system upgrade\n\n" - f" - A list of packages are sourced from {self.package_file}\n" - f" - To ignore a package, add a # in front of the package name\n" - f" - Log file: {self.logfile}\n" - f" - A reboot is recommended when core Linux packages are installed\n" - ) - - self.scrolled_window = Gtk.ScrolledWindow() - - self.textview = Gtk.TextView() - self.textview.set_name("textview_log") - self.textview.set_property("editable", False) - self.textview.set_property("monospace", True) - self.textview.set_border_width(10) - self.textview.set_vexpand(True) - self.textview.set_hexpand(True) - - msg_buffer = self.textview.get_buffer() - msg_buffer.insert( - msg_buffer.get_end_iter(), - "\n Click Yes to confirm install of the following packages:\n\n", - ) - - lbl_title_message = Gtk.Label(xalign=0, yalign=0) - lbl_title_message.set_markup( - "There are %s packages to install, proceed ?" - % len(self.packages_list) - ) - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text("") - - self.infobar = Gtk.InfoBar() - - content = self.infobar.get_content_area() - content.add(lbl_title_message) - - self.infobar.set_revealed(True) - - for package in sorted(self.packages_list): - msg_buffer.insert(msg_buffer.get_end_iter(), " - %s\n" % package) - - # move focus away from the textview, to hide the cursor at load - headerbar.set_property("can-focus", True) - Gtk.Window.grab_focus(headerbar) - - self.scrolled_window.add(self.textview) - - self.button_yes = self.add_button("Yes", Gtk.ResponseType.OK) - self.button_yes.set_size_request(100, 30) - btn_yes_context = self.button_yes.get_style_context() - btn_yes_context.add_class("destructive-action") - - self.button_no = self.add_button("Close", Gtk.ResponseType.CANCEL) - self.button_no.set_size_request(100, 30) - - self.connect("response", self.on_response) - - vbox_log_dir = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - - btn_open_log_dir = Gtk.Button(label="Open log directory") - btn_open_log_dir.connect("clicked", self.on_open_log_dir_clicked) - btn_open_log_dir.set_size_request(100, 30) - - vbox_log_dir.pack_start(btn_open_log_dir, False, False, 0) - - grid_message = Gtk.Grid() - - grid_message.attach(label_install_desc, 0, 0, 1, 1) - grid_message.attach(self.infobar, 0, 1, 1, 1) - grid_message.attach(lbl_padding1, 0, 2, 1, 1) - - grid_message.attach(self.scrolled_window, 0, 3, 1, 1) - grid_message.attach(lbl_padding2, 0, 4, 1, 1) - grid_message.attach(vbox_log_dir, 0, 5, 1, 1) - - self.vbox.add(grid_message) - - def on_open_log_dir_clicked(self, widget): - fn.open_log_dir() - - def display_progress(self): - self.textview.destroy() - self.infobar.destroy() - self.button_yes.destroy() - - self.label_package_status = Gtk.Label(xalign=0, yalign=0) - self.label_package_count = Gtk.Label(xalign=0, yalign=0) - - label_warning_close = Gtk.Label(xalign=0, yalign=0) - label_warning_close.set_markup( - "Do not close this window during package installation" - ) - - self.textview = Gtk.TextView() - self.textview.set_name("textview_log") - self.textview.set_property("editable", False) - self.textview.set_property("monospace", True) - self.textview.set_border_width(10) - self.textview.set_vexpand(True) - self.textview.set_hexpand(True) - - self.scrolled_window.add(self.textview) - - self.msg_buffer = self.textview.get_buffer() - - self.vbox.add(label_warning_close) - self.vbox.add(self.label_package_status) - self.vbox.add(self.label_package_count) - - fn.Thread( - target=fn.monitor_package_import, - args=(self,), - daemon=True, - ).start() - - self.show_all() - - fn.logger.info("Installing packages") - event = "%s [INFO]: Installing packages\n" % fn.datetime.now().strftime( - "%Y-%m-%d-%H-%M-%S" - ) - - fn.logger.info("Log file = %s" % self.logfile) - - self.pkg_import_queue.put(event) - - # debug install, overrride packages_list - # self.packages_list = ["cheese", "firefox", "blackbox-dev-git", "blackbox-git"] - - # starts 2 threads one to install the packages, and another to check install status - - fn.Thread( - target=fn.import_packages, - args=(self,), - daemon=True, - ).start() - - fn.Thread(target=fn.log_package_status, args=(self,), daemon=True).start() - - def on_response(self, dialog, response): - if response in (Gtk.ResponseType.OK, Gtk.ResponseType.YES): - self.stop_thread = False - self.display_progress() - - else: - self.stop_thread = True - dialog.hide() - dialog.destroy() \ No newline at end of file diff --git a/usr/share/blackbox/ui/PacmanLogWindow.py b/usr/share/blackbox/ui/PacmanLogWindow.py deleted file mode 100644 index 2c47bf6..0000000 --- a/usr/share/blackbox/ui/PacmanLogWindow.py +++ /dev/null @@ -1,71 +0,0 @@ -# This class is used to create a window to monitor the pacman log file inside /var/log/pacman.log - -import os -import gi -import Functions as fn -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class PacmanLogWindow(Gtk.Window): - def __init__(self, textview_pacmanlog, btn_pacmanlog): - Gtk.Window.__init__(self) - - self.start_logtimer = True - self.textview_pacmanlog = textview_pacmanlog - self.btn_pacmanlog = btn_pacmanlog - headerbar = Gtk.HeaderBar() - - headerbar.set_show_close_button(True) - - self.set_titlebar(headerbar) - - self.set_title("BlackBox - Pacman log file viewer") - self.set_default_size(800, 600) - self.set_resizable(True) - self.set_border_width(10) - self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - self.connect("delete-event", self.on_close) - - btn_pacmanlog_ok = Gtk.Button(label="OK") - btn_pacmanlog_ok.connect("clicked", self.on_response, "response") - btn_pacmanlog_ok.set_size_request(100, 30) - btn_pacmanlog_ok.set_halign(Gtk.Align.END) - - pacmanlog_scrolledwindow = Gtk.ScrolledWindow() - pacmanlog_scrolledwindow.set_size_request(750, 500) - pacmanlog_scrolledwindow.add(self.textview_pacmanlog) - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - vbox_pacmanlog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - vbox_pacmanlog.pack_start(pacmanlog_scrolledwindow, True, True, 0) - vbox_pacmanlog.pack_start(lbl_padding1, False, False, 0) - vbox_pacmanlog.pack_start(btn_pacmanlog_ok, False, False, 0) - - self.add(vbox_pacmanlog) - - def on_close(self, widget, data): - fn.logger.debug("Closing pacman log monitoring window") - self.start_logtimer = False - self.btn_pacmanlog.set_sensitive(True) - - self.hide() - self.destroy() - - def on_response(self, widget, response): - # stop updating the textview - fn.logger.debug("Closing pacman log monitoring dialog") - self.start_logtimer = False - self.btn_pacmanlog.set_sensitive(True) - - # self.remove(self) - self.hide() - self.destroy() \ No newline at end of file diff --git a/usr/share/blackbox/ui/ProgressBarWindow.py b/usr/share/blackbox/ui/ProgressBarWindow.py deleted file mode 100644 index 95ccc24..0000000 --- a/usr/share/blackbox/ui/ProgressBarWindow.py +++ /dev/null @@ -1,87 +0,0 @@ -from gi.repository import Gtk, GLib -import gi - - -# Since a system can have multiple versions -# of GTK + installed, we want to make -# sure that we are importing GTK + 3. -gi.require_version("Gtk", "3.0") - - -class ProgressBarWindow(Gtk.Window): - new_value = 0.0 - - def __init__(self): - Gtk.Window.__init__(self, title="Progress Bar") - self.set_border_width(10) - - vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) - self.add(vbox) - - # Create a ProgressBar - self.progressbar = Gtk.ProgressBar() - vbox.pack_start(self.progressbar, True, True, 0) - - # Create CheckButton with labels "Show text", - # "Activity mode", "Right to Left" respectively - # button = Gtk.CheckButton(label="Show text") - # button.connect("toggled", self.on_show_text_toggled) - # vbox.pack_start(button, True, True, 0) - - # button = Gtk.CheckButton(label="Activity mode") - # button.connect("toggled", self.on_activity_mode_toggled) - # vbox.pack_start(button, True, True, 0) - - # button = Gtk.CheckButton(label="Right to Left") - # button.connect("toggled", self.on_right_to_left_toggled) - # vbox.pack_start(button, True, True, 0) - - # self.timeout_id = GLib.timeout_add(5000, self.on_timeout, None) - self.activity_mode = False - - def set_text(self, text): - self.progressbar.set_text(text) - self.progressbar.set_show_text(True) - - def reset_timer(self): - new_value = 0.0 - self.progressbar.set_fraction(new_value) - - def on_activity_mode_toggled(self, button): - self.activity_mode = button.get_active() - if self.activity_mode: - self.progressbar.pulse() - else: - self.progressbar.set_fraction(0.0) - - def on_right_to_left_toggled(self, button): - value = button.get_active() - self.progressbar.set_inverted(value) - - def update(self, fraction): - new_value = self.progressbar.get_fraction() + fraction - self.progressbar.set_fraction(new_value) - if new_value >= 1.0: - return False - return True - - def get_complete(self): - if self.progressbar.get_fraction() >= 1.0: - return True - return False - - def on_timeout(self, user_data=0.01): - """ - Update value on the progress bar - """ - if self.activity_mode: - self.progressbar.pulse() - else: - new_value = self.progressbar.get_fraction() + user_data - - if new_value > 1: - new_value = 0.0 - return False - - self.progressbar.set_fraction(new_value) - return True diff --git a/usr/share/blackbox/ui/ProgressDialog.py b/usr/share/blackbox/ui/ProgressDialog.py deleted file mode 100644 index 0af19f6..0000000 --- a/usr/share/blackbox/ui/ProgressDialog.py +++ /dev/null @@ -1,400 +0,0 @@ -# This class is used to create a modal dialog window showing progress of a package install/uninstall and general package information - -import os -import gi -import Functions as fn -from ui.MessageDialog import MessageDialog -from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib - -gi.require_version("Gtk", "3.0") - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class ProgressDialog(Gtk.Dialog): - def __init__(self, action, package, command, package_metadata): - Gtk.Dialog.__init__(self) - - self.package_found = True - # this gets package information using pacman -Si or pacman -Qi whichever returns output - # package_metadata = fn.get_package_information(pkg.name) - - # if a mirrorlist isn't configured properly, pacman will not be able to query its repository - # so the following is a condition to make sure the data returned isn't an error - - if type(package_metadata) is dict: - package_progress_dialog_headerbar = Gtk.HeaderBar() - package_progress_dialog_headerbar.set_show_close_button(True) - self.set_titlebar(package_progress_dialog_headerbar) - - self.connect("delete-event", package_progress_dialog_on_close, self, action) - - if action == "install": - self.set_title("BlackBox - installing package %s" % package.name) - - elif action == "uninstall": - self.set_title("BlackBox - removing package %s" % package.name) - - self.btn_package_progress_close = Gtk.Button(label="OK") - self.btn_package_progress_close.connect( - "clicked", - on_package_progress_close_response, - self, - ) - self.btn_package_progress_close.set_sensitive(False) - self.btn_package_progress_close.set_size_request(100, 30) - self.btn_package_progress_close.set_halign(Gtk.Align.END) - - self.set_resizable(True) - self.set_size_request(850, 700) - self.set_modal(True) - self.set_border_width(10) - self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) - self.set_icon_from_file(os.path.join(base_dir, "images/blackbox.png")) - - lbl_pacman_action_title = Gtk.Label(xalign=0, yalign=0) - lbl_pacman_action_title.set_text("Running command:") - - lbl_pacman_action_value = Gtk.Label(xalign=0, yalign=0) - lbl_pacman_action_value.set_markup("%s" % command) - - stack = Gtk.Stack() - stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) - stack.set_transition_duration(350) - stack.set_hhomogeneous(False) - stack.set_vhomogeneous(False) - - stack_switcher = Gtk.StackSwitcher() - stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) - stack_switcher.set_stack(stack) - stack_switcher.set_homogeneous(True) - - package_progress_grid = Gtk.Grid() - - self.infobar = Gtk.InfoBar() - self.infobar.set_name("infobar_info") - - content = self.infobar.get_content_area() - content.add(lbl_pacman_action_title) - content.add(lbl_pacman_action_value) - - self.infobar.set_revealed(True) - - lbl_padding_header1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding_header1.set_text("") - - package_progress_grid.attach(lbl_padding_header1, 0, 1, 1, 1) - package_progress_grid.attach(self.infobar, 0, 2, 1, 1) - - package_progress_grid.set_property("can-focus", True) - Gtk.Window.grab_focus(package_progress_grid) - - lbl_padding1 = Gtk.Label(xalign=0, yalign=0) - lbl_padding1.set_text("") - - lbl_padding2 = Gtk.Label(xalign=0, yalign=0) - lbl_padding2.set_text("") - lbl_padding2.set_halign(Gtk.Align.END) - - package_progress_grid.attach(lbl_padding1, 0, 3, 1, 1) - - package_progress_scrolled_window = Gtk.ScrolledWindow() - self.package_progress_textview = Gtk.TextView() - self.package_progress_textview.set_property("editable", False) - self.package_progress_textview.set_property("monospace", True) - self.package_progress_textview.set_border_width(10) - self.package_progress_textview.set_vexpand(True) - self.package_progress_textview.set_hexpand(True) - buffer = self.package_progress_textview.get_buffer() - self.package_progress_textview.set_buffer(buffer) - - package_progress_scrolled_window.set_size_request(700, 430) - - package_progress_scrolled_window.add(self.package_progress_textview) - package_progress_grid.attach(package_progress_scrolled_window, 0, 4, 1, 1) - - vbox_close = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - vbox_close.pack_start(lbl_padding2, True, True, 0) - vbox_close.pack_start(self.btn_package_progress_close, True, True, 0) - - stack.add_titled(package_progress_grid, "Progress", "Package Progress") - - # package information - box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - - listbox = Gtk.ListBox() - listbox.set_selection_mode(Gtk.SelectionMode.NONE) - box_outer.pack_start(listbox, True, True, 0) - - # package name - row_package_title = Gtk.ListBoxRow() - vbox_package_title = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_title.add(vbox_package_title) - lbl_package_name_title = Gtk.Label(xalign=0) - lbl_package_name_title.set_markup("Package Name") - - lbl_package_name_value = Gtk.Label(xalign=0) - lbl_package_name_value.set_text(package_metadata["name"]) - vbox_package_title.pack_start(lbl_package_name_title, True, True, 0) - vbox_package_title.pack_start(lbl_package_name_value, True, True, 0) - - listbox.add(row_package_title) - - # repository - - row_package_repo = Gtk.ListBoxRow() - vbox_package_repo = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - row_package_repo.add(vbox_package_repo) - lbl_package_repo_title = Gtk.Label(xalign=0) - lbl_package_repo_title.set_markup("Repository") - - lbl_package_repo_value = Gtk.Label(xalign=0) - lbl_package_repo_value.set_text(package_metadata["repository"]) - vbox_package_repo.pack_start(lbl_package_repo_title, True, True, 0) - vbox_package_repo.pack_start(lbl_package_repo_value, True, True, 0) - - listbox.add(row_package_repo) - - # description - - row_package_description = Gtk.ListBoxRow() - vbox_package_description = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_description.add(vbox_package_description) - lbl_package_description_title = Gtk.Label(xalign=0) - lbl_package_description_title.set_markup("Description") - - lbl_package_description_value = Gtk.Label(xalign=0) - lbl_package_description_value.set_text(package_metadata["description"]) - vbox_package_description.pack_start( - lbl_package_description_title, True, True, 0 - ) - vbox_package_description.pack_start( - lbl_package_description_value, True, True, 0 - ) - - listbox.add(row_package_description) - - # arch - - row_package_arch = Gtk.ListBoxRow() - vbox_package_arch = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - row_package_arch.add(vbox_package_arch) - lbl_package_arch_title = Gtk.Label(xalign=0) - lbl_package_arch_title.set_markup("Architecture") - - lbl_package_arch_value = Gtk.Label(xalign=0) - lbl_package_arch_value.set_text(package_metadata["arch"]) - vbox_package_arch.pack_start(lbl_package_arch_title, True, True, 0) - vbox_package_arch.pack_start(lbl_package_arch_value, True, True, 0) - - listbox.add(row_package_arch) - - # url - - row_package_url = Gtk.ListBoxRow() - vbox_package_url = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - row_package_url.add(vbox_package_url) - lbl_package_url_title = Gtk.Label(xalign=0) - lbl_package_url_title.set_markup("URL") - - lbl_package_url_value = Gtk.Label(xalign=0) - lbl_package_url_value.set_markup( - "%s" - % (package_metadata["url"], package_metadata["url"]) - ) - vbox_package_url.pack_start(lbl_package_url_title, True, True, 0) - vbox_package_url.pack_start(lbl_package_url_value, True, True, 0) - - listbox.add(row_package_url) - - # download size - - row_package_size = Gtk.ListBoxRow() - vbox_package_size = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - row_package_size.add(vbox_package_size) - lbl_package_size_title = Gtk.Label(xalign=0) - lbl_package_size_title.set_markup("Download size") - - lbl_package_size_value = Gtk.Label(xalign=0) - lbl_package_size_value.set_text(package_metadata["download_size"]) - vbox_package_size.pack_start(lbl_package_size_title, True, True, 0) - vbox_package_size.pack_start(lbl_package_size_value, True, True, 0) - - listbox.add(row_package_size) - - # installed size - - row_package_installed_size = Gtk.ListBoxRow() - vbox_package_installed_size = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_installed_size.add(vbox_package_installed_size) - lbl_package_installed_size_title = Gtk.Label(xalign=0) - lbl_package_installed_size_title.set_markup("Installed size") - - lbl_package_installed_size_value = Gtk.Label(xalign=0) - lbl_package_installed_size_value.set_text( - package_metadata["installed_size"] - ) - vbox_package_installed_size.pack_start( - lbl_package_installed_size_title, True, True, 0 - ) - vbox_package_installed_size.pack_start( - lbl_package_installed_size_value, True, True, 0 - ) - - listbox.add(row_package_installed_size) - - # build date - - row_package_build_date = Gtk.ListBoxRow() - vbox_package_build_date = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_build_date.add(vbox_package_build_date) - lbl_package_build_date_title = Gtk.Label(xalign=0) - lbl_package_build_date_title.set_markup("Build date") - - lbl_package_build_date_value = Gtk.Label(xalign=0) - lbl_package_build_date_value.set_text(package_metadata["build_date"]) - vbox_package_build_date.pack_start( - lbl_package_build_date_title, True, True, 0 - ) - vbox_package_build_date.pack_start( - lbl_package_build_date_value, True, True, 0 - ) - - listbox.add(row_package_build_date) - - # packager - - row_package_maintainer = Gtk.ListBoxRow() - vbox_package_maintainer = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_maintainer.add(vbox_package_maintainer) - lbl_package_maintainer_title = Gtk.Label(xalign=0) - lbl_package_maintainer_title.set_markup("Packager") - - lbl_package_maintainer_value = Gtk.Label(xalign=0) - lbl_package_maintainer_value.set_text(package_metadata["packager"]) - vbox_package_maintainer.pack_start( - lbl_package_maintainer_title, True, True, 0 - ) - vbox_package_maintainer.pack_start( - lbl_package_maintainer_value, True, True, 0 - ) - - listbox.add(row_package_maintainer) - - # depends on - - expander_depends_on = Gtk.Expander() - expander_depends_on.set_use_markup(True) - expander_depends_on.set_resize_toplevel(True) - expander_depends_on.set_label("Depends on") - - row_package_depends_on = Gtk.ListBoxRow() - expander_depends_on.add(row_package_depends_on) - vbox_package_depends_on = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_depends_on.add(vbox_package_depends_on) - - if len(package_metadata["depends_on"]) > 0: - treestore_depends = Gtk.TreeStore(str, str) - - for item in package_metadata["depends_on"]: - treestore_depends.append(None, list(item)) - - treeview_depends = Gtk.TreeView(model=treestore_depends) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Package", renderer, text=0) - - treeview_depends.append_column(column) - - vbox_package_depends_on.pack_start(treeview_depends, True, True, 0) - - else: - lbl_package_depends_value = Gtk.Label(xalign=0, yalign=0) - lbl_package_depends_value.set_text("None") - - vbox_package_depends_on.pack_start( - lbl_package_depends_value, True, True, 0 - ) - - listbox.add(expander_depends_on) - - # conflicts with - - expander_conflicts_with = Gtk.Expander() - expander_conflicts_with.set_use_markup(True) - expander_conflicts_with.set_resize_toplevel(True) - expander_conflicts_with.set_label("Conflicts with") - - row_package_conflicts_with = Gtk.ListBoxRow() - expander_conflicts_with.add(row_package_conflicts_with) - vbox_package_conflicts_with = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=0 - ) - row_package_conflicts_with.add(vbox_package_conflicts_with) - - if len(package_metadata["conflicts_with"]) > 0: - treestore_conflicts = Gtk.TreeStore(str, str) - - for item in package_metadata["conflicts_with"]: - treestore_conflicts.append(None, list(item)) - - treeview_conflicts = Gtk.TreeView(model=treestore_conflicts) - - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Package", renderer, text=0) - - treeview_conflicts.append_column(column) - - vbox_package_conflicts_with.pack_start( - treeview_conflicts, True, True, 0 - ) - - else: - lbl_package_conflicts_with_value = Gtk.Label(xalign=0, yalign=0) - lbl_package_conflicts_with_value.set_text("None") - - vbox_package_conflicts_with.pack_start( - lbl_package_conflicts_with_value, True, True, 0 - ) - - listbox.add(expander_conflicts_with) - - package_metadata_scrolled_window = Gtk.ScrolledWindow() - - package_metadata_scrolled_window.add(box_outer) - - stack.add_titled( - package_metadata_scrolled_window, "Package Information", "Information" - ) - - self.vbox.add(stack_switcher) - self.vbox.add(stack) - self.vbox.add(vbox_close) - - -def on_package_progress_close_response(self, widget): - self.pkg_dialog_closed = True - fn.logger.debug("Closing package progress dialog") - widget.hide() - widget.destroy() - - -def package_progress_dialog_on_close(widget, data, self, action): - self.pkg_dialog_closed = True - fn.logger.debug("Closing package progress dialog") - widget.hide() - widget.destroy() diff --git a/usr/share/blackbox/ui/SplashScreen.py b/usr/share/blackbox/ui/SplashScreen.py deleted file mode 100644 index f2ad00d..0000000 --- a/usr/share/blackbox/ui/SplashScreen.py +++ /dev/null @@ -1,30 +0,0 @@ -import gi -from Functions import os - -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, GdkPixbuf, Gdk # noqa - -base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# base_dir = os.path.dirname(os.path.realpath(__file__)) - - -class SplashScreen(Gtk.Window): - def __init__(self): - Gtk.Window.__init__(self, Gtk.WindowType.POPUP, title="") - self.set_decorated(False) - self.set_resizable(False) - self.set_size_request(600, 400) - self.set_position(Gtk.WindowPosition.CENTER) - - main_vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=1) - self.add(main_vbox) - - self.image = Gtk.Image() - pimage = GdkPixbuf.Pixbuf().new_from_file_at_size( - base_dir + "/images/splash.png", 600, 400 - ) - self.image.set_from_pixbuf(pimage) - - main_vbox.pack_start(self.image, True, True, 0) - - self.show_all() diff --git a/usr/share/blackbox/yaml/netinstall-fuzzer.yaml b/usr/share/blackbox/yaml/netinstall-fuzzer.yaml deleted file mode 100644 index 0051487..0000000 --- a/usr/share/blackbox/yaml/netinstall-fuzzer.yaml +++ /dev/null @@ -1,227 +0,0 @@ -- name: "Fuzzer" - description: "Fuzzer" - critical: false - hidden: false - selected: false - expanded: true - packages: - - 0d1n - - abuse-ssl-bypass-waf - - aflplusplus - - aggroargs - - ajpfuzzer - - astra - - atlas - - atscan - - backfuzz - - bbscan - - bfuzz - - bing-lfi-rfi - - birp - - bluebox-ng - - boofuzz - - browser-fuzzer - - brutexss - - bss - - bt_audit - - bunny - - burpsuite - - cantoolz - - capfuzz - - cecster - - chipsec - - choronzon - - cirt-fuzzer - - cisco-auditing-tool - - cmsfuzz - - conscan - - cookie-cadger - - crackql - - crlf-injector - - dalfox - - darkbing - - dharma - - dhcpig - - dizzy - - domato - - doona - - dotdotpwn - - dpscan - - dr-checker - - drozer - - easyfuzzer - - faradaysec - - fdsploit - - feroxbuster - - ffuf - - fhttp - - filebuster - - filefuzz - - fimap - - firewalk - - flyr - - fockcache - - frisbeelite - - ftester - - ftp-fuzz - - fuddly - - fusil - - fuzzball2 - - fuzzdb - - fuzzdiff - - fuzzowski - - fuzztalk - - gloom - - goofuzz - - grammarinator - - graphql-path-enum - - graphqlmap - - grr - - hexorbase - - hodor - - homepwn - - honggfuzz - - http-fuzz - - httpforge - - hwk - - ifuzz - - ikeprober - - inguma - - injectus - - isip - - jbrofuzz - - jok3r - - joomlavs - - jsql-injection - - kitty-framework - - krbrelayx - - leviathan - - lfi-autopwn - - lfi-fuzzploit - - lfi-scanner - - lfi-sploiter - - lfimap - - liffy - - littleblackbox - - log4j-bypass - - log4j-scan - - logmepwn - - lorsrf - - maligno - - malybuzz - - manul - - mdk3 - - mdk4 - - melkor - - metasploit - - mitm6 - - mongoaudit - - network-app-stress-tester - - netzob - - nikto - - nili - - nimbostratus - - notspikefile - - nsoq - - nullscan - - oat - - ohrwurm - - openvas-scanner - - oscanner - - owtf - - pappy-proxy - - parampampam - - peach - - peach-fuzz - - pentbox - - pmcma - - portmanteau - - powerfuzzer - - pret - - profuzz - - pulsar - - pureblood - - pyersinia - - pyjfuzz - - pytbull - - qark - - radamsa - - rapidscan - - ratproxy - - responder - - restler-fuzzer - - s3-fuzzer - - samesame - - sandsifter - - sb0x - - scout2 - - sfuzz - - shortfuzzy - - skipfish - - sloth-fuzzer - - smartphone-pentest-framework - - smbexec - - smod - - smtp-fuzz - - smtptx - - sn00p - - snmp-fuzzer - - soapui - - socketfuzz - - spaf - - spartan - - spiderpig-pdffuzzer - - spike-fuzzer - - sploitego - - sps - - sqlbrute - - sqlmap - - sqlninja - - sshfuzz - - ssrfmap - - stews - - sulley - - taof - - tcpcontrol-fuzzer - - tcpjunk - - termineter - - tftp-fuzz - - thefuzz - - tlsfuzzer - - trinity - - udp-hunter - - udsim - - umap - - unifuzzer - - uniofuzz - - uniscan - - upnp-pentest-toolkit - - uppwn - - vane - - vbscan - - viproy-voipkit - - vsaudit - - vulscan - - w13scan - - w3af - - wafninja - - wafpass - - wapiti - - webscarab - - webshag - - websploit - - webxploiter - - weirdaal - - wfuzz - - witchxtool - - wpscan - - wsfuzzer - - xspear - - xss-freak - - xsser - - xsss - - xssscan - - xsssniper - - yawast - - zaproxy - - zzuf diff --git a/usr/share/blackbox/yaml/netinstall-scanner.yaml b/usr/share/blackbox/yaml/netinstall-scanner.yaml deleted file mode 100644 index 6a38c96..0000000 --- a/usr/share/blackbox/yaml/netinstall-scanner.yaml +++ /dev/null @@ -1,664 +0,0 @@ -- name: "WebApp" - description: "WebApp" - critical: false - hidden: false - selected: false - expanded: true - packages: - - 0d1n - - 0trace - - a2sv - - adenum - - adminpagefinder - - admsnmp - - allthevhosts - - amass - - androidsniffer - - anti-xss - - anubis - - apache-users - - apachetomcatscanner - - apnbf - - appmon - - arjun - - arp-scan - - asp-audit - - assetfinder - - atear - - athena-ssl-scanner - - atscan - - atstaketools - - attk - - autorecon - - aws-extender-cli - - aws-iam-privesc - - awsbucketdump - - badkarma - - badministration - - barmie - - basedomainname - - bashscan - - bbscan - - belati - - billcipher - - bing-lfi-rfi - - bingoo - - birp - - blackbox-scanner - - bleah - - blindy - - blue-hydra - - bluebox-ng - - bluelog - - bluescan - - bluto - - botb - - braa - - brakeman - - bss - - btscanner - - burpsuite - - c5scan - - cameradar - - camscan - - canari - - cangibrina - - cansina - - cantoolz - - cariddi - - casefile - - cecster - - cent - - cero - - changeme - - chaosmap - - check-weak-dh-ssh - - checksec - - chipsec - - chiron - - cipherscan - - cisco-auditing-tool - - cisco-scanner - - cisco-torch - - ciscos - - clair - - clairvoyance - - climber - - cloudflare-enum - - cloudmare - - cloudsploit - - cloudunflare - - cms-few - - cmsfuzz - - cmsmap - - cmsscan - - cmsscanner - - comission - - complemento - - configpush - - conscan - - cookie-cadger - - corscanner - - corstest - - corsy - - cpfinder - - crackmapexec - - creepy - - crlfuzz - - ct-exposer - - cvechecker - - cybercrowl - - cyberscan - - d-tect - - dark-dork-searcher - - darkbing - - darkdump - - darkscrape - - datasploit - - davscan - - davtest - - dawnscanner - - dbusmap - - dcrawl - - deblaze - - delldrac - - dependency-check - - dhcpig - - dirb - - dirble - - dirbuster - - dirbuster-ng - - dirhunt - - dirscanner - - dirscraper - - dirsearch - - dirstalk - - dive - - dmitry - - dnmap - - dns2geoip - - dnsa - - dnsbf - - dnsbrute - - dnscan - - dnsenum - - dnsgoblin - - dnspredict - - dnsspider - - dnstwist - - dnswalk - - dockerscan - - dontgo403 - - dorkbot - - dorkme - - dpscan - - driftnet - - dripper - - droopescan - - drozer - - drupal-module-enum - - drupalscan - - drupwn - - dsfs - - dsjs - - dsss - - dsxs - - dvcs-ripper - - easyda - - eazy - - eigrp-tools - - enteletaor - - enum-shares - - enum4linux - - enum4linux-ng - - enumerate-iam - - enumiax - - eos - - eternal-scanner - - evine - - extended-ssrf-search - - faradaysec - - fernmelder - - feroxbuster - - fgscanner - - fhttp - - fi6s - - fierce - - find-dns - - firewalk - - flashscanner - - flunym0us - - forkingportscanner - - fortiscan - - fping - - fs-nyarl - - fscan - - fsnoop - - ftp-scanner - - ftp-spider - - ftpmap - - ftpscout - - gatecrasher - - gcpbucketbrute - - gethsploit - - gggooglescan - - ghost-phisher - - git-dump - - git-dumper - - gitdorker - - gitrob - - gittools - - gloom - - gobuster - - goofuzz - - goohak - - goop-dump - - gospider - - gpredict - - grabbb - - graphinder - - graphql-cop - - grepforrfi - - grype - - gtp-scan - - gwcheck - - h2buster - - h2t - - habu - - hackredis - - hakku - - hakrawler - - halberd - - hasere - - hbad - - heartleech - - hellraiser - - hexorbase - - hikpwn - - homepwn - - hookshot - - hoppy - - host-extract - - hostbox-ssh - - hsecscan - - htcap - - http-enum - - http2smugl - - httpforge - - httpgrep - - httprobe - - httpsscanner - - httpx - - hwk - - iaxscan - - icmpquery - - idswakeup - - iis-shortname-scanner - - ike-scan - - ikeprobe - - ilo4-toolbox - - infip - - inguma - - injectus - - inurlbr - - ipscan - - iptv - - ipv6toolkit - - ircsnapshot - - isme - - jaadas - - jaeles - - jira-scan - - jok3r - - joomlascan - - joomlavs - - juumla - - kadimus - - kalibrate-rtl - - katana-framework - - katana-pd - - kiterunner - - knock - - knxmap - - konan - - krbrelayx - - kube-hunter - - kubesploit - - kubestriker - - kubolt - - laf - - ldapdomaindump - - ldapenum - - leaklooker - - letmefuckit-scanner - - leviathan - - lfi-scanner - - lfisuite - - lightbulb - - linenum - - linikatz - - linux-smart-enumeration - - list-urls - - littleblackbox - - locasploit - - log4j-bypass - - log4j-scan - - logmepwn - - loki-scanner - - lorsrf - - lotophagi - - lte-cell-scanner - - lulzbuster - - lunar - - lynis - - magescan - - maligno - - maltego - - manspider - - mantra - - maryam - - massbleed - - masscan - - meg - - metasploit - - mingsweeper - - miranda-upnp - - mitm6 - - modscan - - mongoaudit - - mooscan - - morxtraversal - - mptcp-abuse - - mqtt-pwn - - msmailprobe - - mssqlscan - - multiscanner - - mwebfp - - naabu - - nbname - - nbtenum - - nbtool - - nbtscan - - netbios-share-scanner - - netexec - - netreconn - - netscan - - netscan2 - - nettacker - - netz - - nextnet - - nikto - - nili - - nmap - - nmbscan - - nosqlattack - - nosqli - - nray - - nsdtool - - nsec3map - - nsoq - - ntlm-challenger - - ntlm-scanner - - ntlmrecon - - nuclei - - nuclei-templates - - nullinux - - nullscan - - o-saft - - ocs - - okadminfinder - - onesixtyone - - onetwopunch - - onionscan - - onionsearch - - opendoor - - openscap - - openvas-scanner - - owasp-bywaf - - owtf - - pagodo - - paketto - - panhunt - - pappy-proxy - - parameth - - paranoic - - passhunt - - pbscan - - pcredz - - peass - - pentbox - - pentestly - - phonia - - php-malware-finder - - pinkerton - - plcscan - - pmap - - pnscan - - poison - - postenum - - pown - - ppfuzz - - ppmap - - ppscan - - prads - - praeda - - pret - - propecia - - prowler - - proxmark - - proxmark3 - - proxybroker2 - - proxycheck - - proxyp - - proxyscan - - ptf - - pureblood - - puredns - - pwncat - - pwndora - - pyersinia - - pyfiscan - - pyssltest - - pytbull - - pythem - - python-api-dnsdumpster - - python-shodan - - python2-api-dnsdumpster - - python2-ldapdomaindump - - python2-shodan - - qark - - quickrecon - - raccoon - - ranger-scanner - - rapidscan - - ratproxy - - rawr - - rbac-lookup - - rdp-cipher-checker - - rdp-sec-check - - reconscan - - recsech - - red-hawk - - redfang - - relay-scanner - - responder - - retire - - revipd - - rext - - ripdc - - rlogin-scanner - - routerhunter - - rpctools - - rpdscan - - rtlizer - - rtlsdr-scanner - - rustbuster - - rustscan - - s3scanner - - sambascan - - sandcastle - - sandmap - - sandy - - sb0x - - scamper - - scanless - - scanqli - - scanssh - - scap-security-guide - - scap-workbench - - scout2 - - scoutsuite - - scrape-dns - - scrapy - - sctpscan - - sdn-toolkit - - sdnpwn - - seat - - second-order - - secscan - - see-surf - - shareenum - - sharesniffer - - shocker - - shortfuzzy - - shuffledns - - silk - - simple-lan-scan - - sipscan - - sipshock - - sitadel - - skipfish - - slurp-scanner - - smap-scanner - - smartphone-pentest-framework - - smbcrunch - - smbexec - - smbmap - - smbspider - - smbsr - - smod - - smtp-test - - smtp-user-enum - - smtp-vrfy - - smtptx - - smuggler - - smuggler-py - - sn00p - - sn1per - - snallygaster - - snmpattack - - snmpenum - - snmpscan - - snoopbrute - - snscan - - snyk - - spade - - sparta - - spiga - - spipscan - - sploitego - - sprayhound - - sprayingtoolkit - - sqlivulscan - - ssdp-scanner - - ssh-audit - - ssh-user-enum - - sshprank - - sshscan - - ssl-hostname-resolver - - sslcaudit - - ssllabs-scan - - sslmap - - sslscan - - sslscan2 - - sslyze - - ssrfmap - - stacs - - stews - - sticky-keys-hunter - - stig-viewer - - storm-ring - - striker - - strutscan - - subbrute - - subdomainer - - subjack - - sublist3r - - subover - - subscraper - - superscan - - svn-extractor - - swarm - - synscan - - sysdig - - tachyon-scanner - - tactical-exploitation - - taipan - - takeover - - testssl.sh - - tfsec - - thc-ipv6 - - thc-smartbrute - - thcrut - - tiger - - tlsenum - - tlspretense - - tlssled - - tlsx - - topera - - torcrawl - - traxss - - trivy - - typo3scan - - ubiquiti-probing - - udork - - udp-hunter - - udsim - - umap - - unicornscan - - uniscan - - unix-privesc-check - - upnp-pentest-toolkit - - upnpscan - - uptux - - urldigger - - uw-loveimap - - uw-udpscan - - uw-zone - - v3n0m - - vais - - vane - - vanguard - - vault-scanner - - vbrute - - vbscan - - vcsmap - - vhostscan - - videosnarf - - viproy-voipkit - - visql - - vsaudit - - vscan - - vsvbp - - vulmap - - vulnerabilities-spider - - vulnx - - vuls - - vulscan - - w13scan - - w3af - - wafw00f - - waldo - - wapiti - - wascan - - wcvs - - webanalyze - - webborer - - webenum - - webhunter - - webpwn3r - - webrute - - webscarab - - webshag - - websploit - - webtech - - webxploiter - - weirdaal - - whatwaf - - whitewidow - - wifiscanmap - - wig - - winfo - - witchxtool - - wnmap - - wolpertinger - - wordpresscan - - wpintel - - wpscan - - wpseku - - wpsik - - wups - - x-scan - - x8 - - xcname - - xpire-crossdomain-scanner - - xsrfprobe - - xss-freak - - xsscon - - xsspy - - xsss - - xssscan - - xsstracer - - xsstrike - - xssya - - xwaf - - yaaf - - yasat - - yasuo - - yawast - - ycrawler - - yersinia - - zackattack - - zeus - - zmap - - paranoic diff --git a/usr/share/blackbox/yaml/netinstall-webapp.yaml b/usr/share/blackbox/yaml/netinstall-webapp.yaml deleted file mode 100644 index d22ec4f..0000000 --- a/usr/share/blackbox/yaml/netinstall-webapp.yaml +++ /dev/null @@ -1,444 +0,0 @@ -- name: "WebApp" - description: "WebApp" - critical: false - hidden: false - selected: false - expanded: true - packages: - - 0d1n - - abuse-ssl-bypass-waf - - adfind - - adminpagefinder - - albatar - - allthevhosts - - anti-xss - - apachetomcatscanner - - arachni - - archivebox - - arjun - - asp-audit - - astra - - atlas - - atscan - - aws-extender-cli - - backcookie - - badministration - - bbqsql - - bbscan - - belati - - bfac - - bing-lfi-rfi - - bitdump - - blindelephant - - blisqy - - brakeman - - brute-force - - brutemap - - brutexss - - bsqlbf - - bsqlinjector - - burpsuite - - c5scan - - cangibrina - - cansina - - cent - - chankro - - cintruder - - cjexploiter - - clairvoyance - - cloudget - - cms-explorer - - cms-few - - cmseek - - cmsfuzz - - cmsmap - - cmsscan - - cmsscanner - - comission - - commentor - - commix - - conscan - - corscanner - - corstest - - corsy - - cpfinder - - crabstick - - crackql - - crawlic - - crlf-injector - - crlfuzz - - csrftester - - cybercrowl - - d-tect - - dalfox - - darkbing - - darkd0rk3r - - darkdump - - darkjumper - - darkmysqli - - darkscrape - - davscan - - dawnscanner - - dcrawl - - detectem - - dff-scanner - - dirb - - dirble - - dirbuster - - dirbuster-ng - - directorytraversalscan - - dirhunt - - dirscanner - - dirscraper - - dirsearch - - dirstalk - - docem - - domi-owned - - dontgo403 - - doork - - dorknet - - dpscan - - droopescan - - drupal-module-enum - - drupalscan - - drupwn - - dsfs - - dsjs - - dsss - - dsstore-crawler - - dsxs - - dumb0 - - easyfuzzer - - eazy - - eos - - epicwebhoneypot - - evine - - extended-ssrf-search - - eyewitness - - facebot - - facebrute - - fbht - - fdsploit - - feroxbuster - - ffuf - - fhttp - - filebuster - - filegps - - fingerprinter - - flashscanner - - flask-session-cookie-manager2 - - flask-session-cookie-manager3 - - flask-unsign - - flunym0us - - fockcache - - fuxploider - - gau - - ghauri - - ghost-py - - git-dumper - - gitdump - - gittools - - gobuster - - golismero - - goop-dump - - gopherus - - gospider - - gowitness - - grabber - - graphinder - - graphql-cop - - graphql-path-enum - - graphqlmap - - graphw00f - - gwtenum - - h2buster - - h2csmuggler - - h2t - - hakku - - hakrawler - - halberd - - hetty - - hookshot - - host-extract - - htcap - - http2smugl - - httpforge - - httpgrep - - httppwnly - - httpx - - hyperfox - - identywaf - - imagejs - - injectus - - interactsh-client - - inurlbr - - ipsourcebypass - - isr-form - - jaeles - - jaidam - - jast - - jboss-autopwn - - jdeserialize - - jexboss - - jira-scan - - jok3r - - jomplug - - jooforce - - joomlascan - - joomlavs - - joomscan - - jsearch - - jshell - - jsonbee - - jsparser - - jsql-injection - - jstillery - - juumla - - jwt-hack - - kadimus - - katana-pd - - keye - - kiterunner - - kolkata - - konan - - kubolt - - laf - - laudanum - - lbmap - - letmefuckit-scanner - - leviathan - - lfi-exploiter - - lfi-fuzzploit - - lfi-image-helper - - lfi-scanner - - lfi-sploiter - - lfifreak - - lfimap - - lfisuite - - liffy - - lightbulb - - linkfinder - - list-urls - - log4j-bypass - - log4j-scan - - lorsrf - - lulzbuster - - magescan - - mando.me - - mantra - - maryam - - meg - - metoscan - - monsoon - - mooscan - - morxtraversal - - mosquito - - multiinjector - - mwebfp - - nikto - - nosqli - - nosqli-user-pass-enum - - nosqlmap - - novahot - - nuclei - - okadminfinder - - onionsearch - - opendoor - - otori - - owasp-bywaf - - owtf - - pappy-proxy - - parameth - - parampampam - - paranoic - - paros - - payloadmask - - pblind - - peepingtom - - photon - - php-findsock-shell - - php-malware-finder - - php-vulnerability-hunter - - phpggc - - phpsploit - - pinkerton - - pixload - - plecost - - plown - - poly - - poracle - - pown - - ppfuzz - - ppmap - - proxenet - - pureblood - - pwndrop - - pyfiscan - - pythem - - python-arsenic - - python-jsbeautifier - - python-witnessme - - python2-jsbeautifier - - rabid - - rapidscan - - ratproxy - - rawr - - recsech - - red-hawk - - remot3d - - restler-fuzzer - - richsploit - - riwifshell - - ruler - - rustbuster - - rww-attack - - sawef - - scanqli - - scrapy - - scrying - - second-order - - secretfinder - - secscan - - see-surf - - serializationdumper - - shellinabox - - shortfuzzy - - shuffledns - - sitadel - - sitediff - - sjet - - skipfish - - smplshllctrlr - - smuggler - - smuggler-py - - snallygaster - - snare - - snuck - - sourcemapper - - spaf - - sparty - - spiga - - spike-proxy - - spipscan - - sprayingtoolkit - - sqid - - sqlbrute - - sqldict - - sqlivulscan - - sqlmap - - sqlninja - - sqlping - - sqlpowerinjector - - sqlsus - - ssrf-sheriff - - ssrfmap - - stews - - striker - - subjs - - swarm - - swftools - - taipan - - themole - - tidos-framework - - tinfoleak - - tinfoleak2 - - tomcatwardeployer - - torcrawl - - tplmap - - typo3scan - - uatester - - ufonet - - uncaptcha2 - - uniscan - - uppwn - - urlcrazy - - urldigger - - urlextractor - - v3n0m - - vane - - vanguard - - vbscan - - vega - - visql - - vsvbp - - vulnerabilities-spider - - vulnx - - w13scan - - w3af - - wafninja - - wafp - - wafpass - - wafw00f - - wapiti - - wascan - - waybackpack - - wcvs - - web-soul - - webacoo - - webanalyze - - webborer - - webenum - - webexploitationtool - - webhandler - - webhunter - - webkiller - - webpwn3r - - webrute - - webscarab - - webshag - - webshells - - webslayer - - webspa - - webtech - - webxploiter - - weevely - - weirdaal - - wfuzz - - whatwaf - - whatweb - - whichcdn - - whitewidow - - wig - - witchxtool - - wmat - - wordbrutepress - - wordpress-exploit-framework - - wordpresscan - - wpbf - - wpbrute-rpc - - wpbullet - - wpforce - - wpintel - - wpscan - - wpseku - - ws-attacker - - wsfuzzer - - wssip - - wuzz - - x8 - - xmlrpc-bruteforcer - - xspear - - xsrfprobe - - xss-freak - - xsscon - - xsscrapy - - xsser - - xssless - - xsspy - - xsss - - xssscan - - xsssniper - - xsstrike - - xssya - - xwaf - - xxeinjector - - xxexploiter - - xxxpwn - - xxxpwn-smart - - yaaf - - yasuo - - yawast - - ycrawler - - yinjector - - ysoserial - - zaproxy