Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def set_object_value(self, info, value):
def check_settings(self, *args):
old_settings = self.settings
self.settings = self.get_settings()

if self.settings is None: return
for key in self.bindings:
new_value = self.settings[key]["value"]
if new_value != old_settings[key]["value"]:
Expand All @@ -148,9 +148,12 @@ def check_settings(self, *args):
callback(key, new_value)

def get_settings(self):
file = open(self.filepath)
raw_data = file.read()
file.close()
try:
file = open(self.filepath)
raw_data = file.read()
file.close()
except FileNotFoundError:
return
try:
settings = json.loads(raw_data, object_pairs_hook=collections.OrderedDict)
except:
Expand Down
230 changes: 163 additions & 67 deletions files/usr/share/cinnamon/cinnamon-settings/xlet-settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,21 @@ def __init__(self, args):
self.type = args.type
self.uuid = args.uuid
self.tab = 0
self.instance_info = []
self.instance_id = str(args.id)
if args.tab is not None:
self.tab = int(args.tab)

self.selected_instance = None
self.gsettings = Gio.Settings.new("org.cinnamon")
self.monitors = {}
self.g_directories = []
self.custom_modules = {}
if self.type == "applet": changed_key = "enabled-applets"
elif self.type == "desklet": changed_key = "enabled-desklets"
else: changed_key = None
if changed_key:
self.gsettings.connect("changed::" + changed_key, lambda *args: self.on_enabled_xlets_changed(changed_key, *args))

self.load_xlet_data()
self.build_window()
Expand All @@ -128,7 +136,7 @@ def _on_proxy_ready (self, obj, result, data=None):
proxy = None

if proxy:
proxy.highlightXlet('(ssb)', self.uuid, self.selected_instance["id"], True)
self.highlight_xlet(self.selected_instance, True)

def load_xlet_data (self):
self.xlet_dir = "/usr/share/cinnamon/%ss/%s" % (self.type, self.uuid)
Expand Down Expand Up @@ -242,18 +250,22 @@ def check_sizing(widget, data=None):
self.next_button.connect("clicked", self.next_instance)

def load_instances(self):
self.instance_info = []
path = Path(os.path.join(settings_dir, self.uuid))
old_path = Path("%s/.cinnamon/configs/%s" % (home, self.uuid))
instances = 0
for p in path, old_path:
if not p.exists(): continue
self.g_directories.append(Gio.File.new_for_path(str(p)))

new_items = os.listdir(path) if path.exists() else []
old_items = os.listdir(old_path) if old_path.exists() else []
dir_items = sorted(new_items + old_items)

try:
multi_instance = int(self.xlet_meta["max-instances"]) != 1
except (KeyError, ValueError):
multi_instance = False

enabled = [x.split(":") for x in self.gsettings.get_strv('enabled-%ss' % self.type)]
for item in dir_items:
# ignore anything that isn't json
if item[-5:] != ".json":
Expand All @@ -271,66 +283,82 @@ def load_instances(self):
continue # multi-instance should have file names of the form [instance-id].json

instance_exists = False
enabled = self.gsettings.get_strv('enabled-%ss' % self.type)
for definition in enabled:
if self.uuid in definition and instance_id in definition.split(':'):
if self.uuid in definition and instance_id in definition:
instance_exists = True
break

if not instance_exists:
continue

settings = JSONSettingsHandler(os.path.join(path if item in new_items else old_path, item), self.notify_dbus)
settings.instance_id = instance_id
instance_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.instance_stack.add_named(instance_box, instance_id)

info = {"settings": settings, "id": instance_id}
self.instance_info.append(info)
config_path = os.path.join(path if item in new_items else old_path, item)
self.create_settings_page(config_path)

if not self.instance_info:
print(f"No instances were found for {self.uuid}. Exiting...")
sys.exit()

self.next_button.set_no_show_all(True)
self.prev_button.set_no_show_all(True)
self.show_prev_next_buttons() if self.has_multiple_instances() else self.hide_prev_next_buttons()

def create_settings_page(self, config_path):
instance_id = os.path.basename(config_path)[:-5]
if self.instance_stack.get_child_by_name(instance_id) is not None: return
settings = JSONSettingsHandler(config_path, self.notify_dbus)
settings.instance_id = instance_id
instance_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.instance_stack.add_named(instance_box, instance_id)
info = {"settings": settings, "id": instance_id}
self.instance_info.append(info)
settings_map = settings.get_settings()
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential crash if settings.get_settings() returns None (when config file doesn't exist). The code doesn't check for None before calling next(iter(settings_map.values())), which would raise an error. Should add: if settings_map is None: return after line 314.

Suggested change
settings_map = settings.get_settings()
settings_map = settings.get_settings()
if settings_map is None:
return

Copilot uses AI. Check for mistakes.
first_key = next(iter(settings_map.values()))

settings_map = settings.get_settings()
first_key = next(iter(settings_map.values()))
try:
for setting in settings_map:
if setting == "__md5__":
continue
for key in settings_map[setting]:
if key in ("description", "tooltip", "units"):
try:
settings_map[setting][key] = translate(self.uuid, settings_map[setting][key])
except (KeyError, ValueError):
traceback.print_exc()
elif key in "options":
new_opt_data = collections.OrderedDict()
opt_data = settings_map[setting][key]
for option in opt_data:
if opt_data[option] == "custom":
continue
new_opt_data[translate(self.uuid, option)] = opt_data[option]
settings_map[setting][key] = new_opt_data
elif key in "columns":
columns_data = settings_map[setting][key]
for column in columns_data:
column["title"] = translate(self.uuid, column["title"])
finally:
# if a layout is not explicitly defined, generate the settings
# widgets based on the order they occur
if first_key["type"] == "layout":
self.build_with_layout(settings_map, info, instance_box, first_key)
else:
self.build_from_order(settings_map, info, instance_box, first_key)

try:
for setting in settings_map:
if setting == "__md5__":
continue
for key in settings_map[setting]:
if key in ("description", "tooltip", "units"):
try:
settings_map[setting][key] = translate(self.uuid, settings_map[setting][key])
except (KeyError, ValueError):
traceback.print_exc()
elif key in "options":
new_opt_data = collections.OrderedDict()
opt_data = settings_map[setting][key]
for option in opt_data:
if opt_data[option] == "custom":
continue
new_opt_data[translate(self.uuid, option)] = opt_data[option]
settings_map[setting][key] = new_opt_data
elif key in "columns":
columns_data = settings_map[setting][key]
for column in columns_data:
column["title"] = translate(self.uuid, column["title"])
finally:
# if a layout is not explicitly defined, generate the settings
# widgets based on the order they occur
if first_key["type"] == "layout":
self.build_with_layout(settings_map, info, instance_box, first_key)
else:
self.build_from_order(settings_map, info, instance_box, first_key)
if self.selected_instance is None:
self.selected_instance = info
if "stack" in info:
self.stack_switcher.set_stack(info["stack"])

if self.selected_instance is None:
self.selected_instance = info
if "stack" in info:
self.stack_switcher.set_stack(info["stack"])
def has_multiple_instances(self):
return len(self.instance_info) > 1

instances += 1
def hide_prev_next_buttons(self):
self.prev_button.hide()
self.next_button.hide()

if instances < 2:
self.prev_button.set_no_show_all(True)
self.next_button.set_no_show_all(True)
def show_prev_next_buttons(self):
self.prev_button.show()
self.next_button.show()

def build_with_layout(self, settings_map, info, box, first_key):
layout = first_key
Expand Down Expand Up @@ -460,26 +488,95 @@ def set_instance(self, info):
else:
info["stack"].set_visible_child(children[0])
if proxy:
proxy.highlightXlet('(ssb)', self.uuid, self.selected_instance["id"], False)
proxy.highlightXlet('(ssb)', self.uuid, info["id"], True)
old_info = self.selected_instance
new_info = info
self.highlight_xlet(old_info, False)
self.highlight_xlet(new_info, True)
self.selected_instance = info

def highlight_xlet(self, info, highlighted):
try:
proxy.highlightXlet('(ssb)', self.uuid, info["id"], highlighted)
except:
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare except clause catches all exceptions including KeyboardInterrupt and SystemExit. Should catch specific exceptions like Exception or the expected exception types (e.g., KeyError, dbus.exceptions.DBusException).

Suggested change
except:
except Exception:

Copilot uses AI. Check for mistakes.
return

def previous_instance(self, *args):
self.instance_stack.set_transition_type(Gtk.StackTransitionType.OVER_RIGHT)
index = self.instance_info.index(self.selected_instance)
self.set_instance(self.instance_info[index-1])
self.get_next_instance(False)

def next_instance(self, *args):
self.instance_stack.set_transition_type(Gtk.StackTransitionType.OVER_LEFT)
index = self.instance_info.index(self.selected_instance)
if index == len(self.instance_info) - 1:
index = 0
else:
index +=1
self.set_instance(self.instance_info[index])
self.get_next_instance()

def get_next_instance(self, positive_direction = True):
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter default value should not have spaces around the equals sign. Should be positive_direction=True per PEP 8.

Suggested change
def get_next_instance(self, positive_direction = True):
def get_next_instance(self, positive_direction=True):

Copilot uses AI. Check for mistakes.
transition = Gtk.StackTransitionType.OVER_LEFT if positive_direction else Gtk.StackTransitionType.OVER_RIGHT
self.instance_stack.set_transition_type(transition)
step = 1 if positive_direction else -1
instances_length = len(self.instance_info)
start = self.instance_info.index(self.selected_instance)
nextIndex = (start + step) % instances_length
self.set_instance(self.instance_info[nextIndex])

def on_enabled_xlets_changed(self, key, *args):
"""
Args:
key ("enabled-applets"|"enabled-desklets")
"""
current_ids = {info["id"] for info in self.instance_info}
new_ids = set()
for definition in self.gsettings.get_strv(key):
definition = definition.split(":")
uuid, instance_id = (definition[-2], definition[-1]) if key == "enabled-applets"\
else (definition[0], definition[1])
if uuid != self.uuid: continue
new_ids.add(instance_id)
added_ids = new_ids - current_ids

removed_indices = []
selected_removed_index = -1
for i, info in enumerate(self.instance_info):
if info["id"] in new_ids: continue
removed_indices.append(i)
if info == self.selected_instance: selected_removed_index = i

if len(current_ids) + len(added_ids) == len(removed_indices):
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic error in exit condition. This checks if total instances (current + added) equals removed count, which would exit when there are still instances remaining. Should be: if len(current_ids) - len(removed_indices) + len(added_ids) == 0: or equivalently if len(new_ids) == 0:

Suggested change
if len(current_ids) + len(added_ids) == len(removed_indices):
if len(new_ids) == 0:

Copilot uses AI. Check for mistakes.
self.quit()
return

for id in added_ids:
for dir in self.g_directories:
file = dir.get_child(id + ".json")
if file.query_exists(None):
self.create_new_settings_page(file.get_path())
continue
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'continue' statement after creating a settings page will skip monitoring setup for remaining directories. If the config file exists in the first directory but not others, monitors won't be created. Remove the 'continue' or add 'break' instead to stop checking other directories once file is found.

Suggested change
continue
break

Copilot uses AI. Check for mistakes.
# Config files have not been added yet, need to monitor directories
monitor = dir.monitor_directory(Gio.FileMonitorFlags.NONE, None)
monitor.connect("changed", self.on_config_file_added)
self.monitors.setdefault(id, []).append(monitor)

if (selected_removed_index != -1):
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary parentheses around the condition. Python style guide recommends omitting them for simple conditions.

Suggested change
if (selected_removed_index != -1):
if selected_removed_index != -1:

Copilot uses AI. Check for mistakes.
self.get_next_instance()

for index in sorted(removed_indices, reverse=True):
self.monitors.get(self.instance_info[index]["id"], []).clear()
self.instance_stack.remove(self.instance_stack.get_child_by_name(self.instance_info[index]["id"]))
self.instance_info.pop(index)

# def unpack_args(self, args):
# args = {}
if not self.has_multiple_instances(): self.hide_prev_next_buttons()

def on_config_file_added(self, *args):
file, event_type = args[1], args[-1]
instance = file.get_basename()[:-5]
if event_type != Gio.FileMonitorEvent.CHANGES_DONE_HINT : return
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space before colon. Should be CHANGES_DONE_HINT: without space before the colon.

Suggested change
if event_type != Gio.FileMonitorEvent.CHANGES_DONE_HINT : return
if event_type != Gio.FileMonitorEvent.CHANGES_DONE_HINT: return

Copilot uses AI. Check for mistakes.
if instance not in self.monitors: return
for monitor in self.monitors[instance]: monitor.cancel()
del self.monitors[instance]
self.create_new_settings_page(file.get_path())


def create_new_settings_page(self, path):
self.create_settings_page(path)
self.window.show_all()
if self.has_multiple_instances(): self.show_prev_next_buttons()
self.highlight_xlet(self.selected_instance, True)

def backup(self, *args):
dialog = Gtk.FileChooserDialog(_("Select or enter file to export to"),
Expand Down Expand Up @@ -531,8 +628,7 @@ def reload_xlet(self, *args):

def quit(self, *args):
if proxy:
proxy.highlightXlet('(ssb)', self.uuid, self.selected_instance["id"], False)

self.highlight_xlet(self.selected_instance, False)
self.window.destroy()
Gtk.main_quit()

Expand Down
Loading