Skip to content

Commit

Permalink
tuned-ppd: Enable changing profile via function keys
Browse files Browse the repository at this point in the history
On some Thinkpad laptops, it is possible to change the ACPI
platform profile using function keys (e.g., Fn+L). This can
now trigger a complete profile change if the functionality
is enabled.

Resolves #710.
  • Loading branch information
zacikpa committed Dec 3, 2024
1 parent 038dcea commit 23eb673
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
12 changes: 12 additions & 0 deletions tuned/ppd/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BATTERY_SECTION = "battery"
DEFAULT_PROFILE_OPTION = "default"
BATTERY_DETECTION_OPTION = "battery_detection"
THINKPAD_FUNCTION_KEYS_OPTION = "thinkpad_function_keys"


class ProfileMap:
Expand Down Expand Up @@ -73,6 +74,15 @@ def tuned_to_ppd(self):
"""
return self._tuned_to_ppd

@property
def thinkpad_function_keys(self):
"""
Whether to react to changes of ACPI platform profile
done via function keys (e.g., Fn-L) on newer Thinkpad
machines. Experimental feature.
"""
return self._thinkpad_function_keys

def load_from_file(self, config_file):
"""
Loads the configuration from the provided file.
Expand Down Expand Up @@ -130,3 +140,5 @@ def load_from_file(self, config_file):

self._ppd_to_tuned = ProfileMap(profile_dict_ac, profile_dict_dc)
self._tuned_to_ppd = ProfileMap({v: k for k, v in profile_dict_ac.items()}, {v: k for k, v in profile_dict_dc.items()})

self._thinkpad_function_keys = cfg.getboolean(MAIN_SECTION, THINKPAD_FUNCTION_KEYS_OPTION, fallback=False)
76 changes: 75 additions & 1 deletion tuned/ppd/controller.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from tuned import exports, logs
from tuned.utils.commands import commands
from tuned.consts import PPD_CONFIG_FILE, PPD_BASE_PROFILE_FILE, PPD_API_COMPATIBILITY
from tuned.ppd.config import PPDConfig, PPD_PERFORMANCE, PPD_POWER_SAVER
from tuned.ppd.config import PPDConfig, PPD_PERFORMANCE, PPD_BALANCED, PPD_POWER_SAVER

from enum import StrEnum
import pyinotify
import threading
import dbus
import os
import time

log = logs.get()

Expand All @@ -19,6 +21,14 @@
UPOWER_DBUS_PATH = "/org/freedesktop/UPower"
UPOWER_DBUS_INTERFACE = "org.freedesktop.UPower"

PLATFORM_PROFILE_PATH = "/sys/firmware/acpi/platform_profile"
PLATFORM_PROFILE_MAPPING = {
"low-power": PPD_POWER_SAVER,
"balanced": PPD_BALANCED,
"performance": PPD_PERFORMANCE
}


class PerformanceDegraded(StrEnum):
"""
Possible reasons for performance degradation.
Expand All @@ -43,6 +53,51 @@ def process_IN_MODIFY(self, event):
self._controller.check_performance_degraded()


class PlatformProfileEventHandler(pyinotify.ProcessEvent):
"""
Event handler for switching PPD profiles based on the
ACPI platform profile
This handler should only invoke a PPD profile change if the
change of the file at PLATFORM_PROFILE_PATH comes from within
the kernel (e.g., when the user presses Fn-L on a Thinkpad laptop).
This is currently detected as the file being modified without
being opened before.
"""
CLOSE_MODIFY_BUFFER = 0.1

def __init__(self, controller):
super(PlatformProfileEventHandler, self).__init__()
self._controller = controller
self._file_open = False
self._last_close = 0

def process_IN_OPEN(self, event):
if event.pathname != PLATFORM_PROFILE_PATH:
return
self._file_open = True
self._last_close = 0

def process_IN_CLOSE_WRITE(self, event):
if event.pathname != PLATFORM_PROFILE_PATH:
return
self._file_open = False
self._last_close = time.time()

def process_IN_CLOSE_NOWRITE(self, event):
if event.pathname != PLATFORM_PROFILE_PATH:
return
self._file_open = False

def process_IN_MODIFY(self, event):
if event.pathname != PLATFORM_PROFILE_PATH or self._file_open or self._last_close + self.CLOSE_MODIFY_BUFFER > time.time():
# Do not invoke a profile change if a modify event comes:
# 1. when the file is open,
# 2. directly after the file is closed (the events may sometimes come in the wrong order).
return
self._controller.check_platform_profile()


class ProfileHold(object):
"""
Class holding information about a single profile hold,
Expand Down Expand Up @@ -168,6 +223,7 @@ def __init__(self, bus, tuned_interface):
self._watch_manager = pyinotify.WatchManager()
self._notifier = pyinotify.ThreadedNotifier(self._watch_manager)
self._inotify_watches = {}
self._platform_profile_supported = os.path.isfile(PLATFORM_PROFILE_PATH)
self._no_turbo_supported = os.path.isfile(NO_TURBO_PATH)
self._lap_mode_supported = os.path.isfile(LAP_MODE_PATH)
self._tuned_interface.connect_to_signal("profile_changed", self._tuned_profile_changed)
Expand Down Expand Up @@ -233,6 +289,10 @@ def _setup_inotify(self):
self._inotify_watches |= self._watch_manager.add_watch(path=os.path.dirname(LAP_MODE_PATH),
mask=pyinotify.IN_MODIFY,
proc_fun=PerformanceDegradedEventHandler(LAP_MODE_PATH, self))
if self._platform_profile_supported and self._config.thinkpad_function_keys:
self._inotify_watches |= self._watch_manager.add_watch(path=os.path.dirname(PLATFORM_PROFILE_PATH),
mask=pyinotify.IN_OPEN | pyinotify.IN_MODIFY | pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CLOSE_NOWRITE,
proc_fun=PlatformProfileEventHandler(self))

def check_performance_degraded(self):
"""
Expand All @@ -248,6 +308,20 @@ def check_performance_degraded(self):
self._performance_degraded = performance_degraded
exports.property_changed("PerformanceDegraded", performance_degraded)

def check_platform_profile(self):
"""
Sets the active PPD profile based on the content of the ACPI platform profile.
"""
platform_profile = self._cmd.read_file(PLATFORM_PROFILE_PATH).strip()
if platform_profile not in PLATFORM_PROFILE_MAPPING:
return
log.debug("Platform profile changed: %s" % platform_profile)
new_profile = PLATFORM_PROFILE_MAPPING[platform_profile]
self._profile_holds.clear()
self.switch_profile(new_profile)
self._base_profile = new_profile
self._save_base_profile(new_profile)

def _load_base_profile(self):
"""
Loads and returns the saved PPD base profile.
Expand Down

0 comments on commit 23eb673

Please sign in to comment.