From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Gideon Laurie <gideon.a.e.laurie@gmail.com>
Date: Aug, 08 2026 06:39:04 +1000
Subject: [PATCH] <short summary of the patch>

TODO: Put a short summary on the line above and replace this paragraph
with a longer explanation of this change. Complete the meta-information
with other relevant fields (see below for details). To make it easier, the
information below has been extracted from the changelog. Adjust it or drop
it.

---
The information above should follow the Patch Tagging Guidelines, please
checkout https://dep.debian.net/deps/dep3/ to learn about the format. Here
are templates for supplementary fields that you might want to add:

Origin: (upstream|backport|vendor|other), (<patch-url>|commit:<commit-id>)
Bug: <upstream-bugtracker-url>
Bug-<Vendor>: <vendor-bugtracker-url>
Forwarded: (no|not-needed|<patch-forwarded-url>)
Applied-Upstream: <version>, (<commit-url>|commit:<commid-id>)
Reviewed-By: <name and email of someone who approved/reviewed the patch>

--- pkgfinder-1.0.orig/pkgfinder
+++ pkgfinder-1.0/pkgfinder
@@ -1,41 +1,32 @@
 #!/usr/bin/env python3
 import tkinter as tk
-from tkinter import ttk, messagebox
+from tkinter import ttk, messagebox, filedialog
 import subprocess
 import threading
+import os
 
-class PackageSearchGUI:
+class DebInstallerGUI:
     def __init__(self, root):
         self.root = root
-        self.root.title("Linux Package Manager")
-        self.root.geometry("750x650")
+        self.root.title("Local .deb Package Installer")
+        self.root.geometry("750x550")
 
-        # Search Bar Frame
-        search_frame = tk.Frame(root)
-        search_frame.pack(pady=10, fill=tk.X, padx=10)
+        # File Selection Frame
+        file_frame = tk.Frame(root)
+        file_frame.pack(pady=15, fill=tk.X, padx=10)
 
-        self.search_label = tk.Label(search_frame, text="Package Name:", font=("Arial", 10, "bold"))
-        self.search_label.pack(side=tk.LEFT, padx=5)
+        self.file_label = tk.Label(file_frame, text="Selected File:", font=("Arial", 10, "bold"))
+        self.file_label.pack(side=tk.LEFT, padx=5)
 
-        self.search_var = tk.StringVar()
-        self.entry = tk.Entry(search_frame, textvariable=self.search_var, font=("Arial", 11))
+        self.file_var = tk.StringVar()
+        self.entry = tk.Entry(file_frame, textvariable=self.file_var, font=("Arial", 11), state="readonly")
         self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
-        self.entry.bind("<Return>", lambda e: self.search_packages())
 
-        self.btn_search = tk.Button(search_frame, text="Search", command=self.search_packages, bg="#4CAF50", fg="white", width=10)
-        self.btn_search.pack(side=tk.LEFT, padx=5)
-
-        # Results Table
-        columns = ("Package", "Description")
-        self.tree = ttk.Treeview(root, columns=columns, show="headings")
-        self.tree.heading("Package", text="Package Name")
-        self.tree.heading("Description", text="Brief Description")
-        self.tree.column("Package", width=200)
-        self.tree.column("Description", width=500)
-        self.tree.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
+        self.btn_browse = tk.Button(file_frame, text="Browse .deb", command=self.browse_file, bg="#4CAF50", fg="white", width=12)
+        self.btn_browse.pack(side=tk.LEFT, padx=5)
 
         # Installation Action Button
-        self.btn_install = tk.Button(root, text="Install Selected Package", command=self.start_installation, bg="#E91E63", fg="white", font=("Arial", 10, "bold"))
+        self.btn_install = tk.Button(root, text="Install Package & Dependencies", command=self.start_installation, bg="#E91E63", fg="white", font=("Arial", 11, "bold"), state=tk.DISABLED)
         self.btn_install.pack(pady=10)
 
         # Live Console Output Window
@@ -51,7 +42,7 @@ class PackageSearchGUI:
 
         # Progress Bar
         self.progress = ttk.Progressbar(root, orient="horizontal", mode="indeterminate")
-        self.progress.pack(padx=10, pady=10, fill=tk.X)
+        self.progress.pack(padx=10, pady=15, fill=tk.X)
 
     def log_to_console(self, text):
         """Helper to cleanly insert text into the dark console window."""
@@ -60,53 +51,59 @@ class PackageSearchGUI:
         self.console_text.see(tk.END)
         self.console_text.config(state=tk.DISABLED)
 
-    def search_packages(self):
-        query = self.search_var.get().strip()
-        if not query: return
-
-        for item in self.tree.get_children():
-            self.tree.delete(item)
-
-        try:
-            output = subprocess.check_output(["apt-cache", "search", query], universal_newlines=True)
-            for line in output.strip().split('\n'):
-                if " - " in line:
-                    pkg, desc = line.split(" - ", 1)
-                    self.tree.insert("", tk.END, values=(pkg.strip(), desc.strip()))
-        except Exception as e:
-            messagebox.showerror("Error", f"Could not search: {e}")
+    def browse_file(self):
+        """Open a file dialog to select a local .deb package."""
+        file_path = filedialog.askopenfilename(
+            title="Select Debian Package",
+            filetypes=[("Debian Packages", "*.deb"), ("All Files", "*.*")]
+        )
+        if file_path:
+            self.file_var.set(file_path)
+            self.btn_install.config(state=tk.NORMAL)
 
     def start_installation(self):
-        selected = self.tree.selection()
-        if not selected:
-            messagebox.showwarning("Selection", "Please select a package first.")
+        deb_path = self.file_var.get()
+        if not deb_path or not os.path.exists(deb_path):
+            messagebox.showerror("Error", "Invalid file path selected.")
             return
+
+        # Explicitly ask the user if they wish to download and install missing dependencies
+        confirm = messagebox.askyesnocancel(
+            "Dependency Management", 
+            f"Do you want to check for and automatically download any missing dependencies required by '{os.path.basename(deb_path)}'?"
+        )
         
-        pkg_name = self.tree.item(selected)['values'][0]
-        
-        if not messagebox.askyesno("Confirm", f"Do you want to install '{pkg_name}'?"):
+        if confirm is None: # User cancelled the action entirely
             return
-
+            
         # Disable buttons and start progress bar animation
         self.btn_install.config(state=tk.DISABLED)
-        self.btn_search.config(state=tk.DISABLED)
+        self.btn_browse.config(state=tk.DISABLED)
         self.progress.start(10)
         
-        # Clear log and give immediate visual feedback
+        # Clear log
         self.console_text.config(state=tk.NORMAL)
         self.console_text.delete("1.0", tk.END)
         self.console_text.config(state=tk.DISABLED)
-        self.log_to_console(f"Starting installation process for: {pkg_name}\nWaiting for authorization...\n")
+        
+        self.log_to_console(f"Target file: {deb_path}\n")
+        self.log_to_console("Starting installation system...\nWaiting for root authorization...\n")
 
         # Run installation in a separate thread so the GUI does not freeze
-        thread = threading.Thread(target=self.run_install_thread, args=(pkg_name,))
+        thread = threading.Thread(target=self.run_install_thread, args=(deb_path, confirm))
         thread.start()
 
-    def run_install_thread(self, pkg_name):
+    def run_install_thread(self, deb_path, handle_dependencies):
         try:
-            # Command uses pkexec for the fancy popup password box
-            # We redirect standard output and error to capture logs in real-time
-            cmd = ["pkexec", "apt-get", "install", "-y", pkg_name]
+            if handle_dependencies:
+                # OPTION A: gdebi is the cleanest tool for local installs because it resolves, prompts, and downloads dependencies safely.
+                # If gdebi-gtk is installed, it handles everything natively in a UI.
+                # Here we use apt-get with local path resolving, passing -y to accept dependency downloads.
+                cmd = ["pkexec", "apt-get", "install", "-y", deb_path]
+            else:
+                # OPTION B: dpkg installs ONLY the package. It breaks if dependencies are missing, respecting the user's "No" choice.
+                cmd = ["pkexec", "dpkg", "-i", deb_path]
+
             process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
 
             # Read terminal output line by line as it happens
@@ -121,23 +118,25 @@ class PackageSearchGUI:
 
             # Check if the process exited cleanly
             if process.returncode == 0:
-                self.root.after(0, lambda: messagebox.showinfo("Success", f"'{pkg_name}' installed successfully!"))
+                self.root.after(0, lambda: messagebox.showinfo("Success", "Package processed successfully!"))
             else:
-                self.root.after(0, lambda: messagebox.showerror("Failed", "Installation failed or authentication was cancelled."))
+                self.root.after(0, lambda: messagebox.showerror("Failed", "Installation failed, dependencies unmet, or authentication was cancelled."))
 
         except Exception as e:
             self.root.after(0, lambda: messagebox.showerror("Error", f"An unexpected error occurred: {e}"))
-        
+            
         finally:
             # Turn off progress bar and restore buttons when finished
             self.root.after(0, self.cleanup_ui)
 
     def cleanup_ui(self):
         self.progress.stop()
-        self.btn_install.config(state=tk.NORMAL)
-        self.btn_search.config(state=tk.NORMAL)
+        self.btn_browse.config(state=tk.NORMAL)
+        if self.file_var.get():
+            self.btn_install.config(state=tk.NORMAL)
 
 if __name__ == "__main__":
     root = tk.Tk()
-    app = PackageSearchGUI(root)
+    app = DebInstallerGUI(root)
     root.mainloop()
+
