Add support for turning on/off upower at runtime

This will allow DE environments to decide whether they want to allow
actions and profiles to dynamically change from the battery level or
AC adapter presence.
This commit is contained in:
Mario Limonciello 2024-09-13 15:03:37 -05:00
parent 30ea6f6610
commit d08683ecf1
7 changed files with 206 additions and 27 deletions

View file

@ -70,6 +70,25 @@ Actions can be enabled or disabled by running:
powerprofilesctl configure-action <action> --disable powerprofilesctl configure-action <action> --disable
``` ```
## Dynamic profile and action changes
Some actions and profiles have the ability to respond to changes to the battery
level and state of the AC adapter on the system. These change events are triggered
by upower.
If you would prefer not let any of these dynamic changes happen you can disable
upower support by running:
```sh
powerprofilesctl configure-battery-aware --disable
```
If disabled, it can be re-renabled using:
```sh
powerprofilesctl configure-battery-aware --enable
```
## Conflicts ## Conflicts
If `power-profiles-daemon` refuses to start, it's likely that you have [a conflicting If `power-profiles-daemon` refuses to start, it's likely that you have [a conflicting

View file

@ -38,4 +38,14 @@
</defaults> </defaults>
</action> </action>
<action id="org.freedesktop.UPower.PowerProfiles.configure-battery-aware">
<description>Configure action</description>
<message>Privileges are required to configure battery-awareness.</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>
</policyconfig> </policyconfig>

View file

@ -166,5 +166,13 @@
--> -->
<property name="Version" type="s" access="read"/> <property name="Version" type="s" access="read"/>
<!--
BatteryAware:
Whether the daemon is using upower to detect battery and AC
adapter changes.
-->
<property name="BatteryAware" type="b" access="readwrite"/>
</interface> </interface>
</node> </node>

View file

@ -86,6 +86,7 @@ typedef struct {
GPtrArray *actions; GPtrArray *actions;
GHashTable *profile_holds; GHashTable *profile_holds;
gboolean battery_support;
GDBusProxy *upower_proxy; GDBusProxy *upower_proxy;
GDBusProxy *upower_display_proxy; GDBusProxy *upower_display_proxy;
gulong upower_watch_id; gulong upower_watch_id;
@ -170,6 +171,7 @@ typedef enum {
PROP_DEGRADED = 1 << 4, PROP_DEGRADED = 1 << 4,
PROP_ACTIVE_PROFILE_HOLDS = 1 << 5, PROP_ACTIVE_PROFILE_HOLDS = 1 << 5,
PROP_VERSION = 1 << 6, PROP_VERSION = 1 << 6,
PROP_UPOWER = 1 << 7,
} PropertiesMask; } PropertiesMask;
#define PROP_ALL (PROP_ACTIVE_PROFILE | \ #define PROP_ALL (PROP_ACTIVE_PROFILE | \
@ -178,7 +180,8 @@ typedef enum {
PROP_ACTIONS | \ PROP_ACTIONS | \
PROP_DEGRADED | \ PROP_DEGRADED | \
PROP_ACTIVE_PROFILE_HOLDS | \ PROP_ACTIVE_PROFILE_HOLDS | \
PROP_VERSION) PROP_VERSION | \
PROP_UPOWER)
static gboolean static gboolean
driver_profile_support (PpdDriver *driver, driver_profile_support (PpdDriver *driver,
@ -287,6 +290,7 @@ get_legacy_actions_variant (PpdApp *data)
for (i = 0; i < data->actions->len; i++) { for (i = 0; i < data->actions->len; i++) {
PpdAction *action = g_ptr_array_index (data->actions, i); PpdAction *action = g_ptr_array_index (data->actions, i);
if (!ppd_action_get_active (action)) if (!ppd_action_get_active (action))
continue; continue;
@ -310,11 +314,11 @@ get_modern_actions_variant (PpdApp *data)
g_variant_builder_init (&asv_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_init (&asv_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&asv_builder, "{sv}", "Name", g_variant_builder_add (&asv_builder, "{sv}", "Name",
g_variant_new_string (ppd_action_get_action_name (action))); g_variant_new_string (ppd_action_get_action_name (action)));
g_variant_builder_add (&asv_builder, "{sv}", "Description", g_variant_builder_add (&asv_builder, "{sv}", "Description",
g_variant_new_string (ppd_action_get_action_description (action))); g_variant_new_string (ppd_action_get_action_description (action)));
g_variant_builder_add (&asv_builder, "{sv}", "Enabled", g_variant_builder_add (&asv_builder, "{sv}", "Enabled",
g_variant_new_boolean (ppd_action_get_active (action))); g_variant_new_boolean (ppd_action_get_active (action)));
g_variant_builder_add (&builder, "a{sv}", &asv_builder); g_variant_builder_add (&builder, "a{sv}", &asv_builder);
} }
@ -400,6 +404,10 @@ send_dbus_event_iface (PpdApp *data,
g_variant_builder_add (&props_builder, "{sv}", "Version", g_variant_builder_add (&props_builder, "{sv}", "Version",
g_variant_new_string (VERSION)); g_variant_new_string (VERSION));
} }
if (mask & PROP_UPOWER) {
g_variant_builder_add (&props_builder, "{sv}", "BatteryAware",
g_variant_new_boolean (data->battery_support));
}
props_changed = g_variant_new ("(s@a{sv}@as)", iface, props_changed = g_variant_new ("(s@a{sv}@as)", iface,
g_variant_builder_end (&props_builder), g_variant_builder_end (&props_builder),
@ -450,6 +458,8 @@ save_configuration (PpdApp *data)
ppd_action_get_active (action)); ppd_action_get_active (action));
} }
g_key_file_set_boolean (data->config, "State", "battery_aware", data->battery_support);
if (!g_key_file_save_to_file (data->config, data->config_path, &error)) if (!g_key_file_save_to_file (data->config, data->config_path, &error))
g_warning ("Could not save configuration file '%s': %s", data->config_path, error->message); g_warning ("Could not save configuration file '%s': %s", data->config_path, error->message);
} }
@ -460,8 +470,17 @@ apply_configuration (PpdApp *data)
g_autofree char *platform_driver = NULL; g_autofree char *platform_driver = NULL;
g_autofree char *profile_str = NULL; g_autofree char *profile_str = NULL;
g_autofree char *cpu_driver = NULL; g_autofree char *cpu_driver = NULL;
g_autoptr(GError) error = NULL;
PpdProfile profile; PpdProfile profile;
data->battery_support = g_key_file_get_boolean (data->config, "State", "battery_aware", &error);
if (error != NULL) {
g_debug("battery_aware key not found: %s, defaulting to TRUE", error->message);
data->battery_support = TRUE;
g_key_file_set_boolean (data->config, "State", "battery_aware", TRUE);
g_clear_error (&error);
}
cpu_driver = g_key_file_get_string (data->config, "State", "CpuDriver", NULL); cpu_driver = g_key_file_get_string (data->config, "State", "CpuDriver", NULL);
if (PPD_IS_DRIVER_CPU (data->cpu_driver) && if (PPD_IS_DRIVER_CPU (data->cpu_driver) &&
g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), cpu_driver) != 0) g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), cpu_driver) != 0)
@ -622,6 +641,24 @@ release_all_profile_holds (PpdApp *data)
g_hash_table_remove_all (data->profile_holds); g_hash_table_remove_all (data->profile_holds);
} }
static gboolean
set_battery_support (PpdApp *data, gboolean battery_support, GError **error)
{
if (data->battery_support == battery_support) {
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"battery_aware is already set to %s", battery_support ? "TRUE" : "FALSE");
return FALSE;
}
data->battery_support = battery_support;
g_key_file_set_boolean (data->config, "State", "battery_aware", data->battery_support);
g_debug("battery_aware set to %s", data->battery_support ? "TRUE" : "FALSE");
restart_profile_drivers_for_default_app ();
return TRUE;
}
static gboolean static gboolean
set_active_profile (PpdApp *data, set_active_profile (PpdApp *data,
const char *profile, const char *profile,
@ -919,6 +956,8 @@ handle_get_property (GDBusConnection *connection,
return get_modern_actions_variant (data); return get_modern_actions_variant (data);
if (g_str_equal (property_name, "Actions")) if (g_str_equal (property_name, "Actions"))
return get_legacy_actions_variant (data); return get_legacy_actions_variant (data);
if (g_str_equal (property_name, "BatteryAware"))
return g_variant_new_boolean (data->battery_support);
if (g_strcmp0 (property_name, "PerformanceDegraded") == 0) { if (g_strcmp0 (property_name, "PerformanceDegraded") == 0) {
gchar *degraded = get_performance_degraded (data); gchar *degraded = get_performance_degraded (data);
return g_variant_new_take_string (g_steal_pointer (&degraded)); return g_variant_new_take_string (g_steal_pointer (&degraded));
@ -943,22 +982,33 @@ handle_set_property (GDBusConnection *connection,
gpointer user_data) gpointer user_data)
{ {
PpdApp *data = user_data; PpdApp *data = user_data;
const char *profile;
g_return_val_if_fail (data->connection, FALSE); g_return_val_if_fail (data->connection, FALSE);
if (g_strcmp0 (property_name, "ActiveProfile") != 0) { if (g_str_equal (property_name, "ActiveProfile")) {
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, const char *profile;
"No such property: %s", property_name);
return FALSE;
}
if (!check_action_permission (data, sender,
POWER_PROFILES_POLICY_NAMESPACE ".switch-profile",
error))
return FALSE;
g_variant_get (value, "&s", &profile); if (!check_action_permission (data, sender,
return set_active_profile (data, profile, error); POWER_PROFILES_POLICY_NAMESPACE ".switch-profile",
error))
return FALSE;
g_variant_get (value, "&s", &profile);
return set_active_profile (data, profile, error);
} else if (g_str_equal (property_name, "BatteryAware")) {
if (!check_action_permission (data,
sender,
POWER_PROFILES_POLICY_NAMESPACE ".configure-battery-aware",
error)) {
return FALSE;
}
return set_battery_support (data, g_variant_get_boolean (value), error);
}
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"No such property: %s", property_name);
return FALSE;
} }
static gboolean set_action_enabled (PpdApp *data, static gboolean set_action_enabled (PpdApp *data,
@ -1018,12 +1068,14 @@ handle_method_call (GDBusConnection *connection,
release_profile (data, parameters, invocation); release_profile (data, parameters, invocation);
} else if (g_strcmp0 (method_name, "SetActionEnabled") == 0) { } else if (g_strcmp0 (method_name, "SetActionEnabled") == 0) {
g_autoptr(GError) local_error = NULL; g_autoptr(GError) local_error = NULL;
if (g_str_equal (interface_name, POWER_PROFILES_LEGACY_IFACE_NAME)) { if (g_str_equal (interface_name, POWER_PROFILES_LEGACY_IFACE_NAME)) {
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD,
"Method %s is not available in interface %s", method_name, "Method %s is not available in interface %s", method_name,
interface_name); interface_name);
return; return;
} }
if (!check_action_permission (data, if (!check_action_permission (data,
g_dbus_method_invocation_get_sender (invocation), g_dbus_method_invocation_get_sender (invocation),
POWER_PROFILES_POLICY_NAMESPACE ".configure-action", POWER_PROFILES_POLICY_NAMESPACE ".configure-action",
@ -1031,6 +1083,7 @@ handle_method_call (GDBusConnection *connection,
g_dbus_method_invocation_return_gerror (invocation, local_error); g_dbus_method_invocation_return_gerror (invocation, local_error);
return; return;
} }
if (!set_action_enabled (data, parameters, &local_error)) { if (!set_action_enabled (data, parameters, &local_error)) {
g_dbus_method_invocation_return_gerror (invocation, local_error); g_dbus_method_invocation_return_gerror (invocation, local_error);
return; return;
@ -1671,7 +1724,10 @@ start_profile_drivers (PpdApp *data)
send_dbus_event (data, PROP_ALL); send_dbus_event (data, PROP_ALL);
data->was_started = TRUE; data->was_started = TRUE;
if (data->debug_options->disable_upower) { if (data->debug_options->disable_upower)
data->battery_support = FALSE;
if (!data->battery_support) {
g_debug ("upower is disabled, let's skip it"); g_debug ("upower is disabled, let's skip it");
} else if (needs_battery_state_monitor || needs_battery_change_monitor) { } else if (needs_battery_state_monitor || needs_battery_change_monitor) {
/* start watching for power changes */ /* start watching for power changes */

View file

@ -47,6 +47,14 @@ def _version(_args):
print(f"client: {client_version}\ndaemon: {daemon_ver}") print(f"client: {client_version}\ndaemon: {daemon_ver}")
@command
def _set_profile(args):
proxy = get_proxy()
proxy.Set(
"(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0])
)
@command @command
def _get(_args): def _get(_args):
proxy = get_proxy() proxy = get_proxy()
@ -55,11 +63,16 @@ def _get(_args):
@command @command
def _set(args): def _set_battery_aware(args):
enable = args.enable
disable = args.disable
if enable is False and disable is True:
raise ValueError("enable or disable is required")
if enable is True and disable is False:
raise ValueError("can't set both enable and disable")
enable = enable if enable is not None else not disable
proxy = get_proxy() proxy = get_proxy()
proxy.Set( proxy.Set("(ssv)", PP_IFACE, "BatteryAware", GLib.Variant.new_boolean(enable))
"(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0])
)
def get_profiles_property(prop): def get_profiles_property(prop):
@ -166,6 +179,12 @@ def _launch(args):
sys.exit(ret) sys.exit(ret)
@command
def _query_battery_aware(_args):
result = get_profiles_property("BatteryAware")
print(f"Dynamic changes from charger and battery events: {result}")
@command @command
def _list_actions(_args): def _list_actions(_args):
actions = get_profiles_property("ActionsInfo") actions = get_profiles_property("ActionsInfo")
@ -225,7 +244,7 @@ def get_parser():
help="Profile to use for set command", help="Profile to use for set command",
choices=get_profile_choices(), choices=get_profile_choices(),
) )
parser_set.set_defaults(func=_set) parser_set.set_defaults(func=_set_profile)
parser_set_action = subparsers.add_parser( parser_set_action = subparsers.add_parser(
"configure-action", help="Configure the action to be taken for the profile" "configure-action", help="Configure the action to be taken for the profile"
) )
@ -245,6 +264,26 @@ def get_parser():
help="disable action", help="disable action",
) )
parser_set_action.set_defaults(func=_configure_action) parser_set_action.set_defaults(func=_configure_action)
parser_set_battery_aware = subparsers.add_parser(
"configure-battery-aware",
help="Turn on or off dynamic changes from battery level or power adapter",
)
parser_set_battery_aware.add_argument(
"--enable",
action="store_true",
help="enable battery aware",
)
parser_set_battery_aware.add_argument(
"--disable",
action="store_false",
help="disable battery aware",
)
parser_set_battery_aware.set_defaults(func=_set_battery_aware)
parser_query_battery_aware = subparsers.add_parser(
"query-battery-aware",
help="Query if dynamic changes from battery level or power adapter are enabled",
)
parser_query_battery_aware.set_defaults(func=_query_battery_aware)
parser_launch = subparsers.add_parser( parser_launch = subparsers.add_parser(
"launch", "launch",
help="Launch a command while holding a power profile", help="Launch a command while holding a power profile",

View file

@ -144,10 +144,10 @@ ppd_action_class_init (PpdActionClass *klass)
*/ */
g_object_class_install_property (object_class, PROP_ACTION_DESCRIPTION, g_object_class_install_property (object_class, PROP_ACTION_DESCRIPTION,
g_param_spec_string ("action-description", g_param_spec_string ("action-description",
"Action description", "Action description",
"Action description", "Action description",
NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/** /**
* PpdAction::optin: * PpdAction::optin:
@ -159,7 +159,7 @@ ppd_action_class_init (PpdActionClass *klass)
"Opt-in", "Opt-in",
"Whether the action is opt-in or not", "Whether the action is opt-in or not",
FALSE, FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
} }
static void static void

View file

@ -148,6 +148,7 @@ class Tests(dbusmock.DBusTestCase):
"org.freedesktop.UPower.PowerProfiles.switch-profile", "org.freedesktop.UPower.PowerProfiles.switch-profile",
"org.freedesktop.UPower.PowerProfiles.hold-profile", "org.freedesktop.UPower.PowerProfiles.hold-profile",
"org.freedesktop.UPower.PowerProfiles.configure-action", "org.freedesktop.UPower.PowerProfiles.configure-action",
"org.freedesktop.UPower.PowerProfiles.configure-battery-aware",
] ]
) )
@ -2483,6 +2484,52 @@ class Tests(dbusmock.DBusTestCase):
self.assertEqual(len(profiles), 3) self.assertEqual(len(profiles), 3)
self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "") self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "")
def test_powerprofilesctl_configure_battery_aware_command(self):
"""Check powerprofilesctl configure-battery-aware command works"""
self.start_dbus_template(
"upower",
{"DaemonVersion": "0.99", "OnBattery": False},
)
self.start_daemon()
# verify argument is required
cmd = subprocess.run(
self.powerprofilesctl_command() + ["configure-battery-aware"],
capture_output=True,
check=False,
)
self.assertEqual(cmd.returncode, 1)
# check possible arguments
for key, value in {"--disable": "False", "--enable": "True"}.items():
cmd = subprocess.run(
self.powerprofilesctl_command() + ["configure-battery-aware", key],
capture_output=True,
check=True,
)
self.assertEqual(cmd.returncode, 0)
cmd = subprocess.run(
self.powerprofilesctl_command() + ["query-battery-aware"],
capture_output=True,
check=True,
)
self.assertEqual(cmd.returncode, 0)
self.assertIn(
f"Dynamic changes from charger and battery events: {value}",
cmd.stdout.decode("utf-8"),
)
# make sure can't be enabled twice
cmd = subprocess.run(
self.powerprofilesctl_command() + ["configure-battery-aware", "--enable"],
capture_output=True,
check=False,
)
self.assertEqual(cmd.returncode, 1)
def test_powerprofilesctl_configure_action_command(self): def test_powerprofilesctl_configure_action_command(self):
"""Check powerprofilesctl configure-action command works""" """Check powerprofilesctl configure-action command works"""