646 lines
28 KiB
Python
646 lines
28 KiB
Python
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()
|
|
|
|
|