adjustor 3.0.0__tar.gz → 3.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. adjustor-3.1.0/MANIFEST.in +4 -0
  2. {adjustor-3.0.0/src/adjustor.egg-info → adjustor-3.1.0}/PKG-INFO +3 -1
  3. {adjustor-3.0.0 → adjustor-3.1.0}/pyproject.toml +3 -1
  4. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/const.py +6 -1
  5. adjustor-3.1.0/src/adjustor/drivers/amd/__init__.py +325 -0
  6. adjustor-3.1.0/src/adjustor/drivers/amd/power-profiles-daemon.dbus.xml.in +147 -0
  7. adjustor-3.1.0/src/adjustor/drivers/amd/ppd.py +167 -0
  8. adjustor-3.1.0/src/adjustor/drivers/amd/settings.yml +124 -0
  9. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/asus/__init__.py +19 -1
  10. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/lenovo/__init__.py +39 -1
  11. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/smu/__init__.py +30 -7
  12. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/smu/smu.yml +9 -8
  13. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/fuse/driver.py +2 -0
  14. adjustor-3.1.0/src/adjustor/fuse/gpu.py +187 -0
  15. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/fuse/utils.py +1 -1
  16. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/hhd.py +7 -4
  17. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/settings.yml +1 -1
  18. {adjustor-3.0.0 → adjustor-3.1.0/src/adjustor.egg-info}/PKG-INFO +3 -1
  19. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor.egg-info/SOURCES.txt +4 -1
  20. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor.egg-info/requires.txt +2 -0
  21. adjustor-3.1.0/usr/share/dbus-1/system.d/hhd-net.hadess.PowerProfiles.conf +21 -0
  22. adjustor-3.0.0/MANIFEST.in +0 -2
  23. adjustor-3.0.0/src/adjustor/drivers/amd/__init__.py +0 -97
  24. adjustor-3.0.0/src/adjustor/drivers/amd/settings.yml +0 -42
  25. adjustor-3.0.0/src/adjustor/fuse/gpu.py +0 -91
  26. {adjustor-3.0.0 → adjustor-3.1.0}/LICENSE +0 -0
  27. {adjustor-3.0.0 → adjustor-3.1.0}/readme.md +0 -0
  28. {adjustor-3.0.0 → adjustor-3.1.0}/setup.cfg +0 -0
  29. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/__init__.py +0 -0
  30. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/__main__.py +0 -0
  31. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/__init__.py +0 -0
  32. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/acpi.py +0 -0
  33. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/alib.py +0 -0
  34. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/lenovo.py +0 -0
  35. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/core/platform.py +0 -0
  36. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/__init__.py +0 -0
  37. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/asus/settings.yml +0 -0
  38. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/lenovo/settings.yml +0 -0
  39. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/drivers/smu/qam.yml +0 -0
  40. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/events.py +0 -0
  41. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/fuse/__init__.py +0 -0
  42. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor/i18n.py +0 -0
  43. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor.egg-info/dependency_links.txt +0 -0
  44. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor.egg-info/entry_points.txt +0 -0
  45. {adjustor-3.0.0 → adjustor-3.1.0}/src/adjustor.egg-info/top_level.txt +0 -0
@@ -0,0 +1,4 @@
1
+ recursive-include src/adjustor *.yml
2
+ recursive-include src/adjustor *.yaml
3
+ recursive-include src/adjustor *.xml.in
4
+ graft usr/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: adjustor
3
- Version: 3.0.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.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
- ROG_ALLY_PP_MAP = [
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()