This commit is contained in:
Robin
2025-10-04 20:32:22 +02:00
commit 3597e6cc0e
7 changed files with 380 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
#Ignore vscode AI rules
.github\instructions\codacy.instructions.md

22
LICENSE.md Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2025 Robin Oliver Lucas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
# Open Port Killer
A cross-platform GUI utility to find and kill processes with open network ports. Built with Python, tkinter, and psutil.
## Features
- **Cross-Platform:** Works on Windows, Linux, and macOS.
- **Process Listing:** Scans and lists all local processes with open TCP ports.
- **Kill Processes:** Easily terminate one or more selected processes from the GUI.
- **Sortable List:** Click any column header to sort the process list.
- **Multi-Language:** Switch between English and German on-the-fly.
## Installation & Usage
1. **Prerequisites:**
- Python 3.6+ must be installed.
2. **Clone the repository (or download the source):
```sh
git clone <repository_url>
cd OpenPortKiller
```
3. **Install dependencies:**
```sh
pip install -r requirements.txt
```
4. **Run the application:**
```sh
python main.py
```
## Building from Source
You can create a standalone executable for your operating system using the provided build scripts. This requires `pyinstaller`, which is included in `requirements.txt`.
The executable will be placed in the `dist` directory.
### Windows
Run the batch file:
```cmd
.\build.bat
```
### Linux / macOS
First, make the script executable, then run it:
```sh
chmod +x build.sh
./build.sh
```
## License
This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.

17
build.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
echo Building executable for Windows...
REM Check if pyinstaller is installed
python -c "import pyinstaller" 2>nul
if %errorlevel% neq 0 (
echo PyInstaller not found. Please install it first using:
echo pip install -r requirements.txt
exit /b 1
)
pyinstaller --name OpenPortKiller --onefile --windowed --icon=NONE main.py
echo.
echo Build complete. Executable is located in the 'dist' folder.
pause

16
build.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
echo "Building executable for Linux/macOS..."
# Check if pyinstaller is installed
if ! python -c "import pyinstaller" &> /dev/null; then
echo "PyInstaller not found. Please install it first using:"
echo "pip install -r requirements.txt"
exit 1
fi
pyinstaller --name OpenPortKiller --onefile --windowed --icon=NONE main.py
echo ""
echo "Build complete. Executable is located in the 'dist' folder."

261
main.py Normal file
View File

@@ -0,0 +1,261 @@
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("<Button-1>", 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("<Button-1>", 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()

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
psutil
pyinstaller