diff -Nru power-profiles-daemon-0.13/.ci/fail_skipped_tests.py power-profiles-daemon-0.21/.ci/fail_skipped_tests.py --- power-profiles-daemon-0.13/.ci/fail_skipped_tests.py 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/.ci/fail_skipped_tests.py 2024-04-03 23:55:02.000000000 +0000 @@ -3,23 +3,37 @@ from lxml import etree import sys + def format_title(title): """Put title in a box""" box = { - 'tl': '╔', 'tr': '╗', 'bl': '╚', 'br': '╝', 'h': '═', 'v': '║', + "tl": "╔", + "tr": "╗", + "bl": "╚", + "br": "╝", + "h": "═", + "v": "║", } - hline = box['h'] * (len(title) + 2) + hline = box["h"] * (len(title) + 2) + + return "\n".join( + [ + f"{box['tl']}{hline}{box['tr']}", + f"{box['v']} {title} {box['v']}", + f"{box['bl']}{hline}{box['br']}", + ] + ) - return '\n'.join([ - f"{box['tl']}{hline}{box['tr']}", - f"{box['v']} {title} {box['v']}", - f"{box['bl']}{hline}{box['br']}", - ]) tree = etree.parse(sys.argv[1]) -for suite in tree.xpath('/testsuites/testsuite'): - skipped = suite.get('skipped') +for suite in tree.xpath("/testsuites/testsuite"): + skipped = suite.get("skipped") if int(skipped) != 0: - print(format_title('Tests were skipped when they should not have been. All the tests must be run in the CI'), - end='\n\n', flush=True) + print( + format_title( + "Tests were skipped when they should not have been. All the tests must be run in the CI" + ), + end="\n\n", + flush=True, + ) sys.exit(1) diff -Nru power-profiles-daemon-0.13/.gitignore power-profiles-daemon-0.21/.gitignore --- power-profiles-daemon-0.13/.gitignore 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/.gitignore 2024-04-03 23:55:02.000000000 +0000 @@ -1,2 +1,3 @@ power-profiles-daemon -data/net.hadess.PowerProfiles.conf +__pycache__ +.vscode diff -Nru power-profiles-daemon-0.13/.gitlab-ci.yml power-profiles-daemon-0.21/.gitlab-ci.yml --- power-profiles-daemon-0.13/.gitlab-ci.yml 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/.gitlab-ci.yml 2024-04-03 23:55:02.000000000 +0000 @@ -2,7 +2,6 @@ variables: DEPENDENCIES: gcc - gtk-doc pkgconfig(udev) pkgconfig(systemd) pkgconfig(gio-2.0) @@ -12,32 +11,224 @@ systemd meson git - python3-gobject - python3-dbusmock - python3-pylint - umockdev - e2fsprogs + python3-packaging + TEST_DEPENDENCIES: e2fsprogs + python3-dbusmock + python3-gobject + python3-pylint + umockdev + TEST_DEBUG_DEPENDENCIES: glib2 + glibc + libgudev + upower + polkit-libs workflow: rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: $CI_PIPELINE_SOURCE == 'push' -build_stable: +.install-deps: + variables: + TMPDIR: $CI_BUILDS_DIR/tmpdir before_script: - - dnf upgrade -y --nogpgcheck fedora-release fedora-repos* - - dnf update -y && dnf install -y $DEPENDENCIES - - mkdir tmpdir/ - script: - - meson -Dgtk_doc=true -Dpylint=true -Dtests=true _build - - ninja -v -C _build - - ninja -v -C _build install - - ninja -v -C _build uninstall - - TMPDIR=$(pwd)/tmpdir meson test -C _build + - echo 8096000 > /proc/sys/fs/inotify/max_user_instances + - mkdir -m 700 $TMPDIR -p + - if command -v dnf5 &>/dev/null; then ln -sfv $(command -v dnf5) /usr/local/bin/dnf; fi + - dnf update -y + - dnf install -y $DEPENDENCIES $JOB_DEPS + # dnf5 does not include debuginfo-install yet, so let's use dnf4 instead. + - if [ -n "$JOB_DEBUG_DEPS" ]; then + if ! command -v dnf4 &>/dev/null; then + dnf install -y dnf4; + dnf4 update -y; + fi; + dnf4 install -y "dnf-command(debuginfo-install)"; + dnf4 debuginfo-install -y $JOB_DEBUG_DEPS; + fi + +pre_commit: + variables: + DEPENDENCIES: {} + JOB_DEPS: pre-commit + git + extends: + - .install-deps + script: + - pre-commit run --all-files + +build_and_test: + variables: + JOB_DEPS: $TEST_DEPENDENCIES + gcovr + python3-coverage + JOB_DEBUG_DEPS: $TEST_DEBUG_DEPENDENCIES + extends: + - .install-deps + script: + - meson setup + --werror + --fatal-meson-warnings + --warnlevel 2 + -Dpylint=enabled + -Db_coverage=true + _build + - meson test -C _build --print-errorlogs - .ci/fail_skipped_tests.py _build/meson-logs/testlog.junit.xml - - TMPDIR=$(pwd)/tmpdir ninja -v -C _build dist + - ninja -C _build coverage + - python_coverage_data=_build/meson-logs/python.coverage + - coverage3 combine --data-file=$python_coverage_data + _build/python-coverage/* + - coverage3 xml --data-file=$python_coverage_data + -o $python_coverage_data.xml + - sed "s,_build/src/,src/,g" -i $python_coverage_data.xml + - coverage3 report --data-file=$python_coverage_data + - coverage3 html --data-file=$python_coverage_data + -d _build/meson-logs/python-coverage-html + - cat _build/meson-logs/coverage.txt || true artifacts: when: always paths: - _build/meson-logs/*.txt + - _build/meson-logs/*.xml - _build/meson-dist/* + - _build/meson-logs/coveragereport/* + - _build/meson-logs/python-coverage-html/* + reports: + junit: + - _build/meson-logs/testlog.junit.xml + coverage_report: + coverage_format: cobertura + path: _build/meson-logs/*coverage.xml + coverage: '/^TOTAL.*\s+(\d+\%)$/' + +dist_install: + variables: + JOB_DEPS: argparse-manpage + bash-completion-devel + python3-gobject + python3-shtab + extends: + - .install-deps + script: + - meson setup + --werror + --fatal-meson-warnings + --warnlevel 2 + -Dmanpage=enabled + -Dbashcomp=enabled + -Dzshcomp=enabled + -Dtests=false + _build + - meson install -C _build + - ninja -C _build uninstall -v + - meson dist -C _build + artifacts: + when: always + paths: + - _build/meson-logs/*.txt + rules: + - if: ($CI_PIPELINE_SOURCE == "merge_request_event" || + $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH) + +address_sanitizer: + variables: + JOB_DEPS: $TEST_DEPENDENCIES + libasan + libubsan + JOB_DEBUG_DEPS: $TEST_DEBUG_DEPENDENCIES + extends: + - .install-deps + script: + - meson setup + --werror + --buildtype=debug + _build + -Db_sanitize=address,undefined + - meson test -C _build --print-errorlogs -t 3 + artifacts: + when: on_failure + paths: + - _build/meson-logs/*.txt + +valgrind: + variables: + JOB_DEPS: $TEST_DEPENDENCIES + valgrind + JOB_DEBUG_DEPS: $TEST_DEBUG_DEPENDENCIES + extends: + - .install-deps + script: + - meson setup + --werror + --buildtype=debug + _build + - meson test -C _build --print-errorlogs --setup=valgrind + artifacts: + when: on_failure + paths: + - _build/meson-logs/*.txt + +scan_build: + variables: + JOB_DEPS: clang-analyzer + which + extends: + - .install-deps + script: + - meson setup _build -Dtests=false + - env SCANBUILD=$(which scan-build) ninja -C _build scan-build + artifacts: + when: on_failure + paths: + - _build/meson-logs + +docs: + variables: + JOB_DEPS: gtk-doc + extends: + - .install-deps + script: + - meson setup + -Dgtk_doc=true + -Dtests=false + _build-docs + - ninja -C _build-docs power-profiles-daemon-doc + artifacts: + expose_as: "HTML Documentation" + paths: + - _build-docs/docs/html + - _build-docs/docs/html/index.html + expire_in: 1 week + rules: + - if: ($CI_PIPELINE_SOURCE == "merge_request_event" || + $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH) + +pages: + image: alpine:latest + stage: deploy + needs: + - job: build_and_test + artifacts: true + - job: docs + artifacts: true + script: + - mv _build-docs/docs/html public + - mkdir public/coverage + - mv _build/meson-logs/coveragereport public/coverage/daemon + - mv _build/meson-logs/python-coverage-html public/coverage/tool + - echo ' + + Power Profiles Daemon Coverage + + + + ' > public/coverage/index.html + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH diff -Nru power-profiles-daemon-0.13/.markdownlint.json power-profiles-daemon-0.21/.markdownlint.json --- power-profiles-daemon-0.13/.markdownlint.json 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/.markdownlint.json 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,10 @@ +{ + "default": true, + "MD033": false, + "MD041": false, + "MD036": false, + "MD013": { + "tables": false, + "line_length": 1000 + } +} diff -Nru power-profiles-daemon-0.13/.pre-commit-config.yaml power-profiles-daemon-0.21/.pre-commit-config.yaml --- power-profiles-daemon-0.13/.pre-commit-config.yaml 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/.pre-commit-config.yaml 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,39 @@ +default_stages: [commit] +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + args: [--branch, main] + - id: check-added-large-files + - id: check-byte-order-marker + - id: check-executables-have-shebangs + - id: forbid-new-submodules + - id: check-yaml + - id: check-json + - id: pretty-format-json + args: ['--no-sort-keys'] + - id: check-symlinks + - id: check-xml + - id: end-of-file-fixer + types_or: [c, shell, python] + - id: trailing-whitespace + types_or: [c, shell, python, xml] + - id: check-docstring-first + - id: check-merge-conflict + - id: mixed-line-ending + args: [--fix=lf] +- repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + args: ['--write-changes'] +- repo: https://github.com/ambv/black + rev: 23.12.0 + hooks: + - id: black +- repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.38.0 + hooks: + - id: markdownlint + args: ['--fix'] diff -Nru power-profiles-daemon-0.13/NEWS power-profiles-daemon-0.21/NEWS --- power-profiles-daemon-0.13/NEWS 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/NEWS 2024-04-03 23:55:02.000000000 +0000 @@ -1,3 +1,45 @@ +0.21 +---- + +Since this release power-profiles-daemon is battery-state aware and some drivers +use a more power efficient state when using the balanced profile on battery. +In particular both the AMD and Intel P-State drivers will use the +balance_power EPP profile, while for Intel one we also set the energy +performance bias to 8 (instead of 6). + +This release also contains various fixes for the powerprofilesctl command line +tool when using the launch or version commands. +The tool is now better documented as we generate a manual page for it (if +python3-argparse is installed) and bash completions. We're even generating the +ZSH completions, but the install path must be provided. + +The daemon command line interface has been improved for debugging, so use +--help-debug for further information. + +The systemd service lockdown settings have been restricted even more. + +Various code optimizations. + +0.20 +---- + +The project has moved under the freedesktop 'Upower' group. The service is +now advertised as 'org.freedesktop.UPower.PowerProfiles' in addition to the +previous 'net.hadess.PowerProfiles' for compatibility reasons. + +This release adds support for: + +* Multiple power-profiles-daemon drivers to load simultaneously. This notably + allows both CPU based control with amd-pstate or intel-pstate as well as + ACPI platform profile based control. + +* amdgpu panel power savings which uses dedicated hardware in systems with + integrated Radeon graphics to decrease panel power consumption when the + system is on battery. + +This release also enables the test suite by default, so distribution vendors +should update packaging accordingly. + 0.13 ---- diff -Nru power-profiles-daemon-0.13/README.md power-profiles-daemon-0.21/README.md --- power-profiles-daemon-0.13/README.md 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/README.md 2024-04-03 23:55:02.000000000 +0000 @@ -1,18 +1,17 @@ -power-profiles-daemon -===================== +# power-profiles-daemon Makes power profiles handling available over D-Bus. -Installation ------------- +## Installation + ```sh -$ meson _build -Dprefix=/usr -$ ninja -v -C _build install +meson setup _build -Dprefix=/usr +ninja -C _build install ``` + It requires libgudev, systemd and polkit-gobject. -Introduction ------------- +## Introduction power-profiles-daemon offers to modify system behaviour based upon user-selected power profiles. There are 3 different power profiles, a "balanced" default mode, @@ -30,8 +29,7 @@ such as turning the screen off after inaction more aggressively when in power-saver mode. -How to use ----------- +## How to use There are interfaces to switch profiles in the latest versions of KDE and GNOME. Those desktops also include more thorough integration with its low-power mode. Please check @@ -44,6 +42,7 @@ For example, this will be useful to avoid manual switching profiles while compiling large projects: + ```sh powerprofilesctl launch make ``` @@ -52,8 +51,7 @@ through C, or one of its bindings, so your application can react to the user requesting a low-power mode. -Conflicts ---------- +## Conflicts If `power-profiles-daemon` refuses to start, it's likely that you have [a conflicting service installed and running](data/power-profiles-daemon.service.in#L3), or your @@ -65,8 +63,7 @@ systemctl start power-profiles-daemon.service ``` -Debugging ---------- +## Debugging You can now check which mode is in use, and which ones are available by running: @@ -86,30 +83,34 @@ Those commands are also available through the D-Bus interface: -``` -gdbus introspect --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles -gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles --method org.freedesktop.DBus.Properties.Set 'net.hadess.PowerProfiles' 'ActiveProfile' "<'power-saver'>" +```sh +gdbus introspect --system --dest org.freedesktop.UPower.PowerProfiles \ + --object-path /org/freedesktop/UPower/PowerProfiles +gdbus call --system --dest org.freedesktop.UPower.PowerProfiles \ + --object-path /org/freedesktop/UPower/PowerProfiles \ + --method org.freedesktop.DBus.Properties.Set 'org.freedesktop.UPower.PowerProfiles' \ + 'ActiveProfile' "<'power-saver'>" ``` If that doesn't work, please file an issue, attach the output of: ```sh -sudo G_MESSAGES_DEBUG=all /usr/libexec/power-profiles-daemon -r -v +sudo /usr/libexec/power-profiles-daemon -r -vv ``` -Operations on Intel-based machines ----------------------------------- +## Operations on Intel-based machines The "driver" for making the hardware act on the user-selected power profile on Intel CPU-based machines is based on the [Intel P-State scaling driver](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_pstate.html) or the Energy Performance Bias (EPB) feature if available. -It is only used if a `platform_profile` driver isn't available for the system, and the -CPU supports either hardware-managed P-states (HWP) or Energy Performance Bias (EPB). +It is only used if the CPU supports either hardware-managed P-states (HWP) +or Energy Performance Bias (EPB). Example of a system without `platform_profile support` but with `active` P-State operation mode: -``` + +```sh $ cat /sys/firmware/acpi/platform_profile_choices cat: /sys/firmware/acpi/platform_profile_choices: No such file or directory $ cat /sys/devices/system/cpu/intel_pstate/status @@ -117,13 +118,14 @@ ``` Example of a system with `EPB` support: -``` + +```sh $ cat /sys/devices/system/cpu/cpu0/power/energy_perf_bias 0 ``` If the Intel P-State scaling driver is in `passive` mode, either because the system doesn't -support HWP, or the administator has disabled it, and `EPB` isn't available, then the +support HWP, or the administrator has disabled it, and `EPB` isn't available, then the placeholder driver will be used, and there won't be a performance mode. Finally, if the Intel P-State scaling driver is used in `active` mode, the P-State @@ -134,20 +136,22 @@ For more information, please refer to the [Intel P-State scaling driver documentation](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_pstate.html) and the [Intel Performance and Energy Bias Hint](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_epb.html). -Operations on AMD-based machines ----------------------------------- +## Operations on AMD-based machines + +### CPU power savings The "driver" for making the hardware act on the user-selected power profile on AMD CPU-based machines is based on the [AMD P-State scaling driver](https://www.kernel.org/doc/html/v6.3/admin-guide/pm/amd-pstate.html) if available. -It is only used if a `platform_profile` driver isn't available for the system, the -CPU supports Collaborative Processor Performance Control (CPPC), and the AMD P-State -scaling driver is in `active` mode. +It is only used if the CPU supports Collaborative Processor Performance +Control (CPPC), the machine is a laptop or workstation and the +AMD P-State scaling driver is in `active` mode. Example of a system without `platform_profile` support but with `active` P-State operation mode: -``` + +```sh $ cat /sys/firmware/acpi/platform_profile_choices cat: /sys/firmware/acpi/platform_profile_choices: No such file or directory $ cat /sys/devices/system/cpu/amd_pstate/status @@ -163,8 +167,50 @@ For more information, please refer to the [AMD P-State scaling driver documentation](https://www.kernel.org/doc/html/v6.3/admin-guide/pm/amd-pstate.html). -Testing -------- +### Panel power savings + +Laptops with integrated Radeon graphics have a dedicated hardware function +to decrease panel power consumption in exchange for color accuracy. This +function is used when the system is on battery and the user has selected +the "balanced" or "power-saver" profiles. + +If you decide that you don't like how this behaves, you can disable the function +in one of two ways: + +1. Adding `amdgpu.abmlevel=0` to the kernel command line. This will disable abm + value changes entirely. +2. By using `--block-action=amdgpu_panel_power` in the + `power-profiles-daemon` `ExecStart` command. This will allow you to + still change values manually in sysfs but `power-profiles-daemon` will not + change anything. + +## Multiple driver and multiple action operations + +Power-profiles daemon will load all supported drivers and actions by default. +If you have a problem with a given driver or action, you can disable it by +using `--block-action` or `--block-driver` with the name of the driver or action you want to disable +in the environment that launches the daemon (such as the systemd unit file). + +For example to edit the unit: + +```sh +sudo systemctl edit power-profiles-daemon.service +``` + +Then modify the Execstart line to the drop-in file: + +```text +[Service] +ExecStart=/usr/libexec/power-profiles-daemon --block-action=FOO +``` + +Then restart the service: + +```sh +sudo systemctl try-restart power-profiles-daemon.service +``` + +## Testing If you don't have hardware that can support the performance mode, or the degraded mode you can manually run the `power-profiles-daemon` binary as `root` with the environment @@ -174,8 +220,7 @@ sudo POWER_PROFILE_DAEMON_FAKE_DRIVER=1 /usr/libexec/power-profiles-daemon -r -v ``` -References ----------- +## References - [Use Low Power Mode to save battery life on your iPhone (iOS)](https://support.apple.com/en-us/HT205234) - [lowPowerModeEnabled (iOS)](https://developer.apple.com/documentation/foundation/nsprocessinfo/1617047-lowpowermodeenabled?language=objc) @@ -183,8 +228,7 @@ - [[S]ettings that use less battery (Android)](https://support.google.com/android/answer/7664692?hl=en&visit_id=637297348326801871-2263015427&rd=1) - [EnergySaverStatus Enum (Windows)](https://docs.microsoft.com/en-us/uwp/api/windows.system.power.energysaverstatus?view=winrt-19041) -Why power-profiles-daemon -------------------------- +## Why power-profiles-daemon The power-profiles-daemon project was created to help provide a solution for two separate use cases, for desktops, laptops, and other devices running a @@ -208,8 +252,7 @@ 10 years. We would also design that API to be as easily usable to build graphical interfaces as possible. -Why not... ----------- +## Why not This section will contain explanations of why this new daemon was written rather than re-using, or modifying an existing one. Each project obviously @@ -251,6 +294,7 @@ 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: diff -Nru power-profiles-daemon-0.13/check-news.sh power-profiles-daemon-0.21/check-news.sh --- power-profiles-daemon-0.13/check-news.sh 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/check-news.sh 2024-04-03 23:55:02.000000000 +0000 @@ -24,13 +24,13 @@ # # Checks NEWS for the version number: # meson.add_dist_script( -# find_program('check-news.sh').path(), +# find_program('check-news.sh').full_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(), +# find_program('check-news.sh').full_path(), # '@0@'.format(meson.project_version()), # 'NEWS', # 'data/foo.appdata.xml' @@ -48,9 +48,9 @@ # 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"` + DATA=$(sed 15q "$SRC_ROOT/$2") else - DATA=`cat $SRC_ROOT/"$2"` + DATA=$(cat "$SRC_ROOT/$2") fi case "$DATA" in *"$VERSION"*) @@ -71,12 +71,12 @@ shift if [ $# -eq 0 ] ; then - check_version $VERSION 'NEWS' + check_version "$VERSION" 'NEWS' exit 0 fi -for i in $@ ; do - check_version $VERSION "$i" +for i in "$@"; do + check_version "$VERSION" "$i" done -exit 0 \ No newline at end of file +exit 0 diff -Nru power-profiles-daemon-0.13/data/meson.build power-profiles-daemon-0.21/data/meson.build --- power-profiles-daemon-0.13/data/meson.build 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/data/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -1,32 +1,40 @@ -data_conf = configuration_data() -data_conf.set('libexecdir', libexecdir) - configure_file( input: 'power-profiles-daemon.service.in', output: 'power-profiles-daemon.service', - configuration: data_conf, + configuration: { + 'libexecdir': libexecdir, + }, install_dir: systemd_system_unit_dir, ) -configure_file( - input: 'net.hadess.PowerProfiles.conf.in', - output: 'net.hadess.PowerProfiles.conf', - configuration: data_conf, - install_dir: dbusconfdir -) +foreach name, _: bus_names + config = { + 'dbus_name': name, + 'dbus_iface': name, + } -install_data( - 'net.hadess.PowerProfiles.service', - install_dir: dbusservicedir -) + configure_file( + input: 'power-profiles-daemon.dbus.conf.in', + output: name + '.conf', + configuration: config, + install_dir: dbusconfdir + ) + + configure_file( + input: 'power-profiles-daemon.dbus.service.in', + output: name + '.service', + configuration: config, + install_dir: dbusservicedir + ) +endforeach -polkit_policy = 'net.hadess.PowerProfiles.policy' +polkit_policy = 'power-profiles-daemon.policy' if xmllint.found() test(polkit_policy, xmllint, args: [ '--noout', - meson.source_root() / 'data' / polkit_policy, + meson.project_source_root() / 'data' / polkit_policy, ]) endif diff -Nru power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.conf.in power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.conf.in --- power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.conf.in 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.conf.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff -Nru power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.policy power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.policy --- power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.policy 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.policy 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ - - - - - - power-profiles-daemon - https://gitlab.freedesktop.org/hadess/power-profiles-daemon - - - Switch Power Profile - Privileges are required to switch power profiles. - - no - no - yes - - - - - Hold Power Profile - Privileges are required to hold power profiles. - - no - no - yes - - - - diff -Nru power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.service power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.service --- power-profiles-daemon-0.13/data/net.hadess.PowerProfiles.service 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/data/net.hadess.PowerProfiles.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# 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. - -[D-BUS Service] -Name=net.hadess.PowerProfiles -Exec=/bin/false -User=root -SystemdService=power-profiles-daemon.service diff -Nru power-profiles-daemon-0.13/data/power-profiles-daemon.dbus.conf.in power-profiles-daemon-0.21/data/power-profiles-daemon.dbus.conf.in --- power-profiles-daemon-0.13/data/power-profiles-daemon.dbus.conf.in 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/data/power-profiles-daemon.dbus.conf.in 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff -Nru power-profiles-daemon-0.13/data/power-profiles-daemon.dbus.service.in power-profiles-daemon-0.21/data/power-profiles-daemon.dbus.service.in --- power-profiles-daemon-0.13/data/power-profiles-daemon.dbus.service.in 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/data/power-profiles-daemon.dbus.service.in 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# 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. + +[D-BUS Service] +Name=@dbus_name@ +Exec=/bin/false +User=root +SystemdService=power-profiles-daemon.service diff -Nru power-profiles-daemon-0.13/data/power-profiles-daemon.policy power-profiles-daemon-0.21/data/power-profiles-daemon.policy --- power-profiles-daemon-0.13/data/power-profiles-daemon.policy 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/data/power-profiles-daemon.policy 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,31 @@ + + + + + + power-profiles-daemon + https://gitlab.freedesktop.org/hadess/power-profiles-daemon + + + Switch Power Profile + Privileges are required to switch power profiles. + + no + no + yes + + + + + Hold Power Profile + Privileges are required to hold power profiles. + + no + no + yes + + + + diff -Nru power-profiles-daemon-0.13/data/power-profiles-daemon.service.in power-profiles-daemon-0.21/data/power-profiles-daemon.service.in --- power-profiles-daemon-0.13/data/power-profiles-daemon.service.in 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/data/power-profiles-daemon.service.in 2024-04-03 23:55:02.000000000 +0000 @@ -5,23 +5,36 @@ [Service] Type=dbus -BusName=net.hadess.PowerProfiles +BusName=org.freedesktop.UPower.PowerProfiles +# To enable debugging add a -vv to the ExecStart line ExecStart=@libexecdir@/power-profiles-daemon Restart=on-failure # This always corresponds to /var/lib/power-profiles-daemon StateDirectory=power-profiles-daemon -#Uncomment this to enable debug -#Environment="G_MESSAGES_DEBUG=all" # Lockdown +DevicePolicy=closed +KeyringMode=private +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectProc=invisible ProtectSystem=strict -ProtectControlGroups=true -ProtectHome=true -ProtectKernelModules=true -PrivateTmp=true +RemoveIPC=yes RestrictAddressFamilies=AF_UNIX AF_LOCAL AF_NETLINK MemoryDenyWriteExecute=true RestrictRealtime=true +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes [Install] WantedBy=graphical.target diff -Nru power-profiles-daemon-0.13/debian/changelog power-profiles-daemon-0.21/debian/changelog --- power-profiles-daemon-0.13/debian/changelog 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/changelog 2024-04-09 16:20:24.000000000 +0000 @@ -1,3 +1,72 @@ +power-profiles-daemon (0.21-1~23.10.1) mantic; urgency=medium + + * Backport to mantic (LP: #2008958) + * Disable bashcomp + + -- Mario Limonciello Tue, 09 Apr 2024 11:20:24 -0500 + +power-profiles-daemon (0.21-1) unstable; urgency=medium + + * New upstream release + + -- Marco Trevisan (Treviño) Thu, 04 Apr 2024 02:19:26 +0200 + +power-profiles-daemon (0.20-3) unstable; urgency=medium + + * debian/control: Build-depend on systemd-dev instead of systemd + (Closes: #1060563) + * debian/patches: Upstream fix on test failing messages + + -- Marco Trevisan (Treviño) Thu, 15 Feb 2024 18:53:27 +0100 + +power-profiles-daemon (0.20-2) unstable; urgency=medium + + * debian/rules: Only fix generated makefile for old argparse-manpage versions + * debian/patches: Restore systemd conflict with tlp. + Tlp is just too much, it can do too many things to be sure it's not + conflicting with p-p-d, so let's just enable the conflict again. + If someone wants TLP, then it should have only that one running. + Not to mention that tlp debian package already conflicts with + power-profiles-daemon. + * debian/control: Add conflict with laptop-mode-tools and tlp. + This is already handled by systemd, but makes it clearer that it's + better not having both installed at the same time at all. + Tlp is already conflicting with us, so enforce this from our side too. + * debian/patches: Refresh patch for generating manpage with upstream ones + * debian/patches: Cherry-pick upstream post-releases fixes + * debian/patches: Get upstream patches to generate bash/zsh-completions + * debian/control: Add build dependencies for generating bash-completions + * debian/rules: Explicitly enable bash-completions generation + * debian/rules: Enable generating and installing zsh completions in debian + There's not a default path for zsh completions defined, as it depends on + user configuration or distributions, however as per + /usr/share/doc/zsh-common/README.Debian in debian we can use + /usr/share/zsh/vendor-completions. + + -- Marco Trevisan (Treviño) Thu, 15 Feb 2024 17:13:20 +0100 + +power-profiles-daemon (0.20-1) unstable; urgency=medium + + * New upstream release: + - New default bus name is org.freedesktop.UPower.PowerProfiles + - Support multiple drivers (ACPI + amd/intel ones) + * debian/watch: Update reference to new upstream repository + * debian: Update references to new repository under UPower namespace + * debian/copyright: Update copyright to new maintainers + * debian/control: Add myself to uploaders + * debian/control: Add python dependencies + While the daemon does not require python at runtime, the control tool + does so ensure debian picks them + * debian/patches: Refresh + * debian/rules: drop dh_missing override for --fail-missing. + It's default for some time now + * debian/patches: Add upstream patch to generate powerprofilectl manfile + * debian/control: Add manpage generation dependencies + * debian/rules: Explicitly enable manpage feature + * debian/rules: Fix generated manpage entries + + -- Marco Trevisan (Treviño) Thu, 15 Feb 2024 05:38:30 +0100 + power-profiles-daemon (0.13-2) unstable; urgency=medium [ Debian Janitor ] diff -Nru power-profiles-daemon-0.13/debian/control power-profiles-daemon-0.21/debian/control --- power-profiles-daemon-0.13/debian/control 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/control 2024-04-04 03:42:32.000000000 +0000 @@ -2,8 +2,10 @@ Section: admin Priority: optional Maintainer: Debian freedesktop.org maintainers -Uploaders: Sebastien Bacher +Uploaders: Sebastien Bacher , Marco Trevisan (Treviño) Build-Depends: debhelper-compat (= 13), + dh-python, + bash-completion, libglib2.0-dev, libgudev-1.0-dev, libpolkit-gobject-1-dev, @@ -12,19 +14,24 @@ libumockdev-dev, libxml2-utils, meson, + python3, + python3-argparse-manpage, python3-dbus , python3-dbusmock , - python3-gi , - systemd, + python3-gi, + python3-shtab, + systemd-dev, umockdev , Standards-Version: 4.6.2 Vcs-Browser: https://salsa.debian.org/freedesktop-team/power-profiles-daemon Vcs-Git: https://salsa.debian.org/freedesktop-team/power-profiles-daemon.git -Homepage: https://gitlab.freedesktop.org/hadess/power-profiles-daemon +Homepage: https://gitlab.freedesktop.org/upower/power-profiles-daemon +X-Python3-Version: >= 3.11 Package: power-profiles-daemon Architecture: linux-any -Depends: ${misc:Depends}, ${shlibs:Depends}, +Conflicts: laptop-mode-tools, tlp +Depends: ${misc:Depends}, ${shlibs:Depends}, ${python3:Depends}, python3-gi Description: Makes power profiles handling available over D-Bus. power-profiles-daemon offers to modify system behaviour based upon user-selected power profiles. There are 3 different power profiles, a diff -Nru power-profiles-daemon-0.13/debian/copyright power-profiles-daemon-0.21/debian/copyright --- power-profiles-daemon-0.13/debian/copyright 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/copyright 2024-04-04 03:42:32.000000000 +0000 @@ -1,18 +1,26 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: power-profiles-daemon -Source: https://gitlab.freedesktop.org/hadess/power-profiles-daemon +Source: https://gitlab.freedesktop.org/upower/power-profiles-daemon Files: * Copyright: 2014-2016, 2020 Bastien Nocera + 2024 Marco Trevisan + 2024 Mario Limonciello License: GPL-3 Files: debian/* Copyright: 2021 Sebastien Bacher License: GPL-3 -Files: tests/integration-test +Files: tests/integration_test.py Copyright: 2011 Martin Pitt 2020 Bastien Nocera + 2024 Marco Trevisan + 2024 Mario Limonciello +License: GPL-2+ + +Files: tests/unittest_inspector.py +Copyright: Marco Trevisan License: GPL-2+ Files: docs/power-profiles-daemon-docs.xml diff -Nru power-profiles-daemon-0.13/debian/gbp.conf power-profiles-daemon-0.21/debian/gbp.conf --- power-profiles-daemon-0.13/debian/gbp.conf 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/gbp.conf 2024-04-04 03:42:32.000000000 +0000 @@ -7,6 +7,9 @@ [buildpackage] sign-tags = True +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit + [dch] multimaint-merge = True diff -Nru power-profiles-daemon-0.13/debian/patches/remove_tlp_conflict.patch power-profiles-daemon-0.21/debian/patches/remove_tlp_conflict.patch --- power-profiles-daemon-0.13/debian/patches/remove_tlp_conflict.patch 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/patches/remove_tlp_conflict.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -Description: remove the systemd unit conflict on tlp.service -we don't want that in Ubuntu since we patch tlp to not do performance modes -changes when power-profiles-daemon is active but the change can be -included in Debian also since the tlp maintainer made the packages conflict -which means we can't end up installed together -forwarded: not-needed -Index: power-profiles-daemon/data/power-profiles-daemon.service.in -=================================================================== ---- power-profiles-daemon.orig/data/power-profiles-daemon.service.in -+++ power-profiles-daemon/data/power-profiles-daemon.service.in -@@ -1,6 +1,6 @@ - [Unit] - Description=Power Profiles daemon --Conflicts=tuned.service tlp.service auto-cpufreq.service system76-power.service -+Conflicts=tuned.service auto-cpufreq.service system76-power.service - Before=multi-user.target display-manager.target - - [Service] diff -Nru power-profiles-daemon-0.13/debian/patches/series power-profiles-daemon-0.21/debian/patches/series --- power-profiles-daemon-0.13/debian/patches/series 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -remove_tlp_conflict.patch diff -Nru power-profiles-daemon-0.13/debian/rules power-profiles-daemon-0.21/debian/rules --- power-profiles-daemon-0.13/debian/rules 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/rules 2024-04-04 03:42:37.000000000 +0000 @@ -4,7 +4,19 @@ export DEB_LDFLAGS_MAINT_APPEND = -Wl,-z,defs %: - dh $@ + dh $@ --with python3 -override_dh_missing: - dh_missing --fail-missing +override_dh_auto_configure: + dh_auto_configure -- \ + -Dmanpage=enabled \ + -Dbashcomp=disabled \ + -Dzshcomp="/usr/share/zsh/vendor-completions" \ + $(NULL) + + # Newer python3-argparse-manpage is in debian but let's wait removing this + # until it's there for a while, so that there's not a mismatch. + if dpkg --compare-versions \ + "$$(dpkg-query --showformat='${Version}' --show python3-argparse-manpage)" lt "4.0"; then \ + sed -i s,argparse-manpage,powerprofilesctl,g \ + $(CURDIR)/obj-$(DEB_HOST_GNU_TYPE)/src/powerprofilesctl.1; \ + fi diff -Nru power-profiles-daemon-0.13/debian/upstream/metadata power-profiles-daemon-0.21/debian/upstream/metadata --- power-profiles-daemon-0.13/debian/upstream/metadata 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/upstream/metadata 2024-04-04 03:42:32.000000000 +0000 @@ -1,5 +1,5 @@ --- -Bug-Database: https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/issues -Bug-Submit: https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/issues/new -Repository: https://gitlab.freedesktop.org/hadess/power-profiles-daemon.git -Repository-Browse: https://gitlab.freedesktop.org/hadess/power-profiles-daemon +Bug-Database: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues +Bug-Submit: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues/new +Repository: https://gitlab.freedesktop.org/upower/power-profiles-daemon.git +Repository-Browse: https://gitlab.freedesktop.org/upower/power-profiles-daemon diff -Nru power-profiles-daemon-0.13/debian/watch power-profiles-daemon-0.21/debian/watch --- power-profiles-daemon-0.13/debian/watch 2023-06-12 17:29:15.000000000 +0000 +++ power-profiles-daemon-0.21/debian/watch 2024-04-04 03:42:32.000000000 +0000 @@ -1,3 +1,3 @@ version=4 -https://gitlab.freedesktop.org/hadess/power-profiles-daemon/tags \ +https://gitlab.freedesktop.org/upower/power-profiles-daemon/tags \ .*/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@ diff -Nru power-profiles-daemon-0.13/docs/meson.build power-profiles-daemon-0.21/docs/meson.build --- power-profiles-daemon-0.13/docs/meson.build 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/docs/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -11,8 +11,8 @@ content_files += gnome.gdbus_codegen( meson.project_name(), - sources: meson.source_root() / 'src' / 'net.hadess.PowerProfiles.xml', - interface_prefix: 'net.hadess', + sources: dbus_xml['org.freedesktop.UPower.PowerProfiles'], + interface_prefix: 'org.freedesktop.UPower', namespace: 'PowerProfiles', docbook: 'docs', build_by_default: true, @@ -21,13 +21,12 @@ private_headers = [ 'power-profiles-daemon.h', 'ppd-action-trickle-charge.h', - 'ppd-driver-balanced.h', + 'ppd-action-amdgpu-panel-power.h', 'ppd-driver-fake.h', 'ppd-driver-intel-pstate.h', 'ppd-driver-amd-pstate.h', 'ppd-driver-placeholder.h', 'ppd-driver-platform-profile.h', - 'ppd-driver-power-saver.h', 'ppd-utils.h', 'power-profiles-daemon-resources.h', ] @@ -39,8 +38,8 @@ dependencies: libpower_profiles_daemon_dep, gobject_typesfile: [join_paths(meson.current_source_dir(), 'power-profiles-daemon.types')], src_dir: [ - meson.source_root() /'src', - meson.build_root() / 'src', + meson.project_source_root() /'src', + meson.project_build_root() / 'src', ], ignore_headers: private_headers, ) diff -Nru power-profiles-daemon-0.13/docs/power-profiles-daemon-docs.xml power-profiles-daemon-0.21/docs/power-profiles-daemon-docs.xml --- power-profiles-daemon-0.13/docs/power-profiles-daemon-docs.xml 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/docs/power-profiles-daemon-docs.xml 2024-04-03 23:55:02.000000000 +0000 @@ -9,20 +9,46 @@ Version &version; - Bastien - Nocera - -
- hadess@hadess.net -
-
+ Bastien + Nocera + +
+ hadess@hadess.net +
+
+
+ + Mario + Limonciello + +
+ superm1@gmail.com +
+
+ + Marco + Trevisan + +
+ mail@3v1n0.net +
+
+
2020 Red Hat, Inc. + + 2024 + Advanced Micro Devices, Inc + + + 2024 + Canonical Ltd + @@ -63,14 +89,17 @@ Power Profiles daemon. - + Internal API + + + diff -Nru power-profiles-daemon-0.13/docs/power-profiles-daemon-sections.txt power-profiles-daemon-0.21/docs/power-profiles-daemon-sections.txt --- power-profiles-daemon-0.13/docs/power-profiles-daemon-sections.txt 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/docs/power-profiles-daemon-sections.txt 2024-04-03 23:55:02.000000000 +0000 @@ -1,4 +1,11 @@
+config + +VERSION +
+ + +
ppd-action Profile Actions PpdActionClass @@ -8,6 +15,15 @@
+ppd-action-amdgpu-panel-power +AMDGPU Power Panel Saving Action +PpdActionAmdgpuPanelPowerClass +_PpdActionAmdgpuPanelPower + +PPD_TYPE_ACTION +
+ +
ppd-driver Profile Drivers PpdDriverClass @@ -16,6 +32,22 @@ PpdProfileActivationReason PPD_TYPE_DRIVER +PPD_TYPE_DRIVER_CPU +PPD_TYPE_DRIVER_PLATFORM +
+ +
+ppd-driver-cpu +CPU Profile Drivers +PpdDriverCpuClass +PpdDriverCpu +
+ +
+ppd-driver-platform +Platform Profile Drivers +PpdDriverPlatformClass +PpdDriverPlatform
diff -Nru power-profiles-daemon-0.13/meson.build power-profiles-daemon-0.21/meson.build --- power-profiles-daemon-0.13/meson.build 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -1,12 +1,12 @@ project('power-profiles-daemon', [ 'c' ], - version: '0.13', + version: '0.21', license: 'GPLv3+', default_options: [ 'buildtype=debugoptimized', 'warning_level=1', 'c_std=c99', ], - meson_version: '>= 0.54.0') + meson_version: '>= 0.59.0') cc = meson.get_compiler('c') @@ -17,7 +17,12 @@ '-Wstrict-prototypes', '-Werror-implicit-function-declaration', '-Wno-pointer-sign', - '-Wshadow' + '-Wshadow', + '-Wno-sign-compare', + '-Wno-cast-function-type', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers', + '-Wno-type-limits', ]) prefix = get_option('prefix') @@ -29,26 +34,96 @@ systemd_system_unit_dir = get_option('systemdsystemunitdir') if systemd_system_unit_dir == 'auto' systemd_dep = dependency('systemd') - systemd_system_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir') + systemd_system_unit_dir = systemd_dep.get_variable('systemdsystemunitdir') endif +glib_dep = dependency('glib-2.0') +gio_unix_dep = dependency('gio-unix-2.0') gio_dep = dependency('gio-2.0') gudev_dep = dependency('gudev-1.0', version: '>= 234') upower_dep = dependency('upower-glib') -polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.114') -polkit_policy_directory = polkit_gobject_dep.get_pkgconfig_variable('policydir') +polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.99') +polkit_policy_directory = polkit_gobject_dep.get_variable('policydir') + +python3_required_modules = [] +gi_required_modules = {} + +powerprofilesctl_required_gi_modules = { + 'GLib': '2.0', + 'Gio': '2.0', +} gnome = import('gnome') add_global_arguments('-D_GNU_SOURCE=1', language: 'c') add_global_arguments(common_cflags, language: 'c') -if get_option('pylint') - pylint = find_program('pylint-3', 'pylint3', 'pylint', required: true) +pylint = find_program('pylint-3', 'pylint3', 'pylint', required: get_option('pylint')) +if pylint.found() nomalloc = environment({'MALLOC_PERTURB_': '0'}) pylint_flags = ['-d', 'C0116', '-d', 'C0114', '-d', 'W0707', '-d', 'W0706' ] endif xmllint = find_program('xmllint', required: false) +argparse_manpage = find_program('argparse-manpage', required: get_option('manpage')) +if argparse_manpage.found() + gi_required_modules += powerprofilesctl_required_gi_modules +endif + +bus_names = { + 'org.freedesktop.UPower.PowerProfiles': '/org/freedesktop/UPower/PowerProfiles', + 'net.hadess.PowerProfiles': '/net/hadess/PowerProfiles', +} + +address_sanitizer = get_option('b_sanitize') == 'address' or \ + get_option('b_sanitize') == 'address,undefined' or \ + get_option('b_sanitize') == 'leak' + +bashcomp = dependency('bash-completion', required: get_option('bashcomp')) +zshcomp = get_option('zshcomp') != '' + +python = import('python') +python3 = python.find_installation('python3') + +if bashcomp.found() or zshcomp + python3_required_modules += 'shtab' + gi_required_modules += powerprofilesctl_required_gi_modules +endif + +if get_option('tests') + python3_required_modules += [ + 'dbusmock', + ] + gi_required_modules += powerprofilesctl_required_gi_modules + gi_required_modules += { + 'UMockdev': '1.0', + } +endif + +# Python 3 required modules +if gi_required_modules.keys().length() > 0 + python3_required_modules += 'gi' +endif + +foreach p : python3_required_modules + # Source: https://docs.python.org/3/library/importlib.html#checking-if-a-module-can-be-imported + script = 'import importlib.util; import sys; exit(1) if importlib.util.find_spec(\''+ p +'\') is None else exit(0)' + if run_command(python3, '-c', script, check: false).returncode() == 0 + message('Python3 module \'@0@\' found: YES'.format(p)) + else + error('Python3 module \'@0@\' required but not found'.format(p)) + endif +endforeach + +foreach module, version : gi_required_modules + script = 'import gi; gi.require_version("@0@", "@1@")'.format(module, version) + if run_command(python3, '-c', script, check: false).returncode() == 0 + message('Python3 module \'@0@\' found: YES @1@'.format(module, version)) + else + error('''GObject Introspection module '@0@' version @1@ required but not found'''.format( + module, version)) + endif +endforeach + subdir('src') subdir('data') @@ -63,23 +138,19 @@ endif if get_option('tests') - # Python 3 required modules - python3_required_modules = ['dbusmock', 'gi'] - - python = import('python') - python3 = python.find_installation('python3') - foreach p : python3_required_modules - # Source: https://docs.python.org/3/library/importlib.html#checking-if-a-module-can-be-imported - script = 'import importlib.util; import sys; exit(1) if importlib.util.find_spec(\''+ p +'\') is None else exit(0)' - if run_command(python3, '-c', script, check: false).returncode() != 0 - error('Python3 module \'' + p + '\' required for running tests but not found') - endif - endforeach - subdir('tests') endif meson.add_dist_script( - find_program('check-news.sh').path(), + find_program('check-news.sh').full_path(), '@0@'.format(meson.project_version()) ) + +summary({ + 'tests': get_option('tests'), + 'bash-completion': bashcomp, + 'zsh-completion': zshcomp, + 'manpages': argparse_manpage.found(), + 'python linting': pylint.found(), + 'gtk_doc': get_option('gtk_doc'), +}) diff -Nru power-profiles-daemon-0.13/meson_options.txt power-profiles-daemon-0.21/meson_options.txt --- power-profiles-daemon-0.13/meson_options.txt 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/meson_options.txt 2024-04-03 23:55:02.000000000 +0000 @@ -7,10 +7,22 @@ value: false, description: 'Build docs') option('pylint', - type: 'boolean', - value: false, + type: 'feature', + value: 'auto', description: 'Run pylint checks, for developers only') option('tests', description: 'Whether to run tests', type: 'boolean', - value: false) + value: true) +option('manpage', + description: 'generate powerprofilesctl man page', + type: 'feature', + value: 'auto') +option('bashcomp', + description: 'generate bash completion', + type: 'feature', + value: 'auto') +option('zshcomp', + description: 'path for zsh completion file', + type: 'string', + value: '') diff -Nru power-profiles-daemon-0.13/src/completions/meson.build power-profiles-daemon-0.21/src/completions/meson.build --- power-profiles-daemon-0.13/src/completions/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/completions/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,36 @@ +generate_completion = [python3, powerprofilesctl, '--print-completion'] + +completions_common = { + 'env': {'PPD_COMPLETIONS_GENERATION': '1'}, + 'capture': true, + 'install': true, +} + +if bashcomp.found() + completions_dir = bashcomp.get_variable(pkgconfig: 'completionsdir', + pkgconfig_define: bashcomp.version().version_compare('>= 2.10') ? + ['datadir', get_option('datadir')] : ['prefix', prefix], + ) + + custom_target('bash-completion', + output: 'powerprofilesctl', + command: [ + generate_completion, + 'bash', + ], + install_dir: completions_dir, + kwargs: completions_common, + ) +endif + +if zshcomp + custom_target('zsh-completion', + output: '_powerprofilesctl', + command: [ + generate_completion, + 'zsh', + ], + install_dir: get_option('zshcomp'), + kwargs: completions_common, + ) +endif diff -Nru power-profiles-daemon-0.13/src/meson.build power-profiles-daemon-0.21/src/meson.build --- power-profiles-daemon-0.13/src/meson.build 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -1,17 +1,59 @@ -deps = [ gio_dep, gudev_dep, upower_dep, polkit_gobject_dep ] +deps = [ + gio_dep, + gio_unix_dep, + gudev_dep, + polkit_gobject_dep, + upower_dep, +] config_h = configuration_data() config_h.set_quoted('VERSION', meson.project_version()) +config_h.set('POLKIT_HAS_AUTOPOINTERS', polkit_gobject_dep.version().version_compare('>= 0.114')) config_h_files = configure_file( output: 'config.h', configuration: config_h ) +dbus_xml = {} +dbus_xml_sources = [] +resources_contents = [] + +foreach name, path: bus_names + config = { + 'dbus_name': name, + 'dbus_iface': name, + 'dbus_path': path, + } + + xml_source = configure_file( + input: 'power-profiles-daemon.dbus.xml.in', + output: name + '.xml', + configuration: config, + ) + dbus_xml_sources += xml_source + dbus_xml += {name: xml_source} + + resources_contents += '@0@.xml'.format(name) +endforeach + +resources_xml = configure_file( + input: 'power-profiles-daemon.gresource.xml.in', + output: '@BASENAME@', + configuration: { + 'prefix': bus_names['org.freedesktop.UPower.PowerProfiles'], + 'contents': '\n'.join(resources_contents), + }, +) + resources = gnome.compile_resources( - 'power-profiles-daemon-resources', 'power-profiles-daemon.gresource.xml', - c_name: 'power_profiles_daemon', - source_dir: '.', - export: true + 'power-profiles-daemon-resources', resources_xml, + c_name: 'power_profiles_daemon', + dependencies: dbus_xml_sources, + source_dir: [ + meson.current_source_dir(), + meson.current_build_dir(), + ], + export: true ) sources = [ @@ -19,6 +61,8 @@ 'ppd-utils.c', 'ppd-action.c', 'ppd-driver.c', + 'ppd-driver-cpu.c', + 'ppd-driver-platform.c', resources, ] @@ -45,6 +89,7 @@ sources += [ 'power-profiles-daemon.c', 'ppd-action-trickle-charge.c', + 'ppd-action-amdgpu-panel-power.c', 'ppd-driver-intel-pstate.c', 'ppd-driver-amd-pstate.c', 'ppd-driver-platform-profile.c', @@ -59,24 +104,52 @@ 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', +powerprofilesctl = configure_file( + input: files('powerprofilesctl'), output: 'powerprofilesctl', - configuration: ppd_conf, + configuration: { + 'VERSION': meson.project_version(), + }, install_dir: get_option('bindir') ) -if get_option('pylint') +if pylint.found() test('pylint-powerprofilesctl', pylint, - args: pylint_flags + [ script ], + args: pylint_flags + [ powerprofilesctl ], env: nomalloc, ) endif + +if argparse_manpage.found() + argparse_features = run_command(argparse_manpage, '--help', + check: true).stdout().strip() + + install_man(configure_file( + command: [ + argparse_manpage, + '--pyfile', powerprofilesctl, + '--function', 'get_parser', + argparse_features.contains('--author') ? + ['--author', 'Bastien Nocera', '--author', 'Mario Limonciello'] : [], + argparse_features.contains('--author-email') ? + ['--author-email', 'hadess@hadess.net', '--author-email', 'mario.limonciello@amd.com'] : [], + argparse_features.contains('--project-name') ? + ['--project-name', meson.project_name()] : [], + argparse_features.contains('--version') ? + ['--version', meson.project_version()] : [], + argparse_features.contains('--url') ? + ['--url', 'https://gitlab.freedesktop.org/upower/power-profiles-daemon'] : [], + argparse_features.contains('--description') ? + ['--manual-title', 'Power Profiles Daemon Control Program'] : [], + argparse_features.contains('--description') ? + ['--description', 'Command line utility to control Power Profiles Daemon'] : [], + argparse_features.contains('--format') ? + ['--format', 'single-commands-section'] : [], + ], + capture: true, + output: 'powerprofilesctl.1', + )) +endif + +subdir('completions') \ No newline at end of file diff -Nru power-profiles-daemon-0.13/src/net.hadess.PowerProfiles.xml power-profiles-daemon-0.21/src/net.hadess.PowerProfiles.xml --- power-profiles-daemon-0.13/src/net.hadess.PowerProfiles.xml 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/net.hadess.PowerProfiles.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru power-profiles-daemon-0.13/src/power-profiles-daemon.c power-profiles-daemon-0.21/src/power-profiles-daemon.c --- power-profiles-daemon-0.13/src/power-profiles-daemon.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/power-profiles-daemon.c 2024-04-03 23:55:02.000000000 +0000 @@ -8,26 +8,63 @@ * */ +#define G_LOG_DOMAIN "Core" + #include "config.h" +#include #include #include +#include +#include #include "power-profiles-daemon-resources.h" #include "power-profiles-daemon.h" -#include "ppd-driver.h" +#include "ppd-driver-cpu.h" +#include "ppd-driver-platform.h" #include "ppd-action.h" #include "ppd-enums.h" -#define POWER_PROFILES_DBUS_NAME "net.hadess.PowerProfiles" -#define POWER_PROFILES_DBUS_PATH "/net/hadess/PowerProfiles" +#define POWER_PROFILES_DBUS_NAME "org.freedesktop.UPower.PowerProfiles" +#define POWER_PROFILES_DBUS_PATH "/org/freedesktop/UPower/PowerProfiles" #define POWER_PROFILES_IFACE_NAME POWER_PROFILES_DBUS_NAME +#define POWER_PROFILES_LEGACY_DBUS_NAME "net.hadess.PowerProfiles" +#define POWER_PROFILES_LEGACY_DBUS_PATH "/net/hadess/PowerProfiles" +#define POWER_PROFILES_LEGACY_IFACE_NAME POWER_PROFILES_LEGACY_DBUS_NAME + +#define POWER_PROFILES_POLICY_NAMESPACE "org.freedesktop.UPower.PowerProfiles" + +#define POWER_PROFILES_RESOURCES_PATH "/org/freedesktop/UPower/PowerProfiles" + +#define UPOWER_DBUS_NAME "org.freedesktop.UPower" +#define UPOWER_DBUS_PATH "/org/freedesktop/UPower" +#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower" + +#define LOGIND_DBUS_NAME "org.freedesktop.login1" +#define LOGIND_DBUS_PATH "/org/freedesktop/login1" +#define LOGIND_DBUS_INTERFACE "org.freedesktop.login1.Manager" + +#ifndef POLKIT_HAS_AUTOPOINTERS +/* FIXME: Remove this once we're fine to depend on polkit 0.114 */ +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref) +#endif + +typedef struct { + GOptionGroup *group; + GLogLevelFlags log_level; + gboolean replace; + GStrv blocked_drivers; + GStrv blocked_actions; +} DebugOptions; + typedef struct { GMainLoop *main_loop; - GDBusNodeInfo *introspection_data; GDBusConnection *connection; + GCancellable *cancellable; guint name_id; + guint legacy_name_id; gboolean was_started; int ret; @@ -39,9 +76,20 @@ PpdProfile active_profile; PpdProfile selected_profile; GPtrArray *probed_drivers; - PpdDriver *driver; + PpdDriverCpu *cpu_driver; + PpdDriverPlatform *platform_driver; GPtrArray *actions; GHashTable *profile_holds; + + GDBusProxy *upower_proxy; + gulong upower_watch_id; + gulong upower_properties_id; + PpdPowerChangedReason power_changed_reason; + + guint logind_sleep_signal_id; + + GStrv blocked_drivers; + GStrv blocked_actions; } PpdApp; typedef struct { @@ -49,9 +97,19 @@ char *reason; char *application_id; char *requester; + char *requester_iface; } ProfileHold; static void +debug_options_free(DebugOptions *options) +{ + g_strfreev (options->blocked_drivers); + g_strfreev (options->blocked_actions); + g_free (options); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC (DebugOptions, debug_options_free) + +static void profile_hold_free (ProfileHold *hold) { if (hold == NULL) @@ -59,6 +117,7 @@ g_free (hold->reason); g_free (hold->application_id); g_free (hold->requester); + g_free (hold->requester_iface); g_free (hold); } @@ -66,12 +125,11 @@ static void stop_profile_drivers (PpdApp *data); static void start_profile_drivers (PpdApp *data); - -#define GET_DRIVER(p) (ppd_driver_get_profiles (data->driver) & p ? data->driver : NULL) -#define ACTIVE_DRIVER (data->driver) +static void upower_battery_set_power_changed_reason (PpdApp *, PpdPowerChangedReason); /* profile drivers and actions */ #include "ppd-action-trickle-charge.h" +#include "ppd-action-amdgpu-panel-power.h" #include "ppd-driver-placeholder.h" #include "ppd-driver-platform-profile.h" #include "ppd-driver-intel-pstate.h" @@ -92,6 +150,7 @@ /* Actions */ ppd_action_trickle_charge_get_type, + ppd_action_amdgpu_panel_power_get_type, }; typedef enum { @@ -100,19 +159,33 @@ PROP_PROFILES = 1 << 2, PROP_ACTIONS = 1 << 3, PROP_DEGRADED = 1 << 4, - PROP_ACTIVE_PROFILE_HOLDS = 1 << 5 + PROP_ACTIVE_PROFILE_HOLDS = 1 << 5, + PROP_VERSION = 1 << 6, } PropertiesMask; -#define PROP_ALL (PROP_ACTIVE_PROFILE | PROP_INHIBITED | PROP_PROFILES | PROP_ACTIONS | PROP_DEGRADED | PROP_ACTIVE_PROFILE_HOLDS) +#define PROP_ALL (PROP_ACTIVE_PROFILE | \ + PROP_INHIBITED | \ + PROP_PROFILES | \ + PROP_ACTIONS | \ + PROP_DEGRADED | \ + PROP_ACTIVE_PROFILE_HOLDS | \ + PROP_VERSION) + +static gboolean +driver_profile_support (PpdDriver *driver, + PpdProfile profile) +{ + if (!PPD_IS_DRIVER (driver)) + return FALSE; + return (ppd_driver_get_profiles (driver) & profile) != 0; +} static gboolean get_profile_available (PpdApp *data, PpdProfile profile) { - PpdDriver *driver; - - driver = GET_DRIVER(profile); - return driver != NULL; + return driver_profile_support (PPD_DRIVER (data->cpu_driver), profile) || + driver_profile_support (PPD_DRIVER (data->platform_driver), profile); } static const char * @@ -121,18 +194,28 @@ return ppd_profile_to_str (data->active_profile); } -static const char * +static char * get_performance_degraded (PpdApp *data) { - const char *ret; - PpdDriver *driver; + const gchar *cpu_degraded = NULL; + const gchar *platform_degraded = NULL; - driver = GET_DRIVER(PPD_PROFILE_PERFORMANCE); - if (!driver) - return ""; - ret = ppd_driver_get_performance_degraded (driver); - g_assert (ret != NULL); - return ret; + if (driver_profile_support (PPD_DRIVER (data->platform_driver), PPD_PROFILE_PERFORMANCE)) + platform_degraded = ppd_driver_get_performance_degraded (PPD_DRIVER (data->platform_driver)); + + if (driver_profile_support (PPD_DRIVER (data->cpu_driver), PPD_PROFILE_PERFORMANCE)) + cpu_degraded = ppd_driver_get_performance_degraded (PPD_DRIVER (data->cpu_driver)); + + if (!cpu_degraded && !platform_degraded) + return g_strdup (""); + + if (!cpu_degraded) + return g_strdup (platform_degraded); + + if (!platform_degraded) + return g_strdup (cpu_degraded); + + return g_strjoin (",", cpu_degraded, platform_degraded, NULL); } static GVariant * @@ -144,17 +227,40 @@ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); for (i = 0; i < NUM_PROFILES; i++) { - PpdDriver *driver = GET_DRIVER(1 << i); + PpdDriver *platform_driver = PPD_DRIVER (data->platform_driver); + PpdDriver *cpu_driver = PPD_DRIVER (data->cpu_driver); + PpdProfile profile = 1 << i; GVariantBuilder asv_builder; + gboolean cpu, platform; + const gchar *driver = NULL; - if (driver == NULL) + /* check if any of the drivers support */ + if (!get_profile_available (data, profile)) continue; g_variant_builder_init (&asv_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&asv_builder, "{sv}", "Profile", - g_variant_new_string (ppd_profile_to_str (1 << i))); - g_variant_builder_add (&asv_builder, "{sv}", "Driver", - g_variant_new_string (ppd_driver_get_driver_name (driver))); + g_variant_new_string (ppd_profile_to_str (profile))); + cpu = driver_profile_support (cpu_driver, profile); + platform = driver_profile_support (platform_driver, profile); + if (cpu) + g_variant_builder_add (&asv_builder, "{sv}", "CpuDriver", + g_variant_new_string (ppd_driver_get_driver_name (cpu_driver))); + if (platform) + g_variant_builder_add (&asv_builder, "{sv}", "PlatformDriver", + g_variant_new_string (ppd_driver_get_driver_name (platform_driver))); + + /* compatibility with older API */ + if (cpu && platform) + driver = "multiple"; + else if (cpu) + driver = ppd_driver_get_driver_name (cpu_driver); + else if (platform) + driver = ppd_driver_get_driver_name (platform_driver); + + if (driver) + g_variant_builder_add (&asv_builder, "{sv}", "Driver", + g_variant_new_string (driver)); g_variant_builder_add (&builder, "a{sv}", &asv_builder); } @@ -207,18 +313,20 @@ } static void -send_dbus_event (PpdApp *data, - PropertiesMask mask) +send_dbus_event_iface (PpdApp *data, + PropertiesMask mask, + const gchar *iface, + const gchar *path) { GVariantBuilder props_builder; GVariant *props_changed = NULL; - g_assert (data->connection); + g_return_if_fail (data->connection); if (mask == 0) return; - g_assert ((mask & PROP_ALL) != 0); + g_return_if_fail ((mask & PROP_ALL) != 0); g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); @@ -231,8 +339,9 @@ g_variant_new_string ("")); } if (mask & PROP_DEGRADED) { + gchar *degraded = get_performance_degraded (data); g_variant_builder_add (&props_builder, "{sv}", "PerformanceDegraded", - g_variant_new_string (get_performance_degraded (data))); + g_variant_new_take_string (g_steal_pointer (°raded))); } if (mask & PROP_PROFILES) { g_variant_builder_add (&props_builder, "{sv}", "Profiles", @@ -246,26 +355,53 @@ g_variant_builder_add (&props_builder, "{sv}", "ActiveProfileHolds", get_profile_holds_variant (data)); } + if (mask & PROP_VERSION) { + g_variant_builder_add (&props_builder, "{sv}", "Version", + g_variant_new_string (VERSION)); + } - props_changed = g_variant_new ("(s@a{sv}@as)", POWER_PROFILES_IFACE_NAME, + props_changed = g_variant_new ("(s@a{sv}@as)", iface, g_variant_builder_end (&props_builder), g_variant_new_strv (NULL, 0)); g_dbus_connection_emit_signal (data->connection, NULL, - POWER_PROFILES_DBUS_PATH, + path, "org.freedesktop.DBus.Properties", "PropertiesChanged", props_changed, NULL); } static void +send_dbus_event (PpdApp *data, + PropertiesMask mask) +{ + send_dbus_event_iface (data, mask, + POWER_PROFILES_IFACE_NAME, + POWER_PROFILES_DBUS_PATH); + send_dbus_event_iface (data, mask, + POWER_PROFILES_LEGACY_IFACE_NAME, + POWER_PROFILES_LEGACY_DBUS_PATH); +} + +static void save_configuration (PpdApp *data) { g_autoptr(GError) error = NULL; - g_key_file_set_string (data->config, "State", "Driver", ppd_driver_get_driver_name (data->driver)); - g_key_file_set_string (data->config, "State", "Profile", ppd_profile_to_str (data->active_profile)); + if (PPD_IS_DRIVER_CPU (data->cpu_driver)) { + g_key_file_set_string (data->config, "State", "CpuDriver", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver))); + } + + if (PPD_IS_DRIVER_PLATFORM (data->platform_driver)) { + g_key_file_set_string (data->config, "State", "PlatformDriver", + ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver))); + } + + g_key_file_set_string (data->config, "State", "Profile", + ppd_profile_to_str (data->active_profile)); + if (!g_key_file_save_to_file (data->config, data->config_path, &error)) g_warning ("Could not save configuration file '%s': %s", data->config_path, error->message); } @@ -273,16 +409,26 @@ static gboolean apply_configuration (PpdApp *data) { - g_autofree char *driver = NULL; + g_autofree char *platform_driver = NULL; g_autofree char *profile_str = NULL; + g_autofree char *cpu_driver = NULL; PpdProfile profile; - driver = g_key_file_get_string (data->config, "State", "Driver", NULL); - if (g_strcmp0 (ppd_driver_get_driver_name (data->driver), driver) != 0) + cpu_driver = g_key_file_get_string (data->config, "State", "CpuDriver", NULL); + if (PPD_IS_DRIVER_CPU (data->cpu_driver) && + g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), cpu_driver) != 0) return FALSE; + + platform_driver = g_key_file_get_string (data->config, "State", "PlatformDriver", NULL); + + if (PPD_IS_DRIVER_PLATFORM (data->platform_driver) && + g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)), platform_driver) != 0) + return FALSE; + profile_str = g_key_file_get_string (data->config, "State", "Profile", NULL); if (profile_str == NULL) return FALSE; + profile = ppd_profile_from_str (profile_str); if (profile == PPD_PROFILE_UNSET) { g_debug ("Resetting invalid configuration profile '%s'", profile_str); @@ -320,12 +466,10 @@ for (i = 0; i < actions->len; i++) { g_autoptr(GError) error = NULL; PpdAction *action; - gboolean ret; action = g_ptr_array_index (actions, i); - ret = ppd_action_activate_profile (action, profile, &error); - if (!ret) + if (!ppd_action_activate_profile (action, profile, &error)) g_warning ("Failed to activate action '%s' to profile %s: %s", ppd_profile_to_str (profile), ppd_action_get_action_name (action), @@ -339,18 +483,46 @@ PpdProfileActivationReason reason, GError **error) { - GError *internal_error = NULL; + PpdProfile current_profile = data->active_profile; - g_debug ("Setting active profile '%s' for reason '%s' (current: '%s')", + g_info ("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)); + ppd_profile_to_str (current_profile)); + + /* Try CPU first */ + if (driver_profile_support (PPD_DRIVER (data->cpu_driver), target_profile) && + !ppd_driver_activate_profile (PPD_DRIVER (data->cpu_driver), + target_profile, reason, error)) { + g_prefix_error (error, "Failed to activate CPU driver '%s': ", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver))); + return FALSE; + } + + /* Then try platform */ + if (driver_profile_support (PPD_DRIVER (data->platform_driver), target_profile) && + !ppd_driver_activate_profile (PPD_DRIVER (data->platform_driver), + target_profile, reason, error)) { + g_autoptr(GError) recovery_error = NULL; + + g_prefix_error (error, "Failed to activate platform driver '%s': ", + ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver))); + + if (!PPD_IS_DRIVER (data->cpu_driver)) + return FALSE; + + g_debug ("Reverting CPU driver '%s' to profile '%s'", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), + ppd_profile_to_str (current_profile)); + + if (!ppd_driver_activate_profile (PPD_DRIVER (data->cpu_driver), + current_profile, PPD_PROFILE_ACTIVATION_REASON_INTERNAL, + &recovery_error)) { + g_warning ("Failed to revert CPU driver '%s': %s", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), + recovery_error->message); + } - if (!ppd_driver_activate_profile (data->driver, target_profile, reason, &internal_error)) { - g_warning ("Failed to activate driver '%s': %s", - ppd_driver_get_driver_name (data->driver), - internal_error->message); - g_propagate_error (error, internal_error); return FALSE; } @@ -366,6 +538,21 @@ } static void +release_hold_notify (PpdApp *data, + ProfileHold *hold, + guint cookie) +{ + const char *req_path = POWER_PROFILES_DBUS_PATH; + + if (g_strcmp0 (hold->requester_iface, POWER_PROFILES_LEGACY_IFACE_NAME) == 0) + req_path = POWER_PROFILES_LEGACY_DBUS_PATH; + + g_dbus_connection_emit_signal (data->connection, hold->requester, req_path, + hold->requester_iface, "ProfileReleased", + g_variant_new ("(u)", cookie), NULL); +} + +static void release_all_profile_holds (PpdApp *data) { GHashTableIter iter; @@ -376,9 +563,7 @@ ProfileHold *hold = value; guint cookie = GPOINTER_TO_UINT (key); - g_dbus_connection_emit_signal (data->connection, hold->requester, POWER_PROFILES_DBUS_PATH, - POWER_PROFILES_IFACE_NAME, "ProfileReleased", - g_variant_new ("(u)", cookie), NULL); + release_hold_notify (data, hold, cookie); g_bus_unwatch_name (cookie); } g_hash_table_remove_all (data->profile_holds); @@ -496,12 +681,13 @@ hold = g_hash_table_lookup (data->profile_holds, GUINT_TO_POINTER (cookie)); if (!hold) { - g_debug("No hold with cookie %d", cookie); + g_debug ("No hold with cookie %d", cookie); return; } g_bus_unwatch_name (cookie); hold_profile = hold->profile; + release_hold_notify (data, hold, cookie); g_hash_table_remove (data->profile_holds, GUINT_TO_POINTER (cookie)); if (g_hash_table_size (data->profile_holds) == 0 && @@ -587,8 +773,9 @@ hold->reason = g_strdup (reason); hold->application_id = g_strdup (application_id); hold->requester = g_strdup (g_dbus_method_invocation_get_sender (invocation)); + hold->requester_iface = g_strdup (g_dbus_method_invocation_get_interface_name (invocation)); - g_debug ("%s(%s) requesting to hold profile '%s', reason: '%s'", application_id, + g_debug ("%s (%s) requesting to hold profile '%s', reason: '%s'", application_id, hold->requester, profile_name, reason); watch_id = g_bus_watch_name_on_connection (data->connection, hold->requester, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, @@ -666,7 +853,7 @@ { PpdApp *data = user_data; - g_assert (data->connection); + g_return_val_if_fail (data->connection, NULL); if (g_strcmp0 (property_name, "ActiveProfile") == 0) return g_variant_new_string (get_active_profile (data)); @@ -676,10 +863,14 @@ return get_profiles_variant (data); if (g_strcmp0 (property_name, "Actions") == 0) return get_actions_variant (data); - if (g_strcmp0 (property_name, "PerformanceDegraded") == 0) - return g_variant_new_string (get_performance_degraded (data)); + if (g_strcmp0 (property_name, "PerformanceDegraded") == 0) { + gchar *degraded = get_performance_degraded (data); + return g_variant_new_take_string (g_steal_pointer (°raded)); + } if (g_strcmp0 (property_name, "ActiveProfileHolds") == 0) return get_profile_holds_variant (data); + if (g_strcmp0 (property_name, "Version") == 0) + return g_variant_new_string (VERSION); return NULL; } @@ -696,14 +887,16 @@ PpdApp *data = user_data; const char *profile; - g_assert (data->connection); + g_return_val_if_fail (data->connection, FALSE); if (g_strcmp0 (property_name, "ActiveProfile") != 0) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No such property: %s", property_name); return FALSE; } - if (!check_action_permission (data, sender, "net.hadess.PowerProfiles.switch-profile", error)) + if (!check_action_permission (data, sender, + POWER_PROFILES_POLICY_NAMESPACE ".switch-profile", + error)) return FALSE; g_variant_get (value, "&s", &profile); @@ -721,9 +914,10 @@ gpointer user_data) { PpdApp *data = user_data; - g_assert (data->connection); + g_return_if_fail (data->connection); - if (g_strcmp0 (interface_name, POWER_PROFILES_IFACE_NAME) != 0) { + if (!g_str_equal (interface_name, POWER_PROFILES_IFACE_NAME) && + !g_str_equal (interface_name, POWER_PROFILES_LEGACY_IFACE_NAME)) { g_dbus_method_invocation_return_error (invocation,G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface %s", interface_name); return; @@ -733,7 +927,7 @@ g_autoptr(GError) local_error = NULL; if (!check_action_permission (data, g_dbus_method_invocation_get_sender (invocation), - "net.hadess.PowerProfiles.hold-profile", + POWER_PROFILES_POLICY_NAMESPACE ".hold-profile", &local_error)) { g_dbus_method_invocation_return_gerror (invocation, local_error); return; @@ -756,16 +950,42 @@ handle_set_property }; +typedef struct { + PpdApp *app; + GBusNameOwnerFlags flags; + GDBusInterfaceInfo *interface; + GDBusInterfaceInfo *legacy_interface; +} PpdBusOwnData; + +static void +ppd_bus_own_data_free (PpdBusOwnData *data) +{ + g_clear_pointer (&data->interface, g_dbus_interface_info_unref); + g_clear_pointer (&data->legacy_interface, g_dbus_interface_info_unref); + g_free (data); +} + static void 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."); - if (!data->was_started) - data->ret = 1; - g_main_loop_quit (data->main_loop); + PpdBusOwnData *data = user_data; + PpdApp *app = data->app; + + g_warning ("power-profiles-daemon is already running, or it cannot own its D-Bus name. Verify installation."); + if (!app->was_started) + app->ret = EXIT_FAILURE; + + g_main_loop_quit (app->main_loop); +} + +static void +legacy_name_acquired_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("Name '%s' acquired", name); } static void @@ -773,72 +993,362 @@ const gchar *name, gpointer user_data) { - PpdApp *data = user_data; + PpdBusOwnData *data = user_data; g_dbus_connection_register_object (connection, POWER_PROFILES_DBUS_PATH, - data->introspection_data->interfaces[0], + data->interface, + &interface_vtable, + data->app, + NULL, + NULL); + + g_dbus_connection_register_object (connection, + POWER_PROFILES_LEGACY_DBUS_PATH, + data->legacy_interface, &interface_vtable, - data, + data->app, NULL, NULL); - data->connection = g_object_ref (connection); + data->app->legacy_name_id = g_bus_own_name_on_connection (connection, + POWER_PROFILES_LEGACY_DBUS_NAME, + data->flags, + legacy_name_acquired_handler, + name_lost_handler, + data, + NULL); + + data->app->connection = g_object_ref (connection); +} + +static void +upower_battery_update_state_from_value (PpdApp *data, + GVariant *battery_val) +{ + PpdPowerChangedReason reason; + + if (!battery_val) + reason = PPD_POWER_CHANGED_REASON_UNKNOWN; + else if (g_variant_get_boolean (battery_val)) + reason = PPD_POWER_CHANGED_REASON_BATTERY; + else + reason = PPD_POWER_CHANGED_REASON_AC; + + upower_battery_set_power_changed_reason (data, reason); +} + +static void +upower_battery_update_state (PpdApp *data) +{ + g_autoptr(GVariant) battery_val = NULL; + + battery_val = g_dbus_proxy_get_cached_property (data->upower_proxy, "OnBattery"); + upower_battery_update_state_from_value (data, battery_val); +} + +static void +upower_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + PpdApp *data) +{ + g_auto(GVariantDict) props_dict = G_VARIANT_DICT_INIT (changed_properties); + g_autoptr(GVariant) battery_val = NULL; + + battery_val = g_variant_dict_lookup_value (&props_dict, "OnBattery", + G_VARIANT_TYPE_BOOLEAN); + + if (battery_val) + upower_battery_update_state_from_value (data, battery_val); +} + +static void +upower_battery_set_power_changed_reason (PpdApp *data, + PpdPowerChangedReason reason) +{ + if (data->power_changed_reason == reason) + return; + + data->power_changed_reason = reason; + g_info ("Power Changed because of reason %s", + ppd_power_changed_reason_to_str (reason)); + + for (guint i = 0; i < data->actions->len; i++) { + g_autoptr(GError) error = NULL; + PpdAction *action; + + action = g_ptr_array_index (data->actions, i); + + if (!ppd_action_power_changed (action, reason, &error)) { + g_warning ("failed to update action %s: %s", + ppd_action_get_action_name (action), + error->message); + g_clear_error (&error); + continue; + } + } + + if (PPD_IS_DRIVER_CPU (data->cpu_driver)) { + g_autoptr(GError) error = NULL; + + if (!ppd_driver_power_changed (PPD_DRIVER (data->cpu_driver), reason, &error)) { + g_warning ("failed to update driver %s: %s", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), + error->message); + } + } + + if (PPD_IS_DRIVER_PLATFORM (data->platform_driver)) { + g_autoptr(GError) error = NULL; + + if (!ppd_driver_power_changed (PPD_DRIVER (data->platform_driver), reason, &error)) { + g_warning ("failed to update driver %s: %s", + ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)), + error->message); + } + } +} + +static void +upower_name_owner_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + PpdApp *data = user_data; + GDBusProxy *upower_proxy = G_DBUS_PROXY (object); + g_autofree char *name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner (upower_proxy); + + if (name_owner != NULL) { + g_debug ("%s appeared", UPOWER_DBUS_NAME); + upower_battery_update_state (data); + return; + } + + g_debug ("%s vanished", UPOWER_DBUS_NAME); + upower_battery_set_power_changed_reason (data, PPD_POWER_CHANGED_REASON_UNKNOWN); +} + +static void +on_upower_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + PpdApp *data = user_data; + g_autoptr(GDBusProxy) upower_proxy = NULL; + g_autoptr(GError) error = NULL; + + upower_proxy = g_dbus_proxy_new_finish (res, &error); + + if (upower_proxy == NULL) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_warning ("failed to connect to upower: %s", error->message); + return; + } + + g_return_if_fail (data->upower_proxy == NULL); + data->upower_proxy = g_steal_pointer (&upower_proxy); + + data->upower_properties_id = g_signal_connect (data->upower_proxy, + "g-properties-changed", + G_CALLBACK (upower_properties_changed), + data); + + data->upower_watch_id = g_signal_connect (data->upower_proxy, + "notify::g-name-owner", + G_CALLBACK (upower_name_owner_changed), + data); + + upower_battery_update_state (data); +} + +static void +on_logind_prepare_for_sleep_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + PpdApp *data = user_data; + gboolean start; + + g_variant_get (parameters, "(b)", &start); + + if (start) + g_debug ("System preparing for suspend"); + else + g_debug ("System woke up from suspend"); + + if (PPD_IS_DRIVER_CPU (data->cpu_driver)) { + g_autoptr(GError) error = NULL; + + if (!ppd_driver_prepare_to_sleep (PPD_DRIVER (data->cpu_driver), start, &error)) { + g_warning ("failed to notify driver %s: %s", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), + error->message); + } + } + + if (PPD_IS_DRIVER_PLATFORM (data->platform_driver)) { + g_autoptr(GError) error = NULL; + + if (!ppd_driver_prepare_to_sleep (PPD_DRIVER (data->platform_driver), start, &error)) { + g_warning ("failed to notify driver %s: %s", + ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)), + error->message); + } + } } static gboolean has_required_drivers (PpdApp *data) { - PpdDriver *driver; - - driver = GET_DRIVER (PPD_PROFILE_BALANCED); - if (!driver || !G_IS_OBJECT (driver)) + if (!PPD_IS_DRIVER_CPU (data->cpu_driver) && + !PPD_IS_DRIVER_PLATFORM (data->platform_driver)) return FALSE; - driver = GET_DRIVER (PPD_PROFILE_POWER_SAVER); - if (!driver || !G_IS_OBJECT (driver)) + + if (!get_profile_available (data, PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER)) return FALSE; return TRUE; } static void +restart_profile_drivers (PpdApp *data) +{ + stop_profile_drivers (data); + start_profile_drivers (data); +} + +static void driver_probe_request_cb (PpdDriver *driver, gpointer user_data) { PpdApp *data = user_data; - stop_profile_drivers (data); - start_profile_drivers (data); + restart_profile_drivers (data); +} + +static void +disconnect_array_objects_signals_by_data (GPtrArray *array, + void *data) +{ + for (guint i = 0; i < array->len; ++i) { + GObject *object = g_ptr_array_index (array, i); + + g_signal_handlers_disconnect_by_data (object, data); + } +} + +static void +maybe_disconnect_object_by_data (void *object, + void *data) +{ + if (!G_IS_OBJECT (object)) + return; + + g_signal_handlers_disconnect_by_data (object, data); } static void stop_profile_drivers (PpdApp *data) { + if (data->logind_sleep_signal_id) { + g_dbus_connection_signal_unsubscribe (data->connection, data->logind_sleep_signal_id); + data->logind_sleep_signal_id = 0; + } + + upower_battery_set_power_changed_reason (data, PPD_POWER_CHANGED_REASON_UNKNOWN); release_all_profile_holds (data); + g_cancellable_cancel (data->cancellable); + disconnect_array_objects_signals_by_data (data->probed_drivers, data); g_ptr_array_set_size (data->probed_drivers, 0); + disconnect_array_objects_signals_by_data (data->actions, data); g_ptr_array_set_size (data->actions, 0); - g_clear_object (&data->driver); + g_clear_signal_handler (&data->upower_watch_id, data->upower_proxy); + g_clear_signal_handler (&data->upower_properties_id, data->upower_proxy); + g_clear_object (&data->cancellable); + maybe_disconnect_object_by_data (data->upower_proxy, data); + g_clear_object (&data->upower_proxy); + maybe_disconnect_object_by_data (data->cpu_driver, data); + g_clear_object (&data->cpu_driver); + maybe_disconnect_object_by_data (data->platform_driver, data); + g_clear_object (&data->platform_driver); +} + +static gboolean +action_blocked (PpdApp *app, PpdAction *action) +{ + const gchar *action_name = ppd_action_get_action_name (action); + gboolean blocked; + + if (app->blocked_actions == NULL || g_strv_length (app->blocked_actions) == 0) + return FALSE; + + blocked = g_strv_contains ((const gchar *const *) app->blocked_actions, action_name); + + if (blocked) + g_debug ("Action '%s' is blocked", action_name); + return blocked; +} + +static gboolean +driver_blocked (PpdApp *app, PpdDriver *driver) +{ + const gchar *driver_name = ppd_driver_get_driver_name (driver); + gboolean blocked; + + if (app->blocked_drivers == NULL || g_strv_length (app->blocked_drivers) == 0) + return FALSE; + + blocked = g_strv_contains ((const gchar *const *) app->blocked_drivers, driver_name); + if (blocked) + g_debug ("Driver '%s' is blocked", driver_name); + return blocked; } static void start_profile_drivers (PpdApp *data) { guint i; + g_autoptr(GError) initial_error = NULL; + gboolean needs_battery_monitor = FALSE; + gboolean needs_suspend_monitor = FALSE; + + data->cancellable = g_cancellable_new (); for (i = 0; i < G_N_ELEMENTS (objects); i++) { - GObject *object; + g_autoptr(GObject) object = NULL; + + object = g_object_new (objects[i] (), NULL); - object = g_object_new (objects[i](), NULL); if (PPD_IS_DRIVER (object)) { - PpdDriver *driver = PPD_DRIVER (object); + g_autoptr(PpdDriver) driver = PPD_DRIVER (g_steal_pointer (&object)); PpdProfile profiles; PpdProbeResult result; g_debug ("Handling driver '%s'", ppd_driver_get_driver_name (driver)); + if (driver_blocked (data, driver)) { + g_debug ("Driver '%s' is blocked, skipping", ppd_driver_get_driver_name (driver)); + continue; + } + + if (PPD_IS_DRIVER_CPU (data->cpu_driver) && PPD_IS_DRIVER_CPU (driver)) { + g_debug ("CPU driver '%s' already probed, skipping driver '%s'", + ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), + ppd_driver_get_driver_name (driver)); + continue; + } - if (data->driver != NULL) { - g_debug ("Driver '%s' already probed, skipping driver '%s'", - ppd_driver_get_driver_name (data->driver), + if (PPD_IS_DRIVER_PLATFORM (data->platform_driver) && PPD_IS_DRIVER_PLATFORM (driver)) { + g_debug ("Platform driver '%s' already probed, skipping driver '%s'", + ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)), ppd_driver_get_driver_name (driver)); continue; } @@ -848,111 +1358,191 @@ g_warning ("Profile Driver '%s' implements invalid profiles '0x%X'", ppd_driver_get_driver_name (driver), profiles); - g_object_unref (object); continue; } result = ppd_driver_probe (driver); if (result == PPD_PROBE_RESULT_FAIL) { - g_debug ("probe() failed for driver %s, skipping", + 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) { + } + + 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); + g_ptr_array_add (data->probed_drivers, g_steal_pointer (&driver)); continue; } - data->driver = driver; + if (PPD_IS_DRIVER_CPU (driver)) + g_set_object (&data->cpu_driver, PPD_DRIVER_CPU (driver)); + else if (PPD_IS_DRIVER_PLATFORM (driver)) + g_set_object (&data->platform_driver, PPD_DRIVER_PLATFORM (driver)); + else + g_return_if_reached (); + + if (PPD_DRIVER_GET_CLASS (driver)->power_changed != NULL) + needs_battery_monitor = TRUE; + + if (PPD_DRIVER_GET_CLASS (driver)->prepare_to_sleep != NULL) + needs_suspend_monitor = TRUE; + g_info ("Driver '%s' loaded", ppd_driver_get_driver_name (driver)); g_signal_connect (G_OBJECT (driver), "notify::performance-degraded", G_CALLBACK (driver_performance_degraded_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); + continue; + } + + if (PPD_IS_ACTION (object)) { + g_autoptr(PpdAction) action = PPD_ACTION (g_steal_pointer (&object)); g_debug ("Handling action '%s'", ppd_action_get_action_name (action)); - if (!ppd_action_probe (action)) { - g_debug ("probe() failed for action '%s', skipping", + if (action_blocked (data, action)) { + g_debug ("Action '%s' is blocked, skipping", ppd_action_get_action_name (action)); + continue; + } + + if (ppd_action_probe (action) == PPD_PROBE_RESULT_FAIL) { + g_debug ("probe () failed for action '%s', skipping", ppd_action_get_action_name (action)); - g_object_unref (object); continue; } - g_ptr_array_add (data->actions, action); - } else { - g_assert_not_reached (); + if (PPD_ACTION_GET_CLASS (action)->power_changed != NULL) + needs_battery_monitor = TRUE; + + g_info ("Action '%s' loaded", ppd_action_get_action_name (action)); + g_ptr_array_add (data->actions, g_steal_pointer (&action)); + continue; } + + g_return_if_reached (); } if (!has_required_drivers (data)) { + data->ret = EXIT_FAILURE; g_warning ("Some non-optional profile drivers are missing, programmer error"); - goto bail; + g_main_loop_quit (data->main_loop); } /* Set initial state either from configuration, or using the currently selected profile */ apply_configuration (data); - activate_target_profile (data, data->active_profile, PPD_PROFILE_ACTIVATION_REASON_RESET, NULL); + if (!activate_target_profile (data, data->active_profile, PPD_PROFILE_ACTIVATION_REASON_RESET, &initial_error)) + g_warning ("Failed to activate initial profile: %s", initial_error->message); send_dbus_event (data, PROP_ALL); - data->was_started = TRUE; - return; + if (needs_battery_monitor) { + /* start watching for power changes */ + g_debug ("Battery state monitor required, connecting to upower..."); + g_dbus_proxy_new (data->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + UPOWER_DBUS_NAME, + UPOWER_DBUS_PATH, + UPOWER_DBUS_INTERFACE, + data->cancellable, + on_upower_proxy_cb, + data); + } else { + g_debug ("No battery state monitor required by any driver, let's skip it"); + } -bail: - data->ret = 1; - g_debug ("Exiting because some non recoverable error occurred during startup"); - g_main_loop_quit (data->main_loop); + if (needs_suspend_monitor) { + g_debug ("Suspension state monitor required, monitoring logind..."); + data->logind_sleep_signal_id = + g_dbus_connection_signal_subscribe (data->connection, + LOGIND_DBUS_NAME, + LOGIND_DBUS_INTERFACE, + "PrepareForSleep", + LOGIND_DBUS_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_logind_prepare_for_sleep_cb, + data, + NULL); + } else { + g_debug ("No suspension monitor required by any driver, let's skip it"); + } } void -restart_profile_drivers (void) +restart_profile_drivers_for_default_app (void) { - stop_profile_drivers (ppd_app); - start_profile_drivers (ppd_app); + restart_profile_drivers (ppd_app); } + static void name_acquired_handler (GDBusConnection *connection, const gchar *name, gpointer user_data) { - PpdApp *data = user_data; + PpdBusOwnData *data = user_data; - start_profile_drivers (data); + g_debug ("Name '%s' acquired", name); + + start_profile_drivers (data->app); } static gboolean -setup_dbus (PpdApp *data, - gboolean replace) -{ - GBytes *bytes; - GBusNameOwnerFlags flags; +setup_dbus (PpdApp *data, + gboolean replace, + GError **error) +{ + g_autoptr(GBytes) iface_data = NULL; + g_autoptr(GBytes) legacy_iface_data = NULL; + g_autoptr(GDBusNodeInfo) introspection_data = NULL; + g_autoptr(GDBusNodeInfo) legacy_introspection_data = NULL; + PpdBusOwnData *own_data; + + iface_data = g_resources_lookup_data (POWER_PROFILES_RESOURCES_PATH "/" + POWER_PROFILES_DBUS_NAME ".xml", + G_RESOURCE_LOOKUP_FLAGS_NONE, + error); + if (!iface_data) + return FALSE; + + legacy_iface_data = g_resources_lookup_data (POWER_PROFILES_RESOURCES_PATH "/" + POWER_PROFILES_LEGACY_DBUS_NAME ".xml", + G_RESOURCE_LOOKUP_FLAGS_NONE, + error); + if (!legacy_iface_data) + return FALSE; + + introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (iface_data, NULL), + error); + if (!introspection_data) + return FALSE; - bytes = g_resources_lookup_data ("/net/hadess/PowerProfiles/net.hadess.PowerProfiles.xml", - G_RESOURCE_LOOKUP_FLAGS_NONE, - NULL); - data->introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (bytes, NULL), NULL); - g_bytes_unref (bytes); - g_assert (data->introspection_data != NULL); + legacy_introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (legacy_iface_data, NULL), + error); + if (!legacy_introspection_data) + return FALSE; + + own_data = g_new0 (PpdBusOwnData, 1); + own_data->app = data; + own_data->interface = g_dbus_interface_info_ref (introspection_data->interfaces[0]); + own_data->legacy_interface = g_dbus_interface_info_ref (legacy_introspection_data->interfaces[0]); - flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + own_data->flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; if (replace) - flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + own_data->flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; data->name_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, POWER_PROFILES_DBUS_NAME, - flags, + own_data->flags, bus_acquired_handler, name_acquired_handler, name_lost_handler, - data, - NULL); + own_data, + (GDestroyNotify) ppd_bus_own_data_free); return TRUE; } @@ -963,62 +1553,207 @@ if (data == NULL) return; - if (data->name_id != 0) { - g_bus_unown_name (data->name_id); - data->name_id = 0; - } + stop_profile_drivers (data); + + g_clear_handle_id (&data->name_id, g_bus_unown_name); + g_clear_handle_id (&data->legacy_name_id, g_bus_unown_name); + g_strfreev (data->blocked_drivers); + g_strfreev (data->blocked_actions); g_clear_pointer (&data->config_path, g_free); g_clear_pointer (&data->config, g_key_file_unref); - g_ptr_array_free (data->probed_drivers, TRUE); - g_ptr_array_free (data->actions, TRUE); - g_clear_object (&data->driver); + g_clear_pointer (&data->probed_drivers, g_ptr_array_unref); + g_clear_pointer (&data->actions, g_ptr_array_unref); + g_clear_object (&data->cpu_driver); + g_clear_object (&data->platform_driver); g_hash_table_destroy (data->profile_holds); g_clear_object (&data->auth); 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_free (data); ppd_app = NULL; } +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PpdApp, free_app_data) + void main_loop_quit (void) { g_main_loop_quit (ppd_app->main_loop); } +static inline gboolean +use_colored_ouput (void) +{ + if (g_getenv ("NO_COLOR")) + return FALSE; + return isatty (fileno (stdout)); +} + +static void +debug_handler_cb (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + DebugOptions *data = user_data; + g_autoptr(GString) domain = NULL; + gboolean use_color; + gint color; + + /* not in verbose mode */ + if (log_level > data->log_level) + return; + + domain = g_string_new (log_domain); + use_color = use_colored_ouput (); + + for (gsize i = domain->len; i < 15; i++) + g_string_append_c (domain, ' '); + g_print ("%s", domain->str); + + switch (log_level) { + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: + case G_LOG_LEVEL_WARNING: + color = 31; /* red */ + break; + default: + color = 34; /* blue */ + break; + } + + if (use_color) + g_print ("%c[%dm%s\n%c[%dm", 0x1B, color, message, 0x1B, 0); + else + g_print ("%s\n", message); +} + +static gboolean +quit_signal_callback (gpointer user_data) +{ + PpdApp *data = user_data; + + g_main_loop_quit (data->main_loop); + return FALSE; +} + +static gboolean +verbose_arg_cb (const gchar *option_name, + const gchar *value, + gpointer user_data, + GError **error) +{ + DebugOptions *data = user_data; + + if (data->log_level == G_LOG_LEVEL_MESSAGE) { + data->log_level = G_LOG_LEVEL_INFO; + return TRUE; + } + if (data->log_level == G_LOG_LEVEL_INFO) { + data->log_level = G_LOG_LEVEL_DEBUG; + return TRUE; + } + g_set_error_literal (error, + G_OPTION_ERROR, + G_OPTION_ERROR_FAILED, + "No further debug level supported"); + return FALSE; +} + +static gboolean +debug_pre_parse_hook (GOptionContext *context, + GOptionGroup *group, + gpointer user_data, + GError **error) +{ + DebugOptions *data = user_data; + + const GOptionEntry options[] = { + { + "verbose", + 'v', + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (GOptionArgFunc)verbose_arg_cb, + "Show extra debugging information", + NULL, + }, + { + "replace", + 'r', + G_OPTION_FLAG_NONE, + G_OPTION_ARG_NONE, + &data->replace, + "Replace the running instance of power-profiles-daemon", + NULL, + }, + { + "block-driver", + 0, + G_OPTION_FLAG_NONE, + G_OPTION_ARG_STRING_ARRAY, + &data->blocked_drivers, + "Block driver(s) from loading", + NULL, + }, + { + "block-action", + 0, + G_OPTION_FLAG_NONE, + G_OPTION_ARG_STRING_ARRAY, + &data->blocked_actions, + "Block action(s) from loading", + NULL, + }, + { NULL } + }; + g_option_group_add_entries (group, options); + + return TRUE; +} + +static gboolean +debug_post_parse_hook (GOptionContext *context, + GOptionGroup *group, + gpointer user_data, + GError **error) +{ + DebugOptions *debug = (DebugOptions *)user_data; + + g_log_set_default_handler (debug_handler_cb, debug); + + return TRUE; +} + int main (int argc, char **argv) { - PpdApp *data; - int ret = 0; + g_autoptr(DebugOptions) debug_options = g_new0 (DebugOptions, 1); + g_autoptr(PpdApp) data = NULL; g_autoptr(GOptionContext) option_context = NULL; g_autoptr(GError) error = NULL; - gboolean verbose = FALSE; - gboolean replace = FALSE; - const GOptionEntry options[] = { - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Show extra debugging information", NULL }, - { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace the running instance of power-profiles-daemon", NULL }, - { NULL} - }; + + debug_options->log_level = G_LOG_LEVEL_MESSAGE; + debug_options->group = g_option_group_new ("debug", + "Debugging Options", + "Show debugging options", + debug_options, + NULL); + g_option_group_set_parse_hooks (debug_options->group, + debug_pre_parse_hook, + debug_post_parse_hook); setlocale (LC_ALL, ""); option_context = g_option_context_new (""); - g_option_context_add_main_entries (option_context, options, NULL); + g_option_context_add_group (option_context, debug_options->group); - ret = g_option_context_parse (option_context, &argc, &argv, &error); - if (!ret) { + if (!g_option_context_parse (option_context, &argc, &argv, &error)) { g_print ("Failed to parse arguments: %s\n", error->message); return EXIT_FAILURE; } - if (verbose) - g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); - - g_debug ("Starting power-profiles-daemon version "VERSION); - data = g_new0 (PpdApp, 1); data->main_loop = g_main_loop_new (NULL, TRUE); data->auth = polkit_authority_get_sync (NULL, NULL); @@ -1027,15 +1762,24 @@ data->profile_holds = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) profile_hold_free); data->active_profile = PPD_PROFILE_BALANCED; data->selected_profile = PPD_PROFILE_BALANCED; + data->blocked_drivers = g_steal_pointer (&debug_options->blocked_drivers); + data->blocked_actions = g_steal_pointer (&debug_options->blocked_actions); + + g_unix_signal_add (SIGTERM, quit_signal_callback, data); + g_unix_signal_add (SIGINT, quit_signal_callback, data); + + g_info ("Starting power-profiles-daemon version "VERSION); + load_configuration (data); ppd_app = data; /* Set up D-Bus */ - setup_dbus (data, replace); + if (!setup_dbus (data, debug_options->replace, &error)) { + g_error ("Failed to start dbus: %s", error->message); + return EXIT_FAILURE; + } g_main_loop_run (data->main_loop); - ret = data->ret; - free_app_data (data); - return ret; + return data->ret; } diff -Nru power-profiles-daemon-0.13/src/power-profiles-daemon.dbus.xml.in power-profiles-daemon-0.21/src/power-profiles-daemon.dbus.xml.in --- power-profiles-daemon-0.13/src/power-profiles-daemon.dbus.xml.in 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/power-profiles-daemon.dbus.xml.in 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru power-profiles-daemon-0.13/src/power-profiles-daemon.gresource.xml power-profiles-daemon-0.21/src/power-profiles-daemon.gresource.xml --- power-profiles-daemon-0.13/src/power-profiles-daemon.gresource.xml 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/power-profiles-daemon.gresource.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - - - - net.hadess.PowerProfiles.xml - - - diff -Nru power-profiles-daemon-0.13/src/power-profiles-daemon.gresource.xml.in power-profiles-daemon-0.21/src/power-profiles-daemon.gresource.xml.in --- power-profiles-daemon-0.13/src/power-profiles-daemon.gresource.xml.in 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/power-profiles-daemon.gresource.xml.in 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,6 @@ + + + + @contents@ + + diff -Nru power-profiles-daemon-0.13/src/powerprofilesctl power-profiles-daemon-0.21/src/powerprofilesctl --- power-profiles-daemon-0.13/src/powerprofilesctl 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/powerprofilesctl 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 + +import argparse +import os +import signal +import subprocess +import sys +from gi.repository import Gio, GLib + +PP_NAME = "org.freedesktop.UPower.PowerProfiles" +PP_PATH = "/org/freedesktop/UPower/PowerProfiles" +PP_IFACE = "org.freedesktop.UPower.PowerProfiles" +PROPERTIES_IFACE = "org.freedesktop.DBus.Properties" + + +def get_proxy(): + bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + return Gio.DBusProxy.new_sync( + bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PROPERTIES_IFACE, None + ) + + +def command(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except GLib.Error as error: + sys.stderr.write( + f"Failed to communicate with power-profiles-daemon: {error}\n" + ) + sys.exit(1) + except ValueError as error: + sys.stderr.write(f"Error: {error}\n") + sys.exit(1) + + return wrapper + + +@command +def _version(_args): + client_version = "@VERSION@" + try: + proxy = get_proxy() + daemon_ver = proxy.Get("(ss)", PP_IFACE, "Version") + except GLib.Error: + daemon_ver = "unknown" + print(f"client: {client_version}\ndaemon: {daemon_ver}") + + +@command +def _get(_args): + proxy = get_proxy() + profile = proxy.Get("(ss)", PP_IFACE, "ActiveProfile") + print(profile) + + +@command +def _set(args): + proxy = get_proxy() + proxy.Set( + "(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0]) + ) + + +def get_profiles_property(prop): + proxy = get_proxy() + return proxy.Get("(ss)", PP_IFACE, prop) + + +@command +def _list(_args): + profiles = get_profiles_property("Profiles") + reason = get_proxy().Get("(ss)", PP_IFACE, "PerformanceDegraded") + degraded = reason != "" + active = get_proxy().Get("(ss)", PP_IFACE, "ActiveProfile") + + index = 0 + for profile in reversed(profiles): + if index > 0: + print("") + marker = "*" if profile["Profile"] == active else " " + print(f'{marker} {profile["Profile"]}:') + for driver in ["CpuDriver", "PlatformDriver"]: + if driver not in profile: + continue + value = profile[driver] + print(f" {driver}:\t{value}") + if profile["Profile"] == "performance": + print(" Degraded: ", f"yes ({reason})" if degraded else "no") + index += 1 + + +@command +def _list_holds(_args): + holds = get_profiles_property("ActiveProfileHolds") + + index = 0 + for hold in holds: + if index > 0: + print("") + print("Hold:") + print(" Profile: ", hold["Profile"]) + print(" Application ID: ", hold["ApplicationId"]) + print(" Reason: ", hold["Reason"]) + index += 1 + + +@command +def _launch(args): + reason = args.reason + profile = args.profile + appid = args.appid + if not args.arguments: + raise ValueError("No command to launch") + if not args.appid: + appid = args.arguments[0] + if not profile: + profile = "performance" + if not reason: + reason = f"Running {args.appid}" + ret = 0 + bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + proxy = Gio.DBusProxy.new_sync( + bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PP_IFACE, None + ) + cookie = proxy.HoldProfile("(sss)", profile, reason, appid) + + # print (f'Got {cookie} for {profile} hold') + with subprocess.Popen(args.arguments) as launched_app: + # Redirect the same signal to the child + def receive_signal(signum, _stack): + launched_app.send_signal(signum) + + redirected_signals = [ + signal.SIGTERM, + signal.SIGINT, + signal.SIGABRT, + ] + + for sig in redirected_signals: + signal.signal(sig, receive_signal) + + try: + launched_app.wait() + ret = launched_app.returncode + except KeyboardInterrupt: + ret = launched_app.returncode + + for sig in redirected_signals: + signal.signal(sig, signal.SIG_DFL) + + proxy.ReleaseProfile("(u)", cookie) + + if ret < 0: + # Use standard POSIX signal exit code. + os.kill(os.getpid(), -ret) + return + + sys.exit(ret) + + +def get_parser(): + parser = argparse.ArgumentParser( + epilog="Use “powerprofilesctl COMMAND --help” to get detailed help for individual commands", + ) + subparsers = parser.add_subparsers(help="Individual command help", dest="command") + parser_list = subparsers.add_parser("list", help="List available power profiles") + parser_list.set_defaults(func=_list) + parser_list_holds = subparsers.add_parser( + "list-holds", help="List current power profile holds" + ) + parser_list_holds.set_defaults(func=_list_holds) + parser_get = subparsers.add_parser( + "get", help="Print the currently active power profile" + ) + parser_get.set_defaults(func=_get) + parser_set = subparsers.add_parser( + "set", help="Set the currently active power profile" + ) + parser_set.add_argument( + "profile", + nargs=1, + help="Profile to use for set command", + ) + parser_set.set_defaults(func=_set) + parser_launch = subparsers.add_parser( + "launch", + help="Launch a command while holding a power profile", + description="Launch the command while holding a power profile," + "either performance, or power-saver. By default, the profile hold " + "is for the performance profile, but it might not be available on " + "all systems. See the list command for a list of available profiles.", + ) + parser_launch.add_argument( + "arguments", + nargs="*", + help="Command to launch", + ) + parser_launch.add_argument( + "--profile", "-p", required=False, help="Profile to use for launch command" + ) + parser_launch.add_argument( + "--reason", "-r", required=False, help="Reason to use for launch command" + ) + parser_launch.add_argument( + "--appid", "-i", required=False, help="AppId to use for launch command" + ) + parser_launch.set_defaults(func=_launch) + parser_version = subparsers.add_parser( + "version", help="Print version information and exit" + ) + parser_version.set_defaults(func=_version) + + if not os.getenv("PPD_COMPLETIONS_GENERATION"): + return parser + + try: + import shtab # pylint: disable=import-outside-toplevel + + shtab.add_argument_to(parser, ["--print-completion"]) # magic! + except ImportError: + pass + + return parser + + +def check_unknown_args(args, unknown_args, cmd): + if cmd != "launch": + return False + + for idx, unknown_arg in enumerate(unknown_args): + arg = args[idx] + if arg == cmd: + return True + if unknown_arg == arg: + return False + + return True + + +def main(): + parser = get_parser() + args, unknown = parser.parse_known_args() + # default behavior is to run list if no command is given + if not args.command: + args.func = _list + + if check_unknown_args(sys.argv[1:], unknown, args.command): + args.arguments += unknown + unknown = [] + + if unknown: + msg = argparse._("unrecognized arguments: %s") + parser.error(msg % " ".join(unknown)) + + args.func(args) + + +if __name__ == "__main__": + main() diff -Nru power-profiles-daemon-0.13/src/powerprofilesctl.in power-profiles-daemon-0.21/src/powerprofilesctl.in --- power-profiles-daemon-0.13/src/powerprofilesctl.in 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/powerprofilesctl.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,282 +0,0 @@ -#!@PYTHON3@ - -import signal -import subprocess -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(' list-holds List current power profile holds') - print(' launch Launch a command while holding a power profile') - 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_list_holds(): - print('Usage:') - print(' powerprofilesctl list-holds') - print('') - print('List current power profile holds.') - -def usage_launch(): - print('Usage:') - print(' powerprofilesctl launch [COMMAND…]') - print('') - print('Launch a command while holding a power profile.') - print('') - print('Options:') - print(' -p, --profile=PROFILE The power profile to hold') - print(' -r, --reason=REASON The reason for the profile hold') - print(' -i, --appid=APP-ID The application ID for the profile hold') - print('') - print('Launch the command while holding a power profile, either performance, ') - print('or power-saver. By default, the profile hold is for the performance ') - print('profile, but it might not be available on all systems. See the list ') - print('command for a list of available 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 == 'list-holds': - usage_list_holds() - elif _command == 'launch': - usage_launch() - 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 - return proxy - -def _get(): - proxy = get_proxy() - profile = proxy.Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile') - print(profile) - -def _set(profile): - try: - proxy = get_proxy() - proxy.Set('(ssv)', - 'net.hadess.PowerProfiles', - 'ActiveProfile', - GLib.Variant.new_string(profile)) - except: - raise - -def get_profiles_property(prop): - try: - proxy = get_proxy() - except: - raise - - profiles = None - try: - profiles = proxy.Get('(ss)', 'net.hadess.PowerProfiles', prop) - except: - raise - return profiles - -def _list(): - try: - profiles = get_profiles_property('Profiles') - reason = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'PerformanceDegraded') - degraded = reason != '' - active = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile') - except: - raise - - index = 0 - for profile in reversed(profiles): - if index > 0: - print('') - marker = '*' if profile['Profile'] == active else ' ' - print(f'{marker} {profile["Profile"]}:') - print(' Driver: ', profile['Driver']) - if profile['Profile'] == 'performance': - print(' Degraded: ', f'yes ({reason})' if degraded else 'no') - index += 1 - -def _list_holds(): - try: - holds = get_profiles_property('ActiveProfileHolds') - except: - raise - - index = 0 - for hold in holds: - if index > 0: - print('') - print('Hold:') - print(' Profile: ', hold['Profile']) - print(' Application ID: ', hold['ApplicationId']) - print(' Reason: ', hold['Reason']) - index += 1 - -def _launch(args, profile, appid, reason): - 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', - 'net.hadess.PowerProfiles', None) - except: - raise - - cookie = proxy.HoldProfile('(sss)', profile, reason, appid) - - # Kill child when we go away - def receive_signal(_signum, _stack): - launched_app.terminate() - signal.signal(signal.SIGTERM, receive_signal) - - # print (f'Got {cookie} for {profile} hold') - with subprocess.Popen(args) as launched_app: - launched_app.wait() - proxy.ReleaseProfile('(u)', cookie) - -def main(): # pylint: disable=too-many-branches, disable=too-many-statements - 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': - try: - _get() - except GLib.Error as error: - sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n') - sys.exit(1) - elif command == 'set': - if len(args) != 1: - usage_set() - sys.exit(1) - try: - _set(args[0]) - except GLib.Error as error: - sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n') - sys.exit(1) - elif command == 'list': - try: - _list() - except GLib.Error as error: - sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n') - sys.exit(1) - elif command == 'list-holds': - try: - _list_holds() - except GLib.Error as error: - sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n') - sys.exit(1) - elif command == 'launch': - if len(args) == 0: - sys.exit(0) - profile = None - reason = None - appid = None - while True: - if args[0] == '--': - args = args[1:] - break - if args[0][:9] == '--profile' or args[0] == '-p': - if args[0][:10] == '--profile=': - args = args[0].split('=') + args[1:] - profile = args[1] - args = args[2:] - continue - if args[0][:8] == '--reason' or args[0] == '-r': - if args[0][:9] == '--reason=': - args = args[0].split('=') + args[1:] - reason = args[1] - args = args[2:] - continue - if args[0][:7] == '--appid' or args[0] == '-i': - if args[0][:8] == '--appid=': - args = args[0].split('=') + args[1:] - appid = args[1] - args = args[2:] - continue - break - - if len(args) < 1: - sys.exit(0) - if not appid: - appid = args[0] - if not reason: - reason = 'Running ' + appid - if not profile: - profile = 'performance' - try: - _launch(args, profile, appid, reason) - except GLib.Error as error: - sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n') - sys.exit(1) - -if __name__ == '__main__': - main() diff -Nru power-profiles-daemon-0.13/src/ppd-action-amdgpu-panel-power.c power-profiles-daemon-0.21/src/ppd-action-amdgpu-panel-power.c --- power-profiles-daemon-0.13/src/ppd-action-amdgpu-panel-power.c 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action-amdgpu-panel-power.c 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2024 Advanced Micro Devices + * + * 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. + * + */ + +#define G_LOG_DOMAIN "AmdgpuAction" + +#include "config.h" + +#include + +#include "ppd-action-amdgpu-panel-power.h" +#include "ppd-profile.h" +#include "ppd-utils.h" + +#define PROC_CPUINFO_PATH "/proc/cpuinfo" + +#define PANEL_POWER_SYSFS_NAME "amdgpu/panel_power_savings" +#define PANEL_STATUS_SYSFS_NAME "status" + +#define UPOWER_DBUS_NAME "org.freedesktop.UPower" +#define UPOWER_DBUS_PATH "/org/freedesktop/UPower" +#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower" + +/** + * SECTION:ppd-action-amdgpu-panel-power + * @Short_description: Power savings for eDP connected displays + * @Title: AMDGPU Panel power action + * + * The AMDGPU panel power action utilizes the sysfs attribute present on some DRM + * connectors for amdgpu called "panel_power_savings". This will use an AMD specific + * hardware feature for a power savings profile for the panel. + * + */ + +struct _PpdActionAmdgpuPanelPower +{ + PpdAction parent_instance; + PpdProfile last_profile; + + GUdevClient *client; + + gint panel_power_saving; + gboolean valid_battery; + gboolean on_battery; +}; + +G_DEFINE_TYPE (PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD_TYPE_ACTION) + +static GObject* +ppd_action_amdgpu_panel_power_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->constructor (type, + n_construct_params, + construct_params); + g_object_set (object, + "action-name", "amdgpu_panel_power", + NULL); + + return object; +} + +static gboolean +panel_connected (GUdevDevice *device) +{ + const char *value; + g_autofree gchar *stripped = NULL; + + value = g_udev_device_get_sysfs_attr_uncached (device, PANEL_STATUS_SYSFS_NAME); + if (!value) + return FALSE; + stripped = g_strchomp (g_strdup (value)); + + return g_strcmp0 (stripped, "connected") == 0; +} + +static gboolean +set_panel_power (PpdActionAmdgpuPanelPower *self, gint power, GError **error) +{ + GList *devices, *l; + + devices = g_udev_client_query_by_subsystem (self->client, "drm"); + if (devices == NULL) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + "no drm devices found"); + return FALSE; + } + + for (l = devices; l != NULL; l = l->next) { + GUdevDevice *dev = l->data; + const char *value; + guint64 parsed; + + value = g_udev_device_get_devtype (dev); + if (g_strcmp0 (value, "drm_connector") != 0) + continue; + + if (!panel_connected (dev)) + continue; + + value = g_udev_device_get_sysfs_attr_uncached (dev, PANEL_POWER_SYSFS_NAME); + if (!value) + continue; + + parsed = g_ascii_strtoull (value, NULL, 10); + + /* overflow check */ + if (parsed == G_MAXUINT64) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "cannot parse %s as caused overflow", + value); + return FALSE; + } + + if (parsed == power) + continue; + + if (!ppd_utils_write_sysfs_int (dev, PANEL_POWER_SYSFS_NAME, power, error)) + return FALSE; + + break; + } + + g_list_free_full (devices, g_object_unref); + + return TRUE; +} + +static gboolean +ppd_action_amdgpu_panel_update_target (PpdActionAmdgpuPanelPower *self, + GError **error) +{ + gint target = 0; + + /* only activate if we know that we're on battery */ + if (self->on_battery) { + switch (self->last_profile) { + case PPD_PROFILE_POWER_SAVER: + target = 3; + break; + case PPD_PROFILE_BALANCED: + target = 1; + break; + case PPD_PROFILE_PERFORMANCE: + target = 0; + break; + } + } + + if (!set_panel_power (self, target, error)) + return FALSE; + self->panel_power_saving = target; + + return TRUE; +} + +static gboolean +ppd_action_amdgpu_panel_power_activate_profile (PpdAction *action, + PpdProfile profile, + GError **error) +{ + PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action); + self->last_profile = profile; + + if (!self->valid_battery) { + g_debug ("upower not available; battery data might be stale"); + return TRUE; + } + + return ppd_action_amdgpu_panel_update_target (self, error); +} + +static gboolean +ppd_action_amdgpu_panel_power_power_changed (PpdAction *action, + PpdPowerChangedReason reason, + GError **error) +{ + PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action); + + switch (reason) { + case PPD_POWER_CHANGED_REASON_UNKNOWN: + self->valid_battery = FALSE; + return TRUE; + case PPD_POWER_CHANGED_REASON_AC: + self->on_battery = FALSE; + break; + case PPD_POWER_CHANGED_REASON_BATTERY: + self->on_battery = TRUE; + break; + default: + g_assert_not_reached (); + } + + self->valid_battery = TRUE; + return ppd_action_amdgpu_panel_update_target (self, error); +} + +static void +udev_uevent_cb (GUdevClient *client, + gchar *action, + GUdevDevice *device, + gpointer user_data) +{ + PpdActionAmdgpuPanelPower *self = user_data; + + if (!g_str_equal (action, "add")) + return; + + if (!g_udev_device_has_sysfs_attr (device, PANEL_POWER_SYSFS_NAME)) + return; + + if (!panel_connected (device)) + return; + + g_debug ("Updating panel power saving for '%s' to '%d'", + g_udev_device_get_sysfs_path (device), + self->panel_power_saving); + ppd_utils_write_sysfs_int (device, PANEL_POWER_SYSFS_NAME, + self->panel_power_saving, NULL); +} + +static PpdProbeResult +ppd_action_amdgpu_panel_power_probe (PpdAction *action) +{ + g_autofree gchar *cpuinfo_path = NULL; + g_autofree gchar *cpuinfo = NULL; + g_auto(GStrv) lines = NULL; + + cpuinfo_path = ppd_utils_get_sysfs_path (PROC_CPUINFO_PATH); + if (!g_file_get_contents (cpuinfo_path, &cpuinfo, NULL, NULL)) + return PPD_PROBE_RESULT_FAIL; + + lines = g_strsplit (cpuinfo, "\n", -1); + + for (gchar **line = lines; *line != NULL; line++) { + if (g_str_has_prefix (*line, "vendor_id") && + strchr (*line, ':')) { + g_auto(GStrv) sections = g_strsplit (*line, ":", 2); + + if (g_strv_length (sections) < 2) + continue; + if (g_strcmp0 (g_strstrip (sections[1]), "AuthenticAMD") == 0) + return PPD_PROBE_RESULT_SUCCESS; + } + } + + + return PPD_PROBE_RESULT_FAIL; +} + +static void +ppd_action_amdgpu_panel_power_finalize (GObject *object) +{ + PpdActionAmdgpuPanelPower *action; + + action = PPD_ACTION_AMDGPU_PANEL_POWER (object); + g_clear_object (&action->client); + G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->finalize (object); +} + +static void +ppd_action_amdgpu_panel_power_class_init (PpdActionAmdgpuPanelPowerClass *klass) +{ + GObjectClass *object_class; + PpdActionClass *driver_class; + + object_class = G_OBJECT_CLASS(klass); + object_class->constructor = ppd_action_amdgpu_panel_power_constructor; + object_class->finalize = ppd_action_amdgpu_panel_power_finalize; + + driver_class = PPD_ACTION_CLASS(klass); + driver_class->probe = ppd_action_amdgpu_panel_power_probe; + driver_class->activate_profile = ppd_action_amdgpu_panel_power_activate_profile; + driver_class->power_changed = ppd_action_amdgpu_panel_power_power_changed; +} + +static void +ppd_action_amdgpu_panel_power_init (PpdActionAmdgpuPanelPower *self) +{ + const gchar * const subsystem[] = { "drm", NULL }; + + self->client = g_udev_client_new (subsystem); + g_signal_connect_object (G_OBJECT (self->client), "uevent", + G_CALLBACK (udev_uevent_cb), self, 0); +} diff -Nru power-profiles-daemon-0.13/src/ppd-action-amdgpu-panel-power.h power-profiles-daemon-0.21/src/ppd-action-amdgpu-panel-power.h --- power-profiles-daemon-0.13/src/ppd-action-amdgpu-panel-power.h 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action-amdgpu-panel-power.h 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Advanced Micro Devices + * + * 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-action.h" + +#define PPD_TYPE_ACTION_AMDGPU_PANEL_POWER (ppd_action_amdgpu_panel_power_get_type()) +G_DECLARE_FINAL_TYPE(PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD, ACTION_AMDGPU_PANEL_POWER, PpdAction) diff -Nru power-profiles-daemon-0.13/src/ppd-action-trickle-charge.c power-profiles-daemon-0.21/src/ppd-action-trickle-charge.c --- power-profiles-daemon-0.13/src/ppd-action-trickle-charge.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action-trickle-charge.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,6 +7,8 @@ * */ +#define G_LOG_DOMAIN "TrickleChargeAction" + #include #include "ppd-action-trickle-charge.h" @@ -130,11 +132,11 @@ GObjectClass *object_class; PpdActionClass *driver_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->constructor = ppd_action_trickle_charge_constructor; object_class->finalize = ppd_action_trickle_charge_finalize; - driver_class = PPD_ACTION_CLASS(klass); + driver_class = PPD_ACTION_CLASS (klass); driver_class->activate_profile = ppd_action_trickle_charge_activate_profile; } diff -Nru power-profiles-daemon-0.13/src/ppd-action-trickle-charge.h power-profiles-daemon-0.21/src/ppd-action-trickle-charge.h --- power-profiles-daemon-0.13/src/ppd-action-trickle-charge.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action-trickle-charge.h 2024-04-03 23:55:02.000000000 +0000 @@ -11,5 +11,5 @@ #include "ppd-action.h" -#define PPD_TYPE_ACTION_TRICKLE_CHARGE (ppd_action_trickle_charge_get_type()) -G_DECLARE_FINAL_TYPE(PpdActionTrickleCharge, ppd_action_trickle_charge, PPD, ACTION_TRICKLE_CHARGE, PpdAction) +#define PPD_TYPE_ACTION_TRICKLE_CHARGE (ppd_action_trickle_charge_get_type ()) +G_DECLARE_FINAL_TYPE (PpdActionTrickleCharge, ppd_action_trickle_charge, PPD, ACTION_TRICKLE_CHARGE, PpdAction) diff -Nru power-profiles-daemon-0.13/src/ppd-action.c power-profiles-daemon-0.21/src/ppd-action.c --- power-profiles-daemon-0.13/src/ppd-action.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,6 +7,8 @@ * */ +#define G_LOG_DOMAIN "Action" + #include "ppd-action.h" #include "ppd-enums.h" @@ -58,11 +60,11 @@ switch (property_id) { case PROP_ACTION_NAME: - g_assert (priv->action_name == NULL); + g_return_if_fail (priv->action_name == NULL); priv->action_name = g_value_dup_string (value); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } @@ -80,7 +82,7 @@ g_value_set_string (value, priv->action_name); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } @@ -100,7 +102,7 @@ { GObjectClass *object_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->finalize = ppd_action_finalize; object_class->get_property = ppd_action_get_property; object_class->set_property = ppd_action_set_property; @@ -111,7 +113,7 @@ * A unique action name, only used for debugging. */ g_object_class_install_property (object_class, PROP_ACTION_NAME, - g_param_spec_string("action-name", + g_param_spec_string ("action-name", "Action name", "Action name", NULL, @@ -123,13 +125,13 @@ { } -gboolean +PpdProbeResult ppd_action_probe (PpdAction *action) { g_return_val_if_fail (PPD_IS_ACTION (action), FALSE); if (!PPD_ACTION_GET_CLASS (action)->probe) - return TRUE; + return PPD_PROBE_RESULT_SUCCESS; return PPD_ACTION_GET_CLASS (action)->probe (action); } @@ -147,6 +149,18 @@ return PPD_ACTION_GET_CLASS (action)->activate_profile (action, profile, error); } +gboolean ppd_action_power_changed (PpdAction *action, + PpdPowerChangedReason reason, + GError **error) +{ + g_return_val_if_fail (PPD_IS_ACTION (action), FALSE); + + if (!PPD_ACTION_GET_CLASS (action)->power_changed) + return TRUE; + + return PPD_ACTION_GET_CLASS (action)->power_changed (action, reason, error); +} + const char * ppd_action_get_action_name (PpdAction *action) { diff -Nru power-profiles-daemon-0.13/src/ppd-action.h power-profiles-daemon-0.21/src/ppd-action.h --- power-profiles-daemon-0.13/src/ppd-action.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-action.h 2024-04-03 23:55:02.000000000 +0000 @@ -12,8 +12,8 @@ #include #include "ppd-profile.h" -#define PPD_TYPE_ACTION (ppd_action_get_type()) -G_DECLARE_DERIVABLE_TYPE(PpdAction, ppd_action, PPD, ACTION, GObject) +#define PPD_TYPE_ACTION (ppd_action_get_type ()) +G_DECLARE_DERIVABLE_TYPE (PpdAction, ppd_action, PPD, ACTION, GObject) /** * PpdActionClass: @@ -28,14 +28,19 @@ { GObjectClass parent_class; - gboolean (* probe) (PpdAction *action); - gboolean (* activate_profile) (PpdAction *action, - PpdProfile profile, - GError **error); + PpdProbeResult (* probe) (PpdAction *action); + gboolean (* activate_profile) (PpdAction *action, + PpdProfile profile, + GError **error); + gboolean (* power_changed) (PpdAction *action, + PpdPowerChangedReason reason, + GError **error); + }; #ifndef __GTK_DOC_IGNORE__ -gboolean ppd_action_probe (PpdAction *action); +PpdProbeResult ppd_action_probe (PpdAction *action); gboolean ppd_action_activate_profile (PpdAction *action, PpdProfile profile, GError **error); +gboolean ppd_action_power_changed (PpdAction *action, PpdPowerChangedReason reason, GError **error); const char *ppd_action_get_action_name (PpdAction *action); #endif diff -Nru power-profiles-daemon-0.13/src/ppd-driver-amd-pstate.c power-profiles-daemon-0.21/src/ppd-driver-amd-pstate.c --- power-profiles-daemon-0.13/src/ppd-driver-amd-pstate.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-amd-pstate.c 2024-04-03 23:55:02.000000000 +0000 @@ -8,24 +8,40 @@ * */ +#define G_LOG_DOMAIN "CpuDriver" + #include #include "ppd-utils.h" #include "ppd-driver-amd-pstate.h" #define CPUFREQ_POLICY_DIR "/sys/devices/system/cpu/cpufreq/" -#define DEFAULT_CPU_FREQ_SCALING_GOV "powersave" #define PSTATE_STATUS_PATH "/sys/devices/system/cpu/amd_pstate/status" +#define ACPI_PM_PROFILE "/sys/firmware/acpi/pm_profile" + +enum acpi_preferred_pm_profiles { + PM_UNSPECIFIED = 0, + PM_DESKTOP = 1, + PM_MOBILE = 2, + PM_WORKSTATION = 3, + PM_ENTERPRISE_SERVER = 4, + PM_SOHO_SERVER = 5, + PM_APPLIANCE_PC = 6, + PM_PERFORMANCE_SERVER = 7, + PM_TABLET = 8, + NR_PM_PROFILES = 9 +}; struct _PpdDriverAmdPstate { - PpdDriver parent_instance; + PpdDriverCpu parent_instance; PpdProfile activated_profile; - GList *epp_devices; /* GList of paths */ + GPtrArray *epp_devices; /* Array of paths */ + gboolean on_battery; }; -G_DEFINE_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD_TYPE_DRIVER) +G_DEFINE_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD_TYPE_DRIVER_CPU) static gboolean ppd_driver_amd_pstate_activate_profile (PpdDriver *driver, PpdProfile profile, @@ -57,74 +73,101 @@ g_autofree char *policy_dir = NULL; g_autofree char *pstate_status_path = NULL; g_autofree char *status = NULL; + g_autofree char *pm_profile_path = NULL; + g_autofree char *pm_profile_str = NULL; + guint64 pm_profile; const char *dirname; - PpdProbeResult ret = PPD_PROBE_RESULT_FAIL; /* Verify that AMD P-State is running in active mode */ pstate_status_path = ppd_utils_get_sysfs_path (PSTATE_STATUS_PATH); if (!g_file_get_contents (pstate_status_path, &status, NULL, NULL)) - return ret; + return PPD_PROBE_RESULT_FAIL; status = g_strchomp (status); if (g_strcmp0 (status, "active") != 0) { g_debug ("AMD P-State is not running in active mode"); - return ret; + return PPD_PROBE_RESULT_FAIL; } policy_dir = ppd_utils_get_sysfs_path (CPUFREQ_POLICY_DIR); dir = g_dir_open (policy_dir, 0, NULL); if (!dir) { g_debug ("Could not open %s", policy_dir); - return ret; + return PPD_PROBE_RESULT_FAIL; + } + + /* only run on things that we know aren't servers */ + pm_profile_path = ppd_utils_get_sysfs_path (ACPI_PM_PROFILE); + if (!g_file_get_contents (pm_profile_path, &pm_profile_str, NULL, NULL)) + return PPD_PROBE_RESULT_FAIL; + pm_profile = g_ascii_strtoull (pm_profile_str, NULL, 10); + switch (pm_profile) { + case PM_UNSPECIFIED: + case PM_ENTERPRISE_SERVER: + case PM_SOHO_SERVER: + case PM_PERFORMANCE_SERVER: + g_debug ("AMD-P-State not supported on PM profile %" G_GUINT64_FORMAT, pm_profile); + return PPD_PROBE_RESULT_FAIL; + default: + break; } while ((dirname = g_dir_read_name (dir)) != NULL) { + g_autofree char *base = NULL; g_autofree char *path = NULL; - g_autofree char *gov_path = NULL; g_autoptr(GError) error = NULL; - path = g_build_filename (policy_dir, + base = g_build_filename (policy_dir, dirname, + NULL); + + path = g_build_filename (base, "energy_performance_preference", NULL); if (!g_file_test (path, G_FILE_TEST_EXISTS)) continue; - /* Force a scaling_governor where the preference can be written */ - gov_path = g_build_filename (policy_dir, - dirname, - "scaling_governor", - NULL); - if (!ppd_utils_write (gov_path, DEFAULT_CPU_FREQ_SCALING_GOV, &error)) { - g_warning ("Could not change scaling governor %s to '%s'", dirname, DEFAULT_CPU_FREQ_SCALING_GOV); - continue; - } + if (!pstate->epp_devices) + pstate->epp_devices = g_ptr_array_new_with_free_func (g_free); - pstate->epp_devices = g_list_prepend (pstate->epp_devices, g_steal_pointer (&path)); - ret = PPD_PROBE_RESULT_SUCCESS; + g_ptr_array_add (pstate->epp_devices, g_steal_pointer (&base)); } - return ret; + if (pstate->epp_devices && pstate->epp_devices->len) + return PPD_PROBE_RESULT_SUCCESS; + + return PPD_PROBE_RESULT_FAIL; } static PpdProbeResult ppd_driver_amd_pstate_probe (PpdDriver *driver) { PpdDriverAmdPstate *pstate = PPD_DRIVER_AMD_PSTATE (driver); - PpdProbeResult ret = PPD_PROBE_RESULT_FAIL; + PpdProbeResult ret; ret = probe_epp (pstate); - if (ret != PPD_PROBE_RESULT_SUCCESS) - goto out; - -out: g_debug ("%s p-state settings", ret == PPD_PROBE_RESULT_SUCCESS ? "Found" : "Didn't find"); return ret; } static const char * -profile_to_epp_pref (PpdProfile profile) +profile_to_gov_pref (PpdProfile profile) +{ + switch (profile) { + case PPD_PROFILE_POWER_SAVER: + return "powersave"; + case PPD_PROFILE_BALANCED: + return "powersave"; + case PPD_PROFILE_PERFORMANCE: + return "performance"; + } + + g_return_val_if_reached (NULL); +} + +static const char * +profile_to_epp_pref (PpdProfile profile, gboolean battery) { /* Note that we don't check "energy_performance_available_preferences" * as all the values are always available */ @@ -132,50 +175,74 @@ case PPD_PROFILE_POWER_SAVER: return "power"; case PPD_PROFILE_BALANCED: - return "balance_performance"; + return battery ? "balance_power" : "balance_performance"; case PPD_PROFILE_PERFORMANCE: return "performance"; } - g_assert_not_reached (); + g_return_val_if_reached (NULL); } static gboolean -apply_pref_to_devices (GList *devices, - const char *pref, +apply_pref_to_devices (GPtrArray *devices, + PpdProfile profile, + gboolean battery, GError **error) { - gboolean ret = TRUE; - GList *l; + const char *epp_pref; + const char *gov_pref; + + if (profile == PPD_PROFILE_UNSET) + return TRUE; + + epp_pref = profile_to_epp_pref (profile, battery); + gov_pref = profile_to_gov_pref (profile); + + for (guint i = 0; i < devices->len; ++i) { + const char *base = g_ptr_array_index (devices, i); + g_autofree char *epp = NULL; + g_autofree char *gov = NULL; + + gov = g_build_filename (base, + "scaling_governor", + NULL); + + if (!ppd_utils_write (gov, gov_pref, error)) + return FALSE; - for (l = devices; l != NULL; l = l->next) { - const char *path = l->data; + epp = g_build_filename (base, + "energy_performance_preference", + NULL); - ret = ppd_utils_write (path, pref, error); - if (!ret) - break; + if (!ppd_utils_write (epp, epp_pref, error)) + return FALSE; } - return ret; + return TRUE; } static gboolean ppd_driver_amd_pstate_activate_profile (PpdDriver *driver, - PpdProfile profile, - PpdProfileActivationReason reason, - GError **error) + PpdProfile profile, + PpdProfileActivationReason reason, + GError **error) { PpdDriverAmdPstate *pstate = PPD_DRIVER_AMD_PSTATE (driver); gboolean ret = FALSE; - const char *pref; g_return_val_if_fail (pstate->epp_devices != NULL, FALSE); + g_return_val_if_fail (pstate->epp_devices->len != 0, FALSE); - if (pstate->epp_devices) { - pref = profile_to_epp_pref (profile); - ret = apply_pref_to_devices (pstate->epp_devices, pref, error); - if (!ret) - return ret; + ret = apply_pref_to_devices (pstate->epp_devices, profile, pstate->on_battery, error); + if (!ret && pstate->activated_profile != PPD_PROFILE_UNSET) { + g_autoptr(GError) error_local = NULL; + /* reset back to previous */ + if (!apply_pref_to_devices (pstate->epp_devices, + pstate->activated_profile, + pstate->on_battery, + &error_local)) + g_warning ("failed to restore previous profile: %s", error_local->message); + return ret; } if (ret) @@ -184,13 +251,38 @@ return ret; } +static gboolean +ppd_driver_amd_pstate_power_changed (PpdDriver *driver, + PpdPowerChangedReason reason, + GError **error) +{ + PpdDriverAmdPstate *pstate = PPD_DRIVER_AMD_PSTATE (driver); + + switch (reason) { + case PPD_POWER_CHANGED_REASON_UNKNOWN: + case PPD_POWER_CHANGED_REASON_AC: + pstate->on_battery = FALSE; + break; + case PPD_POWER_CHANGED_REASON_BATTERY: + pstate->on_battery = TRUE; + break; + default: + g_assert_not_reached (); + } + + return apply_pref_to_devices (pstate->epp_devices, + pstate->activated_profile, + pstate->on_battery, + error); +} + static void ppd_driver_amd_pstate_finalize (GObject *object) { PpdDriverAmdPstate *driver; driver = PPD_DRIVER_AMD_PSTATE (object); - g_clear_list (&driver->epp_devices, g_free); + g_clear_pointer (&driver->epp_devices, g_ptr_array_unref); G_OBJECT_CLASS (ppd_driver_amd_pstate_parent_class)->finalize (object); } @@ -200,13 +292,14 @@ GObjectClass *object_class; PpdDriverClass *driver_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->constructor = ppd_driver_amd_pstate_constructor; object_class->finalize = ppd_driver_amd_pstate_finalize; - driver_class = PPD_DRIVER_CLASS(klass); + driver_class = PPD_DRIVER_CLASS (klass); driver_class->probe = ppd_driver_amd_pstate_probe; driver_class->activate_profile = ppd_driver_amd_pstate_activate_profile; + driver_class->power_changed = ppd_driver_amd_pstate_power_changed; } static void diff -Nru power-profiles-daemon-0.13/src/ppd-driver-amd-pstate.h power-profiles-daemon-0.21/src/ppd-driver-amd-pstate.h --- power-profiles-daemon-0.13/src/ppd-driver-amd-pstate.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-amd-pstate.h 2024-04-03 23:55:02.000000000 +0000 @@ -10,7 +10,7 @@ #pragma once -#include "ppd-driver.h" +#include "ppd-driver-cpu.h" -#define PPD_TYPE_DRIVER_AMD_PSTATE (ppd_driver_amd_pstate_get_type()) -G_DECLARE_FINAL_TYPE(PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD, DRIVER_AMD_PSTATE, PpdDriver) +#define PPD_TYPE_DRIVER_AMD_PSTATE (ppd_driver_amd_pstate_get_type ()) +G_DECLARE_FINAL_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD, DRIVER_AMD_PSTATE, PpdDriverCpu) diff -Nru power-profiles-daemon-0.13/src/ppd-driver-cpu.c power-profiles-daemon-0.21/src/ppd-driver-cpu.c --- power-profiles-daemon-0.13/src/ppd-driver-cpu.c 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-cpu.c 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Mario Limonciello + * + * 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. + * + */ + +#define G_LOG_DOMAIN "CpuDriver" + +#include "ppd-driver-cpu.h" + +/** + * SECTION:ppd-driver-cpu + * @Short_description: CPU Drivers + * @Title: CPU Profile Drivers + * + * Profile drivers are the implementation of the different profiles for + * the whole system. A driver will need to implement support for `power-saver` + * and `balanced` at a minimum. + * + * CPU drivers are typically used to change specifically the CPU efficiency + * to match the desired platform state. + */ + +G_DEFINE_TYPE (PpdDriverCpu, ppd_driver_cpu, PPD_TYPE_DRIVER) + +static void +ppd_driver_cpu_finalize (GObject *object) +{ + G_OBJECT_CLASS (ppd_driver_cpu_parent_class)->finalize (object); +} + +static void +ppd_driver_cpu_class_init (PpdDriverCpuClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = ppd_driver_cpu_finalize; +} + +static void +ppd_driver_cpu_init (PpdDriverCpu *self) +{ +} diff -Nru power-profiles-daemon-0.13/src/ppd-driver-cpu.h power-profiles-daemon-0.21/src/ppd-driver-cpu.h --- power-profiles-daemon-0.13/src/ppd-driver-cpu.h 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-cpu.h 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Mario Limonciello + * + * 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_CPU (ppd_driver_cpu_get_type ()) +G_DECLARE_DERIVABLE_TYPE (PpdDriverCpu, ppd_driver_cpu, PPD, DRIVER_CPU, PpdDriver) + +/** + * PpdDriverCpuClass: + * @parent_class: The parent class. + * + * New CPU drivers should derive from #PpdDriverCpu and implement + * both @probe () and @activate_profile. + */ +struct _PpdDriverCpuClass +{ + PpdDriverClass parent_class; +}; diff -Nru power-profiles-daemon-0.13/src/ppd-driver-fake.c power-profiles-daemon-0.21/src/ppd-driver-fake.c --- power-profiles-daemon-0.13/src/ppd-driver-fake.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-fake.c 2024-04-03 23:55:02.000000000 +0000 @@ -14,11 +14,11 @@ #include extern void main_loop_quit (void); -void restart_profile_drivers (void); +void restart_profile_drivers_for_default_app (void); struct _PpdDriverFake { - PpdDriver parent_instance; + PpdDriverPlatform parent_instance; gboolean tio_set; struct termios old_tio; @@ -27,7 +27,7 @@ gboolean degraded; }; -G_DEFINE_TYPE (PpdDriverFake, ppd_driver_fake, PPD_TYPE_DRIVER) +G_DEFINE_TYPE (PpdDriverFake, ppd_driver_fake, PPD_TYPE_DRIVER_PLATFORM) static GObject* ppd_driver_fake_constructor (GType type, @@ -88,7 +88,7 @@ break; case 'r': g_print ("Restarting profile drivers\n"); - restart_profile_drivers (); + restart_profile_drivers_for_default_app (); break; case 'q': case 'x': @@ -107,10 +107,10 @@ { struct termios new_tio; - tcgetattr(STDIN_FILENO, &fake->old_tio); + tcgetattr (STDIN_FILENO, &fake->old_tio); new_tio = fake->old_tio; new_tio.c_lflag &=(~ICANON & ~ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); + tcsetattr (STDIN_FILENO, TCSANOW, &new_tio); fake->channel = g_io_channel_unix_new (STDIN_FILENO); if (!fake->channel) { @@ -150,6 +150,10 @@ if (!envvar_set ("POWER_PROFILE_DAEMON_FAKE_DRIVER")) return PPD_PROBE_RESULT_FAIL; + /* don't activate stdin unless interactive */ + if (isatty (fileno (stdout)) == 0) + return PPD_PROBE_RESULT_SUCCESS; + fake = PPD_DRIVER_FAKE (driver); if (!setup_keyboard (fake)) return PPD_PROBE_RESULT_FAIL; @@ -180,7 +184,7 @@ g_clear_pointer (&fake->channel, g_io_channel_unref); g_clear_handle_id (&fake->watch_id, g_source_remove); if (fake->tio_set) - tcsetattr(STDIN_FILENO, TCSANOW, &fake->old_tio); + tcsetattr (STDIN_FILENO, TCSANOW, &fake->old_tio); G_OBJECT_CLASS (ppd_driver_fake_parent_class)->finalize (object); } @@ -190,11 +194,11 @@ GObjectClass *object_class; PpdDriverClass *driver_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->constructor = ppd_driver_fake_constructor; object_class->finalize = ppd_driver_fake_finalize; - driver_class = PPD_DRIVER_CLASS(klass); + driver_class = PPD_DRIVER_CLASS (klass); driver_class->probe = ppd_driver_fake_probe; driver_class->activate_profile = ppd_driver_fake_activate_profile; } diff -Nru power-profiles-daemon-0.13/src/ppd-driver-fake.h power-profiles-daemon-0.21/src/ppd-driver-fake.h --- power-profiles-daemon-0.13/src/ppd-driver-fake.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-fake.h 2024-04-03 23:55:02.000000000 +0000 @@ -9,7 +9,7 @@ #pragma once -#include "ppd-driver.h" +#include "ppd-driver-platform.h" -#define PPD_TYPE_DRIVER_FAKE (ppd_driver_fake_get_type()) -G_DECLARE_FINAL_TYPE(PpdDriverFake, ppd_driver_fake, PPD, DRIVER_FAKE, PpdDriver) +#define PPD_TYPE_DRIVER_FAKE (ppd_driver_fake_get_type ()) +G_DECLARE_FINAL_TYPE (PpdDriverFake, ppd_driver_fake, PPD, DRIVER_FAKE, PpdDriverPlatform) diff -Nru power-profiles-daemon-0.13/src/ppd-driver-intel-pstate.c power-profiles-daemon-0.21/src/ppd-driver-intel-pstate.c --- power-profiles-daemon-0.13/src/ppd-driver-intel-pstate.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-intel-pstate.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,6 +7,8 @@ * */ +#define G_LOG_DOMAIN "CpuDriver" + #include #include "ppd-utils.h" @@ -25,17 +27,17 @@ struct _PpdDriverIntelPstate { - PpdDriver parent_instance; + PpdDriverCpu parent_instance; PpdProfile activated_profile; - GList *epp_devices; /* GList of paths */ - GList *epb_devices; /* GList of paths */ - GDBusProxy *logind_proxy; + GPtrArray *epp_devices; /* Array of paths */ + GPtrArray *epb_devices; /* Array of paths */ GFileMonitor *no_turbo_mon; char *no_turbo_path; + gboolean on_battery; }; -G_DEFINE_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD_TYPE_DRIVER) +G_DEFINE_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD_TYPE_DRIVER_CPU) static gboolean ppd_driver_intel_pstate_activate_profile (PpdDriver *driver, PpdProfile profile, @@ -88,8 +90,12 @@ g_autofree char *path = NULL; path = g_file_get_path (file); - g_debug ("File monitor change happened for '%s'", path); - update_no_turbo (pstate); + g_debug ("File monitor change happened for '%s' (event type %d)", path, event_type); + + g_return_if_fail (event_type != G_FILE_MONITOR_EVENT_DELETED); + + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + update_no_turbo (pstate); } static GFileMonitor * @@ -108,7 +114,7 @@ } static gboolean -has_turbo (void) +sys_has_turbo (void) { g_autofree char *turbo_pct_path = NULL; g_autofree char *contents = NULL; @@ -124,33 +130,28 @@ return has_turbo; } -static void -logind_proxy_signal_cb (GDBusProxy *proxy, - const char *sender_name, - const char *signal_name, - GVariant *parameters, - gpointer user_data) +static gboolean +ppd_driver_intel_pstate_prepare_for_sleep (PpdDriver *driver, + gboolean start, + GError **error) { - PpdDriverIntelPstate *pstate = user_data; - g_autoptr(GError) error = NULL; - gboolean start; - PpdProbeResult ret; - - if (g_strcmp0 (signal_name, "PrepareForSleep") != 0) - return; - g_variant_get (parameters, "(b)", &start); + PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver); + g_autoptr(GError) local_error = NULL; + if (start) - return; + return TRUE; - g_debug ("System woke up from suspend, re-applying energy_perf_bias"); - ret = ppd_driver_intel_pstate_activate_profile (PPD_DRIVER (pstate), - pstate->activated_profile, - PPD_PROFILE_ACTIVATION_REASON_RESUME, - &error); - if (ret != PPD_PROBE_RESULT_SUCCESS) { - g_warning ("Could not reapply energy_perf_bias preference on resume: %s", - error->message); + g_debug ("Re-applying energy_perf_bias"); + if (!ppd_driver_intel_pstate_activate_profile (PPD_DRIVER (pstate), + pstate->activated_profile, + PPD_PROFILE_ACTIVATION_REASON_RESUME, + &local_error)) { + g_propagate_prefixed_error (error, g_steal_pointer (&local_error), + "Could not reapply energy_perf_bias preference on resume: "); + return FALSE; } + + return TRUE; } static PpdProbeResult @@ -159,14 +160,12 @@ g_autoptr(GDir) dir = NULL; g_autofree char *policy_dir = NULL; const char *dirname; - g_autoptr(GError) error = NULL; - PpdProbeResult ret = PPD_PROBE_RESULT_FAIL; policy_dir = ppd_utils_get_sysfs_path (CPU_DIR); dir = g_dir_open (policy_dir, 0, NULL); if (!dir) { g_debug ("Could not open %s", CPU_DIR); - return ret; + return PPD_PROBE_RESULT_FAIL; } while ((dirname = g_dir_read_name (dir)) != NULL) { @@ -180,28 +179,16 @@ if (!g_file_test (path, G_FILE_TEST_EXISTS)) continue; - pstate->epb_devices = g_list_prepend (pstate->epb_devices, g_steal_pointer (&path)); - ret = PPD_PROBE_RESULT_SUCCESS; - } + if (!pstate->epb_devices) + pstate->epb_devices = g_ptr_array_new_with_free_func (g_free); - pstate->logind_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, - 0, - NULL, - SYSTEMD_DBUS_NAME, - SYSTEMD_DBUS_PATH, - SYSTEMD_DBUS_INTERFACE, - NULL, - &error); - if (!pstate->logind_proxy) { - g_debug ("Could not create proxy for logind: %s", - error->message); - } else { - g_signal_connect (pstate->logind_proxy, "g-signal", - G_CALLBACK (logind_proxy_signal_cb), - pstate); + g_ptr_array_add (pstate->epb_devices, g_steal_pointer (&path)); } - return ret; + if (pstate->epb_devices && pstate->epb_devices->len) + return PPD_PROBE_RESULT_SUCCESS; + + return PPD_PROBE_RESULT_FAIL; } static PpdProbeResult @@ -212,23 +199,22 @@ g_autofree char *pstate_status_path = NULL; g_autofree char *status = NULL; const char *dirname; - PpdProbeResult ret = PPD_PROBE_RESULT_FAIL; /* Verify that Intel P-State is running in active mode */ pstate_status_path = ppd_utils_get_sysfs_path (PSTATE_STATUS_PATH); if (!g_file_get_contents (pstate_status_path, &status, NULL, NULL)) - return ret; + return PPD_PROBE_RESULT_FAIL; status = g_strchomp (status); if (g_strcmp0 (status, "active") != 0) { g_debug ("Intel P-State is running in passive mode"); - return ret; + return PPD_PROBE_RESULT_FAIL; } policy_dir = ppd_utils_get_sysfs_path (CPUFREQ_POLICY_DIR); dir = g_dir_open (policy_dir, 0, NULL); if (!dir) { g_debug ("Could not open %s", policy_dir); - return ret; + return PPD_PROBE_RESULT_FAIL; } while ((dirname = g_dir_read_name (dir)) != NULL) { @@ -253,11 +239,16 @@ continue; } - pstate->epp_devices = g_list_prepend (pstate->epp_devices, g_steal_pointer (&path)); - ret = PPD_PROBE_RESULT_SUCCESS; + if (!pstate->epp_devices) + pstate->epp_devices = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (pstate->epp_devices, g_steal_pointer (&path)); } - return ret; + if (pstate->epp_devices && pstate->epp_devices->len) + return PPD_PROBE_RESULT_SUCCESS; + + return PPD_PROBE_RESULT_FAIL; } static PpdProbeResult @@ -265,35 +256,43 @@ { PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver); PpdProbeResult ret = PPD_PROBE_RESULT_FAIL; + PpdProbeResult epp_ret, epb_ret; + gboolean has_turbo; - ret = probe_epp (pstate); - if (ret == PPD_PROBE_RESULT_SUCCESS) - probe_epb (pstate); - else - ret = probe_epb (pstate); + epp_ret = probe_epp (pstate); + epb_ret = probe_epb (pstate); + ret = (epp_ret == PPD_PROBE_RESULT_SUCCESS) ? epp_ret : epb_ret; if (ret != PPD_PROBE_RESULT_SUCCESS) goto out; - if (has_turbo ()) { + has_turbo = sys_has_turbo (); + if (has_turbo) { /* Monitor the first "no_turbo" */ 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", - G_CALLBACK (no_turbo_changed), pstate); + g_signal_connect_object (G_OBJECT (pstate->no_turbo_mon), "changed", + G_CALLBACK (no_turbo_changed), pstate, 0); } update_no_turbo (pstate); } out: - g_debug ("%s p-state settings", + g_debug ("%s Intel p-state settings", ret == PPD_PROBE_RESULT_SUCCESS ? "Found" : "Didn't find"); + if (ret == PPD_PROBE_RESULT_SUCCESS) { + g_debug ("\tEnergy Performance Preference: %s", + epp_ret == PPD_PROBE_RESULT_SUCCESS ? "yes" : "no"); + g_debug ("\tEnergy Performance Bias: %s", + epp_ret == PPD_PROBE_RESULT_SUCCESS ? "yes" : "no"); + g_debug ("\tHas Turbo: %s", has_turbo ? "yes" : "no"); + } return ret; } static const char * -profile_to_epp_pref (PpdProfile profile) +profile_to_epp_pref (PpdProfile profile, gboolean battery) { /* Note that we don't check "energy_performance_available_preferences" * as all the values are always available */ @@ -301,16 +300,16 @@ case PPD_PROFILE_POWER_SAVER: return "power"; case PPD_PROFILE_BALANCED: - return "balance_performance"; + return battery ? "balance_power" : "balance_performance"; case PPD_PROFILE_PERFORMANCE: return "performance"; } - g_assert_not_reached (); + g_return_val_if_reached (NULL); } static const char * -profile_to_epb_pref (PpdProfile profile) +profile_to_epb_pref (PpdProfile profile, gboolean battery) { /* From arch/x86/include/asm/msr-index.h * See ENERGY_PERF_BIAS_* */ @@ -318,61 +317,79 @@ case PPD_PROFILE_POWER_SAVER: return "15"; case PPD_PROFILE_BALANCED: - return "6"; + return battery ? "8" : "6"; case PPD_PROFILE_PERFORMANCE: return "0"; } - g_assert_not_reached (); + g_return_val_if_reached (NULL); } static gboolean -apply_pref_to_devices (GList *devices, - const char *pref, +apply_pref_to_devices (PpdDriver *driver, + PpdProfile profile, GError **error) { - gboolean ret = TRUE; - GList *l; + PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver); + + if (profile == PPD_PROFILE_UNSET) + return TRUE; + + g_return_val_if_fail (pstate->epp_devices != NULL || + pstate->epb_devices != NULL, FALSE); + g_return_val_if_fail ((pstate->epp_devices && pstate->epp_devices->len != 0) || + (pstate->epb_devices && pstate->epb_devices->len != 0), FALSE); + + if (pstate->epp_devices) { + const char *epp_pref = profile_to_epp_pref (profile, pstate->on_battery); + + if (!ppd_utils_write_files (pstate->epp_devices, epp_pref, error)) + return FALSE; + } - for (l = devices; l != NULL; l = l->next) { - const char *path = l->data; + if (pstate->epb_devices) { + const char *epb_pref = profile_to_epb_pref (profile, pstate->on_battery); - ret = ppd_utils_write (path, pref, error); - if (!ret) - break; + if (!ppd_utils_write_files (pstate->epb_devices, epb_pref, error)) + return FALSE; } - return ret; + pstate->activated_profile = profile; + + return TRUE; } static gboolean -ppd_driver_intel_pstate_activate_profile (PpdDriver *driver, - PpdProfile profile, - PpdProfileActivationReason reason, - GError **error) +ppd_driver_intel_pstate_power_changed (PpdDriver *driver, + PpdPowerChangedReason reason, + GError **error) { PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver); - gboolean ret = FALSE; - const char *pref; - - g_return_val_if_fail (pstate->epp_devices != NULL || - pstate->epb_devices, FALSE); - if (pstate->epp_devices) { - pref = profile_to_epp_pref (profile); - ret = apply_pref_to_devices (pstate->epp_devices, pref, error); - if (!ret) - return ret; - } - if (pstate->epb_devices) { - pref = profile_to_epb_pref (profile); - ret = apply_pref_to_devices (pstate->epb_devices, pref, error); + switch (reason) { + case PPD_POWER_CHANGED_REASON_UNKNOWN: + case PPD_POWER_CHANGED_REASON_AC: + pstate->on_battery = FALSE; + break; + case PPD_POWER_CHANGED_REASON_BATTERY: + pstate->on_battery = TRUE; + break; + default: + g_return_val_if_reached (FALSE); } - if (ret) - pstate->activated_profile = profile; + return apply_pref_to_devices (driver, + pstate->activated_profile, + error); +} - return ret; +static gboolean +ppd_driver_intel_pstate_activate_profile (PpdDriver *driver, + PpdProfile profile, + PpdProfileActivationReason reason, + GError **error) +{ + return apply_pref_to_devices (driver, profile, error); } static void @@ -381,11 +398,11 @@ PpdDriverIntelPstate *driver; driver = PPD_DRIVER_INTEL_PSTATE (object); - g_clear_list (&driver->epp_devices, g_free); - g_clear_list (&driver->epb_devices, g_free); + + g_clear_pointer (&driver->epp_devices, g_ptr_array_unref); + g_clear_pointer (&driver->epb_devices, g_ptr_array_unref); g_clear_pointer (&driver->no_turbo_path, g_free); g_clear_object (&driver->no_turbo_mon); - g_clear_object (&driver->logind_proxy); G_OBJECT_CLASS (ppd_driver_intel_pstate_parent_class)->finalize (object); } @@ -395,13 +412,15 @@ GObjectClass *object_class; PpdDriverClass *driver_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->constructor = ppd_driver_intel_pstate_constructor; object_class->finalize = ppd_driver_intel_pstate_finalize; - driver_class = PPD_DRIVER_CLASS(klass); + driver_class = PPD_DRIVER_CLASS (klass); driver_class->probe = ppd_driver_intel_pstate_probe; driver_class->activate_profile = ppd_driver_intel_pstate_activate_profile; + driver_class->prepare_to_sleep = ppd_driver_intel_pstate_prepare_for_sleep; + driver_class->power_changed = ppd_driver_intel_pstate_power_changed; } static void diff -Nru power-profiles-daemon-0.13/src/ppd-driver-intel-pstate.h power-profiles-daemon-0.21/src/ppd-driver-intel-pstate.h --- power-profiles-daemon-0.13/src/ppd-driver-intel-pstate.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-intel-pstate.h 2024-04-03 23:55:02.000000000 +0000 @@ -9,7 +9,7 @@ #pragma once -#include "ppd-driver.h" +#include "ppd-driver-cpu.h" -#define PPD_TYPE_DRIVER_INTEL_PSTATE (ppd_driver_intel_pstate_get_type()) -G_DECLARE_FINAL_TYPE(PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD, DRIVER_INTEL_PSTATE, PpdDriver) +#define PPD_TYPE_DRIVER_INTEL_PSTATE (ppd_driver_intel_pstate_get_type ()) +G_DECLARE_FINAL_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD, DRIVER_INTEL_PSTATE, PpdDriverCpu) diff -Nru power-profiles-daemon-0.13/src/ppd-driver-placeholder.c power-profiles-daemon-0.21/src/ppd-driver-placeholder.c --- power-profiles-daemon-0.13/src/ppd-driver-placeholder.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-placeholder.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,14 +7,16 @@ * */ +#define G_LOG_DOMAIN "PlatformDriver" + #include "ppd-driver-placeholder.h" struct _PpdDriverPlaceholder { - PpdDriver parent_instance; + PpdDriverPlatform parent_instance; }; -G_DEFINE_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD_TYPE_DRIVER) +G_DEFINE_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD_TYPE_DRIVER_PLATFORM) static GObject* ppd_driver_placeholder_constructor (GType type, @@ -39,7 +41,7 @@ { GObjectClass *object_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->constructor = ppd_driver_placeholder_constructor; } diff -Nru power-profiles-daemon-0.13/src/ppd-driver-placeholder.h power-profiles-daemon-0.21/src/ppd-driver-placeholder.h --- power-profiles-daemon-0.13/src/ppd-driver-placeholder.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-placeholder.h 2024-04-03 23:55:02.000000000 +0000 @@ -9,7 +9,7 @@ #pragma once -#include "ppd-driver.h" +#include "ppd-driver-platform.h" -#define PPD_TYPE_DRIVER_PLACEHOLDER (ppd_driver_placeholder_get_type()) -G_DECLARE_FINAL_TYPE(PpdDriverPlaceholder, ppd_driver_placeholder, PPD, DRIVER_PLACEHOLDER, PpdDriver) +#define PPD_TYPE_DRIVER_PLACEHOLDER (ppd_driver_placeholder_get_type ()) +G_DECLARE_FINAL_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD, DRIVER_PLACEHOLDER, PpdDriverPlatform) diff -Nru power-profiles-daemon-0.13/src/ppd-driver-platform-profile.c power-profiles-daemon-0.21/src/ppd-driver-platform-profile.c --- power-profiles-daemon-0.13/src/ppd-driver-platform-profile.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-platform-profile.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,6 +7,8 @@ * */ +#define G_LOG_DOMAIN "PlatformDriver" + #include #include @@ -29,10 +31,10 @@ gboolean has_low_power; GFileMonitor *lapmode_mon; GFileMonitor *acpi_platform_profile_mon; - guint acpi_platform_profile_changed_id; + gulong acpi_platform_profile_changed_id; }; -G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER) +G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER_PLATFORM) static GObject* ppd_driver_platform_profile_constructor (GType type, @@ -69,7 +71,8 @@ return "performance"; } - g_assert_not_reached (); + g_debug ("Unhandled ACPI platform profile '%d'", profile); + g_return_val_if_reached (NULL); } static PpdProfile @@ -88,10 +91,9 @@ case 'p': return PPD_PROFILE_PERFORMANCE; default: - g_debug ("Got unsupported performance_profile value '%s'", str); + g_debug ("Unhandled ACPI platform profile '%c'", str[0]); + g_return_val_if_reached (PPD_PROFILE_UNSET); } - - return PPD_PROFILE_UNSET; } static PpdProfile @@ -195,8 +197,12 @@ gpointer user_data) { PpdDriverPlatformProfile *self = user_data; - g_debug (LAPMODE_SYSFS_NAME " attribute changed"); - update_dytc_lapmode_state (self); + + g_debug (LAPMODE_SYSFS_NAME " attribute changed (event: %d)", event_type); + g_return_if_fail (event_type != G_FILE_MONITOR_EVENT_DELETED); + + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + update_dytc_lapmode_state (self); } static void @@ -207,12 +213,17 @@ gpointer user_data) { PpdDriverPlatformProfile *self = user_data; + g_debug (ACPI_PLATFORM_PROFILE_PATH " changed (%d)", event_type); 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); + + g_return_if_fail (event_type != G_FILE_MONITOR_EVENT_DELETED); + + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + update_acpi_platform_profile_state (self); } static gboolean @@ -222,6 +233,7 @@ GError **error) { PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver); + g_autoptr(GError) local_error = NULL; g_autofree char *platform_profile_path = NULL; const char *platform_profile_value; @@ -243,8 +255,11 @@ 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); + if (!ppd_utils_write (platform_profile_path, + profile_to_acpi_platform_profile_value (self, profile), &local_error)) { + g_debug ("Failed to write to acpi_platform_profile: %s", local_error->message); + g_propagate_prefixed_error (error, g_steal_pointer (&local_error), + "Failed to write to acpi_platform_profile: "); g_signal_handler_unblock (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id); return FALSE; } @@ -314,8 +329,8 @@ 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); + g_signal_connect_object (G_OBJECT (self->lapmode_mon), "changed", + G_CALLBACK (lapmode_changed), self, 0); update_dytc_lapmode_state (self); out: @@ -332,6 +347,8 @@ PpdDriverPlatformProfile *driver; driver = PPD_DRIVER_PLATFORM_PROFILE (object); + g_clear_signal_handler (&driver->acpi_platform_profile_changed_id, + driver->acpi_platform_profile_mon); g_clear_pointer (&driver->profile_choices, g_strfreev); g_clear_object (&driver->device); g_clear_object (&driver->lapmode_mon); @@ -345,11 +362,11 @@ GObjectClass *object_class; PpdDriverClass *driver_class; - object_class = G_OBJECT_CLASS(klass); + 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 = PPD_DRIVER_CLASS (klass); driver_class->probe = ppd_driver_platform_profile_probe; driver_class->activate_profile = ppd_driver_platform_profile_activate_profile; } diff -Nru power-profiles-daemon-0.13/src/ppd-driver-platform-profile.h power-profiles-daemon-0.21/src/ppd-driver-platform-profile.h --- power-profiles-daemon-0.13/src/ppd-driver-platform-profile.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-platform-profile.h 2024-04-03 23:55:02.000000000 +0000 @@ -9,7 +9,7 @@ #pragma once -#include "ppd-driver.h" +#include "ppd-driver-platform.h" -#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) +#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, PpdDriverPlatform) diff -Nru power-profiles-daemon-0.13/src/ppd-driver-platform.c power-profiles-daemon-0.21/src/ppd-driver-platform.c --- power-profiles-daemon-0.13/src/ppd-driver-platform.c 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-platform.c 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Mario Limonciello + * + * 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. + * + */ + +#define G_LOG_DOMAIN "PlatformDriver" + +#include "ppd-driver-platform.h" + +G_DEFINE_TYPE (PpdDriverPlatform, ppd_driver_platform, PPD_TYPE_DRIVER) + +/** + * SECTION:ppd-driver-platform + * @Short_description: Profile Drivers + * @Title: Platform Profile Drivers + * + * Platform drivers are the implementation of the different profiles for + * the whole system. A driver will need to implement support for `power-saver` + * and `balanced` at a minimum. + * + * If no system-specific platform driver is available, a placeholder driver + * will be put in place, and the `performance` profile will be unavailable. + * + * There should not be a need to implement system-specific drivers, as the + * [`platform_profile`] (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/ABI/testing/sysfs-platform_profile) + * kernel API offers a way to implement system-specific profiles which + * `power-profiles-daemon` can consume. + * + * When a driver implements the `performance` profile, it might set the + * #PpdDriver:performance-degraded property if the profile isn't running to + * its fullest performance for any reason, such as thermal limits being + * reached, or because a part of the user's body is too close for safety, + * for example. + */ + +static void +ppd_driver_platform_finalize (GObject *object) +{ + G_OBJECT_CLASS (ppd_driver_platform_parent_class)->finalize (object); +} + +static void +ppd_driver_platform_class_init (PpdDriverPlatformClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = ppd_driver_platform_finalize; +} + +static void +ppd_driver_platform_init (PpdDriverPlatform *self) +{ +} diff -Nru power-profiles-daemon-0.13/src/ppd-driver-platform.h power-profiles-daemon-0.21/src/ppd-driver-platform.h --- power-profiles-daemon-0.13/src/ppd-driver-platform.h 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver-platform.h 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Mario Limonciello + * + * 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_PLATFORM (ppd_driver_platform_get_type ()) +G_DECLARE_DERIVABLE_TYPE (PpdDriverPlatform, ppd_driver_platform, PPD, DRIVER_PLATFORM, PpdDriver) + +/** + * PpdDriverPlatformClass: + * @parent_class: The parent class. + * + * New Platform drivers should derive from #PpdDriverPlatform and implement + * at least one of @probe () and @activate_profile. + */ +struct _PpdDriverPlatformClass +{ + PpdDriverClass parent_class; +}; diff -Nru power-profiles-daemon-0.13/src/ppd-driver.c power-profiles-daemon-0.21/src/ppd-driver.c --- power-profiles-daemon-0.13/src/ppd-driver.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver.c 2024-04-03 23:55:02.000000000 +0000 @@ -19,13 +19,7 @@ * the whole system. A driver will need to implement support `power-saver` * and `balanced` at a minimum. * - * If no system-specific driver is available, a placeholder driver - * will be put in place, and the `performance` profile will be unavailable. - * - * There should not be a need to implement system-specific drivers, as the - * [`platform_profile`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/ABI/testing/sysfs-platform_profile) - * kernel API offers a way to implement system-specific profiles which - * `power-profiles-daemon` can consume. + * All drivers should be derived from either #PpdDriverCpu or #PpdDriverPlatform * * When a driver implements the `performance` profile, it might set the * #PpdDriver:performance-degraded property if the profile isn't running to @@ -69,20 +63,28 @@ PpdDriver *driver = PPD_DRIVER (object); PpdDriverPrivate *priv = PPD_DRIVER_GET_PRIVATE (driver); + g_return_if_fail (PPD_IS_DRIVER (object)); + switch (property_id) { case PROP_DRIVER_NAME: - g_assert (priv->driver_name == NULL); + g_return_if_fail (priv->driver_name == NULL); priv->driver_name = g_value_dup_string (value); break; case PROP_PROFILES: priv->profiles = g_value_get_flags (value); break; case PROP_PERFORMANCE_DEGRADED: - g_clear_pointer (&priv->performance_degraded, g_free); - priv->performance_degraded = g_value_dup_string (value); + { + const char *degraded = g_value_get_string (value); + + g_clear_pointer (&priv->performance_degraded, g_free); + + if (degraded != NULL && *degraded != '\0') + priv->performance_degraded = g_strdup (degraded); + } break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } @@ -106,7 +108,7 @@ g_value_set_string (value, priv->performance_degraded); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } @@ -127,7 +129,7 @@ { GObjectClass *object_class; - object_class = G_OBJECT_CLASS(klass); + object_class = G_OBJECT_CLASS (klass); object_class->finalize = ppd_driver_finalize; object_class->get_property = ppd_driver_get_property; object_class->set_property = ppd_driver_set_property; @@ -173,7 +175,7 @@ * A unique driver name, only used for debugging. */ g_object_class_install_property (object_class, PROP_DRIVER_NAME, - g_param_spec_string("driver-name", + g_param_spec_string ("driver-name", "Driver name", "Profile driver name", NULL, @@ -185,21 +187,20 @@ * The bitmask of #PpdProfiles implemented by this driver. */ g_object_class_install_property (object_class, PROP_PROFILES, - g_param_spec_flags("profiles", + g_param_spec_flags ("profiles", "Profiles", "Profiles implemented by this driver", PPD_TYPE_PROFILE, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - /** - * PpdDriver:performance-degraded: + * PpdPlatformDriver:performance-degraded: * * If set to a non-%NULL value, the reason why the performance profile is unavailable. * The value must be one of the options listed in the D-Bus API reference. */ g_object_class_install_property (object_class, PROP_PERFORMANCE_DEGRADED, - g_param_spec_string("performance-degraded", + g_param_spec_string ("performance-degraded", "Performance Degraded", "Why the performance profile is degraded, if set", NULL, @@ -237,6 +238,32 @@ return PPD_DRIVER_GET_CLASS (driver)->activate_profile (driver, profile, reason, error); } +gboolean +ppd_driver_power_changed (PpdDriver *driver, + PpdPowerChangedReason reason, + GError **error) +{ + g_return_val_if_fail (PPD_IS_DRIVER (driver), FALSE); + + if (!PPD_DRIVER_GET_CLASS (driver)->power_changed) + return TRUE; + + return PPD_DRIVER_GET_CLASS (driver)->power_changed (driver, reason, error); +} + +gboolean +ppd_driver_prepare_to_sleep (PpdDriver *driver, + gboolean start, + GError **error) +{ + g_return_val_if_fail (PPD_IS_DRIVER (driver), FALSE); + + if (!PPD_DRIVER_GET_CLASS (driver)->prepare_to_sleep) + return TRUE; + + return PPD_DRIVER_GET_CLASS (driver)->prepare_to_sleep (driver, start, error); +} + const char * ppd_driver_get_driver_name (PpdDriver *driver) { @@ -278,7 +305,7 @@ g_return_val_if_fail (PPD_IS_DRIVER (driver), NULL); priv = PPD_DRIVER_GET_PRIVATE (driver); - return priv->performance_degraded ? priv->performance_degraded : ""; + return priv->performance_degraded; } gboolean @@ -320,6 +347,6 @@ case PPD_PROFILE_ACTIVATION_REASON_PROGRAM_HOLD: return "program-hold"; default: - g_assert_not_reached (); + g_return_val_if_reached (NULL); } } diff -Nru power-profiles-daemon-0.13/src/ppd-driver.h power-profiles-daemon-0.21/src/ppd-driver.h --- power-profiles-daemon-0.13/src/ppd-driver.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-driver.h 2024-04-03 23:55:02.000000000 +0000 @@ -12,26 +12,8 @@ #include #include "ppd-profile.h" -#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; +#define PPD_TYPE_DRIVER (ppd_driver_get_type ()) +G_DECLARE_DERIVABLE_TYPE (PpdDriver, ppd_driver, PPD, DRIVER, GObject) /** * PpdProfileActivationReason: @@ -63,9 +45,11 @@ * @parent_class: The parent class. * @probe: Called by the daemon on startup. * @activate_profile: Called by the daemon for every profile change. + * @power_changed: Called by the daemon when power adapter status changes * - * New profile drivers should derive from #PpdDriver and implement - * at least one of probe() and @activate_profile. + * New profile drivers should not derive from #PpdDriver. They should + * derive from the child from #PpdDriverCpu or #PpdDriverPlatform drivers + * and implement at least one of probe () and @activate_profile. */ struct _PpdDriverClass { @@ -76,12 +60,20 @@ PpdProfile profile, PpdProfileActivationReason reason, GError **error); + gboolean (* power_changed) (PpdDriver *driver, + PpdPowerChangedReason reason, + GError **error); + gboolean (* prepare_to_sleep) (PpdDriver *driver, + gboolean start, + GError **error); }; #ifndef __GTK_DOC_IGNORE__ PpdProbeResult ppd_driver_probe (PpdDriver *driver); gboolean ppd_driver_activate_profile (PpdDriver *driver, PpdProfile profile, PpdProfileActivationReason reason, GError **error); +gboolean ppd_driver_power_changed (PpdDriver *driver, PpdPowerChangedReason reason, GError **error); +gboolean ppd_driver_prepare_to_sleep (PpdDriver *driver, gboolean start, GError **error); const char *ppd_driver_get_driver_name (PpdDriver *driver); PpdProfile ppd_driver_get_profiles (PpdDriver *driver); const char *ppd_driver_get_performance_degraded (PpdDriver *driver); diff -Nru power-profiles-daemon-0.13/src/ppd-profile.c power-profiles-daemon-0.21/src/ppd-profile.c --- power-profiles-daemon-0.13/src/ppd-profile.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-profile.c 2024-04-03 23:55:02.000000000 +0000 @@ -13,31 +13,37 @@ const char * ppd_profile_to_str (PpdProfile profile) { - GFlagsClass *klass = g_type_class_ref (PPD_TYPE_PROFILE); + g_autoptr(GFlagsClass) klass = g_type_class_ref (PPD_TYPE_PROFILE); GFlagsValue *value = g_flags_get_first_value (klass, profile); const gchar *name = value ? value->value_nick : ""; - g_type_class_unref (klass); return name; } PpdProfile ppd_profile_from_str (const char *str) { - GFlagsClass *klass = g_type_class_ref (PPD_TYPE_PROFILE); + g_autoptr(GFlagsClass) klass = g_type_class_ref (PPD_TYPE_PROFILE); GFlagsValue *value = g_flags_get_value_by_nick (klass, str); PpdProfile profile = value ? value->value : PPD_PROFILE_UNSET; - g_type_class_unref (klass); return profile; } gboolean ppd_profile_has_single_flag (PpdProfile profile) { - GFlagsClass *klass = g_type_class_ref (PPD_TYPE_PROFILE); + g_autoptr(GFlagsClass) klass = g_type_class_ref (PPD_TYPE_PROFILE); GFlagsValue *value = g_flags_get_first_value (klass, profile); - gboolean ret = FALSE; if (value && value->value == profile) - ret = TRUE; - g_type_class_unref (klass); - return ret; + return TRUE; + + return FALSE; +} + +const char * +ppd_power_changed_reason_to_str (PpdPowerChangedReason reason) +{ + g_autoptr(GEnumClass) klass = g_type_class_ref (PPD_TYPE_POWER_CHANGED_REASON); + GEnumValue *value = g_enum_get_value (klass, reason); + const gchar *name = value ? value->value_nick : ""; + return name; } diff -Nru power-profiles-daemon-0.13/src/ppd-profile.h power-profiles-daemon-0.21/src/ppd-profile.h --- power-profiles-daemon-0.13/src/ppd-profile.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-profile.h 2024-04-03 23:55:02.000000000 +0000 @@ -12,6 +12,24 @@ #include /** + * 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; + +/** * PpdProfile: * @PPD_PROFILE_POWER_SAVER: "power-saver", the battery saving profile * @PPD_PROFILE_BALANCED: balanced, the default profile @@ -27,9 +45,27 @@ PPD_PROFILE_PERFORMANCE = 1 << 2 } PpdProfile; +/** + * PpdPowerChangedReason: + * @PPD_POWER_CHANGED_REASON_UNKNOWN: the power state is now unknown. + * This can happen if the power notification service is no longer available. + * @PPD_POWER_CHANGED_REASON_AC: the power source is now AC. + * @PPD_POWER_CHANGED_REASON_BATTERY: the power source is battery. + + * Drivers or actions can use this information to decide what to do within a + * given profile. + */ +typedef enum{ + PPD_POWER_CHANGED_REASON_UNKNOWN = 0, + PPD_POWER_CHANGED_REASON_AC, + PPD_POWER_CHANGED_REASON_BATTERY, +} PpdPowerChangedReason; + #define PPD_PROFILE_ALL (PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER | PPD_PROFILE_PERFORMANCE) #define PPD_PROFILE_UNSET (0) const char *ppd_profile_to_str (PpdProfile profile); PpdProfile ppd_profile_from_str (const char *str); gboolean ppd_profile_has_single_flag (PpdProfile profile); + +const char *ppd_power_changed_reason_to_str (PpdPowerChangedReason reason); diff -Nru power-profiles-daemon-0.13/src/ppd-utils.c power-profiles-daemon-0.21/src/ppd-utils.c --- power-profiles-daemon-0.13/src/ppd-utils.c 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-utils.c 2024-04-03 23:55:02.000000000 +0000 @@ -7,8 +7,12 @@ * */ +#define G_LOG_DOMAIN "Utils" + #include "ppd-utils.h" +#include #include +#include #include #include @@ -24,39 +28,65 @@ return g_build_filename (root, filename, NULL); } -gboolean ppd_utils_write (const char *filename, - const char *value, - GError **error) +gboolean +ppd_utils_write (const char *filename, + const char *value, + GError **error) { - FILE *sysfsfp; - int ret; +#if GLIB_CHECK_VERSION (2, 76, 0) + g_autofd +#endif + int fd = -1; + size_t size; g_return_val_if_fail (filename, FALSE); g_return_val_if_fail (value, FALSE); g_debug ("Writing '%s' to '%s'", value, filename); - sysfsfp = fopen (filename, "w"); - if (sysfsfp == NULL) { + fd = g_open (filename, O_WRONLY | O_TRUNC | O_SYNC); + if (fd == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Could not open '%s' for writing", filename); g_debug ("Could not open for writing '%s'", filename); return FALSE; } - setbuf(sysfsfp, NULL); - ret = fprintf (sysfsfp, "%s", value); - if (ret <= 0) { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), - "Error writing '%s': %s", filename, g_strerror (errno)); - g_debug ("Error writing '%s': %s", filename, g_strerror (errno)); - return FALSE; + + size = strlen (value); + while (size) { + ssize_t written = write (fd, value, size); + + if (written == -1) { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Error writing '%s': %s", filename, g_strerror (errno)); + g_debug ("Error writing '%s': %s", filename, g_strerror (errno)); +#if !GLIB_CHECK_VERSION (2, 76, 0) + g_close (fd, NULL); +#endif + return FALSE; + } + + g_return_val_if_fail (written <= size, FALSE); + size -= written; } - if (fclose (sysfsfp) != 0) { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), - "Error closing '%s': %s", filename, g_strerror (errno)); - g_debug ("Error closing '%s': %s", filename, g_strerror (errno)); - return FALSE; + + return TRUE; +} + +gboolean +ppd_utils_write_files (GPtrArray *filenames, + const char *value, + GError **error) +{ + g_return_val_if_fail (filenames != NULL, FALSE); + + for (guint i = 0; i < filenames->len; i++) { + const char *file = g_ptr_array_index (filenames, i); + + if (!ppd_utils_write (file, value, error)) + return FALSE; } + return TRUE; } @@ -75,6 +105,17 @@ return ppd_utils_write (filename, value, error); } +gboolean ppd_utils_write_sysfs_int (GUdevDevice *device, + const char *attribute, + gint64 value, + GError **error) +{ + g_autofree char *str_value = NULL; + + str_value = g_strdup_printf ("%" G_GINT64_FORMAT, value); + return ppd_utils_write_sysfs (device, attribute, str_value, error); +} + GFileMonitor * ppd_utils_monitor_sysfs_attr (GUdevDevice *device, const char *attribute, diff -Nru power-profiles-daemon-0.13/src/ppd-utils.h power-profiles-daemon-0.21/src/ppd-utils.h --- power-profiles-daemon-0.13/src/ppd-utils.h 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/src/ppd-utils.h 2024-04-03 23:55:02.000000000 +0000 @@ -16,10 +16,17 @@ gboolean ppd_utils_write (const char *filename, const char *value, GError **error); +gboolean ppd_utils_write_files (GPtrArray *filenames, + const char *value, + GError **error); gboolean ppd_utils_write_sysfs (GUdevDevice *device, const char *attribute, const char *value, GError **error); +gboolean ppd_utils_write_sysfs_int (GUdevDevice *device, + const char *attribute, + gint64 value, + GError **error); GFileMonitor *ppd_utils_monitor_sysfs_attr (GUdevDevice *device, const char *attribute, GError **error); diff -Nru power-profiles-daemon-0.13/tests/integration-test.py power-profiles-daemon-0.21/tests/integration-test.py --- power-profiles-daemon-0.13/tests/integration-test.py 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/tests/integration-test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1264 +0,0 @@ -#!/usr/bin/python3 - -# power-profiles-daemon integration test suite -# -# Run in built tree to test local built binaries, or from anywhere else to test -# system installed binaries. -# -# Copyright: (C) 2011 Martin Pitt -# (C) 2020 Bastien Nocera -# (C) 2021 David Redondo -# -# 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 2 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. - -import os -import sys -import dbus -import tempfile -import subprocess -import unittest -import time - -try: - import gi - from gi.repository import GLib - from gi.repository import Gio -except ImportError as e: - sys.stderr.write('Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: %s\n' % str(e)) - sys.exit(77) - -try: - gi.require_version('UMockdev', '1.0') - from gi.repository import UMockdev -except ImportError: - sys.stderr.write('Skipping tests, umockdev not available (https://github.com/martinpitt/umockdev)\n') - sys.exit(77) - -try: - import dbusmock -except ImportError: - sys.stderr.write('Skipping tests, python-dbusmock not available (http://pypi.python.org/pypi/python-dbusmock).\n') - sys.exit(77) - - -PP = 'net.hadess.PowerProfiles' -PP_PATH = '/net/hadess/PowerProfiles' -PP_INTERFACE = 'net.hadess.PowerProfiles' - -class Tests(dbusmock.DBusTestCase): - @classmethod - def setUpClass(cls): - # run from local build tree if we are in one, otherwise use system instance - builddir = os.getenv('top_builddir', '.') - 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) - 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) - else: - cls.daemon_path = None - with open('/usr/lib/systemd/system/power-profiles-daemon.service') as f: - for line in f: - if line.startswith('ExecStart='): - cls.daemon_path = line.split('=', 1)[1].strip() - break - assert cls.daemon_path, 'could not determine daemon path from systemd .service file' - print('Testing installed system binary (%s)' % cls.daemon_path) - - # 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) - cls.test_bus.up() - try: - del os.environ['DBUS_SESSION_BUS_ADDRESS'] - except KeyError: - pass - os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = cls.test_bus.get_bus_address() - - cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) - cls.dbus_con = cls.get_dbus(True) - - @classmethod - def tearDownClass(cls): - cls.test_bus.down() - dbusmock.DBusTestCase.tearDownClass() - - def setUp(self): - '''Set up a local umockdev testbed. - - The testbed is initially empty. - ''' - self.testbed = UMockdev.Testbed.new() - self.polkitd, self.obj_polkit = self.spawn_server_template( - 'polkitd', {}, stdout=subprocess.PIPE) - self.obj_polkit.SetAllowed(['net.hadess.PowerProfiles.switch-profile', - 'net.hadess.PowerProfiles.hold-profile']) - - self.proxy = None - self.log = None - self.daemon = None - - # Used for dytc devices - self.tp_acpi = None - - def run(self, result=None): - super(Tests, self).run(result) - if result and len(result.errors) + len(result.failures) > 0 and self.log: - with open(self.log.name) as f: - sys.stderr.write('\n-------------- daemon log: ----------------\n') - sys.stderr.write(f.read()) - sys.stderr.write('------------------------------\n') - - def tearDown(self): - del self.testbed - self.stop_daemon() - - if self.polkitd: - try: - self.polkitd.kill() - except OSError: - pass - self.polkitd.wait() - self.polkitd = None - self.obj_polkit = None - - del self.tp_acpi - try: - os.remove(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini') - except Exception: - pass - - # - # Daemon control and D-BUS I/O - # - - def start_daemon(self): - '''Start daemon and create DBus proxy. - - When done, this sets self.proxy as the Gio.DBusProxy for power-profiles-daemon. - ''' - env = os.environ.copy() - env['G_DEBUG'] = 'fatal-criticals' - env['G_MESSAGES_DEBUG'] = 'all' - # note: Python doesn't propagate the setenv from Testbed.new(), so we - # have to do that ourselves - env['UMOCKDEV_DIR'] = self.testbed.get_root_dir() - self.log = tempfile.NamedTemporaryFile() - if os.getenv('VALGRIND') != None: - daemon_path = ['valgrind', self.daemon_path, '-v'] - else: - daemon_path = [self.daemon_path, '-v'] - - self.daemon = subprocess.Popen(daemon_path, - env=env, stdout=self.log, - stderr=subprocess.STDOUT) - - # wait until the daemon gets online - timeout = 100 - while timeout > 0: - time.sleep(0.1) - timeout -= 1 - try: - self.get_dbus_property('ActiveProfile') - break - except GLib.GError: - pass - else: - self.fail('daemon did not start in 10 seconds') - - self.proxy = Gio.DBusProxy.new_sync( - self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP, - PP_PATH, PP, None) - - self.assertEqual(self.daemon.poll(), None, 'daemon crashed') - - def stop_daemon(self): - '''Stop the daemon if it is running.''' - - if self.daemon: - try: - self.daemon.kill() - except OSError: - pass - self.daemon.wait() - self.daemon = None - self.proxy = None - - def get_dbus_property(self, name): - '''Get property value from daemon D-Bus interface.''' - - proxy = Gio.DBusProxy.new_sync( - self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP, - PP_PATH, 'org.freedesktop.DBus.Properties', None) - return proxy.Get('(ss)', PP, name) - - def set_dbus_property(self, name, value): - '''Set property value on daemon D-Bus interface.''' - - proxy = Gio.DBusProxy.new_sync( - self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP, - PP_PATH, 'org.freedesktop.DBus.Properties', None) - return proxy.Set('(ssv)', PP, name, value) - - def call_dbus_method(self, name, parameters): - '''Call a method of the daemon D-Bus interface.''' - - proxy = Gio.DBusProxy.new_sync( - self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP, - PP_PATH, PP_INTERFACE, None) - return proxy.call_sync(name, parameters, Gio.DBusCallFlags.NO_AUTO_START, -1, None) - - - def have_text_in_log(self, text): - return self.count_text_in_log(text) > 0 - - def count_text_in_log(self, text): - with open(self.log.name) as f: - return f.read().count(text) - - 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 get_mtime(self, device, attribute): - return os.path.getmtime(self.testbed.get_root_dir() + '/' + 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_empty_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('\n') - with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices: - choices.write('\n') - - 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 remove_platform_profile(self): - acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") - os.remove(os.path.join(acpi_dir, "platform_profile_choices")) - os.remove(os.path.join(acpi_dir, "platform_profile")) - os.removedirs(acpi_dir) - - def assertEventually(self, condition, message=None, timeout=50): - '''Assert that condition function eventually returns True. - - Timeout is in deciseconds, defaulting to 50 (5 seconds). message is - printed on failure. - ''' - while timeout >= 0: - context = GLib.MainContext.default() - while context.iteration(False): - pass - if condition(): - break - timeout -= 1 - time.sleep(0.1) - else: - self.fail(message or 'timed out waiting for ' + str(condition)) - - # - # Actual test cases - # - def test_dbus_startup_error(self): - '''D-Bus startup error''' - - self.start_daemon() - out = subprocess.run([self.daemon_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - 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''' - - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - self.assertEqual(self.get_dbus_property('PerformanceDegraded'), '') - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 2) - 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') - - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - with self.assertRaises(gi.repository.GLib.GError): - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - with self.assertRaises(gi.repository.GLib.GError): - cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', 'testReason', 'testApplication'))) - - # process = subprocess.Popen(['gdbus', 'introspect', '--system', '--dest', 'net.hadess.PowerProfiles', '--object-path', '/net/hadess/PowerProfiles']) - # print (self.get_dbus_property('GPUs')) - - self.stop_daemon() - - def test_inhibited_property(self): - '''Test that the inhibited property exists''' - - 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('PerformanceInhibited'), '') - - def test_degraded_transition(self): - '''Test that transitions work as expected when degraded''' - - 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.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - # Degraded - 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('PerformanceDegraded'), 'lap-detected') - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - # Switch to non-performance - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - def test_intel_pstate(self): - '''Intel P-State driver (no UPower)''' - - # Create 2 CPUs with preferences - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - 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, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir2, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - - # Create Intel P-State configuration - 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\n") - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("active\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'intel_pstate') - self.assertEqual(profiles[0]['Profile'], 'power-saver') - - contents = None - with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'balance_performance') - - # Set performance mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - contents = None - with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance') - - # Disable turbo - with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo: - 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'), 'performance') - self.assertEqual(self.get_dbus_property('PerformanceDegraded'), 'high-operating-temperature') - - self.stop_daemon() - - # Verify that the Lenovo DYTC driver still gets preferred - self.create_platform_profile() - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - 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(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - gov_path = os.path.join(dir1, 'scaling_governor') - with open(gov_path, 'w') as gov: - gov.write('performance\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - 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, "status"),'w') as status: - status.write("active\n") - - upowerd, obj_upower = self.spawn_server_template( - 'upower', {'DaemonVersion': '0.99', 'OnBattery': False}, stdout=subprocess.PIPE) - - self.start_daemon() - - with open(gov_path, 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'powersave') - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'intel_pstate') - self.assertEqual(profiles[0]['Profile'], 'power-saver') - - 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') - - self.stop_daemon() - - upowerd.terminate() - upowerd.wait() - upowerd.stdout.close() - - def test_intel_pstate_error(self): - '''Intel P-State driver in error state''' - - 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, "status"),'w') as status: - status.write("active\n") - - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - pref_path = os.path.join(dir1, "energy_performance_preference") - old_umask = os.umask(0o333) - with open(pref_path,'w') as prefs: - prefs.write("balance_performance\n") - os.umask(old_umask) - # Make file non-writable to root - if os.geteuid() == 0: - if not GLib.find_program_in_path('chattr'): - os._exit(77) - subprocess.check_output(['chattr', '+i', pref_path]) - - self.start_daemon() - - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # Error when setting performance mode - with self.assertRaises(gi.repository.GLib.GError): - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'balance_performance\n') - - self.stop_daemon() - - if os.geteuid() == 0: - subprocess.check_output(['chattr', '-i', pref_path]) - - def test_intel_pstate_passive(self): - '''Intel P-State in passive mode -> placeholder''' - - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - - # Create Intel P-State configuration - 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\n") - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("passive\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 2) - self.assertEqual(profiles[0]['Driver'], 'placeholder') - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance\n') - - # Set performance mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance\n') - - self.stop_daemon() - - def test_intel_pstate_passive_with_epb(self): - '''Intel P-State in passive mode (no HWP) with energy_perf_bias''' - - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - dir2 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpu0/power/") - os.makedirs(dir2) - with open(os.path.join(dir2, 'energy_perf_bias'), 'w') as epb: - epb.write("6") - - # Create Intel P-State configuration - 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\n") - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("passive\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'intel_pstate') - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # Set power-saver mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - contents = None - with open(os.path.join(dir2, "energy_perf_bias"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'15') - - # Set performance mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - contents = None - with open(os.path.join(dir2, "energy_perf_bias"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'0') - - self.stop_daemon() - - def test_amd_pstate(self): - '''AMD P-State driver (no UPower)''' - - # Create 2 CPUs with preferences - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - 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, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir2, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - - # Create AMD P-State configuration - pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate") - os.makedirs(pstate_dir) - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("active\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'amd_pstate') - self.assertEqual(profiles[0]['Profile'], 'power-saver') - - contents = None - with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'balance_performance') - - # Set performance mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - contents = None - with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance') - - self.stop_daemon() - - # Verify that the Lenovo DYTC driver still gets preferred - self.create_platform_profile() - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'platform_profile') - - def test_amd_pstate_balance(self): - '''AMD P-State driver (balance)''' - - # Create CPU with preference - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - gov_path = os.path.join(dir1, 'scaling_governor') - with open(gov_path, 'w') as gov: - gov.write('performance\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate") - os.makedirs(pstate_dir) - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("active\n") - - upowerd, obj_upower = self.spawn_server_template( - 'upower', {'DaemonVersion': '0.99', 'OnBattery': False}, stdout=subprocess.PIPE) - - self.start_daemon() - - with open(gov_path, 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'powersave') - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(profiles[0]['Driver'], 'amd_pstate') - self.assertEqual(profiles[0]['Profile'], 'power-saver') - - 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-amd-pstate.c - self.assertEqual(contents, b'balance_performance') - - self.stop_daemon() - - upowerd.terminate() - upowerd.wait() - upowerd.stdout.close() - - def test_amd_pstate_error(self): - '''AMD P-State driver in error state''' - - pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate") - os.makedirs(pstate_dir) - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("active\n") - - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - pref_path = os.path.join(dir1, "energy_performance_preference") - old_umask = os.umask(0o333) - with open(pref_path,'w') as prefs: - prefs.write("balance_performance\n") - os.umask(old_umask) - # Make file non-writable to root - if os.geteuid() == 0: - if not GLib.find_program_in_path('chattr'): - os._exit(77) - subprocess.check_output(['chattr', '+i', pref_path]) - - self.start_daemon() - - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # Error when setting performance mode - with self.assertRaises(gi.repository.GLib.GError): - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'balance_performance\n') - - self.stop_daemon() - - if os.geteuid() == 0: - subprocess.check_output(['chattr', '-i', pref_path]) - - def test_amd_pstate_passive(self): - '''AMD P-State in passive mode -> placeholder''' - - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - - # Create AMD P-State configuration - pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate") - os.makedirs(pstate_dir) - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("passive\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 2) - self.assertEqual(profiles[0]['Driver'], 'placeholder') - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance\n') - - # Set performance mode - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - contents = None - with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f: - contents = f.read() - self.assertEqual(contents, b'performance\n') - - self.stop_daemon() - - def test_dytc_performance_driver(self): - '''Lenovo DYTC performance driver''' - - 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'], 'platform_profile') - self.assertEqual(profiles[0]['Profile'], 'power-saver') - self.assertEqual(profiles[2]['Driver'], 'platform_profile') - self.assertEqual(profiles[2]['Profile'], 'performance') - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - # lapmode detected - self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n') - self.assertEventually(lambda: self.get_dbus_property('PerformanceDegraded') == 'lap-detected') - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - # Reset lapmode - self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '0\n') - self.assertEventually(lambda: self.get_dbus_property('PerformanceDegraded') == '') - - # Performance mode didn't change - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - # 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') - - # 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''' - - os.environ['POWER_PROFILE_DAEMON_FAKE_DRIVER'] = '1' - self.start_daemon() - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.stop_daemon() - - del os.environ['POWER_PROFILE_DAEMON_FAKE_DRIVER'] - self.start_daemon() - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 2) - - def test_trickle_charge_system(self): - '''Trickle power_supply charge type''' - - fastcharge = self.testbed.add_device('power_supply', 'bq24190-charger', None, - [ 'charge_type', 'Trickle', 'scope', 'System' ], - [] - ) - - self.start_daemon() - - self.assertIn('trickle_charge', self.get_dbus_property('Actions')) - - # Verify that charge-type stays untouched - self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle') - - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle') - - def test_trickle_charge_mode_no_change(self): - '''Trickle power_supply charge type''' - - fastcharge = self.testbed.add_device('power_supply', 'MFi Fastcharge', None, - [ 'charge_type', 'Fast', 'scope', 'Device' ], - [] - ) - - mtime = self.get_mtime(fastcharge, 'charge_type') - self.start_daemon() - - self.assertIn('trickle_charge', self.get_dbus_property('Actions')) - - # Verify that charge-type didn't get touched - self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Fast') - self.assertEqual(self.get_mtime(fastcharge, 'charge_type'), mtime) - - def test_trickle_charge_mode(self): - '''Trickle power_supply charge type''' - - idevice = self.testbed.add_device('usb', 'iDevice', None, - [], - [ 'ID_MODEL', 'iDevice', 'DRIVER', 'apple-mfi-fastcharge' ] - ) - fastcharge = self.testbed.add_device('power_supply', 'MFi Fastcharge', idevice, - [ 'charge_type', 'Trickle', 'scope', 'Device' ], - [] - ) - - self.start_daemon() - - self.assertIn('trickle_charge', self.get_dbus_property('Actions')) - - # Verify that charge-type got changed to Fast on startup - self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Fast') - - # Verify that charge-type got changed to Trickle when power saving - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle') - - # FIXME no performance mode - # Verify that charge-type got changed to Fast in a non-default, non-power save mode - # 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''' - self.create_empty_platform_profile() - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 2) - - acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") - 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'), 'balanced') - self.assertEqual(self.get_dbus_property('PerformanceDegraded'), '') - - 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'), 'balanced') - self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'cool') - 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.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced')) - self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'balanced') - - self.stop_daemon() - - def test_quiet(self): - # Uses quiet 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("quiet\n") - with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices: - choices.write("quiet balanced balanced-performance 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'), 'balanced') - self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'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'quiet') - - self.stop_daemon() - - def test_hold_release_profile(self): - self.create_platform_profile() - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - - cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', 'testReason', 'testApplication'))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - profileHolds = self.get_dbus_property('ActiveProfileHolds') - self.assertEqual(len(profileHolds), 1) - self.assertEqual(profileHolds[0]["Profile"], "performance") - self.assertEqual(profileHolds[0]["Reason"], "testReason") - self.assertEqual(profileHolds[0]["ApplicationId"], "testApplication") - - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", cookie)) - profileHolds = self.get_dbus_property('ActiveProfileHolds') - self.assertEqual(len(profileHolds), 0) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # When the profile is changed manually, holds should be released a - self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 1) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced')) - self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 0) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # When all holds are released, the last manually selected profile should be activated - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", cookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - - self.stop_daemon() - - def test_vanishing_hold(self): - self.create_platform_profile() - self.start_daemon() - - builddir = os.getenv('top_builddir', '.') - tool_path = os.path.join(builddir, 'src', 'powerprofilesctl') - - launch_process = subprocess.Popen([tool_path, 'launch', '-p', 'power-saver', 'sleep', '3600'], - stdout=sys.stdout, stderr=sys.stderr) - assert launch_process - time.sleep(1) - holds = self.get_dbus_property('ActiveProfileHolds') - self.assertEqual(len(holds), 1) - hold = holds[0] - self.assertEqual(hold['Profile'], 'power-saver') - - # Make sure to handle vanishing clients - launch_process.terminate() - launch_process.wait() - - holds = self.get_dbus_property('ActiveProfileHolds') - self.assertEqual(len(holds), 0) - - self.stop_daemon() - - def test_hold_priority(self): - '''power-saver should take priority over performance''' - 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') - - # Test every order of holding and releasing power-saver and performance - # hold performance and then power-saver, release in the same order - performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", powerSaverCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # hold performance and then power-saver, but release power-saver first - performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",powerSaverCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # hold power-saver and then performance, release in the same order - powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",powerSaverCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - # hold power-saver and then performance, but release performance first - powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",performanceCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", powerSaverCookie)) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - self.stop_daemon() - - def test_save_profile(self): - '''save profile across runs''' - - self.create_platform_profile() - - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.stop_daemon() - - # sys.stderr.write('\n-------------- config file: ----------------\n') - # with open(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini') as f: - # sys.stderr.write(f.read()) - # sys.stderr.write('------------------------------\n') - - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - # Programmatically set profile aren't saved - performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance') - self.stop_daemon() - - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver') - self.stop_daemon() - - def test_save_deferred_load(self): - '''save profile across runs, but kernel driver loaded after start''' - - self.create_platform_profile() - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver')) - self.stop_daemon() - self.remove_platform_profile() - - # We could verify the contents of the configuration file here - - self.create_empty_platform_profile() - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") - 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") - - self.assertEventually(lambda: self.get_dbus_property('ActiveProfile') == 'power-saver') - self.stop_daemon() - - def test_not_allowed_profile(self): - '''Check that we get errors when trying to change a profile and not allowed''' - - self.obj_polkit.SetAllowed(dbus.Array([], signature='s')) - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - proxy = Gio.DBusProxy.new_sync( - self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP, - PP_PATH, 'org.freedesktop.DBus.Properties', None) - with self.assertRaises(gi.repository.GLib.GError) as cm: - proxy.Set('(ssv)', PP, 'ActiveProfile', GLib.Variant.new_string('power-saver')) - self.assertIn('AccessDenied', str(cm.exception)) - - self.stop_daemon() - - def test_not_allowed_hold(self): - '''Check that we get an error when trying to hold a profile and not allowed''' - - self.obj_polkit.SetAllowed(dbus.Array([], signature='s')) - self.start_daemon() - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - - with self.assertRaises(gi.repository.GLib.GError) as cm: - self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', ''))) - self.assertIn('AccessDenied', str(cm.exception)) - - self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced') - self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 0) - - self.stop_daemon() - - def test_intel_pstate_noturbo(self): - '''Intel P-State driver (balance)''' - - # Create CPU with preference - dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/") - os.makedirs(dir1) - with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov: - gov.write('powersave\n') - with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs: - prefs.write("performance\n") - - # Create Intel P-State configuration - 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("1\n") - with open(os.path.join(pstate_dir, "turbo_pct"),'w') as no_turbo: - no_turbo.write("0\n") - with open(os.path.join(pstate_dir, "status"),'w') as status: - status.write("active\n") - - self.start_daemon() - - profiles = self.get_dbus_property('Profiles') - self.assertEqual(len(profiles), 3) - self.assertEqual(self.get_dbus_property('PerformanceDegraded'), '') - - self.stop_daemon() - - def test_powerprofilesctl_error(self): - '''Check that powerprofilesctl returns 1 rather than an exception on error''' - - builddir = os.getenv('top_builddir', '.') - tool_path = os.path.join(builddir, 'src', 'powerprofilesctl') - - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'list'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'get'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'set', 'not-a-profile'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'list-holds'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'launch', '-p', 'power-saver', 'sleep', '1'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - - self.start_daemon() - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output([tool_path, 'set', 'not-a-profile'], - stderr=subprocess.PIPE, - universal_newlines=True) - self.assertNotIn('Traceback', cm.exception.stderr) - self.stop_daemon() - - # - # Helper methods - # - - @classmethod - def _props_to_str(cls, properties): - '''Convert a properties dictionary to uevent text representation.''' - - prop_str = '' - if properties: - for k, v in properties.items(): - prop_str += '%s=%s\n' % (k, v) - return prop_str - -if __name__ == '__main__': - # run ourselves under umockdev - if 'umockdev' not in os.environ.get('LD_PRELOAD', ''): - os.execvp('umockdev-wrapper', ['umockdev-wrapper'] + sys.argv) - - unittest.main() diff -Nru power-profiles-daemon-0.13/tests/integration_test.py power-profiles-daemon-0.21/tests/integration_test.py --- power-profiles-daemon-0.13/tests/integration_test.py 1970-01-01 00:00:00.000000000 +0000 +++ power-profiles-daemon-0.21/tests/integration_test.py 2024-04-03 23:55:02.000000000 +0000 @@ -0,0 +1,2305 @@ +#!/usr/bin/python3 + +# power-profiles-daemon integration test suite +# +# Run in built tree to test local built binaries, or from anywhere else to test +# system installed binaries. +# +# Copyright: (C) 2011 Martin Pitt +# (C) 2020 Bastien Nocera +# (C) 2021 David Redondo +# +# 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 2 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. + +import os +import subprocess +import signal +import sys +import tempfile +import time +import unittest + +import dbus + +try: + import gi + from gi.repository import GLib + from gi.repository import Gio +except ImportError as e: + sys.stderr.write( + f"Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: {str(e)}\n" + ) + sys.exit(77) + +try: + gi.require_version("UMockdev", "1.0") + from gi.repository import UMockdev +except ImportError: + sys.stderr.write("Skipping tests, umockdev not available.\n") + sys.stderr.write("(https://github.com/martinpitt/umockdev)\n") + sys.exit(77) + +try: + import dbusmock +except ImportError: + sys.stderr.write("Skipping tests, python-dbusmock not available.\n") + sys.stderr.write("(http://pypi.python.org/pypi/python-dbusmock)") + sys.exit(77) + + +# pylint: disable=too-many-public-methods,too-many-instance-attributes +class Tests(dbusmock.DBusTestCase): + """Dbus based integration unit tests""" + + PP = "org.freedesktop.UPower.PowerProfiles" + PP_PATH = "/org/freedesktop/UPower/PowerProfiles" + PP_INTERFACE = "org.freedesktop.UPower.PowerProfiles" + + @classmethod + def setUpClass(cls): + # run from local build tree if we are in one, otherwise use system instance + builddir = os.getenv("top_builddir", ".") + 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(f"Testing binaries from local build tree {cls.daemon_path}") + 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(f"Testing binaries from JHBuild {cls.daemon_path}") + else: + cls.daemon_path = None + with open( + "/usr/lib/systemd/system/power-profiles-daemon.service", + encoding="utf-8", + ) as tmpf: + for line in tmpf: + if line.startswith("ExecStart="): + cls.daemon_path = line.split("=", 1)[1].strip() + break + assert ( + cls.daemon_path + ), "could not determine daemon path from systemd .service file" + print(f"Testing installed system binary {cls.daemon_path}") + + # 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.start_system_bus() + cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + + def start_dbus_template(self, template, parameters): + process, dbus_object = self.spawn_server_template( + template, parameters, stdout=subprocess.PIPE + ) + + def stop_template(): + process.stdout.close() + try: + process.kill() + except OSError: + pass + process.wait() + + self.addCleanup(stop_template) + self.assertTrue(process) + self.assertTrue(dbus_object) + + return process, dbus_object, stop_template + + def setUp(self): + """Set up a local umockdev testbed. + + The testbed is initially empty. + """ + self.testbed = UMockdev.Testbed.new() + + def del_testbed(): + del self.testbed + + self.addCleanup(del_testbed) + self.proxy = None + self.props_proxy = None + self.log = None + self.daemon = None + self.changed_properties = {} + + # Used for dytc devices + self.tp_acpi = None + + self.polkitd, self.obj_polkit, _ = self.start_dbus_template("polkitd", {}) + self.obj_polkit.SetAllowed( + [ + "org.freedesktop.UPower.PowerProfiles.switch-profile", + "org.freedesktop.UPower.PowerProfiles.hold-profile", + ] + ) + + def run(self, result=None): + super().run(result) + if not result or not self.log: + return + if len(result.errors) + len(result.failures) or os.getenv("PPD_TEST_VERBOSE"): + with open(self.log.name, encoding="utf-8") as tmpf: + sys.stderr.write("\n-------------- daemon log: ----------------\n") + sys.stderr.write(tmpf.read()) + sys.stderr.write("------------------------------\n") + + # + # Daemon control and D-BUS I/O + # + + def start_daemon(self, args=None): + """Start daemon and create DBus proxy. + + When done, this sets self.proxy as the Gio.DBusProxy for power-profiles-daemon. + """ + env = os.environ.copy() + env["G_DEBUG"] = "fatal-criticals" + env["G_MESSAGES_DEBUG"] = "all" + # note: Python doesn't propagate the setenv from Testbed.new(), so we + # have to do that ourselves + env["UMOCKDEV_DIR"] = self.testbed.get_root_dir() + env["LD_PRELOAD"] = os.getenv("PPD_LD_PRELOAD") + " " + os.getenv("LD_PRELOAD") + self.log = tempfile.NamedTemporaryFile() # pylint: disable=consider-using-with + daemon_path = [self.daemon_path, "-vv"] + if args: + daemon_path += args + if os.getenv("PPD_TEST_WRAPPER"): + daemon_path = os.getenv("PPD_TEST_WRAPPER").split(" ") + daemon_path + elif os.getenv("VALGRIND"): + daemon_path = ["valgrind"] + daemon_path + + # pylint: disable=consider-using-with + self.daemon = subprocess.Popen( + daemon_path, env=env, stdout=self.log, stderr=sys.stderr + ) + self.addCleanup(self.stop_daemon, delete_profile=True) + + def on_proxy_connected(_, res): + try: + self.proxy = Gio.DBusProxy.new_finish(res) + print(f"Proxy to {self.proxy.get_name()} connected") + except GLib.Error as exc: + self.fail(exc) + + cancellable = Gio.Cancellable() + self.addCleanup(cancellable.cancel) + Gio.DBusProxy.new( + self.dbus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START, + None, + self.PP, + self.PP_PATH, + self.PP_INTERFACE, + cancellable, + on_proxy_connected, + ) + + # wait until the daemon gets online + wait_time = 20 if "valgrind" in daemon_path[0] else 5 + self.assert_eventually( + lambda: self.proxy and self.proxy.get_name_owner(), + timeout=wait_time * 1000, + message=lambda: f"daemon did not start in {wait_time} seconds: " + + f"proxy is {self.proxy} and owner " + + f"{self.proxy.get_name_owner() if self.proxy else 'None'}", + ) + + def properties_changed_cb(_, changed_properties, invalidated): + self.changed_properties.update(changed_properties.unpack()) + + self.addCleanup( + self.proxy.disconnect, + self.proxy.connect("g-properties-changed", properties_changed_cb), + ) + + self.assertEqual(self.daemon.poll(), None, "daemon crashed") + + def ensure_dbus_properties_proxies(self): + self.props_proxy = Gio.DBusProxy.new_sync( + self.dbus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START + | Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION + | Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES + | Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, + None, + self.PP, + self.PP_PATH, + "org.freedesktop.DBus.Properties", + None, + ) + + def stop_daemon(self, delete_profile=False): + """Stop the daemon if it is running.""" + + if self.daemon: + try: + self.daemon.terminate() + except OSError: + pass + self.assertEqual(self.daemon.wait(timeout=3000), 0) + + if delete_profile: + try: + os.remove(self.testbed.get_root_dir() + "/" + "ppd_test_conf.ini") + except (AttributeError, FileNotFoundError): + pass + + self.daemon = None + self.proxy = None + + def get_dbus_property(self, name): + """Get property value from daemon D-Bus interface.""" + self.ensure_dbus_properties_proxies() + return self.props_proxy.Get("(ss)", self.PP, name) + + def set_dbus_property(self, name, value): + """Set property value on daemon D-Bus interface.""" + self.ensure_dbus_properties_proxies() + return self.props_proxy.Set("(ssv)", self.PP, name, value) + + def call_dbus_method(self, name, parameters): + """Call a method of the daemon D-Bus interface.""" + return self.proxy.call_sync( + name, parameters, Gio.DBusCallFlags.NO_AUTO_START, -1, None + ) + + def have_text_in_log(self, text): + return self.count_text_in_log(text) > 0 + + def count_text_in_log(self, text): + with open(self.log.name, encoding="utf-8") as tmpf: + return tmpf.read().count(text) + + def read_file_contents(self, path): + """Get the contents of a file""" + with open(path, "rb") as tmpf: + return tmpf.read() + + def read_sysfs_file(self, path): + return self.read_file_contents( + self.testbed.get_root_dir() + "/" + path + ).rstrip() + + def read_sysfs_attr(self, device, attribute): + return self.read_sysfs_file(device + "/" + attribute) + + def get_mtime(self, device, attribute): + return os.path.getmtime( + self.testbed.get_root_dir() + "/" + device + "/" + attribute + ) + + def write_file_contents(self, path, contents): + """Set the contents of a file""" + with open(path, "wb") as tmpf: + return tmpf.write( + contents if isinstance(contents, bytes) else contents.encode("utf-8") + ) + + def change_immutable(self, fname, enable): + attr = "-" + if enable: + os.chmod(fname, 0o444) + self.addCleanup(self.change_immutable, fname, False) + attr = "+" + if os.geteuid() == 0: + if not GLib.find_program_in_path("chattr"): + self.skipTest("chattr is not found") + + subprocess.check_output(["chattr", f"{attr}i", fname]) + if not enable: + os.chmod(fname, 0o666) + + 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"], + ) + self.addCleanup(self.testbed.remove_device, self.tp_acpi) + + def create_amd_apu(self): + proc_dir = os.path.join(self.testbed.get_root_dir(), "proc/") + os.makedirs(proc_dir) + self.write_file_contents( + os.path.join(proc_dir, "cpuinfo"), "vendor_id : AuthenticAMD\n" + ) + + def create_empty_platform_profile(self): + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(acpi_dir) + self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "\n") + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), "\n" + ) + + def create_platform_profile(self): + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(acpi_dir, exist_ok=True) + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile"), "performance\n" + ) + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), + "low-power balanced performance\n", + ) + + def remove_platform_profile(self): + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.remove(os.path.join(acpi_dir, "platform_profile_choices")) + os.remove(os.path.join(acpi_dir, "platform_profile")) + os.removedirs(acpi_dir) + + def powerprofilesctl_path(self): + builddir = os.getenv("top_builddir", ".") + return os.path.join(builddir, "src", "powerprofilesctl") + + def python_coverage_commands(self): + coverage = os.getenv("PPD_PYTHON_COVERAGE") + if not coverage: + return [] + + builddir = os.getenv("top_builddir", ".") + data_file = os.path.join(builddir, "python-coverage", self.id() + ".coverage") + # We also may need to use "--parallel-mode" if running with + # meson test --repeat, but this is not a priority for now. + return [ + coverage, + "run", + f"--data-file={data_file}", + f"--include={builddir}/*", + ] + + def powerprofilesctl_command(self): + return self.python_coverage_commands() + [self.powerprofilesctl_path()] + + def assert_eventually(self, condition, message=None, timeout=5000, keep_checking=0): + """Assert that condition function eventually returns True. + + Timeout is in milliseconds, defaulting to 5000 (5 seconds). message is + printed on failure. + """ + if not keep_checking: + if condition(): + return + + done = False + + def on_timeout_reached(): + nonlocal done + done = True + + source = GLib.timeout_add(timeout, on_timeout_reached) + while not done: + if condition(): + GLib.source_remove(source) + if keep_checking > 0: + self.assert_condition_persists( + condition, message, timeout=keep_checking + ) + return + GLib.MainContext.default().iteration(False) + + self.fail(message() if message else f"timed out waiting for {condition}") + + def assert_condition_persists(self, condition, message=None, timeout=1000): + done = False + + def on_timeout_reached(): + nonlocal done + done = True + + source = GLib.timeout_add(timeout, on_timeout_reached) + while not done: + if not condition(): + GLib.source_remove(source) + self.fail( + message() if message else f"Condition is not persisting {condition}" + ) + GLib.MainContext.default().iteration(False) + + def assert_file_eventually_contains( + self, path, contents, timeout=800, keep_checking=0 + ): + """Asserts that file contents eventually matches expectations""" + encoded = contents.encode("utf-8") + return self.assert_eventually( + lambda: self.read_file_contents(path) == encoded, + timeout=timeout, + keep_checking=keep_checking, + message=lambda: f"file '{path}' does not contain '{contents}', " + + f"but '{self.read_file_contents(path)}'", + ) + + # pylint: disable=too-many-arguments + def assert_sysfs_attr_eventually_is( + self, device, attribute, contents, timeout=800, keep_checking=0 + ): + """Asserts that file contents eventually matches expectations""" + encoded = contents.encode("utf-8") + return self.assert_eventually( + lambda: self.read_sysfs_attr(device, attribute) == encoded, + timeout=timeout, + keep_checking=keep_checking, + message=lambda: f"file {device} '{attribute}' does not contain '{contents}', " + + f"but '{self.read_sysfs_attr(device, attribute)}'", + ) + + def assert_dbus_property_eventually_is( + self, prop, value, timeout=1200, keep_checking=0 + ): + """Asserts that a dbus property eventually is what expected""" + return self.assert_eventually( + lambda: self.get_dbus_property(prop) == value, + timeout=timeout, + keep_checking=keep_checking, + message=lambda: f"property '{prop}' is not '{value}', but " + + f"'{self.get_dbus_property(prop)}'", + ) + + # + # Actual test cases + # + def test_dbus_startup_error(self): + """D-Bus startup error""" + + self.start_daemon() + daemon_path = [self.daemon_path] + if os.getenv("PPD_TEST_WRAPPER"): + daemon_path = os.getenv("PPD_TEST_WRAPPER").split(" ") + daemon_path + out = subprocess.run( + daemon_path, + env={ + "LD_PRELOAD": os.getenv("PPD_LD_PRELOAD") + + " " + + os.getenv("LD_PRELOAD") + }, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + 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""" + + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "") + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + self.assertEqual(profiles[1]["Driver"], "placeholder") + self.assertEqual(profiles[1]["PlatformDriver"], "placeholder") + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + self.assertEqual(profiles[1]["Profile"], "balanced") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + with self.assertRaises(gi.repository.GLib.GError): + self.set_dbus_property( + "ActiveProfile", GLib.Variant.new_string("performance") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + with self.assertRaises(gi.repository.GLib.GError): + cookie = self.call_dbus_method( + "HoldProfile", + GLib.Variant("(sss)", ("performance", "testReason", "testApplication")), + ) + assert cookie + + self.stop_daemon() + + def test_inhibited_property(self): + """Test that the inhibited property exists""" + + 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("PerformanceInhibited"), "") + + def test_multi_degredation(self): + """Test handling of degradation from multiple drivers""" + self.create_dytc_device() + self.create_platform_profile() + + # Create CPU with preference + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + self.start_daemon() + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # Degraded CPU + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n") + self.assert_eventually( + lambda: self.have_text_in_log("File monitor change happened for ") + ) + + self.assertEqual( + self.get_dbus_property("PerformanceDegraded"), "high-operating-temperature" + ) + + # Degraded DYTC + lapmode = os.path.join( + self.testbed.get_root_dir(), "sys/devices/thinkpad_acpi/dytc_lapmode" + ) + self.write_file_contents(lapmode, "1\n") + self.assert_eventually(lambda: self.have_text_in_log("dytc_lapmode is now on")) + self.assertEqual( + self.get_dbus_property("PerformanceDegraded"), + "high-operating-temperature,lap-detected", + ) + + def test_degraded_transition(self): + """Test that transitions work as expected when degraded""" + + 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.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # Degraded + lapmode = os.path.join( + self.testbed.get_root_dir(), "sys/devices/thinkpad_acpi/dytc_lapmode" + ) + self.write_file_contents(lapmode, "1\n") + self.assert_eventually(lambda: self.have_text_in_log("dytc_lapmode is now on")) + self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "lap-detected") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # Switch to non-performance + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + def test_intel_pstate(self): + """Intel P-State driver (no UPower)""" + + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir2, "energy_performance_preference"), "performance\n" + ) + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + energy_prefs = os.path.join(dir2, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + self.assert_file_eventually_contains(energy_prefs, "performance") + + # Disable turbo + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n") + + self.assert_eventually( + lambda: self.have_text_in_log("File monitor change happened for ") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + self.assertEqual( + self.get_dbus_property("PerformanceDegraded"), "high-operating-temperature" + ) + + self.stop_daemon() + + # Verify that Lenovo DYTC and Intel P-State drivers are loaded + self.create_platform_profile() + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate") + self.assertEqual(profiles[0]["PlatformDriver"], "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(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + gov_path = os.path.join(dir1, "scaling_governor") + self.write_file_contents(gov_path, "performance\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + + self.start_daemon() + + self.assert_file_eventually_contains(gov_path, "powersave") + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + self.assert_file_eventually_contains( + os.path.join(dir1, "energy_performance_preference"), "balance_performance" + ) + + def test_intel_pstate_reapply_on_resume_from_sleep(self): + # Create CPU with preference + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + gov_path = os.path.join(dir1, "scaling_governor") + self.write_file_contents(gov_path, "performance\n") + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.write_file_contents(energy_prefs, "performance\n") + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + _, obj_logind, _ = self.start_dbus_template("logind", {}) + + self.start_daemon() + self.assert_dbus_property_eventually_is( + "ActiveProfile", "balanced", keep_checking=100 + ) + + # Simulate system changing to performance mode just before going to suspend + self.write_file_contents(energy_prefs, "performance\n") + self.assert_file_eventually_contains( + energy_prefs, "performance\n", keep_checking=500 + ) + + obj_logind.EmitSignal( + "org.freedesktop.login1.Manager", "PrepareForSleep", "b", [True] + ) + self.assert_file_eventually_contains( + energy_prefs, "performance\n", keep_checking=500 + ) + + # Check that on resume the value is reset to the expected one. + obj_logind.EmitSignal( + "org.freedesktop.login1.Manager", "PrepareForSleep", "b", [False] + ) + + self.assert_file_eventually_contains( + energy_prefs, "balance_performance", timeout=3000, keep_checking=100 + ) + + def test_intel_pstate_error(self): + """Intel P-State driver in error state""" + + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + pref_path = os.path.join(dir1, "energy_performance_preference") + old_umask = os.umask(0o333) + self.write_file_contents(pref_path, "balance_performance\n") + os.umask(old_umask) + # Make file non-writable to root + self.change_immutable(pref_path, True) + + self.start_daemon() + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # Error when setting performance mode + with self.assertRaises(gi.repository.GLib.GError): + self.set_dbus_property( + "ActiveProfile", GLib.Variant.new_string("performance") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "balance_performance\n") + + def test_intel_pstate_passive(self): + """Intel P-State in passive mode -> placeholder""" + + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + self.assertEqual(profiles[0]["Driver"], "placeholder") + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "performance\n") + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "performance\n") + + def test_intel_pstate_passive_with_epb(self): + """Intel P-State in passive mode (no HWP) with energy_perf_bias""" + + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpu0/power/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "energy_perf_bias"), "6") + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate") + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # Set power-saver mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + energy_perf_bias = os.path.join(dir2, "energy_perf_bias") + self.assert_file_eventually_contains(energy_perf_bias, "15") + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + self.assert_file_eventually_contains(energy_perf_bias, "0") + + def test_action_blocklist(self): + """Test action blocklist works""" + self.testbed.add_device( + "drm", + "card1-eDP", + None, + ["amdgpu/panel_power_savings", "0"], + ["DEVTYPE", "drm_connector"], + ) + + self.create_amd_apu() + + self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + + # Block panel_power action + self.start_daemon(["--block-action", "amdgpu_panel_power"]) + self.assertNotIn("amdgpu_panel_power", self.get_dbus_property("Actions")) + + def test_driver_blocklist(self): + """Test driver blocklist works""" + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + scaling_governor = os.path.join(dir1, "scaling_governor") + self.write_file_contents(scaling_governor, "powersave\n") + + prefs1 = os.path.join(dir1, "energy_performance_preference") + self.write_file_contents(prefs1, "performance\n") + + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + scaling_governor = os.path.join(dir2, "scaling_governor") + self.write_file_contents(scaling_governor, "powersave\n") + prefs2 = os.path.join( + dir2, + "energy_performance_preference", + ) + self.write_file_contents(prefs2, "prformance\n") + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # create ACPI platform profile + self.create_platform_profile() + profile = os.path.join( + self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile" + ) + self.assertNotEqual(profile, None) + + # desktop PM profile + dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir3, exist_ok=True) + self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n") + + # block platform profile + self.start_daemon(["--block-driver", "platform_profile"]) + # Verify that only amd-pstate is loaded + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate") + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + + self.stop_daemon() + + # block both drivers + self.start_daemon( + ["--block-driver", "amd_pstate", "--block-driver", "platform_profile"] + ) + # Verify that only placeholder is loaded + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + + # pylint: disable=too-many-statements + def test_multi_driver_flows(self): + """Test corner cases associated with multiple drivers""" + + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + prefs1 = os.path.join(dir1, "energy_performance_preference") + self.write_file_contents(prefs1, "performance\n") + + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n") + prefs2 = os.path.join(dir2, "energy_performance_preference") + self.write_file_contents(prefs2, "performance\n") + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # create ACPI platform profile + self.create_platform_profile() + profile = os.path.join( + self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile" + ) + + # desktop PM profile + dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir3, exist_ok=True) + self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n") + + self.start_daemon() + + # Verify that both drivers are loaded + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate") + self.assertEqual(profiles[0]["PlatformDriver"], "platform_profile") + + # test both drivers can switch to power-saver + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + # test both drivers can switch to performance + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # test both drivers can switch to balanced + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # test when CPU driver fails to write + self.change_immutable(prefs1, True) + with self.assertRaises(gi.repository.GLib.GError): + self.set_dbus_property( + "ActiveProfile", GLib.Variant.new_string("power-saver") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.assertEqual( + self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b"balanced" + ) + self.change_immutable(prefs1, False) + + # test when platform driver fails to write + self.change_immutable(profile, True) + with self.assertRaises(gi.repository.GLib.GError): + self.set_dbus_property( + "ActiveProfile", GLib.Variant.new_string("power-saver") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # make sure CPU was undone since platform failed + self.assertEqual( + self.read_sysfs_file( + "sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference" + ), + b"balance_performance", + ) + self.assertEqual( + self.read_sysfs_file( + "sys/devices/system/cpu/cpufreq/policy1/energy_performance_preference" + ), + b"balance_performance", + ) + + # pylint: disable=too-many-statements + def test_amd_pstate(self): + """AMD P-State driver (no UPower)""" + + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir2, "energy_performance_preference"), "performance\n" + ) + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # desktop PM profile + dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir3) + self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + energy_prefs = os.path.join(dir2, "energy_performance_preference") + scaling_governor = os.path.join(dir2, "scaling_governor") + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + self.assert_file_eventually_contains(scaling_governor, "powersave") + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + self.assert_file_eventually_contains(energy_prefs, "performance") + self.assert_file_eventually_contains(scaling_governor, "performance") + + # Set powersave mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + self.assert_file_eventually_contains(energy_prefs, "power") + self.assert_file_eventually_contains(scaling_governor, "powersave") + + def test_amd_pstate_balance(self): + """AMD P-State driver (balance)""" + + # Create CPU with preference + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + gov_path = os.path.join(dir1, "scaling_governor") + self.write_file_contents(gov_path, "performance\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # desktop PM profile + dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n") + + self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + # This matches what's written by ppd-driver-amd-pstate.c + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + scaling_governor = os.path.join(dir1, "scaling_governor") + self.assert_file_eventually_contains(scaling_governor, "powersave") + + def test_amd_pstate_error(self): + """AMD P-State driver in error state""" + + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + pref_path = os.path.join(dir1, "energy_performance_preference") + old_umask = os.umask(0o333) + self.write_file_contents(pref_path, "balance_performance\n") + os.umask(old_umask) + # Make file non-writable to root + self.change_immutable(pref_path, True) + + # desktop PM profile + dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n") + + self.start_daemon() + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # Error when setting performance mode + with self.assertRaises(gi.repository.GLib.GError): + self.set_dbus_property( + "ActiveProfile", GLib.Variant.new_string("performance") + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "balance_performance\n") + + def test_amd_pstate_passive(self): + """AMD P-State in passive mode -> placeholder""" + + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n") + + # desktop PM profile + dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + self.assertEqual(profiles[0]["Driver"], "placeholder") + self.assertEqual(profiles[0]["PlatformDriver"], "placeholder") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + self.assert_file_eventually_contains(energy_prefs, "performance\n") + + # Set performance mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + self.assert_file_eventually_contains(energy_prefs, "performance\n") + + def test_amd_pstate_server(self): + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir2, "energy_performance_preference"), "performance\n" + ) + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # server PM profile + dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir3) + self.write_file_contents(os.path.join(dir3, "pm_profile"), "4\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + with self.assertRaises(KeyError): + print(profiles[0]["CpuDriver"]) + + def test_dytc_performance_driver(self): + """Lenovo DYTC performance driver""" + + 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"], "platform_profile") + self.assertEqual(profiles[0]["PlatformDriver"], "platform_profile") + self.assertEqual(profiles[0]["Profile"], "power-saver") + self.assertEqual(profiles[2]["PlatformDriver"], "platform_profile") + self.assertEqual(profiles[2]["Profile"], "performance") + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # lapmode detected + lapmode = os.path.join( + self.testbed.get_root_dir(), "sys/devices/thinkpad_acpi/dytc_lapmode" + ) + self.write_file_contents(lapmode, "1\n") + self.assert_dbus_property_eventually_is("PerformanceDegraded", "lap-detected") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # Reset lapmode + self.write_file_contents(lapmode, "0\n") + self.assert_dbus_property_eventually_is("PerformanceDegraded", "") + + # Performance mode didn't change + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + # Switch to power-saver mode + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assert_eventually( + lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") + == b"low-power" + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + # And mimic a user pressing a Fn+H + platform_profile = os.path.join( + self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile" + ) + self.write_file_contents(platform_profile, "performance\n") + self.assert_dbus_property_eventually_is("ActiveProfile", "performance") + + def test_fake_driver(self): + """Test that the fake driver works""" + + os.environ["POWER_PROFILE_DAEMON_FAKE_DRIVER"] = "1" + self.start_daemon() + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.stop_daemon() + + del os.environ["POWER_PROFILE_DAEMON_FAKE_DRIVER"] + self.start_daemon() + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + + def test_amd_pstate_upower(self): + """Switching between balance_power and balance_performance based on battery""" + # Create 2 CPUs with preferences + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + dir2 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/" + ) + os.makedirs(dir2) + self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir2, "energy_performance_preference"), "performance\n" + ) + + # Create AMD P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + # desktop PM profile + dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(dir3) + self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n") + + _, _, stop_upowerd = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": True}, + ) + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + + self.assertEqual(profiles[0]["Driver"], "multiple") + self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate") + self.assertEqual(profiles[0]["Profile"], "power-saver") + + energy_prefs = os.path.join(dir2, "energy_performance_preference") + scaling_governor = os.path.join(dir2, "scaling_governor") + + self.assert_file_eventually_contains(energy_prefs, "balance_power") + self.assert_file_eventually_contains(scaling_governor, "powersave") + + stop_upowerd() + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + _, upowerd_obj, stop_upowerd = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", True) + self.assert_file_eventually_contains(energy_prefs, "balance_power") + + # Ensure that changing some other property doesn't change the state. + upowerd_obj.Set("org.freedesktop.UPower", "LidIsClosed", True) + self.assert_file_eventually_contains( + energy_prefs, "balance_power", keep_checking=800 + ) + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", False) + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + self.stop_daemon() + + # start upower after the daemon + stop_upowerd() + + self.start_daemon() + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + _, upowerd_obj, _ = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", True) + self.assert_file_eventually_contains(energy_prefs, "balance_power") + + def test_amdgpu_panel_power(self): + """Verify AMDGPU Panel power actions""" + amdgpu_panel_power_savings = "amdgpu/panel_power_savings" + edp = self.testbed.add_device( + "drm", + "card1-eDP", + None, + ["status", "connected\n", amdgpu_panel_power_savings, "0"], + ["DEVTYPE", "drm_connector"], + ) + + self.create_amd_apu() + + self.start_daemon() + + self.assertIn("amdgpu_panel_power", self.get_dbus_property("Actions")) + + # verify it hasn't been updated yet due to missing upower + self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "0") + + # start upower and try again + self.stop_daemon() + self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": True}, + ) + self.start_daemon() + + # verify balanced updated it + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced")) + self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "1") + + # verify power saver updated it + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "3") + + # add another device that supports the feature + edp2 = self.testbed.add_device( + "drm", + "card2-eDP", + None, + ["status", "connected\n", amdgpu_panel_power_savings, "0"], + ["DEVTYPE", "drm_connector"], + ) + + # verify power saver got updated for it + self.assert_sysfs_attr_eventually_is(edp2, amdgpu_panel_power_savings, "3") + + # add another device that supports the feature, but panel is disconnected + edp3 = self.testbed.add_device( + "drm", + "card3-eDP", + None, + ["status", "disconnected\n", amdgpu_panel_power_savings, "0"], + ["DEVTYPE", "drm_connector"], + ) + + # verify power saver didn't get updated for it + self.assert_sysfs_attr_eventually_is(edp3, amdgpu_panel_power_savings, "0") + + def test_trickle_charge_system(self): + """Trickle power_supply charge type""" + + fastcharge = self.testbed.add_device( + "power_supply", + "bq24190-charger", + None, + ["charge_type", "Trickle", "scope", "System"], + [], + ) + + self.start_daemon() + + self.assertIn("trickle_charge", self.get_dbus_property("Actions")) + + # Verify that charge-type stays untouched + self.assertEqual(self.read_sysfs_attr(fastcharge, "charge_type"), b"Trickle") + + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assertEqual(self.read_sysfs_attr(fastcharge, "charge_type"), b"Trickle") + + def test_trickle_charge_mode_no_change(self): + """Trickle power_supply charge type""" + + fastcharge = self.testbed.add_device( + "power_supply", + "MFi Fastcharge", + None, + ["charge_type", "Fast", "scope", "Device"], + [], + ) + + mtime = self.get_mtime(fastcharge, "charge_type") + self.start_daemon() + + self.assertIn("trickle_charge", self.get_dbus_property("Actions")) + + # Verify that charge-type didn't get touched + self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast") + self.assertEqual(self.get_mtime(fastcharge, "charge_type"), mtime) + + def test_trickle_charge_mode(self): + """Trickle power_supply charge type""" + + idevice = self.testbed.add_device( + "usb", + "iDevice", + None, + [], + ["ID_MODEL", "iDevice", "DRIVER", "apple-mfi-fastcharge"], + ) + fastcharge = self.testbed.add_device( + "power_supply", + "MFi Fastcharge", + idevice, + ["charge_type", "Trickle", "scope", "Device"], + [], + ) + + self.start_daemon() + + self.assertIn("trickle_charge", self.get_dbus_property("Actions")) + + # Verify that charge-type got changed to Fast on startup + self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast") + + # Verify that charge-type got changed to Trickle when power saving + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Trickle") + + # FIXME no performance mode + # Verify that charge-type got changed to Fast in a non-default, non-power save mode + # self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance')) + # self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast") + + def test_platform_driver_late_load(self): + """Test that we can handle the platform_profile driver getting loaded late""" + self.create_empty_platform_profile() + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 2) + + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), + "low-power\nbalanced\nperformance\n", + ) + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile"), "performance\n" + ) + + # Wait for profiles to get reloaded + self.assert_eventually(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"), "balanced") + self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "") + + 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) + self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "cool\n") + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), + "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]["PlatformDriver"], "platform_profile") + self.assertEqual(profiles[0]["Profile"], "power-saver") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.assertEqual( + self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b"cool" + ) + 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.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance")) + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced")) + self.assertEqual( + self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b"balanced" + ) + + def test_quiet(self): + # Uses quiet instead of low-power + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + os.makedirs(acpi_dir) + self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "quiet\n") + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), + "quiet balanced balanced-performance 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]["PlatformDriver"], "platform_profile") + self.assertEqual(profiles[0]["Profile"], "power-saver") + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.assertEqual( + self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b"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"quiet" + ) + + def test_hold_release_profile(self): + self.create_platform_profile() + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + + cookie = self.call_dbus_method( + "HoldProfile", + GLib.Variant("(sss)", ("performance", "testReason", "testApplication")), + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "performance" + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfileHolds") + == [ + { + "ApplicationId": "testApplication", + "Profile": "performance", + "Reason": "testReason", + } + ] + ) + + released_cookie = None + + def signal_cb(_, sender, signal_name, params): + nonlocal released_cookie + if signal_name == "ProfileReleased": + released_cookie = params + + self.addCleanup( + self.proxy.disconnect, self.proxy.connect("g-signal", signal_cb) + ) + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + profile_holds = self.get_dbus_property("ActiveProfileHolds") + self.assertEqual(len(profile_holds), 1) + self.assertEqual(profile_holds[0]["Profile"], "performance") + self.assertEqual(profile_holds[0]["Reason"], "testReason") + self.assertEqual(profile_holds[0]["ApplicationId"], "testApplication") + + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", cookie)) + self.assert_eventually(lambda: released_cookie == cookie) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "balanced" + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfileHolds") == [] + ) + profile_holds = self.get_dbus_property("ActiveProfileHolds") + self.assertEqual(len(profile_holds), 0) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # When the profile is changed manually, holds should be released a + self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 1) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "performance" + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced")) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "balanced" + ) + self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 0) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # When all holds are released, the last manually selected profile should be activated + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "power-saver" + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfileHolds") + == [ + { + "ApplicationId": "", + "Profile": "performance", + "Reason": "", + } + ] + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "performance" + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", cookie)) + self.assert_eventually(lambda: released_cookie == cookie) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfileHolds") == [] + ) + self.assert_eventually( + lambda: self.changed_properties.get("ActiveProfile") == "power-saver" + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + def test_launch_arguments_redirection(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with tempfile.NamedTemporaryFile(mode="+rt") as tmpf: + subprocess.check_call( + self.powerprofilesctl_command() + + [ + "launch", + "sh", + "-c", + f'echo "$@" > {tmpf.name}', + "--", + "--foo", + "--bar", + "-v", + "arg", + ] + ) + self.assertEqual(tmpf.readlines(), ["--foo --bar -v arg\n"]) + + def test_unknown_action(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with self.assertRaises(subprocess.CalledProcessError): + tool_cmd = self.powerprofilesctl_command() + subprocess.check_output( + tool_cmd + ["hopefully-invalid-action"], stderr=subprocess.PIPE + ) + + def test_unknown_list_argument(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_output( + self.powerprofilesctl_command() + ["list", "--invalid-argument"], + stderr=subprocess.PIPE, + ) + + def test_launch_arguments_invalid(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with self.assertRaises(subprocess.CalledProcessError): + tool_cmd = self.powerprofilesctl_command() + subprocess.check_output( + tool_cmd + ["--foo-arg", "launch", "true"], stderr=subprocess.PIPE + ) + + def test_launch_with_command_failure(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + tool_cmd = self.powerprofilesctl_command() + cmd = subprocess.run(tool_cmd + ["launch", "false"], check=False) + self.assertEqual(cmd.returncode, 1) + + cmd = subprocess.run(tool_cmd + ["launch", "sh", "-c", "exit 55"], check=False) + self.assertEqual(cmd.returncode, 55) + + def test_launch_with_command_signaled(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + tool_cmd = self.powerprofilesctl_command() + cmd = subprocess.run( + tool_cmd + ["launch", "sh", "-c", f"kill -{signal.SIGKILL} $$"], check=False + ) + self.assertEqual(cmd.returncode, -signal.SIGKILL) + + cmd = subprocess.run( + tool_cmd + ["launch", "sh", "-c", f"kill -{signal.SIGINT} $$"], check=False + ) + self.assertEqual(cmd.returncode, -signal.SIGINT) + + def test_vanishing_hold(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + tool_cmd = self.powerprofilesctl_command() + with subprocess.Popen( + tool_cmd + ["launch", "-p", "power-saver", "sleep", "3600"], + stdout=sys.stdout, + stderr=sys.stderr, + ) as launch_process: + self.assertTrue(launch_process) + time.sleep(1) + holds = self.get_dbus_property("ActiveProfileHolds") + self.assertEqual(len(holds), 1) + hold = holds[0] + self.assertEqual(hold["Profile"], "power-saver") + + # Make sure to handle vanishing clients + launch_process.terminate() + retcode = launch_process.wait() + self.assertEqual(retcode, -signal.SIGTERM) + + holds = self.get_dbus_property("ActiveProfileHolds") + self.assertEqual(len(holds), 0) + + def test_launch_sigint_wrapper(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with subprocess.Popen( + self.powerprofilesctl_command() + ["launch", "sleep", "3600"], + ) as launch_process: + time.sleep(1) + launch_process.send_signal(signal.SIGINT) + retcode = launch_process.wait() + self.assertEqual(retcode, -signal.SIGINT) + + def test_launch_sigabrt_wrapper(self): + self.create_platform_profile() + self.start_daemon() + self.assert_eventually(lambda: self.get_dbus_property("ActiveProfile")) + + with subprocess.Popen( + self.powerprofilesctl_command() + ["launch", "sleep", "3600"], + ) as launch_process: + time.sleep(1) + launch_process.send_signal(signal.SIGABRT) + retcode = launch_process.wait() + self.assertEqual(retcode, -signal.SIGABRT) + + def test_hold_priority(self): + """power-saver should take priority over performance""" + 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") + + # Test every order of holding and releasing power-saver and performance + # hold performance and then power-saver, release in the same order + performance_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + powersaver_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # hold performance and then power-saver, but release power-saver first + performance_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + powersaver_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # hold power-saver and then performance, release in the same order + powersaver_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + performance_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + # hold power-saver and then performance, but release performance first + powersaver_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + performance_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie)) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + def test_save_profile(self): + """save profile across runs""" + + self.create_platform_profile() + + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.stop_daemon() + + # sys.stderr.write('\n-------------- config file: ----------------\n') + # with open(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini') as tmpf: + # sys.stderr.write(tmpf.read()) + # sys.stderr.write('------------------------------\n') + + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + # Programmatically set profile aren't saved + performance_cookie = self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertTrue(performance_cookie) + self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance") + self.stop_daemon() + + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + def test_save_deferred_load(self): + """save profile across runs, but kernel driver loaded after start""" + + self.create_platform_profile() + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver")) + self.stop_daemon() + self.remove_platform_profile() + + # We could verify the contents of the configuration file here + + self.create_empty_platform_profile() + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/") + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile_choices"), + "low-power\nbalanced\nperformance\n", + ) + self.write_file_contents( + os.path.join(acpi_dir, "platform_profile"), "performance\n" + ) + + self.assert_dbus_property_eventually_is("ActiveProfile", "power-saver") + + def test_not_allowed_profile(self): + """Check that we get errors when trying to change a profile and not allowed""" + + self.obj_polkit.SetAllowed(dbus.Array([], signature="s")) + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + proxy = Gio.DBusProxy.new_sync( + self.dbus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START, + None, + self.PP, + self.PP_PATH, + "org.freedesktop.DBus.Properties", + None, + ) + with self.assertRaises(gi.repository.GLib.GError) as error: + proxy.Set( + "(ssv)", + self.PP, + "ActiveProfile", + GLib.Variant.new_string("power-saver"), + ) + self.assertIn("AccessDenied", str(error.exception)) + + def test_not_allowed_hold(self): + """Check that we get an error when trying to hold a profile and not allowed""" + + self.obj_polkit.SetAllowed(dbus.Array([], signature="s")) + self.start_daemon() + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + with self.assertRaises(gi.repository.GLib.GError) as error: + self.call_dbus_method( + "HoldProfile", GLib.Variant("(sss)", ("performance", "", "")) + ) + self.assertIn("AccessDenied", str(error.exception)) + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 0) + + def test_get_version_prop(self): + """Checks that the version property is advertised""" + self.start_daemon() + self.assertTrue(self.get_dbus_property("Version")) + + def test_intel_pstate_upower(self): + # Create CPU with preference + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n") + self.write_file_contents(os.path.join(pstate_dir, "turbo_pct"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + _, _, stop_upowerd = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": True}, + ) + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "") + + energy_prefs = os.path.join(dir1, "energy_performance_preference") + scaling_governor = os.path.join(dir1, "scaling_governor") + + self.assert_file_eventually_contains(energy_prefs, "balance_power") + self.assert_file_eventually_contains(scaling_governor, "powersave") + + stop_upowerd() + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + _, upowerd_obj, stop_upowerd = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", True) + self.assert_file_eventually_contains(energy_prefs, "balance_power") + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", False) + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + self.stop_daemon() + + # start upower after the daemon + stop_upowerd() + + self.start_daemon() + + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + _, upowerd_obj, _ = self.start_dbus_template( + "upower", + {"DaemonVersion": "0.99", "OnBattery": False}, + ) + self.assert_file_eventually_contains(energy_prefs, "balance_performance") + + upowerd_obj.Set("org.freedesktop.UPower", "OnBattery", True) + self.assert_file_eventually_contains(energy_prefs, "balance_power") + + def test_intel_pstate_noturbo(self): + """Intel P-State driver (balance)""" + + # Create CPU with preference + dir1 = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/" + ) + os.makedirs(dir1) + self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n") + self.write_file_contents( + os.path.join(dir1, "energy_performance_preference"), "performance\n" + ) + + # Create Intel P-State configuration + pstate_dir = os.path.join( + self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate" + ) + os.makedirs(pstate_dir) + self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n") + self.write_file_contents(os.path.join(pstate_dir, "turbo_pct"), "0\n") + self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n") + + self.start_daemon() + + profiles = self.get_dbus_property("Profiles") + self.assertEqual(len(profiles), 3) + self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "") + + def test_powerprofilesctl_version_command(self): + """Check powerprofilesctl version command works""" + + self.start_daemon() + + cmd = subprocess.run(self.powerprofilesctl_command() + ["version"], check=True) + self.assertEqual(cmd.returncode, 0) + + def test_powerprofilesctl_list_command(self): + """Check powerprofilesctl list command works""" + + self.start_daemon() + + tool_cmd = self.powerprofilesctl_command() + cmd = subprocess.run(tool_cmd + ["list"], capture_output=True, check=True) + self.assertEqual(cmd.returncode, 0) + self.assertIn("* balanced", cmd.stdout.decode("utf-8")) + + def test_powerprofilesctl_set_get_commands(self): + """Check powerprofilesctl set/get command works""" + + self.start_daemon() + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced") + + tool_cmd = self.powerprofilesctl_command() + cmd = subprocess.run(tool_cmd + ["get"], capture_output=True, check=True) + self.assertEqual(cmd.returncode, 0) + self.assertEqual(cmd.stdout, b"balanced\n") + + cmd = subprocess.run( + tool_cmd + ["set", "power-saver"], capture_output=True, check=True + ) + self.assertEqual(cmd.returncode, 0) + + self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver") + + cmd = subprocess.run(tool_cmd + ["get"], capture_output=True, check=True) + self.assertEqual(cmd.returncode, 0) + self.assertEqual(cmd.stdout, b"power-saver\n") + + def test_powerprofilesctl_error(self): + """Check that powerprofilesctl returns 1 rather than an exception on error""" + + tool_cmd = self.powerprofilesctl_command() + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["list"], stderr=subprocess.PIPE, universal_newlines=True + ) + self.assertNotIn("Traceback", error.exception.stderr) + + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["get"], stderr=subprocess.PIPE, universal_newlines=True + ) + self.assertNotIn("Traceback", error.exception.stderr) + + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["set", "not-a-profile"], + stderr=subprocess.PIPE, + universal_newlines=True, + ) + self.assertNotIn("Traceback", error.exception.stderr) + + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["list-holds"], + stderr=subprocess.PIPE, + universal_newlines=True, + ) + self.assertNotIn("Traceback", error.exception.stderr) + + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["launch", "-p", "power-saver", "sleep", "1"], + stderr=subprocess.PIPE, + universal_newlines=True, + ) + self.assertNotIn("Traceback", error.exception.stderr) + + self.start_daemon() + with self.assertRaises(subprocess.CalledProcessError) as error: + subprocess.check_output( + tool_cmd + ["set", "not-a-profile"], + stderr=subprocess.PIPE, + universal_newlines=True, + ) + self.assertNotIn("Traceback", error.exception.stderr) + + # + # Helper methods + # + + @classmethod + def _props_to_str(cls, properties): + """Convert a properties dictionary to uevent text representation.""" + + prop_str = "" + if properties: + for key, val in properties.items(): + prop_str += f"{key}={val}\n" + return prop_str + + +class LegacyDBusNameTests(Tests): + """This will repeats all the tests in the Tests class using the legacy dbus name""" + + PP = "net.hadess.PowerProfiles" + PP_PATH = "/net/hadess/PowerProfiles" + PP_INTERFACE = "net.hadess.PowerProfiles" + + +if __name__ == "__main__": + # run ourselves under umockdev + if "umockdev" not in os.environ.get("LD_PRELOAD", ""): + os.execvp("umockdev-wrapper", ["umockdev-wrapper", sys.executable] + sys.argv) + + prog = unittest.main(exit=False) + if prog.result.errors or prog.result.failures: + sys.exit(1) + + # Translate to skip error + if prog.result.testsRun == len(prog.result.skipped): + sys.exit(77) diff -Nru power-profiles-daemon-0.13/tests/meson.build power-profiles-daemon-0.21/tests/meson.build --- power-profiles-daemon-0.13/tests/meson.build 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/tests/meson.build 2024-04-03 23:55:02.000000000 +0000 @@ -1,18 +1,95 @@ envs = environment() -envs.set ('top_builddir', meson.build_root()) -envs.set ('top_srcdir', meson.source_root()) +envs.set('PPD_TEST_VERBOSE', 'true') +envs.set ('top_builddir', meson.project_build_root()) -python3 = find_program('python3') unittest_inspector = find_program('unittest_inspector.py') -r = run_command(unittest_inspector, files('integration-test.py'), check: true) +integration_tests = files('integration_test.py') +r = run_command(python3, unittest_inspector, integration_tests, check: true) unit_tests = r.stdout().strip().split('\n') +valgrind = find_program('valgrind', required: false) +if valgrind.found() + glib_share = glib_dep.get_variable('prefix') / 'share' / glib_dep.name() + glib_suppressions = glib_share + '/valgrind/glib.supp' + libfprint_wrapper = [ + valgrind.full_path(), + '--tool=memcheck', + '--leak-check=full', + '--leak-resolution=high', + '--error-exitcode=1', + '--errors-for-leak-kinds=definite', + '--track-origins=yes', + '--show-leak-kinds=definite,possible', + '--show-error-list=yes', + '--gen-suppressions=all', + '--suppressions=' + glib_suppressions, + ] + add_test_setup('valgrind', + timeout_multiplier: 5, + env: [ + 'G_SLICE=always-malloc', + 'UNDER_VALGRIND=1', + 'PPD_TEST_WRAPPER=' + ' '.join(libfprint_wrapper), + ]) +endif + +preloaded_libs = [] +ppd_tests_ld_preload = [] + +if address_sanitizer + # ASAN has to be the first in list + preloaded_libs += 'asan' +endif + +foreach libname: preloaded_libs + lib = run_command(meson.get_compiler('c'), + '-print-file-name=lib@0@.so'.format(libname), + check: true, + ).stdout().strip() + + # Support linker script files + if run_command('grep', '-qI', '^INPUT', files(lib), check: false).returncode() == 0 + out = run_command('cat', lib, check: true).stdout() + lib = out.split('(')[1].split(')')[0].strip() + endif + + if lib != '' and lib[0] == '/' + message('Found library @0@ as @1@'.format(libname, lib)) + ppd_tests_ld_preload += '@0@'.format(files(lib)[0]) + else + tests = [] + warning('No library found for ' + libname + ', skipping PAM tests') + endif +endforeach + +envs.set('PPD_LD_PRELOAD', ' '.join(ppd_tests_ld_preload)) + +coverage_args = [] +python3_coverage = find_program([ + 'python3-coverage', + 'coverage3', + 'coverage', + ], required: false) +if python3_coverage.found() + envs.set('PPD_PYTHON_COVERAGE', python3_coverage.full_path()) +endif + foreach ut: unit_tests - ut_args = files('integration-test.py') - ut_args += ut test(ut, python3, - args: ut_args, + args: [ + integration_tests, + ut, + ], env: envs, ) endforeach + +if pylint.found() + integration_pylint_flags = ['-d', 'W0511', '-d', 'C0302'] + pylint_flags + test('pylint-integration-tests', + pylint, + args: integration_pylint_flags + integration_tests, + env: nomalloc, + ) +endif diff -Nru power-profiles-daemon-0.13/tests/unittest_inspector.py power-profiles-daemon-0.21/tests/unittest_inspector.py --- power-profiles-daemon-0.13/tests/unittest_inspector.py 2023-04-26 12:53:07.000000000 +0000 +++ power-profiles-daemon-0.21/tests/unittest_inspector.py 2024-04-03 23:55:02.000000000 +0000 @@ -22,23 +22,25 @@ import os import unittest + def list_tests(module): tests = [] for name, obj in inspect.getmembers(module): if inspect.isclass(obj) and issubclass(obj, unittest.TestCase): cases = unittest.defaultTestLoader.getTestCaseNames(obj) - tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ] + tests += [(obj, "{}.{}".format(name, t)) for t in cases] return tests -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('unittest_source', type=argparse.FileType('r')) + parser.add_argument("unittest_source", type=argparse.FileType("r")) args = parser.parse_args() source_path = args.unittest_source.name spec = importlib.util.spec_from_file_location( - os.path.basename(source_path), source_path) + os.path.basename(source_path), source_path + ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module)