GPT Translator
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import webbrowser
import urllib.parse
import json
import os
import sys
import logging
from pathlib import Path
from datetime import datetime
from PIL import ImageGrab, Image, ImageDraw
import time
import subprocess
import tempfile
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class ChatGPTBrowserTool:
def __init__(self, root):
self.root = root
self.root.title("ChatGPT Browser Tool")
self.root.geometry("700x970") # Aumentata l'altezza per la checkbox
logger.info("Initializing ChatGPT Browser Tool")
# Load settings
self.settings_file = Path.cwd() / "chatgpt_browser_tool_settings.json"
self.settings = self.load_settings()
logger.info(f"Settings loaded from {self.settings_file}")
# Screenshot state
self.is_capturing = False
self.selection_window = None
self.start_x = None
self.start_y = None
self.rect_id = None
self.bg_rect_id = None # Background rectangle for better visibility
# Last screenshot path
self.last_screenshot_path = None
# Theme state
self.dark_mode = self.settings.get("dark_mode", False)
# System tray icon
self.tray_icon = None
# Selection cursor color
self.cursor_color = self.settings.get("cursor_color", "Red")
# Latency settings
self.startup_latency = self.settings.get("startup_latency", 2.0) # Valore predefinito: 2 secondi
self.wait_gpt_loaded = self.settings.get("wait_gpt_loaded", False) # Nuova impostazione
# Available cursor colors with configurations (like the example tool)
self.cursor_colors = {
"Red": {
"bg_outline": "#8B0000", # Dark red background
"bg_width": 3,
"fg_outline": "#FF0000", # Bright red foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Green": {
"bg_outline": "#006400", # Dark green background
"bg_width": 3,
"fg_outline": "#00FF00", # Bright green foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Blue": {
"bg_outline": "#00008B", # Dark blue background
"bg_width": 3,
"fg_outline": "#0000FF", # Bright blue foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Yellow": {
"bg_outline": "#8B8B00", # Dark yellow background
"bg_width": 3,
"fg_outline": "#FFFF00", # Bright yellow foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"White": {
"bg_outline": "#808080", # Gray background
"bg_width": 3,
"fg_outline": "#FFFFFF", # White foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Black & White": {
"bg_outline": "black", # Black background
"bg_width": 3,
"fg_outline": "white", # White foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Rainbow Stripes": {
"bg_outline": "black", # Black background
"bg_width": 3,
"fg_outline": "red", # Starting color
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": True, # Special rainbow mode
"bg_alpha": 0.3
},
"Purple": {
"bg_outline": "#4B0082", # Dark purple background
"bg_width": 3,
"fg_outline": "#9370DB", # Light purple foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Orange": {
"bg_outline": "#8B4500", # Dark orange background
"bg_width": 3,
"fg_outline": "#FFA500", # Bright orange foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
},
"Cyan": {
"bg_outline": "#008B8B", # Dark cyan background
"bg_width": 3,
"fg_outline": "#00FFFF", # Bright cyan foreground
"fg_width": 2,
"fg_dash": (5, 5),
"rainbow": False,
"bg_alpha": 0.3
}
}
# Rainbow colors for animation
self.rainbow_colors = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "magenta"]
# Language configuration
self.languages = {
"English": "Please translate all the text visible in this image to English.",
"Italian": "Per favore, traducete tutto il testo visibile in questa immagine in italiano.",
"Spanish": "Por favor, traduce todo el texto visible en esta imagen al español.",
"French": "Veuillez traduire tout le texte visible dans cette image en français.",
"German": "Bitte übersetzen Sie den gesamten in diesem Bild sichtbaren Text ins Deutsche.",
"Portuguese": "Por favor, traduza todo o texto visível nesta imagem para português.",
"Japanese": "この画像に見えるすべてのテキストを日本語に翻訳してください。",
"Chinese": "请将此图片中所有可见文本翻译成中文。",
"Korean": "この画像に見えるすべてのテキストを日本語に翻訳してください。",
"Russian": "Пожалуйста, переведите весь видимый текст на этом изображении на русский язык。",
"Arabic": "يرجى ترجمة كل النص المرئي في هذه الصورة إلى العربية。",
"Custom": "Custom text..."
}
# Configure initial style
self.setup_theme()
# Main frame
self.main_frame = ttk.Frame(root, padding="10")
self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
self.main_frame.columnconfigure(0, weight=1)
# =====================================================================
# TOP TOOLBAR FRAME
# =====================================================================
toolbar_frame = ttk.Frame(self.main_frame)
toolbar_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
toolbar_frame.columnconfigure(0, weight=1)
# Theme toggle button
theme_text = "🌙 Dark" if not self.dark_mode else "☀️ Light"
self.theme_button = ttk.Button(toolbar_frame,
text=theme_text,
command=self.toggle_theme,
width=12)
self.theme_button.grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
# Minimize to tray button
self.tray_button = ttk.Button(toolbar_frame,
text="📌 Minimize to Tray",
command=self.minimize_to_tray,
width=25)
self.tray_button.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))
# Start as icon checkbox
self.start_as_icon_var = tk.BooleanVar(value=self.settings.get("start_as_icon", False))
self.start_as_icon_check = ttk.Checkbutton(toolbar_frame,
text="Start as Icon",
variable=self.start_as_icon_var,
command=self.save_settings)
self.start_as_icon_check.grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
# Spacer
ttk.Label(toolbar_frame, text="").grid(row=0, column=3, sticky=tk.W, padx=(0, 5))
# =====================================================================
# CURSOR COLOR SELECTION
# =====================================================================
cursor_frame = ttk.LabelFrame(self.main_frame, text="Selection Cursor Color", padding="5")
cursor_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
cursor_frame.columnconfigure(1, weight=1)
# Cursor color selection
ttk.Label(cursor_frame, text="Cursor:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.cursor_color_var = tk.StringVar(value=self.cursor_color)
cursor_combo = ttk.Combobox(cursor_frame, textvariable=self.cursor_color_var,
values=list(self.cursor_colors.keys()),
state="readonly", width=15)
cursor_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 10))
cursor_combo.bind("<<ComboboxSelected>>", self.on_cursor_color_change)
# Preview frame for cursor color
self.preview_canvas = tk.Canvas(cursor_frame, width=30, height=30, bg="white", highlightthickness=1)
self.preview_canvas.grid(row=0, column=2, sticky=tk.W, padx=(10, 0))
self.update_cursor_preview()
# =====================================================================
# LATENCY SETTINGS
# =====================================================================
latency_frame = ttk.LabelFrame(self.main_frame, text="Startup Latency", padding="5")
latency_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
latency_frame.columnconfigure(1, weight=1)
# Latency label
ttk.Label(latency_frame, text="Delay before auto-fill:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
# Latency slider
self.latency_var = tk.DoubleVar(value=self.startup_latency)
self.latency_slider = ttk.Scale(latency_frame, from_=0.5, to=10.0, orient=tk.HORIZONTAL,
variable=self.latency_var, length=200,
command=self.on_latency_change)
self.latency_slider.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 10))
# Latency value display
self.latency_value_label = ttk.Label(latency_frame, text=f"{self.latency_var.get():.1f}s")
self.latency_value_label.grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
# Wait GPT checkbox
self.wait_gpt_var = tk.BooleanVar(value=self.wait_gpt_loaded)
self.wait_gpt_check = ttk.Checkbutton(latency_frame,
text="Wait GPT - Wait for page to fully load",
variable=self.wait_gpt_var,
command=self.on_wait_gpt_change)
self.wait_gpt_check.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))
# Info label
info_text = "Adjust if browser is slow to load. Higher = more wait time."
ttk.Label(latency_frame, text=info_text, font=('Arial', 8)).grid(
row=2, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))
# =====================================================================
# LANGUAGE SELECTION
# =====================================================================
lang_frame = ttk.LabelFrame(self.main_frame, text="Translation Language", padding="5")
lang_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
lang_frame.columnconfigure(1, weight=1)
# Language selection
ttk.Label(lang_frame, text="Translate to:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.language_var = tk.StringVar(value=self.settings.get("translation_language", "English"))
self.language_combo = ttk.Combobox(lang_frame, textvariable=self.language_var,
values=list(self.languages.keys()),
state="readonly", width=20)
self.language_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 10))
self.language_combo.bind("<<ComboboxSelected>>", self.on_language_change)
# Custom text entry (shown only when "Custom" is selected)
self.custom_text_var = tk.StringVar(value=self.settings.get("custom_text", ""))
self.custom_label = ttk.Label(lang_frame, text="Custom text:")
self.custom_entry = ttk.Entry(lang_frame, textvariable=self.custom_text_var, width=40)
self.update_custom_text_visibility()
# =====================================================================
# BROWSER SETTINGS
# =====================================================================
browser_frame = ttk.LabelFrame(self.main_frame, text="Browser Settings", padding="5")
browser_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
browser_frame.columnconfigure(1, weight=1)
# Browser type selection
ttk.Label(browser_frame, text="Browser:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.browser_var = tk.StringVar(value=self.settings.get("browser_type", "default"))
browser_combo = ttk.Combobox(browser_frame, textvariable=self.browser_var,
values=["default", "chrome", "firefox", "edge", "brave", "custom"],
state="readonly", width=15)
browser_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))
browser_combo.bind("<<ComboboxSelected>>", self.on_browser_change)
# Custom browser path
self.custom_path_var = tk.StringVar(value=self.settings.get("custom_path", ""))
self.path_label = ttk.Label(browser_frame, text="Custom Path:")
self.path_entry = ttk.Entry(browser_frame, textvariable=self.custom_path_var)
self.path_button = ttk.Button(browser_frame, text="Browse...", command=self.browse_browser)
# Show/hide custom path widgets based on selection
self.update_custom_path_visibility()
# Auto-submit option
self.auto_submit_var = tk.BooleanVar(value=self.settings.get("auto_submit", True))
auto_submit_check = ttk.Checkbutton(browser_frame, text="Auto-submit question (uses PyAutoGUI - press Enter automatically)",
variable=self.auto_submit_var,
command=self.save_settings)
auto_submit_check.grid(row=2, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))
# =====================================================================
# SCREENSHOT SECTION
# =====================================================================
screenshot_frame = ttk.LabelFrame(self.main_frame, text="Screenshot to ChatGPT", padding="10")
screenshot_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=(10, 10))
# Frame per i pulsanti screenshot
screenshot_buttons_frame = ttk.Frame(screenshot_frame)
screenshot_buttons_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
screenshot_buttons_frame.columnconfigure(0, weight=1)
screenshot_buttons_frame.columnconfigure(1, weight=1)
# Pulsante per screenshot area
self.area_button = ttk.Button(screenshot_buttons_frame,
text="📷 CAPTURE AREA\n(Alt+Q)",
command=self.capture_area,
width=25,
style="Large.TButton")
self.area_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
# Pulsante per screenshot schermo intero
self.fullscreen_button = ttk.Button(screenshot_buttons_frame,
text="📸 CAPTURE FULLSCREEN\n(Alt+X)",
command=self.capture_fullscreen,
width=25,
style="Large.TButton")
self.fullscreen_button.grid(row=0, column=1, padx=5, pady=5, sticky=tk.NSEW)
# Pulsante per aprire l'ultimo screenshot
self.open_last_button = ttk.Button(screenshot_buttons_frame,
text="📂 OPEN LAST SCREENSHOT",
command=self.open_last_screenshot,
width=20)
self.open_last_button.grid(row=1, column=0, columnspan=2, padx=5, pady=(10, 5), sticky=tk.NSEW)
# Frame per impostazioni shortcuts
shortcut_frame = ttk.Frame(screenshot_frame)
shortcut_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
ttk.Label(shortcut_frame, text="Area Shortcut:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.area_screenshot_key = tk.StringVar(value=self.settings.get("area_screenshot_key", "alt+q"))
area_entry = ttk.Entry(shortcut_frame, textvariable=self.area_screenshot_key, width=15)
area_entry.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))
area_entry.bind('<FocusOut>', lambda e: self.save_settings())
ttk.Label(shortcut_frame, text="Fullscreen Shortcut:").grid(row=0, column=2, sticky=tk.W, padx=(20, 5))
self.fullscreen_screenshot_key = tk.StringVar(value=self.settings.get("fullscreen_screenshot_key", "alt+x"))
fullscreen_entry = ttk.Entry(shortcut_frame, textvariable=self.fullscreen_screenshot_key, width=15)
fullscreen_entry.grid(row=0, column=3, sticky=tk.W, padx=(0, 5))
fullscreen_entry.bind('<FocusOut>', lambda e: self.save_settings())
ttk.Button(shortcut_frame, text="Update Shortcuts",
command=self.update_hotkeys).grid(row=0, column=4, padx=(10, 0))
# =====================================================================
# QUESTION SECTION
# =====================================================================
question_frame = ttk.LabelFrame(self.main_frame, text="Your Question", padding="5")
question_frame.grid(row=6, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
question_frame.columnconfigure(0, weight=1)
question_frame.rowconfigure(0, weight=1)
# Text area for question
self.text_area = tk.Text(question_frame, wrap=tk.WORD, height=8)
self.text_area.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
self.text_area.focus()
# Apply dark theme to text area if needed
if self.dark_mode:
self.apply_dark_theme_to_widgets()
# Insert default text based on language
self.insert_default_text()
# Scrollbar for text area
scrollbar = ttk.Scrollbar(question_frame, orient=tk.VERTICAL, command=self.text_area.yview)
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.text_area.configure(yscrollcommand=scrollbar.set)
# Bind Enter key (Ctrl+Enter to send)
self.text_area.bind("<Control-Return>", lambda e: self.send_to_chatgpt())
# =====================================================================
# ACTION BUTTONS
# =====================================================================
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=7, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
button_frame.columnconfigure(0, weight=1)
# Send button
send_button = ttk.Button(button_frame, text="📤 SEND TO ChatGPT (Ctrl+Enter)",
command=self.send_to_chatgpt,
style="Accent.TButton")
send_button.grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
# Clear button
clear_button = ttk.Button(button_frame, text="🗑️ CLEAR", command=self.clear_text)
clear_button.grid(row=0, column=1, sticky=tk.E, padx=(0, 5))
# Open ChatGPT button
open_chatgpt_button = ttk.Button(button_frame, text="🌐 OPEN ChatGPT",
command=self.open_chatgpt_only)
open_chatgpt_button.grid(row=0, column=2, sticky=tk.E)
# =====================================================================
# STATUS BAR
# =====================================================================
self.status_var = tk.StringVar(value="Ready")
status_bar = ttk.Label(self.main_frame, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W)
status_bar.grid(row=8, column=0, sticky=(tk.W, tk.E), pady=(0, 0))
# Setup hotkeys immediately
self.root.after(500, self.setup_hotkeys)
# Check if should start as icon
if self.start_as_icon_var.get():
self.root.after(1000, self.minimize_to_tray)
# Setup window close handler
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
def update_cursor_preview(self):
"""Update the cursor color preview"""
self.preview_canvas.delete("all")
color_config = self.cursor_colors.get(self.cursor_color_var.get(), self.cursor_colors["Red"])
# Draw preview rectangle
bg_color = color_config["bg_outline"]
fg_color = color_config["fg_outline"]
# Draw background (thicker)
self.preview_canvas.create_rectangle(
3, 3, 27, 27,
outline=bg_color,
width=color_config["bg_width"]
)
# Draw foreground (thinner with dashes)
self.preview_canvas.create_rectangle(
5, 5, 25, 25,
outline=fg_color,
width=color_config["fg_width"],
dash=color_config["fg_dash"]
)
def on_cursor_color_change(self, event=None):
"""Handle cursor color selection change"""
self.cursor_color = self.cursor_color_var.get()
self.update_cursor_preview()
self.save_settings()
logger.info(f"Cursor color changed to: {self.cursor_color}")
def on_latency_change(self, value):
"""Handle latency slider change"""
self.startup_latency = float(value)
self.latency_value_label.config(text=f"{self.startup_latency:.1f}s")
self.save_settings()
logger.info(f"Startup latency changed to: {self.startup_latency:.1f}s")
def on_wait_gpt_change(self):
"""Handle Wait GPT checkbox change"""
self.wait_gpt_loaded = self.wait_gpt_var.get()
self.save_settings()
if self.wait_gpt_loaded:
logger.info("Wait GPT enabled - will wait for page to fully load")
self.status_var.set("Wait GPT enabled - will detect page load")
else:
logger.info("Wait GPT disabled - using fixed delay")
self.status_var.set("Wait GPT disabled - using fixed delay")
def setup_theme(self):
"""Setup light/dark theme"""
style = ttk.Style()
if self.dark_mode:
# Dark theme colors
bg_color = "#2b2b2b"
fg_color = "#ffffff"
entry_bg = "#3c3f41"
button_bg = "#4c5052"
frame_bg = "#3c3f41"
select_bg = "#4c5052"
select_fg = "#ffffff"
# Configure root window
self.root.configure(bg=bg_color)
# Configure ttk styles
style.theme_use('clam')
style.configure(".",
background=bg_color,
foreground=fg_color,
fieldbackground=entry_bg,
selectbackground=select_bg,
selectforeground=select_fg)
style.configure("TLabel", background=bg_color, foreground=fg_color)
style.configure("TFrame", background=bg_color)
style.configure("TLabelframe", background=frame_bg, foreground=fg_color)
style.configure("TLabelframe.Label", background=frame_bg, foreground=fg_color)
style.configure("TButton",
background=button_bg,
foreground=fg_color,
borderwidth=1,
focusthickness=3,
focuscolor='none')
style.map("TButton",
background=[('active', '#5c6062')])
style.configure("TEntry",
fieldbackground=entry_bg,
foreground=fg_color,
insertcolor=fg_color)
style.configure("TCombobox",
fieldbackground="#ffffff", # Sfondo bianco
foreground="#000000", # Testo nero per contrasto
background=button_bg)
style.map("TCombobox",
fieldbackground=[('readonly', '#ffffff')],
foreground=[('readonly', '#000000')])
style.configure("TCheckbutton",
background=bg_color,
foreground=fg_color)
style.configure("Large.TButton",
font=('Arial', 10, 'bold'),
padding=10,
background=button_bg,
foreground=fg_color)
style.configure("Accent.TButton",
font=('Arial', 10, 'bold'),
foreground='#64b5f6',
background=button_bg)
else:
# Light theme (default)
style.theme_use('clam')
# Configurazione Combobox per tema chiaro
style.configure("TCombobox",
fieldbackground="#ffffff", # Sfondo bianco
foreground="#000000") # Testo nero
style.map("TCombobox",
fieldbackground=[('readonly', '#ffffff')],
foreground=[('readonly', '#000000')])
style.configure("Large.TButton", font=('Arial', 10, 'bold'), padding=10)
style.configure("Accent.TButton", font=('Arial', 10, 'bold'), foreground='blue')
def apply_dark_theme_to_widgets(self):
"""Apply dark theme colors to tkinter widgets"""
# Text widget
self.text_area.configure(
bg='#3c3f41',
fg='#ffffff',
insertbackground='#ffffff',
selectbackground='#4c5052',
selectforeground='#ffffff'
)
# Preview canvas
if hasattr(self, 'preview_canvas'):
self.preview_canvas.configure(bg='#3c3f41')
def toggle_theme(self):
"""Toggle between light and dark theme"""
self.dark_mode = not self.dark_mode
self.save_settings()
# Update button text
theme_text = "🌙 Dark" if not self.dark_mode else "☀️ Light"
self.theme_button.config(text=theme_text)
# Recreate the UI with new theme
# Store current values
current_question = self.text_area.get(1.0, tk.END).strip()
current_language = self.language_var.get()
current_custom_text = self.custom_text_var.get()
current_cursor_color = self.cursor_color_var.get()
current_latency = self.latency_var.get()
current_wait_gpt = self.wait_gpt_var.get()
# Clear the window
for widget in self.root.winfo_children():
widget.destroy()
# Reinitialize with new theme
self.__init__(self.root)
# Restore values
self.language_var.set(current_language)
self.custom_text_var.set(current_custom_text)
self.cursor_color_var.set(current_cursor_color)
self.latency_var.set(current_latency)
self.wait_gpt_var.set(current_wait_gpt)
self.on_latency_change(current_latency) # Update label
self.on_wait_gpt_change() # Update state
self.update_cursor_preview()
self.insert_default_text()
if current_question and current_question != self.languages.get(current_language, ""):
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, current_question)
logger.info(f"Theme changed to: {'Dark' if self.dark_mode else 'Light'}")
def minimize_to_tray(self):
"""Minimize window to system tray"""
try:
import pystray
from PIL import Image, ImageDraw
except ImportError:
messagebox.showwarning("Missing Dependency",
"pystray module not installed!\n\n"
"Install with: pip install pystray")
return
# Create tray icon
def create_image():
# Create a simple icon with ChatGPT colors
image = Image.new('RGB', (64, 64), color='#4c5052' if self.dark_mode else '#10A37F')
draw = ImageDraw.Draw(image)
draw.rectangle([16, 16, 48, 48], fill='white', outline='black')
# Simple GPT logo style
draw.ellipse([20, 20, 44, 44], fill='#10A37F', outline='white')
return image
# Tray menu actions
def restore_window(icon, item):
icon.stop()
self.restore_from_tray()
def show_about(icon, item):
messagebox.showinfo("About", "ChatGPT Browser Tool\n\nVersion 1.0\n\nA tool for sending screenshots to chat.openai.com")
def quit_app(icon, item):
icon.stop()
self.root.quit()
# Create tray menu
menu = pystray.Menu(
pystray.MenuItem("Restore", restore_window),
pystray.MenuItem("About", show_about),
pystray.MenuItem("Quit", quit_app)
)
# Create and run tray icon
self.tray_icon = pystray.Icon("chatgpt_tool", create_image(), "ChatGPT Browser Tool", menu)
# Hide the main window
self.root.withdraw()
self.status_var.set("Minimized to tray - Right-click icon to restore")
# Run tray icon in background thread
import threading
tray_thread = threading.Thread(target=self.tray_icon.run, daemon=True)
tray_thread.start()
logger.info("Application minimized to system tray")
def restore_from_tray(self):
"""Restore window from system tray"""
if self.tray_icon:
self.tray_icon.stop()
self.tray_icon = None
self.root.deiconify()
self.root.lift()
self.root.focus_force()
self.status_var.set("Restored from tray")
logger.info("Application restored from system tray")
def on_closing(self):
"""Handle window closing event"""
if self.tray_icon:
# If minimized to tray, just hide the window
self.root.withdraw()
else:
# Otherwise, quit the application
self.save_settings()
self.root.quit()
def insert_default_text(self):
"""Insert default text based on selected language"""
self.text_area.delete(1.0, tk.END)
lang = self.language_var.get()
if lang == "Custom":
custom_text = self.custom_text_var.get()
if custom_text:
self.text_area.insert(1.0, custom_text)
elif lang in self.languages:
self.text_area.insert(1.0, self.languages[lang])
def on_language_change(self, event=None):
"""Handle language selection change"""
logger.info(f"Language changed to: {self.language_var.get()}")
self.update_custom_text_visibility()
self.insert_default_text()
self.save_settings()
def update_custom_text_visibility(self):
"""Show/hide custom text widgets based on language selection"""
if self.language_var.get() == "Custom":
self.custom_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))
self.custom_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(5, 0))
else:
self.custom_label.grid_remove()
self.custom_entry.grid_remove()
def setup_hotkeys(self):
"""Setup hotkeys using keyboard module"""
try:
import keyboard
# Remove existing hotkeys first
try:
keyboard.remove_hotkey(self.area_screenshot_key.get())
keyboard.remove_hotkey(self.fullscreen_screenshot_key.get())
except:
pass
# Add new hotkeys
keyboard.add_hotkey(self.area_screenshot_key.get(), self.capture_area)
keyboard.add_hotkey(self.fullscreen_screenshot_key.get(), self.capture_fullscreen)
logger.info(f"Hotkeys registered: {self.area_screenshot_key.get()} (area), {self.fullscreen_screenshot_key.get()} (fullscreen)")
self.status_var.set(f"Hotkeys ready: {self.area_screenshot_key.get()} / {self.fullscreen_screenshot_key.get()}")
except ImportError:
logger.warning("keyboard module not installed, hotkeys disabled")
self.status_var.set("Install 'keyboard' module for hotkeys (pip install keyboard)")
except Exception as e:
logger.error(f"Failed to setup hotkeys: {e}")
def update_hotkeys(self):
"""Update hotkeys when shortcuts are changed"""
self.save_settings()
self.setup_hotkeys()
messagebox.showinfo("Success", "Shortcuts updated!")
def load_settings(self):
"""Load settings from file"""
if self.settings_file.exists():
try:
with open(self.settings_file, 'r') as f:
settings = json.load(f)
logger.info(f"Settings loaded successfully: {settings}")
return settings
except Exception as e:
logger.error(f"Failed to load settings: {e}")
return {}
logger.info("No existing settings file found, using defaults")
return {}
def save_settings(self):
"""Save settings to file"""
settings = {
"dark_mode": self.dark_mode,
"cursor_color": self.cursor_color_var.get(),
"start_as_icon": self.start_as_icon_var.get(),
"startup_latency": self.startup_latency,
"wait_gpt_loaded": self.wait_gpt_loaded,
"translation_language": self.language_var.get(),
"custom_text": self.custom_text_var.get(),
"browser_type": self.browser_var.get(),
"custom_path": self.custom_path_var.get(),
"auto_submit": self.auto_submit_var.get(),
"area_screenshot_key": self.area_screenshot_key.get(),
"fullscreen_screenshot_key": self.fullscreen_screenshot_key.get()
}
try:
with open(self.settings_file, 'w') as f:
json.dump(settings, f, indent=2)
logger.info(f"Settings saved: {settings}")
except Exception as e:
logger.error(f"Failed to save settings: {e}")
def on_browser_change(self, event=None):
"""Handle browser selection change"""
logger.info(f"Browser changed to: {self.browser_var.get()}")
self.update_custom_path_visibility()
self.save_settings()
def update_custom_path_visibility(self):
"""Show/hide custom path widgets based on browser selection"""
if self.browser_var.get() == "custom":
self.path_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))
self.path_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(5, 0))
self.path_button.grid(row=1, column=2, sticky=tk.W, pady=(5, 0))
else:
self.path_label.grid_remove()
self.path_entry.grid_remove()
self.path_button.grid_remove()
def browse_browser(self):
"""Browse for custom browser executable"""
filename = filedialog.askopenfilename(
title="Select Browser Executable",
filetypes=[("Executable Files", "*.exe"), ("All Files", "*.*")]
)
if filename:
self.custom_path_var.set(filename)
self.save_settings()
def clear_text(self):
"""Clear the text area"""
self.text_area.delete(1.0, tk.END)
self.text_area.focus()
self.status_var.set("Text cleared")
def open_chatgpt_only(self):
"""Just open ChatGPT without sending anything"""
browser_type = self.browser_var.get()
chatgpt_url = "https://chat.openai.com"
# Prima minimizza questa finestra
self.root.iconify()
# Poi apre il browser
if browser_type == "default":
webbrowser.open(chatgpt_url)
else:
browser_path = self._get_browser_path()
if browser_path and os.path.exists(browser_path):
try:
subprocess.Popen([browser_path, chatgpt_url])
except Exception as e:
webbrowser.open(chatgpt_url)
else:
webbrowser.open(chatgpt_url)
# Aspetta un po' e poi porta il browser in primo piano
time.sleep(0.8)
self._bring_browser_to_foreground_simple()
self.status_var.set("ChatGPT opened - this window minimized")
def capture_area(self):
"""Capture area screenshot"""
if self.is_capturing:
return
self.is_capturing = True
self.status_var.set("Select area to capture (ESC to cancel)...")
self.root.withdraw()
self.root.after(200, self._show_selection_overlay) # Increased delay
def _show_selection_overlay(self):
"""Show overlay for area selection - using approach from example tool"""
self.selection_window = tk.Toplevel(self.root)
self.selection_window.attributes('-fullscreen', True)
self.selection_window.attributes('-alpha', 0.3) # Changed: Semi-transparent gray overlay
self.selection_window.attributes('-topmost', True)
# Get selected cursor configuration
cursor_config = self.cursor_colors.get(self.cursor_color_var.get(), self.cursor_colors["Red"])
# Create canvas with gray background (like the example)
canvas = tk.Canvas(self.selection_window, cursor="cross", bg='grey', highlightthickness=0)
canvas.pack(fill=tk.BOTH, expand=True)
self.start_x = None
self.start_y = None
self.rect_id = None
self.bg_rect_id = None
# For rainbow animation
self.rainbow_color_index = [0]
def update_rainbow_animation():
"""Update rainbow colors for animation"""
if cursor_config["rainbow"] and self.rect_id and canvas.winfo_exists():
self.rainbow_color_index[0] = (self.rainbow_color_index[0] + 1) % len(self.rainbow_colors)
canvas.itemconfig(self.rect_id, outline=self.rainbow_colors[self.rainbow_color_index[0]])
canvas.after(100, update_rainbow_animation)
def on_mouse_down(event):
self.start_x = event.x
self.start_y = event.y
# Create background rectangle (thicker, solid)
self.bg_rect_id = canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline=cursor_config["bg_outline"],
width=cursor_config["bg_width"]
)
# Create foreground rectangle (thinner, dashed)
self.rect_id = canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline=cursor_config["fg_outline"],
width=cursor_config["fg_width"],
dash=cursor_config["fg_dash"]
)
# Start rainbow animation if enabled
if cursor_config["rainbow"]:
update_rainbow_animation()
def on_mouse_move(event):
if self.start_x and self.start_y and self.rect_id:
x1 = min(self.start_x, event.x)
y1 = min(self.start_y, event.y)
x2 = max(self.start_x, event.x)
y2 = max(self.start_y, event.y)
# Update both rectangles
canvas.coords(self.rect_id, x1, y1, x2, y2)
if self.bg_rect_id:
canvas.coords(self.bg_rect_id, x1, y1, x2, y2)
def on_mouse_up(event):
if self.start_x and self.start_y:
x1 = min(self.start_x, event.x)
y1 = min(self.start_y, event.y)
x2 = max(self.start_x, event.x)
y2 = max(self.start_y, event.y)
# Check minimum size
if (x2 - x1) < 10 or (y2 - y1) < 10:
messagebox.showwarning("Area too small", "Please select a larger area.")
self._cancel_selection()
return
self.selection_window.destroy()
self._capture_screen_area(x1, y1, x2, y2)
def on_right_click(event):
"""Right click to cancel"""
self._cancel_selection()
def on_escape(event):
self._cancel_selection()
canvas.bind("<ButtonPress-1>", on_mouse_down)
canvas.bind("<B1-Motion>", on_mouse_move)
canvas.bind("<ButtonRelease-1>", on_mouse_up)
canvas.bind("<ButtonPress-3>", on_right_click) # Right click support
canvas.bind("<Escape>", on_escape)
canvas.focus_set()
# Add instruction label
instruction = canvas.create_text(
canvas.winfo_screenwidth() // 2,
30,
text="Click and drag to select area. Right-click or ESC to cancel.",
fill="white",
font=("Arial", 12, "bold"),
anchor="center"
)
# Make instruction visible above gray background
canvas.tag_raise(instruction)
def _cancel_selection(self):
"""Cancel selection"""
if self.selection_window:
self.selection_window.destroy()
self.selection_window = None
self.is_capturing = False
self.root.deiconify()
self.status_var.set("Area selection cancelled")
def _capture_screen_area(self, x1, y1, x2, y2):
"""Capture the selected area"""
try:
time.sleep(0.2)
screenshot = ImageGrab.grab(bbox=(x1, y1, x2, y2))
self._process_and_send_screenshot(screenshot, "area")
except Exception as e:
logger.error(f"Failed to capture area: {e}", exc_info=True)
messagebox.showerror("Error", f"Failed to capture screenshot:\n{e}")
self.status_var.set("Capture failed")
finally:
self.is_capturing = False
self.root.deiconify()
def capture_fullscreen(self):
"""Capture fullscreen screenshot"""
if self.is_capturing:
return
self.is_capturing = True
self.status_var.set("Capturing fullscreen...")
self.root.withdraw()
self.root.after(200, self._do_fullscreen_capture) # Increased delay
def _do_fullscreen_capture(self):
"""Do the fullscreen capture"""
try:
time.sleep(0.2)
screenshot = ImageGrab.grab()
self._process_and_send_screenshot(screenshot, "fullscreen")
except Exception as e:
logger.error(f"Failed to capture fullscreen: {e}", exc_info=True)
messagebox.showerror("Error", f"Failed to capture screenshot:\n{e}")
self.status_var.set("Capture failed")
finally:
self.is_capturing = False
self.root.deiconify()
def _copy_image_to_clipboard(self, image):
"""Copy a PIL Image to the Windows clipboard as CF_DIB."""
import io
try:
import win32clipboard
import win32con
bmp_buffer = io.BytesIO()
image.save(bmp_buffer, format='BMP')
dib_data = bmp_buffer.getvalue()[14:]
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32con.CF_DIB, dib_data)
win32clipboard.CloseClipboard()
logger.info("Image copied to clipboard as CF_DIB successfully")
return True
except ImportError:
logger.warning("pywin32 not installed – install with: pip install pywin32")
return False
except Exception as e:
logger.error(f"Failed to copy image to clipboard: {e}", exc_info=True)
return False
def _find_chatgpt_input_box(self):
"""
Find the ChatGPT input box using a more reliable method.
Returns (x, y) coordinates if found, None otherwise.
"""
try:
import pyautogui
import pygetwindow as gw
# Try to find ChatGPT window by title (più veloce)
windows = gw.getWindowsWithTitle('ChatGPT')
if not windows:
windows = gw.getWindowsWithTitle('chatgpt')
if windows:
window = windows[0]
window.activate()
time.sleep(0.2)
# Get window dimensions for relative positioning
window_x, window_y = window.topleft
window_width, window_height = window.size
# ChatGPT's input box is typically at the bottom center
input_x = window_x + (window_width // 2)
input_y = window_y + window_height - 100 # About 100px from bottom
logger.info(f"[find-input] Found ChatGPT window, input at: ({input_x}, {input_y})")
return (input_x, input_y)
# Fallback: Use screen coordinates based on common resolutions
screen_width, screen_height = pyautogui.size()
# Try different positions (ChatGPT's UI might vary)
input_x = screen_width // 2
input_y = screen_height - 100 # Bottom center
logger.info(f"[find-input] Using fallback position: ({input_x}, {input_y})")
return (input_x, input_y)
except ImportError:
logger.warning("[find-input] pygetwindow not available")
return None
except Exception as e:
logger.error(f"[find-input] Error finding input box: {e}")
return None
def _detect_gpt_page_loaded(self, max_wait_time=30):
"""
Detect if ChatGPT page is fully loaded by checking for specific elements.
Returns True when page is ready, False if timeout.
"""
try:
import pyautogui
import pygetwindow as gw
start_time = time.time()
logger.info(f"[detect-gpt] Starting detection (max {max_wait_time}s)")
while time.time() - start_time < max_wait_time:
# 1. Check if any browser window with ChatGPT is active
browser_names = ['Chrome', 'Firefox', 'Edge', 'Brave', 'Safari', 'Opera']
chatgpt_window = None
for name in browser_names:
windows = gw.getWindowsWithTitle(name)
for window in windows:
if window.title and ('ChatGPT' in window.title or 'chat.openai.com' in window.title):
chatgpt_window = window
break
if chatgpt_window:
break
if not chatgpt_window:
logger.info(f"[detect-gpt] No ChatGPT window found yet, waiting... ({time.time() - start_time:.1f}s)")
time.sleep(1)
continue
# 2. Activate the window
try:
chatgpt_window.activate()
time.sleep(0.5)
except:
pass
# 3. Try to detect if page is loaded by looking for specific elements
# Method: Look for common ChatGPT UI elements by color/screenshot
# Get window region
window_x, window_y = chatgpt_window.topleft
window_width, window_height = chatgpt_window.size
# Define regions to check (ChatGPT specific)
check_regions = [
(window_x + window_width // 2, window_y + window_height - 50), # Input area (bottom center)
(window_x + 50, window_y + 50), # Top-left logo area
(window_x + window_width - 50, window_y + 50), # Top-right menu
]
# Take a screenshot of a small region to check
try:
# Check a small area in the input region (should be white/gray when loaded)
check_region = (
window_x + window_width // 2 - 10,
window_y + window_height - 60,
window_x + window_width // 2 + 10,
window_y + window_height - 40
)
screenshot = pyautogui.screenshot(region=check_region)
# Simple check: count non-white pixels (input box should not be pure white when loaded)
pixels = list(screenshot.getdata())
white_pixels = sum(1 for pixel in pixels if pixel[0] > 200 and pixel[1] > 200 and pixel[2] > 200)
total_pixels = len(pixels)
# If less than 90% white, probably the input box is loaded
if white_pixels / total_pixels < 0.9:
logger.info(f"[detect-gpt] Page appears loaded! ({time.time() - start_time:.1f}s)")
return True
except Exception as e:
logger.warning(f"[detect-gpt] Error checking region: {e}")
# 4. Alternative: Check if window is not minimized and has reasonable size
if window_width > 100 and window_height > 100:
# Try a simple interaction test
try:
# Click in the center to ensure focus
pyautogui.click(window_x + window_width // 2, window_y + window_height // 2)
time.sleep(0.1)
# Check if we can type (simple test)
pyautogui.write('t', interval=0.05)
time.sleep(0.1)
pyautogui.press('backspace')
logger.info(f"[detect-gpt] Page is interactive! ({time.time() - start_time:.1f}s)")
return True
except:
pass
logger.info(f"[detect-gpt] Page not fully loaded yet, waiting... ({time.time() - start_time:.1f}s)")
time.sleep(2) # Wait 2 seconds between checks
logger.warning(f"[detect-gpt] Timeout after {max_wait_time}s")
return False
except ImportError:
logger.warning("[detect-gpt] Required modules not available")
return False
except Exception as e:
logger.error(f"[detect-gpt] Error detecting page: {e}")
return False
def _ensure_focus_on_input_box(self, click_x, click_y):
"""
Ensure focus is on the input box with multiple strategies.
Returns True if successful.
"""
try:
import pyautogui
# Strategy più semplice e veloce
# Click rapido 2 volte
pyautogui.click(click_x, click_y)
time.sleep(0.05)
pyautogui.click(click_x, click_y)
time.sleep(0.05)
# Verifica rapida scrivendo e cancellando un carattere
pyautogui.write('t', interval=0.03)
time.sleep(0.05)
pyautogui.press('backspace')
logger.info("[focus] Focus strategy completed")
return True
except Exception as e:
logger.error(f"[focus] Error ensuring focus: {e}")
return False
def _bring_browser_to_foreground_simple(self):
"""Simple method to bring browser to foreground - minimizza prima questa app"""
try:
# Prima minimizza questa applicazione (se non è già minimizzata)
if self.root.state() != 'iconic':
self.root.iconify()
time.sleep(0.1)
# Prova a trovare e attivare il browser
import pygetwindow as gw
import pyautogui
# Cerca finestre del browser con ChatGPT
browser_names = ['Chrome', 'Firefox', 'Edge', 'Brave', 'Safari', 'Opera']
# Primo tentativo: cerca finestre con ChatGPT
for name in browser_names:
windows = gw.getWindowsWithTitle(name)
for window in windows:
try:
if window.title and ('ChatGPT' in window.title or 'chat.openai.com' in window.title):
window.activate()
time.sleep(0.1)
return True
except:
continue
# Secondo tentativo: cerca qualsiasi finestra del browser
for name in browser_names:
windows = gw.getWindowsWithTitle(name)
if windows:
try:
window = windows[-1] # Prendi l'ultima finestra
window.activate()
time.sleep(0.1)
return True
except:
continue
# Terzo tentativo: usa Alt+Tab
try:
pyautogui.keyDown('alt')
time.sleep(0.05)
pyautogui.press('tab')
time.sleep(0.05)
pyautogui.keyUp('alt')
time.sleep(0.1)
except:
pass
return False
except ImportError:
logger.warning("pygetwindow not available for foreground control")
return False
except Exception as e:
logger.error(f"Error bringing browser to foreground: {e}")
return False
def _process_and_send_screenshot(self, screenshot, screenshot_type):
"""Save screenshot, copy image to clipboard, open ChatGPT, auto-paste via thread."""
import threading
try:
# 1. Save to disk
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
screenshot_path = tmp.name
screenshot.save(screenshot_path, 'PNG')
self.last_screenshot_path = screenshot_path
logger.info(f"Screenshot saved to: {screenshot_path}")
# 2. Copy IMAGE to clipboard as CF_DIB
clipboard_ok = self._copy_image_to_clipboard(screenshot)
# 3. Get question from text area
question = self.text_area.get(1.0, tk.END).strip()
# 4. Prima minimizza questa finestra
self.root.iconify()
time.sleep(0.1)
# 5. Open ChatGPT via subprocess
browser_path = self._get_browser_path()
self.status_var.set("Opening ChatGPT…")
if browser_path and os.path.exists(browser_path):
# Apri il browser in una nuova finestra
try:
subprocess.Popen([browser_path, "https://chat.openai.com"])
logger.info(f"Launched browser via subprocess: {browser_path}")
except:
webbrowser.open("https://chat.openai.com")
logger.info("Launched browser via webbrowser (fallback)")
else:
webbrowser.open("https://chat.openai.com")
logger.info("Launched browser via webbrowser (fallback)")
# 6. Porta il browser in primo piano
time.sleep(0.8) # Aspetta che il browser si apra
self._bring_browser_to_foreground_simple()
# 7. Auto-paste in background thread
if clipboard_ok:
t = threading.Thread(
target=self._auto_paste_screenshot_into_chatgpt,
args=(question,),
daemon=True
)
t.start()
else:
# Ripristina la finestra per mostrare il messaggio
self.root.deiconify()
self.root.lift()
messagebox.showinfo(
"Screenshot saved – manual paste needed",
f"✅ Screenshot saved to:\n{screenshot_path}\n\n"
f"⚠️ pywin32 not installed – can't auto-paste.\n"
f"Install with: pip install pywin32\n\n"
f"📝 Manual: click 📎 in ChatGPT, select the file, send."
)
self.status_var.set("Manual attachment needed")
except Exception as e:
logger.error(f"Failed to process screenshot: {e}", exc_info=True)
# Ripristina la finestra in caso di errore
self.root.deiconify()
self.root.lift()
messagebox.showerror("Error", f"Failed to process screenshot:\n{str(e)}")
self.status_var.set("Screenshot processing failed")
def _get_browser_path(self):
"""Return the resolved browser executable path based on current settings."""
browser_type = self.browser_var.get()
if browser_type == "chrome":
return self.find_chrome_path()
elif browser_type == "firefox":
return self.find_firefox_path()
elif browser_type == "edge":
return self.find_edge_path()
elif browser_type == "brave":
return self.find_brave_path()
elif browser_type == "custom":
return self.custom_path_var.get()
return None
def _auto_paste_screenshot_into_chatgpt(self, question):
"""
Optimized auto-paste with detection of page load when Wait GPT is enabled.
"""
import pyautogui
# TIMING OTTIMIZZATO - USO LA LATENZA IMPOSTATA DALL'UTENTE O DETECTION
WAIT_AFTER_FOCUS = 0.1
WAIT_IMG_CONSUME = 0.2
try:
# --- 1. Prima di tutto, assicurati che il browser sia in primo piano ---
logger.info("[paste-thread] Ensuring browser is in foreground...")
self._bring_browser_to_foreground_simple()
time.sleep(0.3)
# --- 2. Wait for page to load (USANDO LA LATENZA O DETECTION) ---
if self.wait_gpt_loaded:
# Modalità "Wait GPT": aspetta che la pagina sia effettivamente caricata
logger.info("[paste-thread] Wait GPT enabled - detecting page load...")
self.status_var.set("Detecting ChatGPT page load...")
page_loaded = self._detect_gpt_page_loaded()
if page_loaded:
logger.info("[paste-thread] ChatGPT page detected as loaded!")
self.status_var.set("ChatGPT page loaded - proceeding...")
else:
logger.warning("[paste-thread] Page not detected, using fallback delay")
# Fallback: usa la latenza impostata
self.status_var.set(f"Using fallback delay ({self.startup_latency:.1f}s)...")
time.sleep(self.startup_latency)
else:
# Modalità normale: usa la latenza impostata
logger.info(f"[paste-thread] Wait GPT disabled - waiting {self.startup_latency:.1f}s")
self.status_var.set(f"Waiting for ChatGPT ({self.startup_latency:.1f}s)...")
time.sleep(self.startup_latency)
# --- 3. Find and focus on input box ---
logger.info("[paste-thread] Finding ChatGPT input box...")
input_coords = self._find_chatgpt_input_box()
if input_coords:
click_x, click_y = input_coords
logger.info(f"[paste-thread] Input box at: ({click_x}, {click_y})")
else:
# Fallback rapido
screen_w, screen_h = pyautogui.size()
click_x = screen_w // 2
click_y = screen_h - 100
logger.info(f"[paste-thread] Using default position: ({click_x}, {click_y})")
# --- 4. Assicura il focus ---
logger.info("[paste-thread] Ensuring focus on input box...")
self._ensure_focus_on_input_box(click_x, click_y)
time.sleep(WAIT_AFTER_FOCUS)
# --- 5. Clear any existing text in input (più veloce) ---
logger.info("[paste-thread] Clearing any existing text...")
pyautogui.hotkey('ctrl', 'a')
time.sleep(0.05)
pyautogui.press('delete')
time.sleep(0.2)
# --- 6. Paste IMAGE (UNA SOLA VOLTA) ---
logger.info("[paste-thread] Pasting IMAGE (Ctrl+V)…")
pyautogui.hotkey('ctrl', 'v')
logger.info("[paste-thread] Image Ctrl+V sent (solo una volta).")
time.sleep(0.5)
# --- 7. Wait for browser to consume image (ridotto) ---
logger.info(f"[paste-thread] Waiting {WAIT_IMG_CONSUME}s for browser to consume image…")
time.sleep(WAIT_IMG_CONSUME)
# --- 8. Overwrite clipboard with question text (più veloce) ---
if question:
try:
import win32clipboard
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
if isinstance(question, str):
question = question.encode('utf-8')
win32clipboard.SetClipboardData(win32clipboard.CF_TEXT, question)
win32clipboard.CloseClipboard()
logger.info("[paste-thread] Question text set on clipboard")
except Exception as e:
logger.error(f"[paste-thread] Failed to set text on clipboard: {e}")
try:
import pyperclip
pyperclip.copy(question.decode('utf-8') if isinstance(question, bytes) else question)
logger.info("[paste-thread] Question text set via pyperclip")
except:
pass
time.sleep(0.2)
# --- 9. Paste TEXT (più veloce) ---
logger.info("[paste-thread] Pasting TEXT (Ctrl+V)…")
pyautogui.hotkey('ctrl', 'v')
logger.info("[paste-thread] Text Ctrl+V sent.")
time.sleep(0.3)
# --- 10. Auto-submit (più veloce) ---
if self.auto_submit_var.get():
logger.info("[paste-thread] Auto-submitting (Enter)…")
pyautogui.press('enter')
logger.info("[paste-thread] Enter pressed – question submitted.")
# IMPORTANTE: NON ripristinare la finestra qui!
# Aggiorna solo lo status
self.status_var.set("✅ Screenshot + question sent to ChatGPT!")
else:
self.status_var.set("✅ Screenshot + question pasted – press Enter to send")
else:
self.status_var.set("✅ Screenshot pasted – type your question and press Enter")
except ImportError as e:
logger.error(f"[paste-thread] Missing module: {e}")
self.status_var.set("Auto-paste failed – missing module")
except Exception as e:
logger.error(f"[paste-thread] Auto-paste failed: {e}", exc_info=True)
self.status_var.set("Auto-paste failed – see log")
def open_last_screenshot(self):
"""Open the last captured screenshot"""
if self.last_screenshot_path and os.path.exists(self.last_screenshot_path):
try:
if sys.platform == "win32":
os.startfile(self.last_screenshot_path)
elif sys.platform == "darwin":
subprocess.run(["open", self.last_screenshot_path])
else:
subprocess.run(["xdg-open", self.last_screenshot_path])
self.status_var.set(f"Opened: {os.path.basename(self.last_screenshot_path)}")
except Exception as e:
messagebox.showerror("Error", f"Cannot open file:\n{e}")
else:
messagebox.showinfo("No Screenshot", "No screenshot has been captured yet.")
# =========================================================================
# BROWSER METHODS
# =========================================================================
def send_to_chatgpt(self):
"""Send the question to ChatGPT via browser"""
question = self.text_area.get(1.0, tk.END).strip()
if not question:
messagebox.showwarning("Empty Question", "Please enter a question first!")
return
browser_type = self.browser_var.get()
auto_submit = self.auto_submit_var.get()
if auto_submit:
self.send_with_pyautogui(question, browser_type)
else:
self.send_with_url(question, browser_type)
def send_with_pyautogui(self, question, browser_type):
"""Send question using PyAutoGUI for auto-submit"""
try:
import pyautogui
encoded_question = urllib.parse.quote(question)
chatgpt_url = f"https://chat.openai.com/?q={encoded_question}"
# Prima minimizza questa finestra
self.root.iconify()
time.sleep(0.1)
# Apri il browser
if browser_type == "default":
webbrowser.open(chatgpt_url)
else:
browser_path = self._get_browser_path()
if browser_path and os.path.exists(browser_path):
try:
subprocess.Popen([browser_path, chatgpt_url])
except:
webbrowser.open(chatgpt_url)
else:
webbrowser.open(chatgpt_url)
# Porta il browser in primo piano
time.sleep(0.8)
self._bring_browser_to_foreground_simple()
# Usa la latenza impostata dall'utente prima di premere Enter
time.sleep(self.startup_latency)
pyautogui.press('enter')
self.status_var.set("Question submitted successfully!")
except ImportError:
# Ripristina la finestra
self.root.deiconify()
self.root.lift()
messagebox.showerror("PyAutoGUI Required",
"PyAutoGUI is not installed!\n\n"
"Install: pip install pyautogui\n"
"Or disable auto-submit.")
self.status_var.set("Error: PyAutoGUI not installed")
except Exception as e:
# Ripristina la finestra
self.root.deiconify()
self.root.lift()
messagebox.showerror("Error", f"Failed to auto-submit:\n{str(e)}")
self.status_var.set(f"Error: {str(e)}")
def send_with_url(self, question, browser_type):
"""Send question using URL parameter"""
encoded_question = urllib.parse.quote(question)
chatgpt_url = f"https://chat.openai.com/?q={encoded_question}"
try:
# Prima minimizza questa finestra
self.root.iconify()
time.sleep(0.1)
# Apri il browser
if browser_type == "default":
webbrowser.open(chatgpt_url)
else:
browser_path = self._get_browser_path()
if browser_path and os.path.exists(browser_path):
try:
subprocess.Popen([browser_path, chatgpt_url])
except:
webbrowser.open(chatgpt_url)
else:
webbrowser.open(chatgpt_url)
# Porta il browser in primo piano
time.sleep(0.8)
self._bring_browser_to_foreground_simple()
self.status_var.set("Opened in browser (press Enter to send)")
except Exception as e:
# Ripristina la finestra
self.root.deiconify()
self.root.lift()
messagebox.showerror("Error", f"Failed to open browser:\n{str(e)}")
self.status_var.set(f"Error: {str(e)}")
def open_browser(self, browser_type, url):
"""Open URL in specified browser (legacy method)"""
logger.info(f"Opening {browser_type} with URL: {url}")
if browser_type == "default":
webbrowser.open(url)
self.status_var.set(f"Opened: {url}")
return
browser_path = None
if browser_type == "chrome":
browser_path = self.find_chrome_path()
elif browser_type == "firefox":
browser_path = self.find_firefox_path()
elif browser_type == "edge":
browser_path = self.find_edge_path()
elif browser_type == "brave":
browser_path = self.find_brave_path()
elif browser_type == "custom":
browser_path = self.custom_path_var.get()
if browser_path and os.path.exists(browser_path):
try:
browser_name = f"custom_{browser_type}"
webbrowser.register(browser_name, None, webbrowser.BackgroundBrowser(browser_path))
webbrowser.get(browser_name).open(url)
self.status_var.set(f"Opened in {browser_type}")
except Exception as e:
logger.warning(f"Failed to open with {browser_type}: {e}, falling back to default")
webbrowser.open(url)
self.status_var.set(f"{browser_type} failed, using default")
else:
logger.warning(f"{browser_type} not found, falling back to default")
webbrowser.open(url)
self.status_var.set(f"{browser_type} not found, using default")
def find_chrome_path(self):
"""Find Chrome executable path"""
paths = [
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
]
return self._find_first_existing(paths)
def find_firefox_path(self):
"""Find Firefox executable path"""
paths = [
r"C:\Program Files\Mozilla Firefox\firefox.exe",
r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe",
]
return self._find_first_existing(paths)
def find_edge_path(self):
"""Find Edge executable path"""
paths = [
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
]
return self._find_first_existing(paths)
def find_brave_path(self):
"""Find Brave executable path"""
paths = [
r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",
r"C:\Program Files (x86)\BraveSoftware\Brave-Browser\Application\brave.exe",
os.path.expanduser(r"~\AppData\Local\BraveSoftware\Brave-Browser\Application\brave.exe"),
]
return self._find_first_existing(paths)
def _find_first_existing(self, paths):
"""Find first existing path in list"""
for path in paths:
if os.path.exists(path):
return path
return None
def main():
root = tk.Tk()
app = ChatGPTBrowserTool(root)
root.mainloop()
if __name__ == "__main__":
main()
Commenti
Posta un commento