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

Schedule dark mode #7

Merged
merged 25 commits into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1055c80
Add schema to schedule dark mode
Aug 17, 2020
e27ef04
Add ColorSettings
Aug 18, 2020
a7659a4
Move schema to separate file
Aug 18, 2020
078e530
Rename backend
Aug 18, 2020
d1ff1ff
Rename schema path
meisenzahl Aug 19, 2020
6de1d9c
Rename schema path
meisenzahl Aug 19, 2020
0cd69f7
Port implementation to calculate sunrise and sunset from gnome-settin…
meisenzahl Aug 20, 2020
3b2acbc
Fix date time
meisenzahl Aug 20, 2020
ee7d625
Update formulas
meisenzahl Aug 20, 2020
5bdcb3e
Set from and to based on sunset and sunrise
meisenzahl Aug 20, 2020
364dbd9
Read location from libgeoclue
meisenzahl Aug 20, 2020
a14f223
Fixed inverted logic
meisenzahl Aug 20, 2020
bdabec8
Remove duplicate date time
meisenzahl Aug 20, 2020
e24c135
Merge branch 'master' into schedule-dark-mode
meisenzahl Aug 22, 2020
d62ed3f
Satisfy linter
meisenzahl Aug 22, 2020
34f8d2e
Add missing dependencies
meisenzahl Aug 22, 2020
d86c733
Revert changes
meisenzahl Aug 22, 2020
ab52aa9
Add link to specific commit
meisenzahl Aug 25, 2020
d77befa
Drop enum for simple boolean
meisenzahl Aug 27, 2020
ad5304a
Optimize logic to use location and timer
meisenzahl Aug 27, 2020
45ef1d5
Apply feedback
meisenzahl Aug 28, 2020
2a0cfff
Save and load last coordinates
meisenzahl Aug 28, 2020
eeb5722
Only calculate sunrise and sunset on last coordinates once
meisenzahl Aug 28, 2020
c989baa
Add a comment for fallback times
meisenzahl Sep 26, 2020
4f0b93f
Only read last coordinates if schedule is set to sunset-to-sunrise
meisenzahl Sep 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Install Dependencies
run: |
apt update
apt install -y libaccountsservice-dev libdbus-1-dev meson valac
apt install -y libaccountsservice-dev libdbus-1-dev libgranite-dev libgeoclue-2-dev meson valac
- name: Build
env:
DESTDIR: out
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ You'll need the following dependencies:
* glib-2.0
* gobject-2.0
* libaccountsservice-dev
* libdbus-1-dev
* libdbus-1-dev
* libgranite-dev
* libgeoclue-2-dev
* meson
* valac

Expand Down
3 changes: 1 addition & 2 deletions data/io.elementary.SettingsDaemon.AccountsService.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@
</property>

</interface>
</node>

</node>
26 changes: 26 additions & 0 deletions data/io.elementary.settings-daemon.gschema.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<enum id="PreferDarkScheduleType">
<value nick='disabled' value='0'/>
<value nick='sunset-to-sunrise' value='1'/>
<value nick='manual' value='2'/>
</enum>

<schema path="/io/elementary/settings-daemon/prefers-color-scheme/" id="io.elementary.settings-daemon.prefers-color-scheme">
<key enum="PreferDarkScheduleType" name="prefer-dark-schedule">
<default>'disabled'</default>
<summary>Algorithm for prefer dark schedule</summary>
<description>Choose the algorithm used for prefer dark schedule.</description>
</key>
<key type="d" name="prefer-dark-schedule-from">
<default>20.0</default>
<summary>The start time</summary>
<description></description>
</key>
<key type="d" name="prefer-dark-schedule-to">
<default>6.0</default>
<summary>The stop time</summary>
<description></description>
</key>
</schema>
</schemalist>
5 changes: 5 additions & 0 deletions data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ meson.add_install_script(
join_paths(dbus_interfaces_dir, 'io.elementary.SettingsDaemon.AccountsService.xml'),
join_paths(act_interfacesdir, 'io.elementary.SettingsDaemon.AccountsService.xml'),
)

install_data(
'io.elementary.settings-daemon.gschema.xml',
install_dir: join_paths(datadir, 'glib-2.0', 'schemas')
)
8 changes: 8 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ project('io.elementary.settings-daemon',
license: 'GPL3',
)

granite_dep = dependency('granite', version: '>= 5.3.0')

cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required : false)
libgeoclue_dep = dependency ('libgeoclue-2.0')

prefix = get_option('prefix')
datadir = join_paths(prefix, get_option('datadir'))

symlink = join_paths(meson.current_source_dir (), 'meson', 'create-symlink.sh')

subdir('data')
subdir('src')

meson.add_install_script('meson/post_install.py')
19 changes: 19 additions & 0 deletions meson/post_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3

import os
import subprocess

prefix = os.environ.get('MESON_INSTALL_PREFIX', '/usr/local')
datadir = os.path.join(prefix, 'share')

# Packaging tools define DESTDIR and this isn't needed for them
if 'DESTDIR' not in os.environ:
print('Compiling gsettings schemas...')
subprocess.call(['glib-compile-schemas', os.path.join(datadir, 'glib-2.0', 'schemas')])

print('Updating icon cache...')
subprocess.call(['gtk-update-icon-cache', '-qtf', os.path.join(datadir, 'icons', 'hicolor')])

print('Updating desktop database...')
subprocess.call(['update-desktop-database', '-q', os.path.join(datadir, 'applications')])

5 changes: 5 additions & 0 deletions src/AccountsService.vala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public interface SettingsDaemon.AccountsService : Object {
public abstract uint active_keyboard_layout { get; set; }
}

[DBus (name = "io.elementary.pantheon.AccountsService")]
public interface PantheonShell.Pantheon.AccountsService : Object {
public abstract int prefers_color_scheme { get; set; }
}

[DBus (name = "org.freedesktop.Accounts")]
public interface SettingsDaemon.FDO.Accounts : Object {
public abstract string find_user_by_name (string username) throws GLib.Error;
Expand Down
22 changes: 22 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ public class SettingsDaemon.Application : GLib.Application {

private AccountsService? accounts_service;

private PantheonShell.Pantheon.AccountsService pantheon_accounts_service;

private Backends.KeyboardSettings keyboard_settings;

private Backends.PrefersColorSchemeSettings prefers_color_scheme_settings;

construct {
application_id = Build.PROJECT_NAME;

Expand Down Expand Up @@ -84,6 +88,24 @@ public class SettingsDaemon.Application : GLib.Application {
if (accounts_service != null) {
keyboard_settings = new Backends.KeyboardSettings (accounts_service);
}

try {
var act_service = yield GLib.Bus.get_proxy<FDO.Accounts> (GLib.BusType.SYSTEM,
"org.freedesktop.Accounts",
"/org/freedesktop/Accounts");
var user_path = act_service.find_user_by_name (GLib.Environment.get_user_name ());

pantheon_accounts_service = yield GLib.Bus.get_proxy (GLib.BusType.SYSTEM,
"org.freedesktop.Accounts",
user_path,
GLib.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
} catch (Error e) {
warning ("Unable to get AccountsService proxy, color scheme preference may be incorrect");
}

if (pantheon_accounts_service != null) {
prefers_color_scheme_settings = new Backends.PrefersColorSchemeSettings (pantheon_accounts_service);
}
}

void end_session (bool quit) {
Expand Down
120 changes: 120 additions & 0 deletions src/Backends/PrefersColorSchemeSettings.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2020 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Marius Meisenzahl <[email protected]>
*/

public class SettingsDaemon.Backends.PrefersColorSchemeSettings : GLib.Object {
public unowned PantheonShell.Pantheon.AccountsService accounts_service { get; construct; }

private GLib.Settings color_settings;
private double pos_lat = -1.0;
private double pos_long = -1.0;

public PrefersColorSchemeSettings (PantheonShell.Pantheon.AccountsService accounts_service) {
Object (accounts_service: accounts_service);
}

construct {
color_settings = new GLib.Settings ("io.elementary.settings-daemon.prefers-color-scheme");

get_location.begin ();
meisenzahl marked this conversation as resolved.
Show resolved Hide resolved

var time = new TimeoutSource (1000);

time.set_callback (() => {
var schedule = color_settings.get_string ("prefer-dark-schedule");

var now = new DateTime.now_local ();
double from, to;
if (schedule == "sunset-to-sunrise") {
double sunrise, sunset;

bool success = SettingsDaemon.Utils.SunriseSunsetCalculator.get_sunrise_and_sunset (now, pos_lat, pos_long, out sunrise, out sunset);

if (success) {
from = sunset;
to = sunrise;
}
} else if (schedule == "manual") {
from = color_settings.get_double ("prefer-dark-schedule-from");
to = color_settings.get_double ("prefer-dark-schedule-to");
} else {
return true;
}

var state = get_state (date_time_double (now), from, to);
var new_color_scheme = Granite.Settings.ColorScheme.NO_PREFERENCE;
if (state == State.IN) {
new_color_scheme = Granite.Settings.ColorScheme.DARK;
}

if (new_color_scheme == accounts_service.prefers_color_scheme) {
return true;
}

accounts_service.prefers_color_scheme = new_color_scheme;

return true;
});

time.attach (null);
}

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) {
meisenzahl marked this conversation as resolved.
Show resolved Hide resolved
pos_lat = latitude;
pos_long = longitude;
}

private enum State {
danirabbit marked this conversation as resolved.
Show resolved Hide resolved
UNKNOWN,
IN,
OUT
}

private State get_state (double time_double, double from, double to) {
meisenzahl marked this conversation as resolved.
Show resolved Hide resolved
if (from >= 0.0 && time_double >= from || time_double >= 0.0 && time_double < to) {
return State.IN;
}

return State.OUT;
}

private double date_time_double (DateTime date_time) {
danirabbit marked this conversation as resolved.
Show resolved Hide resolved
double time_double = 0;
time_double += date_time.get_hour ();
time_double += (double) date_time.get_minute () / 60;

return time_double;
}
}
99 changes: 99 additions & 0 deletions src/Utils/SunriseSunsetCalculator.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2020 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Marius Meisenzahl <[email protected]>
*/

/*
* The following code was ported from gnome-settings-daemon
* https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/02295dee94bebcdd246c40ccf0120eb2ab714416/plugins/color/gsd-night-light-common.c
*/

public class SettingsDaemon.Utils.SunriseSunsetCalculator {
public static bool get_sunrise_and_sunset (DateTime dt, double pos_lat, double pos_long, out double sunrise, out double sunset) {
sunrise = -1.0;
sunset = -1.0;

var dt_zero = new DateTime.utc (1900, 1, 1, 0, 0, 0);
var ts = dt.difference (dt_zero);

const int _G_USEC_PER_SEC = 1000000;

if (!(pos_lat <= 90.0f && pos_lat >= -90.0f)) {
return false;
}

if (!(pos_long <= 180.0f && pos_long >= -180.0f)) {
return false;
}

double tz_offset = (double) dt.get_utc_offset () / _G_USEC_PER_SEC / 60 / 60; // B5
double date_as_number = ts / _G_USEC_PER_SEC / 24 / 60 / 60 + 2; // B7
double time_past_local_midnight = 0; // E2, unused in this calculation
double julian_day = date_as_number + 2415018.5 +
time_past_local_midnight - tz_offset / 24;
double julian_century = (julian_day - 2451545) / 36525;
double geom_mean_long_sun = Math.fmod (280.46646 + julian_century *
(36000.76983 + julian_century * 0.0003032), 360);
double geom_mean_anom_sun = 357.52911 + julian_century *
(35999.05029 - 0.0001537 * julian_century); // J2
double eccent_earth_orbit = 0.016708634 - julian_century *
(0.000042037 + 0.0000001267 * julian_century); // K2
double sun_eq_of_ctr = Math.sin (deg2rad (geom_mean_anom_sun)) *
(1.914602 - julian_century * (0.004817 + 0.000014 * julian_century)) +
Math.sin (deg2rad (2 * geom_mean_anom_sun)) * (0.019993 - 0.000101 * julian_century) +
Math.sin (deg2rad (3 * geom_mean_anom_sun)) * 0.000289; // L2
double sun_true_long = geom_mean_long_sun + sun_eq_of_ctr; // M2
double sun_app_long = sun_true_long - 0.00569 - 0.00478 *
Math.sin (deg2rad (125.04 - 1934.136 * julian_century)); // P2
double mean_obliq_ecliptic = 23 + (26 + ((21.448 - julian_century *
(46.815 + julian_century * (0.00059 - julian_century * 0.001813)))) / 60) / 60; // Q2
double obliq_corr = mean_obliq_ecliptic + 0.00256 *
Math.cos (deg2rad (125.04 - 1934.136 * julian_century)); // R2
double sun_declin = rad2deg (Math.asin (Math.sin (deg2rad (obliq_corr)) *
Math.sin (deg2rad (sun_app_long)))); // T2
double var_y = Math.tan (deg2rad (obliq_corr / 2)) * Math.tan (deg2rad (obliq_corr / 2)); // U2
double eq_of_time = 4 * rad2deg (var_y * Math.sin (2 * deg2rad (geom_mean_long_sun)) -
2 * eccent_earth_orbit * Math.sin (deg2rad (geom_mean_anom_sun)) +
4 * eccent_earth_orbit * var_y *
Math.sin (deg2rad (geom_mean_anom_sun)) *
Math.cos (2 * deg2rad (geom_mean_long_sun)) -
0.5 * var_y * var_y * Math.sin (4 * deg2rad (geom_mean_long_sun)) -
1.25 * eccent_earth_orbit * eccent_earth_orbit *
Math.sin (2 * deg2rad (geom_mean_anom_sun))); // V2
double ha_sunrise = rad2deg (Math.acos (Math.cos (deg2rad (90.833)) / (Math.cos (deg2rad (pos_lat)) *
Math.cos (deg2rad (sun_declin))) - Math.tan (deg2rad (pos_lat)) *
Math.tan (deg2rad (sun_declin)))); // W2
double solar_noon = (720 - 4 * pos_long - eq_of_time + tz_offset * 60) / 1440; // X2
double sunrise_time = solar_noon - ha_sunrise * 4 / 1440; // Y2
double sunset_time = solar_noon + ha_sunrise * 4 / 1440; // Z2

sunrise = sunrise_time * 24;
sunset = sunset_time * 24;

return true;
}

private static double deg2rad (double degrees) {
return (Math.PI * degrees) / 180.0f;
}

private static double rad2deg (double radians) {
return radians * (180.0f / Math.PI);
}
}
7 changes: 6 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ sources = files(
'Application.vala',
'SessionManager.vala',
'Backends/KeyboardSettings.vala',
'Backends/PrefersColorSchemeSettings.vala',
'Utils/SunriseSunsetCalculator.vala',
)

executable(
meson.project_name(),
sources,
config_file,
dependencies: [
dependency ('gio-2.0')
dependency ('gio-2.0'),
granite_dep,
m_dep,
libgeoclue_dep
],
install: true,
)