first revision
This commit is contained in:
parent
4cecf24264
commit
c897195083
260
mp-server.py
Normal file
260
mp-server.py
Normal file
@ -0,0 +1,260 @@
|
||||
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(
|
||||
"<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)
|
||||
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()
|
Loading…
x
Reference in New Issue
Block a user