From d6cfe4088236e2e265d0ecad5fb5e260eca7bc58 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Sun, 15 Mar 2026 19:13:11 +0100 Subject: [PATCH 01/20] adding night-theme - first steps --- .gitignore | 3 + cs_themes.py | 1036 +++++++++++++++++ ...ngs-daemon.plugins.color.gschema.xml.in.in | 38 +- plugins/color/csd-color-manager.c | 137 ++- plugins/color/csd-night-light.c | 420 ++++--- ...light-common.c => csd-night-mode-common.c} | 18 +- ...light-common.h => csd-night-mode-common.h} | 14 +- .../{csd-night-light.h => csd-night-mode.h} | 55 +- plugins/color/meson.build | 6 +- 9 files changed, 1509 insertions(+), 218 deletions(-) create mode 100644 cs_themes.py rename plugins/color/{csd-night-light-common.c => csd-night-mode-common.c} (92%) rename plugins/color/{csd-night-light-common.h => csd-night-mode-common.h} (78%) rename plugins/color/{csd-night-light.h => csd-night-mode.h} (51%) diff --git a/.gitignore b/.gitignore index 02888e8b..979dfa18 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ debian/*.links debian/.debhelper/ debian/debhelper-build-stamp debian/*.debhelper +.gitignore +.vscode* +apt_installs_for_this \ No newline at end of file diff --git a/cs_themes.py b/cs_themes.py new file mode 100644 index 00000000..418ad8f6 --- /dev/null +++ b/cs_themes.py @@ -0,0 +1,1036 @@ +#!/usr/bin/python3 + +import os +import json +import tinycss2 + +from gi.repository import Gtk, GdkPixbuf + +from xapp.GSettingsWidgets import * +from bin.CinnamonGtkSettings import CssRange, CssOverrideSwitch, GtkSettingsSwitch, PreviewWidget, Gtk2ScrollbarSizeEditor +from bin.SettingsWidgets import LabelRow, SidePage, walk_directories +from bin.ChooserButtonWidgets import PictureChooserButton +from bin.ExtensionCore import DownloadSpicesPage +from bin.Spices import Spice_Harvester + +from pathlib import Path +import config + +ICON_SIZE = 48 + +# Gtk and Cinnamon check folders in order of precedence. These lists match the +# order. It doesn't really matter here, since we're only looking for names, +# but it's helpful to be aware of it. + +ICON_FOLDERS = [ + os.path.join(GLib.get_home_dir(), ".icons"), + os.path.join(GLib.get_user_data_dir(), "icons") +] + [os.path.join(datadir, "icons") for datadir in GLib.get_system_data_dirs()] + +THEME_FOLDERS = [ + os.path.join(GLib.get_home_dir(), ".themes"), + os.path.join(GLib.get_user_data_dir(), "themes") +] + [os.path.join(datadir, "themes") for datadir in GLib.get_system_data_dirs()] + +THEMES_BLACKLIST = [ + "gnome", # not meant to be used as a theme. Provides icons to inheriting themes. + "hicolor", # same + "adwaita", "adwaita-dark", "adwaitalegacy", # incomplete outside of GNOME, doesn't support Cinnamon. + "highcontrast", # same. Also, available via a11y as a global setting. + "epapirus", "epapirus-dark", # specifically designed for Pantheon + "ubuntu-mono", "ubuntu-mono-dark", "ubuntu-mono-light", "loginicons", # ubuntu-mono icons (non-removable in Ubuntu 24.04) + "humanity", "humanity-dark" # same +] + +class Style: + def __init__(self, json_obj): + self.name = json_obj["name"] + self.modes = {} + self.default_mode = None + +class Mode: + def __init__(self, name): + self.name = name + self.default_variant = None + self.variants = [] + + def get_variant_by_name(self, name): + for variant in self.variants: + if name == variant.name: + return variant + + return None + +class Variant: + def __init__(self, json_obj): + self.name = json_obj["name"] + self.gtk_theme = None + self.icon_theme = None + self.cinnamon_theme = None + self.cursor_theme = None + self.color = "#000000" + self.color2 = "#000000" + if "themes" in json_obj: + themes = json_obj["themes"] + self.gtk_theme = themes + self.icon_theme = themes + self.cinnamon_theme = themes + self.cursor_theme = themes + if "gtk" in json_obj: + self.gtk_theme = json_obj["gtk"] + if "icons" in json_obj: + self.icon_theme = json_obj["icons"] + if "cinnamon" in json_obj: + self.cinnamon_theme = json_obj["cinnamon"] + if "cursor" in json_obj: + self.cursor_theme = json_obj["cursor"] + self.color = json_obj["color"] + self.color2 = self.color + if "color2" in json_obj: + self.color2 = json_obj["color2"] + +class Module: + comment = _("Manage themes to change how your desktop looks") + name = "themes" + category = "appear" + + def __init__(self, content_box): + self.keywords = _("themes, style") + self.icon = "cs-themes" + self.window = None + sidePage = SidePage(_("Themes"), self.icon, self.keywords, content_box, module=self) + self.sidePage = sidePage + self.refreshing = False # flag to ensure we only refresh once at any given moment + + def refresh_themes(self): + # Find all installed themes + self.gtk_themes = [] + self.gtk_theme_names = set() + self.icon_theme_names = [] + self.cinnamon_themes = [] + self.cinnamon_theme_names = set() + self.cursor_themes = [] + self.cursor_theme_names = set() + + # Gtk themes -- Only shows themes that have a gtk-3.* variation + for (name, path) in walk_directories(THEME_FOLDERS, self.filter_func_gtk_dir, return_directories=True): + if name.lower() in THEMES_BLACKLIST: + continue + for theme in self.gtk_themes: + if name == theme[0]: + if path == THEME_FOLDERS[0]: + continue + else: + self.gtk_themes.remove(theme) + self.gtk_theme_names.add(name) + self.gtk_themes.append((name, path)) + self.gtk_themes.sort(key=lambda a: a[0].lower()) + + # Cinnamon themes + for (name, path) in walk_directories(THEME_FOLDERS, lambda d: os.path.exists(os.path.join(d, "cinnamon")), return_directories=True): + for theme in self.cinnamon_themes: + if name == theme[0]: + if path == THEME_FOLDERS[0]: + continue + else: + self.cinnamon_themes.remove(theme) + self.cinnamon_theme_names.add(name) + self.cinnamon_themes.append((name, path)) + self.cinnamon_themes.sort(key=lambda a: a[0].lower()) + + # Icon themes + walked = walk_directories(ICON_FOLDERS, lambda d: os.path.isdir(d), return_directories=True) + valid = [] + for directory in walked: + if directory[0].lower() in THEMES_BLACKLIST: + continue + path = os.path.join(directory[1], directory[0], "index.theme") + if os.path.exists(path): + try: + for line in list(open(path)): + if line.startswith("Hidden=true"): + break + if line.startswith("Directories="): + valid.append(directory) + break + except Exception as e: + print (e) + valid.sort(key=lambda a: a[0].lower()) + for (name, path) in valid: + if name not in self.icon_theme_names: + self.icon_theme_names.append(name) + + # Cursor themes + for (name, path) in walk_directories(ICON_FOLDERS, lambda d: os.path.isdir(d) and os.path.exists(os.path.join(d, "cursors")), return_directories=True): + if name.lower() in THEMES_BLACKLIST: + continue + for theme in self.cursor_themes: + if name == theme[0]: + if path == ICON_FOLDERS[0]: + continue + else: + self.cursor_themes.remove(theme) + self.cursor_theme_names.add(name) + self.cursor_themes.append((name, path)) + self.cursor_themes.sort(key=lambda a: a[0].lower()) + + def on_module_selected(self): + if not self.loaded: + print("Loading Themes module") + + self.refresh_themes() + + self.ui_ready = True + + self.spices = Spice_Harvester('theme', self.window) + + self.sidePage.stack = SettingsStack() + self.sidePage.add_widget(self.sidePage.stack) + + self.settings = Gio.Settings.new("org.cinnamon.desktop.interface") + self.cinnamon_settings = Gio.Settings.new("org.cinnamon.theme") + self.xsettings = Gio.Settings.new("org.x.apps.portal") + + self.scale = self.window.get_scale_factor() + + self.icon_chooser = self.create_button_chooser(self.settings, 'icon-theme', 'icons', 'icons', button_picture_width=ICON_SIZE, menu_picture_width=ICON_SIZE, num_cols=4, frame=False) + self.cursor_chooser = self.create_button_chooser(self.settings, 'cursor-theme', 'icons', 'cursors', button_picture_width=32, menu_picture_width=32, num_cols=4, frame=False) + self.theme_chooser = self.create_button_chooser(self.settings, 'gtk-theme', 'themes', 'gtk-3.0', button_picture_width=125, menu_picture_width=125, num_cols=4, frame=True) + self.cinnamon_chooser = self.create_button_chooser(self.cinnamon_settings, 'name', 'themes', 'cinnamon', button_picture_width=125, menu_picture_width=125*self.scale, num_cols=4, frame=True) + + selected_meta_theme = None + + gladefile = "/usr/share/cinnamon/cinnamon-settings/themes.ui" + builder = Gtk.Builder() + builder.set_translation_domain('cinnamon') + builder.add_from_file(gladefile) + page = builder.get_object("page_simplified") + page.show() + + self.style_combo = builder.get_object("style_combo") + self.mixed_button = builder.get_object("mixed_button") + self.dark_button = builder.get_object("dark_button") + self.light_button = builder.get_object("light_button") + self.color_box = builder.get_object("color_box") + self.customize_button = builder.get_object("customize_button") + self.preset_button = builder.get_object("preset_button") + self.color_label = builder.get_object("color_label") + self.active_style = None + self.active_mode_name = None + self.active_variant = None + + # HiDPI support + for mode in ["mixed", "dark", "light"]: + path = f"/usr/share/cinnamon/cinnamon-settings/appearance-{mode}.svg" + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 112*self.scale, 80*self.scale) + surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.scale) + builder.get_object(f"image_{mode}").set_from_surface(surface) + + self.color_dot_svg = "" + with open("/usr/share/cinnamon/cinnamon-settings/color_dot.svg") as f: + self.color_dot_svg = f.read() + + self.reset_look_ui() + + self.mixed_button.connect("clicked", self.on_mode_button_clicked, "mixed") + self.dark_button.connect("clicked", self.on_mode_button_clicked, "dark") + self.light_button.connect("clicked", self.on_mode_button_clicked, "light") + self.customize_button.connect("clicked", self.on_customize_button_clicked) + self.style_combo.connect("changed", self.on_style_combo_changed) + + self.sidePage.stack.add_named(page, "simplified") + + page = SettingsPage() + self.sidePage.stack.add_titled(page, "themes", _("Themes")) + + settings = page.add_section() + + widget = self.make_group(_("Mouse Pointer"), self.cursor_chooser) + settings.add_row(widget) + + widget = self.make_group(_("Applications"), self.theme_chooser) + settings.add_row(widget) + + widget = self.make_group(_("Icons"), self.icon_chooser) + settings.add_row(widget) + + widget = self.make_group(_("Desktop"), self.cinnamon_chooser) + settings.add_row(widget) + + button = Gtk.Button() + button.set_label(_("Simplified settings...")) + button.set_halign(Gtk.Align.END) + button.connect("clicked", self.on_simplified_button_clicked) + page.add(button) + + page = DownloadSpicesPage(self, 'theme', self.spices, self.window) + self.sidePage.stack.add_titled(page, 'download', _("Add/Remove")) + + page = SettingsPage() + self.sidePage.stack.add_titled(page, "options", _("Settings")) + + settings = page.add_section(_("Miscellaneous options")) + + options = [("default", _("Let applications decide")), + ("prefer-dark", _("Prefer dark mode")), + ("prefer-light", _("Prefer light mode"))] + widget = GSettingsComboBox(_("Dark mode"), "org.x.apps.portal", "color-scheme", options) + widget.set_tooltip_text(_("This setting only affects applications which support dark mode")) + settings.add_row(widget) + + widget = GSettingsSwitch(_("Show icons in menus"), "org.cinnamon.settings-daemon.plugins.xsettings", "menus-have-icons") + settings.add_row(widget) + + widget = GSettingsSwitch(_("Show icons on buttons"), "org.cinnamon.settings-daemon.plugins.xsettings", "buttons-have-icons") + settings.add_row(widget) + + settings = page.add_section(_("Scrollbar behavior")) + + # Translators: The 'trough' is the part of the scrollbar that the 'handle' + # rides in. This setting determines whether clicking in that trough somewhere + # jumps directly to the new position, or if it only scrolls towards it. + switch = GtkSettingsSwitch(_("Jump to position when clicking in a trough"), "gtk-primary-button-warps-slider") + settings.add_row(switch) + + widget = GSettingsSwitch(_("Use overlay scroll bars"), "org.cinnamon.desktop.interface", "gtk-overlay-scrollbars") + settings.add_row(widget) + + self.gtk2_scrollbar_editor = Gtk2ScrollbarSizeEditor(widget.get_scale_factor()) + + switch = CssOverrideSwitch(_("Override the current theme's scrollbar width")) + settings.add_row(switch) + self.scrollbar_switch = switch.content_widget + + widget = CssRange(_("Scrollbar width"), "scrollbar slider", ["min-width", "min-height"], 2, 40, "px", None, switch) + settings.add_reveal_row(widget) + + try: + widget.sync_initial_switch_state() + except PermissionError as e: + print(e) + switch.set_sensitive(False) + + self.scrollbar_css_range = widget.content_widget + self.scrollbar_css_range.get_adjustment().set_page_increment(2.0) + + switch.content_widget.connect("notify::active", self.on_css_override_active_changed) + widget.content_widget.connect("value-changed", self.on_range_slider_value_changed) + + self.on_css_override_active_changed(switch) + + widget = PreviewWidget() + settings.add_row(widget) + + label_widget = LabelRow(_( +"""Changes may not apply to already-running programs, and may not affect all applications.""")) + settings.add_row(label_widget) + + self.builder = self.sidePage.builder + + for path in [THEME_FOLDERS[0], ICON_FOLDERS[0], ICON_FOLDERS[1]]: + try: + os.makedirs(path) + except OSError: + pass + + self.monitors = [] + for path in (THEME_FOLDERS + ICON_FOLDERS): + if os.path.exists(path): + file_obj = Gio.File.new_for_path(path) + try: + file_monitor = file_obj.monitor_directory(Gio.FileMonitorFlags.SEND_MOVED, None) + file_monitor.connect("changed", self.on_file_changed) + self.monitors.append(file_monitor) + except Exception as e: + # File monitors can fail when the OS runs out of file handles + print(e) + + self.refresh_choosers() + if config.PARSED_ARGS.module is None or (config.PARSED_ARGS.module == "themes" and config.PARSED_ARGS.tab is None): + GLib.idle_add(self.set_mode, "simplified" if self.active_variant is not None else "themes", True) + return + + GLib.idle_add(self.set_mode, self.sidePage.stack.get_visible_child_name()) + + def is_variant_active(self, variant): + # returns whether or not the given variant corresponds to the currently selected themes + if variant.gtk_theme != self.settings.get_string("gtk-theme"): + return False + if variant.icon_theme != self.settings.get_string("icon-theme"): + return False + if variant.cinnamon_theme != self.cinnamon_settings.get_string("name"): + return False + if variant.cursor_theme != self.settings.get_string("cursor-theme"): + return False + return True + + def is_variant_valid(self, variant): + # returns whether or not the given variant is valid (i.e. made of themes which are currently installed) + if variant.gtk_theme is None: + print("No Gtk theme defined") + return False + if variant.icon_theme is None: + print("No icon theme defined") + return False + if variant.cinnamon_theme is None: + print("No Cinnamon theme defined") + return False + if variant.cursor_theme is None: + print("No cursor theme defined") + return False + if variant.gtk_theme not in self.gtk_theme_names: + print("Gtk theme not found:", variant.gtk_theme) + return False + if variant.icon_theme not in self.icon_theme_names: + print("icon theme not found:", variant.icon_theme) + return False + if variant.cinnamon_theme not in self.cinnamon_theme_names and variant.cinnamon_theme != "cinnamon": + print("Cinnamon theme not found:", variant.cinnamon_theme) + return False + if variant.cursor_theme not in self.cursor_theme_names: + print("Cursor theme not found:", variant.cursor_theme) + return False + return True + + def cleanup_ui(self): + self.mixed_button.set_state_flags(Gtk.StateFlags.NORMAL, True) + self.dark_button.set_state_flags(Gtk.StateFlags.NORMAL, True) + self.light_button.set_state_flags(Gtk.StateFlags.NORMAL, True) + self.mixed_button.set_sensitive(False) + self.dark_button.set_sensitive(False) + self.light_button.set_sensitive(False) + for child in self.color_box.get_children(): + self.color_box.remove(child) + self.color_label.hide() + model = self.style_combo.get_model() + model.clear() + + def reset_look_ui(self): + if not self.ui_ready: + return + + self.ui_ready = False + self.cleanup_ui() + + # Read the JSON files + self.styles = {} + self.style_objects = {} + self.active_style = None + self.active_mode_name = None + self.active_variant = None + + path = "/usr/share/cinnamon/styles.d" + if os.path.exists(path): + for filename in sorted(os.listdir(path)): + if filename.endswith(".styles"): + try: + with open(os.path.join(path, filename)) as f: + json_text = json.loads(f.read()) + for style_json in json_text["styles"]: + style = Style(style_json) + for mode_name in ["mixed", "dark", "light"]: + if mode_name in style_json: + mode = Mode(mode_name) + for variant_json in style_json[mode_name]: + variant = Variant(variant_json) + if self.is_variant_valid(variant): + # Add the variant to the mode + mode.variants.append(variant) + if mode.default_variant is None: + # Assign the first variant as default + mode.default_variant = variant + if "default" in variant_json and variant_json["default"] == "true": + # Override default if specified + mode.default_variant = variant + # Add the mode to the style (if not done already) + if not mode_name in style.modes: + style.modes[mode_name] = mode + # Set it as the default mode if there's no default mode + if style.default_mode is None: + style.default_mode = mode + # Set active variant variables if the variant is active + if self.is_variant_active(variant): + self.active_style= style + self.active_mode_name = mode_name + self.active_variant = variant + # Override the default mode if specified + if "default" in style_json: + default_name = style_json["default"] + if default_name in style.modes: + style.default_mode = style.modes[default_name] + + if style.default_mode is None: + print ("No valid mode/variants found for style:", style.name) + else: + self.styles[style.name] = style + except Exception as e: + print(f"Failed to parse styles from {filename}.") + print(e) + + # Populate the style combo + for name in sorted(self.styles.keys()): + self.style_combo.append_text(name) + + if self.active_variant is not None: + style = self.active_style + mode = self.active_style.modes[self.active_mode_name] + variant = self.active_variant + print("Found active variant:", style.name, mode.name, variant.name) + # Position the style combo + model = self.style_combo.get_model() + iter = model.get_iter_first() + while (iter != None): + name = model.get_value(iter, 0) + if name == style.name: + self.style_combo.set_active_iter(iter) + break + iter = model.iter_next(iter) + # Set the mode buttons + for mode_name in ["mixed", "dark", "light"]: + if mode_name == "mixed": + button = self.mixed_button + elif mode_name == "dark": + button = self.dark_button + else: + button = self.light_button + # Set the button state + if mode_name == mode.name: + button.set_state_flags(Gtk.StateFlags.CHECKED, True) + else: + button.set_state_flags(Gtk.StateFlags.NORMAL, True) + if mode_name in style.modes: + button.set_sensitive(True) + else: + button.set_sensitive(False) + + if len(mode.variants) > 1: + # Generate the color buttons + self.color_label.show() + for variant in mode.variants: + svg = self.color_dot_svg.replace("#8cffbe", variant.color) + svg = svg.replace("#71718e", variant.color2) + svg = str.encode(svg) + stream = Gio.MemoryInputStream.new_from_bytes(GLib.Bytes.new(svg)) + pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, 22*self.scale, 22*self.scale, True, None) + surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.scale) + image = Gtk.Image.new_from_surface(surface) + button = Gtk.ToggleButton() + button.add(image) + button.show_all() + self.color_box.add(button) + if variant == self.active_variant: + button.set_state_flags(Gtk.StateFlags.CHECKED, True) + button.connect("clicked", self.on_color_button_clicked, variant) + else: + # Position style combo on "Custom" + self.style_combo.append_text(_("Custom")) + self.style_combo.set_active(len(self.styles.keys())) + self.ui_ready = True + + def on_customize_button_clicked(self, widget): + self.set_button_chooser(self.icon_chooser, self.settings.get_string("icon-theme"), 'icons', 'icons', ICON_SIZE) + self.set_button_chooser(self.cursor_chooser, self.settings.get_string("cursor-theme"), 'icons', 'cursors', 32) + self.set_button_chooser(self.theme_chooser, self.settings.get_string("gtk-theme"), 'themes', 'gtk-3.0', 35) + self.set_button_chooser(self.cinnamon_chooser, self.cinnamon_settings.get_string("name"), 'themes', 'cinnamon', 60) + self.set_mode("themes") + + def on_simplified_button_clicked(self, button): + self.reset_look_ui() + self.set_mode("simplified") + + def set_mode(self, mode, startup=False): + # When picking a start page at startup, no transition, or else you'll see the tail end of it happening + # as the page is loading. Otherwise, crossfade when switching between simple/custom. The left/right + # transition is kept as the default for shifting between the 3 custom pages (themes, downloads, settings). + if startup: + transition = Gtk.StackTransitionType.NONE + else: + transition = Gtk.StackTransitionType.CROSSFADE + + switcher_widget = Gio.Application.get_default().stack_switcher + + if mode == "simplified": + switcher_widget.set_opacity(0.0) + switcher_widget.set_sensitive(False) + else: + switcher_widget.set_opacity(1.0) + switcher_widget.set_sensitive(True) + + self.sidePage.stack.set_visible_child_full(mode, transition) + + def on_navigate_out_of_module(self): + switcher_widget = Gio.Application.get_default().stack_switcher + switcher_widget.set_opacity(1.0) + switcher_widget.set_sensitive(True) + + def on_color_button_clicked(self, button, variant): + print("Color button clicked") + self.activate_variant(variant) + + def on_mode_button_clicked(self, button, mode_name): + print("Mode button clicked") + if self.active_style is not None: + mode = self.active_style.modes[mode_name] + self.activate_mode(self.active_style, mode) + + def on_style_combo_changed(self, combobox): + if not self.ui_ready: + return + selected_name = combobox.get_active_text() + if selected_name == None or selected_name == _("Custom"): + return + print("Activating style:", selected_name) + for name in self.styles.keys(): + if name == selected_name: + style = self.styles[name] + mode = style.default_mode + self.activate_mode(style, mode) + + def activate_mode(self, style, mode): + print("Activating mode:", mode.name) + + if mode.name == "mixed": + self.xsettings.set_enum("color-scheme", 0) + elif mode.name == "dark": + self.xsettings.set_enum("color-scheme", 1) + elif mode.name == "light": + self.xsettings.set_enum("color-scheme", 2) + + if self.active_variant is not None: + new_same_variant = mode.get_variant_by_name(self.active_variant.name) + if new_same_variant is not None: + self.activate_variant(new_same_variant) + return + + self.activate_variant(mode.default_variant) + + def activate_variant(self, variant): + print("Activating variant:", variant.name) + self.settings.set_string("gtk-theme", variant.gtk_theme) + self.settings.set_string("icon-theme", variant.icon_theme) + self.cinnamon_settings.set_string("name", variant.cinnamon_theme) + self.settings.set_string("cursor-theme", variant.cursor_theme) + self.reset_look_ui() + + def on_css_override_active_changed(self, switch, pspec=None, data=None): + if self.scrollbar_switch.get_active(): + self.gtk2_scrollbar_editor.set_size(self.scrollbar_css_range.get_value()) + else: + self.gtk2_scrollbar_editor.set_size(0) + + def on_range_slider_value_changed(self, widget, data=None): + if self.scrollbar_switch.get_active(): + self.gtk2_scrollbar_editor.set_size(widget.get_value()) + + def on_file_changed(self, file, other, event, data): + if self.refreshing: + return + self.refreshing = True + GLib.timeout_add_seconds(5, self.refresh_themes) + GLib.timeout_add_seconds(5, self.refresh_choosers) + + def refresh_choosers(self): + array = [(self.cursor_chooser, "cursors", self.cursor_themes, self._on_cursor_theme_selected), + (self.theme_chooser, "gtk-3.0", self.gtk_themes, self._on_gtk_theme_selected), + (self.cinnamon_chooser, "cinnamon", self.cinnamon_themes, self._on_cinnamon_theme_selected), + (self.icon_chooser, "icons", self.icon_theme_names, self._on_icon_theme_selected)] + for element in array: + chooser, path_suffix, themes, callback = element + chooser.clear_menu() + chooser.set_sensitive(False) + chooser.progress = 0.0 + self.refresh_chooser(chooser, path_suffix, themes, callback) + self.refreshing = False + + def get_theme_category(self, theme_name, theme_type): + parts = theme_name.split('-') + + if theme_type == 'cursor': + # For cursors - first part of the name + return (parts[0], "Light") + + elif theme_type in ['gtk', 'icon']: + # Basic category is always the first part of the name + base_category = parts[0] + + # Exception: if the second part is a single letter, it's part of the category (e.g., Mint-X, Mint-Y) + if len(parts) >= 2 and len(parts[1]) == 1: + base_category = f"{parts[0]}-{parts[1]}" + + # Determine variant (light/dark/darker) + theme_lower = theme_name.lower() + if 'darker' in theme_lower: + variant = "Darker" + elif 'dark' in theme_lower: + variant = "Dark" + else: + variant = "Light" + + return (base_category, variant) + + elif theme_type == 'cinnamon': + # For desktop - basic category is the first part of the name + base_category = parts[0] + + # Exception: if the second part is a single letter, it's part of the category + if len(parts) >= 2 and len(parts[1]) == 1: + base_category = f"{parts[0]}-{parts[1]}" + + # Determine variant + theme_lower = theme_name.lower() + if 'darker' in theme_lower: + variant = "Darker" + elif 'dark' in theme_lower: + variant = "Dark" + else: + variant = "Light" + + return (base_category, variant) + + return ("Other", "Light") + + def refresh_chooser(self, chooser, path_suffix, themes, callback): + inc = 1.0 + if len(themes) > 0: + inc = 1.0 / len(themes) + + variant_sort_order = {"Light": 0, "Darker": 1, "Dark": 2} + + if path_suffix == 'icons': + cache_folder = GLib.get_user_cache_dir() + '/cs_themes/' + icon_cache_path = os.path.join(cache_folder, 'icons') + + # Retrieve list of known themes/locations for faster loading (icon theme loading and lookup are very slow) + if os.path.exists(icon_cache_path): + read_path = icon_cache_path + else: + read_path = '/usr/share/cinnamon/cinnamon-settings/icons' + + icon_paths = {} + with open(read_path, 'r') as cache_file: + for line in cache_file: + theme_name, icon_path = line.strip().split(':') + icon_paths[theme_name] = icon_path + + dump = False + + # Collect all themes with their categories and variants + categorized_themes = [] + for theme in themes: + category, variant = self.get_theme_category(theme, 'icon') + categorized_themes.append({'name': theme, 'category': category, 'variant': variant}) + + # Count themes per category + category_counts = {} + for theme_info in categorized_themes: + category_counts[theme_info['category']] = category_counts.get(theme_info['category'], 0) + 1 + + # Separate single-item categories + single_item_category_themes = [] + multi_item_category_themes = [] + + for theme_info in categorized_themes: + if category_counts[theme_info['category']] == 1: + single_item_category_themes.append(theme_info) + else: + multi_item_category_themes.append(theme_info) + + # Sort both lists + single_item_category_themes.sort(key=lambda x: (variant_sort_order.get(x['variant'], 3), x['name'])) + multi_item_category_themes.sort(key=lambda x: (x['category'], variant_sort_order.get(x['variant'], 3), x['name'])) + + # Display single-item category themes first, under an "Other Themes" label + if single_item_category_themes: + for theme_info in single_item_category_themes: + theme = theme_info['name'] + theme_path = None + if theme in icon_paths: + for theme_folder in ICON_FOLDERS: + possible_path = os.path.join(theme_folder, icon_paths[theme]) + if os.path.exists(possible_path): + theme_path = possible_path + break + + if theme_path is None: + icon_theme = Gtk.IconTheme() + icon_theme.set_custom_theme(theme) + folder = icon_theme.lookup_icon('folder', ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG) + if folder: + theme_path = folder.get_filename() + for theme_folder in ICON_FOLDERS: + if os.path.commonpath([theme_folder, theme_path]) == theme_folder: + icon_paths[theme] = os.path.relpath(theme_path, start=theme_folder) + break + dump = True + + if theme_path is None: + continue + + if os.path.exists(theme_path): + chooser.add_picture(theme_path, callback, title=theme, id=theme) + GLib.timeout_add(5, self.increment_progress, (chooser, inc)) + + # Add a blank separator if both single and multi-item categories exist + if single_item_category_themes and multi_item_category_themes: + chooser.add_separator() + + current_category = None + current_variant = None + # Display multi-item category themes + for theme_info in multi_item_category_themes: + category = theme_info['category'] + variant = theme_info['variant'] + theme = theme_info['name'] + + if current_category != category: + if current_category is not None: + chooser.add_separator() # Blank separator between categories + current_category = category + current_variant = None # Reset variant when category changes + chooser.add_separator(category) # Category name separator + + # Update current_variant, no separator here if it's just a variant change + current_variant = variant + + theme_path = None + if theme in icon_paths: + # loop through all possible locations until we find a match + # (user folders should override system ones) + for theme_folder in ICON_FOLDERS: + possible_path = os.path.join(theme_folder, icon_paths[theme]) + if os.path.exists(possible_path): + theme_path = possible_path + break + + if theme_path is None: + icon_theme = Gtk.IconTheme() + icon_theme.set_custom_theme(theme) + folder = icon_theme.lookup_icon('folder', ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG) + if folder: + theme_path = folder.get_filename() + # we need to get the relative path for storage + for theme_folder in ICON_FOLDERS: + if os.path.commonpath([theme_folder, theme_path]) == theme_folder: + icon_paths[theme] = os.path.relpath(theme_path, start=theme_folder) + break + dump = True + + if theme_path is None: + continue + + if os.path.exists(theme_path): + chooser.add_picture(theme_path, callback, title=theme, id=theme) + GLib.timeout_add(5, self.increment_progress, (chooser, inc)) + + if dump: + if not os.path.exists(cache_folder): + os.mkdir(cache_folder) + + with open(icon_cache_path, 'w') as cache_file: + for theme_name, icon_path_val in icon_paths.items(): # Renamed icon_path to avoid conflict + cache_file.write(f'{theme_name}:{icon_path_val}\n') + + else: + if path_suffix == "cinnamon": + chooser.add_picture("/usr/share/cinnamon/theme/thumbnail.png", callback, title="cinnamon", id="cinnamon") + if path_suffix in ["gtk-3.0", "cinnamon"]: + # Sort themes by user-installed first, then alphabetically + themes = sorted(themes, key=lambda t: (not t[1].startswith(GLib.get_home_dir()), t[0].lower())) + + + # Collect all themes with their categories and variants + categorized_themes = [] + for theme_data in themes: # theme_data is a tuple (name, path) + name = theme_data[0] + path = theme_data[1] + theme_type = 'gtk' if path_suffix == 'gtk-3.0' else 'cinnamon' + category, variant = self.get_theme_category(name, theme_type) + categorized_themes.append({'name': name, 'path': path, 'category': category, 'variant': variant, 'original_tuple': theme_data}) + + # Count themes per category + category_counts = {} + for theme_info in categorized_themes: + category_counts[theme_info['category']] = category_counts.get(theme_info['category'], 0) + 1 + + single_item_category_themes = [] + multi_item_category_themes = [] + + for theme_info in categorized_themes: + if category_counts[theme_info['category']] == 1: + single_item_category_themes.append(theme_info) + else: + multi_item_category_themes.append(theme_info) + + # Sort both lists + single_item_category_themes.sort(key=lambda x: (variant_sort_order.get(x['variant'], 3), x['name'].lower())) + multi_item_category_themes.sort(key=lambda x: (x['category'].lower(), variant_sort_order.get(x['variant'], 3), x['name'].lower())) + + # Display single-item category themes first, under an "Other Themes" label + if single_item_category_themes: + for theme_info in single_item_category_themes: + theme_name = theme_info['name'] + theme_path_val = theme_info['path'] # Renamed theme_path to avoid conflict + try: + for path_option in [f"{theme_path_val}/{theme_name}/{path_suffix}/thumbnail.png", + f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme_name}.png", + f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]: + if os.path.exists(path_option): + chooser.add_picture(path_option, callback, title=theme_name, id=theme_name) + break + except: + chooser.add_picture(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png", callback, title=theme_name, id=theme_name) + GLib.timeout_add(5, self.increment_progress, (chooser, inc)) + + # Add a blank separator if both single and multi-item categories exist + if single_item_category_themes and multi_item_category_themes: + chooser.add_separator() + + current_category = None + current_variant = None + # Display multi-item category themes + for theme_info in multi_item_category_themes: + category = theme_info['category'] + variant = theme_info['variant'] + theme_name = theme_info['name'] + theme_path_val = theme_info['path'] # Renamed theme_path to avoid conflict + + if current_category != category: + if current_category is not None: + chooser.add_separator() # Blank separator between categories + current_category = category + current_variant = None # Reset variant when category changes + chooser.add_separator(category) # Category name separator + + # Update current_variant, no separator here if it's just a variant change + current_variant = variant + + try: + for path_option in [f"{theme_path_val}/{theme_name}/{path_suffix}/thumbnail.png", + f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme_name}.png", + f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]: + if os.path.exists(path_option): + chooser.add_picture(path_option, callback, title=theme_name, id=theme_name) + break + except: + chooser.add_picture(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png", callback, title=theme_name, id=theme_name) + GLib.timeout_add(5, self.increment_progress, (chooser, inc)) + GLib.timeout_add(500, self.hide_progress, chooser) + + def increment_progress(self, payload): + (chooser, inc) = payload + chooser.increment_loading_progress(inc) + + def hide_progress(self, chooser): + chooser.set_sensitive(True) + chooser.reset_loading_progress() + + def _setParentRef(self, window): + self.window = window + + def make_group(self, group_label, widget, add_widget_to_size_group=True): + self.size_groups = getattr(self, "size_groups", [Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) for x in range(2)]) + box = SettingsWidget() + label = Gtk.Label() + label.set_markup(group_label) + label.props.xalign = 0.0 + self.size_groups[0].add_widget(label) + box.pack_start(label, False, False, 0) + if add_widget_to_size_group: + self.size_groups[1].add_widget(widget) + box.pack_end(widget, False, False, 0) + + return box + + def create_button_chooser(self, settings, key, path_prefix, path_suffix, button_picture_width, menu_picture_width, num_cols, frame): + chooser = PictureChooserButton(num_cols=num_cols, button_picture_width=button_picture_width, menu_picture_width=menu_picture_width, has_button_label=True, frame=frame) + theme = settings.get_string(key) + self.set_button_chooser(chooser, theme, path_prefix, path_suffix, button_picture_width) + return chooser + + def set_button_chooser(self, chooser, theme, path_prefix, path_suffix, button_picture_width): + self.set_button_chooser_text(chooser, theme) + if path_suffix == "cinnamon" and theme == "cinnamon": + chooser.set_picture_from_file("/usr/share/cinnamon/theme/thumbnail.png") + elif path_suffix == "icons": + current_theme = Gtk.IconTheme.get_default() + folder = current_theme.lookup_icon_for_scale("folder", button_picture_width, self.window.get_scale_factor(), 0) + if folder is not None: + path = folder.get_filename() + chooser.set_picture_from_file(path) + else: + try: + for path in ([os.path.join(datadir, path_prefix, theme, path_suffix, "thumbnail.png") for datadir in GLib.get_system_data_dirs()] + + [os.path.expanduser(f"~/.{path_prefix}/{theme}/{path_suffix}/thumbnail.png"), + f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme}.png", + f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]): + if os.path.exists(path): + chooser.set_picture_from_file(path) + break + except: + chooser.set_picture_from_file(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png") + + def set_button_chooser_text(self, chooser, theme): + chooser.set_button_label(theme) + chooser.set_tooltip_text(theme) + + def _on_icon_theme_selected(self, path, theme): + try: + self.settings.set_string("icon-theme", theme) + self.set_button_chooser_text(self.icon_chooser, theme) + except Exception as detail: + print(detail) + return True + + def _on_gtk_theme_selected(self, path, theme): + try: + self.settings.set_string("gtk-theme", theme) + self.set_button_chooser_text(self.theme_chooser, theme) + except Exception as detail: + print(detail) + return True + + def _on_cursor_theme_selected(self, path, theme): + try: + self.settings.set_string("cursor-theme", theme) + self.set_button_chooser_text(self.cursor_chooser, theme) + except Exception as detail: + print(detail) + + self.update_cursor_theme_link(path, theme) + return True + + def _on_cinnamon_theme_selected(self, path, theme): + try: + self.cinnamon_settings.set_string("name", theme) + self.set_button_chooser_text(self.cinnamon_chooser, theme) + except Exception as detail: + print(detail) + return True + + def filter_func_gtk_dir(self, directory): + theme_dir = Path(directory) + for gtk3_dir in theme_dir.glob("gtk-3.*"): + # Skip gtk key themes + if os.path.exists(os.path.join(gtk3_dir, "gtk.css")): + return True + return False + + def update_cursor_theme_link(self, path, name): + contents = f"[icon theme]\nInherits={name}\n" + self._set_cursor_theme_at(ICON_FOLDERS[0], contents) + self._set_cursor_theme_at(ICON_FOLDERS[1], contents) + + def _set_cursor_theme_at(self, directory, contents): + default_dir = os.path.join(directory, "default") + index_path = os.path.join(default_dir, "index.theme") + + try: + os.makedirs(default_dir) + except os.error as e: + pass + + if os.path.exists(index_path): + os.unlink(index_path) + + with open(index_path, "w") as f: + f.write(contents) \ No newline at end of file diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 9806d049..bd45c699 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -4,6 +4,11 @@ + + + + + 0 @@ -33,17 +38,46 @@ 20.00 The start time - When “night-light-schedule-automatic” is disabled, use this start time in hours from midnight. + When “night-light-schedule-mode” is not 'auto', use this start time in hours from midnight. 6.00 The end time - When “night-light-schedule-automatic” is disabled, use this end time in hours from midnight. + When “night-light-schedule-mode” is not 'auto', use this end time in hours from midnight. (91,181) The last detected position When location services are available this represents the last detected location. The default value is an invalid value to ensure it is always updated at startup. + + false + If the night theme switcher is on + Changes the theme to “night-theme” when night light activates (and back). + + + Mint-Y-Dark-Aqua + The theme which should be activated at night + This theme will be switched on instead of the current theme as soon as the night light gets activated. + + + false + If the night theme is currently active + This is set to true when the cinnamon-settings-daemon activates the night theme (and set to false when it's deactivated again). + + + Mint-Y-Aqua + This is set to the last detected day theme + When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. + + + Mint-Y-Aqua + This is set to the last detected day desktop theme + When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. + + + 'prefer-dark' + This is set to the last detected day xapp color-scheme + When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. diff --git a/plugins/color/csd-color-manager.c b/plugins/color/csd-color-manager.c index 936a5cca..543de3cb 100644 --- a/plugins/color/csd-color-manager.c +++ b/plugins/color/csd-color-manager.c @@ -29,7 +29,7 @@ #include "csd-color-manager.h" #include "csd-color-profiles.h" #include "csd-color-state.h" -#include "csd-night-light.h" +#include "csd-night-mode.h" #define CSD_DBUS_NAME "org.cinnamon.SettingsDaemon" #define CSD_DBUS_PATH "/org/cinnamon/SettingsDaemon" @@ -39,13 +39,19 @@ #define CSD_COLOR_DBUS_PATH CSD_DBUS_PATH "/Color" #define CSD_COLOR_DBUS_INTERFACE CSD_DBUS_BASE_INTERFACE ".Color" +/*introspection_xml is for dbus events this manager listens on - see the methods void on_..._notify(...)*/ + static const gchar introspection_xml[] = "" " " " " " " " " +" " +" " +" " " " +" " " " " " " " @@ -66,9 +72,10 @@ struct _CsdColorManager CsdColorCalibrate *calibrate; CsdColorProfiles *profiles; CsdColorState *state; - CsdNightLight *nlight; + CsdNightMode *nmode; - guint nlight_forced_timeout_id; + guint nlight_forced_timeout_id; + guint ntheme_forced_timeout_id; }; enum { @@ -163,52 +170,62 @@ emit_property_changed (CsdColorManager *manager, } static void -on_active_notify (CsdNightLight *nlight, - GParamSpec *pspec, - gpointer user_data) +on_active_nlight_notify (CsdNightMode *nmode, + GParamSpec *pspec, + gpointer user_data) { CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); emit_property_changed (manager, "NightLightActive", - g_variant_new_boolean (csd_night_light_get_active (manager->nlight))); + g_variant_new_boolean (csd_night_light_get_active (manager->nmode))); } static void -on_sunset_notify (CsdNightLight *nlight, +on_active_ntheme_notify (CsdNightMode *nmode, + GParamSpec *pspec, + gpointer user_data) +{ + CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); + emit_property_changed (manager, "NightThemeActive", + g_variant_new_boolean (csd_night_theme_get_active (manager->nmode))); +} + +static void +on_sunset_notify (CsdNightMode *nmode, GParamSpec *pspec, gpointer user_data) { CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); emit_property_changed (manager, "Sunset", - g_variant_new_double (csd_night_light_get_sunset (manager->nlight))); + g_variant_new_double (csd_night_mode_get_sunset (manager->nmode))); } static void -on_sunrise_notify (CsdNightLight *nlight, +on_sunrise_notify (CsdNightMode *nmode, GParamSpec *pspec, gpointer user_data) { CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); emit_property_changed (manager, "Sunrise", - g_variant_new_double (csd_night_light_get_sunrise (manager->nlight))); + g_variant_new_double (csd_night_mode_get_sunrise (manager->nmode))); } static void -on_disabled_until_tmw_notify (CsdNightLight *nlight, +on_disabled_until_tmw_notify (CsdNightMode *nmode, GParamSpec *pspec, gpointer user_data) { CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); emit_property_changed (manager, "DisabledUntilTomorrow", - g_variant_new_boolean (csd_night_light_get_disabled_until_tmw (manager->nlight))); + g_variant_new_boolean (csd_night_mode_get_disabled_until_tmw (manager->nmode))); } static void -on_temperature_notify (CsdNightLight *nlight, +on_temperature_notify (CsdNightMode *nmode, GParamSpec *pspec, gpointer user_data) { CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); - gdouble temperature = csd_night_light_get_temperature (manager->nlight); + gdouble temperature = csd_night_light_get_temperature (manager->nmode); csd_color_state_set_temperature (manager->state, temperature); emit_property_changed (manager, "Temperature", g_variant_new_double (temperature)); @@ -222,17 +239,17 @@ csd_color_manager_init (CsdColorManager *manager) manager->profiles = csd_color_profiles_new (); manager->state = csd_color_state_new (); - /* night light features */ - manager->nlight = csd_night_light_new (); - g_signal_connect (manager->nlight, "notify::active", + /* night light (and theme) features */ + manager->nmode = csd_night_mode_new (); + g_signal_connect (manager->nmode, "notify::active", G_CALLBACK (on_active_notify), manager); - g_signal_connect (manager->nlight, "notify::sunset", + g_signal_connect (manager->nmode, "notify::sunset", G_CALLBACK (on_sunset_notify), manager); - g_signal_connect (manager->nlight, "notify::sunrise", + g_signal_connect (manager->nmode, "notify::sunrise", G_CALLBACK (on_sunrise_notify), manager); - g_signal_connect (manager->nlight, "notify::temperature", + g_signal_connect (manager->nmode, "notify::temperature", G_CALLBACK (on_temperature_notify), manager); - g_signal_connect (manager->nlight, "notify::disabled-until-tmw", + g_signal_connect (manager->nmode, "notify::disabled-until-tmw", G_CALLBACK (on_disabled_until_tmw_notify), manager); } @@ -261,13 +278,17 @@ csd_color_manager_finalize (GObject *object) manager->name_id = 0; } - if (manager->nlight_forced_timeout_id) + if (manager->nlight_forced_timeout_id) { g_source_remove (manager->nlight_forced_timeout_id); + } + if (manager->ntheme_forced_timeout_id) { + g_source_remove (manager->ntheme_forced_timeout_id); + } g_clear_object (&manager->calibrate); g_clear_object (&manager->profiles); g_clear_object (&manager->state); - g_clear_object (&manager->nlight); + g_clear_object (&manager->nmode); G_OBJECT_CLASS (csd_color_manager_parent_class)->finalize (object); } @@ -278,7 +299,18 @@ nlight_forced_timeout_cb (gpointer user_data) CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); manager->nlight_forced_timeout_id = 0; - csd_night_light_set_forced (manager->nlight, FALSE); + csd_night_light_set_forced (manager->nmode, FALSE); + + return G_SOURCE_REMOVE; +} + +static gboolean +ntheme_forced_timeout_cb (gpointer user_data) +{ + CsdColorManager *manager = CSD_COLOR_MANAGER (user_data); + + manager->ntheme_forced_timeout_id = 0; + csd_night_theme_set_forced (manager->nmode, FALSE); return G_SOURCE_REMOVE; } @@ -298,10 +330,10 @@ handle_method_call (GDBusConnection *connection, if (g_strcmp0 (method_name, "NightLightPreview") == 0) { guint32 duration = 0; - if (!manager->nlight) { + if (!manager->nmode) { g_dbus_method_invocation_return_error_literal (invocation, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, - "Night-light is currently unavailable"); + "Night-mode is currently unavailable"); return; } @@ -320,9 +352,39 @@ handle_method_call (GDBusConnection *connection, g_source_remove (manager->nlight_forced_timeout_id); manager->nlight_forced_timeout_id = g_timeout_add_seconds (duration, nlight_forced_timeout_cb, manager); - csd_night_light_set_forced (manager->nlight, TRUE); + csd_night_light_set_forced (manager->nmode, TRUE); + + g_dbus_method_invocation_return_value (invocation, NULL); + + } else if (g_strcmp0 (method_name, "NightThemePreview") == 0) { + guint duration = 0; + + if (!manager->nmode) { + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "Night-mode is currently unavailable"); + + return; + } + + g_variant_get (parameters, "(u)", &duration); + + if (duration == 0 || duration > 120) { + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Duration is out of the range (0-120]."); + + return; + } + + if (manager->ntheme_forced_timeout_id) + g_source_remove (manager->ntheme_forced_timeout_id); + manager->ntheme_forced_timeout_id = g_timeout_add_seconds (duration, ntheme_forced_timeout_cb, manager); + + csd_night_theme_set_forced (manager->nmode, TRUE); g_dbus_method_invocation_return_value (invocation, NULL); + } else { g_assert_not_reached (); } @@ -345,7 +407,10 @@ handle_get_property (GDBusConnection *connection, } if (g_strcmp0 (property_name, "NightLightActive") == 0) - return g_variant_new_boolean (csd_night_light_get_active (manager->nlight)); + return g_variant_new_boolean (csd_night_light_get_active (manager->nmode)); + + if (g_strcmp0 (property_name, "NightThemeActive") == 0) + return g_variant_new_boolean (csd_night_theme_get_active (manager->nmode)); if (g_strcmp0 (property_name, "Temperature") == 0) { guint temperature; @@ -354,13 +419,13 @@ handle_get_property (GDBusConnection *connection, } if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) - return g_variant_new_boolean (csd_night_light_get_disabled_until_tmw (manager->nlight)); + return g_variant_new_boolean (csd_night_mode_get_disabled_until_tmw (manager->nmode)); if (g_strcmp0 (property_name, "Sunrise") == 0) - return g_variant_new_double (csd_night_light_get_sunrise (manager->nlight)); + return g_variant_new_double (csd_night_mode_get_sunrise (manager->nmode)); if (g_strcmp0 (property_name, "Sunset") == 0) - return g_variant_new_double (csd_night_light_get_sunset (manager->nlight)); + return g_variant_new_double (csd_night_mode_get_sunset (manager->nmode)); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to get property: %s", property_name); @@ -408,7 +473,7 @@ handle_set_property (GDBusConnection *connection, } if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) { - csd_night_light_set_disabled_until_tmw (manager->nlight, + csd_night_mode_set_disabled_until_tmw (manager->nmode, g_variant_get_boolean (value)); return TRUE; } @@ -466,9 +531,9 @@ on_bus_gotten (GObject *source_object, manager, NULL); - /* setup night light module */ - if (!csd_night_light_start (manager->nlight, &error)) { - g_warning ("Could not start night light module: %s", error->message); + /* setup night mode module */ + if (!csd_night_mode_start (manager->nmode, &error)) { + g_warning ("Could not start night mode module: %s", error->message); g_error_free (error); } } diff --git a/plugins/color/csd-night-light.c b/plugins/color/csd-night-light.c index 249fdbd8..011cfc51 100644 --- a/plugins/color/csd-night-light.c +++ b/plugins/color/csd-night-light.c @@ -25,14 +25,18 @@ #include "csd-color-state.h" -#include "csd-night-light.h" -#include "csd-night-light-common.h" +#include "csd-night-mode.h" +#include "csd-night-mode-common.h" #include "tz-coords.h" -struct _CsdNightLight { +struct _CsdNightMode { GObject parent; GSettings *settings; - gboolean forced; + GSettings *theme_settings; + GSettings *x_theme_settings; + GSettings *cinnamon_theme_settings; + gboolean light_forced; + gboolean theme_forced; gboolean disabled_until_tmw; GDateTime *disabled_until_tmw_dt; GSource *source; @@ -51,37 +55,39 @@ struct _CsdNightLight { enum { PROP_0, - PROP_ACTIVE, + PROP_LIGHT_ACTIVE, + PROP_THEME_ACTIVE, PROP_SUNRISE, PROP_SUNSET, PROP_TEMPERATURE, PROP_DISABLED_UNTIL_TMW, - PROP_FORCED, + PROP_LIGHT_FORCED, + PROP_THEME_FORCED, PROP_LAST }; enum { - NIGHT_LIGHT_SCHEDULE_AUTO = 0, - NIGHT_LIGHT_SCHEDULE_MANUAL = 1, - NIGHT_LIGHT_SCHEDULE_ALWAYS_ON = 2 + NIGHT_MODE_SCHEDULE_AUTO = 0, + NIGHT_MODE_SCHEDULE_MANUAL = 1, + NIGHT_MODE_SCHEDULE_ALWAYS_ON = 2 }; -#define CSD_NIGHT_LIGHT_SCHEDULE_TIMEOUT 5 /* seconds */ -#define CSD_NIGHT_LIGHT_POLL_TIMEOUT 60 /* seconds */ +#define CSD_NIGHT_MODE_SCHEDULE_TIMEOUT 5 /* seconds */ +#define CSD_NIGHT_MODE_POLL_TIMEOUT 60 /* seconds */ #define CSD_NIGHT_LIGHT_POLL_SMEAR 1 /* hours */ #define CSD_NIGHT_LIGHT_SMOOTH_SMEAR 5.f /* seconds */ #define CSD_FRAC_DAY_MAX_DELTA (1.f/60.f) /* 1 minute */ #define CSD_TEMPERATURE_MAX_DELTA (10.f) /* Kelvin */ -static void poll_timeout_destroy (CsdNightLight *self); -static void poll_timeout_create (CsdNightLight *self); -static void night_light_recheck (CsdNightLight *self); +static void poll_timeout_destroy (CsdNightMode *self); +static void poll_timeout_create (CsdNightMode *self); +static void night_mode_recheck (CsdNightMode *self); -G_DEFINE_TYPE (CsdNightLight, csd_night_light, G_TYPE_OBJECT); +G_DEFINE_TYPE (CsdNightMode, csd_night_mode, G_TYPE_OBJECT); static GDateTime * -csd_night_light_get_date_time_now (CsdNightLight *self) +csd_night_mode_get_date_time_now (CsdNightMode *self) { if (self->datetime_override != NULL) return g_date_time_ref (self->datetime_override); @@ -89,17 +95,17 @@ csd_night_light_get_date_time_now (CsdNightLight *self) } void -csd_night_light_set_date_time_now (CsdNightLight *self, GDateTime *datetime) +csd_night_mode_set_date_time_now (CsdNightMode *self, GDateTime *datetime) { if (self->datetime_override != NULL) g_date_time_unref (self->datetime_override); self->datetime_override = g_date_time_ref (datetime); - night_light_recheck (self); + night_mode_recheck (self); } static void -poll_smooth_destroy (CsdNightLight *self) +poll_smooth_destroy (CsdNightMode *self) { if (self->smooth_id != 0) { g_source_remove (self->smooth_id); @@ -110,8 +116,8 @@ poll_smooth_destroy (CsdNightLight *self) } void -csd_night_light_set_smooth_enabled (CsdNightLight *self, - gboolean smooth_enabled) +csd_night_mode_set_smooth_enabled (CsdNightMode *self, + gboolean smooth_enabled) { /* ensure the timeout is stopped if called at runtime */ if (!smooth_enabled) @@ -128,7 +134,7 @@ linear_interpolate (gdouble val1, gdouble val2, gdouble factor) } static gboolean -update_cached_sunrise_sunset (CsdNightLight *self) +update_cached_sunrise_sunset (CsdNightMode *self) { gboolean ret = FALSE; gdouble latitude; @@ -136,7 +142,7 @@ update_cached_sunrise_sunset (CsdNightLight *self) gdouble sunrise; gdouble sunset; g_autoptr(GVariant) tmp = NULL; - g_autoptr(GDateTime) dt_now = csd_night_light_get_date_time_now (self); + g_autoptr(GDateTime) dt_now = csd_night_mode_get_date_time_now (self); /* calculate the sunrise/sunset for the location */ tmp = g_settings_get_value (self->settings, "night-light-last-coordinates"); @@ -145,8 +151,8 @@ update_cached_sunrise_sunset (CsdNightLight *self) return FALSE; if (longitude > 180.f || longitude < -180.f) return FALSE; - if (!csd_night_light_get_sunrise_sunset (dt_now, latitude, longitude, - &sunrise, &sunset)) { + if (!csd_night_mode_get_sunrise_sunset (dt_now, latitude, longitude, + &sunrise, &sunset)) { g_warning ("failed to get sunset/sunrise for %.3f,%.3f", latitude, longitude); return FALSE; @@ -156,14 +162,14 @@ update_cached_sunrise_sunset (CsdNightLight *self) if (ABS (self->cached_sunrise - sunrise) > CSD_FRAC_DAY_MAX_DELTA) { self->cached_sunrise = sunrise; g_object_notify (G_OBJECT (self), "sunrise"); - g_autofree gchar *formatted = csd_night_light_time_string_from_frac (sunrise); + g_autofree gchar *formatted = csd_night_mode_time_string_from_frac (sunrise); g_debug ("Sunrise updated: %.3f (%s)", sunrise, formatted); ret = TRUE; } if (ABS (self->cached_sunset - sunset) > CSD_FRAC_DAY_MAX_DELTA) { self->cached_sunset = sunset; g_object_notify (G_OBJECT (self), "sunset"); - g_autofree gchar *formatted = csd_night_light_time_string_from_frac (sunset); + g_autofree gchar *formatted = csd_night_mode_time_string_from_frac (sunset); g_debug ("Sunset updated: %.3f (%s)", sunset, formatted); ret = TRUE; } @@ -171,7 +177,7 @@ update_cached_sunrise_sunset (CsdNightLight *self) } static void -csd_night_light_set_temperature_internal (CsdNightLight *self, gdouble temperature) +csd_night_light_set_temperature_internal (CsdNightMode *self, gdouble temperature) { if (ABS (self->cached_temperature - temperature) <= CSD_TEMPERATURE_MAX_DELTA) return; @@ -182,7 +188,7 @@ csd_night_light_set_temperature_internal (CsdNightLight *self, gdouble temperatu static gboolean csd_night_light_smooth_cb (gpointer user_data) { - CsdNightLight *self = CSD_NIGHT_LIGHT (user_data); + CsdNightMode *self = CSD_NIGHT_MODE (user_data); gdouble tmp; gdouble frac; @@ -205,7 +211,7 @@ csd_night_light_smooth_cb (gpointer user_data) } static void -poll_smooth_create (CsdNightLight *self, gdouble temperature) +poll_smooth_create (CsdNightMode *self, gdouble temperature) { g_assert (self->smooth_id == 0); self->smooth_target_temperature = temperature; @@ -214,7 +220,7 @@ poll_smooth_create (CsdNightLight *self, gdouble temperature) } static void -csd_night_light_set_temperature (CsdNightLight *self, gdouble temperature) +csd_night_light_set_temperature (CsdNightMode *self, gdouble temperature) { /* immediate */ if (!self->smooth_enabled) { @@ -236,21 +242,155 @@ csd_night_light_set_temperature (CsdNightLight *self, gdouble temperature) } static void -csd_night_light_set_active (CsdNightLight *self, gboolean active) +csd_night_light_set_active (CsdNightMode *self, gboolean active) { - if (self->cached_active == active) + if (self->cached_light_active == active) return; - self->cached_active = active; + self->cached_light_active = active; /* ensure set to unity temperature */ if (!active) csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); - g_object_notify (G_OBJECT (self), "active"); + g_object_notify (G_OBJECT (self), "light-active"); +} + +static void +csd_night_theme_set_active (CsdNightMode *self, gboolean active) +{ + if (self->cached_theme_active == active) + return; + self->cached_theme_active = active; + + /* switch off theme if not active & switch on else */ + if (!active) + csd_night_theme_switch_off (self); + else + csd_night_theme_switch_on (self); + + g_object_notify (G_OBJECT (self), "theme-active"); +} + +static void +night_mode_recheck (CsdNightMode *self) +{ + if (self->light_forced && self->theme_forced) { + night_light_recheck (self); + night_theme_recheck (self); + return; + } + + /* check if it's still not tomorrow */ + if (self->disabled_until_tmw) { + GTimeSpan time_span; + gboolean reset = FALSE; + + time_span = g_date_time_difference (dt_now, self->disabled_until_tmw_dt); + + /* Reset if disabled until tomorrow is more than 24h ago. */ + if (time_span > (GTimeSpan) 24 * 60 * 60 * 1000000) { + g_debug ("night mode disabled-until-tomorrow is older than 24h, resetting disabled-until-tomorrow"); + reset = TRUE; + } else if (time_span > 0) { + /* Or if a sunrise lies between the time it was disabled and now. */ + gdouble frac_disabled; + frac_disabled = csd_night_mode_frac_day_from_dt (self->disabled_until_tmw_dt); + if (frac_disabled != frac_day && + csd_night_mode_frac_day_is_between (schedule_to, + frac_disabled, + frac_day)) { + g_debug ("night mode sun rise happened, resetting disabled-until-tomorrow"); + reset = TRUE; + } + } + + if (reset) { + self->disabled_until_tmw = FALSE; + g_clear_pointer(&self->disabled_until_tmw_dt, g_date_time_unref); + g_object_notify (G_OBJECT (self), "disabled-until-tmw"); + } else { + g_debug ("night mode disabled - it's still not tomorrow ):"); + } + } + + if (g_settings_get_enum (self->settings, "night-light-schedule-mode") == NIGHT_MODE_SCHEDULE_AUTO) { + /* calculate the position of the sun */ + update_cached_sunrise_sunset (self); + } + + night_light_recheck (self); + night_theme_recheck (self); } static void -night_light_recheck (CsdNightLight *self) +night_theme_recheck (CsdNightMode *self) +{ + gdouble frac_day; + gdouble schedule_from = -1.f; + gdouble schedule_to = -1.f; + g_autoptr(GDateTime) dt_now = csd_night_mode_get_date_time_now (self); + + /* If forced (e.g. for preview), just switch on and return */ + if (self->theme_forced) { + csd_night_theme_switch_on (self); + g_debug ("night theme forced on"); + return; + } + + if (!g_settings_get_boolean (self->settings, "night-theme-enabled")) { + /* settings say NO */ + csd_night_theme_set_active(self, FALSE); + g_debug ("night theme disabled"); + return; + } + + switch (g_settings_get_enum (self->settings, "night-light-schedule-mode")) { + case NIGHT_MODE_SCHEDULE_ALWAYS_ON: + /* turn OFF and return (night theme always on? Who wants that. Select your theme manually in the settings) */ + g_debug ("night light always on - switching OFF theme (intentionally)"); + csd_night_theme_set_active (self, FALSE); + return; + case NIGHT_MODE_SCHEDULE_AUTO: + /* was updated in night_mode_recheck(self) */ + schedule_to = self->cached_sunrise; + schedule_from = self->cached_sunset; + break; + default: + break; + } + + /* fall back to manual settings (if nothing was calculated) */ + if (schedule_to <= 0.f || schedule_from <= 0.f) { + schedule_from = g_settings_get_double (self->settings, + "night-light-schedule-from"); + schedule_to = g_settings_get_double (self->settings, + "night-light-schedule-to"); + } + + /* get the current hour of a day as a fraction */ + frac_day = csd_night_mode_frac_day_from_dt (dt_now); + + /* disabled until tomorrow - updated in night_mode_recheck(self) */ + if (self->disabled_until_tmw) { + csd_night_theme_set_active(self, FALSE); + return; + } + + /* theme is activated from schedule_from to schedule_to - easy */ + if (!csd_night_mode_frac_day_is_between (frac_day, + schedule_from, + schedule_to)) { + g_debug ("not time for night theme"); + csd_night_theme_set_active (self, FALSE); + return; + } + + csd_night_theme_set_active(self, TRUE); + g_debug ("night theme is active!"); +} + +static void +night_light_recheck (CsdNightMode *self) { gdouble frac_day; gdouble schedule_from = -1.f; @@ -258,13 +398,14 @@ night_light_recheck (CsdNightLight *self) gdouble smear = CSD_NIGHT_LIGHT_POLL_SMEAR; /* hours */ guint temperature; guint temp_smeared; - g_autoptr(GDateTime) dt_now = csd_night_light_get_date_time_now (self); + g_autoptr(GDateTime) dt_now = csd_night_mode_get_date_time_now (self); - /* Forced mode, just set the temperature to night light. + /* Forced mode, just set the temperature to night. * Proper rechecking will happen once forced mode is disabled again */ - if (self->forced) { + if (self->light_forced) { temperature = g_settings_get_uint (self->settings, "night-light-temperature"); csd_night_light_set_temperature (self, temperature); + g_debug ("night light forced on"); return; } @@ -277,27 +418,24 @@ night_light_recheck (CsdNightLight *self) /* schedule-mode */ switch (g_settings_get_enum (self->settings, "night-light-schedule-mode")) { - case NIGHT_LIGHT_SCHEDULE_ALWAYS_ON: + case NIGHT_MODE_SCHEDULE_ALWAYS_ON: /* just set the temperature to night light */ temperature = g_settings_get_uint (self->settings, "night-light-temperature"); - g_debug ("night light mode always on, using temperature of %uK", + g_debug ("night light always on, using temperature of %uK", temperature); csd_night_light_set_active (self, TRUE); csd_night_light_set_temperature (self, temperature); return; - case NIGHT_LIGHT_SCHEDULE_AUTO: - /* calculate the position of the sun */ - update_cached_sunrise_sunset (self); - if (self->cached_sunrise > 0.f && self->cached_sunset > 0.f) { - schedule_to = self->cached_sunrise; - schedule_from = self->cached_sunset; - } + case NIGHT_MODE_SCHEDULE_AUTO: + /* sun was updated in night_mode_recheck(self) */ + schedule_to = self->cached_sunrise; + schedule_from = self->cached_sunset; break; default: break; } - /* fall back to manual settings */ + /* fall back to manual settings in case of no calculation */ if (schedule_to <= 0.f || schedule_from <= 0.f) { schedule_from = g_settings_get_double (self->settings, "night-light-schedule-from"); @@ -310,40 +448,10 @@ night_light_recheck (CsdNightLight *self) g_debug ("fractional day = %.3f, limits = %.3f->%.3f", frac_day, schedule_from, schedule_to); - /* disabled until tomorrow */ + /* disabled until tomorrow - is updated in night_mode_recheck(self) */ if (self->disabled_until_tmw) { - GTimeSpan time_span; - gboolean reset = FALSE; - - time_span = g_date_time_difference (dt_now, self->disabled_until_tmw_dt); - - /* Reset if disabled until tomorrow is more than 24h ago. */ - if (time_span > (GTimeSpan) 24 * 60 * 60 * 1000000) { - g_debug ("night light disabled until tomorrow is older than 24h, resetting disabled until tomorrow"); - reset = TRUE; - } else if (time_span > 0) { - /* Or if a sunrise lies between the time it was disabled and now. */ - gdouble frac_disabled; - frac_disabled = csd_night_light_frac_day_from_dt (self->disabled_until_tmw_dt); - if (frac_disabled != frac_day && - csd_night_light_frac_day_is_between (schedule_to, - frac_disabled, - frac_day)) { - g_debug ("night light sun rise happened, resetting disabled until tomorrow"); - reset = TRUE; - } - } - - if (reset) { - self->disabled_until_tmw = FALSE; - g_clear_pointer(&self->disabled_until_tmw_dt, g_date_time_unref); - g_object_notify (G_OBJECT (self), "disabled-until-tmw"); - } else { - g_debug ("night light still day-disabled, resetting"); - csd_night_light_set_temperature (self, - CSD_COLOR_TEMPERATURE_DEFAULT); - return; - } + csd_night_light_set_active(self, FALSE); + return; } /* lower smearing period to be smaller than the time between start/stop */ @@ -351,10 +459,10 @@ night_light_recheck (CsdNightLight *self) MIN ( ABS (schedule_to - schedule_from), 24 - ABS (schedule_to - schedule_from))); - if (!csd_night_light_frac_day_is_between (frac_day, - schedule_from - smear, - schedule_to)) { - g_debug ("not time for night-light"); + if (!csd_night_mode_frac_day_is_between (frac_day, + schedule_from - smear, + schedule_to)) { + g_debug ("not time for night light"); csd_night_light_set_active (self, FALSE); return; } @@ -373,15 +481,15 @@ night_light_recheck (CsdNightLight *self) if (smear < 0.01) { /* Don't try to smear for extremely short or zero periods */ temp_smeared = temperature; - } else if (csd_night_light_frac_day_is_between (frac_day, - schedule_from - smear, - schedule_from)) { + } else if (csd_night_mode_frac_day_is_between (frac_day, + schedule_from - smear, + schedule_from)) { gdouble factor = 1.f - ((frac_day - (schedule_from - smear)) / smear); temp_smeared = linear_interpolate (CSD_COLOR_TEMPERATURE_DEFAULT, temperature, factor); - } else if (csd_night_light_frac_day_is_between (frac_day, - schedule_to - smear, - schedule_to)) { + } else if (csd_night_mode_frac_day_is_between (frac_day, + schedule_to - smear, + schedule_to)) { gdouble factor = (frac_day - (schedule_to - smear)) / smear; temp_smeared = linear_interpolate (CSD_COLOR_TEMPERATURE_DEFAULT, temperature, factor); @@ -396,12 +504,12 @@ night_light_recheck (CsdNightLight *self) /* called when the time may have changed */ static gboolean -night_light_recheck_cb (gpointer user_data) +night_mode_recheck_cb (gpointer user_data) { - CsdNightLight *self = CSD_NIGHT_LIGHT (user_data); + CsdNightMode *self = CSD_NIGHT_LIGHT (user_data); /* recheck parameters, then reschedule a new timeout */ - night_light_recheck (self); + night_mode_recheck (self); poll_timeout_destroy (self); poll_timeout_create (self); @@ -410,7 +518,7 @@ night_light_recheck_cb (gpointer user_data) } static void -poll_timeout_create (CsdNightLight *self) +poll_timeout_create (CsdNightMode *self) { g_autoptr(GDateTime) dt_now = NULL; g_autoptr(GDateTime) dt_expiry = NULL; @@ -430,7 +538,7 @@ poll_timeout_create (CsdNightLight *self) } static void -poll_timeout_destroy (CsdNightLight *self) +poll_timeout_destroy (CsdNightMode *self) { if (self->source == NULL) @@ -444,13 +552,13 @@ poll_timeout_destroy (CsdNightLight *self) static void settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data) { - CsdNightLight *self = CSD_NIGHT_LIGHT (user_data); + CsdNightMode *self = CSD_NIGHT_LIGHT (user_data); g_debug ("settings changed"); night_light_recheck (self); } static void -update_location_from_timezone (CsdNightLight *self) +update_location_from_timezone (CsdNightMode *self) { GTimeZone *tz = g_time_zone_new_local (); const gchar *id = g_time_zone_get_identifier (tz); @@ -473,7 +581,7 @@ update_location_from_timezone (CsdNightLight *self) } void -csd_night_light_set_disabled_until_tmw (CsdNightLight *self, gboolean value) +csd_night_light_set_disabled_until_tmw (CsdNightMode *self, gboolean value) { g_autoptr(GDateTime) dt = csd_night_light_get_date_time_now (self); @@ -489,60 +597,77 @@ csd_night_light_set_disabled_until_tmw (CsdNightLight *self, gboolean value) } gboolean -csd_night_light_get_disabled_until_tmw (CsdNightLight *self) +csd_night_light_get_disabled_until_tmw (CsdNightMode *self) { return self->disabled_until_tmw; } void -csd_night_light_set_forced (CsdNightLight *self, gboolean value) +csd_night_light_set_forced (CsdNightMode *self, gboolean value) { - if (self->forced == value) + if (self->light_forced == value) return; self->forced = value; - g_object_notify (G_OBJECT (self), "forced"); + g_object_notify (G_OBJECT (self), "light-forced"); /* A simple recheck might not reset the temperature if * night light is currently disabled. */ - if (!self->forced && !self->cached_active) + if (!self->light_forced && !self->cached_active) csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); night_light_recheck (self); } +void +csd_night_theme_set_forced (CsdNightMode *self, gboolean value) +{ + if (self->forced == value) + return; + + self->forced = value; + g_object_notify (G_OBJECT (self), "light-forced"); + + /* A simple recheck might not reset the temperature if + * night light is currently disabled. */ + if (!self->forced && !self->cached_active) + csd_night_theme_switch_on (self); + + night_mode_recheck (self); +} + gboolean -csd_night_light_get_forced (CsdNightLight *self) +csd_night_light_get_forced (CsdNightMode *self) { return self->forced; } gboolean -csd_night_light_get_active (CsdNightLight *self) +csd_night_light_get_active (CsdNightMode *self) { return self->cached_active; } gdouble -csd_night_light_get_sunrise (CsdNightLight *self) +csd_night_light_get_sunrise (CsdNightMode *self) { return self->cached_sunrise; } gdouble -csd_night_light_get_sunset (CsdNightLight *self) +csd_night_light_get_sunset (CsdNightMode *self) { return self->cached_sunset; } gdouble -csd_night_light_get_temperature (CsdNightLight *self) +csd_night_light_get_temperature (CsdNightMode *self) { return self->cached_temperature; } gboolean -csd_night_light_start (CsdNightLight *self, GError **error) +csd_night_light_start (CsdNightMode *self, GError **error) { night_light_recheck (self); poll_timeout_create (self); @@ -559,7 +684,7 @@ csd_night_light_start (CsdNightLight *self, GError **error) static void csd_night_light_finalize (GObject *object) { - CsdNightLight *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_LIGHT (object); poll_timeout_destroy (self); poll_smooth_destroy (self); @@ -582,7 +707,7 @@ csd_night_light_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - CsdNightLight *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_LIGHT (object); switch (prop_id) { case PROP_SUNRISE: @@ -606,16 +731,19 @@ csd_night_light_set_property (GObject *object, } static void -csd_night_light_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +csd_night_mode_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - CsdNightLight *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_LIGHT (object); switch (prop_id) { - case PROP_ACTIVE: - g_value_set_boolean (value, self->cached_active); + case PROP_LIGHT_ACTIVE: + g_value_set_boolean (value, self->cached_light_active); + break; + case PROP_THEME_ACTIVE: + g_value_set_boolean (value, self->cached_theme_active); break; case PROP_SUNRISE: g_value_set_double (value, self->cached_sunrise); @@ -629,7 +757,10 @@ csd_night_light_get_property (GObject *object, case PROP_DISABLED_UNTIL_TMW: g_value_set_boolean (value, csd_night_light_get_disabled_until_tmw (self)); break; - case PROP_FORCED: + case PROP_LIGHT_FORCED: + g_value_set_boolean (value, csd_night_light_get_forced (self)); + break; + case PROP_THEME_FORCED: g_value_set_boolean (value, csd_night_light_get_forced (self)); break; default: @@ -638,21 +769,29 @@ csd_night_light_get_property (GObject *object, } static void -csd_night_light_class_init (CsdNightLightClass *klass) +csd_night_mode_class_init (CsdNightModeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->finalize = csd_night_light_finalize; + object_class->finalize = csd_night_mode_finalize; - object_class->set_property = csd_night_light_set_property; - object_class->get_property = csd_night_light_get_property; + object_class->set_property = csd_night_mode_set_property; + object_class->get_property = csd_night_mode_get_property; g_object_class_install_property (object_class, - PROP_ACTIVE, - g_param_spec_boolean ("active", - "Active", + PROP_LIGHT_ACTIVE, + g_param_spec_boolean ("light-active", + "Light active", "If night light functionality is active right now", FALSE, G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_THEME_ACTIVE, + g_param_spec_boolean ("theme-active", + "Theme active", + "If night theme is active right now", + FALSE, + G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_SUNRISE, @@ -693,27 +832,38 @@ csd_night_light_class_init (CsdNightLightClass *klass) G_PARAM_READWRITE)); g_object_class_install_property (object_class, - PROP_FORCED, - g_param_spec_boolean ("forced", - "Forced", + PROP_LIGHT_FORCED, + g_param_spec_boolean ("light-forced", + "Light forced", "Whether night light should be forced on, useful for previewing", FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_THEME_FORCED, + g_param_spec_boolean ("theme-forced", + "Theme forced", + "Whether night theme should be forced on, useful for previewing", + FALSE, + G_PARAM_READWRITE)); } static void -csd_night_light_init (CsdNightLight *self) +csd_night_mode_init (CsdNightMode *self) { self->smooth_enabled = TRUE; self->cached_sunrise = -1.f; self->cached_sunset = -1.f; self->cached_temperature = CSD_COLOR_TEMPERATURE_DEFAULT; self->settings = g_settings_new ("org.cinnamon.settings-daemon.plugins.color"); + self->theme_settings = g_settings_new ("org.cinnamon.desktop.interface"); + self->x_theme_settings = g_settings_new ("org.x.apps.portal"); + self->cinnamon_theme_settings = g_settings_new ("org.cinnamon.theme"); } -CsdNightLight * -csd_night_light_new (void) +CsdNightMode * +csd_night_mode_new (void) { - return g_object_new (CSD_TYPE_NIGHT_LIGHT, NULL); + return g_object_new (CSD_TYPE_NIGHT_MODE, NULL); } diff --git a/plugins/color/csd-night-light-common.c b/plugins/color/csd-night-mode-common.c similarity index 92% rename from plugins/color/csd-night-light-common.c rename to plugins/color/csd-night-mode-common.c index 6710e5c0..af8051ac 100644 --- a/plugins/color/csd-night-light-common.c +++ b/plugins/color/csd-night-mode-common.c @@ -22,7 +22,7 @@ #include #include -#include "csd-night-light-common.h" +#include "csd-night-mode-common.h" static gdouble deg2rad (gdouble degrees) @@ -47,9 +47,9 @@ rad2deg (gdouble radians) * a sunrise at all. */ gboolean -csd_night_light_get_sunrise_sunset (GDateTime *dt, - gdouble pos_lat, gdouble pos_long, - gdouble *sunrise, gdouble *sunset) +csd_night_mode_get_sunrise_sunset (GDateTime *dt, + gdouble pos_lat, gdouble pos_long, + gdouble *sunrise, gdouble *sunset) { g_autoptr(GDateTime) dt_zero = g_date_time_new_utc (1900, 1, 1, 0, 0, 0); GTimeSpan ts = g_date_time_difference (dt, dt_zero); @@ -107,7 +107,7 @@ csd_night_light_get_sunrise_sunset (GDateTime *dt, } gdouble -csd_night_light_frac_day_from_dt (GDateTime *dt) +csd_night_mode_frac_day_from_dt (GDateTime *dt) { return g_date_time_get_hour (dt) + (gdouble) g_date_time_get_minute (dt) / 60.f + @@ -115,16 +115,16 @@ csd_night_light_frac_day_from_dt (GDateTime *dt) } gchar * -csd_night_light_time_string_from_frac (gdouble fraction) +csd_night_mode_time_string_from_frac (gdouble fraction) { g_autoptr(GDateTime) dt = g_date_time_new_local (2000, 1, 1, (int) trunc (fraction), (int) ((fraction - trunc(fraction)) * 60.0f), 0); return g_date_time_format (dt, "%H:%M"); } gboolean -csd_night_light_frac_day_is_between (gdouble value, - gdouble start, - gdouble end) +csd_night_mode_frac_day_is_between (gdouble value, + gdouble start, + gdouble end) { /* wrap end to the next day if it is before start, * considering equal values as a full 24h period diff --git a/plugins/color/csd-night-light-common.h b/plugins/color/csd-night-mode-common.h similarity index 78% rename from plugins/color/csd-night-light-common.h rename to plugins/color/csd-night-mode-common.h index f095e8d9..b1d916cc 100644 --- a/plugins/color/csd-night-light-common.h +++ b/plugins/color/csd-night-mode-common.h @@ -17,24 +17,24 @@ * */ -#ifndef __CSD_NIGHT_LIGHT_COMMON_H -#define __CSD_NIGHT_LIGHT_COMMON_H +#ifndef __CSD_NIGHT_MODE_COMMON_H +#define __CSD_NIGHT_MODE_COMMON_H #include G_BEGIN_DECLS -gboolean csd_night_light_get_sunrise_sunset (GDateTime *dt, +gboolean csd_night_mode_get_sunrise_sunset (GDateTime *dt, gdouble pos_lat, gdouble pos_long, gdouble *sunrise, gdouble *sunset); -gdouble csd_night_light_frac_day_from_dt (GDateTime *dt); -gchar * csd_night_light_time_string_from_frac (gdouble fraction); -gboolean csd_night_light_frac_day_is_between (gdouble value, +gdouble csd_night_mode_frac_day_from_dt (GDateTime *dt); +gchar * csd_night_mode_time_string_from_frac (gdouble fraction); +gboolean csd_night_mode_frac_day_is_between (gdouble value, gdouble start, gdouble end); G_END_DECLS -#endif /* __CSD_NIGHT_LIGHT_COMMON_H */ +#endif /* __CSD_NIGHT_MODE_COMMON_H */ diff --git a/plugins/color/csd-night-light.h b/plugins/color/csd-night-mode.h similarity index 51% rename from plugins/color/csd-night-light.h rename to plugins/color/csd-night-mode.h index 0786d1aa..6717007b 100644 --- a/plugins/color/csd-night-light.h +++ b/plugins/color/csd-night-mode.h @@ -18,37 +18,40 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __CSD_NIGHT_LIGHT_H__ -#define __CSD_NIGHT_LIGHT_H__ +#ifndef __CSD_NIGHT_MODE_H__ +#define __CSD_NIGHT_MODE_H__ #include G_BEGIN_DECLS -#define CSD_TYPE_NIGHT_LIGHT (csd_night_light_get_type ()) -G_DECLARE_FINAL_TYPE (CsdNightLight, csd_night_light, CSD, NIGHT_LIGHT, GObject) - -CsdNightLight *csd_night_light_new (void); -gboolean csd_night_light_start (CsdNightLight *self, - GError **error); - -gboolean csd_night_light_get_active (CsdNightLight *self); -gdouble csd_night_light_get_sunrise (CsdNightLight *self); -gdouble csd_night_light_get_sunset (CsdNightLight *self); -gdouble csd_night_light_get_temperature (CsdNightLight *self); - -gboolean csd_night_light_get_disabled_until_tmw (CsdNightLight *self); -void csd_night_light_set_disabled_until_tmw (CsdNightLight *self, - gboolean value); - -gboolean csd_night_light_get_forced (CsdNightLight *self); -void csd_night_light_set_forced (CsdNightLight *self, - gboolean value); - -void csd_night_light_set_date_time_now (CsdNightLight *self, - GDateTime *datetime); -void csd_night_light_set_smooth_enabled (CsdNightLight *self, - gboolean smooth_enabled); +#define CSD_TYPE_NIGHT_MODE (csd_night_mode_get_type ()) +G_DECLARE_FINAL_TYPE (CsdNightMode, csd_night_mode, CSD, NIGHT_MODE, GObject) + +CsdNightMode *csd_night_mode_new (void); +gboolean csd_night_mode_start (CsdNightMode *self, + GError **error); + +gboolean csd_night_mode_get_active (CsdNightMode *self); +gdouble csd_night_mode_get_sunrise (CsdNightMode *self); +gdouble csd_night_mode_get_sunset (CsdNightMode *self); +gdouble csd_night_light_get_temperature (CsdNightMode *self); + +gboolean csd_night_mode_get_disabled_until_tmw (CsdNightMode *self); +void csd_night_mode_set_disabled_until_tmw (CsdNightMode *self, + gboolean value); + +gboolean csd_night_light_get_forced (CsdNightMode *self); +void csd_night_light_set_forced (CsdNightMode *self, + gboolean value); +gboolean csd_night_theme_get_forced (CsdNightMode *self); +void csd_night_theme_set_forced (CsdNightMode *self, + gboolean value); + +void csd_night_mode_set_date_time_now (CsdNightMode *self, + GDateTime *datetime); +void csd_night_light_set_smooth_enabled (CsdNightMode *self, + gboolean smooth_enabled); G_END_DECLS diff --git a/plugins/color/meson.build b/plugins/color/meson.build index e421e526..7897d923 100644 --- a/plugins/color/meson.build +++ b/plugins/color/meson.build @@ -26,8 +26,8 @@ sources = files( 'csd-color-manager.c', 'csd-color-profiles.c', 'csd-color-state.c', - 'csd-night-light.c', - 'csd-night-light-common.c', + 'csd-night-mode-common.c', + 'csd-night-mode.c', 'main.c' ) @@ -74,7 +74,7 @@ sources = files( 'ccm-self-test.c', 'gnome-datetime-source.c', 'csd-night-light.c', - 'csd-night-light-common.c' + 'csd-night-common.c' ) # test_unit = 'ccm-self-test' From 4dc223a06e29592aa76d857cb832a7aae4d4da74 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Mon, 16 Mar 2026 17:53:50 +0100 Subject: [PATCH 02/20] first try --- ...ngs-daemon.plugins.color.gschema.xml.in.in | 20 ++-- .../{csd-night-light.c => csd-night-mode.c} | 96 ++++++++++++++++--- plugins/color/meson.build | 4 +- 3 files changed, 95 insertions(+), 25 deletions(-) rename plugins/color/{csd-night-light.c => csd-night-mode.c} (89%) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index bd45c699..1cf6c495 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -7,7 +7,7 @@ - + @@ -58,25 +58,25 @@ Mint-Y-Dark-Aqua The theme which should be activated at night - This theme will be switched on instead of the current theme as soon as the night light gets activated. + This theme will be switched on instead of the current theme as soon as the night mode gets activated. - - false - If the night theme is currently active - This is set to true when the cinnamon-settings-daemon activates the night theme (and set to false when it's deactivated again). + + Mint-Y-Dark-Aqua + The cinnamon-theme which should be activated at night + This theme will be switched on instead of the current cinnamon-theme as soon as the night mode gets activated. - Mint-Y-Aqua + This is set to the last detected day theme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. - - Mint-Y-Aqua + + This is set to the last detected day desktop theme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. - 'prefer-dark' + 'default' This is set to the last detected day xapp color-scheme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. diff --git a/plugins/color/csd-night-light.c b/plugins/color/csd-night-mode.c similarity index 89% rename from plugins/color/csd-night-light.c rename to plugins/color/csd-night-mode.c index 011cfc51..a001a40c 100644 --- a/plugins/color/csd-night-light.c +++ b/plugins/color/csd-night-mode.c @@ -72,6 +72,12 @@ enum { NIGHT_MODE_SCHEDULE_ALWAYS_ON = 2 }; +enum { + COLOR_SCHEME_DEFAULT = 0, + COLOR_SCHEME_DARK = 1, + COLOR_SCHEME_LIGHT = 2 +} + #define CSD_NIGHT_MODE_SCHEDULE_TIMEOUT 5 /* seconds */ #define CSD_NIGHT_MODE_POLL_TIMEOUT 60 /* seconds */ #define CSD_NIGHT_LIGHT_POLL_SMEAR 1 /* hours */ @@ -241,6 +247,70 @@ csd_night_light_set_temperature (CsdNightMode *self, gdouble temperature) poll_smooth_create (self, temperature); } +static void +night_theme_switch_on (CsdNightMode *self) +{ + gboolean is_active = TRUE; + /* get backups */ + gchar *backup_day_theme = g_settings_get_string (self->settings, "backup-day-theme"); + gchar *backup_day_cinnamon_theme = g_settings_get_string (self->settings, "backup-day-cinnamon-theme"); + /* check if there are backups */ + is_active = ( + backup_day_theme != NULL && + g_strcmp0 (backup_day_theme, "") != 0 && + backup_day_cinnamon_theme != NULL && + g_strcmp0 (backup_day_cinnamon_theme, "") != 0 + ); + /* free memory */ + g_free (backup_day_theme); + g_free (backup_day_cinnamon_theme); + + if (is_active) { + return; + } + /* copy values to the backups */ + g_settings_set_string (self->settings, "backup-day-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); + g_settings_set_string (self->settings, "backup-day-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); + g_settings_set_enum (self->setings, "backup-day-color-scheme", g_settings_get_enum (self->x_theme_settings, "color-scheme")); + /* activate the night themes */ + g_settings_set_string (self->theme_settings, "gtk-theme", g_settings_get_string (self->settings, "night-theme")); + g_settings_set_string (self->cinnamon_theme_settings, "name", g_settings_get_string (self->settings, "night-cinnamon-theme")); + g_settings_set_enum (self->x_theme_settings, "color-scheme", COLOR_SCHEME_DARK); +} + +static void +night_theme_switch_off (CsdNightMode *self) +{ + gboolean is_active = TRUE; + /* get backups */ + gchar *backup_day_theme = g_settings_get_string (self->settings, "backup-day-theme"); + gchar *backup_day_cinnamon_theme = g_settings_get_string (self->settings, "backup-day-cinnamon-theme"); + /* check if there are no backups */ + is_active = ( + backup_day_theme != NULL && + g_strcmp0 (backup_day_theme, "") != 0 && + backup_day_cinnamon_theme != NULL && + g_strcmp0 (backup_day_cinnamon_theme, "") != 0 && + ); + + if (!is_active) { + return; + } + /* save the current night theme */ + g_settings_set_string (self->settings, "night-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); + g_settings_set_string (self->settings, "night-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); + /* restore the backups */ + g_settings_set_string (self->theme_settings, "gtk-theme", g_settings_get_string (self->settings, "backup-day-theme")); + g_settings_set_string (self->cinnamon_theme_settings, "name", g_settings_get_string (self->settings, "backup-day-cinnamon-theme")); + g_settings_set_enum (self->x_theme_settings, "color-scheme", g_settings_get_enum (self->settings, "backup-day-color-scheme")); + /* clear the backups */ + g_settings_set_string (self->settings, "backup-day-theme", ""); + g_settings_set_string (self->settings, "backup-day-cinnamon-theme", ""); + /* free memory */ + g_free (backup_day_theme); + g_free (backup_day_cinnamon_theme); +} + static void csd_night_light_set_active (CsdNightMode *self, gboolean active) { @@ -264,9 +334,9 @@ csd_night_theme_set_active (CsdNightMode *self, gboolean active) /* switch off theme if not active & switch on else */ if (!active) - csd_night_theme_switch_off (self); + night_theme_switch_off (self); else - csd_night_theme_switch_on (self); + night_theme_switch_on (self); g_object_notify (G_OBJECT (self), "theme-active"); } @@ -332,7 +402,7 @@ night_theme_recheck (CsdNightMode *self) /* If forced (e.g. for preview), just switch on and return */ if (self->theme_forced) { - csd_night_theme_switch_on (self); + night_theme_switch_on (self); g_debug ("night theme forced on"); return; } @@ -608,7 +678,7 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) if (self->light_forced == value) return; - self->forced = value; + self->light_forced = value; g_object_notify (G_OBJECT (self), "light-forced"); /* A simple recheck might not reset the temperature if @@ -616,22 +686,22 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) if (!self->light_forced && !self->cached_active) csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); - night_light_recheck (self); + night_mode_recheck (self); } void csd_night_theme_set_forced (CsdNightMode *self, gboolean value) { - if (self->forced == value) + if (self->theme_forced == value) return; - self->forced = value; + self->theme_forced = value; g_object_notify (G_OBJECT (self), "light-forced"); /* A simple recheck might not reset the temperature if - * night light is currently disabled. */ - if (!self->forced && !self->cached_active) - csd_night_theme_switch_on (self); + * night theme is currently disabled. */ + if (!self->theme_forced && !self->cached_active) + night_theme_switch_on (self); night_mode_recheck (self); } @@ -649,13 +719,13 @@ csd_night_light_get_active (CsdNightMode *self) } gdouble -csd_night_light_get_sunrise (CsdNightMode *self) +csd_night_mode_get_sunrise (CsdNightMode *self) { return self->cached_sunrise; } gdouble -csd_night_light_get_sunset (CsdNightMode *self) +csd_night_mode_get_sunset (CsdNightMode *self) { return self->cached_sunset; } @@ -669,7 +739,7 @@ csd_night_light_get_temperature (CsdNightMode *self) gboolean csd_night_light_start (CsdNightMode *self, GError **error) { - night_light_recheck (self); + night_mode_recheck (self); poll_timeout_create (self); /* care about changes */ diff --git a/plugins/color/meson.build b/plugins/color/meson.build index 7897d923..619436f2 100644 --- a/plugins/color/meson.build +++ b/plugins/color/meson.build @@ -73,8 +73,8 @@ sources = files( 'ccm-edid.c', 'ccm-self-test.c', 'gnome-datetime-source.c', - 'csd-night-light.c', - 'csd-night-common.c' + 'csd-night-mode.c', + 'csd-night-mode-common.c' ) # test_unit = 'ccm-self-test' From bad32fb29fdde8c5dc660bb07e278f8de970f834 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Mon, 16 Mar 2026 17:54:49 +0100 Subject: [PATCH 03/20] removed one && --- plugins/color/csd-night-mode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index a001a40c..dd775e71 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -290,7 +290,7 @@ night_theme_switch_off (CsdNightMode *self) backup_day_theme != NULL && g_strcmp0 (backup_day_theme, "") != 0 && backup_day_cinnamon_theme != NULL && - g_strcmp0 (backup_day_cinnamon_theme, "") != 0 && + g_strcmp0 (backup_day_cinnamon_theme, "") != 0 ); if (!is_active) { From fd654017c4e623ce5f6d7377cd60e6c72fece649 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Mon, 16 Mar 2026 18:04:37 +0100 Subject: [PATCH 04/20] more name changes (light->mode /+theme) --- plugins/color/csd-color-manager.c | 6 ++++-- plugins/color/csd-night-mode.c | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/plugins/color/csd-color-manager.c b/plugins/color/csd-color-manager.c index 543de3cb..26ee4667 100644 --- a/plugins/color/csd-color-manager.c +++ b/plugins/color/csd-color-manager.c @@ -241,8 +241,10 @@ csd_color_manager_init (CsdColorManager *manager) /* night light (and theme) features */ manager->nmode = csd_night_mode_new (); - g_signal_connect (manager->nmode, "notify::active", - G_CALLBACK (on_active_notify), manager); + g_signal_connect (manager->nmode, "notify::light-active", + G_CALLBACK (on_active_nlight_notify), manager); + g_signal_connect (manager->nmode, "notify::theme-active", + G_CALLBACK (on_active_ntheme_notify), manager); g_signal_connect (manager->nmode, "notify::sunset", G_CALLBACK (on_sunset_notify), manager); g_signal_connect (manager->nmode, "notify::sunrise", diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index dd775e71..89667b26 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -44,7 +44,8 @@ struct _CsdNightMode { gdouble cached_sunrise; gdouble cached_sunset; gdouble cached_temperature; - gboolean cached_active; + gboolean cached_light_active; + gboolean cached_theme_active; gboolean smooth_enabled; GTimer *smooth_timer; guint smooth_id; @@ -683,7 +684,7 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) /* A simple recheck might not reset the temperature if * night light is currently disabled. */ - if (!self->light_forced && !self->cached_active) + if (!self->light_forced && !self->cached_light_active) csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); night_mode_recheck (self); @@ -698,9 +699,9 @@ csd_night_theme_set_forced (CsdNightMode *self, gboolean value) self->theme_forced = value; g_object_notify (G_OBJECT (self), "light-forced"); - /* A simple recheck might not reset the temperature if + /* A simple recheck might not switch off * night theme is currently disabled. */ - if (!self->theme_forced && !self->cached_active) + if (!self->theme_forced && !self->cached_theme_active) night_theme_switch_on (self); night_mode_recheck (self); @@ -715,7 +716,13 @@ csd_night_light_get_forced (CsdNightMode *self) gboolean csd_night_light_get_active (CsdNightMode *self) { - return self->cached_active; + return self->cached_light_active; +} + +gboolean +csd_night_theme_get_active (CsdNightMode *self) +{ + return self->cached_theme_active; } gdouble From 2d6f7335643c716b7f8ed107cd50487cfc9db3fc Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Mon, 16 Mar 2026 21:23:21 +0100 Subject: [PATCH 05/20] updated names of functions, and of test-ccm-functions --- plugins/color/ccm-self-test.c | 251 +++++++++++++++++++-------------- plugins/color/csd-night-mode.c | 49 ++++--- plugins/color/csd-night-mode.h | 4 +- 3 files changed, 180 insertions(+), 124 deletions(-) diff --git a/plugins/color/ccm-self-test.c b/plugins/color/ccm-self-test.c index 1b00697b..c0f2f9a4 100644 --- a/plugins/color/ccm-self-test.c +++ b/plugins/color/ccm-self-test.c @@ -27,13 +27,13 @@ #include "ccm-edid.h" #include "csd-color-state.h" -#include "csd-night-light.h" -#include "csd-night-light-common.h" +#include "csd-night-mode.h" +#include "csd-night-mode-common.h" GMainLoop *mainloop; static void -on_notify (CsdNightLight *nlight, +on_notify (CsdNightMode *nmode, GParamSpec *pspec, gpointer user_data) { @@ -50,58 +50,67 @@ quit_mainloop (gpointer user_data) } static void -ccm_test_night_light (void) +ccm_test_night_mode (void) { gboolean ret; - guint active_cnt = 0; + guint active_light_cnt = 0; + guint active_theme_cnt = 0; guint disabled_until_tmw_cnt = 0; guint sunrise_cnt = 0; guint sunset_cnt = 0; guint temperature_cnt = 0; g_autoptr(GDateTime) datetime_override = NULL; g_autoptr(GError) error = NULL; - g_autoptr(CsdNightLight) nlight = NULL; + g_autoptr(CsdNightMode) nmode = NULL; g_autoptr(GSettings) settings = NULL; - nlight = csd_night_light_new (); - g_assert (CSD_IS_NIGHT_LIGHT (nlight)); - g_signal_connect (nlight, "notify::active", - G_CALLBACK (on_notify), &active_cnt); - g_signal_connect (nlight, "notify::sunset", + nmode = csd_night_mode_new (); + g_assert (CSD_IS_NIGHT_LIGHT (nmode)); + g_signal_connect (nmode, "notify::light-active", + G_CALLBACK (on_notify), &active_light_cnt); + g_signal_connect (nmode, "notify::theme-active", + G_CALLBACK (on_notify), &active_theme_cnt); + g_signal_connect (nmode, "notify::sunset", G_CALLBACK (on_notify), &sunset_cnt); - g_signal_connect (nlight, "notify::sunrise", + g_signal_connect (nmode, "notify::sunrise", G_CALLBACK (on_notify), &sunrise_cnt); - g_signal_connect (nlight, "notify::temperature", + g_signal_connect (nmode, "notify::temperature", G_CALLBACK (on_notify), &temperature_cnt); - g_signal_connect (nlight, "notify::disabled-until-tmw", + g_signal_connect (nmode, "notify::disabled-until-tmw", G_CALLBACK (on_notify), &disabled_until_tmw_cnt); /* hardcode a specific date and time */ datetime_override = g_date_time_new_utc (2017, 2, 8, 20, 0, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); + csd_night_mode_set_date_time_now (nmode, datetime_override); /* do not smooth the transition */ - csd_night_light_set_smooth_enabled (nlight, FALSE); + csd_night_light_set_smooth_enabled (nmode, FALSE); /* switch off */ settings = g_settings_new ("org.gnome.settings-daemon.plugins.color"); - g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE); + /* + to fix: NIGHT_MODE_SCHEDULE_AUTO not defined here?! + */ + // g_settings_set_enum (settings, "night-light-schedule-mode", NIGHT_MODE_SCHEDULE_AUTO); g_settings_set_boolean (settings, "night-light-enabled", FALSE); + g_settings_set_boolean (settings, "night-theme-enabled", FALSE); g_settings_set_uint (settings, "night-light-temperature", 4000); /* check default values */ - g_assert (!csd_night_light_get_active (nlight)); - g_assert_cmpint ((gint) csd_night_light_get_sunrise (nlight), ==, -1); - g_assert_cmpint ((gint) csd_night_light_get_sunset (nlight), ==, -1); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); - g_assert (!csd_night_light_get_disabled_until_tmw (nlight)); + g_assert (!csd_night_light_get_active (nmode)); + g_assert_cmpint ((gint) csd_night_mode_get_sunrise (nmode), ==, -1); + g_assert_cmpint ((gint) csd_night_mode_get_sunset (nmode), ==, -1); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (!csd_night_mode_get_disabled_until_tmw (nmode)); /* start module, disabled */ - ret = csd_night_light_start (nlight, &error); + ret = csd_night_mode_start (nmode, &error); g_assert_no_error (error); g_assert (ret); - g_assert (!csd_night_light_get_active (nlight)); - g_assert_cmpint (active_cnt, ==, 0); + g_assert (!csd_night_light_get_active (nmode)); + g_assert (!csd_night_theme_get_active (nmode)); + g_assert_cmpint (active_light_cnt, ==, 0); + g_assert_cmpint (active_theme_cnt, ==, 0); g_assert_cmpint (sunset_cnt, ==, 0); g_assert_cmpint (sunrise_cnt, ==, 0); g_assert_cmpint (temperature_cnt, ==, 0); @@ -110,56 +119,73 @@ ccm_test_night_light (void) /* enable automatic mode */ g_settings_set_value (settings, "night-light-last-coordinates", g_variant_new ("(dd)", 51.5, -0.1278)); - g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE); + /* + to fix: NIGHT_MODE_SCHEDULE_AUTO not defined here?! + */ + //g_settings_set_boolean (settings, "night-light-schedule-mode", NIGHT_MODE_SCHEDULE_AUTO); g_settings_set_boolean (settings, "night-light-enabled", TRUE); - g_assert (csd_night_light_get_active (nlight)); - g_assert_cmpint (active_cnt, ==, 1); + g_settings_set_boolean (settings, "night-theme-enabled", TRUE); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert_cmpint (active_light_cnt, ==, 1); + g_assert_cmpint (active_theme_cnt, ==, 1); g_assert_cmpint (sunset_cnt, ==, 1); g_assert_cmpint (sunrise_cnt, ==, 1); g_assert_cmpint (temperature_cnt, ==, 1); g_assert_cmpint (disabled_until_tmw_cnt, ==, 0); - g_assert_cmpint ((gint) csd_night_light_get_sunrise (nlight), ==, 7); - g_assert_cmpint ((gint) csd_night_light_get_sunset (nlight), ==, 17); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, 4000); - g_assert (!csd_night_light_get_disabled_until_tmw (nlight)); + g_assert_cmpint ((gint) csd_night_mode_get_sunrise (nmode), ==, 7); + g_assert_cmpint ((gint) csd_night_mode_get_sunset (nmode), ==, 17); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, 4000); + g_assert (!csd_night_mode_get_disabled_until_tmw (nmode)); /* disable for one day */ - csd_night_light_set_disabled_until_tmw (nlight, TRUE); - csd_night_light_set_disabled_until_tmw (nlight, TRUE); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); - g_assert (csd_night_light_get_active (nlight)); - g_assert (csd_night_light_get_disabled_until_tmw (nlight)); + csd_night_mode_set_disabled_until_tmw (nmode, TRUE); + csd_night_mode_set_disabled_until_tmw (nmode, TRUE); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert (csd_night_mode_get_disabled_until_tmw (nmode)); g_assert_cmpint (temperature_cnt, ==, 2); g_assert_cmpint (disabled_until_tmw_cnt, ==, 1); /* change our mind */ - csd_night_light_set_disabled_until_tmw (nlight, FALSE); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, 4000); - g_assert (csd_night_light_get_active (nlight)); - g_assert (!csd_night_light_get_disabled_until_tmw (nlight)); - g_assert_cmpint (active_cnt, ==, 1); + csd_night_mode_set_disabled_until_tmw (nmode, FALSE); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, 4000); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert (!csd_night_mode_get_disabled_until_tmw (nmode)); + g_assert_cmpint (active_light_cnt, ==, 1); + g_assert_cmpint (active_theme_cnt, ==, 1); g_assert_cmpint (temperature_cnt, ==, 3); g_assert_cmpint (disabled_until_tmw_cnt, ==, 2); /* enabled manual mode (night shift) */ g_settings_set_double (settings, "night-light-schedule-from", 4.0); g_settings_set_double (settings, "night-light-schedule-to", 16.f); - g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE); - g_assert_cmpint (active_cnt, ==, 2); + /* + to fix: NIGHT_MODE_SCHEDULE_MANUAL not defined here?! + */ + //g_settings_set_boolean (settings, "night-light-schedule-mode", NIGHT_MODE_SCHEDULE_MANUAL); + g_assert_cmpint (active_light_cnt, ==, 2); // why 2? + g_assert_cmpint (active_theme_cnt, ==, 2); // why 2? (copy-paste) g_assert_cmpint (sunset_cnt, ==, 1); g_assert_cmpint (sunrise_cnt, ==, 1); g_assert_cmpint (temperature_cnt, ==, 4); g_assert_cmpint (disabled_until_tmw_cnt, ==, 2); - g_assert (!csd_night_light_get_active (nlight)); - g_assert_cmpint ((gint) csd_night_light_get_sunrise (nlight), ==, 7); - g_assert_cmpint ((gint) csd_night_light_get_sunset (nlight), ==, 17); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); - g_assert (!csd_night_light_get_disabled_until_tmw (nlight)); + g_assert (!csd_night_light_get_active (nmode)); + g_assert (!csd_night_theme_get_active (nmode)); + g_assert_cmpint ((gint) csd_night_mode_get_sunrise (nmode), ==, 7); + g_assert_cmpint ((gint) csd_night_mode_get_sunset (nmode), ==, 17); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (!csd_night_mode_get_disabled_until_tmw (nmode)); /* disable, with no changes */ g_settings_set_boolean (settings, "night-light-enabled", FALSE); - g_assert (!csd_night_light_get_active (nlight)); - g_assert_cmpint (active_cnt, ==, 2); + g_settings_set_boolean (settings, "night-theme-enabled", FALSE); + g_assert (!csd_night_light_get_active (nmode)); + g_assert (!csd_night_theme_get_active (nmode)); + g_assert_cmpint (active_light_cnt, ==, 2); // why 2? + g_assert_cmpint (active_theme_cnt, ==, 2); // why 2? (copy-paste) g_assert_cmpint (sunset_cnt, ==, 1); g_assert_cmpint (sunrise_cnt, ==, 1); g_assert_cmpint (temperature_cnt, ==, 4); @@ -167,75 +193,90 @@ ccm_test_night_light (void) /* Finally, check that cancelling a smooth transition works */ - csd_night_light_set_smooth_enabled (nlight, TRUE); + csd_night_light_set_smooth_enabled (nmode, TRUE); /* Enable night light and automatic scheduling */ - g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE); + /* + to fix: NIGHT_MODE_SCHEDULE_AUTO not defined here?! + */ + //g_settings_set_boolean (settings, "night-light-schedule-mode", NIGHT_MODE_SCHEDULE_AUTO); g_settings_set_boolean (settings, "night-light-enabled", TRUE); + g_settings_set_boolean (settings, "night-theme-enabled", TRUE); /* It should be active again, and a smooth transition is being done, * so the color temperature is still the default at this point. */ - g_assert (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); /* Turn off immediately, before the first timeout event is fired. */ - g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE); + /* + to fix: NIGHT_MODE_SCHEDULE_MANUAL not defined here?! + */ + //g_settings_set_boolean (settings, "night-light-schedule-mode", NIGHT_MODE_SCHEDULE_MANUAL); g_settings_set_boolean (settings, "night-light-enabled", FALSE); - g_assert (!csd_night_light_get_active (nlight)); + g_settings_set_boolean (settings, "night-theme-enabled", FALSE); + g_assert (!csd_night_light_get_active (nmode)); + g_assert (!csd_night_theme_get_active (nmode)); /* Now, sleep for a bit (the smooth transition time is 5 seconds) */ g_timeout_add (5000, quit_mainloop, NULL); g_main_loop_run (mainloop); /* Ensure that the color temperature is still the default one.*/ - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); /* Check that disabled until tomorrow resets again correctly. */ g_settings_set_double (settings, "night-light-schedule-from", 17.0); g_settings_set_double (settings, "night-light-schedule-to", 7.f); g_settings_set_boolean (settings, "night-light-enabled", TRUE); - csd_night_light_set_disabled_until_tmw (nlight, TRUE); + g_settings_set_boolean (settings, "night-theme-enabled", TRUE); + csd_night_mode_set_disabled_until_tmw (nmode, TRUE); /* Move time past midnight */ g_clear_pointer (&datetime_override, g_date_time_unref); datetime_override = g_date_time_new_utc (2017, 2, 9, 1, 0, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_true (csd_night_light_get_disabled_until_tmw (nlight)); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_true (csd_night_mode_get_disabled_until_tmw (nmode)); /* Move past sunrise */ g_clear_pointer (&datetime_override, g_date_time_unref); datetime_override = g_date_time_new_utc (2017, 2, 9, 8, 0, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_false (csd_night_light_get_disabled_until_tmw (nlight)); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_false (csd_night_mode_get_disabled_until_tmw (nmode)); - csd_night_light_set_disabled_until_tmw (nlight, TRUE); + csd_night_mode_set_disabled_until_tmw (nmode, TRUE); /* Move into night more than 24h in the future */ g_clear_pointer (&datetime_override, g_date_time_unref); datetime_override = g_date_time_new_utc (2017, 2, 10, 20, 0, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_false (csd_night_light_get_disabled_until_tmw (nlight)); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_false (csd_night_mode_get_disabled_until_tmw (nmode)); /* Check that we are always in night mode if from/to are equal. */ - csd_night_light_set_smooth_enabled (nlight, FALSE); + csd_night_light_set_smooth_enabled (nmode, FALSE); g_settings_set_double (settings, "night-light-schedule-from", 6.0); g_settings_set_double (settings, "night-light-schedule-to", 6.0); g_settings_set_boolean (settings, "night-light-enabled", TRUE); + g_settings_set_boolean (settings, "night-theme-enabled", TRUE); datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, 4000); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, 4000); datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 0, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, 4000); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, 4000); datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 10, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, 4000); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert (csd_night_light_get_active (nmode)); + g_assert (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, 4000); /* Check that the smearing time is lowered correctly when the times are close. */ @@ -244,31 +285,35 @@ ccm_test_night_light (void) /* Not enabled 10 minutes before sunset */ datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_false (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_false (csd_night_light_get_active (nmode)); + g_assert_false (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); /* Not enabled >10 minutes after sunrise */ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 20, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_false (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), ==, CSD_COLOR_TEMPERATURE_DEFAULT); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_false (csd_night_light_get_active (nmode)); + g_assert_false (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), ==, CSD_COLOR_TEMPERATURE_DEFAULT); /* ~50% smeared 3 min before sunrise (sunrise at 6 past) */ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_true (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), <=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); - g_assert_cmpint (csd_night_light_get_temperature (nlight), >=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_true (csd_night_light_get_active (nmode)); + g_assert_true (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), <=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); + g_assert_cmpint (csd_night_light_get_temperature (nmode), >=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); /* ~50% smeared 3 min before sunset (sunset at 6 past) */ g_settings_set_double (settings, "night-light-schedule-from", 6.1); g_settings_set_double (settings, "night-light-schedule-to", 6.0); datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0); - csd_night_light_set_date_time_now (nlight, datetime_override); - g_assert_true (csd_night_light_get_active (nlight)); - g_assert_cmpint (csd_night_light_get_temperature (nlight), <=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); - g_assert_cmpint (csd_night_light_get_temperature (nlight), >=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); + csd_night_mode_set_date_time_now (nmode, datetime_override); + g_assert_true (csd_night_light_get_active (nmode)); + g_assert_true (csd_night_theme_get_active (nmode)); + g_assert_cmpint (csd_night_light_get_temperature (nmode), <=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); + g_assert_cmpint (csd_night_light_get_temperature (nmode), >=, (CSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); } static const gboolean @@ -351,7 +396,7 @@ ccm_test_sunset_sunrise (void) g_autoptr(GDateTime) dt = g_date_time_new_utc (2007, 2, 1, 0, 0, 0); /* get for London, today */ - csd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); + csd_night_mode_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1); g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1); g_assert_cmpfloat (sunset, <, sunset_actual + 0.1); @@ -372,7 +417,7 @@ ccm_test_sunset_sunrise_fractional_timezone (void) dt = g_date_time_new (tz, 2007, 2, 1, 0, 0, 0); /* get for our made up timezone, today */ - csd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); + csd_night_mode_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1); g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1); g_assert_cmpfloat (sunset, <, sunset_actual + 0.1); @@ -387,26 +432,26 @@ ccm_test_frac_day (void) gdouble fd_actual = 12.99; /* test for 12:59:59 */ - fd = csd_night_light_frac_day_from_dt (dt); + fd = csd_night_mode_frac_day_from_dt (dt); g_assert_cmpfloat (fd, >, fd_actual - 0.01); g_assert_cmpfloat (fd, <, fd_actual + 0.01); /* test same day */ - g_assert_true (csd_night_light_frac_day_is_between (12, 6, 20)); - g_assert_false (csd_night_light_frac_day_is_between (5, 6, 20)); - g_assert_true (csd_night_light_frac_day_is_between (12, 0, 24)); - g_assert_true (csd_night_light_frac_day_is_between (12, -1, 25)); + g_assert_true (csd_night_mode_frac_day_is_between (12, 6, 20)); + g_assert_false (csd_night_mode_frac_day_is_between (5, 6, 20)); + g_assert_true (csd_night_mode_frac_day_is_between (12, 0, 24)); + g_assert_true (csd_night_mode_frac_day_is_between (12, -1, 25)); /* test rollover to next day */ - g_assert_true (csd_night_light_frac_day_is_between (23, 20, 6)); - g_assert_false (csd_night_light_frac_day_is_between (12, 20, 6)); + g_assert_true (csd_night_mode_frac_day_is_between (23, 20, 6)); + g_assert_false (csd_night_mode_frac_day_is_between (12, 20, 6)); /* test rollover to the previous day */ - g_assert_true (csd_night_light_frac_day_is_between (5, 16, 8)); + g_assert_true (csd_night_mode_frac_day_is_between (5, 16, 8)); /* test equality */ - g_assert_true (csd_night_light_frac_day_is_between (12, 0.5, 24.5)); - g_assert_true (csd_night_light_frac_day_is_between (0.5, 0.5, 0.5)); + g_assert_true (csd_night_mode_frac_day_is_between (12, 0.5, 24.5)); + g_assert_true (csd_night_mode_frac_day_is_between (0.5, 0.5, 0.5)); } int @@ -422,7 +467,7 @@ main (int argc, char **argv) g_test_add_func ("/color/sunset-sunrise", ccm_test_sunset_sunrise); g_test_add_func ("/color/sunset-sunrise/fractional-timezone", ccm_test_sunset_sunrise_fractional_timezone); g_test_add_func ("/color/fractional-day", ccm_test_frac_day); - g_test_add_func ("/color/night-light", ccm_test_night_light); + g_test_add_func ("/color/night-mode", ccm_test_night_mode); return g_test_run (); } diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 89667b26..c11bf586 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -90,6 +90,8 @@ enum { static void poll_timeout_destroy (CsdNightMode *self); static void poll_timeout_create (CsdNightMode *self); static void night_mode_recheck (CsdNightMode *self); +static void night_light_recheck (CsdNightMode *self); +static void night_theme_recheck (CsdNightMode *self); G_DEFINE_TYPE (CsdNightMode, csd_night_mode, G_TYPE_OBJECT); @@ -272,7 +274,7 @@ night_theme_switch_on (CsdNightMode *self) /* copy values to the backups */ g_settings_set_string (self->settings, "backup-day-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); g_settings_set_string (self->settings, "backup-day-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); - g_settings_set_enum (self->setings, "backup-day-color-scheme", g_settings_get_enum (self->x_theme_settings, "color-scheme")); + g_settings_set_enum (self->settings, "backup-day-color-scheme", g_settings_get_enum (self->x_theme_settings, "color-scheme")); /* activate the night themes */ g_settings_set_string (self->theme_settings, "gtk-theme", g_settings_get_string (self->settings, "night-theme")); g_settings_set_string (self->cinnamon_theme_settings, "name", g_settings_get_string (self->settings, "night-cinnamon-theme")); @@ -351,6 +353,10 @@ night_mode_recheck (CsdNightMode *self) return; } + gdouble frac_day; + g_autoptr(GDateTime) dt_now = csd_night_mode_get_date_time_now (self); + frac_day = csd_night_mode_frac_day_from_dt (dt_now); + /* check if it's still not tomorrow */ if (self->disabled_until_tmw) { GTimeSpan time_span; @@ -515,7 +521,7 @@ night_light_recheck (CsdNightMode *self) } /* get the current hour of a day as a fraction */ - frac_day = csd_night_light_frac_day_from_dt (dt_now); + frac_day = csd_night_mode_frac_day_from_dt (dt_now); g_debug ("fractional day = %.3f, limits = %.3f->%.3f", frac_day, schedule_from, schedule_to); @@ -597,13 +603,13 @@ poll_timeout_create (CsdNightMode *self) if (self->source != NULL) return; - dt_now = csd_night_light_get_date_time_now (self); + dt_now = csd_night_mode_get_date_time_now (self); dt_expiry = g_date_time_add_seconds (dt_now, CSD_NIGHT_LIGHT_POLL_TIMEOUT); self->source = _gnome_datetime_source_new (dt_now, dt_expiry, TRUE); g_source_set_callback (self->source, - night_light_recheck_cb, + night_mode_recheck_cb, self, NULL); g_source_attach (self->source, NULL); } @@ -625,7 +631,7 @@ settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data) { CsdNightMode *self = CSD_NIGHT_LIGHT (user_data); g_debug ("settings changed"); - night_light_recheck (self); + night_mode_recheck (self); } static void @@ -652,9 +658,9 @@ update_location_from_timezone (CsdNightMode *self) } void -csd_night_light_set_disabled_until_tmw (CsdNightMode *self, gboolean value) +csd_night_mode_set_disabled_until_tmw (CsdNightMode *self, gboolean value) { - g_autoptr(GDateTime) dt = csd_night_light_get_date_time_now (self); + g_autoptr(GDateTime) dt = csd_night_mode_get_date_time_now (self); if (self->disabled_until_tmw == value) return; @@ -663,7 +669,7 @@ csd_night_light_set_disabled_until_tmw (CsdNightMode *self, gboolean value) g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref); if (self->disabled_until_tmw) self->disabled_until_tmw_dt = g_steal_pointer (&dt); - night_light_recheck (self); + night_mode_recheck (self); g_object_notify (G_OBJECT (self), "disabled-until-tmw"); } @@ -744,7 +750,7 @@ csd_night_light_get_temperature (CsdNightMode *self) } gboolean -csd_night_light_start (CsdNightMode *self, GError **error) +csd_night_mode_start (CsdNightMode *self, GError **error) { night_mode_recheck (self); poll_timeout_create (self); @@ -759,9 +765,9 @@ csd_night_light_start (CsdNightMode *self, GError **error) } static void -csd_night_light_finalize (GObject *object) +csd_night_mode_finalize (GObject *object) { - CsdNightMode *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_MODE (object); poll_timeout_destroy (self); poll_smooth_destroy (self); @@ -775,16 +781,16 @@ csd_night_light_finalize (GObject *object) self->validate_id = 0; } - G_OBJECT_CLASS (csd_night_light_parent_class)->finalize (object); + G_OBJECT_CLASS (csd_night_mode_parent_class)->finalize (object); } static void -csd_night_light_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +csd_night_mode_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - CsdNightMode *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_MODE (object); switch (prop_id) { case PROP_SUNRISE: @@ -797,11 +803,14 @@ csd_night_light_set_property (GObject *object, self->cached_temperature = g_value_get_double (value); break; case PROP_DISABLED_UNTIL_TMW: - csd_night_light_set_disabled_until_tmw (self, g_value_get_boolean (value)); + csd_night_mode_set_disabled_until_tmw (self, g_value_get_boolean (value)); break; - case PROP_FORCED: + case PROP_LIGHT_FORCED: csd_night_light_set_forced (self, g_value_get_boolean (value)); break; + case PROP_THEME_FORCED: + csd_night_theme_set_forced (self, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -838,7 +847,7 @@ csd_night_mode_get_property (GObject *object, g_value_set_boolean (value, csd_night_light_get_forced (self)); break; case PROP_THEME_FORCED: - g_value_set_boolean (value, csd_night_light_get_forced (self)); + g_value_set_boolean (value, csd_night_theme_get_forced (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); diff --git a/plugins/color/csd-night-mode.h b/plugins/color/csd-night-mode.h index 6717007b..49e0ab83 100644 --- a/plugins/color/csd-night-mode.h +++ b/plugins/color/csd-night-mode.h @@ -21,6 +21,7 @@ #ifndef __CSD_NIGHT_MODE_H__ #define __CSD_NIGHT_MODE_H__ +#include "csd-night-mode-common.h" #include G_BEGIN_DECLS @@ -32,7 +33,8 @@ CsdNightMode *csd_night_mode_new (void); gboolean csd_night_mode_start (CsdNightMode *self, GError **error); -gboolean csd_night_mode_get_active (CsdNightMode *self); +gboolean csd_night_light_get_active (CsdNightMode *self); +gboolean csd_night_theme_get_active (CsdNightMode *self); gdouble csd_night_mode_get_sunrise (CsdNightMode *self); gdouble csd_night_mode_get_sunset (CsdNightMode *self); gdouble csd_night_light_get_temperature (CsdNightMode *self); From 2eeffe9f5c816c58f2a04b91fd1a5a169ae47046 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Mon, 16 Mar 2026 21:42:42 +0100 Subject: [PATCH 06/20] updated more name stuff --- plugins/color/csd-night-mode.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index c11bf586..84202bde 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -77,7 +77,7 @@ enum { COLOR_SCHEME_DEFAULT = 0, COLOR_SCHEME_DARK = 1, COLOR_SCHEME_LIGHT = 2 -} +}; #define CSD_NIGHT_MODE_SCHEDULE_TIMEOUT 5 /* seconds */ #define CSD_NIGHT_MODE_POLL_TIMEOUT 60 /* seconds */ @@ -583,7 +583,7 @@ night_light_recheck (CsdNightMode *self) static gboolean night_mode_recheck_cb (gpointer user_data) { - CsdNightMode *self = CSD_NIGHT_LIGHT (user_data); + CsdNightMode *self = CSD_NIGHT_MODE (user_data); /* recheck parameters, then reschedule a new timeout */ night_mode_recheck (self); @@ -604,7 +604,7 @@ poll_timeout_create (CsdNightMode *self) return; dt_now = csd_night_mode_get_date_time_now (self); - dt_expiry = g_date_time_add_seconds (dt_now, CSD_NIGHT_LIGHT_POLL_TIMEOUT); + dt_expiry = g_date_time_add_seconds (dt_now, CSD_NIGHT_MODE_POLL_TIMEOUT); self->source = _gnome_datetime_source_new (dt_now, dt_expiry, TRUE); @@ -629,7 +629,7 @@ poll_timeout_destroy (CsdNightMode *self) static void settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data) { - CsdNightMode *self = CSD_NIGHT_LIGHT (user_data); + CsdNightMode *self = CSD_NIGHT_MODE (user_data); g_debug ("settings changed"); night_mode_recheck (self); } @@ -674,7 +674,7 @@ csd_night_mode_set_disabled_until_tmw (CsdNightMode *self, gboolean value) } gboolean -csd_night_light_get_disabled_until_tmw (CsdNightMode *self) +csd_night_mode_get_disabled_until_tmw (CsdNightMode *self) { return self->disabled_until_tmw; } @@ -693,7 +693,7 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) if (!self->light_forced && !self->cached_light_active) csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); - night_mode_recheck (self); + night_light_recheck (self); } void @@ -710,13 +710,19 @@ csd_night_theme_set_forced (CsdNightMode *self, gboolean value) if (!self->theme_forced && !self->cached_theme_active) night_theme_switch_on (self); - night_mode_recheck (self); + night_theme_recheck (self); } gboolean csd_night_light_get_forced (CsdNightMode *self) { - return self->forced; + return self->light_forced; +} + +gboolean +csd_night_theme_get_forced (CsdNightMode *self) +{ + return self->theme_forced; } gboolean @@ -822,7 +828,7 @@ csd_night_mode_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - CsdNightMode *self = CSD_NIGHT_LIGHT (object); + CsdNightMode *self = CSD_NIGHT_MODE (object); switch (prop_id) { case PROP_LIGHT_ACTIVE: @@ -841,7 +847,7 @@ csd_night_mode_get_property (GObject *object, g_value_set_double (value, self->cached_sunrise); break; case PROP_DISABLED_UNTIL_TMW: - g_value_set_boolean (value, csd_night_light_get_disabled_until_tmw (self)); + g_value_set_boolean (value, csd_night_mode_get_disabled_until_tmw (self)); break; case PROP_LIGHT_FORCED: g_value_set_boolean (value, csd_night_light_get_forced (self)); @@ -913,7 +919,7 @@ csd_night_mode_class_init (CsdNightModeClass *klass) PROP_DISABLED_UNTIL_TMW, g_param_spec_boolean ("disabled-until-tmw", "Disabled until tomorrow", - "If the night light is disabled until the next day", + "If the night mode is disabled until the next day", FALSE, G_PARAM_READWRITE)); From 1d7c6c8ce4a4b4697a5db205aed912761ae9a8c0 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Tue, 17 Mar 2026 16:31:06 +0100 Subject: [PATCH 07/20] repaired missing schedule_to in night_mode_recheck --- plugins/color/csd-night-mode.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 84202bde..cbeaaa7f 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -353,6 +353,9 @@ night_mode_recheck (CsdNightMode *self) return; } + /* calculate the position of the sun */ + update_cached_sunrise_sunset (self); + gdouble frac_day; g_autoptr(GDateTime) dt_now = csd_night_mode_get_date_time_now (self); frac_day = csd_night_mode_frac_day_from_dt (dt_now); @@ -373,7 +376,7 @@ night_mode_recheck (CsdNightMode *self) gdouble frac_disabled; frac_disabled = csd_night_mode_frac_day_from_dt (self->disabled_until_tmw_dt); if (frac_disabled != frac_day && - csd_night_mode_frac_day_is_between (schedule_to, + csd_night_mode_frac_day_is_between (self->cached_sunrise, frac_disabled, frac_day)) { g_debug ("night mode sun rise happened, resetting disabled-until-tomorrow"); @@ -390,11 +393,6 @@ night_mode_recheck (CsdNightMode *self) } } - if (g_settings_get_enum (self->settings, "night-light-schedule-mode") == NIGHT_MODE_SCHEDULE_AUTO) { - /* calculate the position of the sun */ - update_cached_sunrise_sunset (self); - } - night_light_recheck (self); night_theme_recheck (self); } @@ -416,7 +414,7 @@ night_theme_recheck (CsdNightMode *self) if (!g_settings_get_boolean (self->settings, "night-theme-enabled")) { /* settings say NO */ - csd_night_theme_set_active(self, FALSE); + csd_night_theme_set_active (self, FALSE); g_debug ("night theme disabled"); return; } From b20e07c01c1a716bc00dd5a1b1fa9163049892f4 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Tue, 17 Mar 2026 16:35:20 +0100 Subject: [PATCH 08/20] added missing (empty) default value to settings schema --- ...g.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 1cf6c495..7b7d0f8d 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -66,12 +66,12 @@ This theme will be switched on instead of the current cinnamon-theme as soon as the night mode gets activated. - + '' This is set to the last detected day theme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. - + '' This is set to the last detected day desktop theme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. From 8ce1ea965eed8634226122b3b7a1639fa23fd709 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Tue, 17 Mar 2026 16:48:14 +0100 Subject: [PATCH 09/20] repaired wrong xml-settings-schema-default-values --- ...g.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 7b7d0f8d..80b3e4ea 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -56,12 +56,12 @@ Changes the theme to “night-theme” when night light activates (and back). - Mint-Y-Dark-Aqua + '' The theme which should be activated at night This theme will be switched on instead of the current theme as soon as the night mode gets activated. - Mint-Y-Dark-Aqua + '' The cinnamon-theme which should be activated at night This theme will be switched on instead of the current cinnamon-theme as soon as the night mode gets activated. From a36a3b681a17f2ac492514363c723b31aac7ac02 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Tue, 17 Mar 2026 16:57:54 +0100 Subject: [PATCH 10/20] added missing "" tag --- .../org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in | 1 + 1 file changed, 1 insertion(+) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 80b3e4ea..25e19500 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -79,5 +79,6 @@ 'default' This is set to the last detected day xapp color-scheme When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. + From 0b1e8570cc4c0972c25a2f0cc9faf462c94dae34 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Wed, 18 Mar 2026 16:39:04 +0100 Subject: [PATCH 11/20] added icon-theme-switcher --- .gitignore | 5 +- cs_themes.py | 1036 ----------------- ...ngs-daemon.plugins.color.gschema.xml.in.in | 17 +- plugins/color/csd-night-mode.c | 58 +- 4 files changed, 56 insertions(+), 1060 deletions(-) delete mode 100644 cs_themes.py diff --git a/.gitignore b/.gitignore index 979dfa18..d698b74c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,4 @@ debian/*.install debian/*.links debian/.debhelper/ debian/debhelper-build-stamp -debian/*.debhelper -.gitignore -.vscode* -apt_installs_for_this \ No newline at end of file +debian/*.debhelper \ No newline at end of file diff --git a/cs_themes.py b/cs_themes.py deleted file mode 100644 index 418ad8f6..00000000 --- a/cs_themes.py +++ /dev/null @@ -1,1036 +0,0 @@ -#!/usr/bin/python3 - -import os -import json -import tinycss2 - -from gi.repository import Gtk, GdkPixbuf - -from xapp.GSettingsWidgets import * -from bin.CinnamonGtkSettings import CssRange, CssOverrideSwitch, GtkSettingsSwitch, PreviewWidget, Gtk2ScrollbarSizeEditor -from bin.SettingsWidgets import LabelRow, SidePage, walk_directories -from bin.ChooserButtonWidgets import PictureChooserButton -from bin.ExtensionCore import DownloadSpicesPage -from bin.Spices import Spice_Harvester - -from pathlib import Path -import config - -ICON_SIZE = 48 - -# Gtk and Cinnamon check folders in order of precedence. These lists match the -# order. It doesn't really matter here, since we're only looking for names, -# but it's helpful to be aware of it. - -ICON_FOLDERS = [ - os.path.join(GLib.get_home_dir(), ".icons"), - os.path.join(GLib.get_user_data_dir(), "icons") -] + [os.path.join(datadir, "icons") for datadir in GLib.get_system_data_dirs()] - -THEME_FOLDERS = [ - os.path.join(GLib.get_home_dir(), ".themes"), - os.path.join(GLib.get_user_data_dir(), "themes") -] + [os.path.join(datadir, "themes") for datadir in GLib.get_system_data_dirs()] - -THEMES_BLACKLIST = [ - "gnome", # not meant to be used as a theme. Provides icons to inheriting themes. - "hicolor", # same - "adwaita", "adwaita-dark", "adwaitalegacy", # incomplete outside of GNOME, doesn't support Cinnamon. - "highcontrast", # same. Also, available via a11y as a global setting. - "epapirus", "epapirus-dark", # specifically designed for Pantheon - "ubuntu-mono", "ubuntu-mono-dark", "ubuntu-mono-light", "loginicons", # ubuntu-mono icons (non-removable in Ubuntu 24.04) - "humanity", "humanity-dark" # same -] - -class Style: - def __init__(self, json_obj): - self.name = json_obj["name"] - self.modes = {} - self.default_mode = None - -class Mode: - def __init__(self, name): - self.name = name - self.default_variant = None - self.variants = [] - - def get_variant_by_name(self, name): - for variant in self.variants: - if name == variant.name: - return variant - - return None - -class Variant: - def __init__(self, json_obj): - self.name = json_obj["name"] - self.gtk_theme = None - self.icon_theme = None - self.cinnamon_theme = None - self.cursor_theme = None - self.color = "#000000" - self.color2 = "#000000" - if "themes" in json_obj: - themes = json_obj["themes"] - self.gtk_theme = themes - self.icon_theme = themes - self.cinnamon_theme = themes - self.cursor_theme = themes - if "gtk" in json_obj: - self.gtk_theme = json_obj["gtk"] - if "icons" in json_obj: - self.icon_theme = json_obj["icons"] - if "cinnamon" in json_obj: - self.cinnamon_theme = json_obj["cinnamon"] - if "cursor" in json_obj: - self.cursor_theme = json_obj["cursor"] - self.color = json_obj["color"] - self.color2 = self.color - if "color2" in json_obj: - self.color2 = json_obj["color2"] - -class Module: - comment = _("Manage themes to change how your desktop looks") - name = "themes" - category = "appear" - - def __init__(self, content_box): - self.keywords = _("themes, style") - self.icon = "cs-themes" - self.window = None - sidePage = SidePage(_("Themes"), self.icon, self.keywords, content_box, module=self) - self.sidePage = sidePage - self.refreshing = False # flag to ensure we only refresh once at any given moment - - def refresh_themes(self): - # Find all installed themes - self.gtk_themes = [] - self.gtk_theme_names = set() - self.icon_theme_names = [] - self.cinnamon_themes = [] - self.cinnamon_theme_names = set() - self.cursor_themes = [] - self.cursor_theme_names = set() - - # Gtk themes -- Only shows themes that have a gtk-3.* variation - for (name, path) in walk_directories(THEME_FOLDERS, self.filter_func_gtk_dir, return_directories=True): - if name.lower() in THEMES_BLACKLIST: - continue - for theme in self.gtk_themes: - if name == theme[0]: - if path == THEME_FOLDERS[0]: - continue - else: - self.gtk_themes.remove(theme) - self.gtk_theme_names.add(name) - self.gtk_themes.append((name, path)) - self.gtk_themes.sort(key=lambda a: a[0].lower()) - - # Cinnamon themes - for (name, path) in walk_directories(THEME_FOLDERS, lambda d: os.path.exists(os.path.join(d, "cinnamon")), return_directories=True): - for theme in self.cinnamon_themes: - if name == theme[0]: - if path == THEME_FOLDERS[0]: - continue - else: - self.cinnamon_themes.remove(theme) - self.cinnamon_theme_names.add(name) - self.cinnamon_themes.append((name, path)) - self.cinnamon_themes.sort(key=lambda a: a[0].lower()) - - # Icon themes - walked = walk_directories(ICON_FOLDERS, lambda d: os.path.isdir(d), return_directories=True) - valid = [] - for directory in walked: - if directory[0].lower() in THEMES_BLACKLIST: - continue - path = os.path.join(directory[1], directory[0], "index.theme") - if os.path.exists(path): - try: - for line in list(open(path)): - if line.startswith("Hidden=true"): - break - if line.startswith("Directories="): - valid.append(directory) - break - except Exception as e: - print (e) - valid.sort(key=lambda a: a[0].lower()) - for (name, path) in valid: - if name not in self.icon_theme_names: - self.icon_theme_names.append(name) - - # Cursor themes - for (name, path) in walk_directories(ICON_FOLDERS, lambda d: os.path.isdir(d) and os.path.exists(os.path.join(d, "cursors")), return_directories=True): - if name.lower() in THEMES_BLACKLIST: - continue - for theme in self.cursor_themes: - if name == theme[0]: - if path == ICON_FOLDERS[0]: - continue - else: - self.cursor_themes.remove(theme) - self.cursor_theme_names.add(name) - self.cursor_themes.append((name, path)) - self.cursor_themes.sort(key=lambda a: a[0].lower()) - - def on_module_selected(self): - if not self.loaded: - print("Loading Themes module") - - self.refresh_themes() - - self.ui_ready = True - - self.spices = Spice_Harvester('theme', self.window) - - self.sidePage.stack = SettingsStack() - self.sidePage.add_widget(self.sidePage.stack) - - self.settings = Gio.Settings.new("org.cinnamon.desktop.interface") - self.cinnamon_settings = Gio.Settings.new("org.cinnamon.theme") - self.xsettings = Gio.Settings.new("org.x.apps.portal") - - self.scale = self.window.get_scale_factor() - - self.icon_chooser = self.create_button_chooser(self.settings, 'icon-theme', 'icons', 'icons', button_picture_width=ICON_SIZE, menu_picture_width=ICON_SIZE, num_cols=4, frame=False) - self.cursor_chooser = self.create_button_chooser(self.settings, 'cursor-theme', 'icons', 'cursors', button_picture_width=32, menu_picture_width=32, num_cols=4, frame=False) - self.theme_chooser = self.create_button_chooser(self.settings, 'gtk-theme', 'themes', 'gtk-3.0', button_picture_width=125, menu_picture_width=125, num_cols=4, frame=True) - self.cinnamon_chooser = self.create_button_chooser(self.cinnamon_settings, 'name', 'themes', 'cinnamon', button_picture_width=125, menu_picture_width=125*self.scale, num_cols=4, frame=True) - - selected_meta_theme = None - - gladefile = "/usr/share/cinnamon/cinnamon-settings/themes.ui" - builder = Gtk.Builder() - builder.set_translation_domain('cinnamon') - builder.add_from_file(gladefile) - page = builder.get_object("page_simplified") - page.show() - - self.style_combo = builder.get_object("style_combo") - self.mixed_button = builder.get_object("mixed_button") - self.dark_button = builder.get_object("dark_button") - self.light_button = builder.get_object("light_button") - self.color_box = builder.get_object("color_box") - self.customize_button = builder.get_object("customize_button") - self.preset_button = builder.get_object("preset_button") - self.color_label = builder.get_object("color_label") - self.active_style = None - self.active_mode_name = None - self.active_variant = None - - # HiDPI support - for mode in ["mixed", "dark", "light"]: - path = f"/usr/share/cinnamon/cinnamon-settings/appearance-{mode}.svg" - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 112*self.scale, 80*self.scale) - surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.scale) - builder.get_object(f"image_{mode}").set_from_surface(surface) - - self.color_dot_svg = "" - with open("/usr/share/cinnamon/cinnamon-settings/color_dot.svg") as f: - self.color_dot_svg = f.read() - - self.reset_look_ui() - - self.mixed_button.connect("clicked", self.on_mode_button_clicked, "mixed") - self.dark_button.connect("clicked", self.on_mode_button_clicked, "dark") - self.light_button.connect("clicked", self.on_mode_button_clicked, "light") - self.customize_button.connect("clicked", self.on_customize_button_clicked) - self.style_combo.connect("changed", self.on_style_combo_changed) - - self.sidePage.stack.add_named(page, "simplified") - - page = SettingsPage() - self.sidePage.stack.add_titled(page, "themes", _("Themes")) - - settings = page.add_section() - - widget = self.make_group(_("Mouse Pointer"), self.cursor_chooser) - settings.add_row(widget) - - widget = self.make_group(_("Applications"), self.theme_chooser) - settings.add_row(widget) - - widget = self.make_group(_("Icons"), self.icon_chooser) - settings.add_row(widget) - - widget = self.make_group(_("Desktop"), self.cinnamon_chooser) - settings.add_row(widget) - - button = Gtk.Button() - button.set_label(_("Simplified settings...")) - button.set_halign(Gtk.Align.END) - button.connect("clicked", self.on_simplified_button_clicked) - page.add(button) - - page = DownloadSpicesPage(self, 'theme', self.spices, self.window) - self.sidePage.stack.add_titled(page, 'download', _("Add/Remove")) - - page = SettingsPage() - self.sidePage.stack.add_titled(page, "options", _("Settings")) - - settings = page.add_section(_("Miscellaneous options")) - - options = [("default", _("Let applications decide")), - ("prefer-dark", _("Prefer dark mode")), - ("prefer-light", _("Prefer light mode"))] - widget = GSettingsComboBox(_("Dark mode"), "org.x.apps.portal", "color-scheme", options) - widget.set_tooltip_text(_("This setting only affects applications which support dark mode")) - settings.add_row(widget) - - widget = GSettingsSwitch(_("Show icons in menus"), "org.cinnamon.settings-daemon.plugins.xsettings", "menus-have-icons") - settings.add_row(widget) - - widget = GSettingsSwitch(_("Show icons on buttons"), "org.cinnamon.settings-daemon.plugins.xsettings", "buttons-have-icons") - settings.add_row(widget) - - settings = page.add_section(_("Scrollbar behavior")) - - # Translators: The 'trough' is the part of the scrollbar that the 'handle' - # rides in. This setting determines whether clicking in that trough somewhere - # jumps directly to the new position, or if it only scrolls towards it. - switch = GtkSettingsSwitch(_("Jump to position when clicking in a trough"), "gtk-primary-button-warps-slider") - settings.add_row(switch) - - widget = GSettingsSwitch(_("Use overlay scroll bars"), "org.cinnamon.desktop.interface", "gtk-overlay-scrollbars") - settings.add_row(widget) - - self.gtk2_scrollbar_editor = Gtk2ScrollbarSizeEditor(widget.get_scale_factor()) - - switch = CssOverrideSwitch(_("Override the current theme's scrollbar width")) - settings.add_row(switch) - self.scrollbar_switch = switch.content_widget - - widget = CssRange(_("Scrollbar width"), "scrollbar slider", ["min-width", "min-height"], 2, 40, "px", None, switch) - settings.add_reveal_row(widget) - - try: - widget.sync_initial_switch_state() - except PermissionError as e: - print(e) - switch.set_sensitive(False) - - self.scrollbar_css_range = widget.content_widget - self.scrollbar_css_range.get_adjustment().set_page_increment(2.0) - - switch.content_widget.connect("notify::active", self.on_css_override_active_changed) - widget.content_widget.connect("value-changed", self.on_range_slider_value_changed) - - self.on_css_override_active_changed(switch) - - widget = PreviewWidget() - settings.add_row(widget) - - label_widget = LabelRow(_( -"""Changes may not apply to already-running programs, and may not affect all applications.""")) - settings.add_row(label_widget) - - self.builder = self.sidePage.builder - - for path in [THEME_FOLDERS[0], ICON_FOLDERS[0], ICON_FOLDERS[1]]: - try: - os.makedirs(path) - except OSError: - pass - - self.monitors = [] - for path in (THEME_FOLDERS + ICON_FOLDERS): - if os.path.exists(path): - file_obj = Gio.File.new_for_path(path) - try: - file_monitor = file_obj.monitor_directory(Gio.FileMonitorFlags.SEND_MOVED, None) - file_monitor.connect("changed", self.on_file_changed) - self.monitors.append(file_monitor) - except Exception as e: - # File monitors can fail when the OS runs out of file handles - print(e) - - self.refresh_choosers() - if config.PARSED_ARGS.module is None or (config.PARSED_ARGS.module == "themes" and config.PARSED_ARGS.tab is None): - GLib.idle_add(self.set_mode, "simplified" if self.active_variant is not None else "themes", True) - return - - GLib.idle_add(self.set_mode, self.sidePage.stack.get_visible_child_name()) - - def is_variant_active(self, variant): - # returns whether or not the given variant corresponds to the currently selected themes - if variant.gtk_theme != self.settings.get_string("gtk-theme"): - return False - if variant.icon_theme != self.settings.get_string("icon-theme"): - return False - if variant.cinnamon_theme != self.cinnamon_settings.get_string("name"): - return False - if variant.cursor_theme != self.settings.get_string("cursor-theme"): - return False - return True - - def is_variant_valid(self, variant): - # returns whether or not the given variant is valid (i.e. made of themes which are currently installed) - if variant.gtk_theme is None: - print("No Gtk theme defined") - return False - if variant.icon_theme is None: - print("No icon theme defined") - return False - if variant.cinnamon_theme is None: - print("No Cinnamon theme defined") - return False - if variant.cursor_theme is None: - print("No cursor theme defined") - return False - if variant.gtk_theme not in self.gtk_theme_names: - print("Gtk theme not found:", variant.gtk_theme) - return False - if variant.icon_theme not in self.icon_theme_names: - print("icon theme not found:", variant.icon_theme) - return False - if variant.cinnamon_theme not in self.cinnamon_theme_names and variant.cinnamon_theme != "cinnamon": - print("Cinnamon theme not found:", variant.cinnamon_theme) - return False - if variant.cursor_theme not in self.cursor_theme_names: - print("Cursor theme not found:", variant.cursor_theme) - return False - return True - - def cleanup_ui(self): - self.mixed_button.set_state_flags(Gtk.StateFlags.NORMAL, True) - self.dark_button.set_state_flags(Gtk.StateFlags.NORMAL, True) - self.light_button.set_state_flags(Gtk.StateFlags.NORMAL, True) - self.mixed_button.set_sensitive(False) - self.dark_button.set_sensitive(False) - self.light_button.set_sensitive(False) - for child in self.color_box.get_children(): - self.color_box.remove(child) - self.color_label.hide() - model = self.style_combo.get_model() - model.clear() - - def reset_look_ui(self): - if not self.ui_ready: - return - - self.ui_ready = False - self.cleanup_ui() - - # Read the JSON files - self.styles = {} - self.style_objects = {} - self.active_style = None - self.active_mode_name = None - self.active_variant = None - - path = "/usr/share/cinnamon/styles.d" - if os.path.exists(path): - for filename in sorted(os.listdir(path)): - if filename.endswith(".styles"): - try: - with open(os.path.join(path, filename)) as f: - json_text = json.loads(f.read()) - for style_json in json_text["styles"]: - style = Style(style_json) - for mode_name in ["mixed", "dark", "light"]: - if mode_name in style_json: - mode = Mode(mode_name) - for variant_json in style_json[mode_name]: - variant = Variant(variant_json) - if self.is_variant_valid(variant): - # Add the variant to the mode - mode.variants.append(variant) - if mode.default_variant is None: - # Assign the first variant as default - mode.default_variant = variant - if "default" in variant_json and variant_json["default"] == "true": - # Override default if specified - mode.default_variant = variant - # Add the mode to the style (if not done already) - if not mode_name in style.modes: - style.modes[mode_name] = mode - # Set it as the default mode if there's no default mode - if style.default_mode is None: - style.default_mode = mode - # Set active variant variables if the variant is active - if self.is_variant_active(variant): - self.active_style= style - self.active_mode_name = mode_name - self.active_variant = variant - # Override the default mode if specified - if "default" in style_json: - default_name = style_json["default"] - if default_name in style.modes: - style.default_mode = style.modes[default_name] - - if style.default_mode is None: - print ("No valid mode/variants found for style:", style.name) - else: - self.styles[style.name] = style - except Exception as e: - print(f"Failed to parse styles from {filename}.") - print(e) - - # Populate the style combo - for name in sorted(self.styles.keys()): - self.style_combo.append_text(name) - - if self.active_variant is not None: - style = self.active_style - mode = self.active_style.modes[self.active_mode_name] - variant = self.active_variant - print("Found active variant:", style.name, mode.name, variant.name) - # Position the style combo - model = self.style_combo.get_model() - iter = model.get_iter_first() - while (iter != None): - name = model.get_value(iter, 0) - if name == style.name: - self.style_combo.set_active_iter(iter) - break - iter = model.iter_next(iter) - # Set the mode buttons - for mode_name in ["mixed", "dark", "light"]: - if mode_name == "mixed": - button = self.mixed_button - elif mode_name == "dark": - button = self.dark_button - else: - button = self.light_button - # Set the button state - if mode_name == mode.name: - button.set_state_flags(Gtk.StateFlags.CHECKED, True) - else: - button.set_state_flags(Gtk.StateFlags.NORMAL, True) - if mode_name in style.modes: - button.set_sensitive(True) - else: - button.set_sensitive(False) - - if len(mode.variants) > 1: - # Generate the color buttons - self.color_label.show() - for variant in mode.variants: - svg = self.color_dot_svg.replace("#8cffbe", variant.color) - svg = svg.replace("#71718e", variant.color2) - svg = str.encode(svg) - stream = Gio.MemoryInputStream.new_from_bytes(GLib.Bytes.new(svg)) - pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, 22*self.scale, 22*self.scale, True, None) - surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.scale) - image = Gtk.Image.new_from_surface(surface) - button = Gtk.ToggleButton() - button.add(image) - button.show_all() - self.color_box.add(button) - if variant == self.active_variant: - button.set_state_flags(Gtk.StateFlags.CHECKED, True) - button.connect("clicked", self.on_color_button_clicked, variant) - else: - # Position style combo on "Custom" - self.style_combo.append_text(_("Custom")) - self.style_combo.set_active(len(self.styles.keys())) - self.ui_ready = True - - def on_customize_button_clicked(self, widget): - self.set_button_chooser(self.icon_chooser, self.settings.get_string("icon-theme"), 'icons', 'icons', ICON_SIZE) - self.set_button_chooser(self.cursor_chooser, self.settings.get_string("cursor-theme"), 'icons', 'cursors', 32) - self.set_button_chooser(self.theme_chooser, self.settings.get_string("gtk-theme"), 'themes', 'gtk-3.0', 35) - self.set_button_chooser(self.cinnamon_chooser, self.cinnamon_settings.get_string("name"), 'themes', 'cinnamon', 60) - self.set_mode("themes") - - def on_simplified_button_clicked(self, button): - self.reset_look_ui() - self.set_mode("simplified") - - def set_mode(self, mode, startup=False): - # When picking a start page at startup, no transition, or else you'll see the tail end of it happening - # as the page is loading. Otherwise, crossfade when switching between simple/custom. The left/right - # transition is kept as the default for shifting between the 3 custom pages (themes, downloads, settings). - if startup: - transition = Gtk.StackTransitionType.NONE - else: - transition = Gtk.StackTransitionType.CROSSFADE - - switcher_widget = Gio.Application.get_default().stack_switcher - - if mode == "simplified": - switcher_widget.set_opacity(0.0) - switcher_widget.set_sensitive(False) - else: - switcher_widget.set_opacity(1.0) - switcher_widget.set_sensitive(True) - - self.sidePage.stack.set_visible_child_full(mode, transition) - - def on_navigate_out_of_module(self): - switcher_widget = Gio.Application.get_default().stack_switcher - switcher_widget.set_opacity(1.0) - switcher_widget.set_sensitive(True) - - def on_color_button_clicked(self, button, variant): - print("Color button clicked") - self.activate_variant(variant) - - def on_mode_button_clicked(self, button, mode_name): - print("Mode button clicked") - if self.active_style is not None: - mode = self.active_style.modes[mode_name] - self.activate_mode(self.active_style, mode) - - def on_style_combo_changed(self, combobox): - if not self.ui_ready: - return - selected_name = combobox.get_active_text() - if selected_name == None or selected_name == _("Custom"): - return - print("Activating style:", selected_name) - for name in self.styles.keys(): - if name == selected_name: - style = self.styles[name] - mode = style.default_mode - self.activate_mode(style, mode) - - def activate_mode(self, style, mode): - print("Activating mode:", mode.name) - - if mode.name == "mixed": - self.xsettings.set_enum("color-scheme", 0) - elif mode.name == "dark": - self.xsettings.set_enum("color-scheme", 1) - elif mode.name == "light": - self.xsettings.set_enum("color-scheme", 2) - - if self.active_variant is not None: - new_same_variant = mode.get_variant_by_name(self.active_variant.name) - if new_same_variant is not None: - self.activate_variant(new_same_variant) - return - - self.activate_variant(mode.default_variant) - - def activate_variant(self, variant): - print("Activating variant:", variant.name) - self.settings.set_string("gtk-theme", variant.gtk_theme) - self.settings.set_string("icon-theme", variant.icon_theme) - self.cinnamon_settings.set_string("name", variant.cinnamon_theme) - self.settings.set_string("cursor-theme", variant.cursor_theme) - self.reset_look_ui() - - def on_css_override_active_changed(self, switch, pspec=None, data=None): - if self.scrollbar_switch.get_active(): - self.gtk2_scrollbar_editor.set_size(self.scrollbar_css_range.get_value()) - else: - self.gtk2_scrollbar_editor.set_size(0) - - def on_range_slider_value_changed(self, widget, data=None): - if self.scrollbar_switch.get_active(): - self.gtk2_scrollbar_editor.set_size(widget.get_value()) - - def on_file_changed(self, file, other, event, data): - if self.refreshing: - return - self.refreshing = True - GLib.timeout_add_seconds(5, self.refresh_themes) - GLib.timeout_add_seconds(5, self.refresh_choosers) - - def refresh_choosers(self): - array = [(self.cursor_chooser, "cursors", self.cursor_themes, self._on_cursor_theme_selected), - (self.theme_chooser, "gtk-3.0", self.gtk_themes, self._on_gtk_theme_selected), - (self.cinnamon_chooser, "cinnamon", self.cinnamon_themes, self._on_cinnamon_theme_selected), - (self.icon_chooser, "icons", self.icon_theme_names, self._on_icon_theme_selected)] - for element in array: - chooser, path_suffix, themes, callback = element - chooser.clear_menu() - chooser.set_sensitive(False) - chooser.progress = 0.0 - self.refresh_chooser(chooser, path_suffix, themes, callback) - self.refreshing = False - - def get_theme_category(self, theme_name, theme_type): - parts = theme_name.split('-') - - if theme_type == 'cursor': - # For cursors - first part of the name - return (parts[0], "Light") - - elif theme_type in ['gtk', 'icon']: - # Basic category is always the first part of the name - base_category = parts[0] - - # Exception: if the second part is a single letter, it's part of the category (e.g., Mint-X, Mint-Y) - if len(parts) >= 2 and len(parts[1]) == 1: - base_category = f"{parts[0]}-{parts[1]}" - - # Determine variant (light/dark/darker) - theme_lower = theme_name.lower() - if 'darker' in theme_lower: - variant = "Darker" - elif 'dark' in theme_lower: - variant = "Dark" - else: - variant = "Light" - - return (base_category, variant) - - elif theme_type == 'cinnamon': - # For desktop - basic category is the first part of the name - base_category = parts[0] - - # Exception: if the second part is a single letter, it's part of the category - if len(parts) >= 2 and len(parts[1]) == 1: - base_category = f"{parts[0]}-{parts[1]}" - - # Determine variant - theme_lower = theme_name.lower() - if 'darker' in theme_lower: - variant = "Darker" - elif 'dark' in theme_lower: - variant = "Dark" - else: - variant = "Light" - - return (base_category, variant) - - return ("Other", "Light") - - def refresh_chooser(self, chooser, path_suffix, themes, callback): - inc = 1.0 - if len(themes) > 0: - inc = 1.0 / len(themes) - - variant_sort_order = {"Light": 0, "Darker": 1, "Dark": 2} - - if path_suffix == 'icons': - cache_folder = GLib.get_user_cache_dir() + '/cs_themes/' - icon_cache_path = os.path.join(cache_folder, 'icons') - - # Retrieve list of known themes/locations for faster loading (icon theme loading and lookup are very slow) - if os.path.exists(icon_cache_path): - read_path = icon_cache_path - else: - read_path = '/usr/share/cinnamon/cinnamon-settings/icons' - - icon_paths = {} - with open(read_path, 'r') as cache_file: - for line in cache_file: - theme_name, icon_path = line.strip().split(':') - icon_paths[theme_name] = icon_path - - dump = False - - # Collect all themes with their categories and variants - categorized_themes = [] - for theme in themes: - category, variant = self.get_theme_category(theme, 'icon') - categorized_themes.append({'name': theme, 'category': category, 'variant': variant}) - - # Count themes per category - category_counts = {} - for theme_info in categorized_themes: - category_counts[theme_info['category']] = category_counts.get(theme_info['category'], 0) + 1 - - # Separate single-item categories - single_item_category_themes = [] - multi_item_category_themes = [] - - for theme_info in categorized_themes: - if category_counts[theme_info['category']] == 1: - single_item_category_themes.append(theme_info) - else: - multi_item_category_themes.append(theme_info) - - # Sort both lists - single_item_category_themes.sort(key=lambda x: (variant_sort_order.get(x['variant'], 3), x['name'])) - multi_item_category_themes.sort(key=lambda x: (x['category'], variant_sort_order.get(x['variant'], 3), x['name'])) - - # Display single-item category themes first, under an "Other Themes" label - if single_item_category_themes: - for theme_info in single_item_category_themes: - theme = theme_info['name'] - theme_path = None - if theme in icon_paths: - for theme_folder in ICON_FOLDERS: - possible_path = os.path.join(theme_folder, icon_paths[theme]) - if os.path.exists(possible_path): - theme_path = possible_path - break - - if theme_path is None: - icon_theme = Gtk.IconTheme() - icon_theme.set_custom_theme(theme) - folder = icon_theme.lookup_icon('folder', ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG) - if folder: - theme_path = folder.get_filename() - for theme_folder in ICON_FOLDERS: - if os.path.commonpath([theme_folder, theme_path]) == theme_folder: - icon_paths[theme] = os.path.relpath(theme_path, start=theme_folder) - break - dump = True - - if theme_path is None: - continue - - if os.path.exists(theme_path): - chooser.add_picture(theme_path, callback, title=theme, id=theme) - GLib.timeout_add(5, self.increment_progress, (chooser, inc)) - - # Add a blank separator if both single and multi-item categories exist - if single_item_category_themes and multi_item_category_themes: - chooser.add_separator() - - current_category = None - current_variant = None - # Display multi-item category themes - for theme_info in multi_item_category_themes: - category = theme_info['category'] - variant = theme_info['variant'] - theme = theme_info['name'] - - if current_category != category: - if current_category is not None: - chooser.add_separator() # Blank separator between categories - current_category = category - current_variant = None # Reset variant when category changes - chooser.add_separator(category) # Category name separator - - # Update current_variant, no separator here if it's just a variant change - current_variant = variant - - theme_path = None - if theme in icon_paths: - # loop through all possible locations until we find a match - # (user folders should override system ones) - for theme_folder in ICON_FOLDERS: - possible_path = os.path.join(theme_folder, icon_paths[theme]) - if os.path.exists(possible_path): - theme_path = possible_path - break - - if theme_path is None: - icon_theme = Gtk.IconTheme() - icon_theme.set_custom_theme(theme) - folder = icon_theme.lookup_icon('folder', ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG) - if folder: - theme_path = folder.get_filename() - # we need to get the relative path for storage - for theme_folder in ICON_FOLDERS: - if os.path.commonpath([theme_folder, theme_path]) == theme_folder: - icon_paths[theme] = os.path.relpath(theme_path, start=theme_folder) - break - dump = True - - if theme_path is None: - continue - - if os.path.exists(theme_path): - chooser.add_picture(theme_path, callback, title=theme, id=theme) - GLib.timeout_add(5, self.increment_progress, (chooser, inc)) - - if dump: - if not os.path.exists(cache_folder): - os.mkdir(cache_folder) - - with open(icon_cache_path, 'w') as cache_file: - for theme_name, icon_path_val in icon_paths.items(): # Renamed icon_path to avoid conflict - cache_file.write(f'{theme_name}:{icon_path_val}\n') - - else: - if path_suffix == "cinnamon": - chooser.add_picture("/usr/share/cinnamon/theme/thumbnail.png", callback, title="cinnamon", id="cinnamon") - if path_suffix in ["gtk-3.0", "cinnamon"]: - # Sort themes by user-installed first, then alphabetically - themes = sorted(themes, key=lambda t: (not t[1].startswith(GLib.get_home_dir()), t[0].lower())) - - - # Collect all themes with their categories and variants - categorized_themes = [] - for theme_data in themes: # theme_data is a tuple (name, path) - name = theme_data[0] - path = theme_data[1] - theme_type = 'gtk' if path_suffix == 'gtk-3.0' else 'cinnamon' - category, variant = self.get_theme_category(name, theme_type) - categorized_themes.append({'name': name, 'path': path, 'category': category, 'variant': variant, 'original_tuple': theme_data}) - - # Count themes per category - category_counts = {} - for theme_info in categorized_themes: - category_counts[theme_info['category']] = category_counts.get(theme_info['category'], 0) + 1 - - single_item_category_themes = [] - multi_item_category_themes = [] - - for theme_info in categorized_themes: - if category_counts[theme_info['category']] == 1: - single_item_category_themes.append(theme_info) - else: - multi_item_category_themes.append(theme_info) - - # Sort both lists - single_item_category_themes.sort(key=lambda x: (variant_sort_order.get(x['variant'], 3), x['name'].lower())) - multi_item_category_themes.sort(key=lambda x: (x['category'].lower(), variant_sort_order.get(x['variant'], 3), x['name'].lower())) - - # Display single-item category themes first, under an "Other Themes" label - if single_item_category_themes: - for theme_info in single_item_category_themes: - theme_name = theme_info['name'] - theme_path_val = theme_info['path'] # Renamed theme_path to avoid conflict - try: - for path_option in [f"{theme_path_val}/{theme_name}/{path_suffix}/thumbnail.png", - f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme_name}.png", - f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]: - if os.path.exists(path_option): - chooser.add_picture(path_option, callback, title=theme_name, id=theme_name) - break - except: - chooser.add_picture(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png", callback, title=theme_name, id=theme_name) - GLib.timeout_add(5, self.increment_progress, (chooser, inc)) - - # Add a blank separator if both single and multi-item categories exist - if single_item_category_themes and multi_item_category_themes: - chooser.add_separator() - - current_category = None - current_variant = None - # Display multi-item category themes - for theme_info in multi_item_category_themes: - category = theme_info['category'] - variant = theme_info['variant'] - theme_name = theme_info['name'] - theme_path_val = theme_info['path'] # Renamed theme_path to avoid conflict - - if current_category != category: - if current_category is not None: - chooser.add_separator() # Blank separator between categories - current_category = category - current_variant = None # Reset variant when category changes - chooser.add_separator(category) # Category name separator - - # Update current_variant, no separator here if it's just a variant change - current_variant = variant - - try: - for path_option in [f"{theme_path_val}/{theme_name}/{path_suffix}/thumbnail.png", - f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme_name}.png", - f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]: - if os.path.exists(path_option): - chooser.add_picture(path_option, callback, title=theme_name, id=theme_name) - break - except: - chooser.add_picture(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png", callback, title=theme_name, id=theme_name) - GLib.timeout_add(5, self.increment_progress, (chooser, inc)) - GLib.timeout_add(500, self.hide_progress, chooser) - - def increment_progress(self, payload): - (chooser, inc) = payload - chooser.increment_loading_progress(inc) - - def hide_progress(self, chooser): - chooser.set_sensitive(True) - chooser.reset_loading_progress() - - def _setParentRef(self, window): - self.window = window - - def make_group(self, group_label, widget, add_widget_to_size_group=True): - self.size_groups = getattr(self, "size_groups", [Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) for x in range(2)]) - box = SettingsWidget() - label = Gtk.Label() - label.set_markup(group_label) - label.props.xalign = 0.0 - self.size_groups[0].add_widget(label) - box.pack_start(label, False, False, 0) - if add_widget_to_size_group: - self.size_groups[1].add_widget(widget) - box.pack_end(widget, False, False, 0) - - return box - - def create_button_chooser(self, settings, key, path_prefix, path_suffix, button_picture_width, menu_picture_width, num_cols, frame): - chooser = PictureChooserButton(num_cols=num_cols, button_picture_width=button_picture_width, menu_picture_width=menu_picture_width, has_button_label=True, frame=frame) - theme = settings.get_string(key) - self.set_button_chooser(chooser, theme, path_prefix, path_suffix, button_picture_width) - return chooser - - def set_button_chooser(self, chooser, theme, path_prefix, path_suffix, button_picture_width): - self.set_button_chooser_text(chooser, theme) - if path_suffix == "cinnamon" and theme == "cinnamon": - chooser.set_picture_from_file("/usr/share/cinnamon/theme/thumbnail.png") - elif path_suffix == "icons": - current_theme = Gtk.IconTheme.get_default() - folder = current_theme.lookup_icon_for_scale("folder", button_picture_width, self.window.get_scale_factor(), 0) - if folder is not None: - path = folder.get_filename() - chooser.set_picture_from_file(path) - else: - try: - for path in ([os.path.join(datadir, path_prefix, theme, path_suffix, "thumbnail.png") for datadir in GLib.get_system_data_dirs()] - + [os.path.expanduser(f"~/.{path_prefix}/{theme}/{path_suffix}/thumbnail.png"), - f"/usr/share/cinnamon/thumbnails/{path_suffix}/{theme}.png", - f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png"]): - if os.path.exists(path): - chooser.set_picture_from_file(path) - break - except: - chooser.set_picture_from_file(f"/usr/share/cinnamon/thumbnails/{path_suffix}/unknown.png") - - def set_button_chooser_text(self, chooser, theme): - chooser.set_button_label(theme) - chooser.set_tooltip_text(theme) - - def _on_icon_theme_selected(self, path, theme): - try: - self.settings.set_string("icon-theme", theme) - self.set_button_chooser_text(self.icon_chooser, theme) - except Exception as detail: - print(detail) - return True - - def _on_gtk_theme_selected(self, path, theme): - try: - self.settings.set_string("gtk-theme", theme) - self.set_button_chooser_text(self.theme_chooser, theme) - except Exception as detail: - print(detail) - return True - - def _on_cursor_theme_selected(self, path, theme): - try: - self.settings.set_string("cursor-theme", theme) - self.set_button_chooser_text(self.cursor_chooser, theme) - except Exception as detail: - print(detail) - - self.update_cursor_theme_link(path, theme) - return True - - def _on_cinnamon_theme_selected(self, path, theme): - try: - self.cinnamon_settings.set_string("name", theme) - self.set_button_chooser_text(self.cinnamon_chooser, theme) - except Exception as detail: - print(detail) - return True - - def filter_func_gtk_dir(self, directory): - theme_dir = Path(directory) - for gtk3_dir in theme_dir.glob("gtk-3.*"): - # Skip gtk key themes - if os.path.exists(os.path.join(gtk3_dir, "gtk.css")): - return True - return False - - def update_cursor_theme_link(self, path, name): - contents = f"[icon theme]\nInherits={name}\n" - self._set_cursor_theme_at(ICON_FOLDERS[0], contents) - self._set_cursor_theme_at(ICON_FOLDERS[1], contents) - - def _set_cursor_theme_at(self, directory, contents): - default_dir = os.path.join(directory, "default") - index_path = os.path.join(default_dir, "index.theme") - - try: - os.makedirs(default_dir) - except os.error as e: - pass - - if os.path.exists(index_path): - os.unlink(index_path) - - with open(index_path, "w") as f: - f.write(contents) \ No newline at end of file diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 25e19500..13d8d088 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -65,6 +65,16 @@ The cinnamon-theme which should be activated at night This theme will be switched on instead of the current cinnamon-theme as soon as the night mode gets activated. + + '' + The icon-theme which should be activated at night + This icon-theme will be switched on instead of the current icon-theme as soon as the night mode gets activated. + + + 'prefer-dark' + The xapp color scheme which should be activated at night + This scheme will be switched on instead of the current color-scheme as soon as the night mode gets activated. + '' This is set to the last detected day theme @@ -73,7 +83,12 @@ '' This is set to the last detected day desktop theme - When the cinnamon-settings-daemon activates the night theme, this is set to the replaced theme. This theme will get reactivated when the night theme switches off. + When the cinnamon-settings-daemon activates the night theme, this is set to the replaced cinnamon-theme. This theme will get reactivated when the night theme switches off. + + + '' + This is set to the last detected day icon theme + When the cinnamon-settings-daemon activates the night theme, this is set to the replaced icon-theme. This theme will get reactivated when the night theme switches off. 'default' diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index cbeaaa7f..c20a9496 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -73,12 +73,6 @@ enum { NIGHT_MODE_SCHEDULE_ALWAYS_ON = 2 }; -enum { - COLOR_SCHEME_DEFAULT = 0, - COLOR_SCHEME_DARK = 1, - COLOR_SCHEME_LIGHT = 2 -}; - #define CSD_NIGHT_MODE_SCHEDULE_TIMEOUT 5 /* seconds */ #define CSD_NIGHT_MODE_POLL_TIMEOUT 60 /* seconds */ #define CSD_NIGHT_LIGHT_POLL_SMEAR 1 /* hours */ @@ -253,64 +247,87 @@ csd_night_light_set_temperature (CsdNightMode *self, gdouble temperature) static void night_theme_switch_on (CsdNightMode *self) { - gboolean is_active = TRUE; + gboolean is_active; /* get backups */ gchar *backup_day_theme = g_settings_get_string (self->settings, "backup-day-theme"); + gchar *backup_day_icon_theme = g_settings_get_string (self->settings, "backup-day-icon-theme"); gchar *backup_day_cinnamon_theme = g_settings_get_string (self->settings, "backup-day-cinnamon-theme"); /* check if there are backups */ is_active = ( - backup_day_theme != NULL && - g_strcmp0 (backup_day_theme, "") != 0 && - backup_day_cinnamon_theme != NULL && - g_strcmp0 (backup_day_cinnamon_theme, "") != 0 + (backup_day_theme != NULL && + g_strcmp0 (backup_day_theme, "") != 0) || + (backup_day_icon_theme != NULL && + g_strcmp0 (backup_day_icon_theme, "") != 0) || + (backup_day_cinnamon_theme != NULL && + g_strcmp0 (backup_day_cinnamon_theme, "") != 0) ); /* free memory */ g_free (backup_day_theme); + g_free (backup_day_icon_theme); g_free (backup_day_cinnamon_theme); if (is_active) { + g_debug ("night theme already active, not switching on"); return; + } else { + g_debug ("switching on night theme..."); } /* copy values to the backups */ g_settings_set_string (self->settings, "backup-day-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); g_settings_set_string (self->settings, "backup-day-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); + g_settings_set_string (self->settings, "backup-day-icon-theme", g_settings_get_string (self->theme_settings, "icon-theme")); g_settings_set_enum (self->settings, "backup-day-color-scheme", g_settings_get_enum (self->x_theme_settings, "color-scheme")); /* activate the night themes */ g_settings_set_string (self->theme_settings, "gtk-theme", g_settings_get_string (self->settings, "night-theme")); + g_settings_set_string (self->theme_settings, "icon-theme", g_settings_get_string (self->settings, "night-icon-theme")); g_settings_set_string (self->cinnamon_theme_settings, "name", g_settings_get_string (self->settings, "night-cinnamon-theme")); - g_settings_set_enum (self->x_theme_settings, "color-scheme", COLOR_SCHEME_DARK); + g_settings_set_enum (self->x_theme_settings, "color-scheme", g_settings_get_enum (self->settings, "night-color-scheme")); } static void night_theme_switch_off (CsdNightMode *self) { - gboolean is_active = TRUE; + gboolean is_active; /* get backups */ gchar *backup_day_theme = g_settings_get_string (self->settings, "backup-day-theme"); + gchar *backup_day_icon_theme = g_settings_get_string (self->settings, "backup-day-icon-theme"); gchar *backup_day_cinnamon_theme = g_settings_get_string (self->settings, "backup-day-cinnamon-theme"); /* check if there are no backups */ is_active = ( - backup_day_theme != NULL && - g_strcmp0 (backup_day_theme, "") != 0 && - backup_day_cinnamon_theme != NULL && - g_strcmp0 (backup_day_cinnamon_theme, "") != 0 + (backup_day_theme != NULL && + g_strcmp0 (backup_day_theme, "") != 0) || + (backup_day_icon_theme != NULL && + g_strcmp0 (backup_day_icon_theme, "") != 0) || + (backup_day_cinnamon_theme != NULL && + g_strcmp0 (backup_day_cinnamon_theme, "") != 0) ); if (!is_active) { + g_debug ("night theme inactive, not switching off"); + g_free (backup_day_theme); + g_free (backup_day_icon_theme); + g_free (backup_day_cinnamon_theme); return; + } else { + g_debug ("switching off night theme..."); } /* save the current night theme */ g_settings_set_string (self->settings, "night-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); + g_settings_set_string (self->settings, "night-icon-theme", g_settings_get_string (self->theme_settings, "icon-theme")); g_settings_set_string (self->settings, "night-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); + g_settings_set_enum (self->settings, "night-color-scheme", g_settings_get_enum (self->x_theme_settings, "color-scheme")); /* restore the backups */ - g_settings_set_string (self->theme_settings, "gtk-theme", g_settings_get_string (self->settings, "backup-day-theme")); - g_settings_set_string (self->cinnamon_theme_settings, "name", g_settings_get_string (self->settings, "backup-day-cinnamon-theme")); + g_settings_set_string (self->theme_settings, "gtk-theme", backup_day_theme); + g_settings_set_string (self->theme_settings, "icon-theme", backup_day_icon_theme); + g_settings_set_string (self->cinnamon_theme_settings, "name", backup_day_cinnamon_theme); g_settings_set_enum (self->x_theme_settings, "color-scheme", g_settings_get_enum (self->settings, "backup-day-color-scheme")); /* clear the backups */ g_settings_set_string (self->settings, "backup-day-theme", ""); + g_settings_set_string (self->settings, "backup-day-icon-theme", ""); g_settings_set_string (self->settings, "backup-day-cinnamon-theme", ""); /* free memory */ g_free (backup_day_theme); + g_free (backup_day_icon_theme); g_free (backup_day_cinnamon_theme); } @@ -777,6 +794,9 @@ csd_night_mode_finalize (GObject *object) poll_smooth_destroy (self); g_clear_object (&self->settings); + g_clear_object (&self->theme_settings); + g_clear_object (&self->cinnamon_theme_settings); + g_clear_object (&self->x_theme_settings); g_clear_pointer (&self->datetime_override, g_date_time_unref); g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref); From 5171b6f505e44a59bd8f9569954d9a0918deca16 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Wed, 18 Mar 2026 16:58:50 +0100 Subject: [PATCH 12/20] removed ineffective debug call --- plugins/color/csd-night-mode.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index c20a9496..6020c110 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -267,10 +267,7 @@ night_theme_switch_on (CsdNightMode *self) g_free (backup_day_cinnamon_theme); if (is_active) { - g_debug ("night theme already active, not switching on"); return; - } else { - g_debug ("switching on night theme..."); } /* copy values to the backups */ g_settings_set_string (self->settings, "backup-day-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); @@ -303,13 +300,10 @@ night_theme_switch_off (CsdNightMode *self) ); if (!is_active) { - g_debug ("night theme inactive, not switching off"); g_free (backup_day_theme); g_free (backup_day_icon_theme); g_free (backup_day_cinnamon_theme); return; - } else { - g_debug ("switching off night theme..."); } /* save the current night theme */ g_settings_set_string (self->settings, "night-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); @@ -438,9 +432,9 @@ night_theme_recheck (CsdNightMode *self) switch (g_settings_get_enum (self->settings, "night-light-schedule-mode")) { case NIGHT_MODE_SCHEDULE_ALWAYS_ON: - /* turn OFF and return (night theme always on? Who wants that. Select your theme manually in the settings) */ - g_debug ("night light always on - switching OFF theme (intentionally)"); - csd_night_theme_set_active (self, FALSE); + /* turn on and return */ + g_debug ("night mode always on - switching on theme"); + csd_night_theme_set_active (self, TRUE); return; case NIGHT_MODE_SCHEDULE_AUTO: /* was updated in night_mode_recheck(self) */ From 5ce70bc7f5102a0478b235b5c8869546180e0fbb Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Wed, 18 Mar 2026 17:17:41 +0100 Subject: [PATCH 13/20] changed settings description from "is not 'auto'" to "is 'manual'" --- ...g.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 13d8d088..86525107 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -38,12 +38,12 @@ 20.00 The start time - When “night-light-schedule-mode” is not 'auto', use this start time in hours from midnight. + When “night-light-schedule-mode” is 'manual', use this start time in hours from midnight. 6.00 The end time - When “night-light-schedule-mode” is not 'auto', use this end time in hours from midnight. + When “night-light-schedule-mode” is 'manual', use this end time in hours from midnight. (91,181) From ce697a3e9c79014405246cf45f4db115f3559b40 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Wed, 18 Mar 2026 17:22:29 +0100 Subject: [PATCH 14/20] changed light to mode (settings description) --- ...org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in index 86525107..180e4f80 100644 --- a/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in +++ b/data/org.cinnamon.settings-daemon.plugins.color.gschema.xml.in.in @@ -53,7 +53,7 @@ false If the night theme switcher is on - Changes the theme to “night-theme” when night light activates (and back). + Changes the theme to “night-theme” when night mode activates (and back). '' From 54076148fcc99d490cccd6c0a753502c069aec0b Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Thu, 19 Mar 2026 08:33:20 +0100 Subject: [PATCH 15/20] re-added (not useless after all) debug calls --- plugins/color/csd-night-mode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 6020c110..18681ad5 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -267,8 +267,10 @@ night_theme_switch_on (CsdNightMode *self) g_free (backup_day_cinnamon_theme); if (is_active) { + g_debug ("night theme already active => not switching on"); return; } + g_debug ("switching on night theme..."); /* copy values to the backups */ g_settings_set_string (self->settings, "backup-day-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); g_settings_set_string (self->settings, "backup-day-cinnamon-theme", g_settings_get_string (self->cinnamon_theme_settings, "name")); @@ -300,11 +302,13 @@ night_theme_switch_off (CsdNightMode *self) ); if (!is_active) { + g_debug ("night already inactive => not switching off"); g_free (backup_day_theme); g_free (backup_day_icon_theme); g_free (backup_day_cinnamon_theme); return; } + g_debug ("switching off night theme..."); /* save the current night theme */ g_settings_set_string (self->settings, "night-theme", g_settings_get_string (self->theme_settings, "gtk-theme")); g_settings_set_string (self->settings, "night-icon-theme", g_settings_get_string (self->theme_settings, "icon-theme")); From eea055de727190f93e0444f6672c8e206ce16088 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Thu, 19 Mar 2026 08:39:26 +0100 Subject: [PATCH 16/20] typo --- plugins/color/csd-night-mode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 18681ad5..7c3830d4 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -302,7 +302,7 @@ night_theme_switch_off (CsdNightMode *self) ); if (!is_active) { - g_debug ("night already inactive => not switching off"); + g_debug ("night theme already inactive => not switching off"); g_free (backup_day_theme); g_free (backup_day_icon_theme); g_free (backup_day_cinnamon_theme); From 04753153184af098c9e3ac6d93ec510240da4648 Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Fri, 20 Mar 2026 13:46:08 +0100 Subject: [PATCH 17/20] added cached_active_unset boolean --- plugins/color/csd-night-mode.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 7c3830d4..e1e9e6a5 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -35,6 +35,8 @@ struct _CsdNightMode { GSettings *theme_settings; GSettings *x_theme_settings; GSettings *cinnamon_theme_settings; + gboolean cached_light_active_unset; + gboolean cached_theme_active_unset; gboolean light_forced; gboolean theme_forced; gboolean disabled_until_tmw; @@ -332,8 +334,11 @@ night_theme_switch_off (CsdNightMode *self) static void csd_night_light_set_active (CsdNightMode *self, gboolean active) { - if (self->cached_light_active == active) + if (self->cached_light_active == active && !self->cached_light_active_unset) { return; + } else if (self->cached_light_active_unset) { + self->cached_light_active_unset = FALSE; + } self->cached_light_active = active; /* ensure set to unity temperature */ @@ -346,8 +351,11 @@ csd_night_light_set_active (CsdNightMode *self, gboolean active) static void csd_night_theme_set_active (CsdNightMode *self, gboolean active) { - if (self->cached_theme_active == active) + if (self->cached_theme_active == active && !self->cached_theme_active_unset) { return; + } else if (self->cached_theme_active_unset) { + self->cached_theme_active_unset = FALSE; + } self->cached_theme_active = active; /* switch off theme if not active & switch on else */ @@ -961,6 +969,8 @@ static void csd_night_mode_init (CsdNightMode *self) { self->smooth_enabled = TRUE; + self->cached_light_active_unset = TRUE; + self->cached_theme_active_unset = TRUE; self->cached_sunrise = -1.f; self->cached_sunset = -1.f; self->cached_temperature = CSD_COLOR_TEMPERATURE_DEFAULT; From 459c023a68c3d37e6ce02f4d599db47b00aa6bbe Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Fri, 20 Mar 2026 16:51:04 +0100 Subject: [PATCH 18/20] repair forced theme mode --- plugins/color/csd-night-mode.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index e1e9e6a5..134e111e 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -711,8 +711,10 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) /* A simple recheck might not reset the temperature if * night light is currently disabled. */ - if (!self->light_forced && !self->cached_light_active) + if (!self->light_forced && !self->cached_light_active) { csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); + return; + } night_light_recheck (self); } @@ -726,10 +728,12 @@ csd_night_theme_set_forced (CsdNightMode *self, gboolean value) self->theme_forced = value; g_object_notify (G_OBJECT (self), "light-forced"); - /* A simple recheck might not switch off + /* A simple recheck might not switch off if * night theme is currently disabled. */ - if (!self->theme_forced && !self->cached_theme_active) - night_theme_switch_on (self); + if (!self->theme_forced && !self->cached_theme_active) { + night_theme_switch_off (self); + return; + } night_theme_recheck (self); } From 84ae8e85f27030bd81a9502f78f23e3a7e452aee Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Fri, 20 Mar 2026 17:29:32 +0100 Subject: [PATCH 19/20] improved debug messages --- plugins/color/csd-color-manager.c | 2 -- plugins/color/csd-night-mode.c | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/color/csd-color-manager.c b/plugins/color/csd-color-manager.c index 26ee4667..b5752cf5 100644 --- a/plugins/color/csd-color-manager.c +++ b/plugins/color/csd-color-manager.c @@ -39,8 +39,6 @@ #define CSD_COLOR_DBUS_PATH CSD_DBUS_PATH "/Color" #define CSD_COLOR_DBUS_INTERFACE CSD_DBUS_BASE_INTERFACE ".Color" -/*introspection_xml is for dbus events this manager listens on - see the methods void on_..._notify(...)*/ - static const gchar introspection_xml[] = "" " " diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index 134e111e..fa8544c0 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -412,7 +412,7 @@ night_mode_recheck (CsdNightMode *self) g_clear_pointer(&self->disabled_until_tmw_dt, g_date_time_unref); g_object_notify (G_OBJECT (self), "disabled-until-tmw"); } else { - g_debug ("night mode disabled - it's still not tomorrow ):"); + g_debug ("night mode disabled-until-tomorrow is true"); } } @@ -445,7 +445,7 @@ night_theme_recheck (CsdNightMode *self) switch (g_settings_get_enum (self->settings, "night-light-schedule-mode")) { case NIGHT_MODE_SCHEDULE_ALWAYS_ON: /* turn on and return */ - g_debug ("night mode always on - switching on theme"); + g_debug ("night mode always on - theme on"); csd_night_theme_set_active (self, TRUE); return; case NIGHT_MODE_SCHEDULE_AUTO: @@ -713,6 +713,7 @@ csd_night_light_set_forced (CsdNightMode *self, gboolean value) * night light is currently disabled. */ if (!self->light_forced && !self->cached_light_active) { csd_night_light_set_temperature (self, CSD_COLOR_TEMPERATURE_DEFAULT); + g_debug ("night light exited forced mode"); return; } @@ -726,12 +727,13 @@ csd_night_theme_set_forced (CsdNightMode *self, gboolean value) return; self->theme_forced = value; - g_object_notify (G_OBJECT (self), "light-forced"); + g_object_notify (G_OBJECT (self), "theme-forced"); /* A simple recheck might not switch off if * night theme is currently disabled. */ if (!self->theme_forced && !self->cached_theme_active) { night_theme_switch_off (self); + g_debug ("night theme exited forced mode"); return; } From a0181a4bd621f9f615343b4d17e09b9f88661eba Mon Sep 17 00:00:00 2001 From: HansFritzPommes Date: Thu, 9 Apr 2026 16:35:57 +0200 Subject: [PATCH 20/20] repaired night light get_properties temperature & sunset --- plugins/color/csd-night-mode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/color/csd-night-mode.c b/plugins/color/csd-night-mode.c index fa8544c0..a2edd56b 100644 --- a/plugins/color/csd-night-mode.c +++ b/plugins/color/csd-night-mode.c @@ -871,10 +871,10 @@ csd_night_mode_get_property (GObject *object, g_value_set_double (value, self->cached_sunrise); break; case PROP_SUNSET: - g_value_set_double (value, self->cached_sunrise); + g_value_set_double (value, self->cached_sunset); break; case PROP_TEMPERATURE: - g_value_set_double (value, self->cached_sunrise); + g_value_set_double (value, self->cached_temperature); break; case PROP_DISABLED_UNTIL_TMW: g_value_set_boolean (value, csd_night_mode_get_disabled_until_tmw (self));