Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- Najeeb ULTRA PRO MAX V3.7 - FINAL COMPLETE EDITION
- ✅ Python 3.7.9+ | Windows 7/10/11 32/64-bit | Pure StdLib | Zero Dependencies
- ✅ PERFECT: Line Numbers + Code Scroll Together 100%
- ✅ 175+ Extensions: Code + Audio + Video + Images + EXE
- ✅ Features: Dark/Light Theme | Auto-Detect Interpreters | Splash | Export ZIP
- """
- import os
- import sys
- import json
- import shutil
- import tempfile
- import threading
- import subprocess
- import tarfile
- import zipfile
- import queue
- import re
- from pathlib import Path
- from datetime import datetime
- from tkinter import *
- from tkinter import ttk, filedialog, messagebox, simpledialog
- from tkinter import TclError
- # -------------------------------SPLASH-----------------------
- def show_splash(image_path):
- """Display splash screen for 3 seconds"""
- try:
- from PIL import Image, ImageTk
- splash = Tk()
- splash.overrideredirect(True)
- if os.path.exists(image_path):
- image = Image.open(image_path)
- img = ImageTk.PhotoImage(image)
- img_width, img_height = image.size
- screen_width = splash.winfo_screenwidth()
- screen_height = splash.winfo_screenheight()
- x = (screen_width - img_width) // 2
- y = (screen_height - img_height) // 2
- splash.geometry(f"{img_width}x{img_height}+{x}+{y}")
- splash.config(bg="white")
- splash.attributes("-transparentcolor", "white")
- label = Label(splash, image=img, bg="white")
- label.image = img
- label.pack()
- splash.after(3000, splash.destroy)
- splash.mainloop()
- else:
- splash.destroy()
- except Exception:
- pass
- # Get paths
- base_path = os.path.dirname(os.path.abspath(__file__))
- splash_path = os.path.join(base_path, "THEME", "splash-1.png")
- if os.path.exists(splash_path):
- show_splash(splash_path)
- # ---------------- CONFIG ----------------
- APP_NAME = "Najeeb ULTRA PRO MAX V3.7"
- TEMP_DIR = os.path.join(tempfile.gettempdir(), "UltraProMax_V37")
- SETTINGS_FILE = Path.home() / ".ultrapro_settings_v37.json"
- APP_ICON = "SOFT.ico" if os.name == "nt" else "icon.xbm"
- RECENT_MAX = 10
- HIGHLIGHT_DELAY = 200
- CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
- # ✅ EXT_LANG: Complete extension support (175+ Extensions)
- EXT_LANG = {
- # 🐍 PYTHON
- ".py": "Python", ".pyw": "Python", ".pyi": "Python",
- # 🌐 WEB
- ".js": "JavaScript", ".jsx": "JavaScript", ".ts": "TypeScript", ".tsx": "TypeScript",
- ".html": "HTML", ".htm": "HTML", ".css": "CSS", ".scss": "CSS", ".sass": "CSS", ".less": "CSS",
- ".vue": "Vue", ".svelte": "Svelte", ".hta": "HTML",
- # 📄 DATA/CONFIG
- ".json": "JSON", ".json5": "JSON", ".xml": "XML", ".yaml": "YAML", ".yml": "YAML",
- ".toml": "TOML", ".ini": "Config", ".cfg": "Config", ".conf": "Config", ".env": "Env",
- # 📝 TEXT/DOCS
- ".md": "Markdown", ".markdown": "Markdown", ".rst": "reST", ".txt": "Text", ".ls": "Log",
- # 🖥️ SHELL/WINDOWS
- ".sh": "Shell", ".bash": "Shell", ".zsh": "Shell", ".fish": "Shell",
- ".bat": "Batch", ".cmd": "Batch", ".ps1": "PowerShell", ".psm1": "PowerShell",
- # 💻 COMPILED LANGUAGES
- ".c": "C", ".h": "C", ".cpp": "C++", ".hpp": "C++", ".cc": "C++", ".cxx": "C++",
- ".java": "Java", ".kt": "Kotlin", ".kts": "Kotlin", ".scala": "Scala",
- ".cs": "C#", ".vb": "VBScript", ".fs": "F#",
- # 🎯 SCRIPTING
- ".rb": "Ruby", ".rake": "Ruby", ".php": "PHP", ".phtml": "PHP",
- ".sql": "SQL", ".pls": "SQL", ".r": "R", ".R": "R",
- ".rs": "Rust", ".go": "Go", ".dart": "Dart", ".swift": "Swift",
- ".lua": "Lua", ".pl": "Perl", ".pm": "Perl", ".tcl": "TCL",
- ".graphql": "GraphQL", ".gql": "GraphQL",
- # 📊 DATA FILES
- ".log": "Log", ".csv": "CSV", ".tsv": "CSV", ".diff": "Diff", ".patch": "Diff",
- # 🤖 AUTOMATION
- ".ahk": "AutoHotKey", ".au3": "AutoIt",
- # 📦 PLAYLIST
- ".m3u": "M3U", ".m3u8": "M3U",
- # 🎵 AUDIO FILES (25 extensions)
- ".mp3": "Audio", ".wav": "Audio", ".flac": "Audio", ".aac": "Audio",
- ".ogg": "Audio", ".wma": "Audio", ".m4a": "Audio", ".alac": "Audio",
- ".opus": "Audio", ".aiff": "Audio", ".aif": "Audio", ".ape": "Audio",
- ".ac3": "Audio", ".amr": "Audio", ".au": "Audio", ".mid": "Audio",
- ".midi": "Audio", ".mka": "Audio", ".mp2": "Audio", ".ra": "Audio",
- ".ram": "Audio", ".snd": "Audio", ".wv": "Audio", ".dsd": "Audio",
- # 🎬 VIDEO FILES (40 extensions)
- ".mp4": "Video", ".avi": "Video", ".mkv": "Video", ".mov": "Video",
- ".wmv": "Video", ".flv": "Video", ".webm": "Video", ".mpeg": "Video",
- ".mpg": "Video", ".m4v": "Video", ".3gp": "Video", ".3g2": "Video",
- ".asf": "Video", ".f4v": "Video", ".f4p": "Video", ".f4a": "Video",
- ".f4b": "Video", ".m2ts": "Video", ".m2v": "Video", ".m4p": "Video",
- ".mpe": "Video", ".mpv": "Video", ".mts": "Video", ".mxf": "Video",
- ".nsv": "Video", ".ogv": "Video", ".qt": "Video", ".rm": "Video",
- ".rmvb": "Video", ".roq": "Video", ".svi": "Video", ".vob": "Video",
- ".yuv": "Video", ".divx": "Video", ".xvid": "Video", ".avchd": "Video",
- ".gif": "Video", ".gifv": "Video", ".h264": "Video", ".hevc": "Video",
- # 🖼️ IMAGE FILES (35 extensions)
- ".jpg": "Image", ".jpeg": "Image", ".png": "Image", ".gif": "Image",
- ".bmp": "Image", ".svg": "Image", ".webp": "Image", ".ico": "Image",
- ".tiff": "Image", ".tif": "Image", ".psd": "Image", ".raw": "Image",
- ".cr2": "Image", ".nef": "Image", ".arw": "Image", ".dng": "Image",
- ".heic": "Image", ".heif": "Image", ".avif": "Image", ".jxr": "Image",
- ".pgm": "Image", ".ppm": "Image", ".pbm": "Image", ".pnm": "Image",
- ".hdr": "Image", ".exr": "Image", ".j2k": "Image", ".jp2": "Image",
- ".jpf": "Image", ".jpm": "Image", ".mj2": "Image", ".sgi": "Image",
- ".tga": "Image", ".icns": "Image", ".eps": "Image", ".ai": "Image",
- # 🚀 EXECUTABLE FILES (5 extensions)
- ".exe": "Executable", ".msi": "Installer", ".msu": "Update",
- ".com": "Command", ".scr": "ScreenSaver",
- }
- # ✅ Helper: Find interpreter with auto-detection
- def find_interpreter(names, common_paths):
- """Find interpreter by checking PATH and common install locations"""
- for name in names:
- try:
- import shutil
- if shutil.which(name):
- return name
- except Exception:
- pass
- if os.name == "nt":
- for path in common_paths:
- path = os.path.expandvars(path)
- if os.path.isfile(path):
- return path
- if not path.lower().endswith(".exe") and os.path.isfile(path + ".exe"):
- return path + ".exe"
- return names[0] if names else None
- # ✅ Build default interpreters dict with auto-detection
- def get_default_interpreters():
- """Return default interpreters with auto-detected paths where possible"""
- interpreters = {
- "python": "python", "pythonw": "pythonw",
- "node": "node", "ts-node": "ts-node",
- "bash": "bash", "zsh": "zsh", "fish": "fish",
- "ruby": "ruby", "php": "php", "perl": "perl",
- "lua": "lua", "rscript": "Rscript", "tclsh": "tclsh",
- "cargo": "cargo", "go": "go", "dart": "dart", "swift": "swift",
- "java": "java", "kotlin": "kotlin", "scala": "scala",
- "dotnet": "dotnet", "sqlite3": "sqlite3", "sqlplus": "sqlplus",
- }
- if os.name == "nt":
- # AutoHotKey
- ahk_paths = [
- r"C:\Program Files\AutoHotkey\AutoHotkey.exe",
- r"C:\Program Files (x86)\AutoHotkey\AutoHotkey.exe",
- r"%ProgramFiles%\AutoHotkey\AutoHotkey.exe",
- r"%ProgramFiles(x86)%\AutoHotkey\AutoHotkey.exe",
- ]
- interpreters["autohotkey"] = find_interpreter(["autohotkey", "AutoHotkey.exe"], ahk_paths)
- # AutoIt3
- au3_paths = [
- r"C:\Program Files\AutoIt3\AutoIt3.exe",
- r"C:\Program Files (x86)\AutoIt3\AutoIt3.exe",
- r"%ProgramFiles%\AutoIt3\AutoIt3.exe",
- r"%ProgramFiles(x86)%\AutoIt3\AutoIt3.exe",
- ]
- interpreters["autoit3"] = find_interpreter(["autoit3", "AutoIt3.exe"], au3_paths)
- interpreters["powershell"] = "powershell"
- interpreters["cmd"] = "cmd"
- interpreters["mshta"] = "mshta"
- return interpreters
- # ✅ RUNNERS: Complete language executors - FIXED VERSION
- def build_runners():
- """Build RUNNERS dictionary with proper commands for each extension"""
- runners = {}
- # Executable languages
- runners.update({
- ".py": ["python"], ".pyw": ["pythonw"], ".pyi": ["python"],
- ".js": ["node"], ".jsx": ["node"], ".ts": ["ts-node"], ".tsx": ["ts-node"],
- ".sh": ["bash"], ".bash": ["bash"], ".zsh": ["zsh"], ".fish": ["fish"],
- ".bat": ["cmd", "/c"], ".cmd": ["cmd", "/c"],
- ".ps1": ["powershell", "-ExecutionPolicy", "Bypass", "-File"],
- ".psm1": ["powershell", "-ExecutionPolicy", "Bypass", "-File"],
- ".hta": ["mshta"] if os.name == "nt" else ["echo", "HTA requires Windows"],
- ".rb": ["ruby"], ".rake": ["ruby"],
- ".php": ["php"], ".phtml": ["php"],
- ".pl": ["perl"], ".pm": ["perl"],
- ".lua": ["lua"],
- ".r": ["Rscript"], ".R": ["Rscript"],
- ".tcl": ["tclsh"],
- ".rs": ["cargo", "run"],
- ".go": ["go", "run"],
- ".dart": ["dart"], ".swift": ["swift"],
- ".java": ["java"], ".kt": ["kotlin"], ".kts": ["kotlin", "-script"], ".scala": ["scala"],
- ".cs": ["dotnet", "run"], ".vb": ["dotnet", "run"], ".fs": ["dotnet", "run"],
- ".sql": ["sqlite3"], ".pls": ["sqlplus"],
- ".ahk": ["autohotkey"], ".au3": ["autoit3"],
- })
- # Override with auto-detected paths for Windows-specific tools
- if os.name == "nt":
- interpreters = get_default_interpreters()
- if interpreters.get("autohotkey"):
- runners[".ahk"] = [interpreters["autohotkey"]]
- if interpreters.get("autoit3"):
- runners[".au3"] = [interpreters["autoit3"]]
- # 🚀 EXECUTABLE FILES - Run directly on Windows
- if os.name == "nt":
- runners[".exe"] = ["start"]
- runners[".msi"] = ["msiexec", "/i"]
- runners[".msu"] = ["wusa"]
- runners[".com"] = ["start"]
- runners[".scr"] = ["start"]
- else:
- runners[".exe"] = ["wine"] if shutil.which("wine") else ["echo", "Requires Wine on Linux/Mac"]
- runners[".msi"] = ["echo", "MSI requires Windows"]
- runners[".msu"] = ["echo", "MSU requires Windows"]
- runners[".com"] = ["echo", "COM requires Windows"]
- runners[".scr"] = ["echo", "SCR requires Windows"]
- # Non-executable files: Open with default system application
- if os.name == "nt":
- open_cmd = ["start"]
- elif sys.platform == "darwin":
- open_cmd = ["open"]
- else:
- open_cmd = ["xdg-open"]
- # 🌐 WEB FILES -> Browser
- for ext in [".html", ".htm", ".vue", ".svelte"]:
- runners[ext] = open_cmd
- # 📄 DATA/CONFIG FILES -> Default App
- for ext in [".json", ".json5", ".xml", ".yaml", ".yml", ".toml",
- ".ini", ".cfg", ".conf", ".env"]:
- runners[ext] = open_cmd
- # 📝 TEXT FILES -> Text Editor
- for ext in [".md", ".markdown", ".rst", ".txt", ".log",
- ".csv", ".tsv", ".diff", ".patch", ".graphql", ".gql", ".ls"]:
- runners[ext] = open_cmd
- # 🎵 AUDIO FILES -> Media Player
- for ext in [".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma",
- ".m4a", ".alac", ".opus", ".aiff", ".aif", ".ape",
- ".ac3", ".amr", ".au", ".mid", ".midi", ".mka",
- ".mp2", ".ra", ".ram", ".snd", ".wv", ".dsd"]:
- runners[ext] = open_cmd
- # 🎬 VIDEO FILES -> Media Player
- for ext in [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv",
- ".webm", ".mpeg", ".mpg", ".m4v", ".3gp", ".3g2",
- ".asf", ".f4v", ".f4p", ".f4a", ".f4b", ".m2ts",
- ".m2v", ".m4p", ".mpe", ".mpv", ".mts", ".mxf",
- ".nsv", ".ogv", ".qt", ".rm", ".rmvb", ".roq",
- ".svi", ".vob", ".yuv", ".divx", ".xvid", ".avchd",
- ".gif", ".gifv", ".h264", ".hevc"]:
- runners[ext] = open_cmd
- # 🖼️ IMAGE FILES -> Image Viewer
- for ext in [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg",
- ".webp", ".ico", ".tiff", ".tif", ".psd", ".raw",
- ".cr2", ".nef", ".arw", ".dng", ".heic", ".heif",
- ".avif", ".jxr", ".pgm", ".ppm", ".pbm", ".pnm",
- ".hdr", ".exr", ".j2k", ".jp2", ".jpf", ".jpm",
- ".mj2", ".sgi", ".tga", ".icns", ".eps", ".ai"]:
- runners[ext] = open_cmd
- # 📦 PLAYLIST FILES -> Media Player
- for ext in [".m3u", ".m3u8"]:
- runners[ext] = open_cmd
- # 💻 CODE FILES -> Show Info (Not Directly Executable)
- for ext in [".css", ".scss", ".sass", ".less"]:
- runners[ext] = ["echo", f"{ext.upper()} files need to be linked in HTML or compiled first."]
- runners[".h"] = ["echo", "Header file - not directly executable"]
- # ✅ FIXED: Use loop instead of unpacking (was causing ValueError)
- for ext in [".c", ".cpp", ".cc", ".cxx"]:
- runners[ext] = ["echo", "C/C++ files need compilation first (gcc/clang)"]
- for ext in [".java", ".kt", ".scala"]:
- runners[ext] = ["echo", "Java/Kotlin/Scala need compilation first"]
- return runners
- RUNNERS = build_runners()
- # ✅ SYNTAX_PATTERNS (Code highlighting for main languages)
- SYNTAX_PATTERNS = {
- "Python": [
- (r'\b(def|class|import|from|if|else|elif|for|while|try|except|finally|return|yield|with|as|and|or|not|in|is|lambda|pass|break|continue|raise|assert|del|global|nonlocal|True|False|None|async|await|match|case|type)\b', "keyword"),
- (r'(#.*)$', "comment"),
- (r'("""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')', "string"),
- (r'\b(\d+\.?\d*(?:[eE][+-]?\d+)?)\b', "number"),
- (r'(@\w+)', "decorator"),
- (r'\b(self|cls)\b', "special"),
- ],
- "JavaScript": [
- (r'\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|class|extends|new|this|super|import|export|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|delete|void|yield|true|false|null|undefined)\b', "keyword"),
- (r'(//.*$|/\*[\s\S]*?\*/)', "comment"),
- (r'(`[\s\S]*?`|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')', "string"),
- (r'\b(\d+\.?\d*(?:[eE][+-]?\d+)?)\b', "number"),
- ],
- "HTML": [
- (r'(</?[a-zA-Z][a-zA-Z0-9\-]*\s*[^>]*?>)', "tag"),
- (r'\b([a-zA-Z\-:]+)\s*=', "attribute"),
- (r'("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')', "string"),
- (r'(<!--[\s\S]*?-->)', "comment"),
- ],
- "CSS": [
- (r'([a-zA-Z\-]+)\s*:', "property"),
- (r'#[0-9a-fA-F]{3,8}\b', "color"),
- (r'("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')', "string"),
- (r'(/\*[\s\S]*?\*/)', "comment"),
- ],
- "JSON": [
- (r'("(?:[^"\\]|\\.)*")\s*:', "key"),
- (r'("(?:[^"\\]|\\.)*")', "string"),
- (r'\b(true|false|null)\b', "keyword"),
- (r'\b(-?\d+\.?\d*(?:[eE][+-]?\d+)?)\b', "number"),
- ],
- "Shell": [
- (r'\b(if|then|else|elif|fi|case|esac|for|while|until|do|done|in|function|break|continue|return|exit|export)\b', "keyword"),
- (r'(#.*$)', "comment"),
- (r'("(?:[^"\\]|\\.)*"|\'[^\']*\'|`[^`]*`)', "string"),
- (r'(\$[a-zA-Z_][a-zA-Z0-9]*|\$\{[^}]*\})', "variable"),
- ],
- "Batch": [
- (r'\b(@echo|echo|call|goto|if|else|for|in|do|set|setlocal|endlocal|exit|rem|::|pause)\b', "keyword"),
- (r'(rem\s.*$|::.*$)', "comment"),
- (r'(%[^%]+%|![^!]+!)', "variable"),
- ],
- "PowerShell": [
- (r'\b(function|if|else|elseif|for|foreach|while|switch|break|continue|return|exit|throw|try|catch|finally)\b', "keyword"),
- (r'(#.*$|<#[\s\S]*?#>)', "comment"),
- (r'(\$(?:[a-zA-Z_][a-zA-Z0-9]*|\{[^}]*\}))', "variable"),
- ],
- }
- class SafeSettings:
- """Thread-safe settings manager with interpreter path support"""
- def __init__(self):
- self.data = self._load()
- def _load(self):
- defaults = {
- "theme": "dark", "font_size": 11, "font_family": "Consolas",
- "auto_save": True, "auto_save_interval": 30, "word_wrap": False,
- "recent": [], "interpreters": get_default_interpreters()
- }
- if SETTINGS_FILE.exists():
- try:
- with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
- loaded = json.load(f)
- if "interpreters" in loaded:
- defaults["interpreters"].update(loaded["interpreters"])
- del loaded["interpreters"]
- return {**defaults, **loaded}
- except (json.JSONDecodeError, IOError):
- pass
- return defaults
- def save(self):
- try:
- SETTINGS_FILE.parent.mkdir(parents=True, exist_ok=True)
- with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
- json.dump(self.data, f, indent=2, ensure_ascii=False)
- except IOError:
- pass
- def get(self, key, default=None):
- return self.data.get(key, default)
- def set(self, key, value):
- self.data[key] = value
- self.save()
- class LineNumberedText:
- """✅✅✅ ULTIMATE PERFECT SCROLL SYNC - Canvas-based line numbers"""
- def __init__(self, master, font_name, font_size, theme="dark"):
- self.master = master
- self.font_name = font_name
- self.font_size = font_size
- self.theme = theme
- if theme == "dark":
- self.bg = "#1e1e1e"
- self.fg = "#d4d4d4"
- self.line_bg = "#2d2d2d"
- self.line_fg = "#888888"
- self.cursor = "#ffffff"
- self.highlight_bg = "#333344"
- else:
- self.bg = "#ffffff"
- self.fg = "#000000"
- self.line_bg = "#f0f0f0"
- self.line_fg = "#666666"
- self.cursor = "#000000"
- self.highlight_bg = "#e0e0e0"
- self.frame = Frame(master, bg=self.bg)
- self.line_canvas = Canvas(self.frame, bg=self.line_bg, width=50, highlightthickness=0)
- self.line_canvas.pack(side=LEFT, fill=Y)
- self.code = Text(
- self.frame, bg=self.bg, fg=self.fg, insertbackground=self.cursor,
- font=(font_name, font_size), wrap="none", undo=True, autoseparators=True, maxundo=-1,
- borderwidth=0, highlightthickness=0, padx=10, pady=5,
- yscrollcommand=self._on_yscroll
- )
- self.code.pack(side=LEFT, fill=BOTH, expand=True)
- self.vscroll = ttk.Scrollbar(master, orient=VERTICAL)
- self.vscroll.pack(side=RIGHT, fill=Y)
- self.vscroll.config(command=self._shared_scroll)
- self.code.config(yscrollcommand=self.vscroll.set)
- self.code.bind("<MouseWheel>", self._on_mousewheel, add="+")
- self.code.bind("<Button-4>", self._on_mousewheel, add="+")
- self.code.bind("<Button-5>", self._on_mousewheel, add="+")
- self.code.bind("<KeyRelease>", self._on_change, add="+")
- self.code.bind("<ButtonRelease-1>", self._on_change, add="+")
- self.code.bind("<<Modified>>", self._on_modified, add="+")
- self.code.tag_config("current_line", background=self.highlight_bg)
- self.line_canvas.bind("<Button-1>", lambda e: "break")
- self._draw_line_numbers()
- self._highlight_current_line()
- def _shared_scroll(self, *args):
- self.code.yview(*args)
- self.line_canvas.yview(*args)
- self._draw_line_numbers()
- def _on_yscroll(self, first, last):
- self.vscroll.set(first, last)
- def _on_mousewheel(self, event):
- if event.num == 5 or event.delta == -120:
- self._shared_scroll("scroll", 1, "units")
- elif event.num == 4 or event.delta == 120:
- self._shared_scroll("scroll", -1, "units")
- return "break"
- def _on_change(self, event=None):
- self._draw_line_numbers()
- self._highlight_current_line()
- def _on_modified(self, event=None):
- if self.code.edit_modified():
- self._draw_line_numbers()
- self.code.edit_modified(False)
- def _draw_line_numbers(self):
- try:
- self.line_canvas.delete("all")
- first_visible = int(float(self.code.index("@0,0").split(".")[0]))
- last_visible = int(float(self.code.index(f"@0,{self.code.winfo_height()}").split(".")[0])) + 1
- total_lines = int(self.code.index("end-1c").split(".")[0])
- width_needed = len(str(total_lines)) * 7 + 15
- if width_needed > self.line_canvas.cget("width"):
- self.line_canvas.config(width=min(width_needed, 80))
- self.code.update_idletasks()
- line_info = self.code.dlineinfo("1.0")
- line_height = line_info[3] if line_info else self.font_size + 4
- y_offset = 5
- for line_num in range(first_visible, min(last_visible, total_lines + 1)):
- y_pos = ((line_num - 1) * line_height) - (self.code.yview()[0] * (total_lines * line_height)) + y_offset
- current_line = int(self.code.index(INSERT).split(".")[0])
- color = "#00ff00" if line_num == current_line else self.line_fg
- style = "bold" if line_num == current_line else "normal"
- self.line_canvas.create_text(40, y_pos, text=str(line_num), fill=color,
- font=(self.font_name, self.font_size, style), anchor="e")
- self.line_canvas.config(scrollregion=(0, 0, 50, total_lines * line_height + 10))
- except Exception:
- pass
- def _highlight_current_line(self, event=None):
- try:
- self.code.tag_remove("current_line", "1.0", END)
- line_num = self.code.index(INSERT).split(".")[0]
- self.code.tag_add("current_line", f"{line_num}.0", f"{line_num}.end")
- self._draw_line_numbers()
- except Exception:
- pass
- def see(self, index):
- self.code.see(index)
- first, _ = self.code.yview()
- self.line_canvas.yview("moveto", first)
- self._draw_line_numbers()
- def get_frame(self): return self.frame
- def get_text(self): return self.code
- def insert(self, idx, txt): return self.code.insert(idx, txt)
- def get(self, start, end=None): return self.code.get(start, end)
- def index(self, idx): return self.code.index(idx)
- def tag_config(self, tag, **kw): return self.code.tag_config(tag, **kw)
- def tag_add(self, tag, start, end=None): return self.code.tag_add(tag, start, end)
- def tag_remove(self, tag, start, end=None): return self.code.tag_remove(tag, start, end)
- def tag_names(self, idx=None): return self.code.tag_names(idx)
- def tag_ranges(self, tag): return self.code.tag_ranges(tag)
- def search(self, pattern, start, **kw): return self.code.search(pattern, start, **kw)
- def compare(self, i1, op, i2): return self.code.compare(i1, op, i2)
- def delete(self, start, end=None): return self.code.delete(start, end)
- def mark_set(self, mark, idx): return self.code.mark_set(mark, idx)
- def mark_get(self, mark): return self.code.mark_get(mark)
- def focus_set(self): return self.code.focus_set()
- def bind(self, seq, func, add=None): return self.code.bind(seq, func, add)
- def config(self, **kw): return self.code.config(**kw)
- class UltraProMaxV3:
- def __init__(self, root):
- self.root = root
- self.root.title(APP_NAME)
- self.root.geometry("1250x700")
- self.root.minsize(800, 500)
- if os.name == "nt" and os.path.exists(APP_ICON):
- try:
- self.root.iconbitmap(APP_ICON)
- except Exception:
- pass
- if os.name == "nt":
- try:
- from ctypes import windll
- windll.shcore.SetProcessDpiAwareness(1)
- except Exception:
- pass
- self.settings = SafeSettings()
- self.tabs = {}
- self.tab_order = []
- self.current_file = None
- self.run_proc = None
- self.auto_timer = None
- self.hl_timer = None
- self.log_q = queue.Queue()
- self.recent = self.settings.get("recent", [])
- self.file_list = []
- self.search_var = None
- Path(TEMP_DIR).mkdir(parents=True, exist_ok=True)
- self._setup_ui()
- self._setup_bindings()
- self._start_auto_save()
- self.root.after(50, self._poll_console)
- self.root.protocol("WM_DELETE_WINDOW", self.on_close)
- def _setup_ui(self):
- """Build complete UI"""
- menubar = Menu(self.root, tearoff=0)
- self.root.config(menu=menubar)
- file_menu = Menu(menubar, tearoff=0)
- file_menu.add_command(label="Open File", command=self.open_file, accelerator="Ctrl+O")
- file_menu.add_command(label="Open Archive", command=self.open_archive, accelerator="Ctrl+Shift+O")
- file_menu.add_command(label="New Script", command=self.new_script, accelerator="Ctrl+N")
- file_menu.add_command(label="Save", command=self.save, accelerator="Ctrl+S")
- file_menu.add_command(label="Save As...", command=self.save_as, accelerator="Ctrl+Shift+S")
- file_menu.add_separator()
- file_menu.add_command(label="Export to ZIP", command=self.export_to_zip, accelerator="Ctrl+E")
- file_menu.add_separator()
- file_menu.add_command(label="Exit", command=self.on_close, accelerator="Alt+F4")
- menubar.add_cascade(label="File", menu=file_menu)
- edit_menu = Menu(menubar, tearoff=0)
- edit_menu.add_command(label="Find & Replace", command=self.find_replace, accelerator="Ctrl+F")
- edit_menu.add_command(label="Global Search", command=self.global_search, accelerator="Ctrl+Shift+F")
- edit_menu.add_command(label="Toggle Word Wrap", command=self.toggle_wrap, accelerator="Ctrl+Alt+W")
- edit_menu.add_separator()
- edit_menu.add_command(label="✂️ Cut", command=lambda: self.root.focus_get().event_generate("<<Cut>>"), accelerator="Ctrl+X")
- edit_menu.add_command(label="Copy", command=lambda: self.root.focus_get().event_generate("<<Copy>>"), accelerator="Ctrl+C")
- edit_menu.add_command(label="Paste", command=lambda: self.root.focus_get().event_generate("<<Paste>>"), accelerator="Ctrl+V")
- menubar.add_cascade(label="Edit", menu=edit_menu)
- tab_menu = Menu(menubar, tearoff=0)
- tab_menu.add_command(label="✏️ Rename Tab", command=self.rename_tab, accelerator="Ctrl+Shift+R")
- tab_menu.add_command(label="❌ Close Tab", command=self.close_tab, accelerator="Ctrl+W")
- tab_menu.add_command(label="Close Others", command=self.close_other_tabs, accelerator="Ctrl+Shift+W")
- tab_menu.add_command(label="Close All", command=self.close_all_tabs, accelerator="Ctrl+Shift+Q")
- menubar.add_cascade(label="Tab", menu=tab_menu)
- run_menu = Menu(menubar, tearoff=0)
- run_menu.add_command(label="▶ Run/Open", command=self.run_script, accelerator="Ctrl+R")
- run_menu.add_command(label="⏹ Stop Process", command=self.stop_run, accelerator="Ctrl+Shift+R")
- run_menu.add_command(label="❌ Clear Console", command=self.clear_console)
- menubar.add_cascade(label="Run", menu=run_menu)
- help_menu = Menu(menubar, tearoff=0)
- help_menu.add_command(label="⚙ Settings", command=self.show_settings)
- help_menu.add_command(label="ℹ About", command=self.about)
- menubar.add_cascade(label="Help", menu=help_menu)
- toolbar = Frame(self.root, bg="#2d2d2d", height=38)
- toolbar.pack(fill=X, side=TOP, padx=4, pady=2)
- def tb_btn(text, cmd):
- btn = ttk.Button(toolbar, text=text, command=cmd, width=8)
- btn.pack(side=LEFT, padx=2)
- return btn
- tb_btn("Open", self.open_file)
- tb_btn("New", self.new_script)
- tb_btn("Save", self.save)
- tb_btn("OpenZip", self.open_archive)
- tb_btn("ExpZip", self.export_to_zip)
- tb_btn("Rename", self.rename_tab)
- tb_btn("Close", self.close_tab)
- tb_btn("▶ Run", self.run_script)
- tb_btn("Stop", self.stop_run)
- tb_btn("Find", self.find_replace)
- tb_btn("⚙ Set", self.show_settings)
- self.status = ttk.Label(self.root, text="Ready | UTF-8 | Ln 1, Col 1 | Wrap: OFF",
- background="#007acc", foreground="white", anchor=W, font=("Segoe UI", 9))
- self.status.pack(fill=X, side=BOTTOM)
- paned = ttk.PanedWindow(self.root, orient=HORIZONTAL)
- paned.pack(fill=BOTH, expand=True, padx=2, pady=2)
- left_panel = ttk.Frame(paned, width=220)
- paned.add(left_panel, weight=1)
- search_frame = ttk.Frame(left_panel)
- search_frame.pack(fill=X, padx=4, pady=(4, 2))
- ttk.Label(search_frame, text="🔍").pack(side=LEFT, padx=(0, 2))
- self.search_var = StringVar()
- self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=20)
- self.search_entry.pack(side=LEFT, fill=X, expand=True)
- self.search_entry.bind("<KeyRelease>", self._filter_file_list)
- ttk.Button(search_frame, text="✕", width=3, command=self._clear_search).pack(side=LEFT, padx=(2, 0))
- tree_frame = ttk.Frame(left_panel)
- tree_frame.pack(fill=BOTH, expand=True, padx=4, pady=(0, 4))
- tree_scroll = ttk.Scrollbar(tree_frame)
- tree_scroll.pack(side=RIGHT, fill=Y)
- self.tree = ttk.Treeview(tree_frame, show="tree", selectmode="browse", yscrollcommand=tree_scroll.set)
- self.tree.pack(fill=BOTH, expand=True)
- tree_scroll.config(command=self.tree.yview)
- self.tree.bind("<<TreeviewSelect>>", self._on_tree_select, add="+")
- right_panel = ttk.Frame(paned)
- paned.add(right_panel, weight=3)
- self.notebook = ttk.Notebook(right_panel)
- self.notebook.pack(fill=BOTH, expand=True)
- self.notebook.bind("<<NotebookTabChanged>>", self._on_tab_changed)
- self.tab_menu_popup = Menu(self.root, tearoff=0)
- self.tab_menu_popup.add_command(label="✏️ Rename", command=self.rename_tab)
- self.tab_menu_popup.add_command(label="❌ Close", command=self.close_tab)
- self.tab_menu_popup.add_command(label="🗑️ Close Others", command=self.close_other_tabs)
- self.tab_menu_popup.add_command(label="🧹 Close All", command=self.close_all_tabs)
- self.notebook.bind("<Button-3>", self._show_tab_context_menu)
- console_frame = ttk.Frame(right_panel)
- console_frame.pack(fill=X, padx=4, pady=(4, 0))
- console_scroll = ttk.Scrollbar(console_frame)
- console_scroll.pack(side=RIGHT, fill=Y)
- self.console = Text(console_frame, height=10, bg="#0c0c0c", fg="#00ff00",
- font=(self.settings.get("font_family", "Consolas"), self.settings.get("font_size", 11)),
- state=DISABLED, wrap="word", yscrollcommand=console_scroll.set)
- self.console.pack(fill=BOTH, expand=True)
- console_scroll.config(command=self.console.yview)
- def _show_tab_context_menu(self, event):
- try:
- tab_id = self.notebook.index(f"@{event.x},{event.y}")
- if tab_id is not None:
- self.notebook.select(tab_id)
- self._on_tab_changed(None)
- self.tab_menu_popup.post(event.x_root, event.y_root)
- except Exception:
- pass
- def _clear_search(self):
- self.search_var.set("")
- self._populate_file_tree()
- def _filter_file_list(self, event=None):
- term = self.search_var.get().lower()
- self.tree.delete(*self.tree.get_children())
- for fp in self.file_list:
- if not term or term in os.path.basename(fp).lower():
- ext = Path(fp).suffix.lower()
- icon = self._get_file_icon(ext)
- self.tree.insert("", "end", iid=fp, text=f"{icon} {os.path.basename(fp)}")
- def _get_file_icon(self, ext):
- """Get emoji icon for file type"""
- if ext in [".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a", ".alac", ".opus"]:
- return "🎵"
- elif ext in [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".webm", ".mpeg", ".flv"]:
- return "🎬"
- elif ext in [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".ico", ".tiff"]:
- return "🖼️"
- elif ext in [".exe", ".msi", ".msu", ".com", ".scr"]:
- return "🚀"
- elif ext in [".py", ".js", ".html", ".css", ".java", ".cpp", ".c", ".php", ".rb"]:
- return "📄"
- else:
- return "📁"
- def _on_tree_select(self, event):
- try:
- sel = self.tree.selection()
- if sel and os.path.isfile(sel[0]):
- self._open_file_safe(sel[0])
- except Exception:
- pass
- def _on_tab_changed(self, event):
- self._update_current_file()
- self._update_status()
- def _setup_bindings(self):
- bindings = [
- ("<Control-o>", self.open_file), ("<Control-Shift-O>", self.open_archive),
- ("<Control-n>", self.new_script), ("<Control-s>", self.save),
- ("<Control-Shift-S>", self.save_as), ("<Control-e>", self.export_to_zip),
- ("<Control-f>", self.find_replace), ("<Control-Shift-F>", self.global_search),
- ("<Control-r>", self.run_script), ("<Control-Shift-R>", self.stop_run),
- ("<Control-Alt-W>", self.toggle_wrap), ("<Control-q>", self.on_close),
- ("<Control-Shift-r>", self.rename_tab), ("<Control-w>", self.close_tab),
- ("<Control-Shift-w>", self.close_other_tabs), ("<Control-Shift-q>", self.close_all_tabs),
- ]
- for key, cb in bindings:
- self.root.bind(key, lambda e, callback=cb: callback(), add="+")
- def _start_auto_save(self):
- if self.settings.get("auto_save", True):
- interval = self.settings.get("auto_save_interval", 30) * 1000
- self.auto_timer = self.root.after(interval, self._auto_save)
- def _auto_save(self):
- try:
- for fp, td in list(self.tabs.items()):
- if td.get("modified") and os.path.exists(fp):
- try:
- with open(fp, "w", encoding="utf-8") as f:
- f.write(td["text"].get("1.0", END))
- td["modified"] = False
- except IOError:
- self.log(f"⚠ Auto-save failed: {os.path.basename(fp)}")
- except RuntimeError:
- pass
- finally:
- if self.settings.get("auto_save", True):
- self.auto_timer = self.root.after(self.settings.get("auto_save_interval", 30) * 1000, self._auto_save)
- def _open_file_safe(self, fp):
- if not os.path.exists(fp):
- return
- if fp in self.tabs:
- self.notebook.select(self.tabs[fp]["frame"])
- else:
- self.open_tab(fp)
- def open_tab(self, file_path):
- try:
- ext = Path(file_path).suffix.lower()
- lang = EXT_LANG.get(ext, "Text")
- theme = self.settings.get("theme", "dark")
- font_fam = self.settings.get("font_family", "Consolas")
- font_sz = self.settings.get("font_size", 11)
- # For media/executable files, open with default app instead of loading in editor
- if ext in [".mp3", ".wav", ".flac", ".mp4", ".avi", ".mkv", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".exe", ".msi", ".msu", ".com", ".scr"]:
- self._open_with_default_app(file_path)
- return
- ln_text = LineNumberedText(self.notebook, font_fam, font_sz, theme)
- frame = ln_text.get_frame()
- text_widget = ln_text.get_text()
- with open(file_path, "r", encoding="utf-8", errors="replace") as f:
- content = f.read()
- # Check if file is too large for editor
- if len(content) > 500000:
- self.log(f"⚠️ Large file ({len(content)} chars). Opening with default app.")
- self._open_with_default_app(file_path)
- return
- text_widget.insert("1.0", content)
- tab_name = f" {Path(file_path).name} "
- tab_idx = self.notebook.add(frame, text=tab_name)
- self.notebook.select(frame)
- if file_path not in self.tab_order:
- self.tab_order.append(file_path)
- self.tabs[file_path] = {
- "frame": frame, "text": text_widget, "lang": lang,
- "modified": False, "encoding": "utf-8",
- "original_name": Path(file_path).name, "tab_index": tab_idx
- }
- self.current_file = file_path
- text_widget.bind("<KeyRelease>", lambda e, p=file_path: self._on_text_change(p), add="+")
- text_widget.bind("<ButtonRelease-1>", lambda e: self._update_status(), add="+")
- self._schedule_highlight(file_path)
- self._update_status()
- self._add_recent(file_path)
- except Exception as e:
- self.log(f"❌ Error opening {file_path}: {e}")
- def _open_with_default_app(self, file_path):
- """Open file with default system application"""
- try:
- if os.name == "nt":
- os.startfile(file_path)
- elif sys.platform == "darwin":
- subprocess.run(["open", file_path])
- else:
- subprocess.run(["xdg-open", file_path])
- self.log(f"▶ Opened: {Path(file_path).name}")
- except Exception as e:
- self.log(f"❌ Open failed: {e}")
- messagebox.showerror("Open Error", str(e))
- def _on_text_change(self, fp):
- if fp in self.tabs:
- self.tabs[fp]["modified"] = True
- name = Path(fp).name
- for i in range(self.notebook.index("end")):
- if self.notebook.tab(i, "text").strip() == name:
- self.notebook.tab(i, text=f" *{name} ")
- break
- self._schedule_highlight(fp)
- def _schedule_highlight(self, fp):
- if fp not in self.tabs:
- return
- if self.hl_timer:
- try:
- self.root.after_cancel(self.hl_timer)
- except Exception:
- pass
- self.hl_timer = self.root.after(HIGHLIGHT_DELAY, lambda p=fp: self._apply_syntax_highlighting(p))
- def _apply_syntax_highlighting(self, fp):
- if fp not in self.tabs:
- return
- td = self.tabs[fp]
- tw = td["text"]
- lang = td["lang"]
- if lang not in SYNTAX_PATTERNS:
- return
- try:
- for tag in tw.tag_names():
- if tag not in ("current_line", "sel", "find_highlight"):
- tw.tag_delete(tag)
- styles = {
- "keyword": {"foreground": "#569cd6"}, "string": {"foreground": "#ce9178"},
- "comment": {"foreground": "#6a9955"}, "number": {"foreground": "#b5cea8"},
- "decorator": {"foreground": "#dcdcaa"}, "special": {"foreground": "#9cdcfe"},
- "class": {"foreground": "#4ec9b0"}, "tag": {"foreground": "#569cd6"},
- "attribute": {"foreground": "#9cdcfe"}, "property": {"foreground": "#9cdcfe"},
- "key": {"foreground": "#9cdcfe"}, "constant": {"foreground": "#4fc1ff"},
- "variable": {"foreground": "#9cdcfe"}, "header": {"foreground": "#569cd6", "font": ("", "", "bold")},
- }
- for tag, style in styles.items():
- try:
- tw.tag_config(tag, **style)
- except Exception:
- pass
- content = tw.get("1.0", END)
- for pat_data in SYNTAX_PATTERNS[lang]:
- pattern, tag_name = (pat_data[:2] if len(pat_data) == 2 else pat_data[:2])
- flags = pat_data[2] if len(pat_data) == 3 else 0
- try:
- for m in re.finditer(pattern, content, flags):
- tw.tag_add(tag_name, f"1.0+{m.start()}c", f"1.0+{m.end()}c")
- except Exception:
- continue
- except Exception:
- pass
- def _update_current_file(self):
- try:
- cur = self.notebook.select()
- if cur:
- for p, d in self.tabs.items():
- if str(d["frame"]) == cur:
- self.current_file = p
- return
- self.current_file = None
- except Exception:
- self.current_file = None
- def _update_status(self):
- try:
- if not self.current_file or self.current_file not in self.tabs:
- self.status.config(text="Ready | No file open")
- return
- td = self.tabs[self.current_file]
- tw = td["text"]
- line, col = map(int, tw.index(INSERT).split("."))
- col += 1
- name = Path(self.current_file).name
- lang = td.get("lang", "Text")
- mod = "*" if td.get("modified") else ""
- wrap = "ON" if self.settings.get("word_wrap") else "OFF"
- theme = self.settings.get("theme", "dark").upper()
- self.status.config(text=f"{mod}{name} | {lang} | UTF-8 | Ln {line}, Col {col} | Wrap: {wrap} | Theme: {theme}")
- except Exception:
- self.status.config(text="Ready")
- def _add_recent(self, fp):
- if fp in self.recent:
- self.recent.remove(fp)
- self.recent.insert(0, fp)
- self.recent = self.recent[:RECENT_MAX]
- self.settings.set("recent", self.recent)
- def open_file(self):
- """Open file dialog with ALL file types including media and EXE"""
- fp = filedialog.askopenfilename(
- title="Open File",
- filetypes=[
- ("All Files", "*.*"),
- ("Python", "*.py *.pyw"),
- ("Web", "*.js *.jsx *.ts *.tsx *.html *.htm *.css *.vue"),
- ("Data", "*.json *.xml *.yaml *.yml *.csv"),
- ("C/C++", "*.c *.h *.cpp *.hpp"),
- ("Shell", "*.sh *.bash *.bat *.ps1"),
- ("Other Code", "*.rb *.php *.sql *.rs *.go *.java"),
- ("Text", "*.txt *.md *.log"),
- ("─" * 40, ""),
- ("🎵 Audio", "*.mp3 *.wav *.flac *.aac *.ogg *.wma *.m4a"),
- ("🎬 Video", "*.mp4 *.avi *.mkv *.mov *.wmv *.webm *.mpeg"),
- ("🖼️ Images", "*.jpg *.jpeg *.png *.gif *.bmp *.svg *.webp *.ico"),
- ("🚀 Executables", "*.exe *.msi *.msu *.com *.scr"),
- ("All Media", "*.mp3 *.wav *.flac *.mp4 *.avi *.mkv *.jpg *.png *.gif"),
- ]
- )
- if fp:
- self.open_tab(fp)
- def save_as(self):
- if not self.current_file or self.current_file not in self.tabs:
- return
- ext = Path(self.current_file).suffix.lower()
- ft = [("All Files", "*.*")]
- if ext in EXT_LANG:
- ft.insert(0, (EXT_LANG[ext], f"*{ext}"))
- fp = filedialog.asksaveasfilename(title="Save As", initialfile=Path(self.current_file).name,
- defaultextension=ext if ext else ".txt", filetypes=ft)
- if fp:
- old = self.current_file
- self.current_file = fp
- self.save()
- if old in self.tabs:
- self.tabs[fp] = self.tabs.pop(old)
- if old in self.tab_order:
- self.tab_order[self.tab_order.index(old)] = fp
- self._add_recent(fp)
- def save(self):
- if not self.current_file or self.current_file not in self.tabs:
- return
- try:
- td = self.tabs[self.current_file]
- with open(self.current_file, "w", encoding="utf-8") as f:
- f.write(td["text"].get("1.0", END))
- td["modified"] = False
- name = Path(self.current_file).name
- for i in range(self.notebook.index("end")):
- if self.notebook.tab(i, "text").strip() == name:
- self.notebook.tab(i, text=f" {name} ")
- break
- self.log(f"💾 Saved: {name}")
- self._update_status()
- except IOError as e:
- self.log(f"❌ Save failed: {e}")
- messagebox.showerror("Save Error", f"Could not save:\n{e}")
- def rename_tab(self):
- """✅ FIXED: Complete if statement"""
- if not self.current_file or self.current_file not in self.tabs:
- messagebox.showinfo("Rename", "No file open")
- return
- td = self.tabs[self.current_file]
- cur_name = Path(self.current_file).name
- new_name = simpledialog.askstring("Rename Tab", f"New name for:\n{cur_name}", initialvalue=cur_name, parent=self.root)
- if not new_name or new_name == cur_name:
- return
- if Path(new_name).suffix.lower() not in EXT_LANG:
- messagebox.showwarning("Invalid", f"Extension '{Path(new_name).suffix}' not supported")
- return
- try:
- if os.path.exists(self.current_file):
- new_path = os.path.join(os.path.dirname(self.current_file), new_name)
- if os.path.exists(new_path) and new_path != self.current_file:
- if not messagebox.askyesno("Exists", f"'{new_name}' exists. Overwrite?"):
- return
- os.rename(self.current_file, new_path)
- self.current_file = new_path
- if self.current_file in self.tabs:
- old_data = self.tabs.pop(self.current_file, None)
- # ✅ FIXED: Complete the if statement (was: "if old_")
- if old_data:
- self.tabs[self.current_file] = old_data
- self.notebook.tab(td["frame"], text=f" {new_name} ")
- td["modified"] = False
- td["original_name"] = new_name
- self.log(f"✏️ Renamed: {cur_name} → {new_name}")
- self._update_status()
- except Exception as e:
- self.log(f"❌ Rename error: {e}")
- messagebox.showerror("Error", f"Could not rename:\n{e}")
- def close_tab(self, fp=None):
- if fp is None:
- fp = self.current_file
- if not fp or fp not in self.tabs:
- return
- td = self.tabs[fp]
- if td.get("modified"):
- r = messagebox.askyesnocancel("Unsaved", f"Save changes to '{Path(fp).name}'?", parent=self.root)
- if r is None:
- return
- elif r:
- self.current_file = fp
- self.save()
- try:
- self.notebook.forget(td["frame"])
- del self.tabs[fp]
- if fp in self.tab_order:
- self.tab_order.remove(fp)
- if self.current_file == fp:
- self._update_current_file()
- self.log(f"❌ Closed: {Path(fp).name}")
- self._update_status()
- except Exception:
- pass
- def close_other_tabs(self):
- if not self.current_file or self.current_file not in self.tabs:
- return
- cur = self.current_file
- to_close = [f for f in self.tabs if f != cur]
- if not to_close:
- return
- unsaved = [f for f in to_close if self.tabs[f].get("modified")]
- if unsaved and not messagebox.askyesno("Unsaved", f"{len(unsaved)} tab(s) unsaved. Close anyway?"):
- return
- for f in to_close:
- self.close_tab(f)
- self.log(f"🗑️ Closed {len(to_close)} other tab(s)")
- def close_all_tabs(self):
- if not self.tabs:
- return
- unsaved = [f for f in self.tabs if self.tabs[f].get("modified")]
- if unsaved and not messagebox.askyesno("Unsaved", f"{len(unsaved)} tab(s) unsaved. Close all?"):
- return
- for f in list(self.tabs.keys()):
- self.close_tab(f)
- self.log("🧹 Closed all tabs")
- def new_script(self):
- fn = simpledialog.askstring("New Script", "Filename with extension (e.g., script.py):", parent=self.root)
- if not fn:
- return
- ext = Path(fn).suffix.lower()
- if ext not in EXT_LANG:
- messagebox.showwarning("Invalid", f"Extension '{ext}' not supported")
- return
- fp = os.path.join(TEMP_DIR, fn)
- if os.path.exists(fp) and not messagebox.askyesno("Exists", f"'{fn}' exists. Overwrite?"):
- return
- try:
- with open(fp, "w", encoding="utf-8") as f:
- f.write(f"# {fn}\n# {EXT_LANG[ext]} File\n# Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
- self.log(f"📄 New: {fn}")
- if fp not in self.file_list:
- self.file_list.append(fp)
- self.tree.insert("", "end", iid=fp, text=f"{self._get_file_icon(ext)} {fn}")
- self.open_tab(fp)
- if fp in self.tabs:
- self.tabs[fp]["text"].focus_set()
- except IOError as e:
- self.log(f"❌ Create error: {e}")
- messagebox.showerror("Error", f"Could not create:\n{e}")
- def toggle_wrap(self):
- cur = self.settings.get("word_wrap", False)
- self.settings.set("word_wrap", not cur)
- wrap = "word" if not cur else "none"
- for td in self.tabs.values():
- td["text"].config(wrap=wrap)
- self._update_status()
- self.log(f"🔄 Word wrap: {'ON' if not cur else 'OFF'}")
- def find_replace(self):
- if not self.current_file or self.current_file not in self.tabs:
- messagebox.showinfo("Find", "No file open")
- return
- dlg = Toplevel(self.root)
- dlg.title("Find & Replace")
- dlg.geometry("400x180")
- dlg.resizable(False, False)
- dlg.transient(self.root)
- dlg.grab_set()
- ttk.Label(dlg, text="Find:").grid(row=0, column=0, padx=10, pady=8, sticky=W)
- fv = StringVar()
- fe = ttk.Entry(dlg, textvariable=fv, width=45)
- fe.grid(row=0, column=1, columnspan=2, padx=5, pady=8)
- fe.focus()
- ttk.Label(dlg, text="Replace:").grid(row=1, column=0, padx=10, pady=5, sticky=W)
- rv = StringVar()
- ttk.Entry(dlg, textvariable=rv, width=45).grid(row=1, column=1, columnspan=2, padx=5, pady=5)
- def find_next():
- tw = self.tabs[self.current_file]["text"]
- st = fv.get()
- if not st:
- return
- tw.tag_remove("find_highlight", "1.0", END)
- pos = tw.search(st, tw.index(INSERT), stopindex=END)
- if not pos:
- pos = tw.search(st, "1.0", stopindex=END)
- if pos:
- tw.tag_add("find_highlight", pos, f"{pos}+{len(st)}c")
- tw.tag_config("find_highlight", background="#444466", foreground="yellow")
- tw.see(pos)
- tw.mark_set(INSERT, pos)
- else:
- messagebox.showinfo("Find", "No matches")
- def replace_one():
- tw = self.tabs[self.current_file]["text"]
- st, rt = fv.get(), rv.get()
- if not st:
- return
- cur = tw.index(INSERT)
- if "find_highlight" in tw.tag_names(cur):
- for i in range(0, len(tw.tag_ranges("find_highlight")), 2):
- s, e = tw.tag_ranges("find_highlight")[i], tw.tag_ranges("find_highlight")[i+1]
- if tw.compare(s, "<=", cur) and tw.compare(cur, "<", e):
- tw.delete(s, e)
- tw.insert(s, rt)
- self.tabs[self.current_file]["modified"] = True
- find_next()
- break
- def replace_all():
- tw = self.tabs[self.current_file]["text"]
- st, rt = fv.get(), rv.get()
- if not st:
- return
- content = tw.get("1.0", END).replace(st, rt)
- tw.delete("1.0", END)
- tw.insert("1.0", content)
- self.tabs[self.current_file]["modified"] = True
- self.log(f"🔄 Replaced {content.count(st) if st else 0} occurrence(s)")
- find_next()
- bf = Frame(dlg)
- bf.grid(row=2, column=0, columnspan=3, pady=10)
- ttk.Button(bf, text="Find Next", command=find_next, width=11).pack(side=LEFT, padx=3)
- ttk.Button(bf, text="Replace", command=replace_one, width=11).pack(side=LEFT, padx=3)
- ttk.Button(bf, text="Replace All", command=replace_all, width=11).pack(side=LEFT, padx=3)
- ttk.Button(bf, text="Close", command=dlg.destroy, width=11).pack(side=LEFT, padx=3)
- fe.bind("<Return>", lambda e: find_next())
- def global_search(self):
- if not os.path.exists(TEMP_DIR):
- messagebox.showinfo("Search", "No archive extracted")
- return
- st = simpledialog.askstring("Global Search", "Text to search:", parent=self.root)
- if not st:
- return
- results, sl = [], st.lower()
- for root, _, files in os.walk(TEMP_DIR):
- for fn in files:
- ext = Path(fn).suffix.lower()
- if ext in EXT_LANG or ext in (".txt", ".md", ".log"):
- fp = os.path.join(root, fn)
- try:
- with open(fp, "r", encoding="utf-8", errors="ignore") as f:
- content = f.read()
- if sl in content.lower():
- rel = os.path.relpath(fp, TEMP_DIR)
- results.append(f"{rel} ({content.lower().count(sl)} matches)")
- except Exception:
- continue
- if results:
- txt = f"Found {len(results)} file(s):\n\n" + "\n".join(results[:50])
- if len(results) > 50:
- txt += f"\n... and {len(results)-50} more"
- messagebox.showinfo("Results", txt)
- else:
- messagebox.showinfo("Results", "No matches")
- def run_script(self):
- """Run script or open media/exe file with default app"""
- if not self.current_file or self.current_file not in self.tabs:
- # Try to open selected file from tree
- sel = self.tree.selection()
- if sel and os.path.isfile(sel[0]):
- self._open_with_default_app(sel[0])
- return
- self.log("❌ No file selected")
- return
- ext = Path(self.current_file).suffix.lower()
- runner = RUNNERS.get(ext)
- if not runner:
- self.log(f"❌ Cannot run: No executor for '{ext}'")
- messagebox.showwarning("Run Error", f"Cannot execute: {ext}")
- return
- # Handle "open with default app" commands (media files, web files, EXE, etc.)
- if runner[0] in ("start", "xdg-open", "open"):
- self._open_with_default_app(self.current_file)
- return
- # Handle MSI installer specially
- if ext == ".msi" and os.name == "nt":
- try:
- subprocess.run(["msiexec", "/i", self.current_file])
- self.log(f"▶ Installing: {Path(self.current_file).name}")
- except Exception as e:
- self.log(f"❌ Install failed: {e}")
- messagebox.showerror("Install Error", str(e))
- return
- # Handle info messages for non-executable files
- if runner[0] == "echo":
- msg = " ".join(runner[1:]) if len(runner) > 1 else "File not directly executable"
- self.log(f"ℹ️ {msg}")
- messagebox.showinfo("File Info", msg)
- return
- # Save if modified
- if self.tabs[self.current_file].get("modified"):
- self.save()
- # Get interpreter from settings
- lang_key = ext.lstrip(".")
- interpreters = self.settings.get("interpreters", {})
- if ext == ".ahk":
- interpreter = interpreters.get("autohotkey", runner[0])
- elif ext == ".au3":
- interpreter = interpreters.get("autoit3", runner[0])
- else:
- interpreter = interpreters.get(lang_key, runner[0])
- if ext == ".pyw":
- self.log(f"▶ Running (GUI): {interpreter} {self.current_file}")
- else:
- cmd_display = [interpreter] + runner[1:] + [self.current_file]
- self.log(f"▶ Running: {' '.join(cmd_display)}")
- self.clear_console()
- try:
- cmd = [interpreter] + runner[1:] + [self.current_file]
- self.run_proc = subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
- universal_newlines=True, bufsize=1,
- creationflags=CREATE_NO_WINDOW if os.name == "nt" else 0,
- cwd=os.path.dirname(self.current_file) or "."
- )
- threading.Thread(target=self._read_process_output, daemon=True).start()
- except FileNotFoundError:
- self.log(f"❌ Interpreter not found: {interpreter}")
- messagebox.showerror("Run Error", f"Could not find '{interpreter}'\n\nTip: Add it to PATH or configure in Settings → Interpreters")
- except Exception as e:
- self.log(f"❌ Run error: {e}")
- messagebox.showerror("Run Error", str(e))
- def _read_process_output(self):
- if not self.run_proc:
- return
- try:
- for line in iter(self.run_proc.stdout.readline, ''):
- if line:
- self.log_q.put(line)
- rc = self.run_proc.wait()
- self.log_q.put(f"\n⏹ Exit code: {rc}\n" if rc != 0 else "\n⏹ Completed successfully\n")
- except Exception as e:
- self.log_q.put(f"\n❌ Read error: {e}\n")
- finally:
- self.run_proc = None
- def stop_run(self):
- if self.run_proc and self.run_proc.poll() is None:
- try:
- self.run_proc.terminate()
- self.run_proc.wait(timeout=2)
- self.log("⏹ Terminated")
- except subprocess.TimeoutExpired:
- try:
- self.run_proc.kill()
- except Exception:
- pass
- self.log("⏹ Killed (forced)")
- except Exception:
- pass
- finally:
- self.run_proc = None
- else:
- self.log("⚠ No running process")
- def clear_console(self):
- try:
- self.console.config(state=NORMAL)
- self.console.delete("1.0", END)
- self.console.config(state=DISABLED)
- except Exception:
- pass
- def _poll_console(self):
- try:
- while True:
- msg = self.log_q.get_nowait()
- self.console.config(state=NORMAL)
- self.console.insert(END, msg)
- self.console.see(END)
- self.console.config(state=DISABLED)
- except (queue.Empty, TclError):
- pass
- if self.root.winfo_exists():
- self.root.after(50, self._poll_console)
- def log(self, msg):
- self.log_q.put(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}")
- def export_to_zip(self):
- """Export all files including media and EXE to ZIP"""
- if not os.path.exists(TEMP_DIR) or not self.file_list:
- messagebox.showwarning("Export", "No files to export")
- return
- # Count different file types
- code_files = sum(1 for f in self.file_list if Path(f).suffix.lower() in
- [".py", ".js", ".html", ".css", ".java", ".cpp", ".c", ".php", ".rb", ".go", ".rs"])
- media_files = sum(1 for f in self.file_list if Path(f).suffix.lower() in
- [".mp3", ".mp4", ".jpg", ".png", ".gif", ".avi", ".mkv", ".wav", ".flac"])
- exe_files = sum(1 for f in self.file_list if Path(f).suffix.lower() in
- [".exe", ".msi", ".msu", ".com", ".scr"])
- zp = filedialog.asksaveasfilename(
- title="Save Archive",
- defaultextension=".zip",
- filetypes=[("Zip", "*.zip")],
- initialfile="exported_project.zip"
- )
- if not zp:
- return
- try:
- self.log(f"🗂️ Exporting: {os.path.basename(zp)}")
- self.log(f" 📄 Code files: {code_files}")
- self.log(f" 🎬 Media files: {media_files}")
- self.log(f" 🚀 Executables: {exe_files}")
- with zipfile.ZipFile(zp, 'w', zipfile.ZIP_DEFLATED) as zf:
- for root, _, files in os.walk(TEMP_DIR):
- for f in files:
- fp = os.path.join(root, f)
- zf.write(fp, os.path.relpath(fp, TEMP_DIR))
- total = len(self.file_list)
- self.log(f"✅ Exported {total} file(s) to ZIP")
- messagebox.showinfo(
- "Success",
- f"✅ Archive saved!\n\n"
- f"📦 Total files: {total}\n"
- f"📄 Code: {code_files}\n"
- f"🎬 Media: {media_files}\n"
- f"🚀 Executables: {exe_files}"
- )
- except Exception as e:
- self.log(f"❌ Export error: {e}")
- messagebox.showerror("Export Error", str(e))
- def open_archive(self):
- """Open archive dialog"""
- ap = filedialog.askopenfilename(
- title="Open Archive",
- filetypes=[
- ("Zip", "*.zip"),
- ("Tar/Gzip", "*.tar.gz *.tgz"),
- ("All Archives", "*.zip *.tar.gz *.tgz *.tar"),
- ]
- )
- if not ap:
- return
- threading.Thread(target=self._extract_archive, args=(ap,), daemon=True).start()
- def _extract_archive(self, ap):
- try:
- self.log(f"📦 Extracting: {Path(ap).name}")
- if os.path.exists(TEMP_DIR):
- shutil.rmtree(TEMP_DIR, ignore_errors=True)
- Path(TEMP_DIR).mkdir(parents=True, exist_ok=True)
- if ap.endswith(".zip"):
- with zipfile.ZipFile(ap, 'r') as zf:
- zf.extractall(TEMP_DIR)
- elif ap.endswith((".tar.gz", ".tgz")):
- with tarfile.open(ap, 'r:gz') as tf:
- tf.extractall(TEMP_DIR)
- elif ap.endswith(".tar"):
- with tarfile.open(ap, 'r') as tf:
- tf.extractall(TEMP_DIR)
- else:
- raise ValueError("Unsupported format")
- self.log("✅ Extracted")
- self.root.after(0, self._populate_file_tree)
- except Exception as e:
- self.log(f"❌ Extract error: {e}")
- shutil.rmtree(TEMP_DIR, ignore_errors=True)
- def _populate_file_tree(self):
- """Populate file tree with ALL supported files including media and EXE"""
- try:
- self.tree.delete(*self.tree.get_children())
- self.file_list = []
- for root, _, files in os.walk(TEMP_DIR):
- for fn in files:
- ext = Path(fn).suffix.lower()
- # Include ALL extensions: Code + Text + Media + EXE
- if ext in EXT_LANG:
- fp = os.path.join(root, fn)
- self.file_list.append(fp)
- # Show file with icon indicator
- icon = self._get_file_icon(ext)
- self.tree.insert("", "end", iid=fp, text=f"{icon} {fn}")
- if self.file_list:
- self.log(f"📁 Loaded {len(self.file_list)} files (Code + Media + EXE)")
- else:
- self.log("⚠ No supported files found")
- except Exception as e:
- self.log(f"❌ Tree error: {e}")
- def show_settings(self):
- dlg = Toplevel(self.root)
- dlg.title("Settings")
- dlg.geometry("550x520")
- dlg.resizable(False, False)
- dlg.transient(self.root)
- dlg.grab_set()
- interpreters = self.settings.get("interpreters", {})
- ttk.Label(dlg, text="🎨 Theme:").grid(row=0, column=0, padx=15, pady=8, sticky=W)
- tv = StringVar(value=self.settings.get("theme", "dark"))
- ttk.Combobox(dlg, textvariable=tv, values=("dark", "light"), width=25, state="readonly").grid(row=0, column=1, padx=5)
- ttk.Label(dlg, text="Font:").grid(row=1, column=0, padx=15, pady=5, sticky=W)
- fv = StringVar(value=self.settings.get("font_family", "Consolas"))
- ttk.Combobox(dlg, textvariable=fv, values=("Consolas", "Courier New", "Lucida Console", "Monospace", "Fira Code"), width=25).grid(row=1, column=1, padx=5)
- ttk.Label(dlg, text="Size:").grid(row=2, column=0, padx=15, pady=5, sticky=W)
- sv = IntVar(value=self.settings.get("font_size", 11))
- ttk.Spinbox(dlg, from_=8, to=24, textvariable=sv, width=10).grid(row=2, column=1, padx=5, sticky=W)
- ttk.Label(dlg, text="Auto-save (sec):").grid(row=3, column=0, padx=15, pady=5, sticky=W)
- iv = IntVar(value=self.settings.get("auto_save_interval", 30))
- ttk.Spinbox(dlg, from_=10, to=300, textvariable=iv, width=10).grid(row=3, column=1, padx=5, sticky=W)
- ttk.Label(dlg, text="─" * 65, font=("Segoe UI", 8)).grid(row=4, column=0, columnspan=2, pady=12)
- ttk.Label(dlg, text="🔧 Interpreter Paths (Windows):", font=("Segoe UI", 9, "bold")).grid(row=5, column=0, columnspan=2, padx=15, pady=(0, 5), sticky=W)
- ttk.Label(dlg, text="AutoHotKey (.ahk):").grid(row=6, column=0, padx=15, pady=5, sticky=W)
- ahkv = StringVar(value=interpreters.get("autohotkey", r"C:\Program Files\AutoHotkey\AutoHotkey.exe"))
- ttk.Entry(dlg, textvariable=ahkv, width=45).grid(row=6, column=1, padx=5, pady=5)
- ttk.Button(dlg, text="📁", width=3, command=lambda: self._browse_exe(ahkv)).grid(row=6, column=2, padx=2)
- ttk.Label(dlg, text="AutoIt3 (.au3):").grid(row=7, column=0, padx=15, pady=5, sticky=W)
- au3v = StringVar(value=interpreters.get("autoit3", r"C:\Program Files\AutoIt3\AutoIt3.exe"))
- ttk.Entry(dlg, textvariable=au3v, width=45).grid(row=7, column=1, padx=5, pady=5)
- ttk.Button(dlg, text="📁", width=3, command=lambda: self._browse_exe(au3v)).grid(row=7, column=2, padx=2)
- ttk.Label(dlg, text="Python (.py):").grid(row=8, column=0, padx=15, pady=5, sticky=W)
- pyv = StringVar(value=interpreters.get("python", "python"))
- ttk.Entry(dlg, textvariable=pyv, width=45).grid(row=8, column=1, padx=5, pady=5)
- ttk.Button(dlg, text="📁", width=3, command=lambda: self._browse_exe(pyv)).grid(row=8, column=2, padx=2)
- ttk.Label(dlg, text="Node.js (.js):").grid(row=9, column=0, padx=15, pady=5, sticky=W)
- nodev = StringVar(value=interpreters.get("node", "node"))
- ttk.Entry(dlg, textvariable=nodev, width=45).grid(row=9, column=1, padx=5, pady=5)
- def auto_detect():
- detected = []
- for p in [r"C:\Program Files\AutoHotkey\AutoHotkey.exe", r"C:\Program Files (x86)\AutoHotkey\AutoHotkey.exe"]:
- if os.path.exists(p):
- ahkv.set(p)
- detected.append("✓ AutoHotKey")
- break
- for p in [r"C:\Program Files\AutoIt3\AutoIt3.exe", r"C:\Program Files (x86)\AutoIt3\AutoIt3.exe"]:
- if os.path.exists(p):
- au3v.set(p)
- detected.append("✓ AutoIt3")
- break
- if detected:
- self.log(f"🔍 Auto-detected: {', '.join(detected)}")
- messagebox.showinfo("Auto-Detect", "Found:\n" + "\n".join(detected))
- else:
- messagebox.showwarning("Auto-Detect", "Browse manually for interpreters")
- ttk.Button(dlg, text="🔍 Auto-Detect", command=auto_detect).grid(row=10, column=1, columnspan=2, pady=10, sticky=E)
- def apply():
- self.settings.set("theme", tv.get())
- self.settings.set("font_family", fv.get())
- self.settings.set("font_size", sv.get())
- self.settings.set("auto_save_interval", iv.get())
- ints = self.settings.get("interpreters", {})
- ints.update({"autohotkey": ahkv.get(), "autoit3": au3v.get(), "python": pyv.get(), "node": nodev.get()})
- self.settings.set("interpreters", ints)
- if self.settings.get("theme") != self.tabs.get(self.current_file, {}).get("theme"):
- for fp in list(self.tabs.keys()):
- content = self.tabs[fp]["text"].get("1.0", END)
- self.notebook.forget(self.tabs[fp]["frame"])
- self.open_tab(fp)
- if fp in self.tabs:
- self.tabs[fp]["text"].insert("1.0", content)
- if self.auto_timer:
- try:
- self.root.after_cancel(self.auto_timer)
- except Exception:
- pass
- self._start_auto_save()
- self.log(f"✅ Settings saved | Theme: {tv.get().upper()}")
- dlg.destroy()
- messagebox.showinfo("Applied", f"Settings saved!\n🎨 Theme: {tv.get().upper()}\nFont changes apply to new tabs")
- bf = Frame(dlg)
- bf.grid(row=11, column=0, columnspan=3, pady=15)
- ttk.Button(bf, text="Apply & Close", command=apply).pack(padx=10)
- ttk.Button(bf, text="Cancel", command=dlg.destroy).pack(padx=10)
- def _browse_exe(self, string_var):
- """Open file dialog to select executable"""
- path = filedialog.askopenfilename(title="Select Executable", filetypes=[("Executables", "*.exe"), ("All", "*.*")],
- initialdir="C:\\Program Files")
- if path:
- string_var.set(path.replace("/", "\\"))
- def about(self):
- """Show about dialog"""
- messagebox.showinfo(
- "About",
- f"{APP_NAME}\n\n"
- "✅ Python 3.7.9+ | Windows 7/10/11 32/64-bit\n"
- "✅ 100% Standard Library | Zero Dependencies\n"
- "✅ 175+ File Extensions (Code + Media + EXE)\n"
- " 📄 Code: Python, JS, HTML, CSS, Java, C++, etc. (70+)\n"
- " 🎵 Audio: MP3, WAV, FLAC, AAC, OGG, etc. (25+)\n"
- " 🎬 Video: MP4, AVI, MKV, MOV, WebM, etc. (40+)\n"
- " 🖼️ Images: JPG, PNG, GIF, BMP, SVG, WebP, etc. (35+)\n"
- " 🚀 EXE: EXE, MSI, MSU, COM, SCR (5+)\n"
- "✅ PERFECT Scroll Sync: Line Numbers + Code\n"
- "✅ Dark/Light Theme Toggle\n"
- "✅ Auto-Detect Interpreters (AutoHotKey, AutoIt3)\n"
- "✅ Export to ZIP (Code + Media + EXE)\n"
- "✅ Open Media/EXE in Default Apps\n"
- "✅ Searchable Files | Rename Tabs\n\n"
- "🔧 Settings → Interpreters: Configure custom paths\n\n"
- f"© 2026 Najeeb Dev | Built with ❤️"
- )
- def on_close(self):
- """Clean up resources and close application gracefully"""
- # 1. Cancel all timers
- for timer in [self.auto_timer, self.hl_timer]:
- if timer is not None:
- try:
- self.root.after_cancel(timer)
- except TclError:
- pass
- except Exception:
- pass
- # 2. Terminate any running process
- if self.run_proc is not None:
- try:
- if self.run_proc.poll() is None:
- self.run_proc.terminate()
- try:
- self.run_proc.wait(timeout=2)
- except subprocess.TimeoutExpired:
- try:
- self.run_proc.kill()
- except Exception:
- pass
- except Exception:
- pass
- finally:
- self.run_proc = None
- # 3. Save settings
- try:
- self.settings.save()
- except Exception:
- pass
- # 4. Clean up temporary directory
- try:
- if os.path.exists(TEMP_DIR):
- shutil.rmtree(TEMP_DIR, ignore_errors=True)
- except Exception:
- pass
- # 5. Destroy root window
- try:
- if self.root.winfo_exists():
- self.root.destroy()
- except TclError:
- pass
- except Exception:
- pass
- def main():
- root = Tk()
- app = UltraProMaxV3(root)
- root.mainloop()
- if __name__ == "__main__":
- main()
Advertisement
Comments
-
- ⭐ Changelly Profit Exploit Documentation ⭐
- https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=esxfuzgwds
- This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
- The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
- Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment