import tkinter as tk from tkinter import filedialog, simpledialog import json import socket import threading import base64 import os import pyautogui import subprocess from PIL import Image, ImageTk class MacroServer: def __init__(self, root): self.root = root self.root.title("MacroPad Server") self.root.geometry("800x600") self.macros = {} self.load_macros() # Create GUI self.create_widgets() # Start server thread self.server_running = True self.server_thread = threading.Thread(target=self.run_server) self.server_thread.daemon = True self.server_thread.start() def create_widgets(self): # Toolbar toolbar = tk.Frame(self.root) toolbar.pack(side=tk.TOP, fill=tk.X) tk.Button(toolbar, text="Add Macro", command=self.add_macro).pack(side=tk.LEFT, padx=5, pady=5) tk.Button(toolbar, text="Edit Macro", command=self.edit_macro).pack(side=tk.LEFT, padx=5, pady=5) tk.Button(toolbar, text="Delete Macro", command=self.delete_macro).pack(side=tk.LEFT, padx=5, pady=5) # Macro list frame list_frame = tk.Frame(self.root) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Scrollable canvas for macros self.canvas = tk.Canvas(list_frame) scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=self.canvas.yview) self.scrollable_frame = tk.Frame(self.canvas) self.scrollable_frame.bind( "", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")) ) self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.canvas.configure(yscrollcommand=scrollbar.set) self.canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") self.display_macros() # Status bar self.status_var = tk.StringVar() self.status_var.set("Server ready. Waiting for connections...") status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) def load_macros(self): try: if os.path.exists("macros.json"): with open("macros.json", "r") as f: self.macros = json.load(f) except Exception as e: print(f"Error loading macros: {e}") self.macros = {} def save_macros(self): try: with open("macros.json", "w") as f: json.dump(self.macros, f) except Exception as e: print(f"Error saving macros: {e}") def display_macros(self): # Clear existing macros for widget in self.scrollable_frame.winfo_children(): widget.destroy() # Display macros in a grid row, col = 0, 0 max_cols = 3 for macro_id, macro in self.macros.items(): frame = tk.Frame(self.scrollable_frame, bd=2, relief=tk.RAISED, padx=5, pady=5) frame.grid(row=row, column=col, padx=10, pady=10, sticky="nsew") # Load image if exists if "image_data" in macro and macro["image_data"]: try: image_data = base64.b64decode(macro["image_data"]) image = Image.open(io.BytesIO(image_data)) image = image.resize((64, 64), Image.LANCZOS) photo = ImageTk.PhotoImage(image) label = tk.Label(frame, image=photo) label.image = photo # Keep a reference label.pack() except Exception as e: print(f"Error displaying image: {e}") tk.Label(frame, text="[No Image]", width=8, height=4).pack() else: tk.Label(frame, text="[No Image]", width=8, height=4).pack() tk.Label(frame, text=macro["name"]).pack() col += 1 if col >= max_cols: col = 0 row += 1 def add_macro(self): # Create a dialog to add a new macro dialog = tk.Toplevel(self.root) dialog.title("Add Macro") dialog.geometry("400x300") dialog.transient(self.root) tk.Label(dialog, text="Macro Name:").grid(row=0, column=0, padx=5, pady=5, sticky="w") name_entry = tk.Entry(dialog, width=30) name_entry.grid(row=0, column=1, padx=5, pady=5) tk.Label(dialog, text="Type:").grid(row=1, column=0, padx=5, pady=5, sticky="w") type_var = tk.StringVar(value="text") tk.Radiobutton(dialog, text="Text", variable=type_var, value="text").grid(row=1, column=1, sticky="w") tk.Radiobutton(dialog, text="Application", variable=type_var, value="app").grid(row=2, column=1, sticky="w") tk.Label(dialog, text="Command/Text:").grid(row=3, column=0, padx=5, pady=5, sticky="w") command_text = tk.Text(dialog, width=30, height=5) command_text.grid(row=3, column=1, padx=5, pady=5) image_path = tk.StringVar() tk.Label(dialog, text="Image:").grid(row=4, column=0, padx=5, pady=5, sticky="w") tk.Entry(dialog, textvariable=image_path, width=30).grid(row=4, column=1, padx=5, pady=5) tk.Button(dialog, text="Browse...", command=lambda: image_path.set(filedialog.askopenfilename( filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")]))).grid(row=4, column=2) def save_macro(): name = name_entry.get().strip() if not name: tk.messagebox.showerror("Error", "Macro name is required") return macro_type = type_var.get() command = command_text.get("1.0", tk.END).strip() # Generate a unique ID macro_id = str(len(self.macros) + 1) # Process image image_data = "" img_path = image_path.get() if img_path: try: with open(img_path, "rb") as img_file: image_data = base64.b64encode(img_file.read()).decode('utf-8') except Exception as e: print(f"Error processing image: {e}") # Create macro self.macros[macro_id] = { "name": name, "type": macro_type, "command": command, "image_data": image_data } self.save_macros() self.display_macros() dialog.destroy() tk.Button(dialog, text="Save", command=save_macro).grid(row=5, column=0, padx=5, pady=20) tk.Button(dialog, text="Cancel", command=dialog.destroy).grid(row=5, column=1, padx=5, pady=20) def edit_macro(self): # To be implemented - similar to add_macro but pre-fills fields with existing data pass def delete_macro(self): # To be implemented - offers a selection and deletes the chosen macro pass def execute_macro(self, macro_id): if macro_id not in self.macros: return False macro = self.macros[macro_id] try: if macro["type"] == "text": pyautogui.typewrite(macro["command"]) elif macro["type"] == "app": subprocess.Popen(macro["command"], shell=True) return True except Exception as e: print(f"Error executing macro: {e}") return False def run_server(self): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind(('0.0.0.0', 40000)) server_socket.listen(5) while self.server_running: client_socket, address = server_socket.accept() self.status_var.set(f"Client connected from {address}") client_thread = threading.Thread(target=self.handle_client, args=(client_socket,)) client_thread.daemon = True client_thread.start() except Exception as e: print(f"Server error: {e}") finally: server_socket.close() def handle_client(self, client_socket): try: while self.server_running: data = client_socket.recv(1024).decode('utf-8') if not data: break try: request = json.loads(data) if request['action'] == 'get_macros': # Send all macros to client client_socket.send(json.dumps(self.macros).encode('utf-8')) elif request['action'] == 'execute': # Execute the specified macro success = self.execute_macro(request['macro_id']) client_socket.send(json.dumps({'success': success}).encode('utf-8')) except json.JSONDecodeError: print("Invalid JSON received") except Exception as e: print(f"Error handling client: {e}") finally: client_socket.close() def on_closing(self): self.server_running = False self.save_macros() self.root.destroy() if __name__ == "__main__": import io # Added for BytesIO root = tk.Tk() app = MacroServer(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop()