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()
 |