# Macro management and execution import json import os import uuid import pyautogui import subprocess import time from PIL import Image class MacroManager: def __init__(self, data_file, images_dir, app_dir): self.data_file = data_file self.images_dir = images_dir self.app_dir = app_dir self.macros = {} self.load_macros() def load_macros(self): """Load macros from JSON file""" try: if os.path.exists(self.data_file): with open(self.data_file, "r") as file: self.macros = json.load(file) except Exception as e: print(f"Error loading macros: {e}") self.macros = {} def save_macros(self): """Save macros to JSON file""" try: with open(self.data_file, "w") as file: json.dump(self.macros, file, indent=4) except Exception as e: print(f"Error saving macros: {e}") def get_sorted_macros(self, sort_by="name"): """Get macros sorted by specified criteria""" macro_list = list(self.macros.items()) if sort_by == "name": macro_list.sort(key=lambda x: x[1]["name"].lower()) elif sort_by == "type": macro_list.sort(key=lambda x: (x[1].get("type", ""), x[1]["name"].lower())) elif sort_by == "recent": # Sort by last_used timestamp if available, otherwise by name macro_list.sort(key=lambda x: (x[1].get("last_used", 0), x[1]["name"].lower()), reverse=True) return macro_list def filter_macros_by_tab(self, macro_list, tab_name): """Filter macros based on tab name""" if tab_name == "All": return macro_list filtered = [] for macro_id, macro in macro_list: # Check type match if macro.get("type", "").title() == tab_name: filtered.append((macro_id, macro)) # Check custom category match elif macro.get("category") == tab_name: filtered.append((macro_id, macro)) return filtered def get_unique_tabs(self): """Get list of unique tabs based on macro types and categories""" tabs = ["All"] unique_types = set() for macro in self.macros.values(): if macro.get("type"): unique_types.add(macro["type"].title()) if macro.get("category"): unique_types.add(macro["category"]) for tab_type in sorted(unique_types): if tab_type not in ["All"]: tabs.append(tab_type) return tabs def add_macro(self, name, macro_type, command, category="", modifiers=None, image_path=""): """Add a new macro""" if modifiers is None: modifiers = {"ctrl": False, "alt": False, "shift": False, "enter": False} macro_id = str(uuid.uuid4()) # Process image if provided image_path_reference = "" if image_path: try: # Generate unique filename for the image file_ext = os.path.splitext(image_path)[1].lower() unique_filename = f"{uuid.uuid4().hex}{file_ext}" dest_path = os.path.join(self.images_dir, unique_filename) # Resize image to max 256x256 with Image.open(image_path) as img: img.thumbnail((256, 256)) img.save(dest_path) # Store the relative path to the image image_path_reference = os.path.join("macro_images", unique_filename) except Exception as e: print(f"Error processing image: {e}") # Create macro data macro_data = { "name": name, "type": macro_type, "command": command, "image_path": image_path_reference, "modifiers": modifiers, "last_used": 0 } if category: macro_data["category"] = category self.macros[macro_id] = macro_data self.save_macros() return macro_id def update_macro(self, macro_id, name, macro_type, command, category="", modifiers=None, image_path=""): """Update an existing macro""" if macro_id not in self.macros: return False if modifiers is None: modifiers = {"ctrl": False, "alt": False, "shift": False, "enter": False} macro = self.macros[macro_id] # Keep the old image or update with new one image_path_reference = macro.get("image_path", "") if image_path: try: # Generate unique filename for the image file_ext = os.path.splitext(image_path)[1].lower() unique_filename = f"{uuid.uuid4().hex}{file_ext}" dest_path = os.path.join(self.images_dir, unique_filename) # Resize image to max 256x256 with Image.open(image_path) as img: img.thumbnail((256, 256)) img.save(dest_path) # Store the relative path to the image image_path_reference = os.path.join("macro_images", unique_filename) except Exception as e: print(f"Error processing image: {e}") # Update macro data updated_macro = { "name": name, "type": macro_type, "command": command, "image_path": image_path_reference, "modifiers": modifiers, "last_used": macro.get("last_used", 0) } if category: updated_macro["category"] = category self.macros[macro_id] = updated_macro self.save_macros() return True def delete_macro(self, macro_id): """Delete a macro""" if macro_id not in self.macros: return False macro = self.macros[macro_id] # Delete associated image file if it exists if "image_path" in macro and macro["image_path"]: try: img_path = os.path.join(self.app_dir, macro["image_path"]) if os.path.exists(img_path): os.remove(img_path) except Exception as e: print(f"Error removing image file: {e}") del self.macros[macro_id] self.save_macros() return True def execute_macro(self, macro_id): """Execute a macro by ID""" if macro_id not in self.macros: return False macro = self.macros[macro_id] # Update last_used timestamp for recent sorting self.macros[macro_id]["last_used"] = time.time() self.save_macros() try: if macro["type"] == "text": # Handle key modifiers modifiers = macro.get("modifiers", {}) # Add modifier keys if enabled if modifiers.get("ctrl", False): pyautogui.keyDown('ctrl') if modifiers.get("alt", False): pyautogui.keyDown('alt') if modifiers.get("shift", False): pyautogui.keyDown('shift') # Handle single character vs multi-character commands if str(macro["command"]) and len(str(macro["command"])) == 1: pyautogui.keyDown(macro["command"]) time.sleep(0.5) pyautogui.keyUp(macro["command"]) else: pyautogui.typewrite(macro["command"], interval=0.02) # Release modifier keys in reverse order if modifiers.get("shift", False): pyautogui.keyUp('shift') if modifiers.get("alt", False): pyautogui.keyUp('alt') if modifiers.get("ctrl", False): pyautogui.keyUp('ctrl') # Add Enter/Return if requested if modifiers.get("enter", False): pyautogui.press('enter') elif macro["type"] == "app": subprocess.Popen(macro["command"], shell=True) return True except Exception as e: print(f"Error executing macro: {e}") return False