# Copyright (c) 2025 Emma Nora Theuer
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import re
import subprocess
from libqtile import bar
from libqtile.widget import base
[docs]class TunedManager(base.ThreadPoolText):
"""
A widget to interact with the Tuned power management daemon.
It always displays the name of the currently active profile.
The user can define a list of profiles to be used, 3 default
profiles exist. These 3 are the default profiles on RHEL and
Fedora Linux.
A left click on the widget will go to the next layout in the
list, a right click will go to the previous one. If the end
of the list is reached, it cycles back to the beginning.
Scrolling can also be used to cycle through the list, though
keep in mind that switching the profile takes a while and so
scrolling through the list quickly is not feasible.
"""
orientations = base.ORIENTATION_HORIZONTAL
defaults = [
(
"modes",
["powersave", "balanced-battery", "throughput-performance"],
"The modes to cycle through",
)
]
def __init__(self, **config):
base.ThreadPoolText.__init__(self, "", **config)
self.add_defaults(TunedManager.defaults)
self.length_type = bar.CALCULATED
self.regex = re.compile(r"Current active profile:\s+(\S+)")
self.current_mode = self.find_mode()
self.add_callbacks(
{
"Button1": self.next_mode, # Left Click
"Button3": self.previous_mode, # Right Click
"Button4": self.next_mode, # Scroll up
"Button5": self.previous_mode, # Scroll down
}
)
def _configure(self, qtile, bar):
base.ThreadPoolText._configure(self, qtile, bar)
self.text = self.find_mode()
def poll(self):
return self.find_mode()
def find_mode(self):
result = subprocess.run("tuned-adm active", shell=True, capture_output=True, text=True)
output = result.stdout
mode = self.regex.findall(output)
if not mode:
return ""
return mode[0]
def update_bar(self):
self.current_mode = self.find_mode()
self.text = self.current_mode
self.bar.draw()
def execute_command(self, index: int):
argument = self.modes[index] # pyright: ignore
try:
subprocess.run(["tuned-adm", "profile", argument], check=True)
self.update_bar()
except subprocess.CalledProcessError as e:
self.update(f"Error setting mode: {e}")
def _change_mode(self, step=1):
next_index: int = (self.modes.index(self.current_mode) + step) % len(self.modes) # pyright: ignore
self.execute_command(next_index)
def next_mode(self):
self._change_mode()
def previous_mode(self):
self._change_mode(step=-1)