diff --git a/data/io.elementary.settings-daemon.gschema.xml b/data/io.elementary.settings-daemon.gschema.xml
index 506e381b..723d97de 100644
--- a/data/io.elementary.settings-daemon.gschema.xml
+++ b/data/io.elementary.settings-daemon.gschema.xml
@@ -71,6 +71,14 @@
+
+
+ []
+ The current schedules.
+ a(a{sv}a{sv})
+
+
+
false
diff --git a/src/Application.vala b/src/Application.vala
index 0014d548..33b95d33 100644
--- a/src/Application.vala
+++ b/src/Application.vala
@@ -21,6 +21,8 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
private Backends.Housekeeping housekeeping;
+ private Backends.ScheduleManager schedule_manager;
+
private const string FDO_ACCOUNTS_NAME = "org.freedesktop.Accounts";
private const string FDO_ACCOUNTS_PATH = "/org/freedesktop/Accounts";
@@ -39,6 +41,8 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
GLib.Intl.textdomain (Build.GETTEXT_PACKAGE);
add_main_option ("version", 'v', NONE, NONE, "Display the version", null);
+
+ schedule_manager = new Backends.ScheduleManager ();
}
protected override int handle_local_options (VariantDict options) {
@@ -77,6 +81,7 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
protected override bool dbus_register (DBusConnection connection, string object_path) throws Error {
base.dbus_register (connection, object_path);
+ connection.register_object (object_path, schedule_manager);
connection.register_object (object_path, new Backends.SystemUpdate ());
return true;
@@ -117,7 +122,10 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
try {
pantheon_service = yield connection.get_proxy (FDO_ACCOUNTS_NAME, path, GET_INVALIDATED_PROPERTIES);
- prefers_color_scheme_settings = new Backends.PrefersColorSchemeSettings (pantheon_service);
+
+ // prefers_color_scheme_settings = new Backends.PrefersColorSchemeSettings (pantheon_service);
+ schedule_manager.pantheon_service = pantheon_service;
+
accent_color_manager = new Backends.AccentColorManager (pantheon_service);
} catch {
warning ("Unable to get pantheon's AccountsService proxy, color scheme preference may be incorrect");
diff --git a/src/Backends/ScheduleManager/DaylightSchedule.vala b/src/Backends/ScheduleManager/DaylightSchedule.vala
new file mode 100644
index 00000000..406a5ad6
--- /dev/null
+++ b/src/Backends/ScheduleManager/DaylightSchedule.vala
@@ -0,0 +1,63 @@
+public class SettingsDaemon.Backends.DaylightSchedule : Schedule {
+ private double sunrise = 6.0;
+ private double sunset = 20.0;
+
+ public DaylightSchedule.from_parsed (Parsed parsed) {
+ base.from_parsed (parsed);
+ }
+
+ construct {
+ schedule_type = DAYLIGHT;
+ get_location.begin ();
+ Timeout.add (1000, time_callback);
+ }
+
+ private bool time_callback () {
+ var is_in = is_in_time_window ();
+
+ if (active != is_in) {
+ active = is_in;
+ }
+
+ return Source.CONTINUE;
+ }
+
+ private async void get_location () {
+ try {
+ var simple = yield new GClue.Simple (Build.PROJECT_NAME, GClue.AccuracyLevel.CITY, null);
+
+ simple.notify["location"].connect (() => {
+ on_location_updated (simple.location.latitude, simple.location.longitude);
+ });
+
+ on_location_updated (simple.location.latitude, simple.location.longitude);
+ } catch (Error e) {
+ warning ("Failed to connect to GeoClue2 service: %s", e.message);
+ return;
+ }
+ }
+
+ private void on_location_updated (double latitude, double longitude) {
+ var now = new DateTime.now_local ();
+ double _sunrise, _sunset;
+ if (SettingsDaemon.Utils.SunriseSunsetCalculator.get_sunrise_and_sunset (now, latitude, longitude, out _sunrise, out _sunset)) {
+ sunrise = _sunrise;
+ sunset = _sunset;
+ }
+ }
+
+ private bool is_in_time_window () {
+ var date_time = new DateTime.now_local ();
+ double time_double = 0;
+ time_double += date_time.get_hour ();
+ time_double += (double) date_time.get_minute () / 60;
+
+ // PM to AM
+ if (sunset > sunrise) {
+ return time_double < sunrise ? time_double <= sunset : time_double >= sunset;
+ }
+
+ // AM to AM, PM to PM, AM to PM
+ return (time_double >= sunset && time_double <= sunrise);
+ }
+}
diff --git a/src/Backends/ScheduleManager/ManualSchedule.vala b/src/Backends/ScheduleManager/ManualSchedule.vala
new file mode 100644
index 00000000..e611b706
--- /dev/null
+++ b/src/Backends/ScheduleManager/ManualSchedule.vala
@@ -0,0 +1,60 @@
+public class SettingsDaemon.Backends.ManualSchedule : Schedule {
+ public double from { get; construct set; }
+ public double to { get; construct set; }
+
+ public ManualSchedule (string name, double from, double to) {
+ base ();
+ this.name = name;
+ this.from = from;
+ this.to = to;
+ }
+
+ public ManualSchedule.from_parsed (Parsed parsed) {
+ base.from_parsed (parsed);
+ schedule_type = MANUAL;
+ from = (double) parsed.args["from"];
+ to = (double) parsed.args["to"];
+ }
+
+ construct {
+ Timeout.add (1000, time_callback);
+ }
+
+ private bool time_callback () {
+ var is_in = is_in_time_window ();
+
+ if (active != is_in) {
+ active = is_in;
+ }
+
+ return Source.CONTINUE;
+ }
+
+ private bool is_in_time_window () {
+ var date_time = new DateTime.now_local ();
+ double time_double = 0;
+ time_double += date_time.get_hour ();
+ time_double += (double) date_time.get_minute () / 60;
+
+ // PM to AM
+ if (from > to) {
+ return time_double < to ? time_double <= from : time_double >= from;
+ }
+
+ // AM to AM, PM to PM, AM to PM
+ return (time_double >= from && time_double <= to);
+ }
+
+ protected override void update (Schedule.Parsed parsed) {
+ base.update (parsed);
+ from = (double) parsed.args["from"];
+ to = (double) parsed.args["to"];
+ }
+
+ protected override HashTable get_private_args () {
+ var result = new HashTable (str_hash, str_equal);
+ result["from"] = from;
+ result["to"] = to;
+ return result;
+ }
+}
diff --git a/src/Backends/ScheduleManager/Schedule.vala b/src/Backends/ScheduleManager/Schedule.vala
new file mode 100644
index 00000000..acd642ca
--- /dev/null
+++ b/src/Backends/ScheduleManager/Schedule.vala
@@ -0,0 +1,67 @@
+public class SettingsDaemon.Backends.Schedule : Object {
+ public enum Type {
+ MANUAL,
+ DAYLIGHT
+ }
+
+ public struct Parsed {
+ string id;
+ Type type;
+ string name;
+ bool enabled;
+ HashTable args;
+ HashTable active_settings;
+ HashTable inactive_settings;
+ }
+
+ public string id { get; protected set; }
+ public Type schedule_type { get; construct set; }
+ public string name { get; protected set; }
+ public bool enabled { get; protected set; default = true; }
+ public bool active { get; protected set; default = false; }
+ public HashTable active_settings { get; private set; }
+ public HashTable inactive_settings { get; private set; } //Inactive settings should usually be !active_settings but can also be e.g. a default wallpaper path
+
+ public Schedule () {
+ id = Uuid.string_random ();
+ active_settings = new HashTable (str_hash, str_equal);
+ inactive_settings = new HashTable (str_hash, str_equal);
+ }
+
+ public Schedule.from_parsed (Parsed parsed) {
+ id = parsed.id;
+ name = parsed.name;
+ enabled = parsed.enabled;
+ active_settings = parsed.active_settings;
+ inactive_settings = parsed.inactive_settings;
+ }
+
+ /* Convenience method to add the same boolean inverted to inactive settings */
+ public void add_boolean (string key, bool val) {
+ active_settings[key] = val;
+ inactive_settings[key] = !val;
+ }
+
+ public Parsed get_parsed () {
+ Parsed result = {
+ id,
+ schedule_type,
+ name,
+ enabled,
+ get_private_args (),
+ active_settings,
+ inactive_settings
+ };
+ return result;
+ }
+
+ public virtual void update (Schedule.Parsed parsed) {
+ enabled = parsed.enabled;
+ active_settings = parsed.active_settings;
+ inactive_settings = parsed.inactive_settings;
+ }
+
+ protected virtual HashTable get_private_args () {
+ return new HashTable (str_hash, str_equal);
+ }
+}
diff --git a/src/Backends/ScheduleManager/ScheduleManager.vala b/src/Backends/ScheduleManager/ScheduleManager.vala
new file mode 100644
index 00000000..0af37d32
--- /dev/null
+++ b/src/Backends/ScheduleManager/ScheduleManager.vala
@@ -0,0 +1,116 @@
+[DBus (name="io.elementary.settings_daemon.ScheduleManager")]
+public class SettingsDaemon.Backends.ScheduleManager : GLib.Object {
+ private const string NIGHT_LIGHT = "night-light";
+ private const string DARK_MODE = "dark-mode";
+ private const string DND = "dnd";
+ private const string MONOCHROME = "monochrome";
+
+ private static Settings settings = new Settings ("io.elementary.settings-daemon.schedules");
+ private static Settings dnd_settings = new Settings ("io.elementary.notifications");
+ private static Settings monochrome_settings = new Settings ("io.elementary.desktop.wm.accessibility");
+
+ [DBus (visible=false)]
+ public unowned Pantheon.AccountsService? pantheon_service { get; set; }
+
+ private HashTable schedules = new HashTable (str_hash, str_equal);
+
+ construct {
+ foreach (var parsed_schedule in (Schedule.Parsed[]) settings.get_value ("schedules")) {
+ create_schedule_internal (parsed_schedule);
+ }
+ }
+
+ private void create_schedule_internal (Schedule.Parsed parsed) {
+ if (parsed.name in schedules) {
+ warning ("Schedule with the same name already exists");
+ return;
+ }
+
+ switch (parsed.type) {
+ case MANUAL:
+ add_schedule (new ManualSchedule.from_parsed (parsed));
+ break;
+ case DAYLIGHT:
+ add_schedule (new DaylightSchedule.from_parsed (parsed));
+ break;
+ default:
+ break;
+ }
+
+ save_schedules ();
+ }
+
+ public Schedule.Parsed[] list_schedules () throws DBusError, IOError {
+ Schedule.Parsed[] parsed_schedules = {};
+ foreach (var schedule in schedules.get_values ()) {
+ parsed_schedules += schedule.get_parsed ();
+ }
+
+ return parsed_schedules;
+ }
+
+ public void update_schedule (Schedule.Parsed parsed) throws DBusError, IOError {
+ if (parsed.id in schedules) {
+ schedules.remove (parsed.id);
+ }
+
+ create_schedule_internal (parsed);
+
+ save_schedules ();
+ }
+
+ public void delete_schedule (string id) throws DBusError, IOError {
+ if (!(id in schedules)) {
+ throw new IOError.NOT_FOUND ("Schedule with the same name not found");
+ }
+
+ schedules.remove (id);
+
+ save_schedules ();
+ }
+
+ private void add_schedule (Schedule schedule) {
+ schedule.notify["active"].connect (() => schedule_active_changed (schedule));
+ schedule_active_changed (schedule);
+
+ schedules[schedule.id] = schedule;
+ }
+
+ private void schedule_active_changed (Schedule schedule) {
+ if (schedule.enabled) {
+ activate_settings (schedule.active ? schedule.active_settings : schedule.inactive_settings);
+ }
+ }
+
+ private void activate_settings (HashTable settings) {
+ foreach (var key in settings.get_keys ()) {
+ switch (key) {
+ case NIGHT_LIGHT:
+ //TODO
+ break;
+ case DARK_MODE:
+ if (pantheon_service != null) {
+ pantheon_service.prefers_color_scheme = ((bool) settings[DARK_MODE]) ? Granite.Settings.ColorScheme.DARK : Granite.Settings.ColorScheme.LIGHT;
+ }
+ break;
+ case DND:
+ dnd_settings.set_boolean ("do-not-disturb", (bool) settings[DND]);
+ break;
+ case MONOCHROME:
+ monochrome_settings.set_boolean ("enable-monochrome-filter", (bool) settings[MONOCHROME]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void save_schedules () {
+ Schedule.Parsed[] parsed_schedules = {};
+ foreach (var schedule in schedules.get_values ()) {
+ parsed_schedules += schedule.get_parsed ();
+ }
+
+ settings.set_value ("schedules", parsed_schedules);
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 05f2250f..243c7531 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -8,6 +8,10 @@ sources = files(
'Backends/MouseSettings.vala',
'Backends/NightLightSettings.vala',
'Backends/PrefersColorSchemeSettings.vala',
+ 'Backends' / 'ScheduleManager' / 'DaylightSchedule.vala',
+ 'Backends' / 'ScheduleManager' / 'ManualSchedule.vala',
+ 'Backends' / 'ScheduleManager' / 'Schedule.vala',
+ 'Backends' / 'ScheduleManager' / 'ScheduleManager.vala',
'Backends/SystemUpdate.vala',
'Utils/SunriseSunsetCalculator.vala',
)