/*
* Copyright 2014 Canonical Ltd.
* Copyright 2023-2024 Robert Tari
*
* 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; version 3.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* Authors:
* Charles Kerr
* Robert Tari
*/
#include
#include
#ifdef COLOR_TEMP_ENABLED
#include
#endif
extern "C"
{
#include
#ifdef COLOR_TEMP_ENABLED
#include
#include
#include "solar.h"
#endif
}
#ifdef COLOR_TEMP_ENABLED
#define GREETER_BUS_NAME "org.ayatana.greeter"
#define GREETER_BUS_PATH "/org/ayatana/greeter"
typedef struct
{
guint nTempLow;
guint nTempHigh;
const gchar *sName;
} TempProfile;
TempProfile m_lTempProfiles[] =
{
{0, 0, N_("Manual")},
{4500, 6500, N_("Adaptive (Colder)")},
{3627, 4913, N_("Adaptive")},
{3058, 4913, N_("Adaptive (Warmer)")},
{0, 0, NULL}
};
#endif
class DisplayIndicator::Impl
{
public:
Impl()
{
#ifdef COLOR_TEMP_ENABLED
const gchar *sTest = g_getenv ("TEST_NAME");
this->bTest = (sTest != NULL && g_str_equal (sTest, "rotation-lock-test"));
#endif
const char *sUserName = g_get_user_name();
this->bGreeter = g_str_equal (sUserName, "lightdm");
GSettingsSchemaSource *pSource = g_settings_schema_source_get_default();
if (pSource != NULL)
{
if (ayatana_common_utils_is_lomiri()) {
GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "com.lomiri.touch.system", FALSE);
if (pSchema != NULL)
{
g_settings_schema_unref(pSchema);
m_settings = g_settings_new("com.lomiri.touch.system");
}
else
{
g_error("No schema could be found");
}
}
else {
GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "org.ayatana.indicator.display", FALSE);
if (pSchema != NULL)
{
g_settings_schema_unref(pSchema);
m_settings = g_settings_new("org.ayatana.indicator.display");
}
else
{
g_error("No schema could be found");
}
#ifdef COLOR_TEMP_ENABLED
this->pAccountsServiceConnection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
if (!this->bGreeter)
{
const gchar *sSchema = NULL;
const gchar *sCursorSchema = NULL;
const gchar *sMetacitySchema = NULL;
if (this->bTest)
{
sSchema = "org.ayatana.indicator.display";
sCursorSchema = "org.ayatana.indicator.display";
sMetacitySchema = "org.ayatana.indicator.display";
}
else
{
if (ayatana_common_utils_is_mate ())
{
sSchema = "org.mate.interface";
sCursorSchema = "org.mate.peripherals-mouse";
sMetacitySchema = "org.mate.Marco.general";
}
else
{
sSchema = "org.gnome.desktop.interface";
sCursorSchema = "org.gnome.desktop.interface";
sMetacitySchema = "org.gnome.desktop.wm.preferences";
}
}
pSchema = g_settings_schema_source_lookup (pSource, sSchema, FALSE);
if (pSchema != NULL)
{
g_settings_schema_unref (pSchema);
pThemeSettings = g_settings_new (sSchema);
}
else
{
g_error("No %s schema could be found", sSchema);
}
pSchema = g_settings_schema_source_lookup (pSource, sCursorSchema, FALSE);
if (pSchema != NULL)
{
g_settings_schema_unref (pSchema);
pCursorSettings = g_settings_new (sCursorSchema);
}
else
{
g_error("No %s schema could be found", sCursorSchema);
}
pSchema = g_settings_schema_source_lookup (pSource, sMetacitySchema, FALSE);
if (pSchema != NULL)
{
g_settings_schema_unref (pSchema);
pMetacitySettings = g_settings_new (sMetacitySchema);
}
else
{
g_error("No %s schema could be found", sMetacitySchema);
}
if (this->bTest)
{
sSchema = "org.ayatana.indicator.display";
}
else
{
sSchema = "org.gnome.desktop.interface";
}
pSchema = g_settings_schema_source_lookup (pSource, sSchema, FALSE);
if (pSchema != NULL)
{
gboolean bColorScheme = g_settings_schema_has_key (pSchema, "color-scheme");
g_settings_schema_unref (pSchema);
if (bColorScheme)
{
pColorSchemeSettings = g_settings_new (sSchema);
}
else
{
g_warning ("org.gnome.desktop.interface::color-scheme not found. Native theme profile changes will not be triggered.");
}
}
else
{
g_error("No %s schema could be found", sSchema);
}
}
else
{
this->pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
this->nGreeterSubscription = g_dbus_connection_signal_subscribe (this->pConnection, NULL, GREETER_BUS_NAME, "UserChanged", GREETER_BUS_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, onUserChanged, this, NULL);
loadManager (this);
}
#endif
}
}
#ifdef COLOR_TEMP_ENABLED
if (!this->bGreeter)
{
gint nUid = geteuid ();
getAccountsService (this, nUid);
}
#endif
m_action_group = create_action_group();
// build the icon
const char *icon_name {"orientation-lock"};
if (!ayatana_common_utils_is_lomiri())
{
icon_name = "display-panel";
}
auto icon = g_themed_icon_new_with_default_fallbacks(icon_name);
auto icon_deleter = [](GIcon* o){g_object_unref(G_OBJECT(o));};
m_icon.reset(icon, icon_deleter);
// build the phone profile
auto menu_model_deleter = [](GMenuModel* o){g_object_unref(G_OBJECT(o));};
std::shared_ptr phone_menu (create_phone_menu(), menu_model_deleter);
m_phone = std::make_shared("phone", phone_menu);
update_phone_header();
// build the desktop profile
std::shared_ptr desktop_menu (create_desktop_menu(), menu_model_deleter);
m_desktop = std::make_shared("desktop", desktop_menu);
update_desktop_header();
#ifdef COLOR_TEMP_ENABLED
if (ayatana_common_utils_is_lomiri() == FALSE)
{
if (!this->bTest)
{
this->fLatitude = g_settings_get_double (this->m_settings, "latitude");
this->fLongitude = g_settings_get_double (this->m_settings, "longitude");
gclue_simple_new ("ayatana-indicator-display", GCLUE_ACCURACY_LEVEL_CITY, NULL, onGeoClueLoaded, this);
this->nCallback = g_timeout_add_seconds (60, updateColor, this);
updateColor (this);
}
}
#endif
}
~Impl()
{
#ifdef COLOR_TEMP_ENABLED
if (this->nGreeterSubscription)
{
g_dbus_connection_signal_unsubscribe (this->pConnection, this->nGreeterSubscription);
}
if (this->lUsers)
{
g_slist_free (this->lUsers);
}
if (this->pConnection)
{
g_object_unref (this->pConnection);
}
if (nCallback)
{
g_source_remove (nCallback);
}
if (sLastTheme)
{
g_free (sLastTheme);
}
if (pThemeSettings)
{
g_clear_object (&pThemeSettings);
}
if (pCursorSettings)
{
g_clear_object (&pCursorSettings);
}
if (pMetacitySettings)
{
g_clear_object (&pMetacitySettings);
}
if (pColorSchemeSettings)
{
g_clear_object (&pColorSchemeSettings);
}
if (this->pAccountsServiceConnection)
{
g_object_unref (this->pAccountsServiceConnection);
}
#endif
g_signal_handlers_disconnect_by_data(m_settings, this);
g_clear_object(&m_action_group);
g_clear_object(&m_settings);
}
GSimpleActionGroup* action_group() const
{
return m_action_group;
}
std::vector> profiles()
{
std::vector> ret;
ret.push_back(m_phone);
ret.push_back(m_desktop);
return ret;
}
private:
#ifdef COLOR_TEMP_ENABLED
static void onUserChanged (GDBusConnection *pConnection, const gchar *sSender, const gchar *sPath, const gchar *sInterface, const gchar *sSignal, GVariant *pParameters, gpointer pUserData)
{
DisplayIndicator::Impl *pImpl = static_cast(pUserData);
g_variant_get (pParameters, "(s)", &pImpl->sUser);
loadManager (pImpl);
}
static void getAccountsService (DisplayIndicator::Impl *pImpl, gint nUid)
{
pImpl->bReadingAccountsService = TRUE;
gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid);
GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL);
g_free (sPath);
if (pProxy)
{
const gchar *lProperties[] = {"brightness", "color-temp", "color-temp-profile"};
for (gint nIndex = 0; nIndex < 3; nIndex++)
{
GVariant *pParams = g_variant_new ("(ss)", "org.ayatana.indicator.display.AccountsService", lProperties[nIndex]);
GVariant *pValue = g_dbus_proxy_call_sync (pProxy, "Get", pParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
if (pValue)
{
GVariant *pChild0 = g_variant_get_child_value (pValue, 0);
g_variant_unref (pValue);
GVariant *pChild1 = g_variant_get_child_value (pChild0, 0);
g_variant_unref (pChild0);
g_settings_set_value (pImpl->m_settings, lProperties[nIndex], pChild1);
g_variant_unref (pChild1);
}
}
}
pImpl->bReadingAccountsService = FALSE;
}
static void onUserLoaded (DisplayIndicator::Impl *pImpl, ActUser *pUser)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (pUser), (gpointer) G_CALLBACK (onUserLoaded), pImpl);
if (!pImpl->sUser)
{
GError *pError = NULL;
GVariant *pGreeterUser = g_dbus_connection_call_sync (pImpl->pConnection, GREETER_BUS_NAME, GREETER_BUS_PATH, GREETER_BUS_NAME, "GetUser", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &pError);
if (pError)
{
g_debug ("Failed calling GetUser, the greeter may not be ready yet: %s", pError->message);
g_error_free (pError);
return;
}
g_variant_get (pGreeterUser, "(s)", &pImpl->sUser);
}
gboolean bPrefix = g_str_has_prefix (pImpl->sUser, "*");
if (!bPrefix)
{
const gchar *sUserName = act_user_get_user_name (pUser);
gboolean bSame = g_str_equal (pImpl->sUser, sUserName);
if (bSame)
{
gint nUid = act_user_get_uid (pUser);
getAccountsService (pImpl, nUid);
updateColor (pImpl);
}
}
}
static void onManagerLoaded (DisplayIndicator::Impl *pImpl)
{
ActUserManager *pManager = act_user_manager_get_default ();
if (!pImpl->lUsers)
{
pImpl->lUsers = act_user_manager_list_users (pManager);
}
for (GSList *lUser = pImpl->lUsers; lUser; lUser = lUser->next)
{
ActUser *pUser = static_cast(lUser->data);
gboolean bLoaded = act_user_is_loaded (pUser);
if (bLoaded)
{
onUserLoaded (pImpl, pUser);
}
else
{
g_signal_connect_swapped (pUser, "notify::is-loaded", G_CALLBACK (onUserLoaded), pImpl);
}
}
}
static void loadManager (DisplayIndicator::Impl *pImpl)
{
ActUserManager *pManager = act_user_manager_get_default ();
gboolean bLoaded = FALSE;
g_object_get (pManager, "is-loaded", &bLoaded, NULL);
if (bLoaded)
{
onManagerLoaded (pImpl);
}
else
{
g_signal_connect_swapped (pManager, "notify::is-loaded", G_CALLBACK (onManagerLoaded), pImpl);
}
}
static gboolean updateColor (gpointer pData)
{
DisplayIndicator::Impl *pImpl = static_cast(pData);
if (pImpl->bReadingAccountsService)
{
return G_SOURCE_CONTINUE;
}
guint nProfile = 0;
g_settings_get (pImpl->m_settings, "color-temp-profile", "q", &nProfile);
gdouble fBrightness = g_settings_get_double (pImpl->m_settings, "brightness");
gchar *sThemeProfile = NULL;
gboolean bThemeAdaptive = FALSE;
if (!pImpl->bGreeter)
{
sThemeProfile = g_settings_get_string (pImpl->m_settings, "theme-profile");
bThemeAdaptive = g_str_equal (sThemeProfile, "adaptive");
}
guint nTemperature = 0;
const gchar *sColorScheme = NULL;
gchar *sTheme = NULL;
gint64 nNow = g_get_real_time ();
gdouble fElevation = solar_elevation((gdouble) nNow / 1000000.0, pImpl->fLatitude, pImpl->fLongitude);
if (nProfile == 0)
{
g_settings_get (pImpl->m_settings, "color-temp", "q", &nTemperature);
}
else
{
gdouble fShifting = 0.0;
if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV)
{
fShifting = 1.0;
}
else if (fElevation < 3.0)
{
fShifting = 1.0 - ((SOLAR_CIVIL_TWILIGHT_ELEV - fElevation) / (SOLAR_CIVIL_TWILIGHT_ELEV - 3.0));
}
nTemperature = m_lTempProfiles[nProfile].nTempHigh - (m_lTempProfiles[nProfile].nTempHigh - m_lTempProfiles[nProfile].nTempLow) * fShifting;
pImpl->bAutoSliderUpdate = TRUE;
}
if (!pImpl->bGreeter)
{
if (!bThemeAdaptive)
{
gchar *sThemeKey = g_strdup_printf ("%s-theme", sThemeProfile);
sTheme = g_settings_get_string (pImpl->m_settings, sThemeKey);
g_free (sThemeKey);
gboolean bLightTheme = g_str_equal (sThemeProfile, "light");
if (bLightTheme)
{
sColorScheme = "prefer-light";
}
else
{
sColorScheme = "prefer-dark";
}
}
else
{
if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV)
{
sColorScheme = "prefer-dark";
sTheme = g_settings_get_string (pImpl->m_settings, "dark-theme");
}
else
{
sColorScheme = "prefer-light";
sTheme = g_settings_get_string (pImpl->m_settings, "light-theme");
}
}
}
if (pImpl->fLastBrightness != fBrightness || pImpl->nLasColorTemp != nTemperature)
{
g_debug ("Calling xsct with %u %f", nTemperature, fBrightness);
GAction *pAction = g_action_map_lookup_action (G_ACTION_MAP (pImpl->m_action_group), "color-temp");
GVariant *pTemperature = g_variant_new_double (nTemperature);
g_action_change_state (pAction, pTemperature);
GError *pError = NULL;
gchar *sCommand = g_strdup_printf ("xsct %u %f", nTemperature, fBrightness);
gboolean bSuccess = g_spawn_command_line_sync (sCommand, NULL, NULL, NULL, &pError);
if (!bSuccess)
{
g_error ("The call to '%s' failed: %s", sCommand, pError->message);
g_error_free (pError);
}
pImpl->fLastBrightness = fBrightness;
pImpl->nLasColorTemp = nTemperature;
g_free (sCommand);
gint nUid = 0;
if (!pImpl->bGreeter)
{
nUid = geteuid ();
}
else if (pImpl->sUser)
{
const struct passwd *pPasswd = getpwnam (pImpl->sUser);
if (pPasswd)
{
nUid = pPasswd->pw_uid;
}
}
if (nUid)
{
gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid);
GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL);
g_free (sPath);
GVariant *pBrightnessValue = g_variant_new ("d", pImpl->fLastBrightness);
GVariant *pBrightnessParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "brightness", pBrightnessValue);
g_dbus_proxy_call (pProxy, "Set", pBrightnessParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
GVariant *pColorTempValue = g_variant_new ("q", pImpl->nLasColorTemp);
GVariant *pColorTempParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp", pColorTempValue);
g_dbus_proxy_call (pProxy, "Set", pColorTempParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
GVariant *pProfileValue = g_variant_new ("q", nProfile);
GVariant *pProfileParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp-profile", pProfileValue);
g_dbus_proxy_call (pProxy, "Set", pProfileParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
}
}
if (!pImpl->bGreeter)
{
gboolean bSameColorScheme = g_str_equal (sColorScheme, pImpl->sLastColorScheme);
if (pImpl->pColorSchemeSettings && !bSameColorScheme)
{
g_debug ("Changing color scheme to %s", sColorScheme);
g_settings_set_string (pImpl->pColorSchemeSettings, "color-scheme", sColorScheme);
pImpl->sLastColorScheme = sColorScheme;
}
gboolean bSameTheme = FALSE;
if (pImpl->sLastTheme)
{
bSameTheme = g_str_equal (pImpl->sLastTheme, sTheme);
}
gboolean bCurrentTheme = g_str_equal ("current", sTheme);
if (!bSameTheme && !bCurrentTheme)
{
g_debug ("Changing theme to %s", sTheme);
g_settings_set_string (pImpl->pThemeSettings, "gtk-theme", sTheme);
gchar *sThemePath = g_strdup_printf ("/usr/share/themes/%s/index.theme", sTheme);
gboolean bThemePath = g_file_test (sThemePath, G_FILE_TEST_EXISTS);
if (bThemePath)
{
gchar *sFile = NULL;
GError *pError = NULL;
g_file_get_contents (sThemePath, &sFile, NULL, &pError);
if (!pError)
{
#if GLIB_CHECK_VERSION(2, 73, 0)
GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
#else
GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
#endif
if (!pError)
{
GMatchInfo *pMatchInfo = NULL;
#if GLIB_CHECK_VERSION(2, 73, 0)
gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
#else
gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
#endif
if (bMatch)
{
gchar *sIconTheme = g_match_info_fetch (pMatchInfo, 1);
g_settings_set_string (pImpl->pThemeSettings, "icon-theme", sIconTheme);
g_free (sIconTheme);
}
else
{
g_warning ("/usr/share/themes/%s/index.theme does not define an IconTheme", sTheme);
}
g_match_info_free (pMatchInfo);
g_regex_unref (pRegex);
}
else
{
g_error ("PANIC: Failed to compile regex: %s", pError->message);
g_error_free (pError);
}
#if GLIB_CHECK_VERSION(2, 73, 0)
pRegex = g_regex_new ("MetacityTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
#else
pRegex = g_regex_new ("MetacityTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
#endif
if (!pError)
{
GMatchInfo *pMatchInfo = NULL;
#if GLIB_CHECK_VERSION(2, 73, 0)
gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
#else
gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
#endif
if (bMatch)
{
gchar *sMetacityTheme = g_match_info_fetch (pMatchInfo, 1);
g_settings_set_string (pImpl->pMetacitySettings, "theme", sMetacityTheme);
g_free (sMetacityTheme);
}
else
{
g_warning ("/usr/share/themes/%s/index.theme does not define a MetacityTheme", sTheme);
}
g_match_info_free (pMatchInfo);
g_regex_unref (pRegex);
}
else
{
g_error ("PANIC: Failed to compile regex: %s", pError->message);
g_error_free (pError);
}
#if GLIB_CHECK_VERSION(2, 73, 0)
pRegex = g_regex_new ("CursorTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
#else
pRegex = g_regex_new ("CursorTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
#endif
if (!pError)
{
GMatchInfo *pMatchInfo = NULL;
#if GLIB_CHECK_VERSION(2, 73, 0)
gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
#else
gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
#endif
if (bMatch)
{
gchar *sCursorTheme = g_match_info_fetch (pMatchInfo, 1);
g_settings_set_string (pImpl->pCursorSettings, "cursor-theme", sTheme);
g_free (sCursorTheme);
}
else
{
g_warning ("/usr/share/themes/%s/index.theme does not define a CursorTheme", sTheme);
}
g_match_info_free (pMatchInfo);
g_regex_unref (pRegex);
}
else
{
g_error ("PANIC: Failed to compile regex: %s", pError->message);
g_error_free (pError);
}
g_free (sFile);
}
else
{
g_error ("PANIC: Failed to get index.theme contents: %s", pError->message);
g_error_free (pError);
}
}
else
{
g_warning ("/usr/share/themes/%s/index.theme does not exist", sTheme);
}
g_free (sThemePath);
if (pImpl->sLastTheme)
{
g_free (pImpl->sLastTheme);
}
pImpl->sLastTheme = g_strdup (sTheme);
}
g_free (sTheme);
g_free (sThemeProfile);
}
return G_SOURCE_CONTINUE;
}
static void onGeoClueLoaded (GObject *pObject, GAsyncResult *pResult, gpointer pData)
{
DisplayIndicator::Impl *pImpl = static_cast(pData);
GError *pError = NULL;
GClueSimple *pSimple = gclue_simple_new_finish (pResult, &pError);
if (pError != NULL)
{
g_warning ("Failed to connect to GeoClue2 service: %s", pError->message);
}
else
{
GClueLocation *pLocation = gclue_simple_get_location (pSimple);
pImpl->fLatitude = gclue_location_get_latitude (pLocation);
pImpl->fLongitude = gclue_location_get_longitude (pLocation);
g_settings_set_double (pImpl->m_settings, "latitude", pImpl->fLatitude);
g_settings_set_double (pImpl->m_settings, "longitude", pImpl->fLongitude);
}
updateColor (pImpl);
}
static void onColorTempSettings (GSettings *pSettings, const gchar *sKey, gpointer pData)
{
GVariant *pProfile = g_variant_new_uint16 (0);
g_settings_set_value (pSettings, "color-temp-profile", pProfile);
updateColor (pData);
}
static gboolean settingsIntToActionStateString (GValue *pValue, GVariant *pVariant, gpointer pData)
{
guint16 nVariant = g_variant_get_uint16 (pVariant);
gchar *sVariant = g_strdup_printf ("%u", nVariant);
GVariant *pVariantString = g_variant_new_string (sVariant);
g_free (sVariant);
g_value_set_variant (pValue, pVariantString);
return TRUE;
}
static GVariant* actionStateStringToSettingsInt (const GValue *pValue, const GVariantType *pVariantType, gpointer pData)
{
GVariant *pVariantString = g_value_get_variant (pValue);
const gchar *sValue = g_variant_get_string (pVariantString, NULL);
guint16 nValue = (guint16) g_ascii_strtoull (sValue, NULL, 10);
GVariant *pVariantInt = g_variant_new_uint16 (nValue);
GValue cValue = G_VALUE_INIT;
g_value_init (&cValue, G_TYPE_VARIANT);
g_value_set_variant (&cValue, pVariantInt);
return g_value_dup_variant (&cValue);
}
static void onColorTempState (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
{
g_simple_action_set_state (pAction, pVariant);
DisplayIndicator::Impl *pImpl = static_cast(pData);
if (pImpl->bAutoSliderUpdate)
{
pImpl->bAutoSliderUpdate = FALSE;
return;
}
GVariant *pProfile = g_variant_new_uint16 (0);
g_settings_set_value (pImpl->m_settings, "color-temp-profile", pProfile);
guint16 nTemperature = (guint16) g_variant_get_double (pVariant);
GVariant *pTemperature = g_variant_new_uint16 (nTemperature);
g_settings_set_value (pImpl->m_settings, "color-temp", pTemperature);
}
#endif
/***
**** Actions
***/
static gboolean settings_to_action_state(GValue *value,
GVariant *variant,
gpointer /*unused*/)
{
g_value_set_variant(value, variant);
return TRUE;
}
static GVariant* action_state_to_settings(const GValue *value,
const GVariantType * /*expected_type*/,
gpointer /*unused*/)
{
return g_value_dup_variant(value);
}
GSimpleActionGroup* create_action_group()
{
GSimpleActionGroup* group;
GSimpleAction* action;
group = g_simple_action_group_new();
GVariantType *pVariantType = g_variant_type_new("b");
action = g_simple_action_new_stateful("rotation-lock",
pVariantType,
g_variant_new_boolean(false));
g_variant_type_free(pVariantType);
g_settings_bind_with_mapping(m_settings, "rotation-lock",
action, "state",
G_SETTINGS_BIND_DEFAULT,
settings_to_action_state,
action_state_to_settings,
nullptr,
nullptr);
g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
g_object_unref(G_OBJECT(action));
g_signal_connect_swapped(m_settings, "changed::rotation-lock",
G_CALLBACK(on_rotation_lock_setting_changed), this);
#ifdef COLOR_TEMP_ENABLED
if (ayatana_common_utils_is_lomiri() == FALSE)
{
pVariantType = g_variant_type_new ("d");
guint nTemperature = 0;
g_settings_get (this->m_settings, "color-temp", "q", &nTemperature);
action = g_simple_action_new_stateful ("color-temp", pVariantType, g_variant_new_double (nTemperature));
g_variant_type_free (pVariantType);
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_signal_connect (m_settings, "changed::color-temp", G_CALLBACK (onColorTempSettings), this);
g_signal_connect (action, "change-state", G_CALLBACK (onColorTempState), this);
g_object_unref(G_OBJECT (action));
pVariantType = g_variant_type_new ("s");
guint nProfile = 0;
g_settings_get (this->m_settings, "color-temp-profile", "q", &nProfile);
gchar *sProfile = g_strdup_printf ("%i", nProfile);
action = g_simple_action_new_stateful ("profile", pVariantType, g_variant_new_string (sProfile));
g_free (sProfile);
g_variant_type_free (pVariantType);
g_settings_bind_with_mapping (this->m_settings, "color-temp-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settingsIntToActionStateString, actionStateStringToSettingsInt, NULL, NULL);
g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
g_object_unref(G_OBJECT(action));
g_signal_connect_swapped (m_settings, "changed::color-temp-profile", G_CALLBACK (updateColor), this);
pVariantType = g_variant_type_new("d");
gdouble fBrightness = g_settings_get_double (this->m_settings, "brightness");
action = g_simple_action_new_stateful ("brightness", pVariantType, g_variant_new_double (fBrightness));
g_variant_type_free(pVariantType);
g_settings_bind_with_mapping (m_settings, "brightness", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL);
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (G_OBJECT (action));
g_signal_connect_swapped (m_settings, "changed::brightness", G_CALLBACK (updateColor), this);
if (!this->bGreeter)
{
pVariantType = g_variant_type_new ("s");
action = g_simple_action_new_stateful ("theme", pVariantType, g_variant_new_string ("light"));
g_variant_type_free (pVariantType);
g_settings_bind_with_mapping (this->m_settings, "theme-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL);
g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
g_object_unref(G_OBJECT(action));
g_signal_connect_swapped (m_settings, "changed::theme-profile", G_CALLBACK (updateColor), this);
g_signal_connect_swapped (m_settings, "changed::light-theme", G_CALLBACK (updateColor), this);
g_signal_connect_swapped (m_settings, "changed::dark-theme", G_CALLBACK (updateColor), this);
}
}
#endif
if (!this->bGreeter)
{
action = g_simple_action_new ("settings", NULL);
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_signal_connect (action, "activate", G_CALLBACK (onSettings), this);
g_object_unref (G_OBJECT (action));
}
return group;
}
/***
**** Phone profile
***/
static void on_rotation_lock_setting_changed (gpointer gself)
{
static_cast(gself)->update_phone_header();
}
GMenuModel* create_phone_menu()
{
GMenu* menu;
GMenu* section;
GMenuItem* menu_item;
menu = g_menu_new();
section = g_menu_new();
menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)");
g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
g_menu_append_item(section, menu_item);
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
g_object_unref(section);
g_object_unref(menu_item);
return G_MENU_MODEL(menu);
}
static void onSettings (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
{
if (ayatana_common_utils_is_mate ())
{
ayatana_common_utils_execute_command ("mate-display-properties");
}
else if (ayatana_common_utils_is_xfce ())
{
ayatana_common_utils_execute_command ("xfce4-display-settings");
}
else
{
ayatana_common_utils_execute_command ("gnome-control-center display");
}
}
GMenuModel* create_desktop_menu()
{
GMenu* menu;
GMenu* section;
GMenuItem* menu_item;
menu = g_menu_new();
if (ayatana_common_utils_is_lomiri())
{
section = g_menu_new();
menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)");
g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
g_menu_append_item(section, menu_item);
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
g_object_unref(section);
g_object_unref(menu_item);
}
else
{
#ifdef COLOR_TEMP_ENABLED
section = g_menu_new ();
GIcon *pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-low");
GIcon *pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-high");
GVariant *pIconMinSerialised = g_icon_serialize (pIconMin);
GVariant *pIconMaxSerialised = g_icon_serialize (pIconMax);
menu_item = g_menu_item_new (_("Brightness"), "indicator.brightness");
g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised);
g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised);
g_menu_item_set_attribute (menu_item, "min-value", "d", 0.5);
g_menu_item_set_attribute (menu_item, "max-value", "d", 1.0);
g_menu_item_set_attribute (menu_item, "step", "d", 0.01);
g_menu_item_set_attribute (menu_item, "digits", "y", 2);
g_menu_append_item (section, menu_item);
pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-on");
pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-off");
pIconMinSerialised = g_icon_serialize (pIconMin);
pIconMaxSerialised = g_icon_serialize (pIconMax);
menu_item = g_menu_item_new (_("Color temperature"), "indicator.color-temp");
g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised);
g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised);
g_menu_item_set_attribute (menu_item, "min-value", "d", 3000.0);
g_menu_item_set_attribute (menu_item, "max-value", "d", 6500.0);
g_menu_item_set_attribute (menu_item, "step", "d", 100.0);
g_menu_item_set_attribute (menu_item, "digits", "y", 0);
g_menu_append_item (section, menu_item);
GMenu *pMenuProfiles = g_menu_new ();
GMenuItem *pItemProfiles = g_menu_item_new_submenu (_("Color temperature profile"), G_MENU_MODEL (pMenuProfiles));
guint nProfile = 0;
while (m_lTempProfiles[nProfile].sName != NULL)
{
gchar *sAction = g_strdup_printf ("indicator.profile::%u", nProfile);
gchar *sName = gettext (m_lTempProfiles[nProfile].sName);
GMenuItem *pItemProfile = g_menu_item_new (sName, sAction);
g_free(sAction);
g_menu_append_item (pMenuProfiles, pItemProfile);
g_object_unref (pItemProfile);
nProfile++;
}
g_menu_append_item (section, pItemProfiles);
g_object_unref (pItemProfiles);
g_object_unref (pMenuProfiles);
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (pIconMin);
g_object_unref (pIconMax);
g_variant_unref (pIconMinSerialised);
g_variant_unref (pIconMaxSerialised);
g_object_unref (section);
g_object_unref (menu_item);
if (!this->bGreeter)
{
section = g_menu_new ();
pMenuProfiles = g_menu_new ();
pItemProfiles = g_menu_item_new_submenu (_("Theme profile"), G_MENU_MODEL (pMenuProfiles));
GMenuItem *pItemProfile = g_menu_item_new (_("Light"), "indicator.theme::light");
g_menu_append_item (pMenuProfiles, pItemProfile);
g_object_unref (pItemProfile);
pItemProfile = g_menu_item_new (_("Dark"), "indicator.theme::dark");
g_menu_append_item (pMenuProfiles, pItemProfile);
g_object_unref (pItemProfile);
pItemProfile = g_menu_item_new (_("Adaptive"), "indicator.theme::adaptive");
g_menu_append_item (pMenuProfiles, pItemProfile);
g_object_unref (pItemProfile);
g_menu_append_item (section, pItemProfiles);
g_object_unref (pItemProfiles);
g_object_unref (pMenuProfiles);
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
}
#endif
// cppcheck fails on !this->bGreeter
if (this->bGreeter == FALSE)
{
section = g_menu_new ();
menu_item = g_menu_item_new (_("Display settingsā¦"), "indicator.settings");
g_menu_append_item (section, menu_item);
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
g_object_unref (menu_item);
}
}
return G_MENU_MODEL(menu);
}
void update_phone_header()
{
Header h;
h.title = _("Rotation");
h.tooltip = h.title;
h.a11y = h.title;
h.is_visible = g_settings_get_boolean(m_settings, "rotation-lock");
h.icon = m_icon;
m_phone->header().set(h);
}
void update_desktop_header()
{
Header h;
h.title = _("Display");
h.tooltip = _("Display settings and features");
h.a11y = h.title;
h.is_visible = TRUE;
h.icon = m_icon;
m_desktop->header().set(h);
}
/***
****
***/
GSettings* m_settings = nullptr;
GSimpleActionGroup* m_action_group = nullptr;
std::shared_ptr m_phone;
std::shared_ptr m_desktop;
std::shared_ptr m_icon;
gboolean bGreeter;
#ifdef COLOR_TEMP_ENABLED
gdouble fLatitude = 0.0;
gdouble fLongitude = 0.0;
gboolean bAutoSliderUpdate = FALSE;
guint nCallback = 0;
gdouble fLastBrightness = 0.0;
guint nLasColorTemp = 0;
gchar *sLastTheme = NULL;
const gchar *sLastColorScheme = "default";
GSettings *pThemeSettings = NULL;
GSettings *pCursorSettings = NULL;
GSettings *pMetacitySettings = NULL;
GSettings *pColorSchemeSettings = NULL;
gboolean bTest;
guint nGreeterSubscription = 0;
GDBusConnection *pConnection = NULL;
gchar *sUser = NULL;
GSList *lUsers = NULL;
gboolean bReadingAccountsService = FALSE;
GDBusConnection *pAccountsServiceConnection = NULL;
#endif
};
/***
****
***/
DisplayIndicator::DisplayIndicator():
impl(new Impl())
{
}
DisplayIndicator::~DisplayIndicator()
{
}
std::vector>
DisplayIndicator::profiles() const
{
return impl->profiles();
}
GSimpleActionGroup*
DisplayIndicator::action_group() const
{
return impl->action_group();
}
const char*
DisplayIndicator::name() const
{
return "display";
}
/***
****
***/