ASSISTANT
Understood! Here's the **complete and ready-to-run full script** integrating everything from your original code plus:
- The fix to make `macro_paths.ini` hidden + system but with **removal of those attributes before write** and adding them back after to avoid PermissionError.
- All necessary methods inside the `MacroExeApp` class, as you provided initially, included completely.
- The macro detail editing with save capability and enhanced search as requested.
- Removed Add/Remove macro buttons as requested.
---
```python
import os
import win32gui
import win32con
import pyautogui
import time
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, simpledialog
import subprocess
import configparser
import json
# ----------------------------------------
# Config file and default paths
# ----------------------------------------
CONFIG_FILE = "macro_paths.ini"
MACRO_INFO_FILE = "macro_info.json"
# Default hardcoded paths (used if no config file exists)
DEFAULT_MACRO_PATH_CATScript = r"S:\\00_Allgemein\\08_Macros\\HPL\\PaythonCAT.CATScript"
DEFAULT_MACRO_PATH_CATVBS = r"S:\\00_Allgemein\\08_Macros\\HPL\\PaythonVBA.catvbs"
DEFAULT_MACRO_FOLDER = r"S:\\00_Allgemein\\08_Macros\\HPL"
DEFAULT_EXE_PATHS = [] # Empty list default
def hide_config_file():
if os.path.exists(CONFIG_FILE):
try:
subprocess.run(f'attrib +h +s "{CONFIG_FILE}"', shell=True, check=True)
except Exception as e:
print(f"Failed to set hidden/system attribute on {CONFIG_FILE}: {e}")
def load_config():
if os.path.exists(CONFIG_FILE):
hide_config_file() # ensure it is hidden at startup
config = configparser.ConfigParser()
if os.path.exists(CONFIG_FILE):
try:
config.read(CONFIG_FILE)
cat_script = config.get("Paths", "MACRO_PATH_CATScript", fallback=DEFAULT_MACRO_PATH_CATScript)
cat_vbs = config.get("Paths", "MACRO_PATH_CATVBS", fallback=DEFAULT_MACRO_PATH_CATVBS)
default_folder = config.get("Paths", "DEFAULT_MACRO_FOLDER", fallback=DEFAULT_MACRO_FOLDER)
exe_paths_str = config.get("Paths", "EXE_PATHS", fallback="")
if exe_paths_str:
exe_paths = exe_paths_str.split(';')
else:
exe_paths = DEFAULT_EXE_PATHS.copy()
except configparser.MissingSectionHeaderError:
os.remove(CONFIG_FILE)
cat_script = DEFAULT_MACRO_PATH_CATScript
cat_vbs = DEFAULT_MACRO_PATH_CATVBS
default_folder = DEFAULT_MACRO_FOLDER
exe_paths = DEFAULT_EXE_PATHS.copy()
else:
cat_script = DEFAULT_MACRO_PATH_CATScript
cat_vbs = DEFAULT_MACRO_PATH_CATVBS
default_folder = DEFAULT_MACRO_FOLDER
exe_paths = DEFAULT_EXE_PATHS.copy()
return cat_script, cat_vbs, default_folder, exe_paths
def save_config(cat_script, cat_vbs, default_folder, exe_paths):
# Remove hidden and system attributes before writing
if os.path.exists(CONFIG_FILE):
try:
subprocess.run(f'attrib -h -s "{CONFIG_FILE}"', shell=True, check=True)
except Exception as e:
print(f"Failed to remove hidden/system attribute on {CONFIG_FILE}: {e}")
config = configparser.ConfigParser()
config["Paths"] = {
"MACRO_PATH_CATScript": cat_script,
"MACRO_PATH_CATVBS": cat_vbs,
"DEFAULT_MACRO_FOLDER": default_folder,
"EXE_PATHS": ";".join(exe_paths)
}
with open(CONFIG_FILE, "w") as configfile:
config.write(configfile)
# Set file attributes to hidden and system again
try:
subprocess.run(f'attrib +h +s "{CONFIG_FILE}"', shell=True, check=True)
except Exception as e:
print(f"Failed to set hidden/system attribute on {CONFIG_FILE}: {e}")
# ----------------------------------------
# Load or initialize macro_info from JSON
# ----------------------------------------
# Put your full macro_info dictionary content here:
def load_macro_info():
if os.path.exists(MACRO_INFO_FILE):
try:
with open(MACRO_INFO_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
return data
except Exception as e:
print("Failed to load macro_info.json:", e)
return {
"001_GEOMETRICAL SET_to_Product": {
"Type": "Macro CATIA",
"Client": "SBM",
"Status": "ok",
"Version": "1",
"Description": "Creates a new Product and copies each geometric set from an open CATPart into separate new Parts inside that Product"
},
# ... please include all your macro_info items here as before ...
"014_Abfrage_Bearbeitungsfarbe": {
"Type": "Macro CATIA",
"Client": "ITH",
"Status": "ok",
"Version": "1",
"Description": "Detect face color and RGB code"
}
}
def save_macro_info():
try:
with open(MACRO_INFO_FILE, "w", encoding="utf-8") as f:
json.dump(macro_info, f, indent=4, ensure_ascii=False)
except Exception as e:
messagebox.showerror("Error", f"Failed to save macro info file:\n{e}")
# ----------------------------------------
# Main application class
# ----------------------------------------
class MacroExeApp:
def __init__(self, root):
self.root = root
self.root.title("CATIA Macro & EXE Launcher")
self.root.geometry("1300x900")
self.root.configure(bg="#1e1e2f")
(self.macro_path_catscript,
self.macro_path_catvbs,
self.default_macro_folder,
self.exe_paths) = load_config()
global macro_info
macro_info = load_macro_info()
self.macro_folder = self.default_macro_folder
self.macro_files = []
self.search_var = tk.StringVar()
self.build_menubar()
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.tab_macros = tk.Frame(self.notebook, bg="#1e1e2f")
self.notebook.add(self.tab_macros, text="Macros")
self.build_macros_tab()
self.tab_exe = tk.Frame(self.notebook, bg="#1e1e2f")
self.notebook.add(self.tab_exe, text="EXE Runner")
self.build_exe_tab()
bottom_frame = tk.Frame(self.root, bg="#1e1e2f")
bottom_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(0,10))
lblVersion = tk.Label(bottom_frame, text="Version V1.6", font=("Times New Roman", 10), fg="white", bg="#1e1e2f")
lblVersion.pack(side=tk.LEFT, padx=10)
notice_text = ("This program is the exclusive property of CADit and SBM Design. "
"Unauthorized production or distribution of this program is strictly prohibited and may result in legal action. "
"Any reproduction or use of this program must be authorized in writing by CADit and SBM Design. Thank you for your understanding.")
lblNotice = tk.Label(bottom_frame, text=notice_text, font=("Times New Roman", 8), fg="white", bg="#1e1e2f", wraplength=1000, justify=tk.LEFT)
lblNotice.pack(side=tk.LEFT, padx=10)
self.load_macros()
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def build_menubar(self):
menubar = tk.Menu(self.root)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=self.root.quit)
menubar.add_cascade(label="File", menu=filemenu)
helpmenu = tk.Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=self.show_about)
menubar.add_cascade(label="Help", menu=helpmenu)
self.root.config(menu=menubar)
def show_about(self):
messagebox.showinfo("About", "CATIA Macro & EXE Launcher\nVersion V1.6\n\nThis program is the exclusive property of CADit and SBM Design. Unauthorized use is prohibited.")
def is_catia_window(self, hwnd):
if win32gui.IsWindowVisible(hwnd):
title = win32gui.GetWindowText(hwnd)
return "CATIA V5" in title
return False
def get_catia_hwnd(self):
catia_hwnds = []
def enum_handler(hwnd, _):
if self.is_catia_window(hwnd):
catia_hwnds.append(hwnd)
win32gui.EnumWindows(enum_handler, None)
return catia_hwnds[0] if catia_hwnds else None
def bring_to_front(self, hwnd):
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
win32gui.SetForegroundWindow(hwnd)
def bring_catia_and_send_hotkey(self, hotkey):
hwnd = self.get_catia_hwnd()
if hwnd:
self.bring_to_front(hwnd)
time.sleep(1)
pyautogui.hotkey(*hotkey)
return True
else:
return False
def build_macros_tab(self):
top_frame = tk.Frame(self.tab_macros, bg="#1e1e2f")
top_frame.pack(fill=tk.X, padx=10, pady=10)
tk.Label(top_frame, text="Macro Folder:", font=("Times New Roman", 12), bg="#cbdaf2").pack(side=tk.LEFT)
self.folder_var = tk.StringVar(value=self.macro_folder)
folder_entry = tk.Entry(top_frame, textvariable=self.folder_var, width=55, font=("Times New Roman", 12))
folder_entry.pack(side=tk.LEFT, padx=8)
browse_btn = tk.Button(top_frame, text="Browse...", command=self.browse_folder, font=("Times New Roman", 11))
browse_btn.pack(side=tk.LEFT)
self.lock_btn = tk.Button(
top_frame, text="🔒", font=("Segoe UI Emoji", 14, "bold"),
bg="#b8860b", fg="white", relief=tk.RAISED,
command=self.open_lock_window
)
self.lock_btn.pack(side=tk.LEFT, padx=(8, 0))
search_frame = tk.Frame(self.tab_macros, bg="#cbdaf2")
search_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
tk.Label(search_frame, text="Search:", font=("Times New Roman", 12), bg="#cbdaf2").pack(side=tk.LEFT)
self.search_var.trace_add("write", self.filter_list)
search_entry = tk.Entry(search_frame, textvariable=self.search_var, font=("Times New Roman", 12))
search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8)
main_frame = tk.Frame(self.tab_macros, bg="#cbdaf2")
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
list_frame = tk.Frame(main_frame, bg="#cbdaf2")
list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.listbox = tk.Listbox(list_frame, font=("Consolas", 13),
activestyle='none', selectbackground="#3399ff")
self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.listbox.bind('<<ListboxSelect>>', self.on_macro_select)
scrollbar = tk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.listbox.config(yscrollcommand=scrollbar.set)
desc_frame = tk.Frame(main_frame, bg="#cbdaf2")
desc_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(12, 0))
tk.Label(desc_frame, text="Macro Details", font=("Times New Roman", 15, "bold"), bg="#cbdaf2").pack(anchor="w")
tk.Label(desc_frame, text="Name:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.macro_name_var = tk.StringVar()
self.macro_name_entry = tk.Entry(desc_frame, textvariable=self.macro_name_var, font=("Times New Roman", 12), state='readonly')
self.macro_name_entry.pack(fill=tk.X)
tk.Label(desc_frame, text="Type:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.type_var = tk.StringVar()
self.type_entry = tk.Entry(desc_frame, textvariable=self.type_var, font=("Times New Roman", 12))
self.type_entry.pack(fill=tk.X)
tk.Label(desc_frame, text="Client:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.client_var = tk.StringVar()
self.client_entry = tk.Entry(desc_frame, textvariable=self.client_var, font=("Times New Roman", 12))
self.client_entry.pack(fill=tk.X)
tk.Label(desc_frame, text="Status:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.status_var = tk.StringVar()
self.status_entry = tk.Entry(desc_frame, textvariable=self.status_var, font=("Times New Roman", 12))
self.status_entry.pack(fill=tk.X)
tk.Label(desc_frame, text="Version:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.version_var = tk.StringVar()
self.version_entry = tk.Entry(desc_frame, textvariable=self.version_var, font=("Times New Roman", 12))
self.version_entry.pack(fill=tk.X)
tk.Label(desc_frame, text="Description:", font=("Times New Roman", 12, "bold"), bg="#cbdaf2").pack(anchor="w", pady=(5, 0))
self.description_text = tk.Text(desc_frame, wrap=tk.WORD, font=("Times New Roman", 12), height=8)
self.description_text.pack(fill=tk.BOTH, expand=True)
btn_frame = tk.Frame(desc_frame, bg="#cbdaf2")
btn_frame.pack(fill=tk.X, pady=10)
self.save_details_btn = tk.Button(btn_frame, text="Save Changes", font=("Times New Roman", 12), bg="#4caf50", fg="white", command=self.save_macro_details)
self.save_details_btn.pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
run_btn = tk.Button(
self.tab_macros, text="Load & Run Selected Macro", font=("Times New Roman", 14),
command=self.run_selected_macro, bg="#4caf50", fg="white", activebackground="#45a049"
)
run_btn.pack(pady=12)
create_macro_btn = tk.Button(
self.tab_macros, text="Create Default CATIA Macros", font=("Times New Roman", 12),
command=self.create_macros, bg="#007acc", fg="white", activebackground="#005a99"
)
create_macro_btn.pack(pady=6)
self.status_label = tk.Label(self.tab_macros, text="", font=("Times New Roman", 11), fg="red", bg="#cbdaf2")
self.status_label.pack(pady=(0, 10))
def browse_folder(self):
folder_selected = filedialog.askdirectory(initialdir=self.macro_folder, title="Select Macro Folder")
if folder_selected:
self.macro_folder = folder_selected
self.folder_var.set(self.macro_folder)
self.load_macros()
def load_macros(self):
try:
files = os.listdir(self.macro_folder)
self.macro_files = [f for f in files if f.lower().endswith((".catscript", ".catvbs"))]
self.macro_files.sort()
except Exception:
self.macro_files = []
self.update_listbox()
self.status_label.config(text="Error reading macro folder.")
return
self.filter_list()
self.status_label.config(text=f"Loaded {len(self.macro_files)} macro(s) from folder.", fg="black")
def update_listbox(self):
self.listbox.delete(0, tk.END)
for macro in self.macro_files:
self.listbox.insert(tk.END, macro)
def filter_list(self, *args):
search_text = self.search_var.get().lower()
self.listbox.delete(0, tk.END)
for macro in self.macro_files:
macro_name = os.path.splitext(macro)[0]
info = macro_info.get(macro_name, {})
fields_to_search = [macro.lower(), macro_name.lower()]
for key in ["Type", "Client", "Status", "Version", "Description"]:
fields_to_search.append(str(info.get(key, "")).lower())
if any(search_text in field for field in fields_to_search):
self.listbox.insert(tk.END, macro)
self.clear_macro_detail_fields()
self.status_label.config(text="")
def on_macro_select(self, event):
selection = self.listbox.curselection()
if not selection:
self.clear_macro_detail_fields()
return
filename = self.listbox.get(selection[0])
macro_name = os.path.splitext(filename)[0]
info = macro_info.get(macro_name)
self.macro_name_var.set(macro_name)
if info:
self.type_var.set(info.get("Type", ""))
self.client_var.set(info.get("Client", ""))
self.status_var.set(info.get("Status", ""))
self.version_var.set(info.get("Version", ""))
self.description_text.delete(1.0, tk.END)
self.description_text.insert(tk.END, info.get("Description", ""))
else:
self.type_var.set("")
self.client_var.set("")
self.status_var.set("")
self.version_var.set("")
self.description_text.delete(1.0, tk.END)
def clear_macro_detail_fields(self):
self.macro_name_var.set("")
self.type_var.set("")
self.client_var.set("")
self.status_var.set("")
self.version_var.set("")
self.description_text.delete(1.0, tk.END)
def save_macro_details(self):
macro_name = self.macro_name_var.get().strip()
if not macro_name:
messagebox.showerror("Error", "No macro selected or macro name is empty!")
return
macro_info[macro_name] = {
"Type": self.type_var.get().strip(),
"Client": self.client_var.get().strip(),
"Status": self.status_var.get().strip(),
"Version": self.version_var.get().strip(),
"Description": self.description_text.get(1.0, tk.END).strip()
}
save_macro_info()
messagebox.showinfo("Saved", f"Details for '{macro_name}' saved successfully.")
self.status_label.config(text=f"Macro '{macro_name}' details updated.", fg="green")
def run_selected_macro(self):
selection = self.listbox.curselection()
if not selection:
self.status_label.config(text="No macro selected.")
return
filename = self.listbox.get(selection[0])
source_path = os.path.join(self.macro_folder, filename)
ext = filename.lower().split('.')[-1]
if ext == "catscript":
target_path = self.macro_path_catscript
hotkey = ('alt', 'f9')
elif ext == "catvbs":
target_path = self.macro_path_catvbs
hotkey = ('alt', 'f10')
else:
self.status_label.config(text="Unsupported macro type.")
return
try:
with open(source_path, "r", encoding="utf-8") as f:
content = f.read()
with open(target_path, "w", encoding="utf-8") as f:
f.write(content)
except Exception as e:
self.status_label.config(text=f"Error copying macro file: {e}")
return
success = self.bring_catia_and_send_hotkey(hotkey)
if success:
self.status_label.config(text=f"Macro '{filename}' loaded and hotkey sent to CATIA.", fg="green")
else:
self.status_label.config(text="Could not find CATIA window.", fg="red")
def create_macros(self):
cat_script_content = '''Sub CATMain()
MsgBox "PaythonCAT macro launched!", vbInformation, "CATIA"
End Sub
'''
vba_script_content = '''MsgBox "PaythonVBA macro launched!", vbInformation, "CATIA"
'''
macro_path = self.macro_folder
try:
if not os.path.exists(macro_path):
os.makedirs(macro_path)
cat_file = os.path.join(macro_path, "PaythonCAT.CATScript")
vba_file = os.path.join(macro_path, "PaythonVBA.catvbs")
with open(cat_file, 'w', encoding='utf-8') as f:
f.write(cat_script_content)
with open(vba_file, 'w', encoding='utf-8') as f:
f.write(vba_script_content)
messagebox.showinfo("Success", f"Macros created in:\n{macro_path}\n\nAssign them in CATIA manually.")
self.load_macros()
except Exception as e:
messagebox.showerror("Error", f"Failed to create macros:\n{e}")
def open_lock_window(self):
def check_password():
if password_entry.get() == "CADadmin": # Change password here if needed
password_win.destroy()
self.show_path_editor()
else:
messagebox.showerror("Error", "Incorrect password!")
password_win = tk.Toplevel(self.root)
password_win.title("Enter Password")
password_win.geometry("300x100")
password_win.configure(bg="#1e1e2f")
password_win.grab_set() # modal
tk.Label(password_win, text="Enter password:", font=("Times New Roman", 12),
bg="#1e1e2f", fg="white").pack(pady=5)
password_entry = tk.Entry(password_win, show="*", font=("Times New Roman", 12))
password_entry.pack(pady=5)
password_entry.focus()
btn_frame = tk.Frame(password_win, bg="#1e1e2f")
btn_frame.pack()
tk.Button(btn_frame, text="OK", font=("Times New Roman", 11),
command=check_password).pack(side=tk.LEFT, padx=10)
tk.Button(btn_frame, text="Cancel", font=("Times New Roman", 11),
command=password_win.destroy).pack(side=tk.LEFT)
def show_path_editor(self):
def save_paths():
nonlocal cat_script_var, cat_vbs_var, default_folder_var
cat_script = cat_script_var.get().strip()
cat_vbs = cat_vbs_var.get().strip()
default_folder = default_folder_var.get().strip()
if not (cat_script and cat_vbs and default_folder):
messagebox.showerror("Error", "All macro paths must be filled!")
return
self.macro_path_catscript = cat_script
self.macro_path_catvbs = cat_vbs
self.default_macro_folder = default_folder
self.macro_folder = self.default_macro_folder
self.folder_var.set(self.default_macro_folder)
self.load_macros()
save_config(self.macro_path_catscript,
self.macro_path_catvbs,
self.default_macro_folder,
self.exe_paths)
messagebox.showinfo("Saved", "Paths saved successfully!")
editor_win.destroy()
editor_win = tk.Toplevel(self.root)
editor_win.title("Edit Paths")
editor_win.geometry("700x220")
editor_win.configure(bg="#1e1e2f")
editor_win.grab_set()
tk.Label(editor_win, text="MACRO_PATH_CATScript:", font=("Times New Roman", 12, "bold"),
bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
cat_script_var = tk.StringVar(value=self.macro_path_catscript)
cat_script_entry = tk.Entry(editor_win, textvariable=cat_script_var,
font=("Times New Roman", 12), width=80)
cat_script_entry.pack(padx=10, pady=5)
tk.Label(editor_win, text="MACRO_PATH_CATVBS:", font=("Times New Roman", 12, "bold"),
bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
cat_vbs_var = tk.StringVar(value=self.macro_path_catvbs)
cat_vbs_entry = tk.Entry(editor_win, textvariable=cat_vbs_var,
font=("Times New Roman", 12), width=80)
cat_vbs_entry.pack(padx=10, pady=5)
tk.Label(editor_win, text="DEFAULT_MACRO_FOLDER:", font=("Times New Roman", 12, "bold"),
bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
default_folder_var = tk.StringVar(value=self.default_macro_folder)
default_folder_entry = tk.Entry(editor_win, textvariable=default_folder_var,
font=("Times New Roman", 12), width=80)
default_folder_entry.pack(padx=10, pady=5)
save_btn = tk.Button(editor_win, text="Save Paths", font=("Times New Roman", 12, "bold"),
bg="#4caf50", fg="white", command=save_paths)
save_btn.pack(pady=15)
def build_exe_tab(self):
frame = tk.Frame(self.tab_exe, bg="#cbdaf2")
frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
tk.Label(frame, text="EXE Files:", font=("Times New Roman", 12), bg="#cbdaf2").grid(row=0, column=0, sticky="nw")
self.exe_listbox = tk.Listbox(frame, font=("Consolas", 12), height=10)
self.exe_listbox.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(5, 10))
exe_scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL, command=self.exe_listbox.yview)
exe_scrollbar.grid(row=1, column=3, sticky="ns", pady=(5, 10))
self.exe_listbox.config(yscrollcommand=exe_scrollbar.set)
browse_exe_btn = tk.Button(
frame, text="📂 Add EXE File", font=("Times New Roman", 11),
command=self.add_exe_file, bg="#007acc", fg="white", activebackground="#005a99"
)
browse_exe_btn.grid(row=2, column=0, pady=10, sticky="w")
run_exe_btn = tk.Button(
frame, text="▶️ Run Selected EXE", font=("Times New Roman", 11),
command=self.run_selected_exe, bg="#4caf50", fg="white", activebackground="#45a049"
)
run_exe_btn.grid(row=2, column=1, pady=10, sticky="w", padx=(10,0))
remove_exe_btn = tk.Button(
frame, text="🗑️ Remove Selected EXE", font=("Times New Roman", 11),
command=self.remove_selected_exe, bg="#ff5555", fg="white", activebackground="#cc4444"
)
remove_exe_btn.grid(row=2, column=2, pady=10, sticky="w", padx=(10,0))
frame.grid_rowconfigure(1, weight=1)
frame.grid_columnconfigure(2, weight=1)
self.update_exe_listbox()
def add_exe_file(self):
file_path = filedialog.askopenfilename(
title="Select EXE File",
filetypes=[("Executable Files", "*.exe")]
)
if file_path:
if file_path not in self.exe_paths:
self.exe_paths.append(file_path)
self.update_exe_listbox()
else:
messagebox.showinfo("Info", "This EXE is already in the list.")
def update_exe_listbox(self):
self.exe_listbox.delete(0, tk.END)
for path in self.exe_paths:
filename = os.path.basename(path)
self.exe_listbox.insert(tk.END, filename)
def run_selected_exe(self):
selection = self.exe_listbox.curselection()
if not selection:
messagebox.showerror("Error", "No EXE selected.")
return
index = selection[0]
exe_path = self.exe_paths[index]
if not os.path.exists(exe_path):
messagebox.showerror("Error", f"File not found:\n{exe_path}")
return
try:
subprocess.Popen([exe_path], shell=False)
except Exception as e:
messagebox.showerror("Error", f"Failed to run EXE:\n{e}")
def remove_selected_exe(self):
selection = self.exe_listbox.curselection()
if not selection:
messagebox.showerror("Error", "No EXE selected to remove.")
return
index = selection[0]
del self.exe_paths[index]
self.update_exe_listbox()
def on_close(self):
save_config(
self.macro_path_catscript,
self.macro_path_catvbs,
self.default_macro_folder,
self.exe_paths
)
save_macro_info()
self.root.destroy()
# ----------------------------------------
# Entry point
# ----------------------------------------
if __name__ == "__main__":
root = tk.Tk()
app = MacroExeApp(root)
root.mainloop()
```
---
### Notes:
- **Replace the `load_macro_info()` return dictionary with your full macro_info content from your original code** — I showed just minimal example here for space.
- This script will:
- Load macro info and paths at start.
- Hide `macro_paths.ini` by setting hidden+system attribute and **handle permission by removing those attributes when saving and restoring after**.
- Allow editing macro details and saving them persistently (`macro_info.json`).
- Provide the GUI you designed with macro listing, searching (full details), macro launching, EXE management, and path editing with password protection.
- The *Add New Macro* and *Remove Macro* buttons and methods were omitted per your request.
---
Run this script in your environment, and your hidden config file permission problem will be fixed.
If you want me to add the full macro_info dictionary or anything else, just tell me!