@@ -280,6 +280,36 @@ def ensureCustomListChanges(custom_list):
280280 else :
281281 custom_list .append (DUMMY_CUSTOM_ENTRY )
282282
283+ def _load_xlet_metadata (uuid , _type ):
284+ local_path = Path .home () / ".local/share/cinnamon" / _type / uuid / "metadata.json"
285+ system_path = Path ("/usr/share/cinnamon" ) / _type / uuid / "metadata.json"
286+
287+ for metadata_path , is_local in ((local_path , True ), (system_path , False )):
288+ if metadata_path .exists ():
289+ try :
290+ with open (metadata_path , encoding = "utf-8" ) as metadata_file :
291+ return json .load (metadata_file ), is_local
292+ except (OSError , json .JSONDecodeError ):
293+ pass
294+
295+ return None , False
296+
297+ # Matches the _get_is_multi_instance_xlet implementations in js/ui/settings.js
298+ def _xlet_is_multi_instance (metadata , _type ):
299+ if metadata is None :
300+ return False
301+
302+ try :
303+ max_instances = int (metadata .get ("max-instances" , 1 ))
304+ except (TypeError , ValueError ):
305+ return False
306+
307+ if _type == "applets" :
308+ return max_instances != 1
309+ if _type == "desklets" :
310+ return max_instances > 1
311+ return False
312+
283313class KeyBindingCategory :
284314 def __init__ (self , label , int_name , parent , icon , dbus_info = {}):
285315 self .label = label
@@ -633,8 +663,8 @@ def _load_spice_store(self):
633663 enabled_extensions .add (extension )
634664 enabled_spices .add ((extension , 'extensions' , None ))
635665
636- # Build set of enabled instance IDs per uuid, so we only process
637- # config files for instances that are actually running .
666+ # Enabled instance IDs per uuid, to decide whether a multi-instance
667+ # xlet needs per-instance subcategories .
638668 enabled_ids = {}
639669 for uuid , _type , instance_id in enabled_spices :
640670 enabled_ids .setdefault (uuid , set ())
@@ -646,33 +676,45 @@ def _load_spice_store(self):
646676 spice_properties = {}
647677
648678 for uuid , _type , instance_id in keyboard_spices :
679+ metadata , metadata_is_local = _load_xlet_metadata (uuid , _type )
680+ multi_instance = instance_id is not None and _xlet_is_multi_instance (metadata , _type )
681+
682+ # Mirror js/ui/settings.js: multi-instance xlets store their config
683+ # as <instance_id>.json, single-instance ones as <uuid>.json. Only
684+ # look for the expected file, so stale configs are ignored.
685+ if multi_instance :
686+ config_name = f"{ instance_id } .json"
687+ key_name = f"{ uuid } _{ instance_id } "
688+ else :
689+ config_name = f"{ uuid } .json"
690+ key_name = uuid
691+
649692 for settings_dir in (OLD_SETTINGS_DIR , SETTINGS_DIR ):
650- config_path = Path .joinpath (settings_dir , uuid )
651- if Path .exists (config_path ):
652- configs = [x for x in os .listdir (config_path ) if x .endswith (".json" )]
653- # Filter out config files for instances that aren't running
654- ids_for_uuid = enabled_ids .get (uuid , set ())
655- configs = [x for x in configs
656- if not x .split (".json" )[0 ].isdigit () or x .split (".json" )[0 ] in ids_for_uuid ]
657- for config in configs :
658- config_json = Path .joinpath (config_path , config )
659- _id = config .split (".json" )[0 ]
660- key_name = f"{ uuid } _{ _id } " if _id .isdigit () else uuid
661- with open (config_json , encoding = "utf-8" ) as config_file :
662- _config = json .load (config_file )
663-
664- for key , val in _config .items ():
665- if isinstance (val , dict ) and val .get ("type" ) == "keybinding" :
666- spice_properties .setdefault (key_name , {})
667- spice_properties [key_name ]["highlight" ] = uuid not in enabled_extensions
668- spice_properties [key_name ]["path" ] = str (config_json )
669- spice_properties [key_name ]["type" ] = _type
670- spice_properties [key_name ]["uuid" ] = uuid
671- spice_properties [key_name ]["instance_id" ] = instance_id
672- spice_properties [key_name ]["config_id" ] = _id
673- spice_keybinds .setdefault (key_name , {})
674- spice_keybinds [key_name ].setdefault (key , {})
675- spice_keybinds [key_name ][key ] = {val .get ("description" ): val .get ("value" ).split ("::" )}
693+ config_json = Path .joinpath (settings_dir , uuid , config_name )
694+ if not Path .exists (config_json ):
695+ continue
696+
697+ try :
698+ with open (config_json , encoding = "utf-8" ) as config_file :
699+ _config = json .load (config_file )
700+ except (OSError , json .JSONDecodeError ) as e :
701+ print (f"KeybindingTable could not read { config_json } : { e } " )
702+ continue
703+
704+ for key , val in _config .items ():
705+ if isinstance (val , dict ) and val .get ("type" ) == "keybinding" :
706+ spice_properties .setdefault (key_name , {})
707+ spice_properties [key_name ]["highlight" ] = uuid not in enabled_extensions
708+ spice_properties [key_name ]["path" ] = str (config_json )
709+ spice_properties [key_name ]["type" ] = _type
710+ spice_properties [key_name ]["uuid" ] = uuid
711+ spice_properties [key_name ]["instance_id" ] = instance_id
712+ spice_properties [key_name ]["config_id" ] = config_name [:- len (".json" )]
713+ spice_properties [key_name ]["multi_instance" ] = multi_instance
714+ spice_properties [key_name ]["metadata" ] = metadata
715+ spice_properties [key_name ]["metadata_is_local" ] = metadata_is_local
716+ spice_keybinds .setdefault (key_name , {})
717+ spice_keybinds [key_name ][key ] = {val .get ("description" ): val .get ("value" ).split ("::" )}
676718
677719 self ._spice_categories = {}
678720 self ._spice_store = []
@@ -681,30 +723,24 @@ def _load_spice_store(self):
681723 new_keybindings = []
682724
683725 for spice , bindings in spice_keybinds .items ():
684- uuid , * _id = spice .split ("_" )
685-
686726 spice_props = spice_properties [spice ]
687- _type = spice_props ["type" ]
688- local_metadata_path = Path .home () / '.local/share/cinnamon' / _type / uuid / 'metadata.json'
689- if local_metadata_path .exists ():
690- gettext .bindtextdomain (uuid , str (Path .home () / '.local/share/locale' ))
691- gettext .textdomain (uuid )
692- with open (local_metadata_path , encoding = "utf-8" ) as metadata :
693- json_data = json .load (metadata )
694- category_label = _ (json_data ["name" ])
695- else :
696- system_metadata_path = Path ("/usr/share/cinnamon" ) / _type / uuid / "metadata.json"
697- if system_metadata_path .exists ():
698- with open (system_metadata_path , encoding = "utf-8" ) as metadata :
699- json_data = json .load (metadata )
700- category_label = _ (json_data ["name" ])
701- if not _id or len (enabled_ids .get (uuid , set ())) <= 1 :
702- cat_label = category_label if category_label else uuid
703- new_categories .append ([cat_label , uuid , "spices" , None , spice_props ])
727+ uuid = spice_props ["uuid" ]
728+ multi_instance = spice_props ["multi_instance" ]
729+
730+ category_label = None
731+ if spice_props ["metadata" ] is not None :
732+ if spice_props ["metadata_is_local" ]:
733+ gettext .bindtextdomain (uuid , str (Path .home () / '.local/share/locale' ))
734+ gettext .textdomain (uuid )
735+ category_label = _ (spice_props ["metadata" ]["name" ])
736+ if not category_label :
737+ category_label = uuid
738+
739+ if not multi_instance or len (enabled_ids .get (uuid , set ())) <= 1 :
740+ new_categories .append ([category_label , uuid , "spices" , None , spice_props ])
704741 instance_num = 1
705742 elif len (new_categories ) == 0 or uuid != new_categories [- 1 ][2 ]:
706- cat_label = category_label if category_label else uuid
707- new_categories .append ([cat_label , uuid , "spices" , None , {}])
743+ new_categories .append ([category_label , uuid , "spices" , None , {}])
708744 instance_num = 1
709745 label = _ ("Instance" ) + f" { instance_num } "
710746 new_categories .append ([label , f"{ uuid } _{ instance_num } " , uuid , None , spice_props ])
@@ -727,8 +763,8 @@ def _load_spice_store(self):
727763 gettext .bindtextdomain (uuid , f"{ home } /.local/share/locale" )
728764 gettext .textdomain (uuid )
729765 binding_label = gettext .gettext (list (binding_values .keys ())[0 ])
730- binding_schema = spice_properties [ spice ] ["path" ]
731- binding_category = f"{ uuid } _{ instance_num - 1 } " if _id and len (enabled_ids .get (uuid , set ())) > 1 else uuid
766+ binding_schema = spice_props ["path" ]
767+ binding_category = f"{ uuid } _{ instance_num - 1 } " if multi_instance and len (enabled_ids .get (uuid , set ())) > 1 else uuid
732768 new_keybindings .append ([binding_label , binding_schema , binding_key , binding_category , dbus_info ])
733769 self ._spice_categories [binding_category ] = category_label
734770
@@ -747,6 +783,7 @@ def _load_spice_store(self):
747783
748784 self ._add_to_collision_table (kb )
749785 category .add (kb )
786+ break
750787
751788 def _load_custom_store (self ):
752789 settings = self ._get_settings_for_schema (CUSTOM_KEYS_PARENT_SCHEMA )
0 commit comments