22
33from __future__ import annotations
44
5+ import os
6+ import subprocess
7+ import sys
58from typing import TYPE_CHECKING
69
7- from PyQt6 .QtCore import QSettings , Qt
8- from PyQt6 .QtWidgets import QFileDialog , QVBoxLayout , QWidget , QScrollArea
10+ from PyQt6 .QtCore import QSettings , QTimer
11+ from PyQt6 .QtWidgets import (
12+ QApplication , QFileDialog , QScrollArea , QVBoxLayout , QWidget ,
13+ )
914from qfluentwidgets import (
1015 BodyLabel , CardWidget , FluentIcon , InfoBar , InfoBarPosition , MessageBox ,
1116 OptionsConfigItem , OptionsSettingCard , OptionsValidator , PlainTextEdit ,
12- PrimaryPushButton , PushButton , PushSettingCard , QConfig , SettingCardGroup ,
17+ PrimaryPushButton , PushButton , PushSettingCard , SettingCardGroup ,
1318 StrongBodyLabel , Theme , setTheme ,
1419)
1520
@@ -183,10 +188,8 @@ def _on_theme_change(self, item) -> None:
183188 def _on_lang_change (self , item ) -> None :
184189 val = item .value if hasattr (item , "value" ) else str (item )
185190 self ._qs .setValue ("locale" , val )
186- InfoBar .info (
187- title = t ("kms-settings-language-restart" ), content = "" ,
188- parent = self , position = InfoBarPosition .TOP_RIGHT , duration = 3000 ,
189- )
191+ self ._qs .sync ()
192+ self ._prompt_restart ()
190193
191194 def _on_clear_atlas (self ) -> None :
192195 n = atlas_count ()
@@ -213,10 +216,69 @@ def _on_clear_atlas(self) -> None:
213216 parent = self , position = InfoBarPosition .TOP_RIGHT , duration = 2000 )
214217
215218 def _on_change_data_root (self ) -> None :
216- new_path = QFileDialog .getExistingDirectory (self , t ("kms-settings-data-change" ), str (DATA_ROOT ))
219+ new_path = QFileDialog .getExistingDirectory (
220+ self , t ("kms-settings-data-change" ), str (DATA_ROOT )
221+ )
217222 if new_path :
218223 self ._qs .setValue ("data_root" , new_path )
224+ self ._qs .sync ()
225+ self ._prompt_restart ()
226+
227+ # ------------------------------------------------------------------
228+ def _prompt_restart (self ) -> None :
229+ """Confirm + restart the app so the new setting takes effect."""
230+ # Resolve top-level window so the MessageBox has the correct parent and
231+ # blocks the whole app while we ask, not just the settings scroll area.
232+ parent = self .window () or self
233+ box = MessageBox (
234+ t ("kms-settings-restart-title" ),
235+ t ("kms-settings-restart-message" ),
236+ parent ,
237+ )
238+ # Customize button labels when available — MessageBox ships yes/cancel.
239+ if hasattr (box , "yesButton" ):
240+ box .yesButton .setText (t ("kms-settings-restart-now" ))
241+ if hasattr (box , "cancelButton" ):
242+ box .cancelButton .setText (t ("kms-settings-restart-later" ))
243+ if box .exec ():
244+ # Delay the exec so Qt can finish paint + settings flush cleanly.
245+ QTimer .singleShot (0 , self ._restart_app )
246+ else :
219247 InfoBar .info (
220- title = t ("kms-settings-language- restart" ), content = new_path ,
248+ title = t ("kms-settings-restart-later-banner " ), content = "" ,
221249 parent = self , position = InfoBarPosition .TOP_RIGHT , duration = 3000 ,
222250 )
251+
252+ @staticmethod
253+ def _restart_app () -> None :
254+ """Spawn a fresh process of ourselves, then exit this one.
255+
256+ ``subprocess.Popen`` + ``sys.exit`` is preferred over ``os.execv``
257+ on macOS: re-exec'ing a Qt/Cocoa-initialized process makes the
258+ kernel reject its task-policy inheritance ("Task policy set failed:
259+ 4 ((os/kern) invalid argument)"). A clean child process, started
260+ with ``start_new_session=True`` so the parent exit does not kill
261+ the child, avoids the warning entirely.
262+
263+ Works in both ``python -m scikms`` and the PyInstaller-bundled
264+ executable: when ``sys.frozen`` is set, ``sys.executable`` IS the
265+ bundle binary and its own argv is re-used; otherwise we re-invoke
266+ the Python interpreter with the current argv (the console script
267+ stub installed by ``uv sync`` is a plain Python script).
268+ """
269+ if getattr (sys , "frozen" , False ):
270+ cmd = [sys .executable , * sys .argv [1 :]]
271+ else :
272+ cmd = [sys .executable , * sys .argv ]
273+ # The original process eagerly sets SCIKMS_LOCALE from QSettings at
274+ # startup so the i18n module picks it up. If we let the child inherit
275+ # that env var, it will ignore the freshly-saved QSettings value and
276+ # stay on the old locale after restart. Strip it so the child re-reads
277+ # QSettings on its own.
278+ env = os .environ .copy ()
279+ env .pop ("SCIKMS_LOCALE" , None )
280+ subprocess .Popen (cmd , env = env , start_new_session = True )
281+ app = QApplication .instance ()
282+ if app is not None :
283+ app .quit ()
284+ sys .exit (0 )
0 commit comments