From 3d05d3ee7002b560ce157da11788f77b43af6c1f Mon Sep 17 00:00:00 2001 From: Jonny Leaders Date: Wed, 25 Mar 2026 18:37:03 -0500 Subject: [PATCH 1/2] cinnamon-settings: make extension toggles directly clickable Use an inline checkbox for installed extensions so enable and disable actions are visible on each row instead of being hidden behind the bottom toolbar buttons. Made-with: Cursor --- .../cinnamon-settings/bin/ExtensionCore.py | 71 +++++++++++++++---- .../modules/cs_extensions.py | 5 ++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py b/files/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py index 3b32c14e67..3bc14c0a33 100644 --- a/files/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py +++ b/files/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py @@ -160,10 +160,12 @@ def sanitize_html(string): class ManageSpicesRow(Gtk.ListBoxRow): - def __init__(self, extension_type, metadata, size_groups): + def __init__(self, extension_type, metadata, size_groups, use_inline_toggle=False, toggle_callback=None): super().__init__() self.extension_type = extension_type self.metadata = metadata + self.use_inline_toggle = use_inline_toggle + self.toggle_callback = toggle_callback self.status_ids = {} @@ -225,16 +227,31 @@ def __init__(self, extension_type, metadata, size_groups): enabled_box = Gtk.Box() enabled_box.set_spacing(4) + enabled_box.set_halign(Gtk.Align.CENTER) + enabled_box.set_valign(Gtk.Align.CENTER) size_groups[0].add_widget(enabled_box) - self.enabled_image = Gtk.Image.new_from_icon_name('xsi-object-select-symbolic', 2) - if self.extension_type == "applet": - self.enabled_image.set_tooltip_text(_("This applet is currently enabled")) - elif self.extension_type == "desklet": - self.enabled_image.set_tooltip_text(_("This desklet is currently enabled")) - elif self.extension_type == "extension": - self.enabled_image.set_tooltip_text(_("This extension is currently enabled")) - self.enabled_image.set_no_show_all(True) - enabled_box.pack_end(self.enabled_image, False, False, 0) + self.enabled_image = None + self.enabled_toggle = None + self.enabled_toggle_id = None + + if self.use_inline_toggle: + self.enabled_toggle = Gtk.CheckButton() + self.enabled_toggle.set_valign(Gtk.Align.CENTER) + self.enabled_toggle.set_halign(Gtk.Align.CENTER) + self.enabled_toggle.set_size_request(32, 32) + self.enabled_toggle.set_tooltip_text(_("Enable or disable this extension")) + self.enabled_toggle_id = self.enabled_toggle.connect('toggled', self.on_enabled_toggled) + enabled_box.pack_end(self.enabled_toggle, False, False, 0) + else: + self.enabled_image = Gtk.Image.new_from_icon_name('xsi-object-select-symbolic', 2) + if self.extension_type == "applet": + self.enabled_image.set_tooltip_text(_("This applet is currently enabled")) + elif self.extension_type == "desklet": + self.enabled_image.set_tooltip_text(_("This desklet is currently enabled")) + elif self.extension_type == "extension": + self.enabled_image.set_tooltip_text(_("This extension is currently enabled")) + self.enabled_image.set_no_show_all(True) + enabled_box.pack_end(self.enabled_image, False, False, 0) enabled_box.show() grid.attach(enabled_box, 0, 0, 1, 1) @@ -375,10 +392,20 @@ def remove_status(self, status_id): self.status_ids[status_id].destroy() del self.status_ids[status_id] + def on_enabled_toggled(self, button): + if self.toggle_callback is None: + return + + self.toggle_callback(self, button.get_active()) + def set_enabled(self, enabled): self.enabled = enabled - if self.enabled: + if self.enabled_toggle is not None: + self.enabled_toggle.handler_block(self.enabled_toggle_id) + self.enabled_toggle.set_active(self.enabled > 0) + self.enabled_toggle.handler_unblock(self.enabled_toggle_id) + elif self.enabled: self.enabled_image.show() else: self.enabled_image.hide() @@ -425,6 +452,8 @@ def on_scan_complete(self, is_dangerous): class ManageSpicesPage(SettingsPage): + use_inline_toggle = False + def __init__(self, parent, collection_type, spices, window): super().__init__() self.expand = True @@ -589,6 +618,18 @@ def add_instance(self, *args): extension_row = self.list_box.get_selected_row() self.enable_extension(extension_row.uuid, extension_row.name, extension_row.version_supported) + def on_row_toggle_enabled(self, extension_row, enabled): + self.list_box.select_row(extension_row) + + if enabled: + if extension_row.enabled == 0: + self.enable_extension(extension_row.uuid, extension_row.name, extension_row.version_supported) + elif extension_row.enabled: + self.spices.disable_extension(extension_row.uuid) + + extension_row.set_enabled(self.spices.get_enabled(extension_row.uuid)) + self.update_button_states() + def enable_extension(self, uuid, name, version_check=True): if not version_check: show_message(_("Extension %s is not compatible with your version of Cinnamon.") % uuid, self.window) @@ -654,7 +695,13 @@ def load_extensions(self, *args): for uuid, metadata in self.spices.get_installed().items(): try: - extension_row = ManageSpicesRow(self.collection_type, metadata, size_groups) + extension_row = ManageSpicesRow( + self.collection_type, + metadata, + size_groups, + use_inline_toggle=self.use_inline_toggle, + toggle_callback=self.on_row_toggle_enabled, + ) self.list_box.add(extension_row) self.extension_rows.append(extension_row) extension_row.set_enabled(self.spices.get_enabled(uuid)) diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py index d5544dcd2a..8bd944e79b 100755 --- a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py @@ -55,6 +55,11 @@ class ManageExtensionsPage(ManageSpicesPage): remove_button_text = _("Disable") uninstall_button_text = _("Uninstall") restore_button_text = _("Disable all") + use_inline_toggle = True def __init__(self, parent, spices, window): super(ManageExtensionsPage, self).__init__(parent, self.collection_type, spices, window) + self.instance_button.set_no_show_all(True) + self.remove_button.set_no_show_all(True) + self.instance_button.hide() + self.remove_button.hide() From 789bcfba424d34b635d48d05eb5b71f31f94d7d0 Mon Sep 17 00:00:00 2001 From: Jonathan Leaders Date: Sun, 26 Jul 2026 14:29:46 -0500 Subject: [PATCH 2/2] Add test script for extensions manager checkbox overlay Helper for verifying clickable extension toggles on the feature branch. --- tools/test-extensions-checkbox-overlay.sh | 123 ++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100755 tools/test-extensions-checkbox-overlay.sh diff --git a/tools/test-extensions-checkbox-overlay.sh b/tools/test-extensions-checkbox-overlay.sh new file mode 100755 index 0000000000..050e001183 --- /dev/null +++ b/tools/test-extensions-checkbox-overlay.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +BACKUP_ROOT="${HOME}/.cache/cinnamon-settings-overlay" +STATE_FILE="${BACKUP_ROOT}/current-backup" + +SRC_EXTENSION_CORE="${REPO_ROOT}/files/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py" +SRC_CS_EXTENSIONS="${REPO_ROOT}/files/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py" + +DST_EXTENSION_CORE="/usr/share/cinnamon/cinnamon-settings/bin/ExtensionCore.py" +DST_CS_EXTENSIONS="/usr/share/cinnamon/cinnamon-settings/modules/cs_extensions.py" + +usage() { + cat <<'EOF' +Usage: + ./tools/test-extensions-checkbox-overlay.sh install + ./tools/test-extensions-checkbox-overlay.sh restore + ./tools/test-extensions-checkbox-overlay.sh status + +Commands: + install Back up the installed Cinnamon settings files and overlay this repo's versions. + restore Restore the last backup created by install. + status Show the active backup location, if any. +EOF +} + +require_sources() { + [[ -f "${SRC_EXTENSION_CORE}" ]] || { echo "Missing source file: ${SRC_EXTENSION_CORE}" >&2; exit 1; } + [[ -f "${SRC_CS_EXTENSIONS}" ]] || { echo "Missing source file: ${SRC_CS_EXTENSIONS}" >&2; exit 1; } +} + +require_installed_targets() { + [[ -f "${DST_EXTENSION_CORE}" ]] || { echo "Missing installed file: ${DST_EXTENSION_CORE}" >&2; exit 1; } + [[ -f "${DST_CS_EXTENSIONS}" ]] || { echo "Missing installed file: ${DST_CS_EXTENSIONS}" >&2; exit 1; } +} + +install_overlay() { + require_sources + require_installed_targets + + mkdir -p "${BACKUP_ROOT}" + + local backup_dir + backup_dir="${BACKUP_ROOT}/$(date +%Y%m%d-%H%M%S)" + mkdir -p "${backup_dir}/bin" "${backup_dir}/modules" + + echo "Creating backup in ${backup_dir}" + sudo cp "${DST_EXTENSION_CORE}" "${backup_dir}/bin/ExtensionCore.py" + sudo cp "${DST_CS_EXTENSIONS}" "${backup_dir}/modules/cs_extensions.py" + + echo "Overlaying repo versions into /usr/share/cinnamon/cinnamon-settings" + sudo cp "${SRC_EXTENSION_CORE}" "${DST_EXTENSION_CORE}" + sudo cp "${SRC_CS_EXTENSIONS}" "${DST_CS_EXTENSIONS}" + + printf '%s\n' "${backup_dir}" > "${STATE_FILE}" + + cat <<'EOF' + +Overlay installed. + +Launch the Extensions settings page with: + cinnamon-settings extensions --tab installed + +Restore the original files later with: + ./tools/test-extensions-checkbox-overlay.sh restore +EOF +} + +restore_overlay() { + require_installed_targets + + if [[ ! -f "${STATE_FILE}" ]]; then + echo "No backup state file found at ${STATE_FILE}" >&2 + exit 1 + fi + + local backup_dir + backup_dir="$(<"${STATE_FILE}")" + + [[ -f "${backup_dir}/bin/ExtensionCore.py" ]] || { echo "Missing backup file in ${backup_dir}" >&2; exit 1; } + [[ -f "${backup_dir}/modules/cs_extensions.py" ]] || { echo "Missing backup file in ${backup_dir}" >&2; exit 1; } + + echo "Restoring backup from ${backup_dir}" + sudo cp "${backup_dir}/bin/ExtensionCore.py" "${DST_EXTENSION_CORE}" + sudo cp "${backup_dir}/modules/cs_extensions.py" "${DST_CS_EXTENSIONS}" + + rm -f "${STATE_FILE}" + echo "Restore complete." +} + +show_status() { + if [[ -f "${STATE_FILE}" ]]; then + echo "Active backup: $(<"${STATE_FILE}")" + else + echo "No active overlay backup recorded." + fi +} + +main() { + local command="${1:-}" + + case "${command}" in + install) + install_overlay + ;; + restore) + restore_overlay + ;; + status) + show_status + ;; + *) + usage + exit 1 + ;; + esac +} + +main "$@"