Add installation scripts for Windows and Linux, and update README for usage instructions
217 lines
7.3 KiB
Python
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()
|