Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schedules #89

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions data/io.elementary.settings-daemon.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@
</key>
</schema>

<schema path="/io/elementary/settings-daemon/schedules/" id="io.elementary.settings-daemon.schedules">
<key type="a(sisba{sv}a{sv}a{sv})" name="schedules">
<default>[]</default>
<summary>The current schedules.</summary>
<description>a(a{sv}a{sv})</description>
</key>
</schema>

<schema path="/io/elementary/settings-daemon/system-update/" id="io.elementary.settings-daemon.system-update">
<key type="b" name="automatic-updates">
<default>false</default>
Expand Down
10 changes: 9 additions & 1 deletion src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
63 changes: 63 additions & 0 deletions src/Backends/ScheduleManager/DaylightSchedule.vala
Original file line number Diff line number Diff line change
@@ -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);
}
}
60 changes: 60 additions & 0 deletions src/Backends/ScheduleManager/ManualSchedule.vala
Original file line number Diff line number Diff line change
@@ -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<string, Variant> get_private_args () {
var result = new HashTable<string, Variant> (str_hash, str_equal);
result["from"] = from;
result["to"] = to;
return result;
}
}
67 changes: 67 additions & 0 deletions src/Backends/ScheduleManager/Schedule.vala
Original file line number Diff line number Diff line change
@@ -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<string, Variant> args;
HashTable<string, Variant> active_settings;
HashTable<string, Variant> 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<string, Variant> active_settings { get; private set; }
public HashTable<string, Variant> 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<string, Variant> (str_hash, str_equal);
inactive_settings = new HashTable<string, Variant> (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<string, Variant> get_private_args () {
return new HashTable<string, Variant> (str_hash, str_equal);
}
}
116 changes: 116 additions & 0 deletions src/Backends/ScheduleManager/ScheduleManager.vala
Original file line number Diff line number Diff line change
@@ -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<string, Schedule> schedules = new HashTable<string, Schedule> (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<string, Variant> 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);
}
}
4 changes: 4 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
Expand Down