Skip to content

Commit 89b068b

Browse files
Copilotthawn
andcommitted
Make tkinter import optional to fix Docker build failures
Co-authored-by: thawn <1308449+thawn@users.noreply.github.com>
1 parent a57cbcc commit 89b068b

File tree

1 file changed

+64
-47
lines changed

1 file changed

+64
-47
lines changed

src/ttmp32gme/gui_handler.py

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@
44
import platform
55
import sys
66
import threading
7-
import tkinter as tk
8-
from tkinter import ttk
9-
from typing import TYPE_CHECKING, Callable, Optional
7+
from typing import TYPE_CHECKING, Any, Callable
108

119
if TYPE_CHECKING:
1210
from flask import Flask
1311

1412
logger = logging.getLogger(__name__)
1513

14+
# Check if tkinter is available (it won't be in Docker/headless environments)
15+
_tkinter_available = True
16+
tk: Any = None
17+
ttk: Any = None
18+
try:
19+
import tkinter as tk # noqa: F811
20+
from tkinter import ttk # noqa: F811
21+
except ImportError:
22+
_tkinter_available = False
23+
logger.debug("tkinter not available - GUI will be disabled")
24+
1625

1726
class ServerStatusWindow:
1827
"""A simple status window for displaying server information and shutdown control.
@@ -33,73 +42,76 @@ def __init__(self, host: str, port: int, shutdown_callback: Callable[[], None]):
3342
port: Server port number
3443
shutdown_callback: Function to call when server should be shut down
3544
"""
45+
if not _tkinter_available:
46+
raise RuntimeError("tkinter is not available - GUI cannot be used")
47+
3648
self.host = host
3749
self.port = port
3850
self.shutdown_callback = shutdown_callback
39-
self.root: Optional[tk.Tk] = None
51+
self.root: Any = None
4052
self.is_running = False
41-
self.logs_window: Optional[tk.Toplevel] = None
42-
self.logs_text: Optional[tk.Text] = None
53+
self.logs_window: Any = None
54+
self.logs_text: Any = None
4355

4456
def create_window(self) -> None:
4557
"""Create and configure the tkinter window."""
46-
self.root = tk.Tk()
47-
self.root.title("ttmp32gme Server")
48-
self.root.geometry("400x250")
49-
self.root.resizable(False, False)
58+
self.root = tk.Tk() # type: ignore[union-attr]
59+
self.root.title("ttmp32gme Server") # type: ignore[union-attr]
60+
self.root.geometry("400x250") # type: ignore[union-attr]
61+
self.root.resizable(False, False) # type: ignore[union-attr]
5062

5163
# Handle window close event
52-
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
64+
self.root.protocol("WM_DELETE_WINDOW", self.on_close) # type: ignore[union-attr]
5365

5466
# Create main frame with padding
55-
main_frame = ttk.Frame(self.root, padding="20")
67+
main_frame = ttk.Frame(self.root, padding="20") # type: ignore[union-attr]
5668
main_frame.grid(row=0, column=0, sticky="wens")
5769

5870
# Title label
59-
title_label = ttk.Label(
71+
title_label = ttk.Label( # type: ignore[union-attr]
6072
main_frame, text="TipToi MP3 GME Converter", font=("Helvetica", 16, "bold")
6173
)
6274
title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20))
6375

6476
# Server status section
65-
status_label = ttk.Label(
77+
status_label = ttk.Label( # type: ignore[union-attr]
6678
main_frame, text="Server is running", font=("Helvetica", 12)
6779
)
6880
status_label.grid(row=1, column=0, columnspan=2, pady=(0, 10))
6981

7082
# URL display
7183
url = f"http://{self.host}:{self.port}/"
72-
url_frame = ttk.Frame(main_frame)
84+
url_frame = ttk.Frame(main_frame) # type: ignore[union-attr]
7385
url_frame.grid(row=2, column=0, columnspan=2, pady=(0, 20))
7486

75-
ttk.Label(url_frame, text="URL:").grid(row=0, column=0, padx=(0, 5))
76-
url_entry = ttk.Entry(url_frame, width=30)
87+
ttk.Label(url_frame, text="URL:").grid(row=0, column=0, padx=(0, 5)) # type: ignore[union-attr]
88+
url_entry = ttk.Entry(url_frame, width=30) # type: ignore[union-attr]
7789
url_entry.insert(0, url)
7890
url_entry.config(state="readonly")
7991
url_entry.grid(row=0, column=1)
8092

8193
# Buttons frame
82-
button_frame = ttk.Frame(main_frame)
94+
button_frame = ttk.Frame(main_frame) # type: ignore[union-attr]
8395
button_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0))
8496

8597
# Open Browser button
86-
open_button = ttk.Button(
98+
open_button = ttk.Button( # type: ignore[union-attr]
8799
button_frame, text="Open Browser", command=self.open_browser
88100
)
89101
open_button.grid(row=0, column=0, padx=(0, 5))
90102

91103
# Show Logs button
92-
logs_button = ttk.Button(button_frame, text="Show Logs", command=self.show_logs)
104+
logs_button = ttk.Button(button_frame, text="Show Logs", command=self.show_logs) # type: ignore[union-attr]
93105
logs_button.grid(row=0, column=1, padx=(0, 5))
94106

95107
# Stop Server button
96-
stop_button = ttk.Button(
108+
stop_button = ttk.Button( # type: ignore[union-attr]
97109
button_frame, text="Stop Server", command=self.on_close
98110
)
99111
stop_button.grid(row=0, column=2)
100112

101113
# Info label at the bottom
102-
info_label = ttk.Label(
114+
info_label = ttk.Label( # type: ignore[union-attr]
103115
main_frame,
104116
text="Close this window to stop the server",
105117
font=("Helvetica", 9),
@@ -115,65 +127,65 @@ def open_browser(self) -> None:
115127

116128
def show_logs(self) -> None:
117129
"""Open a window showing server logs."""
118-
if self.logs_window and tk.Toplevel.winfo_exists(self.logs_window):
130+
if self.logs_window and tk.Toplevel.winfo_exists(self.logs_window): # type: ignore[union-attr]
119131
# Window already exists, just raise it
120-
self.logs_window.lift()
121-
self.logs_window.focus_force()
132+
self.logs_window.lift() # type: ignore[union-attr]
133+
self.logs_window.focus_force() # type: ignore[union-attr]
122134
return
123135

124136
# Create logs window
125-
self.logs_window = tk.Toplevel(self.root)
126-
self.logs_window.title("Server Logs")
127-
self.logs_window.geometry("700x500")
137+
self.logs_window = tk.Toplevel(self.root) # type: ignore[union-attr]
138+
self.logs_window.title("Server Logs") # type: ignore[union-attr]
139+
self.logs_window.geometry("700x500") # type: ignore[union-attr]
128140

129141
# Create frame for logs
130-
logs_frame = ttk.Frame(self.logs_window, padding="10")
142+
logs_frame = ttk.Frame(self.logs_window, padding="10") # type: ignore[union-attr]
131143
logs_frame.grid(row=0, column=0, sticky="nsew")
132-
self.logs_window.grid_rowconfigure(0, weight=1)
133-
self.logs_window.grid_columnconfigure(0, weight=1)
144+
self.logs_window.grid_rowconfigure(0, weight=1) # type: ignore[union-attr]
145+
self.logs_window.grid_columnconfigure(0, weight=1) # type: ignore[union-attr]
134146

135147
# Create scrolled text widget for logs
136-
logs_text_frame = ttk.Frame(logs_frame)
148+
logs_text_frame = ttk.Frame(logs_frame) # type: ignore[union-attr]
137149
logs_text_frame.grid(row=0, column=0, sticky="nsew")
138150
logs_frame.grid_rowconfigure(0, weight=1)
139151
logs_frame.grid_columnconfigure(0, weight=1)
140152

141153
# Text widget with scrollbar
142-
scrollbar = ttk.Scrollbar(logs_text_frame)
143-
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
154+
scrollbar = ttk.Scrollbar(logs_text_frame) # type: ignore[union-attr]
155+
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # type: ignore[union-attr]
144156

145-
self.logs_text = tk.Text(
157+
self.logs_text = tk.Text( # type: ignore[union-attr]
146158
logs_text_frame,
147-
wrap=tk.WORD,
159+
wrap=tk.WORD, # type: ignore[union-attr]
148160
yscrollcommand=scrollbar.set,
149161
font=("Courier", 10),
150162
bg="white",
151163
fg="black",
152164
)
153-
self.logs_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
154-
scrollbar.config(command=self.logs_text.yview)
165+
self.logs_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # type: ignore[union-attr]
166+
scrollbar.config(command=self.logs_text.yview) # type: ignore[union-attr]
155167

156168
# Buttons frame at bottom
157-
button_frame = ttk.Frame(logs_frame)
169+
button_frame = ttk.Frame(logs_frame) # type: ignore[union-attr]
158170
button_frame.grid(row=1, column=0, pady=(10, 0))
159171

160172
# Refresh button
161-
refresh_button = ttk.Button(
173+
refresh_button = ttk.Button( # type: ignore[union-attr]
162174
button_frame, text="Refresh", command=self.refresh_logs
163175
)
164-
refresh_button.pack(side=tk.LEFT, padx=(0, 5))
176+
refresh_button.pack(side=tk.LEFT, padx=(0, 5)) # type: ignore[union-attr]
165177

166178
# Clear button
167-
clear_button = ttk.Button(
179+
clear_button = ttk.Button( # type: ignore[union-attr]
168180
button_frame, text="Clear", command=self.clear_logs_display
169181
)
170-
clear_button.pack(side=tk.LEFT, padx=(0, 5))
182+
clear_button.pack(side=tk.LEFT, padx=(0, 5)) # type: ignore[union-attr]
171183

172184
# Close button
173-
close_button = ttk.Button(
174-
button_frame, text="Close", command=self.logs_window.destroy
185+
close_button = ttk.Button( # type: ignore[union-attr]
186+
button_frame, text="Close", command=self.logs_window.destroy # type: ignore[union-attr]
175187
)
176-
close_button.pack(side=tk.LEFT)
188+
close_button.pack(side=tk.LEFT) # type: ignore[union-attr]
177189

178190
# Load initial logs
179191
self.refresh_logs()
@@ -241,11 +253,16 @@ def should_use_gui() -> bool:
241253
- Running on macOS
242254
- Running from PyInstaller bundle
243255
- Not in development mode
256+
- tkinter is available
244257
245258
Returns:
246259
True if GUI should be used, False otherwise
247260
"""
248-
return platform.system() == "Darwin" and getattr(sys, "frozen", False)
261+
return (
262+
_tkinter_available
263+
and platform.system() == "Darwin"
264+
and getattr(sys, "frozen", False)
265+
)
249266

250267

251268
def run_server_with_gui(app: "Flask", host: str, port: int) -> None:

0 commit comments

Comments
 (0)