@@ -85,7 +85,17 @@ def __init__(self):
8585 threading .Thread (target = self ._preload_nexus_cache , daemon = True ).start ()
8686
8787 def _preload_nexus_cache (self ):
88- """Read all cached Nexus JSON files from disk (background thread)."""
88+ """Sync game profiles from GitHub, then read cached Nexus JSON files (background thread)."""
89+ # Silently download any missing game profiles
90+ try :
91+ from mm .profiles import sync_profiles
92+ downloaded , _ = sync_profiles (_gc ._PROFILES_DIR )
93+ if downloaded :
94+ self .after (0 , lambda d = downloaded : self ._log_write (
95+ f"[profiles] Downloaded: { ', ' .join (d )} \n " ))
96+ except Exception :
97+ pass
98+
8999 cache = {}
90100 cache_dir = _gc ._NEXUS_CACHE_DIR # capture at thread-start time
91101 if cache_dir .exists ():
@@ -278,8 +288,8 @@ def _open_settings(self):
278288
279289 win = ctk .CTkToplevel (self )
280290 win .title (f"Settings — { game_name_label } " )
281- win .geometry ("580x530 " )
282- win .minsize (480 , 440 )
291+ win .geometry ("580x620 " )
292+ win .minsize (480 , 500 )
283293 win .resizable (True , True )
284294 win .transient (self )
285295 win .withdraw () # hide until fully built (prevents blank flash on Linux)
@@ -290,8 +300,8 @@ def _open_settings(self):
290300 # Center over main window
291301 self .update_idletasks ()
292302 wx = self .winfo_x () + (self .winfo_width () - 580 ) // 2
293- wy = self .winfo_y () + (self .winfo_height () - 530 ) // 2
294- win .geometry (f"580x530 +{ wx } +{ wy } " )
303+ wy = self .winfo_y () + (self .winfo_height () - 620 ) // 2
304+ win .geometry (f"580x620 +{ wx } +{ wy } " )
295305
296306 scroll = ctk .CTkScrollableFrame (win , fg_color = "transparent" )
297307 scroll .grid (row = 0 , column = 0 , sticky = "nsew" )
@@ -387,6 +397,45 @@ def _clear_cache():
387397 ).grid (row = 0 , column = 0 , sticky = "w" )
388398 cache_info .grid (row = 0 , column = 1 , sticky = "w" , padx = (12 , 0 ))
389399
400+ # ── Game Profiles ─────────────────────────────────────────────
401+ row = _section_header ("GAME PROFILES" , row )
402+
403+ prof_frame = ctk .CTkFrame (scroll , fg_color = "transparent" )
404+ prof_frame .grid (row = row , column = 0 , sticky = "ew" , padx = 16 , pady = (4 , 0 ))
405+ row += 1
406+
407+ prof_status = ctk .CTkLabel (prof_frame , text = "" ,
408+ font = ctk .CTkFont (size = 11 ),
409+ text_color = ("gray50" , "gray55" ))
410+
411+ def _update_profiles ():
412+ prof_status .configure (text = "Checking GitHub…" )
413+ prof_frame .update_idletasks ()
414+
415+ def _do ():
416+ try :
417+ from mm .profiles import sync_profiles
418+ downloaded , failed = sync_profiles (_gc ._PROFILES_DIR , force = True )
419+ if downloaded :
420+ msg = f"Updated: { ', ' .join (downloaded )} "
421+ elif failed :
422+ msg = f"Failed: { ', ' .join (failed )} "
423+ else :
424+ msg = "All profiles are up to date."
425+ except Exception as exc :
426+ msg = f"Error: { exc } "
427+ self .after (0 , lambda : prof_status .configure (text = msg ))
428+
429+ threading .Thread (target = _do , daemon = True ).start ()
430+
431+ ctk .CTkButton (
432+ prof_frame , text = "Update Game Profiles" , width = 160 , height = 28 ,
433+ fg_color = ("gray72" , "gray30" ), hover_color = ("gray62" , "gray38" ),
434+ font = ctk .CTkFont (size = 11 ),
435+ command = _update_profiles ,
436+ ).grid (row = 0 , column = 0 , sticky = "w" )
437+ prof_status .grid (row = 0 , column = 1 , sticky = "w" , padx = (12 , 0 ))
438+
390439 # ── Paths ─────────────────────────────────────────────────────
391440 row = _section_header ("PATHS" , row )
392441
0 commit comments