Skip to content

Commit 1492fa6

Browse files
authored
✨ Initial setup: GUI + build files
Main GUI script, custom icons, default config and PyInstaller build tools included.
1 parent cdc72e4 commit 1492fa6

File tree

6 files changed

+419
-0
lines changed

6 files changed

+419
-0
lines changed

build.bat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@echo off
2+
echo ------------------------------------------
3+
echo 🧼 Cleaning previous build...
4+
echo ------------------------------------------
5+
rmdir /s /q build
6+
rmdir /s /q dist
7+
8+
echo ------------------------------------------
9+
echo 🔨 Building Chromium Updater GUI (.exe)...
10+
echo ------------------------------------------
11+
12+
pyinstaller main_gui.spec
13+
14+
echo.
15+
echo ✅ Build complete! Check the 'dist' folder.
16+
pause

chromium_updater_icon.ico

59.7 KB
Binary file not shown.

chromium_updater_icon.png

1.07 MB
Loading

main_gui.py

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
# Modern Chromium Updater with Dark Mode (customtkinter)
2+
# SHA256 validation, sync/nosync selection, embedded log viewer, scheduler support
3+
# Developed by Fatih | Designed with customtkinter for a modern, user-friendly experience
4+
# Requirements: pip install customtkinter requests pefile plyer
5+
6+
# Copyright (c) 2025 Fatih
7+
# This tool is provided as-is under the MIT License.
8+
9+
import customtkinter as ctk
10+
from tkinter import filedialog
11+
import os, json, subprocess, sys, tempfile, threading, time, requests, pefile, hashlib, webbrowser
12+
from datetime import datetime
13+
from plyer import notification
14+
15+
# === CONFIG ===
16+
CONFIG_FILE = "settings.json"
17+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
18+
LOG_FILE = os.path.join(SCRIPT_DIR, "chromium_updater.log")
19+
ICON_FILE = os.path.join(SCRIPT_DIR, "chromium_updater_icon.ico")
20+
TEMP_DIR = tempfile.gettempdir()
21+
VERS_FILE = os.path.join(TEMP_DIR, "chromium_last_version.txt")
22+
GITHUB_API = "https://api.github.com/repos/Hibbiki/chromium-win64/releases/latest" # Uses Hibbiki's Chromium builds
23+
VERSION = "1.0.0"
24+
25+
DEFAULT_CONFIG = {
26+
"install_path": "",
27+
"notifications": True,
28+
"check_interval": "manual",
29+
"enable_scheduler": False,
30+
"download_type": "sync"
31+
}
32+
33+
# === CORE FUNCTIONS ===
34+
def log(msg):
35+
ts = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
36+
line = f"{ts} {msg}\n"
37+
with open(LOG_FILE, "a", encoding="utf-8") as f:
38+
f.write(line)
39+
if logbox:
40+
logbox.insert("end", line)
41+
logbox.see("end")
42+
43+
def clear_log():
44+
if os.path.exists(LOG_FILE):
45+
with open(LOG_FILE, "w", encoding="utf-8") as f:
46+
f.write("")
47+
if logbox:
48+
logbox.delete("0.0", "end")
49+
log("[✓] Log cleared")
50+
51+
def notify(title, msg, enabled=True):
52+
if enabled:
53+
try:
54+
icon_path = os.path.join(SCRIPT_DIR, "chromium_updater_icon.ico")
55+
notification.notify(
56+
title=title,
57+
message=msg,
58+
app_name="Chromium Updater",
59+
app_icon=icon_path,
60+
timeout=6
61+
)
62+
except Exception as e:
63+
log(f"[X] Notification error: {e}")
64+
65+
def version_tuple(v):
66+
import re
67+
m = re.match(r"(\d+)\.(\d+)\.(\d+)\.(\d+)", v.strip("v"))
68+
return tuple(map(int, m.groups())) if m else (0,0,0,0)
69+
70+
def get_chrome_version(path):
71+
try:
72+
pe = pefile.PE(path)
73+
for fileinfo in pe.FileInfo:
74+
for entry in fileinfo:
75+
if entry.Key == b'StringFileInfo':
76+
for st in entry.StringTable:
77+
return st.entries.get(b'ProductVersion', b'').decode()
78+
except:
79+
return ""
80+
81+
def sha256sum(filepath):
82+
h = hashlib.sha256()
83+
with open(filepath, 'rb') as f:
84+
for chunk in iter(lambda: f.read(4096), b""):
85+
h.update(chunk)
86+
return h.hexdigest()
87+
88+
def download_with_progress(url, dest, cb=None):
89+
r = requests.get(url, stream=True)
90+
total = int(r.headers.get('content-length', 0))
91+
downloaded = 0
92+
start = time.time()
93+
with open(dest, 'wb') as f:
94+
for chunk in r.iter_content(chunk_size=8192):
95+
if chunk:
96+
f.write(chunk)
97+
downloaded += len(chunk)
98+
elapsed = time.time() - start
99+
eta = int((total - downloaded) / (downloaded/elapsed)) if downloaded else 0
100+
if cb: cb(downloaded, total, eta)
101+
102+
def validate_sha256(download_path, hash_url):
103+
try:
104+
r = requests.get(hash_url)
105+
expected_hash = r.text.strip().split()[0]
106+
actual_hash = sha256sum(download_path)
107+
return expected_hash == actual_hash
108+
except:
109+
return False
110+
111+
def apply_scheduler(enabled, interval):
112+
task_name = "ChromiumUpdaterAutoCheck"
113+
exe_path = os.path.abspath(sys.argv[0])
114+
if enabled:
115+
interval_map = {
116+
"daily": "DAILY",
117+
"weekly": "WEEKLY",
118+
"monthly": "MONTHLY"
119+
}
120+
if interval in interval_map:
121+
subprocess.run([
122+
"schtasks", "/Create", "/SC", interval_map[interval], "/TN", task_name,
123+
"/TR", f'\"{exe_path}\"', "/ST", "10:00", "/F"
124+
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
125+
log(f"[Scheduler] Task created: {interval_map[interval]}")
126+
else:
127+
subprocess.run(["schtasks", "/Delete", "/TN", task_name, "/F"], stderr=subprocess.DEVNULL)
128+
log("[Scheduler] Task removed")
129+
130+
# GUI bağlantısı: zamanlayıcı değiştiğinde hemen uygula
131+
132+
def scheduler_settings_updated():
133+
apply_scheduler(scheduler_var.get(), interval_var.get())
134+
135+
136+
def auto_detect_chrome():
137+
candidates = [
138+
os.path.expandvars(r"%LocalAppData%\\Chromium\\Application\\chrome.exe"),
139+
os.path.expandvars(r"%ProgramFiles%\\Chromium\\Application\\chrome.exe")
140+
]
141+
for path in candidates:
142+
if os.path.exists(path):
143+
return path
144+
return ""
145+
146+
147+
def threaded_update():
148+
threading.Thread(target=check_for_update).start()
149+
150+
def check_for_update():
151+
chrome_path = path_var.get()
152+
dl_type = dl_type_var.get()
153+
notify_enabled = notify_var.get()
154+
155+
if not os.path.exists(chrome_path):
156+
progress_label.set("Chromium not found. Please set path.")
157+
notify("Chromium Updater", "Chromium not found on this system.", notify_enabled)
158+
return
159+
160+
progress_label.set("Checking for updates...")
161+
try:
162+
r = requests.get(GITHUB_API, timeout=10)
163+
release = r.json()
164+
latest = release["tag_name"]
165+
except Exception as e:
166+
log(f"[GitHub] Error: {e}")
167+
notify("GitHub Error", str(e), notify_enabled)
168+
progress_label.set("GitHub error.")
169+
return
170+
171+
current = get_chrome_version(chrome_path)
172+
if version_tuple(current) >= version_tuple(latest):
173+
progress_label.set("Chromium is up-to-date.")
174+
notify("Chromium", "Already up-to-date", notify_enabled)
175+
return
176+
177+
asset = next((a for a in release["assets"] if f".{dl_type}.exe" in a["name"]), None)
178+
hash_asset = next((a for a in release["assets"] if f".{dl_type}.exe.sha256sum" in a["name"]), None)
179+
if not asset or not hash_asset:
180+
progress_label.set("Installer or hash missing.")
181+
notify("Missing Asset", "Installer or checksum file missing", notify_enabled)
182+
return
183+
184+
url = asset["browser_download_url"]
185+
hash_url = hash_asset["browser_download_url"]
186+
filename = os.path.join(TEMP_DIR, asset["name"])
187+
188+
def update_bar(done, total, eta):
189+
percent = int((done / total) * 100)
190+
bar.set(percent)
191+
m, s = divmod(eta, 60)
192+
progress_label.set(f"Downloading... {percent}% | ETA: {m:02}:{s:02}")
193+
194+
download_with_progress(url, filename, cb=update_bar)
195+
196+
progress_label.set("Verifying file integrity...")
197+
if not validate_sha256(filename, hash_url):
198+
progress_label.set("SHA256 mismatch! Aborting.")
199+
notify("Security Warning", "Downloaded file failed hash check", notify_enabled)
200+
return
201+
202+
subprocess.Popen([filename], shell=True)
203+
with open(VERS_FILE, 'w') as f:
204+
f.write(latest)
205+
progress_label.set("Installation started.")
206+
notify("Chromium Updated", f"{latest} installed.", notify_enabled)
207+
208+
# === GUI SETUP ===
209+
ctk.set_appearance_mode("dark")
210+
ctk.set_default_color_theme("blue")
211+
root = ctk.CTk()
212+
root.title("Chromium Updater")
213+
root.geometry("600x680")
214+
215+
path_var = ctk.StringVar()
216+
notify_var = ctk.BooleanVar(value=True)
217+
dl_type_var = ctk.StringVar(value="sync")
218+
progress_label = ctk.StringVar(value="Idle.")
219+
scheduler_var = ctk.BooleanVar(value=False)
220+
interval_var = ctk.StringVar(value="daily")
221+
logbox = None
222+
223+
config = DEFAULT_CONFIG
224+
if os.path.exists(CONFIG_FILE):
225+
with open(CONFIG_FILE, "r") as f:
226+
config.update(json.load(f))
227+
228+
if not config["install_path"]:
229+
detected = auto_detect_chrome()
230+
config["install_path"] = detected
231+
if detected:
232+
notify("Chromium Updater", f"Chromium detected at: {detected}")
233+
else:
234+
notify("Chromium Updater", "Chromium not detected.")
235+
236+
path_var.set(config["install_path"])
237+
notify_var.set(config["notifications"])
238+
dl_type_var.set(config.get("download_type", "sync"))
239+
scheduler_var.set(config.get("enable_scheduler", False))
240+
interval_var.set(config.get("check_interval", "daily"))
241+
242+
frame = ctk.CTkFrame(root, corner_radius=8)
243+
frame.pack(padx=6, pady=6, fill="both", expand=True)
244+
245+
ctk.CTkLabel(frame, text="Chromium Executable Path:").pack(anchor="w")
246+
row = ctk.CTkFrame(frame)
247+
row.pack(fill="x")
248+
ctk.CTkEntry(row, textvariable=path_var).pack(side="left", fill="x", expand=True)
249+
ctk.CTkButton(row, text="Browse", command=lambda: path_var.set(filedialog.askopenfilename(filetypes=[("Executable Files", "*.exe")]))).pack(side="left", padx=5)
250+
251+
ctk.CTkLabel(frame, text="Download Type:").pack(anchor="w", pady=(10,0))
252+
drop = ctk.CTkOptionMenu(frame, variable=dl_type_var, values=["sync", "nosync"])
253+
drop.pack(fill="x")
254+
ctk.CTkLabel(
255+
frame,
256+
text="\nSYNC VERSION:\n• Standard Chromium build with Google Sign-in & Sync functionality.\n\nNO SYNC VERSION:\n• Ungoogled Chromium – Privacy-enhanced version without Google services.",
257+
text_color="gray",
258+
justify="left",
259+
font=ctk.CTkFont(size=12)
260+
).pack(anchor="w")
261+
262+
ctk.CTkLabel(frame, text="Schedule Auto-Update:").pack(anchor="w", pady=(10,0))
263+
scheduler_frame = ctk.CTkFrame(frame)
264+
scheduler_frame.pack(fill="x")
265+
ctk.CTkCheckBox(scheduler_frame, text="Enable Scheduler", variable=scheduler_var, command=scheduler_settings_updated).pack(side="left")
266+
interval_menu = ctk.CTkOptionMenu(scheduler_frame, variable=interval_var, values=["daily", "weekly", "monthly"], command=lambda _: scheduler_settings_updated())
267+
interval_menu.pack(side="right")
268+
269+
ctk.CTkCheckBox(frame, text="Show Desktop Notifications", variable=notify_var).pack(anchor="w", pady=10)
270+
271+
ctk.CTkButton(frame, text="Check for Updates", command=threaded_update).pack(pady=10)
272+
273+
bar = ctk.CTkProgressBar(frame, width=400)
274+
bar.set(0)
275+
bar.pack(pady=5)
276+
ctk.CTkLabel(frame, textvariable=progress_label).pack()
277+
278+
ctk.CTkLabel(frame, text="Update Log:").pack(anchor="w", pady=(10,0))
279+
logbox = ctk.CTkTextbox(frame, height=160)
280+
logbox.pack(fill="both", expand=True)
281+
if os.path.exists(LOG_FILE):
282+
with open(LOG_FILE, "r", encoding="utf-8") as f:
283+
logbox.insert("0.0", f.read())
284+
285+
# === LOG CLEAR BUTTON ===
286+
ctk.CTkButton(frame, text="Clear Log", command=clear_log).pack(pady=5)
287+
288+
# Developer credit footer (clickable GitHub profile)
289+
def open_github():
290+
webbrowser.open("https://github.com/fatih-gh")
291+
292+
footer_frame = ctk.CTkFrame(root, fg_color="transparent")
293+
footer_frame.pack(pady=(0, 4), fill="x")
294+
295+
def open_repo():
296+
webbrowser.open("https://github.com/fatih-gh/ChroMate/tree/main")
297+
298+
def open_license():
299+
webbrowser.open("https://github.com/fatih-gh/ChroMate/blob/main/LICENSE")
300+
301+
302+
ctk.CTkButton(
303+
footer_frame,
304+
text=f"Version {VERSION}",
305+
command=open_repo,
306+
width=1,
307+
height=1,
308+
fg_color="transparent",
309+
text_color="gray",
310+
font=ctk.CTkFont(size=11),
311+
hover=True
312+
).pack(side="right", padx=10)
313+
314+
ctk.CTkButton(
315+
footer_frame,
316+
text="GPLv3 License",
317+
command=open_license,
318+
width=1,
319+
height=1,
320+
fg_color="transparent",
321+
text_color="gray",
322+
font=ctk.CTkFont(size=11),
323+
hover=True
324+
).pack(side="right")
325+
326+
327+
ctk.CTkButton(footer_frame, text="Developed by Fatih © 2025", text_color="gray", font=ctk.CTkFont(size=13, weight="bold"), fg_color="transparent", hover=False, command=open_github).pack(side="left", padx=10)
328+
329+
# Save config and apply scheduler on close
330+
def on_exit():
331+
data = {
332+
"install_path": path_var.get(),
333+
"notifications": notify_var.get(),
334+
"check_interval": interval_var.get(),
335+
"enable_scheduler": scheduler_var.get(),
336+
"download_type": dl_type_var.get()
337+
}
338+
with open(CONFIG_FILE, "w") as f:
339+
json.dump(data, f, indent=4)
340+
apply_scheduler(data["enable_scheduler"], data["check_interval"])
341+
root.destroy()
342+
343+
root.protocol("WM_DELETE_WINDOW", on_exit)
344+
root.mainloop()

0 commit comments

Comments
 (0)