[neon/backports-jammy/power-profiles-daemon/Neon/unstable] /: 0.8.1-1 (patches unapplied)

git-ubuntu importer null at kde.org
Tue Sep 24 23:21:42 BST 2024


Git commit d12abf1e3d7886da6844d018423b060dc330f408 by git-ubuntu importer, on behalf of Sebastien Bacher.
Committed on 15/06/2021 at 16:37.
Pushed by carlosdem into branch 'Neon/unstable'.

0.8.1-1 (patches unapplied)

Imported using git-ubuntu import.

M  +15   -0    NEWS
M  +18   -4    README.md
A  +82   -0    check-news.sh
M  +6    -0    debian/changelog
M  +1    -1    docs/meson.build
M  +2    -0    docs/power-profiles-daemon-sections.txt
M  +10   -2    meson.build
M  +23   -3    src/meson.build
M  +129  -92   src/power-profiles-daemon.c
A  +151  -0    src/powerprofilesctl.in
D  +0    -49   src/ppd-driver-balanced.c
M  +32   -9    src/ppd-driver-fake.c
M  +23   -79   src/ppd-driver-intel-pstate.c
D  +0    -211  src/ppd-driver-lenovo-dytc.c
A  +49   -0    src/ppd-driver-placeholder.c     [License: GPL (v3)]
R  +2    -2    src/ppd-driver-placeholder.h [from: src/ppd-driver-lenovo-dytc.h - 064% similarity]
A  +352  -0    src/ppd-driver-platform-profile.c     [License: GPL (v3)]
R  +2    -2    src/ppd-driver-platform-profile.h [from: src/ppd-driver-balanced.h - 059% similarity]
D  +0    -49   src/ppd-driver-power-saver.c
D  +0    -15   src/ppd-driver-power-saver.h
M  +92   -9    src/ppd-driver.c
M  +51   -6    src/ppd-driver.h
M  +61   -0    src/ppd-utils.c
M  +8    -0    src/ppd-utils.h
M  +130  -73   tests/integration-test
M  +1    -0    tests/meson.build

https://invent.kde.org/neon/backports-jammy/power-profiles-daemon/-/commit/d12abf1e3d7886da6844d018423b060dc330f408

diff --git a/NEWS b/NEWS
index ee04942..bc8a646 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,18 @@
+0.8.1
+-----
+
+This release works-around a cosmetic issue in gnome-shell animations when
+on battery and using the intel-pstate driver.
+
+0.8
+---
+
+This release adds support for the new generic `platform_profile` kernel
+API to replace the Lenovo specific `dytc_perfmode`, meaning it should also
+support profile selection on a number of HP and Microsoft Surface devices.
+
+This release also add the `powerprofilesctl` command-line application.
+
 0.1
 ---
 
diff --git a/README.md b/README.md
index 9e5a192..768a744 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ gdbus introspect --system --dest net.hadess.PowerProfiles --object-path /net/had
 You can change the selected profile by running (change `power-saver` for the
 chosen profile):
 ```
-gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles --method org.freedesktop.DBus.Properties.Set 'net.hadess.PowerProfiles' 'SelectedProfile' "<'power-saver'>"
+gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles --method org.freedesktop.DBus.Properties.Set 'net.hadess.PowerProfiles' 'ActiveProfile' "<'power-saver'>"
 ```
 
 If that doesn't work, please file an issue, make sure any running power-profiles-daemon
@@ -125,11 +125,12 @@ A fair number of the tweaks that could apply to devices running GNOME or
 another free desktop are either potentially destructive (eg. some of the
 SATA power-saving mode resulting in corrupted data), or working well
 enough to be put into place by default (eg. audio codec power-saving), even
-if some quirks might be needed on some hardware.
+if we need to disable the power saving on some hardware that reacts
+badly to it.
 
-Both are good projects to use if the intent is to experiment with particular
+Both are good projects to use for the purpose of experimenting with particular
 settings to see if they'd be something that can be implemented by default,
-or to put some fine-grained policies in place on server-type workloads
+or to put some fine-grained, static, policies in place on server-type workloads
 which are not as fluid and changing as desktop workloads can be.
 
 ### [auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq)
@@ -138,3 +139,16 @@ It doesn't take user-intent into account, doesn't have a D-Bus interface and
 seems to want to work automatically by monitoring the CPU usage, which kind
 of goes against a user's wishes as a user might still want to conserve as
 much energy as possible under high-CPU usage.
+
+### [slimbookbattery](https://launchpad.net/~slimbook)
+This is **not** free software (*Source code available but not modifiable
+without express authorization.*). The application does a lot of things in
+addition to the "3 profiles" selection:
+
+- replaces part of the suspend mechanism with its own hybrid sleep implementation
+  (systemd already implements one)
+- implements charging limits for batteries
+- implements some power saving tricks, which could also be implemented
+
+A lot of those power-saving tricks could be analysed and used, but we
+obviously can't rely on "source available" software for our free desktops.
diff --git a/check-news.sh b/check-news.sh
new file mode 100644
index 0000000..11ae861
--- /dev/null
+++ b/check-news.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+# Copyright (C) 2019 Red Hat, Inc.
+# Author: Bastien Nocera <hadess at hadess.net>
+#
+# 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.
+
+
+# Add to your top-level meson.build to check for an updated NEWS file
+# when doing a "dist" release, similarly to automake's check-news:
+# https://www.gnu.org/software/automake/manual/html_node/List-of-Automake-options.html
+#
+# Checks NEWS for the version number:
+# meson.add_dist_script(
+#   find_program('check-news.sh').path(),
+#   '@0@'.format(meson.project_version())
+# )
+#
+# Checks NEWS and data/foo.appdata.xml for the version number:
+# meson.add_dist_script(
+#   find_program('check-news.sh').path(),
+#   '@0@'.format(meson.project_version()),
+#   'NEWS',
+#   'data/foo.appdata.xml'
+# )
+
+usage()
+{
+	echo "$0 VERSION [FILES...]"
+	exit 1
+}
+
+check_version()
+{
+	VERSION=$1
+	# Look in the first 15 lines for NEWS files, but look
+	# everywhere for other types of files
+	if [ "$2" = "NEWS" ]; then
+		DATA=`sed 15q $SRC_ROOT/"$2"`
+	else
+		DATA=`cat $SRC_ROOT/"$2"`
+	fi
+	case "$DATA" in
+	*"$VERSION"*)
+		:
+		;;
+	*)
+		echo "$2 not updated; not releasing" 1>&2;
+		exit 1
+		;;
+	esac
+}
+
+SRC_ROOT=${MESON_DIST_ROOT:-"./"}
+
+if [ $# -lt 1 ] ; then usage ; fi
+
+VERSION=$1
+shift
+
+if [ $# -eq 0 ] ; then
+	check_version $VERSION 'NEWS'
+	exit 0
+fi
+
+for i in $@ ; do
+	check_version $VERSION "$i"
+done
+
+exit 0
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 475c4e5..3155e44 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+power-profiles-daemon (0.8.1-1) experimental; urgency=medium
+
+  * New upstream version
+
+ -- Sebastien Bacher <seb128 at ubuntu.com>  Tue, 15 Jun 2021 15:29:30 +0200
+
 power-profiles-daemon (0.1-5) experimental; urgency=medium
 
   * Don't split the test into a separate binary, it's not useful
diff --git a/docs/meson.build b/docs/meson.build
index b8b10ab..cfd2406 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -24,7 +24,7 @@ private_headers = [
   'ppd-driver-balanced.h',
   'ppd-driver-fake.h',
   'ppd-driver-intel-pstate.h',
-  'ppd-driver-lenovo-dytc.h',
+  'ppd-driver-platform-profile.h',
   'ppd-driver-power-saver.h',
   'ppd-utils.h',
   'power-profiles-daemon-resources.h',
diff --git a/docs/power-profiles-daemon-sections.txt b/docs/power-profiles-daemon-sections.txt
index 0ad1c3a..0063310 100644
--- a/docs/power-profiles-daemon-sections.txt
+++ b/docs/power-profiles-daemon-sections.txt
@@ -12,6 +12,8 @@ PPD_TYPE_ACTION
 <TITLE>Profile Drivers</TITLE>
 PpdDriverClass
 PpdDriver
+PpdProbeResult
+PpdProfileActivationReason
 <SUBSECTION Private>
 PPD_TYPE_DRIVER
 </SECTION>
diff --git a/meson.build b/meson.build
index 0a68a9e..81181f6 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('power-profiles-daemon', [ 'c' ],
-        version: '0.1',
+        version: '0.8.1',
         license: 'GPLv3+',
         default_options: [
           'buildtype=debugoptimized',
@@ -35,13 +35,16 @@ if systemd_system_unit_dir == 'auto'
     systemd_system_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir')
 endif
 gio_dep = dependency('gio-2.0')
-gudev_dep = dependency('gudev-1.0', version: '>= 232')
+gudev_dep = dependency('gudev-1.0', version: '>= 234')
 upower_dep = dependency('upower-glib')
 
 gnome = import('gnome')
 
 add_global_arguments('-D_GNU_SOURCE=1', language: 'c')
 
+pylint = find_program('pylint-3', 'pylint3', 'pylint', required: false)
+pylint_flags = ['-d', 'C0116', '-d', 'C0114', '-d', 'W0707']
+
 subdir('src')
 subdir('data')
 
@@ -56,3 +59,8 @@ if get_option('gtk_doc')
 endif
 
 subdir('tests')
+
+meson.add_dist_script(
+  find_program('check-news.sh').path(),
+  '@0@'.format(meson.project_version())
+)
diff --git a/src/meson.build b/src/meson.build
index 71625d7..318f284 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -39,9 +39,8 @@ sources += [
   'power-profiles-daemon.c',
   'ppd-action-trickle-charge.c',
   'ppd-driver-intel-pstate.c',
-  'ppd-driver-lenovo-dytc.c',
-  'ppd-driver-balanced.c',
-  'ppd-driver-power-saver.c',
+  'ppd-driver-platform-profile.c',
+  'ppd-driver-placeholder.c',
   'ppd-driver-fake.c',
 ]
 
@@ -51,3 +50,24 @@ executable('power-profiles-daemon',
   install: true,
   install_dir: libexecdir
 )
+
+python = import('python')
+py_installation = python.find_installation('python3', required: true)
+
+ppd_conf = configuration_data()
+ppd_conf.set('VERSION', meson.project_version())
+ppd_conf.set('PYTHON3', py_installation.path())
+
+script = configure_file(
+  input: 'powerprofilesctl.in',
+  output: 'powerprofilesctl',
+  configuration: ppd_conf,
+  install_dir: get_option('bindir')
+)
+
+if pylint.found()
+  test('pylint-powerprofilesctl',
+       pylint,
+       args: pylint_flags + [ script ],
+       )
+endif
diff --git a/src/power-profiles-daemon.c b/src/power-profiles-daemon.c
index 77c561d..c07d8a5 100644
--- a/src/power-profiles-daemon.c
+++ b/src/power-profiles-daemon.c
@@ -19,45 +19,32 @@
 #define POWER_PROFILES_DBUS_PATH          "/net/hadess/PowerProfiles"
 #define POWER_PROFILES_IFACE_NAME         POWER_PROFILES_DBUS_NAME
 
-GMainLoop *main_loop = NULL;
-
 typedef struct {
+  GMainLoop *main_loop;
   GDBusNodeInfo *introspection_data;
   GDBusConnection *connection;
   guint name_id;
+  gboolean was_started;
   int ret;
 
   PpdProfile active_profile;
-  GPtrArray *drivers;
+  GPtrArray *probed_drivers;
+  PpdDriver *driver;
   GPtrArray *actions;
 } PpdApp;
 
-static PpdDriver *
-get_driver_for_profile (PpdApp     *data,
-                        PpdProfile  profile)
-{
-  guint i;
-
-  g_return_val_if_fail (ppd_profile_has_single_flag (profile), NULL);
+static PpdApp *ppd_app = NULL;
 
-  for (i = 0; i < data->drivers->len; i++) {
-    PpdDriver *driver = g_ptr_array_index (data->drivers, i);
-
-    if (ppd_driver_get_profiles (driver) & profile)
-      return driver;
-  }
-
-  return NULL;
-}
+static void stop_profile_drivers (PpdApp *data);
+static void start_profile_drivers (PpdApp *data);
 
-#define GET_DRIVER(p) (get_driver_for_profile (data, p))
-#define ACTIVE_DRIVER (get_driver_for_profile (data, data->active_profile))
+#define GET_DRIVER(p) (ppd_driver_get_profiles (data->driver) & p ? data->driver : NULL)
+#define ACTIVE_DRIVER (data->driver)
 
 /* profile drivers and actions */
 #include "ppd-action-trickle-charge.h"
-#include "ppd-driver-balanced.h"
-#include "ppd-driver-power-saver.h"
-#include "ppd-driver-lenovo-dytc.h"
+#include "ppd-driver-placeholder.h"
+#include "ppd-driver-platform-profile.h"
 #include "ppd-driver-intel-pstate.h"
 #include "ppd-driver-fake.h"
 
@@ -66,12 +53,11 @@ typedef GType (*GTypeGetFunc) (void);
 static GTypeGetFunc objects[] = {
   /* Hardware specific profile drivers */
   ppd_driver_fake_get_type,
-  ppd_driver_lenovo_dytc_get_type,
+  ppd_driver_platform_profile_get_type,
   ppd_driver_intel_pstate_get_type,
 
-  /* Generic profile drivers */
-  ppd_driver_balanced_get_type,
-  ppd_driver_power_saver_get_type,
+  /* Generic profile driver */
+  ppd_driver_placeholder_get_type,
 
   /* Actions */
   ppd_action_trickle_charge_get_type,
@@ -95,8 +81,8 @@ get_active_profile (PpdApp *data)
 static const char *
 get_performance_inhibited (PpdApp *data)
 {
-  PpdDriver *driver;
   const char *ret;
+  PpdDriver *driver;
 
   driver = GET_DRIVER(PPD_PROFILE_PERFORMANCE);
   if (!driver)
@@ -220,24 +206,21 @@ actions_activate_profile (GPtrArray *actions,
 }
 
 static void
-activate_target_profile (PpdApp     *data,
-                         PpdProfile  target_profile)
+activate_target_profile (PpdApp                     *data,
+                         PpdProfile                  target_profile,
+                         PpdProfileActivationReason  reason)
 {
-  guint i;
+  g_autoptr(GError) error = NULL;
 
-  g_debug ("Setting active profile '%s' (current: '%s')",
+  g_debug ("Setting active profile '%s' for reason '%s' (current: '%s')",
            ppd_profile_to_str (target_profile),
+           ppd_profile_activation_reason_to_str (reason),
            ppd_profile_to_str (data->active_profile));
 
-  for (i = 0; i < data->drivers->len; i++) {
-    PpdDriver *driver = g_ptr_array_index (data->drivers, i);
-    g_autoptr(GError) error = NULL;
-
-    if (!ppd_driver_activate_profile (driver, target_profile, &error)) {
-      g_warning ("Failed to activate driver '%s': %s",
-                 ppd_driver_get_driver_name (driver),
-                 error->message);
-    }
+  if (!ppd_driver_activate_profile (data->driver, target_profile, reason, &error)) {
+    g_warning ("Failed to activate driver '%s': %s",
+               ppd_driver_get_driver_name (data->driver),
+               error->message);
   }
 
   actions_activate_profile (data->actions, target_profile);
@@ -259,11 +242,8 @@ set_active_profile (PpdApp      *data,
     return FALSE;
   }
 
-  if (target_profile == data->active_profile) {
-    g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
-                 "Profile '%s' already active", profile);
-    return FALSE;
-  }
+  if (target_profile == data->active_profile)
+    return TRUE;
 
   if (target_profile == PPD_PROFILE_PERFORMANCE &&
       ppd_driver_is_performance_inhibited (GET_DRIVER (PPD_PROFILE_PERFORMANCE))) {
@@ -272,11 +252,10 @@ set_active_profile (PpdApp      *data,
     return FALSE;
   }
 
-  g_debug ("Transitioning active profile from '%s' to '%s'",
+  g_debug ("Transitioning active profile from '%s' to '%s' by user request",
            ppd_profile_to_str (data->active_profile), profile);
-  data->active_profile = target_profile;
 
-  activate_target_profile (data, target_profile);
+  activate_target_profile (data, target_profile, PPD_PROFILE_ACTIVATION_REASON_USER);
   send_dbus_event (data, PROP_ACTIVE_PROFILE);
 
   return TRUE;
@@ -307,7 +286,25 @@ driver_performance_inhibited_changed_cb (GObject    *gobject,
   if (!ppd_driver_is_performance_inhibited (driver))
     return;
 
-  activate_target_profile (data, PPD_PROFILE_BALANCED);
+  activate_target_profile (data, PPD_PROFILE_BALANCED, PPD_PROFILE_ACTIVATION_REASON_INHIBITION);
+  send_dbus_event (data, PROP_ACTIVE_PROFILE);
+}
+
+static void
+driver_profile_changed_cb (PpdDriver *driver,
+                           PpdProfile new_profile,
+                           gpointer   user_data)
+{
+  PpdApp *data = user_data;
+
+  g_debug ("Driver '%s' switched internally to profile '%s' (current: '%s')",
+           ppd_driver_get_driver_name (driver),
+           ppd_profile_to_str (new_profile),
+           ppd_profile_to_str (data->active_profile));
+  if (new_profile == data->active_profile)
+    return;
+
+  activate_target_profile (data, new_profile, PPD_PROFILE_ACTIVATION_REASON_INTERNAL);
   send_dbus_event (data, PROP_ACTIVE_PROFILE);
 }
 
@@ -372,8 +369,11 @@ name_lost_handler (GDBusConnection *connection,
                    const gchar     *name,
                    gpointer         user_data)
 {
+  PpdApp *data = user_data;
   g_debug ("power-profiles-daemon is already running, or it cannot own its D-Bus name. Verify installation.");
-  exit (0);
+  if (!data->was_started)
+    data->ret = 1;
+  g_main_loop_quit (data->main_loop);
 }
 
 static void
@@ -409,39 +409,31 @@ has_required_drivers (PpdApp *data)
   return TRUE;
 }
 
-static gboolean
-profile_already_handled (PpdApp     *data,
-                         PpdDriver  *driver,
-                         PpdProfile  profiles)
+static void
+driver_probe_request_cb (PpdDriver *driver,
+                         gpointer   user_data)
 {
-  guint i;
-
-  for (i = 0; i < NUM_PROFILES; i++) {
-    PpdDriver *existing_driver;
-
-    if (!(profiles & (1 << i)))
-      continue;
+  PpdApp *data = user_data;
 
-    existing_driver = GET_DRIVER(1 << i);
-    if (existing_driver) {
-      g_debug ("Driver '%s' conflicts with already probed driver '%s' for profile %s",
-               ppd_driver_get_driver_name (driver),
-               ppd_driver_get_driver_name (existing_driver),
-               ppd_profile_to_str (1 << i));
-      return TRUE;
-    }
-  }
+  stop_profile_drivers (data);
+  start_profile_drivers (data);
+}
 
-  return FALSE;
+static void
+stop_profile_drivers (PpdApp *data)
+{
+  g_ptr_array_set_size (data->probed_drivers, 0);
+  g_ptr_array_set_size (data->actions, 0);
+  g_clear_object (&data->driver);
 }
 
 static void
-name_acquired_handler (GDBusConnection *connection,
-                       const gchar     *name,
-                       gpointer         user_data)
+start_profile_drivers (PpdApp *data)
 {
-  PpdApp *data = user_data;
   guint i;
+  PpdProfile prev_profile;
+
+  prev_profile = data->active_profile;
 
   for (i = 0; i < G_N_ELEMENTS (objects); i++) {
     GObject *object;
@@ -450,9 +442,17 @@ name_acquired_handler (GDBusConnection *connection,
     if (PPD_IS_DRIVER (object)) {
       PpdDriver *driver = PPD_DRIVER (object);
       PpdProfile profiles;
+      PpdProbeResult result;
 
       g_debug ("Handling driver '%s'", ppd_driver_get_driver_name (driver));
 
+      if (data->driver != NULL) {
+        g_debug ("Driver '%s' already probed, skipping driver '%s'",
+                 ppd_driver_get_driver_name (data->driver),
+                 ppd_driver_get_driver_name (driver));
+        continue;
+      }
+
       profiles = ppd_driver_get_profiles (driver);
       if (!(profiles & PPD_PROFILE_ALL)) {
         g_warning ("Profile Driver '%s' implements invalid profiles '0x%X'",
@@ -462,22 +462,25 @@ name_acquired_handler (GDBusConnection *connection,
         continue;
       }
 
-      if (profile_already_handled (data, driver, profiles)) {
-        g_object_unref (object);
-        continue;
-      }
-
-      if (!ppd_driver_probe (driver)) {
+      result = ppd_driver_probe (driver, &prev_profile);
+      if (result == PPD_PROBE_RESULT_FAIL) {
         g_debug ("probe() failed for driver %s, skipping",
                  ppd_driver_get_driver_name (driver));
         g_object_unref (object);
         continue;
+      } else if (result == PPD_PROBE_RESULT_DEFER) {
+        g_signal_connect (G_OBJECT (driver), "probe-request",
+                          G_CALLBACK (driver_probe_request_cb), data);
+        g_ptr_array_add (data->probed_drivers, driver);
+        continue;
       }
 
-      g_ptr_array_add (data->drivers, driver);
+      data->driver = driver;
 
       g_signal_connect (G_OBJECT (driver), "notify::performance-inhibited",
                         G_CALLBACK (driver_performance_inhibited_changed_cb), data);
+      g_signal_connect (G_OBJECT (driver), "profile-changed",
+                        G_CALLBACK (driver_profile_changed_cb), data);
     } else if (PPD_IS_ACTION (object)) {
       PpdAction *action = PPD_ACTION (object);
 
@@ -501,17 +504,42 @@ name_acquired_handler (GDBusConnection *connection,
     goto bail;
   }
 
+  if (prev_profile != data->active_profile) {
+    g_debug ("Using '%s' as current profile from probed driver",
+             ppd_profile_to_str (prev_profile));
+    data->active_profile = prev_profile;
+  }
+
   /* Set initial state */
-  activate_target_profile (data, data->active_profile);
+  activate_target_profile (data, data->active_profile, PPD_PROFILE_ACTIVATION_REASON_RESET);
 
   send_dbus_event (data, PROP_ALL);
 
+  data->was_started = TRUE;
+
   return;
 
 bail:
-  data->ret = 0;
+  data->ret = 1;
   g_debug ("Exiting because some non recoverable error occurred during startup");
-  g_main_loop_quit (main_loop);
+  g_main_loop_quit (data->main_loop);
+}
+
+void
+restart_profile_drivers (void)
+{
+  stop_profile_drivers (ppd_app);
+  start_profile_drivers (ppd_app);
+}
+
+static void
+name_acquired_handler (GDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  PpdApp *data = user_data;
+
+  start_profile_drivers (data);
 }
 
 static gboolean
@@ -555,13 +583,21 @@ free_app_data (PpdApp *data)
     data->name_id = 0;
   }
 
+  g_ptr_array_free (data->probed_drivers, TRUE);
   g_ptr_array_free (data->actions, TRUE);
-  g_ptr_array_free (data->drivers, TRUE);
+  g_clear_object (&data->driver);
 
+  g_clear_pointer (&data->main_loop, g_main_loop_unref);
   g_clear_pointer (&data->introspection_data, g_dbus_node_info_unref);
   g_clear_object (&data->connection);
-  g_clear_pointer (&main_loop, g_main_loop_unref);
   g_free (data);
+  ppd_app = NULL;
+}
+
+void
+main_loop_quit (void)
+{
+  g_main_loop_quit (ppd_app->main_loop);
 }
 
 int main (int argc, char **argv)
@@ -592,15 +628,16 @@ int main (int argc, char **argv)
     g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
 
   data = g_new0 (PpdApp, 1);
+  data->main_loop = g_main_loop_new (NULL, TRUE);
+  data->probed_drivers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
   data->actions = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-  data->drivers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
   data->active_profile = PPD_PROFILE_BALANCED;
+  ppd_app = data;
 
   /* Set up D-Bus */
   setup_dbus (data, replace);
 
-  main_loop = g_main_loop_new (NULL, TRUE);
-  g_main_loop_run (main_loop);
+  g_main_loop_run (data->main_loop);
   ret = data->ret;
   free_app_data (data);
 
diff --git a/src/powerprofilesctl.in b/src/powerprofilesctl.in
new file mode 100755
index 0000000..b525e91
--- /dev/null
+++ b/src/powerprofilesctl.in
@@ -0,0 +1,151 @@
+#!@PYTHON3@
+
+import sys
+from gi.repository import Gio, GLib
+
+VERSION = '@VERSION@'
+
+def usage_main():
+    print('Usage:')
+    print('  powerprofilesctl COMMAND [ARGS…]')
+    print('')
+    print('Commands:')
+    print('  help       Print help')
+    print('  version    Print version')
+    print('  get        Print the currently active power profile')
+    print('  set        Set the currently active power profile')
+    print('  list       List available power profiles')
+    print('')
+    print('Use “powerprofilesctl help COMMAND” to get detailed help.')
+
+def usage_version():
+    print('Usage:')
+    print('  powerprofilesctl version')
+    print('')
+    print('Print version information and exit.')
+
+def usage_get():
+    print('Usage:')
+    print('  powerprofilesctl get')
+    print('')
+    print('Print the currently active power profile.')
+
+def usage_set():
+    print('Usage:')
+    print('  powerprofilesctl set PROFILE')
+    print('')
+    print('Set the currently active power profile. Must be one of the ')
+    print('available profiles.')
+
+def usage_list():
+    print('Usage:')
+    print('  powerprofilesctl list')
+    print('')
+    print('List available power profiles.')
+
+def usage(_command=None):
+    if not _command:
+        usage_main()
+    elif _command == 'get':
+        usage_get()
+    elif _command == 'set':
+        usage_set()
+    elif _command == 'list':
+        usage_list()
+    elif _command == 'version':
+        usage_version()
+    else:
+        usage_main()
+
+def version():
+    print (VERSION)
+
+def get_proxy():
+    try:
+        bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+        proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
+                                       'net.hadess.PowerProfiles',
+                                       '/net/hadess/PowerProfiles',
+                                       'org.freedesktop.DBus.Properties', None)
+    except:
+        raise SystemError
+    return proxy
+
+def _get():
+    proxy = get_proxy()
+    profile = proxy.Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile')
+    print(profile)
+
+def _set(profile):
+    proxy = get_proxy()
+    proxy.Set('(ssv)',
+        'net.hadess.PowerProfiles',
+        'ActiveProfile',
+        GLib.Variant.new_string(profile))
+
+def get_profiles_property(prop):
+    try:
+        proxy = get_proxy()
+    except:
+        raise SystemError
+
+    profiles = None
+    try:
+        profiles = proxy.Get('(ss)', 'net.hadess.PowerProfiles', prop)
+    except:
+        raise ReferenceError
+    else:
+        return profiles
+
+def _list():
+    try:
+        profiles = get_profiles_property('Profiles')
+        reason = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'PerformanceInhibited')
+        inhibited = (reason != '')
+        active = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile')
+    except:
+        print("Couldn\'t get Profiles: ", sys.exc_info()[0])
+        raise SystemError
+    else:
+        index = 0
+        for profile in reversed(profiles):
+            if index > 0:
+                print('')
+            print(('%s %s:') % ('*' if profile['Profile'] == active else ' ', profile['Profile']))
+            print('    Driver:    ', profile['Driver'])
+            if profile['Profile'] == 'performance':
+                print('    Inhibited: ', f'yes ({reason})' if inhibited else 'no')
+            index += 1
+
+def main(): # pylint: disable=too-many-branches
+    args = None
+    if len(sys.argv) == 1:
+        command = 'list'
+    elif len(sys.argv) >= 2:
+        command = sys.argv[1]
+        if command == '--help':
+            command = 'help'
+        if command == '--version':
+            command = 'version'
+        else:
+            args = sys.argv[2:]
+
+    if command == 'help':
+        if len(args) > 0:
+            usage(args[0])
+        else:
+            usage(None)
+    elif command == 'version':
+        version()
+    elif command == 'get':
+        _get()
+    elif command == 'set':
+        if len(args) != 1:
+            usage_set()
+            sys.exit(1)
+        _set(args[0])
+    elif command == 'list':
+        _list()
+
+if __name__ == '__main__':
+    main()
diff --git a/src/ppd-driver-balanced.c b/src/ppd-driver-balanced.c
deleted file mode 100644
index 6ddbac9..0000000
--- a/src/ppd-driver-balanced.c
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- */
-
-#include "ppd-driver-balanced.h"
-
-struct _PpdDriverBalanced
-{
-  PpdDriver  parent_instance;
-};
-
-G_DEFINE_TYPE (PpdDriverBalanced, ppd_driver_balanced, PPD_TYPE_DRIVER)
-
-static GObject*
-ppd_driver_balanced_constructor (GType                  type,
-                                      guint                  n_construct_params,
-                                      GObjectConstructParam *construct_params)
-{
-  GObject *object;
-
-  object = G_OBJECT_CLASS (ppd_driver_balanced_parent_class)->constructor (type,
-                                                                                n_construct_params,
-                                                                                construct_params);
-  g_object_set (object,
-                "driver-name", "balanced",
-                "profiles", PPD_PROFILE_BALANCED,
-                NULL);
-
-  return object;
-}
-
-static void
-ppd_driver_balanced_class_init (PpdDriverBalancedClass *klass)
-{
-  GObjectClass *object_class;
-
-  object_class = G_OBJECT_CLASS(klass);
-  object_class->constructor = ppd_driver_balanced_constructor;
-}
-
-static void
-ppd_driver_balanced_init (PpdDriverBalanced *self)
-{
-}
diff --git a/src/ppd-driver-fake.c b/src/ppd-driver-fake.c
index e7d5975..431ba64 100644
--- a/src/ppd-driver-fake.c
+++ b/src/ppd-driver-fake.c
@@ -13,12 +13,14 @@
 #include <stdio.h>
 #include <termios.h>
 
-extern GMainLoop *main_loop;
+extern void main_loop_quit (void);
+void restart_profile_drivers (void);
 
 struct _PpdDriverFake
 {
   PpdDriver  parent_instance;
 
+  gboolean tio_set;
   struct termios old_tio;
   gboolean inhibited;
 };
@@ -37,7 +39,7 @@ ppd_driver_fake_constructor (GType                  type,
                                                                        construct_params);
   g_object_set (object,
                 "driver-name", "fake",
-                "profiles", PPD_PROFILE_PERFORMANCE,
+                "profiles", PPD_PROFILE_ALL,
                 NULL);
 
   return object;
@@ -56,7 +58,7 @@ toggle_inhibition (PpdDriverFake *fake)
 static void
 keyboard_usage (void)
 {
-  g_print ("Valid keys are: i (toggle inhibition), q/x (quit)\n");
+  g_print ("Valid keys are: i (toggle inhibition), r (restart drivers), q/x (quit)\n");
 }
 
 static gboolean
@@ -82,9 +84,13 @@ check_keyboard (GIOChannel    *source,
     g_print ("Toggling inhibition\n");
     toggle_inhibition (fake);
     break;
+  case 'r':
+    g_print ("Restarting profile drivers\n");
+    restart_profile_drivers ();
+    break;
   case 'q':
   case 'x':
-    g_main_loop_quit (main_loop);
+    main_loop_quit ();
     break;
   default:
     keyboard_usage ();
@@ -117,6 +123,7 @@ setup_keyboard (PpdDriverFake *fake)
   }
 
   g_io_add_watch (channel, G_IO_IN, (GIOFunc) check_keyboard, fake);
+  fake->tio_set = TRUE;
   return TRUE;
 }
 
@@ -134,19 +141,33 @@ envvar_set (const char *key)
   return TRUE;
 }
 
-static gboolean
-ppd_driver_fake_probe (PpdDriver *driver)
+static PpdProbeResult
+ppd_driver_fake_probe (PpdDriver  *driver,
+                       PpdProfile *prev_profile)
 {
   PpdDriverFake *fake;
 
   if (!envvar_set ("POWER_PROFILE_DAEMON_FAKE_DRIVER"))
-    return FALSE;
+    return PPD_PROBE_RESULT_FAIL;
 
   fake = PPD_DRIVER_FAKE (driver);
   if (!setup_keyboard (fake))
-    return FALSE;
+    return PPD_PROBE_RESULT_FAIL;
   keyboard_usage ();
 
+  return PPD_PROBE_RESULT_SUCCESS;
+}
+
+static gboolean
+ppd_driver_fake_activate_profile (PpdDriver                   *driver,
+                                  PpdProfile                   profile,
+                                  PpdProfileActivationReason   reason,
+                                  GError                     **error)
+{
+  g_print ("Receive '%s' profile activation for reason '%s'\n",
+           ppd_profile_to_str (profile),
+           ppd_profile_activation_reason_to_str (reason));
+
   return TRUE;
 }
 
@@ -156,7 +177,8 @@ ppd_driver_fake_finalize (GObject *object)
   PpdDriverFake *fake;
 
   fake = PPD_DRIVER_FAKE (object);
-  tcsetattr(STDIN_FILENO, TCSANOW, &fake->old_tio);
+  if (fake->tio_set)
+    tcsetattr(STDIN_FILENO, TCSANOW, &fake->old_tio);
   G_OBJECT_CLASS (ppd_driver_fake_parent_class)->finalize (object);
 }
 
@@ -172,6 +194,7 @@ ppd_driver_fake_class_init (PpdDriverFakeClass *klass)
 
   driver_class = PPD_DRIVER_CLASS(klass);
   driver_class->probe = ppd_driver_fake_probe;
+  driver_class->activate_profile = ppd_driver_fake_activate_profile;
 }
 
 static void
diff --git a/src/ppd-driver-intel-pstate.c b/src/ppd-driver-intel-pstate.c
index 4d21524..de84339 100644
--- a/src/ppd-driver-intel-pstate.c
+++ b/src/ppd-driver-intel-pstate.c
@@ -12,16 +12,14 @@
 #include "ppd-utils.h"
 #include "ppd-driver-intel-pstate.h"
 
-#define CPUFREQ_POLICY_DIR "/devices/system/cpu/cpufreq/"
-#define NO_TURBO_PATH "/devices/system/cpu/intel_pstate/no_turbo"
+#define CPUFREQ_POLICY_DIR "/sys/devices/system/cpu/cpufreq/"
+#define NO_TURBO_PATH "/sys/devices/system/cpu/intel_pstate/no_turbo"
 
 struct _PpdDriverIntelPstate
 {
   PpdDriver  parent_instance;
 
-  UpClient *client;
   PpdProfile activated_profile;
-  gboolean on_battery;
   GList *devices; /* GList of paths */
   GFileMonitor *no_turbo_mon;
   char *no_turbo_path;
@@ -29,9 +27,10 @@ struct _PpdDriverIntelPstate
 
 G_DEFINE_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD_TYPE_DRIVER)
 
-static gboolean ppd_driver_intel_pstate_activate_profile (PpdDriver   *driver,
-                                                          PpdProfile   profile,
-                                                          GError     **error);
+static gboolean ppd_driver_intel_pstate_activate_profile (PpdDriver                   *driver,
+                                                          PpdProfile                   profile,
+                                                          PpdProfileActivationReason   reason,
+                                                          GError                     **error);
 
 static GObject*
 ppd_driver_intel_pstate_constructor (GType                  type,
@@ -51,28 +50,6 @@ ppd_driver_intel_pstate_constructor (GType                  type,
   return object;
 }
 
-static void
-on_battery_changed (GObject    *gobject,
-                    GParamSpec *pspec,
-                    gpointer    user_data)
-{
-  PpdDriverIntelPstate *pstate = user_data;
-  gboolean old_on_battery;
-
-  old_on_battery = pstate->on_battery;
-  pstate->on_battery = up_client_get_on_battery (pstate->client);
-
-  if (pstate->activated_profile == PPD_PROFILE_BALANCED) {
-    ppd_driver_intel_pstate_activate_profile (PPD_DRIVER (pstate),
-                                              pstate->activated_profile,
-                                              NULL);
-  }
-
-  g_debug ("Battery status changed from %s to %s",
-           old_on_battery ? "on battery" : "on mains",
-           pstate->on_battery ? "on battery" : "on mains");
-}
-
 static void
 update_no_turbo (PpdDriverIntelPstate *pstate)
 {
@@ -80,6 +57,7 @@ update_no_turbo (PpdDriverIntelPstate *pstate)
   gboolean turbo_disabled = FALSE;
 
   if (g_file_get_contents (pstate->no_turbo_path, &contents, NULL, NULL)) {
+    contents = g_strchomp (contents);
     if (g_strcmp0 (contents, "1") == 0)
       turbo_disabled = TRUE;
   }
@@ -119,54 +97,30 @@ monitor_no_turbo_prop (const char *path)
   return g_file_monitor (no_turbo, G_FILE_MONITOR_NONE, NULL, NULL);
 }
 
-static char *
-get_no_turbo_path (void)
-{
-  const char *root;
-  g_autofree char *dir = NULL;
-
-  root = g_getenv ("UMOCKDEV_DIR");
-  if (!root || *root == '\0')
-    root = "/sys";
-
-  return g_build_filename (root, NO_TURBO_PATH, NULL);
-}
-
-static char *
-get_policy_dir (void)
-{
-  const char *root;
-  g_autofree char *dir = NULL;
-
-  root = g_getenv ("UMOCKDEV_DIR");
-  if (!root || *root == '\0')
-    root = "/sys";
-
-  return g_build_filename (root, CPUFREQ_POLICY_DIR, NULL);
-}
-
 static GDir *
 open_policy_dir (void)
 {
   g_autofree char *dir = NULL;
-  dir = get_policy_dir ();
+  dir = ppd_utils_get_sysfs_path (CPUFREQ_POLICY_DIR);
+  g_debug ("Opening policy dir '%s'", dir);
   return g_dir_open (dir, 0, NULL);
 }
 
 static gboolean
-ppd_driver_intel_pstate_probe (PpdDriver *driver)
+ppd_driver_intel_pstate_probe (PpdDriver  *driver,
+                               PpdProfile *prev_profile)
 {
   PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver);
   g_autoptr(GDir) dir = NULL;
   g_autofree char *policy_dir = NULL;
   const char *dirname;
-  gboolean ret = FALSE;
+  PpdProbeResult ret = PPD_PROBE_RESULT_FAIL;
 
   dir = open_policy_dir ();
   if (!dir)
     goto out;
 
-  policy_dir = get_policy_dir ();
+  policy_dir = ppd_utils_get_sysfs_path (CPUFREQ_POLICY_DIR);
   while ((dirname = g_dir_read_name (dir)) != NULL) {
     g_autofree char *path = NULL;
 
@@ -178,21 +132,14 @@ ppd_driver_intel_pstate_probe (PpdDriver *driver)
       continue;
 
     pstate->devices = g_list_prepend (pstate->devices, g_steal_pointer (&path));
-    ret = TRUE;
+    ret = PPD_PROBE_RESULT_SUCCESS;
   }
 
-  if (!ret)
+  if (ret != PPD_PROBE_RESULT_SUCCESS)
     goto out;
 
-  pstate->client = up_client_new ();
-  if (pstate->client) {
-    g_signal_connect (G_OBJECT (pstate->client), "notify::on-battery",
-                      G_CALLBACK (on_battery_changed), pstate);
-    pstate->on_battery = up_client_get_on_battery (pstate->client);
-  }
-
   /* Monitor the first "no_turbo" */
-  pstate->no_turbo_path = get_no_turbo_path ();
+  pstate->no_turbo_path = ppd_utils_get_sysfs_path (NO_TURBO_PATH);
   pstate->no_turbo_mon = monitor_no_turbo_prop (pstate->no_turbo_path);
   if (pstate->no_turbo_mon) {
     g_signal_connect (G_OBJECT (pstate->no_turbo_mon), "changed",
@@ -202,13 +149,12 @@ ppd_driver_intel_pstate_probe (PpdDriver *driver)
 
 out:
   g_debug ("%s p-state settings",
-           ret ? "Found" : "Didn't find");
+           ret == PPD_PROBE_RESULT_SUCCESS ? "Found" : "Didn't find");
   return ret;
 }
 
 static const char *
-profile_to_pref (PpdProfile profile,
-                 gboolean   on_battery)
+profile_to_pref (PpdProfile profile)
 {
   /* Note that we don't check "energy_performance_available_preferences"
    * as all the values are always available */
@@ -216,8 +162,6 @@ profile_to_pref (PpdProfile profile,
   case PPD_PROFILE_POWER_SAVER:
     return "power";
   case PPD_PROFILE_BALANCED:
-    if (on_battery)
-      return "balance_power";
     return "balance_performance";
   case PPD_PROFILE_PERFORMANCE:
     return "performance";
@@ -227,9 +171,10 @@ profile_to_pref (PpdProfile profile,
 }
 
 static gboolean
-ppd_driver_intel_pstate_activate_profile (PpdDriver   *driver,
-                                          PpdProfile   profile,
-                                          GError     **error)
+ppd_driver_intel_pstate_activate_profile (PpdDriver                    *driver,
+                                          PpdProfile                   profile,
+                                          PpdProfileActivationReason   reason,
+                                          GError                     **error)
 {
   PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver);
   gboolean ret = TRUE;
@@ -238,7 +183,7 @@ ppd_driver_intel_pstate_activate_profile (PpdDriver   *driver,
 
   g_return_val_if_fail (pstate->devices != NULL, FALSE);
 
-  pref = profile_to_pref (profile, pstate->on_battery);
+  pref = profile_to_pref (profile);
 
   for (l = pstate->devices; l != NULL; l = l->next) {
     const char *path = l->data;
@@ -261,7 +206,6 @@ ppd_driver_intel_pstate_finalize (GObject *object)
 
   driver = PPD_DRIVER_INTEL_PSTATE (object);
   g_clear_list (&driver->devices, g_free);
-  g_clear_object (&driver->client);
   g_clear_pointer (&driver->no_turbo_path, g_free);
   g_clear_object (&driver->no_turbo_mon);
   G_OBJECT_CLASS (ppd_driver_intel_pstate_parent_class)->finalize (object);
diff --git a/src/ppd-driver-lenovo-dytc.c b/src/ppd-driver-lenovo-dytc.c
deleted file mode 100644
index 422349b..0000000
--- a/src/ppd-driver-lenovo-dytc.c
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- */
-
-#include <gudev/gudev.h>
-#include <gio/gio.h>
-
-#include "ppd-driver-lenovo-dytc.h"
-#include "ppd-utils.h"
-
-#define LAPMODE_SYSFS_NAME "dytc_lapmode"
-#define PERFMODE_SYSFS_NAME "dytc_perfmode"
-
-struct _PpdDriverLenovoDytc
-{
-  PpdDriver  parent_instance;
-
-  GUdevClient *client;
-  GUdevDevice *device;
-  gboolean lapmode;
-};
-
-G_DEFINE_TYPE (PpdDriverLenovoDytc, ppd_driver_lenovo_dytc, PPD_TYPE_DRIVER)
-
-static GObject*
-ppd_driver_lenovo_dytc_constructor (GType                  type,
-                                    guint                  n_construct_params,
-                                    GObjectConstructParam *construct_params)
-{
-  GObject *object;
-
-  object = G_OBJECT_CLASS (ppd_driver_lenovo_dytc_parent_class)->constructor (type,
-                                                                              n_construct_params,
-                                                                              construct_params);
-  g_object_set (object,
-                "driver-name", "lenovo_dytc",
-                "profiles", PPD_PROFILE_PERFORMANCE | PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER,
-                NULL);
-
-  return object;
-}
-
-static gboolean
-sysfs_attr_as_boolean (GUdevDevice *device,
-                       const char  *attribute)
-{
-  g_autofree char *contents = NULL;
-  g_autofree char *filename = NULL;
-
-  filename = g_build_filename (g_udev_device_get_sysfs_path (device), attribute, NULL);
-  if (!g_file_get_contents (filename, &contents, NULL, NULL))
-    return FALSE;
-
-  g_strdelimit (contents, "\n", '\0');
-  return (g_strcmp0 (contents, "1") == 0);
-}
-
-static const char *
-profile_to_perfmode_value (PpdProfile profile)
-{
-  switch (profile) {
-  case PPD_PROFILE_POWER_SAVER:
-    return "L";
-  case PPD_PROFILE_BALANCED:
-    return "M";
-  case PPD_PROFILE_PERFORMANCE:
-    return "H";
-  }
-
-  g_assert_not_reached ();
-}
-
-static void
-update_dytc_state (PpdDriverLenovoDytc *dytc)
-{
-  gboolean new_lapmode;
-
-  new_lapmode = sysfs_attr_as_boolean (dytc->device, LAPMODE_SYSFS_NAME);
-  if (new_lapmode != dytc->lapmode) {
-    dytc->lapmode = new_lapmode;
-    g_debug ("dytc_lapmode is now %s, so profile is %s",
-             dytc->lapmode ? "on" : "off",
-             dytc->lapmode ? "inhibited" : "uninhibited");
-    g_object_set (G_OBJECT (dytc),
-                  "performance-inhibited", dytc->lapmode ? "lap-detected" : NULL,
-                  NULL);
-  }
-}
-
-static void
-uevent_cb (GUdevClient *client,
-           gchar       *action,
-           GUdevDevice *device,
-           gpointer     user_data)
-{
-  PpdDriverLenovoDytc *dytc = user_data;
-
-  if (g_strcmp0 (action, "change") != 0)
-    return;
-
-  if (g_strcmp0 (g_udev_device_get_sysfs_path (device),
-                 g_udev_device_get_sysfs_path (dytc->device)) != 0)
-      return;
-
-  update_dytc_state (dytc);
-}
-
-static gboolean
-ppd_driver_lenovo_dytc_activate_profile (PpdDriver   *driver,
-                                         PpdProfile   profile,
-                                         GError     **error)
-{
-  PpdDriverLenovoDytc *dytc = PPD_DRIVER_LENOVO_DYTC (driver);
-
-  g_return_val_if_fail (dytc->client, FALSE);
-
-  if (profile == PPD_PROFILE_PERFORMANCE &&
-      dytc->lapmode) {
-    g_debug ("Can't switch to performance mode, lapmode is detected");
-    g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Mode is inhibited");
-    return FALSE;
-  }
-
-  if (!ppd_utils_write_sysfs (dytc->device, PERFMODE_SYSFS_NAME, profile_to_perfmode_value (profile), error)) {
-    g_debug ("Failed to write to perfmode: %s", (* error)->message);
-    return FALSE;
-  }
-
-  g_debug ("Successfully switched to profile %s", ppd_profile_to_str (profile));
-  return TRUE;
-}
-
-static gboolean
-ppd_driver_lenovo_dytc_probe (PpdDriver *driver)
-{
-  const gchar * const subsystem[] = { "platform", NULL };
-  GList *devices, *l;
-  gboolean ret = FALSE;
-  PpdDriverLenovoDytc *dytc = PPD_DRIVER_LENOVO_DYTC (driver);
-
-  g_return_val_if_fail (!dytc->client, FALSE);
-
-  dytc->client = g_udev_client_new (subsystem);
-  devices = g_udev_client_query_by_subsystem (dytc->client, "platform");
-  if (devices == NULL)
-    goto out;
-
-  for (l = devices; l != NULL; l = l->next) {
-    GUdevDevice *dev = l->data;
-
-    if (g_strcmp0 (g_udev_device_get_name (dev), "thinkpad_acpi") != 0)
-      continue;
-
-    if (!g_udev_device_get_sysfs_attr (dev, LAPMODE_SYSFS_NAME) ||
-        !g_udev_device_get_sysfs_attr (dev, PERFMODE_SYSFS_NAME))
-      break;
-
-    dytc->device = g_object_ref (dev);
-    ret = TRUE;
-    break;
-  }
-
-  if (ret) {
-    g_signal_connect (G_OBJECT (dytc->client), "uevent",
-                      G_CALLBACK (uevent_cb), dytc);
-    update_dytc_state (dytc);
-  }
-
-out:
-  g_list_free_full (devices, g_object_unref);
-
-  g_debug ("%s a dytc_lapmode sysfs attribute to thinkpad_acpi",
-           ret ? "Found" : "Didn't find");
-  return ret;
-}
-
-static void
-ppd_driver_lenovo_dytc_finalize (GObject *object)
-{
-  PpdDriverLenovoDytc *driver;
-
-  driver = PPD_DRIVER_LENOVO_DYTC (object);
-  g_clear_object (&driver->device);
-  g_clear_object (&driver->client);
-  G_OBJECT_CLASS (ppd_driver_lenovo_dytc_parent_class)->finalize (object);
-}
-
-static void
-ppd_driver_lenovo_dytc_class_init (PpdDriverLenovoDytcClass *klass)
-{
-  GObjectClass *object_class;
-  PpdDriverClass *driver_class;
-
-  object_class = G_OBJECT_CLASS(klass);
-  object_class->constructor = ppd_driver_lenovo_dytc_constructor;
-  object_class->finalize = ppd_driver_lenovo_dytc_finalize;
-
-  driver_class = PPD_DRIVER_CLASS(klass);
-  driver_class->probe = ppd_driver_lenovo_dytc_probe;
-  driver_class->activate_profile = ppd_driver_lenovo_dytc_activate_profile;
-}
-
-static void
-ppd_driver_lenovo_dytc_init (PpdDriverLenovoDytc *self)
-{
-}
diff --git a/src/ppd-driver-placeholder.c b/src/ppd-driver-placeholder.c
new file mode 100644
index 0000000..11d9e93
--- /dev/null
+++ b/src/ppd-driver-placeholder.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+#include "ppd-driver-placeholder.h"
+
+struct _PpdDriverPlaceholder
+{
+  PpdDriver  parent_instance;
+};
+
+G_DEFINE_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD_TYPE_DRIVER)
+
+static GObject*
+ppd_driver_placeholder_constructor (GType                  type,
+                                    guint                  n_construct_params,
+                                    GObjectConstructParam *construct_params)
+{
+  GObject *object;
+
+  object = G_OBJECT_CLASS (ppd_driver_placeholder_parent_class)->constructor (type,
+                                                                              n_construct_params,
+                                                                              construct_params);
+  g_object_set (object,
+                "driver-name", "placeholder",
+                "profiles", PPD_PROFILE_POWER_SAVER | PPD_PROFILE_BALANCED,
+                NULL);
+
+  return object;
+}
+
+static void
+ppd_driver_placeholder_class_init (PpdDriverPlaceholderClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS(klass);
+  object_class->constructor = ppd_driver_placeholder_constructor;
+}
+
+static void
+ppd_driver_placeholder_init (PpdDriverPlaceholder *self)
+{
+}
diff --git a/src/ppd-driver-lenovo-dytc.h b/src/ppd-driver-placeholder.h
similarity index 64%
rename from src/ppd-driver-lenovo-dytc.h
rename to src/ppd-driver-placeholder.h
index 64157cb..a822790 100644
--- a/src/ppd-driver-lenovo-dytc.h
+++ b/src/ppd-driver-placeholder.h
@@ -11,5 +11,5 @@
 
 #include "ppd-driver.h"
 
-#define PPD_TYPE_DRIVER_LENOVO_DYTC (ppd_driver_lenovo_dytc_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverLenovoDytc, ppd_driver_lenovo_dytc, PPD, DRIVER_LENOVO_DYTC, PpdDriver)
+#define PPD_TYPE_DRIVER_PLACEHOLDER (ppd_driver_placeholder_get_type())
+G_DECLARE_FINAL_TYPE(PpdDriverPlaceholder, ppd_driver_placeholder, PPD, DRIVER_PLACEHOLDER, PpdDriver)
diff --git a/src/ppd-driver-platform-profile.c b/src/ppd-driver-platform-profile.c
new file mode 100644
index 0000000..9e20ee8
--- /dev/null
+++ b/src/ppd-driver-platform-profile.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+#include <gudev/gudev.h>
+#include <gio/gio.h>
+
+#include "ppd-driver-platform-profile.h"
+#include "ppd-utils.h"
+
+#define LAPMODE_SYSFS_NAME "dytc_lapmode"
+#define ACPI_PLATFORM_PROFILE_PATH "/sys/firmware/acpi/platform_profile"
+#define ACPI_PLATFORM_PROFILE_CHOICES_PATH "/sys/firmware/acpi/platform_profile_choices"
+
+struct _PpdDriverPlatformProfile
+{
+  PpdDriver  parent_instance;
+
+  PpdProbeResult probe_result;
+  GUdevDevice *device;
+  int lapmode;
+  PpdProfile acpi_platform_profile;
+  char **profile_choices;
+  GFileMonitor *lapmode_mon;
+  GFileMonitor *acpi_platform_profile_mon;
+  guint acpi_platform_profile_changed_id;
+};
+
+G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER)
+
+static GObject*
+ppd_driver_platform_profile_constructor (GType                  type,
+                                         guint                  n_construct_params,
+                                         GObjectConstructParam *construct_params)
+{
+  GObject *object;
+
+  object = G_OBJECT_CLASS (ppd_driver_platform_profile_parent_class)->constructor (type,
+                                                                                   n_construct_params,
+                                                                                   construct_params);
+  g_object_set (object,
+                "driver-name", "platform_profile",
+                "profiles", PPD_PROFILE_PERFORMANCE | PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER,
+                NULL);
+
+  return object;
+}
+
+static const char *
+profile_to_acpi_platform_profile_value (PpdDriverPlatformProfile *self,
+                                        PpdProfile                profile)
+{
+  switch (profile) {
+  case PPD_PROFILE_POWER_SAVER:
+    if (g_strv_contains ((const char * const*) self->profile_choices, "low-power"))
+      return "low-power";
+    return "cool";
+  case PPD_PROFILE_BALANCED:
+    return "balanced";
+  case PPD_PROFILE_PERFORMANCE:
+    return "performance";
+  }
+
+  g_assert_not_reached ();
+}
+
+static PpdProfile
+acpi_platform_profile_value_to_profile (const char *str)
+{
+  if (str == NULL)
+    return PPD_PROFILE_UNSET;
+
+  switch (str[0]) {
+  case 'l': /* low-power */
+  case 'c': /* cool */
+  case 'q': /* quiet */
+    return PPD_PROFILE_POWER_SAVER;
+  case 'b':
+    return PPD_PROFILE_BALANCED;
+  case 'p':
+    return PPD_PROFILE_PERFORMANCE;
+  default:
+    g_debug ("Got unsupported performance_profile value '%s'", str);
+  }
+
+  return PPD_PROFILE_UNSET;
+}
+
+static PpdProfile
+read_platform_profile (void)
+{
+  g_autofree char *platform_profile_path = NULL;
+  g_autofree char *new_profile_str = NULL;
+  g_autoptr(GError) error = NULL;
+  PpdProfile new_profile;
+
+  platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH);
+  if (!g_file_get_contents (platform_profile_path,
+                            &new_profile_str, NULL, NULL)) {
+    g_debug ("Failed to get contents for '%s': %s",
+             platform_profile_path,
+             error->message);
+    return PPD_PROFILE_UNSET;
+  }
+
+  new_profile = acpi_platform_profile_value_to_profile (new_profile_str);
+  g_debug ("ACPI performance_profile is now %c, so profile is detected as %s",
+           new_profile_str[0],
+           ppd_profile_to_str (new_profile));
+  return new_profile;
+}
+
+static gboolean
+save_platform_profile_choices (PpdDriverPlatformProfile *self)
+{
+  g_autofree char *platform_profile_choices_path = NULL;
+  g_autofree char *choices_str = NULL;
+  g_autoptr(GError) error = NULL;
+
+  platform_profile_choices_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_CHOICES_PATH);
+  if (!g_file_get_contents (platform_profile_choices_path,
+                            &choices_str, NULL, NULL)) {
+    return FALSE;
+  }
+
+  self->profile_choices = g_strsplit_set (choices_str, " \n", -1);
+  return TRUE;
+}
+
+static PpdProbeResult
+verify_acpi_platform_profile_choices (PpdDriverPlatformProfile *self)
+{
+  const char * const *choices = (const char * const*) self->profile_choices;
+
+  if ((g_strv_contains (choices, "low-power") ||
+       g_strv_contains (choices, "cool")) &&
+      g_strv_contains (choices, "balanced") &&
+      g_strv_contains (choices, "performance"))
+    return PPD_PROBE_RESULT_SUCCESS;
+  return PPD_PROBE_RESULT_DEFER;
+}
+
+static void
+update_dytc_lapmode_state (PpdDriverPlatformProfile *self)
+{
+  int new_lapmode;
+
+  new_lapmode = g_udev_device_get_sysfs_attr_as_int_uncached (self->device, LAPMODE_SYSFS_NAME);
+  if (new_lapmode == self->lapmode)
+    return;
+
+  self->lapmode = new_lapmode;
+  g_debug ("dytc_lapmode is now %s, so profile is %s",
+           self->lapmode ? "on" : "off",
+           self->lapmode ? "inhibited" : "uninhibited");
+  g_object_set (G_OBJECT (self),
+                "performance-inhibited", self->lapmode ? "lap-detected" : NULL,
+                NULL);
+}
+
+static void
+update_acpi_platform_profile_state (PpdDriverPlatformProfile *self)
+{
+  PpdProfile new_profile;
+
+  new_profile = read_platform_profile ();
+  if (new_profile == PPD_PROFILE_UNSET ||
+      new_profile == self->acpi_platform_profile)
+    return;
+
+  self->acpi_platform_profile = new_profile;
+  ppd_driver_emit_profile_changed (PPD_DRIVER (self), new_profile);
+}
+
+static void
+lapmode_changed (GFileMonitor      *monitor,
+                 GFile             *file,
+                 GFile             *other_file,
+                 GFileMonitorEvent  event_type,
+                 gpointer           user_data)
+{
+  PpdDriverPlatformProfile *self = user_data;
+  g_debug (LAPMODE_SYSFS_NAME " attribute changed");
+  update_dytc_lapmode_state (self);
+}
+
+static void
+acpi_platform_profile_changed (GFileMonitor      *monitor,
+                               GFile             *file,
+                               GFile             *other_file,
+                               GFileMonitorEvent  event_type,
+                               gpointer           user_data)
+{
+  PpdDriverPlatformProfile *self = user_data;
+  g_debug (ACPI_PLATFORM_PROFILE_PATH " changed");
+  if (self->probe_result == PPD_PROBE_RESULT_DEFER) {
+    g_signal_emit_by_name (G_OBJECT (self), "probe-request", 0);
+    return;
+  }
+  update_acpi_platform_profile_state (self);
+}
+
+static gboolean
+ppd_driver_platform_profile_activate_profile (PpdDriver                   *driver,
+                                              PpdProfile                   profile,
+                                              PpdProfileActivationReason   reason,
+                                              GError                     **error)
+{
+  PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver);
+  g_autofree char *platform_profile_path = NULL;
+
+  g_return_val_if_fail (self->acpi_platform_profile_mon, FALSE);
+
+  if (self->acpi_platform_profile == profile) {
+    g_debug ("Can't switch to %s mode, already there",
+             ppd_profile_to_str (profile));
+    return TRUE;
+  }
+
+  if (profile == PPD_PROFILE_PERFORMANCE &&
+      self->lapmode) {
+    g_debug ("Can't switch to performance mode, lapmode is detected");
+    g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Mode is inhibited");
+    return FALSE;
+  }
+
+  g_signal_handler_block (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id);
+  platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH);
+  if (!ppd_utils_write (platform_profile_path, profile_to_acpi_platform_profile_value (self, profile), error)) {
+    g_debug ("Failed to write to acpi_platform_profile: %s", (* error)->message);
+    g_signal_handler_unblock (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id);
+    return FALSE;
+  }
+  g_signal_handler_unblock (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id);
+
+  g_debug ("Successfully switched to profile %s", ppd_profile_to_str (profile));
+  self->acpi_platform_profile = profile;
+  return TRUE;
+}
+
+static int
+find_dytc (GUdevDevice *dev,
+           gpointer     user_data)
+{
+  if (g_strcmp0 (g_udev_device_get_name (dev), "thinkpad_acpi") != 0)
+    return 1;
+
+  if (!g_udev_device_get_sysfs_attr (dev, LAPMODE_SYSFS_NAME))
+    return 1;
+
+  return 0;
+}
+
+static PpdProbeResult
+ppd_driver_platform_profile_probe (PpdDriver  *driver,
+                                   PpdProfile *prev_profile)
+{
+  PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver);
+  g_autoptr(GFile) acpi_platform_profile = NULL;
+  g_autofree char *platform_profile_path = NULL;
+
+  g_return_val_if_fail (self->probe_result == PPD_PROBE_RESULT_UNSET, PPD_PROBE_RESULT_FAIL);
+
+  /* Profile interface */
+  platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH);
+  if (!g_file_test (platform_profile_path, G_FILE_TEST_EXISTS)) {
+    g_debug ("No platform_profile sysfs file");
+    return PPD_PROBE_RESULT_FAIL;
+  }
+  if (!save_platform_profile_choices (self))
+    return PPD_PROBE_RESULT_FAIL;
+  self->probe_result = verify_acpi_platform_profile_choices (self);
+  if (self->probe_result == PPD_PROBE_RESULT_FAIL) {
+    g_debug ("No supported platform_profile choices");
+    return self->probe_result;
+  }
+
+  acpi_platform_profile = g_file_new_for_path (platform_profile_path);
+  self->acpi_platform_profile_mon = g_file_monitor (acpi_platform_profile,
+                                                    G_FILE_MONITOR_NONE,
+                                                    NULL,
+                                                    NULL);
+  self->acpi_platform_profile_changed_id =
+    g_signal_connect (G_OBJECT (self->acpi_platform_profile_mon), "changed",
+                      G_CALLBACK (acpi_platform_profile_changed), self);
+  if (self->probe_result == PPD_PROBE_RESULT_DEFER) {
+    g_debug ("Monitoring platform_profile sysfs file");
+    return self->probe_result;
+  }
+
+  *prev_profile = read_platform_profile ();
+
+  /* Lenovo-specific proximity sensor */
+  self->device = ppd_utils_find_device ("platform",
+                                        (GCompareFunc) find_dytc,
+                                        NULL);
+  if (!self->device)
+    goto out;
+
+  self->lapmode_mon = ppd_utils_monitor_sysfs_attr (self->device,
+                                                    LAPMODE_SYSFS_NAME,
+                                                    NULL);
+  g_signal_connect (G_OBJECT (self->lapmode_mon), "changed",
+                    G_CALLBACK (lapmode_changed), self);
+  update_dytc_lapmode_state (self);
+
+out:
+  update_acpi_platform_profile_state (self);
+
+  g_debug ("%s a dytc_lapmode sysfs attribute to thinkpad_acpi",
+           self->device ? "Found" : "Didn't find");
+  return PPD_PROBE_RESULT_SUCCESS;
+}
+
+static void
+ppd_driver_platform_profile_finalize (GObject *object)
+{
+  PpdDriverPlatformProfile *driver;
+
+  driver = PPD_DRIVER_PLATFORM_PROFILE (object);
+  g_clear_pointer (&driver->profile_choices, g_strfreev);
+  g_clear_object (&driver->device);
+  g_clear_object (&driver->lapmode_mon);
+  g_clear_object (&driver->acpi_platform_profile_mon);
+  G_OBJECT_CLASS (ppd_driver_platform_profile_parent_class)->finalize (object);
+}
+
+static void
+ppd_driver_platform_profile_class_init (PpdDriverPlatformProfileClass *klass)
+{
+  GObjectClass *object_class;
+  PpdDriverClass *driver_class;
+
+  object_class = G_OBJECT_CLASS(klass);
+  object_class->constructor = ppd_driver_platform_profile_constructor;
+  object_class->finalize = ppd_driver_platform_profile_finalize;
+
+  driver_class = PPD_DRIVER_CLASS(klass);
+  driver_class->probe = ppd_driver_platform_profile_probe;
+  driver_class->activate_profile = ppd_driver_platform_profile_activate_profile;
+}
+
+static void
+ppd_driver_platform_profile_init (PpdDriverPlatformProfile *self)
+{
+  self->probe_result = PPD_PROBE_RESULT_UNSET;
+}
diff --git a/src/ppd-driver-balanced.h b/src/ppd-driver-platform-profile.h
similarity index 59%
rename from src/ppd-driver-balanced.h
rename to src/ppd-driver-platform-profile.h
index ce829ad..916fd7e 100644
--- a/src/ppd-driver-balanced.h
+++ b/src/ppd-driver-platform-profile.h
@@ -11,5 +11,5 @@
 
 #include "ppd-driver.h"
 
-#define PPD_TYPE_DRIVER_BALANCED (ppd_driver_balanced_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverBalanced, ppd_driver_balanced, PPD, DRIVER_BALANCED, PpdDriver)
+#define PPD_TYPE_DRIVER_PLATFORM_PROFILE (ppd_driver_platform_profile_get_type())
+G_DECLARE_FINAL_TYPE(PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD, DRIVER_PLATFORM_PROFILE, PpdDriver)
diff --git a/src/ppd-driver-power-saver.c b/src/ppd-driver-power-saver.c
deleted file mode 100644
index d0ab9df..0000000
--- a/src/ppd-driver-power-saver.c
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- */
-
-#include "ppd-driver-power-saver.h"
-
-struct _PpdDriverPowerSaver
-{
-  PpdDriver  parent_instance;
-};
-
-G_DEFINE_TYPE (PpdDriverPowerSaver, ppd_driver_power_saver, PPD_TYPE_DRIVER)
-
-static GObject*
-ppd_driver_power_saver_constructor (GType                  type,
-                                         guint                  n_construct_params,
-                                         GObjectConstructParam *construct_params)
-{
-  GObject *object;
-
-  object = G_OBJECT_CLASS (ppd_driver_power_saver_parent_class)->constructor (type,
-                                                                                   n_construct_params,
-                                                                                   construct_params);
-  g_object_set (object,
-                "driver-name", "power-saver",
-                "profiles", PPD_PROFILE_POWER_SAVER,
-                NULL);
-
-  return object;
-}
-
-static void
-ppd_driver_power_saver_class_init (PpdDriverPowerSaverClass *klass)
-{
-  GObjectClass *object_class;
-
-  object_class = G_OBJECT_CLASS(klass);
-  object_class->constructor = ppd_driver_power_saver_constructor;
-}
-
-static void
-ppd_driver_power_saver_init (PpdDriverPowerSaver *self)
-{
-}
diff --git a/src/ppd-driver-power-saver.h b/src/ppd-driver-power-saver.h
deleted file mode 100644
index 7b1ee49..0000000
--- a/src/ppd-driver-power-saver.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (c) 2020 Bastien Nocera <hadess at hadess.net>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- */
-
-#pragma once
-
-#include "ppd-driver.h"
-
-#define PPD_TYPE_DRIVER_POWER_SAVER (ppd_driver_power_saver_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverPowerSaver, ppd_driver_power_saver, PPD, DRIVER_POWER_SAVER, PpdDriver)
diff --git a/src/ppd-driver.c b/src/ppd-driver.c
index 0bfc2bf..8ccd305 100644
--- a/src/ppd-driver.c
+++ b/src/ppd-driver.c
@@ -53,6 +53,14 @@ enum {
   PROP_PERFORMANCE_INHIBITED
 };
 
+enum {
+  PROFILE_CHANGED,
+  PROBE_REQUEST,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 #define PPD_DRIVER_GET_PRIVATE(o) (ppd_driver_get_instance_private (o))
 G_DEFINE_TYPE_WITH_PRIVATE (PpdDriver, ppd_driver, G_TYPE_OBJECT)
 
@@ -128,6 +136,41 @@ ppd_driver_class_init (PpdDriverClass *klass)
   object_class->get_property = ppd_driver_get_property;
   object_class->set_property = ppd_driver_set_property;
 
+  /**
+   * PpdDriver::profile-changed:
+   * @profile: the updated #PpdProfile
+   *
+   * Emitted when the profile was changed from the outside, usually
+   * by key combinations implemented in firmware.
+   */
+  signals[PROFILE_CHANGED] = g_signal_new ("profile-changed",
+                                           G_TYPE_FROM_CLASS (klass),
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           g_cclosure_marshal_generic,
+                                           G_TYPE_NONE,
+                                           1,
+                                           PPD_TYPE_PROFILE);
+
+
+  /**
+   * PpdDriver::probe-request
+   *
+   * The driver requested to be reprobed, because it became available.
+   */
+  signals[PROBE_REQUEST] = g_signal_new ("probe-request",
+                                         G_TYPE_FROM_CLASS (klass),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         g_cclosure_marshal_generic,
+                                         G_TYPE_NONE,
+                                         0,
+                                         G_TYPE_NONE);
+
   /**
    * PpdDriver::driver-name:
    *
@@ -172,21 +215,32 @@ ppd_driver_init (PpdDriver *self)
 {
 }
 
-gboolean
-ppd_driver_probe (PpdDriver *driver)
+PpdProbeResult
+ppd_driver_probe (PpdDriver  *driver,
+                  PpdProfile *previous_profile)
 {
+  PpdProfile profile = PPD_PROFILE_UNSET;
+  PpdProbeResult ret;
+
   g_return_val_if_fail (PPD_IS_DRIVER (driver), FALSE);
+  g_return_val_if_fail (previous_profile != NULL, FALSE);
 
   if (!PPD_DRIVER_GET_CLASS (driver)->probe)
-    return TRUE;
-
-  return PPD_DRIVER_GET_CLASS (driver)->probe (driver);
+    return PPD_PROBE_RESULT_SUCCESS;
+
+  ret = PPD_DRIVER_GET_CLASS (driver)->probe (driver, &profile);
+  if (ret == PPD_PROBE_RESULT_SUCCESS &&
+      profile != PPD_PROFILE_UNSET &&
+      ppd_profile_has_single_flag (profile))
+    *previous_profile = profile;
+  return ret;
 }
 
 gboolean
-ppd_driver_activate_profile (PpdDriver  *driver,
-                             PpdProfile         profile,
-                             GError           **error)
+ppd_driver_activate_profile (PpdDriver                   *driver,
+                             PpdProfile                   profile,
+                             PpdProfileActivationReason   reason,
+                             GError                     **error)
 {
   g_return_val_if_fail (PPD_IS_DRIVER (driver), FALSE);
   g_return_val_if_fail (ppd_profile_has_single_flag (profile), FALSE);
@@ -194,7 +248,7 @@ ppd_driver_activate_profile (PpdDriver  *driver,
   if (!PPD_DRIVER_GET_CLASS (driver)->activate_profile)
     return TRUE;
 
-  return PPD_DRIVER_GET_CLASS (driver)->activate_profile (driver, profile, error);
+  return PPD_DRIVER_GET_CLASS (driver)->activate_profile (driver, profile, reason, error);
 }
 
 const char *
@@ -252,3 +306,32 @@ ppd_driver_is_performance_inhibited (PpdDriver *driver)
 
   return (priv->performance_inhibited != NULL);
 }
+
+void
+ppd_driver_emit_profile_changed (PpdDriver  *driver,
+                                 PpdProfile  profile)
+{
+  g_return_if_fail (PPD_IS_DRIVER (driver));
+  g_return_if_fail (ppd_profile_has_single_flag (profile));
+
+  g_signal_emit_by_name (G_OBJECT (driver),
+                         "profile-changed",
+                         profile);
+}
+
+const char *
+ppd_profile_activation_reason_to_str (PpdProfileActivationReason reason)
+{
+  switch (reason) {
+  case PPD_PROFILE_ACTIVATION_REASON_INHIBITION:
+    return "inhibition";
+  case PPD_PROFILE_ACTIVATION_REASON_INTERNAL:
+    return "internal";
+  case PPD_PROFILE_ACTIVATION_REASON_RESET:
+    return "reset";
+  case PPD_PROFILE_ACTIVATION_REASON_USER:
+    return "user";
+  default:
+    g_assert_not_reached ();
+  }
+}
diff --git a/src/ppd-driver.h b/src/ppd-driver.h
index b405fe4..7885c1e 100644
--- a/src/ppd-driver.h
+++ b/src/ppd-driver.h
@@ -15,6 +15,46 @@
 #define PPD_TYPE_DRIVER (ppd_driver_get_type())
 G_DECLARE_DERIVABLE_TYPE(PpdDriver, ppd_driver, PPD, DRIVER, GObject)
 
+/**
+ * PpdProbeResult:
+ * @PPD_PROBE_RESULT_UNSET: unset
+ * @PPD_PROBE_RESULT_DEFER: driver should be kept alive, as kernel
+ *   support might appear.
+ * @PPD_PROBE_RESULT_FAIL: driver failed to load.
+ * @PPD_PROBE_RESULT_SUCCESS: driver successfully loaded.
+ *
+ * Those are the three possible values returned by a driver probe,
+ * along with an unset value for convenience.
+ */
+typedef enum {
+  PPD_PROBE_RESULT_UNSET = -2,
+  PPD_PROBE_RESULT_DEFER = -1,
+  PPD_PROBE_RESULT_FAIL = 0,
+  PPD_PROBE_RESULT_SUCCESS = 1
+} PpdProbeResult;
+
+/**
+ * PpdProfileActivationReason:
+ * PPD_PROFILE_ACTIVATION_REASON_INHIBITION: switching profiles because
+ *   of performance profile inhibition.
+ * PPD_PROFILE_ACTIVATION_REASON_INTERNAL: the driver profile changed
+ *   internally, usually because of a key combination.
+ * PPD_PROFILE_ACTIVATION_REASON_RESET: setting profile on startup, or
+ *   because drivers are getting reprobed.
+ * PPD_PROFILE_ACTIVATION_REASON_USER: setting profile because the user
+ *   requested it.
+ *
+ * Those are possible reasons for a profile being activated. Based on those
+ * reasons, drivers can choose whether or not that changes the effective
+ * profile internally.
+ */
+typedef enum{
+  PPD_PROFILE_ACTIVATION_REASON_INHIBITION = 0,
+  PPD_PROFILE_ACTIVATION_REASON_INTERNAL,
+  PPD_PROFILE_ACTIVATION_REASON_RESET,
+  PPD_PROFILE_ACTIVATION_REASON_USER
+} PpdProfileActivationReason;
+
 /**
  * PpdDriverClass:
  * @parent_class: The parent class.
@@ -28,17 +68,22 @@ struct _PpdDriverClass
 {
   GObjectClass   parent_class;
 
-  gboolean       (* probe)            (PpdDriver   *driver);
-  gboolean       (* activate_profile) (PpdDriver   *driver,
-                                       PpdProfile   profile,
-                                       GError     **error);
+  PpdProbeResult (* probe)            (PpdDriver                   *driver,
+                                       PpdProfile                  *previous_profile);
+  gboolean       (* activate_profile) (PpdDriver                   *driver,
+                                       PpdProfile                   profile,
+                                       PpdProfileActivationReason   reason,
+                                       GError                     **error);
 };
 
 #ifndef __GTK_DOC_IGNORE__
-gboolean ppd_driver_probe (PpdDriver *driver);
-gboolean ppd_driver_activate_profile (PpdDriver *driver, PpdProfile profile, GError **error);
+PpdProbeResult ppd_driver_probe (PpdDriver *driver, PpdProfile *previous_profile);
+gboolean ppd_driver_activate_profile (PpdDriver *driver,
+  PpdProfile profile, PpdProfileActivationReason reason, GError **error);
 const char *ppd_driver_get_driver_name (PpdDriver *driver);
 PpdProfile ppd_driver_get_profiles (PpdDriver *driver);
 const char *ppd_driver_get_performance_inhibited (PpdDriver *driver);
 gboolean ppd_driver_is_performance_inhibited (PpdDriver *driver);
+void ppd_driver_emit_profile_changed (PpdDriver *driver, PpdProfile profile);
+const char *ppd_profile_activation_reason_to_str (PpdProfileActivationReason reason);
 #endif
diff --git a/src/ppd-utils.c b/src/ppd-utils.c
index d80e7cf..553a1e0 100644
--- a/src/ppd-utils.c
+++ b/src/ppd-utils.c
@@ -12,6 +12,18 @@
 #include <stdio.h>
 #include <errno.h>
 
+char *
+ppd_utils_get_sysfs_path (const char *filename)
+{
+  const char *root;
+
+  root = g_getenv ("UMOCKDEV_DIR");
+  if (!root || *root == '\0')
+    root = "/";
+
+  return g_build_filename (root, filename, NULL);
+}
+
 gboolean ppd_utils_write (const char  *filename,
                           const char  *value,
                           GError     **error)
@@ -54,3 +66,52 @@ gboolean ppd_utils_write_sysfs (GUdevDevice  *device,
   filename = g_build_filename (g_udev_device_get_sysfs_path (device), attribute, NULL);
   return ppd_utils_write (filename, value, error);
 }
+
+GFileMonitor *
+ppd_utils_monitor_sysfs_attr (GUdevDevice  *device,
+                              const char   *attribute,
+                              GError      **error)
+{
+  g_autofree char *path = NULL;
+  g_autoptr(GFile) file = NULL;
+
+  path = g_build_filename (g_udev_device_get_sysfs_path (device), attribute, NULL);
+  file = g_file_new_for_path (path);
+  return g_file_monitor_file (file,
+                              G_FILE_MONITOR_NONE,
+                              NULL,
+                              error);
+}
+
+GUdevDevice *
+ppd_utils_find_device (const char   *subsystem,
+                       GCompareFunc  func,
+                       gpointer      user_data)
+{
+  const gchar * subsystems[] = { NULL, NULL };
+  g_autoptr(GUdevClient) client = NULL;
+  GUdevDevice *ret = NULL;
+  GList *devices, *l;
+
+  g_return_val_if_fail (subsystem != NULL, NULL);
+  g_return_val_if_fail (func != NULL, NULL);
+
+  subsystems[0] = subsystem;
+  client = g_udev_client_new (subsystems);
+  devices = g_udev_client_query_by_subsystem (client, subsystem);
+  if (devices == NULL)
+    return NULL;
+
+  for (l = devices; l != NULL; l = l->next) {
+    GUdevDevice *dev = l->data;
+
+    if ((func) (dev, user_data) != 0)
+      continue;
+
+    ret = g_object_ref (dev);
+    break;
+  }
+  g_list_free_full (devices, g_object_unref);
+
+  return ret;
+}
diff --git a/src/ppd-utils.h b/src/ppd-utils.h
index bee7f5a..9b12e5b 100644
--- a/src/ppd-utils.h
+++ b/src/ppd-utils.h
@@ -10,7 +10,9 @@
 #pragma once
 
 #include <gudev/gudev.h>
+#include <gio/gio.h>
 
+char * ppd_utils_get_sysfs_path (const char *filename);
 gboolean ppd_utils_write (const char  *filename,
                           const char  *value,
                           GError     **error);
@@ -18,3 +20,9 @@ gboolean ppd_utils_write_sysfs (GUdevDevice  *device,
                                 const char   *attribute,
                                 const char   *value,
                                 GError      **error);
+GFileMonitor *ppd_utils_monitor_sysfs_attr (GUdevDevice  *device,
+                                            const char   *attribute,
+                                            GError      **error);
+GUdevDevice *ppd_utils_find_device (const char   *subsystem,
+                                    GCompareFunc  func,
+                                    gpointer      user_data);
diff --git a/tests/integration-test b/tests/integration-test
index 54eec01..3ca5643 100755
--- a/tests/integration-test
+++ b/tests/integration-test
@@ -59,12 +59,10 @@ class Tests(dbusmock.DBusTestCase):
         if os.access(os.path.join(builddir, 'src', 'power-profiles-daemon'), os.X_OK):
             cls.daemon_path = os.path.join(builddir, 'src', 'power-profiles-daemon')
             print('Testing binaries from local build tree (%s)' % cls.daemon_path)
-            cls.local_daemon = True
         elif os.environ.get('UNDER_JHBUILD', False):
             jhbuild_prefix = os.environ['JHBUILD_PREFIX']
             cls.daemon_path = os.path.join(jhbuild_prefix, 'libexec', 'power-profiles-daemon')
             print('Testing binaries from JHBuild (%s)' % cls.daemon_path)
-            cls.local_daemon = False
         else:
             cls.daemon_path = None
             with open('/usr/lib/systemd/system/power-profiles-daemon.service') as f:
@@ -73,13 +71,13 @@ class Tests(dbusmock.DBusTestCase):
                         cls.daemon_path = line.split('=', 1)[1].strip()
                         break
             assert cls.daemon_path, 'could not determine daemon path from systemd .service file'
-            cls.local_daemon = False
             print('Testing installed system binary (%s)' % cls.daemon_path)
 
-        # fail on CRITICALs on client side
+        # fail on CRITICALs on client and server side
         GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_WARNING |
                                   GLib.LogLevelFlags.LEVEL_ERROR |
                                   GLib.LogLevelFlags.LEVEL_CRITICAL)
+        os.environ['G_DEBUG'] = 'fatal_warnings'
 
         # set up a fake system D-BUS
         cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE)
@@ -109,9 +107,13 @@ class Tests(dbusmock.DBusTestCase):
         self.log = None
         self.daemon = None
 
+        # Used for dytc devices
+        self.tp_acpi = None
+
     def tearDown(self):
         del self.testbed
         self.stop_daemon()
+        del self.tp_acpi
 
         # on failures, print daemon log
         errors = [x[1] for x in self._outcome.errors if x[1]]
@@ -138,10 +140,7 @@ class Tests(dbusmock.DBusTestCase):
         env['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
         self.log = tempfile.NamedTemporaryFile()
         if os.getenv('VALGRIND') != None:
-            if self.local_daemon:
-                daemon_path = ['libtool', '--mode=execute', 'valgrind', self.daemon_path, '-v']
-            else:
-                daemon_path = ['valgrind', self.daemon_path, '-v']
+            daemon_path = ['valgrind', self.daemon_path, '-v']
         else:
             daemon_path = [self.daemon_path, '-v']
 
@@ -203,16 +202,33 @@ class Tests(dbusmock.DBusTestCase):
         with open(self.log.name) as f:
             return f.read().count(text)
 
-    def read_sysfs_attr(self, device, attribute):
-        with open(os.path.join(self.testbed.get_root_dir() + device, attribute), 'rb') as f:
-            return f.read()
+    def read_sysfs_file(self, path):
+        with open(self.testbed.get_root_dir() + '/' + path, 'rb') as f:
+          return f.read().rstrip()
         return None
 
+    def read_sysfs_attr(self, device, attribute):
+        return self.read_sysfs_file(device + '/' + attribute)
+
     def read_file(self, path):
         with open(path, 'rb') as f:
             return f.read()
         return None
 
+    def create_dytc_device(self):
+      self.tp_acpi = self.testbed.add_device('platform', 'thinkpad_acpi', None,
+          ['dytc_lapmode', '0\n'],
+          [ 'DEVPATH', '/devices/platform/thinkpad_acpi' ]
+      )
+
+    def create_platform_profile(self):
+      acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+      os.makedirs(acpi_dir)
+      with open(os.path.join(acpi_dir, "platform_profile")  ,'w') as profile:
+        profile.write("performance\n")
+      with open(os.path.join(acpi_dir, "platform_profile_choices")  ,'w') as choices:
+        choices.write("low-power balanced performance\n")
+
     def assertEventually(self, condition, message=None, timeout=50):
         '''Assert that condition function eventually returns True.
 
@@ -233,6 +249,13 @@ class Tests(dbusmock.DBusTestCase):
     #
     # Actual test cases
     #
+    def test_dbus_startup_error(self):
+      '''D-Bus startup error'''
+
+      self.start_daemon()
+      out = subprocess.run([self.daemon_path], capture_output=True)
+      self.assertEqual(out.returncode, 1, "power-profile-daemon started but should have failed")
+      self.stop_daemon()
 
     def test_no_performance_driver(self):
       '''no performance driver'''
@@ -243,8 +266,8 @@ class Tests(dbusmock.DBusTestCase):
 
       profiles = self.get_dbus_property('Profiles')
       self.assertEqual(len(profiles), 2)
-      self.assertEqual(profiles[1]['Driver'], 'balanced')
-      self.assertEqual(profiles[0]['Driver'], 'power-saver')
+      self.assertEqual(profiles[1]['Driver'], 'placeholder')
+      self.assertEqual(profiles[0]['Driver'], 'placeholder')
       self.assertEqual(profiles[1]['Profile'], 'balanced')
       self.assertEqual(profiles[0]['Profile'], 'power-saver')
 
@@ -259,23 +282,20 @@ class Tests(dbusmock.DBusTestCase):
     def test_inhibited_transition(self):
       '''Test that transitions work as expected when inhibited'''
 
-      tp_acpi = self.testbed.add_device('platform', 'thinkpad_acpi', None,
-          ['dytc_lapmode', '0', 'dytc_perfmode', 'H'],
-          [ 'DEVPATH', '/devices/platform/thinkpad_acpi' ]
-      )
-
+      self.create_dytc_device()
+      self.create_platform_profile()
       self.start_daemon()
 
       profiles = self.get_dbus_property('Profiles')
       self.assertEqual(len(profiles), 3)
-      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
 
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced'))
       self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
 
       # Inhibit
-      self.testbed.set_attribute(tp_acpi, 'dytc_lapmode', '1')
-      self.testbed.uevent(tp_acpi, 'change')
+      self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n')
       self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now on'))
       self.assertEqual(self.get_dbus_property('PerformanceInhibited'), 'lap-detected')
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
@@ -288,20 +308,20 @@ class Tests(dbusmock.DBusTestCase):
       '''Intel P-State driver (no UPower)'''
 
       # Create 2 CPUs with preferences
-      dir1 = os.path.join(self.testbed.get_root_dir(), "devices/system/cpu/cpufreq/policy0/")
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
       os.makedirs(dir1)
       with open(os.path.join(dir1, "energy_performance_preference")  ,'w') as prefs:
-        prefs.write("performance")
-      dir2 = os.path.join(self.testbed.get_root_dir(), "devices/system/cpu/cpufreq/policy1/")
+        prefs.write("performance\n")
+      dir2 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/")
       os.makedirs(dir2)
       with open(os.path.join(dir2, "energy_performance_preference")  ,'w') as prefs:
-        prefs.write("performance")
+        prefs.write("performance\n")
 
       # Create no_turbo pref
-      pstate_dir = os.path.join(self.testbed.get_root_dir(), "devices/system/cpu/intel_pstate")
+      pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate")
       os.makedirs(pstate_dir)
       with open(os.path.join(pstate_dir, "no_turbo")  ,'w') as no_turbo:
-        no_turbo.write("0")
+        no_turbo.write("0\n")
 
       self.start_daemon()
 
@@ -326,7 +346,7 @@ class Tests(dbusmock.DBusTestCase):
 
       # Disable turbo
       with open(os.path.join(pstate_dir, "no_turbo")  ,'w') as no_turbo:
-        no_turbo.write("1")
+        no_turbo.write("1\n")
 
       self.assertEventually(lambda: self.have_text_in_log('File monitor change happened for '))
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
@@ -335,25 +355,21 @@ class Tests(dbusmock.DBusTestCase):
       self.stop_daemon()
 
       # Verify that the Lenovo DYTC driver still gets preferred
-      tp_acpi = self.testbed.add_device('platform', 'thinkpad_acpi', None,
-          ['dytc_lapmode', '0', 'dytc_perfmode', 'H'],
-          [ 'DEVPATH', '/devices/platform/thinkpad_acpi' ]
-      )
-
+      self.create_platform_profile()
       self.start_daemon()
 
       profiles = self.get_dbus_property('Profiles')
       self.assertEqual(len(profiles), 3)
-      self.assertEqual(profiles[0]['Driver'], 'lenovo_dytc')
+      self.assertEqual(profiles[0]['Driver'], 'platform_profile')
 
     def test_intel_pstate_balance(self):
       '''Intel P-State driver (balance)'''
 
       # Create CPU with preference
-      dir1 = os.path.join(self.testbed.get_root_dir(), "devices/system/cpu/cpufreq/policy0/")
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
       os.makedirs(dir1)
       with open(os.path.join(dir1, "energy_performance_preference")  ,'w') as prefs:
-        prefs.write("performance")
+        prefs.write("performance\n")
 
       upowerd, obj_upower = self.spawn_server_template(
             'upower', {'DaemonVersion': '0.99', 'OnBattery': False}, stdout=subprocess.PIPE)
@@ -368,79 +384,65 @@ class Tests(dbusmock.DBusTestCase):
       contents = None
       with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f:
         contents = f.read()
+      # This matches what's written by ppd-driver-intel-pstate.c
       self.assertEqual(contents, b'balance_performance')
 
-      # Unplug from the mains
-      obj_upower.Set('org.freedesktop.UPower', 'OnBattery', True)
-      obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
-
-      contents = None
-      self.assertEventually(lambda: self.read_file(os.path.join(dir1, "energy_performance_preference")) == b'balance_power')
-
       self.stop_daemon()
 
       upowerd.terminate()
       upowerd.wait()
+      upowerd.stdout.close()
 
     def test_dytc_performance_driver(self):
       '''Lenovo DYTC performance driver'''
 
-      tp_acpi = self.testbed.add_device('platform', 'thinkpad_acpi', None,
-          ['dytc_lapmode', '0', 'dytc_perfmode', 'H'],
-          [ 'DEVPATH', '/devices/platform/thinkpad_acpi' ]
-      )
-
+      self.create_dytc_device()
+      self.create_platform_profile()
       self.start_daemon()
 
       profiles = self.get_dbus_property('Profiles')
       self.assertEqual(len(profiles), 3)
-      self.assertEqual(profiles[0]['Driver'], 'lenovo_dytc')
+      self.assertEqual(profiles[0]['Driver'], 'platform_profile')
       self.assertEqual(profiles[0]['Profile'], 'power-saver')
-      self.assertEqual(profiles[2]['Driver'], 'lenovo_dytc')
+      self.assertEqual(profiles[2]['Driver'], 'platform_profile')
       self.assertEqual(profiles[2]['Profile'], 'performance')
-      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
-      # Verify that perfmode got reset
-      self.assertEventually(lambda: self.read_sysfs_attr(tp_acpi, 'dytc_perfmode') == b'M')
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
 
       # lapmode detected, but performance wasn't selected anyway
-      self.testbed.set_attribute(tp_acpi, 'dytc_lapmode', '1')
-      self.testbed.uevent(tp_acpi, 'change')
-      self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now on'))
-
+      self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n')
+      self.assertEventually(lambda: self.get_dbus_property('PerformanceInhibited') == 'lap-detected')
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-      self.assertEqual(self.get_dbus_property('PerformanceInhibited'), 'lap-detected')
 
       # Reset lapmode
-      self.testbed.set_attribute(tp_acpi, 'dytc_lapmode', '0')
-      self.testbed.uevent(tp_acpi, 'change')
-      self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now off'))
+      self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '0\n')
+      self.assertEventually(lambda: self.get_dbus_property('PerformanceInhibited') == '')
 
       # Set performance mode
       self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-      self.assertEventually(lambda: self.read_sysfs_attr(tp_acpi, 'dytc_perfmode') == b'H')
+      self.assertEventually(lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") == b'performance')
 
       # And turn on lapmode
-      self.testbed.set_attribute(tp_acpi, 'dytc_lapmode', '1')
-      self.testbed.uevent(tp_acpi, 'change')
-      self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now on'))
-      self.assertEventually(lambda: self.read_sysfs_attr(tp_acpi, 'dytc_perfmode') == b'M')
+      self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n')
+      self.assertEventually(lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") == b'balanced')
 
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
       self.assertEqual(self.get_dbus_property('PerformanceInhibited'), 'lap-detected')
 
       # Turn off lapmode, profile stays balanced
-      self.testbed.set_attribute(tp_acpi, 'dytc_lapmode', '0')
-      self.testbed.uevent(tp_acpi, 'change')
-      self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now off'))
-      self.assertEventually(lambda: self.read_sysfs_attr(tp_acpi, 'dytc_perfmode') == b'M')
+      self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '0\n')
+      self.assertEventually(lambda: self.get_dbus_property('PerformanceInhibited') == '')
+      self.assertEventually(lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") == b'balanced')
 
       # Switch to power-saver mode
       self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
+      self.assertEventually(lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") == b'low-power')
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
-      self.assertEqual(self.get_dbus_property('PerformanceInhibited'), '')
-      self.assertEventually(lambda: self.read_sysfs_attr(tp_acpi, 'dytc_perfmode') == b'L')
+
+      # And mimick a user pressing a Fn+H
+      with open(os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile"), 'w') as platform_profile:
+        platform_profile.write('performance\n')
+      self.assertEventually(lambda: self.get_dbus_property('ActiveProfile') == 'performance')
 
     def test_fake_driver(self):
       '''Test that the fake driver works'''
@@ -484,6 +486,61 @@ class Tests(dbusmock.DBusTestCase):
       # self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
       # self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), 'Fast')
 
+    def test_platform_driver_late_load(self):
+      '''Test that we can handle the platform_profile driver getting loaded late'''
+      acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+      os.makedirs(acpi_dir)
+      with open(os.path.join(acpi_dir, "platform_profile")  ,'w') as profile:
+        profile.write('\n')
+      with open(os.path.join(acpi_dir, "platform_profile_choices")  ,'w') as choices:
+        choices.write('\n')
+
+      self.start_daemon()
+
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 2)
+
+      with open(os.path.join(acpi_dir, "platform_profile_choices")  ,'w') as choices:
+        choices.write("low-power\nbalanced\nperformance\n")
+      with open(os.path.join(acpi_dir, "platform_profile")  ,'w') as profile:
+        profile.write("performance\n")
+
+      # Wait for profiles to get reloaded
+      self.assertEventually(lambda: len(self.get_dbus_property('Profiles')) == 3)
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 3)
+      # Was set in platform_profile before we loaded the drivers
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
+      self.assertEqual(self.get_dbus_property('PerformanceInhibited'), '')
+
+      self.stop_daemon()
+
+    def test_hp_wmi(self):
+
+      # Uses cool instead of low-power
+      acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+      os.makedirs(acpi_dir)
+      with open(os.path.join(acpi_dir, "platform_profile")  ,'w') as profile:
+        profile.write("cool\n")
+      with open(os.path.join(acpi_dir, "platform_profile_choices")  ,'w') as choices:
+        choices.write("cool balanced performance\n")
+
+      self.start_daemon()
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 3)
+      self.assertEqual(profiles[0]['Driver'], 'platform_profile')
+      self.assertEqual(profiles[0]['Profile'], 'power-saver')
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
+      self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'cool')
+
+      # Check that we can set the power-saver/cool profile again
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced'))
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
+      self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'cool')
+
+      self.stop_daemon()
+
     #
     # Helper methods
     #
diff --git a/tests/meson.build b/tests/meson.build
index baf4699..462c3aa 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -2,6 +2,7 @@ integration_test = find_program('integration-test')
 
 envs = environment()
 envs.set ('top_builddir', meson.build_root())
+envs.set ('top_srcdir', meson.source_root())
 
 test('power-profiles-daemon-integration-test',
   integration_test,



More information about the Neon-commits mailing list