Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions daemon/gamemode-context.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ struct GameModeContext {

char initial_profile[64];
enum GameModeProfile current_profile;
GameModePowerProfileHold *profile_hold;

struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
Expand Down Expand Up @@ -423,28 +424,49 @@ static int game_mode_set_profile(GameModeContext *self, enum GameModeProfile pro
return 0;
}

if (!profile_exists()) {
LOG_MSG("Setting platform profile unsupported; skipping\n");
return 0;
}

const char *prof_str = NULL;
char prof_config_str[CONFIG_VALUE_MAX] = { 0 };
switch (prof) {
case GAME_MODE_PROFILE_DEFAULT:
if (self->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,
};
Expand Down
149 changes: 149 additions & 0 deletions daemon/gamemode-dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
9 changes: 9 additions & 0 deletions daemon/gamemode.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
6 changes: 4 additions & 2 deletions example/gamemode.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down