2020from gi .repository import Gio , Gdk , GLib , Gtk , Pango
2121
2222try :
23+ from .box64rc import SectionKey
2324 from .model import ConfigStore , EntryRecord , ValueState , box64_arch_from_machine
2425 from .usage import EnvOption , load_default_usage_catalog , load_usage_catalog
2526except ImportError :
27+ from box64rc import SectionKey
2628 from model import ConfigStore , EntryRecord , ValueState , box64_arch_from_machine
2729 from usage import EnvOption , load_default_usage_catalog , load_usage_catalog
2830
2931
3032APP_ID = "org.box64.Configurator"
33+ PROFILE_CHOICES = ("default" , "safest" , "safe" , "fast" , "fastest" )
34+ PROFILE_OPTION_NAME = "BOX64_PROFILE"
3135
3236
3337_EMBEDDED_TEXTS : Optional [Dict [str , Dict [str , str ]]] = globals ().get ("_EMBEDDED_TEXTS" )
@@ -248,6 +252,66 @@ def dropped_executable_entry(path: Path) -> str:
248252 return resolved .name
249253
250254
255+ def profile_menu_entry (path : Path ) -> str :
256+ resolved = path .expanduser ().resolve (strict = True )
257+ if not resolved .is_file ():
258+ raise ValueError (f"Not a file: { path } " )
259+ name = resolved .name
260+ if not name or any (char in name for char in "\r \n []#" ):
261+ raise ValueError (f"Invalid executable name for rcfile section: { name } " )
262+ return name
263+
264+
265+ def entry_for_profile_menu (store : ConfigStore , name : str ) -> Optional [EntryRecord ]:
266+ normalized = name .lower ()
267+ matches = [
268+ entry
269+ for entry in store .entries ()
270+ if entry .key .kind == "exact" and entry .key .name == normalized
271+ ]
272+ return next ((entry for entry in matches if not entry .key .arch ), matches [0 ] if matches else None )
273+
274+
275+ def get_profile_from_menu (store : ConfigStore , executable : Path ) -> str :
276+ name = profile_menu_entry (executable )
277+ entry = entry_for_profile_menu (store , name )
278+ if entry is None :
279+ entry = EntryRecord (SectionKey ("exact" , name .lower ()), name , None , None )
280+
281+ active , _often_used , _defaults = store .effective_values (entry )
282+ profile = next (
283+ (state .value .strip ().lower () for state in active if state .option .name == PROFILE_OPTION_NAME ),
284+ "default" ,
285+ )
286+ return profile if profile in PROFILE_CHOICES else "default"
287+
288+
289+ def profile_only_user_entry (entry : EntryRecord ) -> bool :
290+ return (
291+ entry .user_section is not None
292+ and entry .system_section is None
293+ and all (assignment .key == PROFILE_OPTION_NAME for assignment in entry .user_section .assignments )
294+ )
295+
296+
297+ def set_profile_from_menu (store : ConfigStore , executable : Path , profile : str ) -> None :
298+ name = profile_menu_entry (executable )
299+ entry = entry_for_profile_menu (store , name )
300+ if entry is None :
301+ if profile == "default" :
302+ return
303+ new_key = store .create_entry (name )
304+ entry = store .entry_for_key (new_key )
305+ if entry is None :
306+ raise KeyError (new_key )
307+
308+ remove_profile_only_section = profile == "default" and profile_only_user_entry (entry )
309+ updated_key , _forked = store .set_option (entry , PROFILE_OPTION_NAME , profile )
310+ if remove_profile_only_section :
311+ store .delete_user_entry (updated_key )
312+ store .save ()
313+
314+
251315class EntryRow (Gtk .ListBoxRow ):
252316 def __init__ (self , entry : EntryRecord , language : str ) -> None :
253317 super ().__init__ ()
@@ -576,6 +640,25 @@ def _on_drag_data_received(
576640 elif errors :
577641 self ._show_error (ValueError ("\n " .join (errors )))
578642
643+ def open_executable (self , path : Path ) -> None :
644+ try :
645+ name = dropped_executable_entry (path )
646+ except Exception as error :
647+ self ._show_error (error )
648+ return
649+
650+ try :
651+ new_key = self .store .create_entry (name )
652+ except Exception as error :
653+ self ._show_error (error )
654+ return
655+
656+ self .selected_option_name = None
657+ self .current_key = new_key
658+ self ._refresh_view (new_key )
659+ if self .store .dirty :
660+ self ._push_status (tr (self .language , "dirty" ))
661+
579662 def _on_entry_list_button_press (self , _listbox : Gtk .ListBox , event ) -> bool :
580663 if event .button != 3 :
581664 return False
@@ -1217,11 +1300,15 @@ def __init__(self, store: ConfigStore) -> None:
12171300 super ().__init__ (application_id = APP_ID , flags = Gio .ApplicationFlags .FLAGS_NONE )
12181301 self .store = store
12191302 self .window : Optional [ConfiguratorWindow ] = None
1303+ self .open_executable : Optional [Path ] = None
12201304
12211305 def do_activate (self ) -> None :
12221306 if self .window is None :
12231307 self .window = ConfiguratorWindow (self , self .store )
12241308 self .window .present ()
1309+ if self .open_executable is not None :
1310+ self .window .open_executable (self .open_executable )
1311+ self .open_executable = None
12251312
12261313
12271314def parse_args (argv : Optional [Sequence [str ]]) -> argparse .Namespace :
@@ -1230,7 +1317,16 @@ def parse_args(argv: Optional[Sequence[str]]) -> argparse.Namespace:
12301317 parser .add_argument ("--usage-cn" , type = Path )
12311318 parser .add_argument ("--system-rc" , type = Path , default = default_system_rc ())
12321319 parser .add_argument ("--user-rc" , type = Path , default = default_user_rc ())
1233- return parser .parse_args (argv )
1320+ parser .add_argument ("--set-profile" , choices = PROFILE_CHOICES )
1321+ parser .add_argument ("--get-profile" , action = "store_true" )
1322+ parser .add_argument ("executable" , nargs = "?" , type = Path )
1323+ args = parser .parse_args (argv )
1324+ profile_mode_count = int (args .set_profile is not None ) + int (args .get_profile )
1325+ if profile_mode_count > 1 :
1326+ parser .error ("--set-profile and --get-profile cannot be used together" )
1327+ if profile_mode_count > 0 and args .executable is None :
1328+ parser .error ("--set-profile and --get-profile require an executable" )
1329+ return args
12341330
12351331
12361332def main (argv : Optional [Sequence [str ]] = None ) -> int :
@@ -1242,7 +1338,15 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
12421338 else :
12431339 catalog = load_default_usage_catalog ()
12441340 store = ConfigStore (args .system_rc , args .user_rc , catalog , box64_arch_from_machine (platform .machine ()))
1341+ if args .set_profile is not None :
1342+ set_profile_from_menu (store , args .executable , args .set_profile )
1343+ return 0
1344+ if args .get_profile :
1345+ print (get_profile_from_menu (store , args .executable ))
1346+ return 0
12451347 app = ConfiguratorApplication (store )
1348+ if args .executable is not None :
1349+ app .open_executable = args .executable
12461350 return app .run ([sys .argv [0 ]])
12471351
12481352
0 commit comments