import tkinter as tk
from tkinter import filedialog, ttk
import json
import socket
import threading
import os
import pyautogui
import subprocess
from PIL import Image, ImageTk
import pystray  # Add this import for system tray functionality
import uuid

class MacroPadServer:
    def __init__(self, root):
        self.root = root
        self.root.title("MacroPad Server")
        self.root.geometry("800x600")

        # Create the image Directory
        self.images_dir = "macro_images"
        os.makedirs(self.images_dir, exist_ok=True)

        # Set dark theme colors
        self.bg_color = "#2E2E2E"
        self.fg_color = "#FFFFFF"
        self.highlight_color = "#3D3D3D"
        self.accent_color = "#007BFF"
        self.button_bg = "#444444"
        self.button_fg = "#FFFFFF"

        # Apply theme
        self.root.configure(bg=self.bg_color)
        self.style = ttk.Style()
        self.style.theme_use('clam')

        # Configure styles
        self.style.configure('TFrame', background=self.bg_color)
        self.style.configure('TLabel', background=self.bg_color, foreground=self.fg_color)
        self.style.configure('TButton', background=self.button_bg, foreground=self.button_fg, borderwidth=0)
        self.style.map('TButton',
                   background=[('active', self.accent_color), ('disabled', self.highlight_color)],
                   foreground=[('active', self.button_fg), ('disabled', '#999999')])
        self.style.configure('TRadiobutton', background=self.bg_color, foreground=self.fg_color)
        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()

        # Set up system tray icon
        self.setup_tray_icon()

        # Capture the window close event
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        # Capture the window minimize event
        self.root.bind("<Unmap>", lambda e: self.minimize_to_tray() if self.root.state() == 'iconic' else None)

    def setup_tray_icon(self):
        # Create a simple icon for the tray
        icon_image = Image.new("RGB", (64, 64), color=self.accent_color)

        # Menu for the tray icon
        menu = (
            pystray.MenuItem('Show', self.show_window),
            pystray.MenuItem('Exit', self.exit_app)
        )

        self.icon = pystray.Icon("macropad_server", icon_image, "MacroPad Server", menu)

    def minimize_to_tray(self):
        self.root.withdraw()  # Hide window
        if not self.icon.visible:
            # Start the tray icon if it's not already running
            self.icon_thread = threading.Thread(target=self.icon.run)
            self.icon_thread.daemon = True
            self.icon_thread.start()

    def show_window(self, icon=None, item=None):
        self.root.deiconify()  # Show window
        self.root.lift()       # Bring window to front
        self.root.focus_force() # Focus the window
        if self.icon.visible:
            self.icon.stop()   # Remove icon

    def exit_app(self, icon=None, item=None):
        if self.icon.visible:
            self.icon.stop()
        self.server_running = False
        self.save_macros()
        self.root.destroy()

    def create_widgets(self):
        # Toolbar
        toolbar = tk.Frame(self.root, bg=self.highlight_color)
        toolbar.pack(side=tk.TOP, fill=tk.X)

        # Custom button style
        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'padx': 10,
                        'pady': 5, 'bd': 0, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'relief': tk.FLAT}
        tk.Button(toolbar, text="Add Macro", command=self.add_macro, **button_style).pack(side=tk.LEFT, padx=5, pady=5)
        tk.Button(toolbar, text="Edit Macro", command=self.edit_macro, **button_style).pack(side=tk.LEFT, padx=5, pady=5)
        tk.Button(toolbar, text="Delete Macro", command=self.delete_macro, **button_style).pack(side=tk.LEFT, padx=5, pady=5)
        tk.Button(toolbar, text="Minimize to Tray", command=self.minimize_to_tray, **button_style).pack(side=tk.LEFT, padx=5, pady=5)

        # Macro list frame
        list_frame = tk.Frame(self.root, bg=self.bg_color)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Scrollable canvas for macros
        self.canvas = tk.Canvas(list_frame, bg=self.bg_color, highlightthickness=0)
        scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = tk.Frame(self.canvas, bg=self.bg_color)

        self.scrollable_frame.bind(
            "<Configure>",
            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, bg=self.highlight_color, fg=self.fg_color)
        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,
                            bg=self.highlight_color)
            frame.grid(row=row, column=col, padx=10, pady=10, sticky="nsew")

            # Load image if exists
            if "image_path" in macro and macro["image_path"]:
                try:
                    img_path = macro["image_path"]
                    img = Image.open(img_path)
                    # Resize for display
                    img.thumbnail((64, 64))  # Keep aspect ratio for display
                    photo = ImageTk.PhotoImage(img)
                    label = tk.Label(frame, image=photo, bg=self.highlight_color)
                    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, bg=self.highlight_color, fg=self.fg_color).pack()
            else:
                tk.Label(frame, text="[No Image]", width=8, height=4, bg=self.highlight_color, fg=self.fg_color).pack()

            tk.Label(frame, text=macro["name"], bg=self.highlight_color, fg=self.fg_color).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("450x350")  # Increased height for additional options
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Apply dark theme to dialog
        tk.Label(dialog, text="Macro Name:", bg=self.bg_color, fg=self.fg_color).grid(row=0, column=0, padx=5, pady=5, sticky="w")
        name_entry = tk.Entry(dialog, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        name_entry.grid(row=0, column=1, padx=5, pady=5)

        tk.Label(dialog, text="Type:", bg=self.bg_color, fg=self.fg_color).grid(row=1, column=0, padx=5, pady=5, sticky="w")
        type_var = tk.StringVar(value="text")

        radio_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color}
        tk.Radiobutton(dialog, text="Text", variable=type_var, value="text", **radio_style).grid(row=1, column=1, sticky="w")
        tk.Radiobutton(dialog, text="Application", variable=type_var, value="app", **radio_style).grid(row=2, column=1, sticky="w")

        tk.Label(dialog, text="Command/Text:", bg=self.bg_color, fg=self.fg_color).grid(row=3, column=0, padx=5, pady=5, sticky="w")
        command_text = tk.Text(dialog, width=30, height=5, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        command_text.grid(row=3, column=1, padx=5, pady=5)

        # Key modifiers frame
        mod_frame = tk.Frame(dialog, bg=self.bg_color)
        mod_frame.grid(row=4, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        tk.Label(mod_frame, text="Key Modifiers:", bg=self.bg_color, fg=self.fg_color).pack(side=tk.LEFT, padx=5)

        # Add checkboxes for modifiers
        ctrl_var = tk.BooleanVar(value=False)
        alt_var = tk.BooleanVar(value=False)
        shift_var = tk.BooleanVar(value=False)
        enter_var = tk.BooleanVar(value=False)

        checkbox_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color,
                        'activebackground': self.bg_color, 'activeforeground': self.fg_color}

        tk.Checkbutton(mod_frame, text="Ctrl", variable=ctrl_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Alt", variable=alt_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Shift", variable=shift_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Add Enter", variable=enter_var, **checkbox_style).pack(side=tk.LEFT, padx=5)

        image_path = tk.StringVar()
        tk.Label(dialog, text="Image:", bg=self.bg_color, fg=self.fg_color).grid(row=5, column=0, padx=5, pady=5, sticky="w")
        tk.Entry(dialog, textvariable=image_path, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color).grid(row=5, column=1, padx=5, pady=5)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Browse...", command=lambda: image_path.set(filedialog.askopenfilename(
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")])), **button_style).grid(row=5, 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_path_reference = ""
            img_path = image_path.get()
            if img_path:
                try:
                     # Generate unique filename for the image
                    file_ext = os.path.splitext(img_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(img_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 with modifier keys
            self.macros[macro_id] = {
                "name": name,
                "type": macro_type,
                "command": command,
                "image_path": image_path_reference,
                "modifiers": {
                    "ctrl": ctrl_var.get(),
                    "alt": alt_var.get(),
                    "shift": shift_var.get(),
                    "enter": enter_var.get()
                }
            }

            self.save_macros()
            self.display_macros()
            dialog.destroy()

        tk.Button(dialog, text="Save", command=save_macro, **button_style).grid(row=6, column=0, padx=5, pady=20)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).grid(row=6, column=1, padx=5, pady=20)

    def edit_macro(self):
        # First, show a dialog to select which macro to edit
        if not self.macros:
            tk.messagebox.showinfo("No Macros", "There are no macros to edit.")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("Select Macro to Edit")
        dialog.geometry("200x340")
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Create a listbox to show available macros
        tk.Label(dialog, text="Select a macro to edit:", bg=self.bg_color, fg=self.fg_color).pack(pady=5)

        listbox = tk.Listbox(dialog, bg=self.highlight_color, fg=self.fg_color, selectbackground=self.accent_color)
        listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # Populate the listbox with macro names
        macro_ids = []
        for macro_id, macro in self.macros.items():
            listbox.insert(tk.END, macro["name"])
            macro_ids.append(macro_id)

        def on_select():
            if not listbox.curselection():
                tk.messagebox.showwarning("No Selection", "Please select a macro to edit.")
                return

            idx = listbox.curselection()[0]
            selected_macro_id = macro_ids[idx]
            dialog.destroy()
            self.open_edit_dialog(selected_macro_id)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Edit Selected", command=on_select, **button_style).pack(pady=10)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).pack(pady=5)

    def open_edit_dialog(self, macro_id):
        # Open dialog to edit the selected macro
        macro = self.macros[macro_id]

        dialog = tk.Toplevel(self.root)
        dialog.title("Edit Macro")
        dialog.geometry("450x350")  # Increased height for additional options
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Apply dark theme to dialog
        tk.Label(dialog, text="Macro Name:", bg=self.bg_color, fg=self.fg_color).grid(row=0, column=0, padx=5, pady=5, sticky="w")
        name_entry = tk.Entry(dialog, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        name_entry.grid(row=0, column=1, padx=5, pady=5)
        name_entry.insert(0, macro["name"])

        tk.Label(dialog, text="Type:", bg=self.bg_color, fg=self.fg_color).grid(row=1, column=0, padx=5, pady=5, sticky="w")
        type_var = tk.StringVar(value=macro["type"])

        radio_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color}
        tk.Radiobutton(dialog, text="Text", variable=type_var, value="text", **radio_style).grid(row=1, column=1, sticky="w")
        tk.Radiobutton(dialog, text="Application", variable=type_var, value="app", **radio_style).grid(row=2, column=1, sticky="w")

        tk.Label(dialog, text="Command/Text:", bg=self.bg_color, fg=self.fg_color).grid(row=3, column=0, padx=5, pady=5, sticky="w")
        command_text = tk.Text(dialog, width=30, height=5, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        command_text.grid(row=3, column=1, padx=5, pady=5)
        command_text.insert("1.0", macro["command"])

        # Modifiers frame
        mod_frame = tk.Frame(dialog, bg=self.bg_color)
        mod_frame.grid(row=4, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        tk.Label(mod_frame, text="Key Modifiers:", bg=self.bg_color, fg=self.fg_color).pack(side=tk.LEFT, padx=5)

        # Get existing modifiers or set defaults
        modifiers = macro.get("modifiers", {})

        # Add checkboxes for modifiers
        ctrl_var = tk.BooleanVar(value=modifiers.get("ctrl", False))
        alt_var = tk.BooleanVar(value=modifiers.get("alt", False))
        shift_var = tk.BooleanVar(value=modifiers.get("shift", False))
        enter_var = tk.BooleanVar(value=modifiers.get("enter", False))

        checkbox_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color,
                        'activebackground': self.bg_color, 'activeforeground': self.fg_color}

        tk.Checkbutton(mod_frame, text="Ctrl", variable=ctrl_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Alt", variable=alt_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Shift", variable=shift_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Add Enter", variable=enter_var, **checkbox_style).pack(side=tk.LEFT, padx=5)

        image_path = tk.StringVar()
        tk.Label(dialog, text="Image:", bg=self.bg_color, fg=self.fg_color).grid(row=5, column=0, padx=5, pady=5, sticky="w")
        image_entry = tk.Entry(dialog, textvariable=image_path, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        image_entry.grid(row=5, column=1, padx=5, pady=5)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Browse...", command=lambda: image_path.set(filedialog.askopenfilename(
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")])), **button_style).grid(row=5, column=2)

        def save_edited_macro():
            name = name_entry.get().strip()
            if not name:
                tk.messagebox.showerror("Error", "Macro name is required")
                return

            new_type = type_var.get()
            command = command_text.get("1.0", tk.END).strip()

            # Keep the old image or update with new one
            image_path_reference = macro.get("image_path", "")
            img_path = image_path.get()
            if img_path:
                try:
                        # Generate unique filename for the image
                    file_ext = os.path.splitext(img_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(img_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 with modifiers
            self.macros[macro_id] = {
                "name": name,
                "type": new_type,
                "command": command,
                "image_path": image_path_reference,
                "modifiers": {
                    "ctrl": ctrl_var.get(),
                    "alt": alt_var.get(),
                    "shift": shift_var.get(),
                    "enter": enter_var.get()
                }
            }

            self.save_macros()
            self.display_macros()
            dialog.destroy()

        tk.Button(dialog, text="Save", command=save_edited_macro, **button_style).grid(row=6, column=0, padx=5, pady=20)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).grid(row=6, column=1, padx=5, pady=20)

    def delete_macro(self):
        # Show a dialog to select which macro to delete
        if not self.macros:
            tk.messagebox.showinfo("No Macros", "There are no macros to delete.")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("Delete Macro")
        dialog.geometry("200x340")
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Create a listbox to show available macros
        tk.Label(dialog, text="Select a macro to delete:", bg=self.bg_color, fg=self.fg_color).pack(pady=5)

        listbox = tk.Listbox(dialog, bg=self.highlight_color, fg=self.fg_color, selectbackground=self.accent_color)
        listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # Populate the listbox with macro names
        macro_ids = []
        for macro_id, macro in self.macros.items():
            listbox.insert(tk.END, macro["name"])
            macro_ids.append(macro_id)

        def on_delete():
            if not listbox.curselection():
                tk.messagebox.showwarning("No Selection", "Please select a macro to delete.")
                return

            idx = listbox.curselection()[0]
            selected_macro_id = macro_ids[idx]
            selected_name = self.macros[selected_macro_id]["name"]

            # Confirm deletion
            if tk.messagebox.askyesno("Confirm Deletion", f"Are you sure you want to delete macro '{selected_name}'?"):
                # Delete associated image file if it exists
                macro = self.macros[selected_macro_id]
                if "image_path" in macro and macro["image_path"]:
                    try:
                        img_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 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[selected_macro_id]
                self.save_macros()
                self.display_macros()
                dialog.destroy()

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Delete Selected", command=on_delete, **button_style).pack(pady=10)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).pack(pady=5)

    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":
                # Handle key modifiers
                modifiers = macro.get("modifiers", {})
                keys_to_press = []

                # Add modifier keys if enabled
                if modifiers.get("ctrl", False):
                    keys_to_press.append('ctrl')
                if modifiers.get("alt", False):
                    keys_to_press.append('alt')
                if modifiers.get("shift", False):
                    keys_to_press.append('shift')

                # If there are modifier keys, use hotkey functionality
                if keys_to_press:
                    # For single characters with modifiers, use hotkey
                    if len(macro["command"]) == 1:
                        keys_to_press.append(macro["command"], interval=0.02)
                        pyautogui.hotkey(*keys_to_press)
                    else:
                        # For longer text, press modifiers, type text, then release
                        for key in keys_to_press:
                            pyautogui.keyDown(key)
                        pyautogui.typewrite(macro["command"], interval=0.02)
                        for key in reversed(keys_to_press):
                            pyautogui.keyUp(key)
                else:
                    # No modifiers, just type the text
                    pyautogui.typewrite(macro["command"], interval=0.02)

                # 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

    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 = b""
                while True:
                    chunk = client_socket.recv(4096)
                    if not chunk:
                        break
                    data += chunk
                    try:
                        # Try to see if we've received a complete JSON object
                        json_data = json.loads(data.decode('utf-8'))
                        break  # If successful, break out of the inner loop
                    except json.JSONDecodeError:
                        # If it's not a complete JSON object yet, keep reading
                        continue

                if not data:
                    break

                try:
                    request = json_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'])
                        response = {'success': success}
                        client_socket.send(json.dumps(response).encode('utf-8'))
                    elif request['action'] == 'get_image':
                        image_path = request['image_path']
                        if os.path.exists(image_path):
                            try:
                                with open(image_path, 'rb') as f:
                                    image_data =f.read()
                                image_size = len(image_data)
                                client_socket.send(str(image_size).encode('utf-8'))
                                client_socket.recv(1024)  # Wait for acknowledgment
                                client_socket.sendall(image_data)
                            except FileNotFoundError:
                                client_socket.send(b"ERROR: Image not found")
                            except Exception as e:
                                client_socket.send(f"ERROR: {str(e)}".encode('utf-8'))
                    else:
                        client_socket.send(json.dumps({'error': 'Unknown action'}).encode('utf-8'))
                except (json.JSONDecodeError, KeyError, TypeError) as e:
                    error_msg = {'error': f'Invalid request: {str(e)}'}
                    client_socket.send(json.dumps(error_msg).encode('utf-8'))
                    print(f"JSON processing error: {e}, Data received: {data[:100]}")

        except Exception as e:
            print(f"Client handling error: {e}")
        finally:
            client_socket.close()
            self.status_var.set("Client disconnected. Waiting for connections...")

    def on_closing(self):
        # When the window is closed, minimize to tray instead of closing
        self.minimize_to_tray()


if __name__ == "__main__":
    root = tk.Tk()
    app = MacroPadServer(root)
    root.mainloop()


