Skip to content

Instantly share code, notes, and snippets.

@Wowfunhappy
Last active October 29, 2025 12:16
Show Gist options
  • Select an option

  • Save Wowfunhappy/48df1d390f0eb8e25088d3df4a904684 to your computer and use it in GitHub Desktop.

Select an option

Save Wowfunhappy/48df1d390f0eb8e25088d3df4a904684 to your computer and use it in GitHub Desktop.
Setup Thonny on OS X 10.9. Run from a Python 3.10 virtual environment created with --system-site-packages.
"""
Complete Thonny setup script for Tk 8.5 compatibility
- Installs/reinstalls Thonny
- Converts PNG images to GIF with transparency
- Patches code for compatibility and updates all PNG references to GIF
(Script written by Claude 3.5 Sonnet)
"""
import sys
import subprocess
from pathlib import Path
from PIL import Image
import re
def main():
print("=" * 60)
print("Thonny Tk 8.5 Compatibility Setup")
print("=" * 60)
# Step 1: Install Thonny
print("\n[1/4] Installing Thonny...")
subprocess.run([
sys.executable, "-m", "pip", "install", "--force-reinstall", "thonny"
], check=True)
print("✓ Thonny installed")
# Step 2: Convert images
print("\n[2/4] Converting PNG images to GIF with transparency...")
res_dir = Path("lib/python3.10/site-packages/thonny/res")
png_files = list(res_dir.rglob("*.png"))
print(f"Found {len(png_files)} PNG files to convert")
converted = 0
errors = 0
for png_path in png_files:
gif_path = png_path.with_suffix('.gif')
try:
with Image.open(png_path) as img:
if img.mode == 'RGBA':
# Split the image
alpha = img.split()[3]
# Create RGB version
rgb_img = Image.new("RGB", img.size, (255, 255, 255))
rgb_img.paste(img, mask=alpha)
# Convert to palette mode with only 254 colors
palette_img = rgb_img.quantize(colors=254, method=2)
# Get palette and alpha data
palette_pixels = palette_img.load()
alpha_pixels = alpha.load()
# Set transparent pixels to index 255
for y in range(img.height):
for x in range(img.width):
if alpha_pixels[x, y] < 20:
palette_pixels[x, y] = 255
# Save with transparency at index 255
palette_img.save(gif_path, 'GIF', transparency=255, optimize=False)
else:
# No alpha channel, just convert
if img.mode not in ('RGB', 'P'):
img = img.convert('RGB')
img.save(gif_path, 'GIF')
converted += 1
if converted % 20 == 0:
print(f" Converted {converted}/{len(png_files)}...")
except Exception as e:
print(f" Error converting {png_path}: {e}")
errors += 1
print(f"✓ Converted {converted} files ({errors} errors)")
# Remove PNG files
print(" Removing PNG files...")
for png_path in png_files:
png_path.unlink()
print("✓ PNG files removed")
# Step 3: Patch all Python files to use .gif instead of .png
print("\n[3/4] Patching all .png references to .gif...")
thonny_dir = Path("lib/python3.10/site-packages/thonny")
py_files = list(thonny_dir.rglob("*.py"))
patched_files = 0
for py_file in py_files:
try:
content = py_file.read_text()
original_content = content
# Replace all .png references with .gif
# Handle various patterns of PNG references
patterns = [
(r'\.png"', '.gif"'),
(r"\.png'", ".gif'"),
(r'\.png\)', '.gif)'),
(r'\.png,', '.gif,'),
(r'\.png\s', '.gif '),
(r'\.png$', '.gif'),
]
for pattern, replacement in patterns:
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
if content != original_content:
py_file.write_text(content)
patched_files += 1
print(f" Patched {py_file.name}")
except Exception as e:
print(f" Error patching {py_file}: {e}")
print(f"✓ Patched {patched_files} files with PNG references")
# Step 4: Apply specific patches
print("\n[4/4] Applying specific compatibility patches...")
# Patch 1: Fix crimson color in codeview.py
codeview_path = Path("lib/python3.10/site-packages/thonny/codeview.py")
if codeview_path.exists():
content = codeview_path.read_text()
content = content.replace(
'self._gutter.tag_configure("breakpoint", foreground="crimson")',
'self._gutter.tag_configure("breakpoint", foreground="#DC143C")'
)
codeview_path.write_text(content)
print("✓ Patched codeview.py")
# Patch 2: Fix crimson color in base_syntax_themes.py
themes_path = Path("lib/python3.10/site-packages/thonny/plugins/base_syntax_themes.py")
if themes_path.exists():
content = themes_path.read_text()
content = content.replace(
'"breakpoint": {"foreground": "crimson"}',
'"breakpoint": {"foreground": "#DC143C"}'
)
themes_path.write_text(content)
print("✓ Patched base_syntax_themes.py")
# Patch 3: Add error handling to workbench.py
workbench_path = Path("lib/python3.10/site-packages/thonny/workbench.py")
if workbench_path.exists():
content = workbench_path.read_text()
# First replacement - the else block
old_text1 = """ else:
img = tk.PhotoImage(file=filename)
# can't use zoom method, because this doesn't allow name
img2 = tk.PhotoImage(tk_name)
self.tk.call(
img2,
"copy",
img.name,
"-zoom",
2,
2,
)
self._images.add(img2)
return img2
img = tk.PhotoImage(tk_name, file=filename)
self._images.add(img)
return img"""
new_text1 = """ else:
try:
img = tk.PhotoImage(file=filename)
# can't use zoom method, because this doesn't allow name
img2 = tk.PhotoImage(tk_name)
self.tk.call(
img2,
"copy",
img.name,
"-zoom",
2,
2,
)
self._images.add(img2)
return img2
except Exception:
return None
try:
img = tk.PhotoImage(tk_name, file=filename)
self._images.add(img)
return img
except Exception:
return None"""
if old_text1 in content:
content = content.replace(old_text1, new_text1)
workbench_path.write_text(content)
print("✓ Patched workbench.py")
else:
print("⚠ workbench.py already patched or format changed")
print("\n" + "=" * 60)
print("Setup complete! You can now run: thonny &")
print("=" * 60)
if __name__ == "__main__":
main()(Script written by Claude 3.5 Sonnet)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment