import tkinter as tk from tkinter import ttk, messagebox, font import psutil import threading import locale import webbrowser # --- Internationalization (i18n) --- STRINGS = { "en": { "title": "Open Port Killer", "pid": "PID", "name": "Process Name", "port": "Port", "ip": "IP Address", "refresh": "Refresh", "kill": "Kill Selected Process", "loading": "Loading process list...", "searching": "Searching for processes...", "found": "{count} processes with open ports found.", "error_fetch": "Error fetching processes: {error}", "no_selection_title": "No Selection", "no_selection_msg": "Please select a process from the list.", "confirm_kill_title": "Confirm Kill", "confirm_kill_msg": "Do you really want to kill the selected processes?", "error_title": "Error", "no_such_process": "Process with PID {pid} not found anymore.", "access_denied": "Permission denied to kill process with PID {pid}. Try running the script as an administrator/root.", "unexpected_error": "An unexpected error occurred: {error}", "help": "Help", "about": "About", "language": "Language", "about_title": "About Open Port Killer", "about_copyright": "Copyright © 2025 by Robin Oliver Lucas", "about_homepage": "Homepage:", "about_email": "Email:", }, "de": { "title": "Open Port Killer", "pid": "PID", "name": "Programmname", "port": "Port", "ip": "IP-Adresse", "refresh": "Aktualisieren", "kill": "Ausgewählten Prozess beenden", "loading": "Lade Prozessliste...", "searching": "Suche nach Prozessen...", "found": "{count} Prozesse mit offenen Ports gefunden.", "error_fetch": "Fehler beim Abrufen der Prozesse: {error}", "no_selection_title": "Keine Auswahl", "no_selection_msg": "Bitte wählen Sie einen Prozess aus der Liste aus.", "confirm_kill_title": "Bestätigen", "confirm_kill_msg": "Möchten Sie die ausgewählten Prozesse wirklich beenden?", "error_title": "Fehler", "no_such_process": "Prozess mit PID {pid} nicht mehr gefunden.", "access_denied": "Keine Berechtigung, den Prozess mit PID {pid} zu beenden. Versuchen Sie, das Skript als Administrator/root auszuführen.", "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten: {error}", "help": "Hilfe", "about": "Über", "language": "Sprache", "about_title": "Über Open Port Killer", "about_copyright": "Copyright © 2025 by Robin Oliver Lucas", "about_homepage": "Homepage:", "about_email": "E-Mail:", } } class PortKillerApp: def __init__(self, root): self.root = root self.process_data = [] self.sort_column = "port" self.sort_reverse = False self.strings = STRINGS["en"] # Default to English self.root.geometry("650x450") # --- Style & Fonts --- self.style = ttk.Style() self.style.theme_use("clam") self.link_font = font.Font(family="Helvetica", size=10, underline=True) # --- Menu --- self.menu = tk.Menu(self.root) self.root.config(menu=self.menu) # Language Menu self.lang_menu = tk.Menu(self.menu, tearoff=0) self.menu.add_cascade(label=self.strings["language"], menu=self.lang_menu) self.lang_menu.add_command(label="English", command=lambda: self.switch_language("en")) self.lang_menu.add_command(label="Deutsch", command=lambda: self.switch_language("de")) # Help Menu self.help_menu = tk.Menu(self.menu, tearoff=0) self.menu.add_cascade(label=self.strings["help"], menu=self.help_menu) self.help_menu.add_command(label=self.strings["about"], command=self.show_about_window) # --- Widgets --- self.tree_frame = ttk.Frame(self.root) self.tree_frame.pack(pady=10, padx=10, fill="both", expand=True) self.columns = ("pid", "name", "port", "ip") self.tree = ttk.Treeview(self.tree_frame, columns=self.columns, show="headings") self.tree.column("pid", width=80, anchor="e") self.tree.column("name", width=220) self.tree.column("port", width=80, anchor="e") self.tree.column("ip", width=150) self.tree.pack(side="left", fill="both", expand=True) self.scrollbar = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview) self.tree.configure(yscroll=self.scrollbar.set) self.scrollbar.pack(side="right", fill="y") self.button_frame = ttk.Frame(self.root) self.button_frame.pack(pady=10) self.refresh_button = ttk.Button(self.button_frame, command=self.start_refresh_thread) self.refresh_button.pack(side="left", padx=5) self.kill_button = ttk.Button(self.button_frame, command=self.kill_selected_process) self.kill_button.pack(side="left", padx=5) self.status_label = ttk.Label(self.root) self.status_label.pack(pady=5) self.retranslate_ui() self.start_refresh_thread() def switch_language(self, lang_code): self.strings = STRINGS.get(lang_code, STRINGS["en"]) self.retranslate_ui() def retranslate_ui(self): self.root.title(self.strings["title"]) # --- Recreate Menu on language change --- self.menu.delete(0, "end") # Clear existing menu # Language Menu self.lang_menu = tk.Menu(self.menu, tearoff=0) self.menu.add_cascade(label=self.strings["language"], menu=self.lang_menu) self.lang_menu.add_command(label="English", command=lambda: self.switch_language("en")) self.lang_menu.add_command(label="Deutsch", command=lambda: self.switch_language("de")) # Help Menu self.help_menu = tk.Menu(self.menu, tearoff=0) self.menu.add_cascade(label=self.strings["help"], menu=self.help_menu) self.help_menu.add_command(label=self.strings["about"], command=self.show_about_window) # --- Update other widgets --- for col in self.columns: self.tree.heading(col, text=self.strings[col], command=lambda _col=col: self.sort_by_column(_col)) self.refresh_button.config(text=self.strings["refresh"]) self.kill_button.config(text=self.strings["kill"]) self.status_label.config(text=self.strings["loading"]) self.update_ui() def show_about_window(self): about_win = tk.Toplevel(self.root) about_win.title(self.strings["about_title"]) about_win.geometry("350x150") about_win.resizable(False, False) about_win.transient(self.root) ttk.Label(about_win, text=self.strings["about_copyright"], font=("Helvetica", 10, "bold")).pack(pady=(15, 5)) hp_frame = ttk.Frame(about_win) hp_frame.pack() ttk.Label(hp_frame, text=self.strings["about_homepage"]).pack(side="left") hp_link = tk.Label(hp_frame, text="https://rl-dev.de", fg="blue", cursor="hand2", font=self.link_font) hp_link.pack(side="left") hp_link.bind("", lambda e: webbrowser.open_new_tab("https://rl-dev.de")) email_frame = ttk.Frame(about_win) email_frame.pack() ttk.Label(email_frame, text=self.strings["about_email"]).pack(side="left") email_link = tk.Label(email_frame, text="admin@rl-dev.de", fg="blue", cursor="hand2", font=self.link_font) email_link.pack(side="left") email_link.bind("", lambda e: webbrowser.open_new_tab("mailto:admin@rl-dev.de")) ttk.Button(about_win, text="OK", command=about_win.destroy).pack(pady=15) def sort_by_column(self, col): if self.sort_column == col: self.sort_reverse = not self.sort_reverse else: self.sort_column = col self.sort_reverse = False self.update_ui() def start_refresh_thread(self): self.refresh_button.config(state="disabled") self.kill_button.config(state="disabled") self.status_label.config(text=self.strings["searching"]) thread = threading.Thread(target=self.refresh_processes) thread.daemon = True thread.start() def refresh_processes(self): processes = [] try: connections = psutil.net_connections(kind='inet') for conn in connections: if conn.status == 'LISTEN' and conn.pid is not None: try: proc = psutil.Process(conn.pid) processes.append({"pid": conn.pid, "name": proc.name(), "port": conn.laddr.port, "ip": conn.laddr.ip}) except (psutil.NoSuchProcess, psutil.AccessDenied): continue self.process_data = processes except Exception as e: self.process_data = [] self.root.after(0, lambda: messagebox.showerror(self.strings["error_title"], self.strings["error_fetch"].format(error=e))) self.root.after(0, self.update_ui) def update_ui(self): for i in self.tree.get_children(): self.tree.delete(i) if self.process_data: key_func = lambda p: p.get(self.sort_column, 0) if self.sort_column in ["pid", "port"] else p.get(self.sort_column, "").lower() sorted_data = sorted(self.process_data, key=key_func, reverse=self.sort_reverse) for proc in sorted_data: self.tree.insert("", "end", values=(proc["pid"], proc["name"], proc["port"], proc["ip"])) self.refresh_button.config(state="normal") self.kill_button.config(state="normal") self.status_label.config(text=self.strings["found"].format(count=len(self.process_data))) def kill_selected_process(self): selected_items = self.tree.selection() if not selected_items: messagebox.showwarning(self.strings["no_selection_title"], self.strings["no_selection_msg"]) return if not messagebox.askyesno(self.strings["confirm_kill_title"], self.strings["confirm_kill_msg"]): return for selected_item in selected_items: item = self.tree.item(selected_item) pid = item["values"][0] try: psutil.Process(pid).kill() except psutil.NoSuchProcess: messagebox.showerror(self.strings["error_title"], self.strings["no_such_process"].format(pid=pid)) except psutil.AccessDenied: messagebox.showerror(self.strings["error_title"], self.strings["access_denied"].format(pid=pid)) except Exception as e: messagebox.showerror(self.strings["error_title"], self.strings["unexpected_error"].format(error=e)) self.start_refresh_thread() if __name__ == "__main__": root = tk.Tk() app = PortKillerApp(root) root.mainloop()