This update will need to get cleaned up, but for right now we are doing testing.
244 lines
8.5 KiB
Python
244 lines
8.5 KiB
Python
# 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 |