adjustor 3.0.1__tar.gz → 3.1.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- adjustor-3.1.0/MANIFEST.in +4 -0
- {adjustor-3.0.1/src/adjustor.egg-info → adjustor-3.1.0}/PKG-INFO +3 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/pyproject.toml +3 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/const.py +6 -1
- adjustor-3.1.0/src/adjustor/drivers/amd/__init__.py +325 -0
- adjustor-3.1.0/src/adjustor/drivers/amd/power-profiles-daemon.dbus.xml.in +147 -0
- adjustor-3.1.0/src/adjustor/drivers/amd/ppd.py +167 -0
- adjustor-3.1.0/src/adjustor/drivers/amd/settings.yml +124 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/asus/__init__.py +19 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/lenovo/__init__.py +39 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/smu/__init__.py +25 -2
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/smu/smu.yml +9 -8
- adjustor-3.1.0/src/adjustor/fuse/gpu.py +187 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/fuse/utils.py +1 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/hhd.py +7 -4
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/settings.yml +1 -1
- {adjustor-3.0.1 → adjustor-3.1.0/src/adjustor.egg-info}/PKG-INFO +3 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor.egg-info/SOURCES.txt +4 -1
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor.egg-info/requires.txt +2 -0
- adjustor-3.1.0/usr/share/dbus-1/system.d/hhd-net.hadess.PowerProfiles.conf +21 -0
- adjustor-3.0.1/MANIFEST.in +0 -2
- adjustor-3.0.1/src/adjustor/drivers/amd/__init__.py +0 -97
- adjustor-3.0.1/src/adjustor/drivers/amd/settings.yml +0 -42
- adjustor-3.0.1/src/adjustor/fuse/gpu.py +0 -91
- {adjustor-3.0.1 → adjustor-3.1.0}/LICENSE +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/readme.md +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/setup.cfg +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/__init__.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/__main__.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/__init__.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/acpi.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/alib.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/lenovo.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/core/platform.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/__init__.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/asus/settings.yml +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/lenovo/settings.yml +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/drivers/smu/qam.yml +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/events.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/fuse/__init__.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/fuse/driver.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor/i18n.py +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor.egg-info/dependency_links.txt +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor.egg-info/entry_points.txt +0 -0
- {adjustor-3.0.1 → adjustor-3.1.0}/src/adjustor.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: adjustor
|
3
|
-
Version: 3.0
|
3
|
+
Version: 3.1.0
|
4
4
|
Summary: Adjustor, a userspace program for managing the TDP of handheld devices.
|
5
5
|
Author-email: Kapenekakis Antheas <pypi@antheas.dev>
|
6
6
|
Project-URL: Homepage, https://github.com/hhd-dev/adjustor
|
@@ -16,6 +16,8 @@ License-File: LICENSE
|
|
16
16
|
Requires-Dist: rich>=13.5.2
|
17
17
|
Requires-Dist: pyroute2>=0.7.3
|
18
18
|
Requires-Dist: fuse-python>=1.0.7
|
19
|
+
Requires-Dist: PyGObject>=3.46.0
|
20
|
+
Requires-Dist: dbus-python>=1.3.2
|
19
21
|
|
20
22
|
# Adjustor
|
21
23
|
Home of the Adjustor TDP plugin for Handheld Daemon.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "adjustor"
|
3
|
-
version = "3.0
|
3
|
+
version = "3.1.0"
|
4
4
|
authors = [
|
5
5
|
{ name="Kapenekakis Antheas", email="pypi@antheas.dev" },
|
6
6
|
]
|
@@ -19,6 +19,8 @@ dependencies = [
|
|
19
19
|
"rich>=13.5.2",
|
20
20
|
"pyroute2>=0.7.3",
|
21
21
|
"fuse-python>=1.0.7",
|
22
|
+
"PyGObject>=3.46.0",
|
23
|
+
"dbus-python>=1.3.2",
|
22
24
|
]
|
23
25
|
|
24
26
|
[project.urls]
|
@@ -1,11 +1,16 @@
|
|
1
1
|
from .alib import A, D, DeviceParams, AlibParams
|
2
2
|
|
3
|
-
|
3
|
+
PLATFORM_PROFILE_MAP = [
|
4
4
|
("low-power", 0),
|
5
5
|
("quiet", 0),
|
6
6
|
("balanced", 13),
|
7
7
|
("performance", 20),
|
8
8
|
]
|
9
|
+
ENERGY_MAP = [
|
10
|
+
("power", 0),
|
11
|
+
("balanced", 13),
|
12
|
+
("performance", 20),
|
13
|
+
]
|
9
14
|
|
10
15
|
ALIB_PARAMS = {
|
11
16
|
# TDPs
|
@@ -0,0 +1,325 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
from threading import Event as TEvent
|
6
|
+
from threading import Thread
|
7
|
+
from typing import Literal
|
8
|
+
import time
|
9
|
+
|
10
|
+
from hhd.plugins import Context, HHDPlugin, load_relative_yaml
|
11
|
+
from hhd.plugins.conf import Config
|
12
|
+
|
13
|
+
from adjustor.fuse.gpu import (
|
14
|
+
get_igpu_status,
|
15
|
+
set_cpu_boost,
|
16
|
+
set_epp_mode,
|
17
|
+
set_gpu_auto,
|
18
|
+
set_gpu_manual,
|
19
|
+
set_powersave_governor,
|
20
|
+
can_use_nonlinear,
|
21
|
+
set_frequency_scaling,
|
22
|
+
)
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
APPLY_DELAY = 0.5
|
27
|
+
|
28
|
+
|
29
|
+
def _ppd_client(emit, proc):
|
30
|
+
os.set_blocking(proc.stdin.fileno(), False)
|
31
|
+
|
32
|
+
while True:
|
33
|
+
if proc.poll() is not None:
|
34
|
+
break
|
35
|
+
line = proc.stdout.readline().decode().strip()
|
36
|
+
if not line:
|
37
|
+
break
|
38
|
+
if line not in ("power", "balanced", "performance"):
|
39
|
+
logger.error(f"Invalid PPD mode: {line}")
|
40
|
+
continue
|
41
|
+
emit({"type": "ppd", "status": line})
|
42
|
+
|
43
|
+
|
44
|
+
def _open_ppd_server(emit):
|
45
|
+
logger.info("Launching PPD server.")
|
46
|
+
proc = subprocess.Popen(
|
47
|
+
[sys.executable, "-m", "adjustor.drivers.amd.ppd"],
|
48
|
+
stdout=subprocess.PIPE,
|
49
|
+
stdin=subprocess.PIPE,
|
50
|
+
)
|
51
|
+
t = Thread(target=_ppd_client, args=(emit, proc))
|
52
|
+
t.start()
|
53
|
+
return proc, t
|
54
|
+
|
55
|
+
|
56
|
+
class AmdGPUPlugin(HHDPlugin):
|
57
|
+
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
) -> None:
|
61
|
+
self.name = f"adjustor_ppd"
|
62
|
+
self.priority = 8
|
63
|
+
self.log = "agpu"
|
64
|
+
self.core_available = False
|
65
|
+
self.core_enabled = False
|
66
|
+
self.enabled = False
|
67
|
+
self.ppd_conflict = False
|
68
|
+
self.initialized = False
|
69
|
+
self.supports_boost = False
|
70
|
+
|
71
|
+
self.proc = None
|
72
|
+
self.t = None
|
73
|
+
|
74
|
+
self.queue = None
|
75
|
+
self.old_ppd = False
|
76
|
+
self.old_gpu = None
|
77
|
+
self.old_freq = None
|
78
|
+
self.old_boost = None
|
79
|
+
self.old_epp = None
|
80
|
+
self.old_target = None
|
81
|
+
self.old_min_freq = None
|
82
|
+
self.target: Literal["power", "balanced", "performance"] = "balanced"
|
83
|
+
|
84
|
+
self.logged_boost = False
|
85
|
+
self.logged_error = False
|
86
|
+
|
87
|
+
def settings(self):
|
88
|
+
if not self.core_enabled:
|
89
|
+
self.initialized = False
|
90
|
+
self.core_available = False
|
91
|
+
return {}
|
92
|
+
|
93
|
+
status = get_igpu_status()
|
94
|
+
if not status:
|
95
|
+
self.core_available = False
|
96
|
+
if not self.logged_error:
|
97
|
+
logger.error(
|
98
|
+
"Could not get frequency status. Disabling AMD GPU plugin."
|
99
|
+
)
|
100
|
+
self.logged_error = True
|
101
|
+
return {}
|
102
|
+
|
103
|
+
sets = load_relative_yaml("./settings.yml")
|
104
|
+
self.core_available = True
|
105
|
+
if not self.enabled:
|
106
|
+
self.initialized = False
|
107
|
+
return {"hhd": {"settings": sets["core"]}}
|
108
|
+
|
109
|
+
self.ppd_conflict = False
|
110
|
+
try:
|
111
|
+
out = subprocess.check_output(
|
112
|
+
[
|
113
|
+
"systemctl",
|
114
|
+
"list-units",
|
115
|
+
"-t",
|
116
|
+
"service",
|
117
|
+
"--full",
|
118
|
+
"--all",
|
119
|
+
"--plain",
|
120
|
+
"--no-legend",
|
121
|
+
]
|
122
|
+
)
|
123
|
+
for line in out.decode().splitlines():
|
124
|
+
if "power-profiles-daemon" in line or "tuned" in line.lower():
|
125
|
+
self.ppd_conflict = True
|
126
|
+
break
|
127
|
+
except Exception as e:
|
128
|
+
logger.error(f"Failed to check for PPD conflict:\n{e}")
|
129
|
+
|
130
|
+
if self.ppd_conflict:
|
131
|
+
self.initialized = False
|
132
|
+
return {
|
133
|
+
"tdp": {"amd_energy": sets["conflict"]},
|
134
|
+
"hhd": {"settings": sets["core"]},
|
135
|
+
}
|
136
|
+
|
137
|
+
self.initialized = True
|
138
|
+
freq = sets["enabled"]["children"]["mode"]["modes"]["manual"]["children"][
|
139
|
+
"gpu_freq"
|
140
|
+
]["modes"]["manual"]["children"]["frequency"]
|
141
|
+
freq["min"] = status.freq_min
|
142
|
+
freq["max"] = status.freq_max
|
143
|
+
freq["default"] = ((status.freq_min + status.freq_max) // 200) * 100
|
144
|
+
|
145
|
+
self.supports_boost = status.cpu_boost is not None
|
146
|
+
if self.supports_boost:
|
147
|
+
if not self.logged_boost:
|
148
|
+
logger.info(f"CPU Boost toggling is supported.")
|
149
|
+
else:
|
150
|
+
if not self.logged_boost:
|
151
|
+
logger.warning(f"CPU Boost toggling is not supported.")
|
152
|
+
del sets["enabled"]["children"]["mode"]["manual"]["children"]["cpu_boost"]
|
153
|
+
|
154
|
+
self.supports_nonlinear = can_use_nonlinear()
|
155
|
+
if not self.supports_nonlinear:
|
156
|
+
del sets["enabled"]["children"]["mode"]["modes"]["manual"]["children"][
|
157
|
+
"cpu_min_freq"
|
158
|
+
]
|
159
|
+
|
160
|
+
self.supports_epp = status.epp_avail is not None
|
161
|
+
if self.supports_epp:
|
162
|
+
epp = sets["enabled"]["children"]["mode"]["modes"]["manual"]["children"][
|
163
|
+
"cpu_pref"
|
164
|
+
]
|
165
|
+
epp["options"] = {
|
166
|
+
k: v for k, v in epp["options"].items() if k in status.epp_avail
|
167
|
+
}
|
168
|
+
else:
|
169
|
+
del sets["enabled"]["children"]["mode"]["modes"]["manual"]["children"][
|
170
|
+
"cpu_pref"
|
171
|
+
]
|
172
|
+
|
173
|
+
self.logged_boost = True
|
174
|
+
return {
|
175
|
+
"tdp": {"amd_energy": sets["enabled"]},
|
176
|
+
"hhd": {"settings": sets["core"]},
|
177
|
+
}
|
178
|
+
|
179
|
+
def open(
|
180
|
+
self,
|
181
|
+
emit,
|
182
|
+
context: Context,
|
183
|
+
):
|
184
|
+
self.emit = emit
|
185
|
+
|
186
|
+
def notify(self, events):
|
187
|
+
for event in events:
|
188
|
+
if event["type"] == "energy":
|
189
|
+
self.target = event["status"]
|
190
|
+
try:
|
191
|
+
if self.proc and self.proc.stdin:
|
192
|
+
self.proc.stdin.write(f"{self.target}\n".encode())
|
193
|
+
self.proc.stdin.flush()
|
194
|
+
except Exception as e:
|
195
|
+
logger.error(f"Failed to send PPD mode:\n{e}")
|
196
|
+
self.close()
|
197
|
+
|
198
|
+
def update(self, conf: Config):
|
199
|
+
self.core_enabled = conf["hhd.settings.tdp_enable"].to(bool)
|
200
|
+
if not self.core_enabled or not self.core_available:
|
201
|
+
return
|
202
|
+
|
203
|
+
enabled = conf["hhd.settings.amd_energy_enable"].to(bool)
|
204
|
+
if enabled != self.enabled:
|
205
|
+
self.emit({"type": "settings"})
|
206
|
+
self.enabled = enabled
|
207
|
+
|
208
|
+
if self.ppd_conflict and conf.get("tdp.amd_energy.enable", False):
|
209
|
+
conf["tdp.amd_energy.enable"] = False
|
210
|
+
self.emit({"type": "settings"})
|
211
|
+
|
212
|
+
if not self.initialized:
|
213
|
+
return
|
214
|
+
|
215
|
+
new_ppd = conf["hhd.settings.amd_energy_ppd"].to(bool)
|
216
|
+
if new_ppd != self.old_ppd:
|
217
|
+
self.old_ppd = new_ppd
|
218
|
+
if new_ppd:
|
219
|
+
try:
|
220
|
+
self.proc, self.t = _open_ppd_server(self.emit)
|
221
|
+
except Exception as e:
|
222
|
+
logger.error(f"Failed to open PPD server:\n{e}")
|
223
|
+
self.close()
|
224
|
+
else:
|
225
|
+
self.close()
|
226
|
+
|
227
|
+
if conf["tdp.amd_energy.mode.mode"].to(str) == "auto":
|
228
|
+
curr = time.perf_counter()
|
229
|
+
if self.target != self.old_target:
|
230
|
+
self.old_target = self.target
|
231
|
+
self.queue = curr + APPLY_DELAY
|
232
|
+
|
233
|
+
if self.queue is not None and curr >= self.queue:
|
234
|
+
self.queue = None
|
235
|
+
logger.info(
|
236
|
+
f"Handling energy settings for power profile '{self.target}'."
|
237
|
+
)
|
238
|
+
try:
|
239
|
+
match self.target:
|
240
|
+
case "balanced":
|
241
|
+
set_gpu_auto()
|
242
|
+
if self.supports_boost:
|
243
|
+
set_cpu_boost(True)
|
244
|
+
if self.supports_epp:
|
245
|
+
set_powersave_governor()
|
246
|
+
set_epp_mode("balance_power")
|
247
|
+
set_frequency_scaling(nonlinear=False)
|
248
|
+
case "performance":
|
249
|
+
set_gpu_auto()
|
250
|
+
if self.supports_boost:
|
251
|
+
set_cpu_boost(True)
|
252
|
+
if self.supports_epp:
|
253
|
+
set_powersave_governor()
|
254
|
+
set_epp_mode("balance_power")
|
255
|
+
set_frequency_scaling(nonlinear=True)
|
256
|
+
case _: # power
|
257
|
+
set_gpu_auto()
|
258
|
+
if self.supports_boost:
|
259
|
+
set_cpu_boost(False)
|
260
|
+
if self.supports_epp:
|
261
|
+
set_powersave_governor()
|
262
|
+
set_epp_mode("power")
|
263
|
+
set_frequency_scaling(False)
|
264
|
+
except Exception as e:
|
265
|
+
logger.error(f"Failed to set mode:\n{e}")
|
266
|
+
|
267
|
+
self.old_gpu = None
|
268
|
+
self.old_freq = None
|
269
|
+
self.old_boost = None
|
270
|
+
self.old_epp = None
|
271
|
+
self.old_min_freq = None
|
272
|
+
else:
|
273
|
+
self.old_target = None
|
274
|
+
new_gpu = conf["tdp.amd_energy.mode.manual.gpu_freq.mode"].to(str)
|
275
|
+
new_freq = conf["tdp.amd_energy.mode.manual.gpu_freq.manual.frequency"].to(
|
276
|
+
int
|
277
|
+
)
|
278
|
+
if new_gpu != self.old_gpu or new_freq != self.old_freq:
|
279
|
+
self.old_gpu = new_gpu
|
280
|
+
self.old_freq = new_freq
|
281
|
+
|
282
|
+
try:
|
283
|
+
if new_gpu == "manual":
|
284
|
+
set_gpu_manual(new_freq)
|
285
|
+
else:
|
286
|
+
set_gpu_auto()
|
287
|
+
except Exception as e:
|
288
|
+
logger.error(f"Failed to set GPU mode:\n{e}")
|
289
|
+
|
290
|
+
if self.supports_boost:
|
291
|
+
new_boost = conf["tdp.amd_energy.mode.manual.cpu_boost"].to(bool)
|
292
|
+
if new_boost != self.old_boost:
|
293
|
+
self.old_boost = new_boost
|
294
|
+
try:
|
295
|
+
set_cpu_boost(new_boost == "enabled")
|
296
|
+
except Exception as e:
|
297
|
+
logger.error(f"Failed to set CPU boost:\n{e}")
|
298
|
+
|
299
|
+
if self.supports_epp:
|
300
|
+
new_epp = conf["tdp.amd_energy.mode.manual.cpu_pref"].to(str)
|
301
|
+
if new_epp != self.old_epp:
|
302
|
+
self.old_epp = new_epp
|
303
|
+
try:
|
304
|
+
# Set governor to powersave as well
|
305
|
+
set_powersave_governor()
|
306
|
+
set_epp_mode(new_epp) # type: ignore
|
307
|
+
except Exception as e:
|
308
|
+
logger.error(f"Failed to set EPP mode:\n{e}")
|
309
|
+
|
310
|
+
if self.supports_nonlinear:
|
311
|
+
new_min_freq = conf["tdp.amd_energy.mode.manual.cpu_min_freq"].to(int)
|
312
|
+
if new_min_freq != self.old_min_freq:
|
313
|
+
self.old_min_freq = new_min_freq
|
314
|
+
try:
|
315
|
+
set_frequency_scaling(nonlinear=new_min_freq == "nonlinear")
|
316
|
+
except Exception as e:
|
317
|
+
logger.error(f"Failed to set minimum CPU frequency:\n{e}")
|
318
|
+
|
319
|
+
def close(self):
|
320
|
+
if self.proc is not None:
|
321
|
+
self.proc.terminate()
|
322
|
+
self.proc.wait()
|
323
|
+
if self.t is not None:
|
324
|
+
self.t.join()
|
325
|
+
self.t = None
|
@@ -0,0 +1,147 @@
|
|
1
|
+
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
2
|
+
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
3
|
+
|
4
|
+
<node>
|
5
|
+
|
6
|
+
<!--
|
7
|
+
@dbus_iface@:
|
8
|
+
@short_description: Power Profiles daemon
|
9
|
+
|
10
|
+
The power-profiles-daemon API is meant to be used by parts of the OS or
|
11
|
+
desktop environment to switch system power profiles based on user choice,
|
12
|
+
or user intent.
|
13
|
+
|
14
|
+
OS components would typically use the "Profiles" property to construct
|
15
|
+
their UI (2 or 3 profiles available), and monitor the "ActiveProfile"
|
16
|
+
and the "PerformanceDegraded" properties to update that UI. The UI
|
17
|
+
would try to set the "ActiveProfile" property if the user selected
|
18
|
+
a different one.
|
19
|
+
|
20
|
+
Note that the reason why the project exists and how it is different from
|
21
|
+
existing projects is explained <ulink href=" https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/blob/master/README.md">
|
22
|
+
in the project's README file</ulink>.
|
23
|
+
|
24
|
+
The object path will be "@dbus_path@".
|
25
|
+
-->
|
26
|
+
<interface name="@dbus_iface@">
|
27
|
+
|
28
|
+
<!--
|
29
|
+
HoldProfile:
|
30
|
+
|
31
|
+
This forces the passed profile (either 'power-saver' or 'performance')
|
32
|
+
to be activated until either the caller quits, "ReleaseProfile" is
|
33
|
+
called, or the "ActiveProfile" is changed by the user.
|
34
|
+
|
35
|
+
This should be used programmatically by OS components when, eg. high-
|
36
|
+
performance workloads are started with the "performance" profile, or
|
37
|
+
battery will soon be critically low with the "power-saver" profile.
|
38
|
+
|
39
|
+
When conflicting profiles are requested to be held, the 'power-saver' profile
|
40
|
+
will be activated in preference to the 'performance' profile.
|
41
|
+
|
42
|
+
Those holds will be automatically cancelled if the user manually switches
|
43
|
+
to another profile, and the "ProfileReleased" signal will be emitted.
|
44
|
+
-->
|
45
|
+
<method name="HoldProfile">
|
46
|
+
<arg name="profile" type="s" direction="in"/>
|
47
|
+
<arg name="reason" type="s" direction="in"/>
|
48
|
+
<arg name="application_id" type="s" direction="in" />
|
49
|
+
<arg name="cookie" type="u" direction="out"/>
|
50
|
+
</method>
|
51
|
+
|
52
|
+
<!--
|
53
|
+
ReleaseProfile:
|
54
|
+
|
55
|
+
This removes the hold that was set on a profile.
|
56
|
+
-->
|
57
|
+
<method name="ReleaseProfile">
|
58
|
+
<arg name="cookie" type="u" direction="in"/>
|
59
|
+
</method>
|
60
|
+
|
61
|
+
<!--
|
62
|
+
ProfileReleased:
|
63
|
+
|
64
|
+
This signal will be emitted if the profile is released because the
|
65
|
+
"ActiveProfile" was manually changed. The signal will only be emitted
|
66
|
+
to the process that originally called "HoldProfile".
|
67
|
+
-->
|
68
|
+
<signal name="ProfileReleased">
|
69
|
+
<arg name="cookie" type="u" direction="out"/>
|
70
|
+
</signal>
|
71
|
+
|
72
|
+
<!--
|
73
|
+
ActiveProfile:
|
74
|
+
|
75
|
+
The type of the currently active profile. It might change automatically
|
76
|
+
if a profile is held, using the "HoldProfile" function.
|
77
|
+
-->
|
78
|
+
<property name="ActiveProfile" type="s" access="readwrite"/>
|
79
|
+
|
80
|
+
<!--
|
81
|
+
PerformanceInhibited:
|
82
|
+
|
83
|
+
This property is deprecated, and unused since version 0.9.
|
84
|
+
-->
|
85
|
+
<property name="PerformanceInhibited" type="s" access="read"/>
|
86
|
+
|
87
|
+
<!--
|
88
|
+
PerformanceDegraded:
|
89
|
+
|
90
|
+
This will be set if the performance power profile is running in degraded
|
91
|
+
mode, with the value being used to identify the reason for that degradation.
|
92
|
+
As new reasons can be added, it is recommended that front-ends show a generic
|
93
|
+
reason if they do not recognise the value. Possible values are:
|
94
|
+
- "lap-detected" (the computer is sitting on the user's lap)
|
95
|
+
- "high-operating-temperature" (the computer is close to overheating)
|
96
|
+
- "" (the empty string, if not performance is not degraded)
|
97
|
+
-->
|
98
|
+
<property name="PerformanceDegraded" type="s" access="read"/>
|
99
|
+
|
100
|
+
<!--
|
101
|
+
Profiles:
|
102
|
+
|
103
|
+
An array of key-pair values representing each profile. The key named
|
104
|
+
"Driver" (s) identifies the power-profiles-daemon backend code used to
|
105
|
+
implement the profile.
|
106
|
+
|
107
|
+
The key named "Profile" (s) will be one of:
|
108
|
+
- "power-saver" (battery saving profile)
|
109
|
+
- "balanced" (the default profile)
|
110
|
+
- "performance" (a profile that does not care about noise or battery consumption)
|
111
|
+
|
112
|
+
Only one of each type of profile will be listed, with the daemon choosing the
|
113
|
+
more appropriate "driver" for each profile type.
|
114
|
+
|
115
|
+
This list is guaranteed to be sorted in the same order that the profiles
|
116
|
+
are listed above.
|
117
|
+
-->
|
118
|
+
<property name="Profiles" type="aa{sv}" access="read"/>
|
119
|
+
|
120
|
+
<!--
|
121
|
+
Actions:
|
122
|
+
|
123
|
+
An array of strings listing each one of the "actions" implemented in
|
124
|
+
the running daemon. This is used by API users to figure out whether
|
125
|
+
particular functionality is available in a version of the daemon.
|
126
|
+
-->
|
127
|
+
<property name="Actions" type="as" access="read"/>
|
128
|
+
|
129
|
+
<!--
|
130
|
+
ActiveProfileHolds:
|
131
|
+
|
132
|
+
A list of dictionaries representing the current profile holds.
|
133
|
+
The keys in the dict are "ApplicationId", "Profile" and "Reason",
|
134
|
+
and correspond to the "application_id", "profile" and "reason" arguments
|
135
|
+
passed to the HoldProfile() method.
|
136
|
+
-->
|
137
|
+
<property name="ActiveProfileHolds" type="aa{sv}" access="read"/>
|
138
|
+
|
139
|
+
<!--
|
140
|
+
Version:
|
141
|
+
|
142
|
+
The version of the power-profiles-daemon software.
|
143
|
+
-->
|
144
|
+
<property name="Version" type="s" access="read"/>
|
145
|
+
|
146
|
+
</interface>
|
147
|
+
</node>
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import fcntl
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
|
7
|
+
import dbus
|
8
|
+
import dbus.mainloop.glib
|
9
|
+
import dbus.service
|
10
|
+
from gi.repository import GLib
|
11
|
+
|
12
|
+
BASE_NAME = "org.freedesktop.UPower.PowerProfiles"
|
13
|
+
BASE_PATH = "/org/freedesktop/UPower/PowerProfiles"
|
14
|
+
LEGACY_NAME = "net.hadess.PowerProfiles"
|
15
|
+
LEGACY_PATH = "/net/hadess/PowerProfiles"
|
16
|
+
XML_PATH = "power-profiles-daemon.dbus.xml.in"
|
17
|
+
|
18
|
+
SUPPORTED_PROFILES = {
|
19
|
+
"performance": "performance",
|
20
|
+
"balanced": "balanced",
|
21
|
+
"power-saver": "power",
|
22
|
+
}
|
23
|
+
SUPPORTED_PROFILES_REVERSE = {v: k for k, v in SUPPORTED_PROFILES.items()}
|
24
|
+
|
25
|
+
|
26
|
+
def load_introspect(legacy=False):
|
27
|
+
"""Returns the yaml data of a file in the relative dir provided."""
|
28
|
+
import inspect
|
29
|
+
import os
|
30
|
+
|
31
|
+
script_fn = inspect.currentframe().f_back.f_globals["__file__"] # type: ignore
|
32
|
+
dirname = os.path.dirname(script_fn)
|
33
|
+
with open(os.path.join(dirname, XML_PATH), "r") as f:
|
34
|
+
base = f.read()
|
35
|
+
return base.replace(
|
36
|
+
"@dbus_iface@", LEGACY_NAME if legacy else BASE_NAME
|
37
|
+
).replace("@dbus_path@", LEGACY_PATH if legacy else BASE_PATH)
|
38
|
+
|
39
|
+
|
40
|
+
def iface(legacy: bool):
|
41
|
+
return LEGACY_NAME if legacy else BASE_NAME
|
42
|
+
|
43
|
+
|
44
|
+
def gpath(legacy: bool):
|
45
|
+
return LEGACY_PATH if legacy else BASE_PATH
|
46
|
+
|
47
|
+
|
48
|
+
def create_interface(legacy: bool):
|
49
|
+
class HhdPpd(dbus.service.Object):
|
50
|
+
|
51
|
+
def __init__(self, conn=None):
|
52
|
+
self.profile_holds = []
|
53
|
+
self.actions = []
|
54
|
+
self.profile = "power-saver" # next(iter(SUPPORTED_PROFILES))
|
55
|
+
|
56
|
+
super().__init__(conn, gpath(legacy), None)
|
57
|
+
|
58
|
+
@dbus.service.method(
|
59
|
+
dbus.INTROSPECTABLE_IFACE, in_signature="", out_signature="s"
|
60
|
+
)
|
61
|
+
def Introspect(self):
|
62
|
+
return load_introspect(legacy)
|
63
|
+
|
64
|
+
@dbus.service.method(
|
65
|
+
dbus.PROPERTIES_IFACE, in_signature="ss", out_signature="v"
|
66
|
+
)
|
67
|
+
def Get(self, interface_name, property_name):
|
68
|
+
return self.GetAll(interface_name)[property_name]
|
69
|
+
|
70
|
+
@dbus.service.method(
|
71
|
+
dbus.PROPERTIES_IFACE,
|
72
|
+
in_signature="s",
|
73
|
+
out_signature="a{sv}",
|
74
|
+
sender_keyword="sender",
|
75
|
+
)
|
76
|
+
def GetAll(self, interface_name, sender=None):
|
77
|
+
if interface_name == iface(legacy):
|
78
|
+
return {
|
79
|
+
"Actions": dbus.Array(self.actions, signature="s"),
|
80
|
+
"ActiveProfile": self.profile,
|
81
|
+
"ActiveProfileHolds": dbus.Array(self.profile_holds, signature="u"),
|
82
|
+
"PerformanceDegraded": "",
|
83
|
+
"PerformanceInhibited": "",
|
84
|
+
"Profiles": [{"Profile": p} for p in SUPPORTED_PROFILES],
|
85
|
+
"Version": "0.21",
|
86
|
+
}
|
87
|
+
else:
|
88
|
+
raise dbus.exceptions.DBusException(
|
89
|
+
"com.example.UnknownInterface",
|
90
|
+
"Handheld daemon does not implement the %s interface."
|
91
|
+
% interface_name,
|
92
|
+
)
|
93
|
+
|
94
|
+
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
|
95
|
+
def Set(self, interface_name, property_name, new_value):
|
96
|
+
# validate the property name and value, update internal state…
|
97
|
+
self.PropertiesChanged(interface_name, {property_name: new_value}, [])
|
98
|
+
|
99
|
+
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
|
100
|
+
def PropertiesChanged(
|
101
|
+
self, interface_name, changed_properties, invalidated_properties
|
102
|
+
):
|
103
|
+
if interface_name != iface(legacy):
|
104
|
+
return
|
105
|
+
|
106
|
+
for k, v in changed_properties.items():
|
107
|
+
if not k == "ActiveProfile":
|
108
|
+
continue
|
109
|
+
if v not in SUPPORTED_PROFILES:
|
110
|
+
continue
|
111
|
+
np = SUPPORTED_PROFILES[v]
|
112
|
+
if np == self.profile:
|
113
|
+
continue
|
114
|
+
print(np, flush=True)
|
115
|
+
|
116
|
+
@dbus.service.method(iface(legacy), in_signature="sss", out_signature="u")
|
117
|
+
def HoldProfile(self, profile: str, reason: str, application_id: str):
|
118
|
+
# TODO
|
119
|
+
return 1
|
120
|
+
|
121
|
+
@dbus.service.method(iface(legacy), in_signature="u", out_signature="")
|
122
|
+
def ReleaseProfile(self, handle: int):
|
123
|
+
# TODO
|
124
|
+
self.ProfileReleased(handle)
|
125
|
+
|
126
|
+
@dbus.service.signal(iface(legacy), signature="u")
|
127
|
+
def ProfileReleased(self, handle: int):
|
128
|
+
# TODO
|
129
|
+
return handle
|
130
|
+
|
131
|
+
def update_profile(self):
|
132
|
+
for line in sys.stdin:
|
133
|
+
if not line:
|
134
|
+
break
|
135
|
+
profile = line.strip()
|
136
|
+
if profile not in SUPPORTED_PROFILES_REVERSE:
|
137
|
+
continue
|
138
|
+
|
139
|
+
self.profile = profile
|
140
|
+
self.PropertiesChanged(
|
141
|
+
iface(legacy), {"ActiveProfile": self.profile}, []
|
142
|
+
)
|
143
|
+
return True
|
144
|
+
|
145
|
+
return HhdPpd
|
146
|
+
|
147
|
+
|
148
|
+
if __name__ == "__main__":
|
149
|
+
mainloop = None
|
150
|
+
try:
|
151
|
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
152
|
+
|
153
|
+
# set sys.stdin non-blocking
|
154
|
+
orig_fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL)
|
155
|
+
fcntl.fcntl(sys.stdin, fcntl.F_SETFL, orig_fl | os.O_NONBLOCK)
|
156
|
+
|
157
|
+
legacy = True
|
158
|
+
session_bus = dbus.SystemBus()
|
159
|
+
name = dbus.service.BusName(iface(legacy), session_bus)
|
160
|
+
object = create_interface(legacy)(session_bus)
|
161
|
+
|
162
|
+
GLib.timeout_add(100, object.update_profile)
|
163
|
+
mainloop = GLib.MainLoop()
|
164
|
+
mainloop.run()
|
165
|
+
except KeyboardInterrupt:
|
166
|
+
if mainloop:
|
167
|
+
mainloop.quit()
|