ESP32-discord/main.pyw
ZareMate f6aa6cd626
Change rendering method for GUI
Add installation scripts for Windows and Linux, and update README for usage instructions
2025-01-15 02:25:36 +01:00

217 lines
7.3 KiB
Python

import serial
import time
import keyboard
import serial.tools.list_ports as list_ports
import mss
import numpy as np
import sys
import pystray
from PIL import Image, ImageDraw
import threading
import ctypes
import os
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
# Define the region where the mute and deafen icons are located
x = -1772
y = 1064
x2 = -1746
y2 = 1063
# Define the size of the region to check
width = 4
height = 4
grid = 4
# Define the expected color of the mute and deafen icons
expected_r = 242
expected_g = 63
expected_b = 67
# Set the title for the PowerShell window
ctypes.windll.kernel32.SetConsoleTitleW("ESP32 Discord Controller")
# Set the process name for Task Manager
try:
import ctypes
ctypes.windll.kernel32.SetConsoleTitleW("ESP32 Discord Controller")
os.system("title ESP32 Discord Controller")
except:
pass
def get_serial_port():
if len(sys.argv) > 1 and sys.argv[1].startswith("-com="):
return sys.argv[1].split("=")[1]
ports = [port for port in list(list_ports.comports()) if port.device != "COM1"]
if not ports:
messagebox.showerror("Error", "No serial ports found. Please connect a device.")
return None
if len(ports) == 1:
return ports[0].device
port_choices = [port.device for port in ports]
def on_select():
selected_port.set(port_listbox.get(port_listbox.curselection()))
port_window.destroy()
port_window = tk.Tk()
port_window.title("Select Serial Port")
port_window.configure(bg="#1e1e1e")
tk.Label(port_window, text="Available Ports:", bg="#1e1e1e", fg="#ffffff").pack()
port_listbox = tk.Listbox(port_window, bg="#2d2d2d", fg="#ffffff", selectbackground="#0078d7")
for port in port_choices:
port_listbox.insert(tk.END, port)
port_listbox.pack()
selected_port = tk.StringVar()
tk.Button(port_window, text="Select", command=on_select, bg="#0078d7", fg="#ffffff").pack()
port_window.mainloop()
return selected_port.get()
def get_discord_status():
with mss.mss() as sct:
# Define the region where the mute and deafen icons are located
mute_region = {"top": y, "left": x, "width": width, "height": height}
deafen_region = {"top": y2, "left": x2, "width": width, "height": height}
# Capture the screen region
mute_screenshot = sct.grab(mute_region)
deafen_screenshot = sct.grab(deafen_region)
# Convert the screenshots to numpy arrays
mute_image = np.array(mute_screenshot)
deafen_image = np.array(deafen_screenshot)
# Check the pixel color in a 2x2 region
mute_status = any(
(mute_image[i, j, 2] == expected_r and
mute_image[i, j, 1] == expected_g and
mute_image[i, j, 0] == expected_b)
for i in range(grid) for j in range(grid)
)
deafen_status = any(
(deafen_image[i, j, 2] == expected_r and
deafen_image[i, j, 1] == expected_g and
deafen_image[i, j, 0] == expected_b)
for i in range(grid) for j in range(grid)
)
return mute_status, deafen_status
def create_image():
# Generate an image for the system tray icon
width = 64
height = 64
image = Image.new('RGB', (width, height), (255, 255, 255))
dc = ImageDraw.Draw(image)
dc.rectangle(
[(width // 2, 0), (width, height // 2)],
fill="black")
dc.rectangle(
[(0, height // 2), (width // 2, height)],
fill="black")
return image
def toggle_console():
if console_window.state() == "normal":
console_window.withdraw()
else:
console_window.deiconify()
def log_message(message):
log_text.insert(tk.END, message + "\n")
log_text.see(tk.END)
def on_quit(icon, item):
icon.stop()
if 'ser' in globals() and ser.is_open:
ser.close()
log_message("Serial connection closed.")
console_window.withdraw()
console_window.quit()
sys.exit(0)
def hide_console():
log_message("Hiding console window...")
time.sleep(2)
console_window.withdraw()
def hide_in_tray():
icon = pystray.Icon("ESP32-discord")
icon.icon = create_image()
icon.menu = pystray.Menu(pystray.MenuItem('Quit', on_quit), pystray.MenuItem('Toggle Console', toggle_console))
threading.Thread(target=icon.run).start()
hide_console()
def main():
global ser
port = get_serial_port()
if not port:
return
try:
ser = serial.Serial(port, 115200, timeout=1)
time.sleep(2) # Wait for the connection to initialize
log_message("Listening for commands...")
last_mute_status, last_deafen_status = get_discord_status()
log_message(f"Mute: {last_mute_status}, Deafen: {last_deafen_status}")
ser.write(f"Mute: {last_mute_status}, Deafen: {last_deafen_status}\n".encode('utf-8'))
hide_in_tray()
while True:
try:
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').strip()
log_message(f"Received: {line}")
if line == "mute":
keyboard.send('ctrl+shift+f16')
elif line == "deafen":
keyboard.send('ctrl+shift+f15')
elif line == "status":
mute_status, deafen_status = get_discord_status()
ser.write(f"Mute: {mute_status}, Deafen: {deafen_status}\n".encode('utf-8'))
else:
log_message(f"Unknown command: {line}")
current_mute_status, current_deafen_status = get_discord_status()
if current_mute_status != last_mute_status or current_deafen_status != last_deafen_status:
ser.write(f"Mute: {current_mute_status}, Deafen: {current_deafen_status}\n".encode('utf-8'))
log_message(f"Mute: {current_mute_status}, Deafen: {current_deafen_status}")
last_mute_status, last_deafen_status = current_mute_status, current_deafen_status
time.sleep(0.1)
except KeyboardInterrupt:
log_message("Exiting program.")
break
except Exception as e:
log_message(f"Error during execution: {e}")
break
except serial.SerialException as e:
messagebox.showerror("Error", f"Could not open serial port: {e}")
finally:
if 'ser' in globals() and ser.is_open:
ser.close()
log_message("Serial connection closed.")
if __name__ == "__main__":
console_window = tk.Tk()
console_window.title("ESP32 Discord Controller")
console_window.configure(bg="#1e1e1e")
style = ttk.Style()
style.theme_use('clam')
style.configure("TLabel", background="#1e1e1e", foreground="#ffffff")
style.configure("TButton", background="#0078d7", foreground="#ffffff")
style.configure("TText", background="#2d2d2d", foreground="#ffffff")
log_text = tk.Text(console_window, wrap=tk.WORD, bg="#2d2d2d", fg="#ffffff")
log_text.pack(expand=True, fill=tk.BOTH)
threading.Thread(target=main).start()
console_window.mainloop()