Skip to content

Commit a19ba16

Browse files
committed
KeybindingTable.py: Don't pick up obsolete/unused configs.
After making the inhibit applet multi-instance (cf1f5ad), my keybindings settings was picking up the old <uuid>.json config file in addition to the instanced one, confusing our keybinding list. Also fixes #13690.
1 parent 311b492 commit a19ba16

1 file changed

Lines changed: 88 additions & 51 deletions

File tree

files/usr/share/cinnamon/cinnamon-settings/bin/KeybindingTable.py

Lines changed: 88 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
283313
class 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

Comments
 (0)