diff --git a/README.md b/README.md index 143fb0e..aa4b079 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,25 @@ Actions can be enabled or disabled by running: powerprofilesctl configure-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 If `power-profiles-daemon` refuses to start, it's likely that you have [a conflicting diff --git a/data/power-profiles-daemon.policy b/data/power-profiles-daemon.policy index 732d6eb..2de9bab 100644 --- a/data/power-profiles-daemon.policy +++ b/data/power-profiles-daemon.policy @@ -38,4 +38,14 @@ + + Configure action + Privileges are required to configure battery-awareness. + + no + no + yes + + + diff --git a/src/org.freedesktop.UPower.PowerProfiles.xml.in b/src/org.freedesktop.UPower.PowerProfiles.xml.in index 83ea079..fe1a0e5 100644 --- a/src/org.freedesktop.UPower.PowerProfiles.xml.in +++ b/src/org.freedesktop.UPower.PowerProfiles.xml.in @@ -166,5 +166,13 @@ --> + + + diff --git a/src/power-profiles-daemon.c b/src/power-profiles-daemon.c index fc790a1..01aac72 100644 --- a/src/power-profiles-daemon.c +++ b/src/power-profiles-daemon.c @@ -86,6 +86,7 @@ typedef struct { GPtrArray *actions; GHashTable *profile_holds; + gboolean battery_support; GDBusProxy *upower_proxy; GDBusProxy *upower_display_proxy; gulong upower_watch_id; @@ -170,6 +171,7 @@ typedef enum { PROP_DEGRADED = 1 << 4, PROP_ACTIVE_PROFILE_HOLDS = 1 << 5, PROP_VERSION = 1 << 6, + PROP_UPOWER = 1 << 7, } PropertiesMask; #define PROP_ALL (PROP_ACTIVE_PROFILE | \ @@ -178,7 +180,8 @@ typedef enum { PROP_ACTIONS | \ PROP_DEGRADED | \ PROP_ACTIVE_PROFILE_HOLDS | \ - PROP_VERSION) + PROP_VERSION | \ + PROP_UPOWER) static gboolean driver_profile_support (PpdDriver *driver, @@ -287,6 +290,7 @@ get_legacy_actions_variant (PpdApp *data) for (i = 0; i < data->actions->len; i++) { PpdAction *action = g_ptr_array_index (data->actions, i); + if (!ppd_action_get_active (action)) 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_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_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_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); } @@ -400,6 +404,10 @@ send_dbus_event_iface (PpdApp *data, g_variant_builder_add (&props_builder, "{sv}", "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, g_variant_builder_end (&props_builder), @@ -450,6 +458,8 @@ save_configuration (PpdApp *data) 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)) 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 *profile_str = NULL; g_autofree char *cpu_driver = NULL; + g_autoptr(GError) error = NULL; 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); if (PPD_IS_DRIVER_CPU (data->cpu_driver) && 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); } +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 set_active_profile (PpdApp *data, const char *profile, @@ -919,6 +956,8 @@ handle_get_property (GDBusConnection *connection, return get_modern_actions_variant (data); if (g_str_equal (property_name, "Actions")) 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) { gchar *degraded = get_performance_degraded (data); return g_variant_new_take_string (g_steal_pointer (°raded)); @@ -943,22 +982,33 @@ handle_set_property (GDBusConnection *connection, gpointer user_data) { PpdApp *data = user_data; - const char *profile; g_return_val_if_fail (data->connection, FALSE); - if (g_strcmp0 (property_name, "ActiveProfile") != 0) { - g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, - "No such property: %s", property_name); - return FALSE; - } - if (!check_action_permission (data, sender, - POWER_PROFILES_POLICY_NAMESPACE ".switch-profile", - error)) - return FALSE; + if (g_str_equal (property_name, "ActiveProfile")) { + const char *profile; - g_variant_get (value, "&s", &profile); - return set_active_profile (data, profile, error); + if (!check_action_permission (data, sender, + 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, @@ -1018,12 +1068,14 @@ handle_method_call (GDBusConnection *connection, release_profile (data, parameters, invocation); } else if (g_strcmp0 (method_name, "SetActionEnabled") == 0) { g_autoptr(GError) local_error = NULL; + 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, "Method %s is not available in interface %s", method_name, interface_name); return; } + if (!check_action_permission (data, g_dbus_method_invocation_get_sender (invocation), POWER_PROFILES_POLICY_NAMESPACE ".configure-action", @@ -1031,6 +1083,7 @@ handle_method_call (GDBusConnection *connection, g_dbus_method_invocation_return_gerror (invocation, local_error); return; } + if (!set_action_enabled (data, parameters, &local_error)) { g_dbus_method_invocation_return_gerror (invocation, local_error); return; @@ -1671,7 +1724,10 @@ start_profile_drivers (PpdApp *data) send_dbus_event (data, PROP_ALL); 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"); } else if (needs_battery_state_monitor || needs_battery_change_monitor) { /* start watching for power changes */ diff --git a/src/powerprofilesctl b/src/powerprofilesctl index 40a2421..db3e35a 100755 --- a/src/powerprofilesctl +++ b/src/powerprofilesctl @@ -47,6 +47,14 @@ def _version(_args): 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 def _get(_args): proxy = get_proxy() @@ -55,11 +63,16 @@ def _get(_args): @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.Set( - "(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0]) - ) + proxy.Set("(ssv)", PP_IFACE, "BatteryAware", GLib.Variant.new_boolean(enable)) def get_profiles_property(prop): @@ -166,6 +179,12 @@ def _launch(args): 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 def _list_actions(_args): actions = get_profiles_property("ActionsInfo") @@ -225,7 +244,7 @@ def get_parser(): help="Profile to use for set command", choices=get_profile_choices(), ) - parser_set.set_defaults(func=_set) + parser_set.set_defaults(func=_set_profile) parser_set_action = subparsers.add_parser( "configure-action", help="Configure the action to be taken for the profile" ) @@ -245,6 +264,26 @@ def get_parser(): help="disable 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( "launch", help="Launch a command while holding a power profile", diff --git a/src/ppd-action.c b/src/ppd-action.c index 1edcb9c..72ed975 100644 --- a/src/ppd-action.c +++ b/src/ppd-action.c @@ -144,10 +144,10 @@ ppd_action_class_init (PpdActionClass *klass) */ g_object_class_install_property (object_class, PROP_ACTION_DESCRIPTION, g_param_spec_string ("action-description", - "Action description", - "Action description", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + "Action description", + "Action description", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * PpdAction::optin: @@ -159,7 +159,7 @@ ppd_action_class_init (PpdActionClass *klass) "Opt-in", "Whether the action is opt-in or not", FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void diff --git a/tests/integration_test.py b/tests/integration_test.py index cf30a23..e16e5fb 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -148,6 +148,7 @@ class Tests(dbusmock.DBusTestCase): "org.freedesktop.UPower.PowerProfiles.switch-profile", "org.freedesktop.UPower.PowerProfiles.hold-profile", "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(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): """Check powerprofilesctl configure-action command works"""