From 1fa3d6051ae6ac27e0e92c8152be743e4cf3a2fa Mon Sep 17 00:00:00 2001 From: "Can H. Tartanoglu" Date: Thu, 13 Aug 2026 07:06:52 +0200 Subject: [PATCH] Hold power profile through power-profiles-daemon --- daemon/gamemode-context.c | 34 +++++++-- daemon/gamemode-dbus.c | 149 ++++++++++++++++++++++++++++++++++++++ daemon/gamemode.h | 9 +++ example/gamemode.ini | 6 +- 4 files changed, 190 insertions(+), 8 deletions(-) diff --git a/daemon/gamemode-context.c b/daemon/gamemode-context.c index f0524936..878ac20c 100644 --- a/daemon/gamemode-context.c +++ b/daemon/gamemode-context.c @@ -91,6 +91,7 @@ struct GameModeContext { char initial_profile[64]; enum GameModeProfile current_profile; + GameModePowerProfileHold *profile_hold; struct GameModeGPUInfo *stored_gpu; /**profile_hold) { + game_mode_destroy_power_profile_hold(self->profile_hold); + self->profile_hold = NULL; + self->current_profile = prof; + return 0; + } config_get_default_profile(self->config, prof_config_str); prof_str = prof_config_str[0] != '\0' ? prof_config_str : self->initial_profile; break; - case GAME_MODE_PROFILE_DESIRED: + case GAME_MODE_PROFILE_DESIRED: { config_get_desired_profile(self->config, prof_config_str); prof_str = prof_config_str[0] != '\0' ? prof_config_str : "performance"; + + char default_profile[CONFIG_VALUE_MAX] = { 0 }; + config_get_default_profile(self->config, default_profile); + if (default_profile[0] == '\0' && + (strcmp(prof_str, "performance") == 0 || strcmp(prof_str, "power-saver") == 0)) { + enum GameModePowerProfileHoldResult result = + game_mode_create_power_profile_hold(prof_str, &self->profile_hold); + if (result == GAME_MODE_POWER_PROFILE_HELD) { + self->current_profile = prof; + return 0; + } + if (result == GAME_MODE_POWER_PROFILE_FAILED) + return -1; + } break; + } default: assert(!"Invalid platform profile requested"); } + if (!profile_exists()) { + LOG_MSG("Setting platform profile unsupported; skipping\n"); + return 0; + } + const char *const exec_args[] = { "pkexec", LIBEXECDIR "/platprofctl", "set", prof_str, NULL, }; diff --git a/daemon/gamemode-dbus.c b/daemon/gamemode-dbus.c index b00ccb65..d41a338e 100644 --- a/daemon/gamemode-dbus.c +++ b/daemon/gamemode-dbus.c @@ -832,3 +832,152 @@ void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor) sd_bus_unrefp(&inhibitor->bus); free(inhibitor); } + +struct GameModePowerProfileHold { + sd_bus *bus; + uint32_t cookie; + const char *destination; + const char *path; + const char *interface; +}; + +struct PowerProfilesEndpoint { + const char *destination; + const char *path; + const char *interface; +}; + +static const struct PowerProfilesEndpoint power_profiles_endpoints[] = { + { + "org.freedesktop.UPower.PowerProfiles", + "/org/freedesktop/UPower/PowerProfiles", + "org.freedesktop.UPower.PowerProfiles", + }, + { + "net.hadess.PowerProfiles", + "/net/hadess/PowerProfiles", + "net.hadess.PowerProfiles", + }, +}; + +static bool power_profiles_api_unavailable(const sd_bus_error *error) +{ + return sd_bus_error_has_name(error, "org.freedesktop.DBus.Error.ServiceUnknown") || + sd_bus_error_has_name(error, "org.freedesktop.DBus.Error.NameHasNoOwner") || + sd_bus_error_has_name(error, "org.freedesktop.DBus.Error.UnknownObject") || + sd_bus_error_has_name(error, "org.freedesktop.DBus.Error.UnknownInterface") || + sd_bus_error_has_name(error, "org.freedesktop.DBus.Error.UnknownMethod"); +} + +enum GameModePowerProfileHoldResult game_mode_create_power_profile_hold( + const char *profile, GameModePowerProfileHold **hold) +{ + sd_bus *bus_local = NULL; + *hold = NULL; + + int ret = sd_bus_open_system(&bus_local); + if (ret < 0) { + LOG_ERROR("Could not connect to system bus for power profile hold: %s\n", strerror(-ret)); + return GAME_MODE_POWER_PROFILE_FAILED; + } + + for (size_t i = 0; i < sizeof(power_profiles_endpoints) / sizeof(power_profiles_endpoints[0]); + i++) { + const struct PowerProfilesEndpoint *endpoint = &power_profiles_endpoints[i]; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + + ret = sd_bus_call_method(bus_local, + endpoint->destination, + endpoint->path, + endpoint->interface, + "HoldProfile", + &error, + &reply, + "sss", + profile, + "GameMode is active", + "com.feralinteractive.GameMode"); + if (ret < 0) { + bool unavailable = power_profiles_api_unavailable(&error); + if (!unavailable) { + LOG_ERROR("Failed to hold power profile through %s: %s\n", + endpoint->destination, + error.message ? error.message : strerror(-ret)); + } + sd_bus_message_unref(reply); + sd_bus_error_free(&error); + if (unavailable) + continue; + + sd_bus_close(bus_local); + sd_bus_unref(bus_local); + return GAME_MODE_POWER_PROFILE_FAILED; + } + + uint32_t cookie = 0; + ret = sd_bus_message_read(reply, "u", &cookie); + sd_bus_message_unref(reply); + sd_bus_error_free(&error); + if (ret < 0) { + LOG_ERROR("Invalid HoldProfile response from %s: %s\n", + endpoint->destination, + strerror(-ret)); + sd_bus_close(bus_local); + sd_bus_unref(bus_local); + return GAME_MODE_POWER_PROFILE_FAILED; + } + + GameModePowerProfileHold *profile_hold = malloc(sizeof(*profile_hold)); + if (!profile_hold) { + sd_bus_close(bus_local); + sd_bus_unref(bus_local); + return GAME_MODE_POWER_PROFILE_FAILED; + } + + profile_hold->bus = bus_local; + profile_hold->cookie = cookie; + profile_hold->destination = endpoint->destination; + profile_hold->path = endpoint->path; + profile_hold->interface = endpoint->interface; + *hold = profile_hold; + LOG_MSG("Held power profile %s through %s\n", profile, endpoint->destination); + return GAME_MODE_POWER_PROFILE_HELD; + } + + sd_bus_close(bus_local); + sd_bus_unref(bus_local); + LOG_MSG("Power profiles D-Bus API unavailable; using platform profile directly\n"); + return GAME_MODE_POWER_PROFILE_UNAVAILABLE; +} + +void game_mode_destroy_power_profile_hold(GameModePowerProfileHold *hold) +{ + if (!hold) + return; + + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + int ret = sd_bus_call_method(hold->bus, + hold->destination, + hold->path, + hold->interface, + "ReleaseProfile", + &error, + &reply, + "u", + hold->cookie); + if (ret < 0) { + LOG_MSG("Failed to release power profile hold through %s; closing connection: %s\n", + hold->destination, + error.message ? error.message : strerror(-ret)); + } else { + LOG_MSG("Released power profile hold through %s\n", hold->destination); + } + + sd_bus_message_unref(reply); + sd_bus_error_free(&error); + sd_bus_close(hold->bus); + sd_bus_unref(hold->bus); + free(hold); +} diff --git a/daemon/gamemode.h b/daemon/gamemode.h index e0fa201b..2532bb94 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -230,8 +230,17 @@ void game_mode_undo_core_pinning(const GameModeCPUInfo *info, const pid_t client * Provides an API interface for using dbus */ typedef struct GameModeIdleInhibitor GameModeIdleInhibitor; +typedef struct GameModePowerProfileHold GameModePowerProfileHold; +enum GameModePowerProfileHoldResult { + GAME_MODE_POWER_PROFILE_HELD, + GAME_MODE_POWER_PROFILE_UNAVAILABLE, + GAME_MODE_POWER_PROFILE_FAILED, +}; void game_mode_context_loop(GameModeContext *context) __attribute__((noreturn)); GameModeIdleInhibitor *game_mode_create_idle_inhibitor(void); void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor); +enum GameModePowerProfileHoldResult game_mode_create_power_profile_hold( + const char *profile, GameModePowerProfileHold **hold); +void game_mode_destroy_power_profile_hold(GameModePowerProfileHold *hold); void game_mode_client_registered(pid_t); void game_mode_client_unregistered(pid_t); diff --git a/example/gamemode.ini b/example/gamemode.ini index 67cf0887..0e21e0db 100644 --- a/example/gamemode.ini +++ b/example/gamemode.ini @@ -7,10 +7,12 @@ desiredgov=performance ; The default governor is used when leaving GameMode instead of restoring the original value ;defaultgov=powersave -; The desired platform profile is used when entering GameMode instead of "performance" +; The desired platform profile is used when entering GameMode instead of "performance". +; GameMode requests "performance" or "power-saver" through power-profiles-daemon when available. desiredprof=performance ; The default platform profile is used when leaving GameMode instead of restoring the original value -;defaultgov=low-power +; Setting this uses direct platform profile control instead of a power-profiles-daemon hold. +;defaultprof=low-power ; The iGPU desired governor is used when the integrated GPU is under heavy load igpu_desiredgov=powersave