Skip to content

Commit

Permalink
Major refactor: added support for asusctl 4.x with power profile swit…
Browse files Browse the repository at this point in the history
…ching, Gfx control and boost toggling. Ported everything to dbus instead of relying on config files
  • Loading branch information
Baldomo committed Sep 30, 2021
1 parent bb66e85 commit dcffa98
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 45 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Unofficial support for tray icons can be easily brought back to the shell with [
Moreover, GNOME users should check out the excellent [asusctl-gex](https://gitlab.com/asus-linux/asusctl-gex/-/tree/main) shell extension from the maintainers of asusctl.

## Usage
On click: open context menu with all the profiles, selecting one will apply it (calls `asusctl profile <profile>`).
> ⚠️ `pkexec` is required for boost toggling
On click: open context menu with all the profiles, selecting one will apply it (`dbus` is used extensively for both profile switching and GFX control).

## Screenshots
![tray.png](screenshots/tray.png)
Expand Down
150 changes: 106 additions & 44 deletions asusctltray
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import configparser
import json
import os
import signal
Expand All @@ -31,10 +32,11 @@ import gi
gi.require_version("Gtk", "3.0")
gi.require_version("AppIndicator3", "0.1")
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Gio, GLib, Gtk
from gi.repository import Gio, GLib, Gtk, GObject

ASUSD_CONF = "/etc/asusd/asusd.conf"
ICON_BASE_PATH = "/usr/share/pixmaps"
GFX_CONF = "/etc/supergfxd.conf"
PROFILE_CONF = "/etc/asusd/profile.conf"
GFX_MODES = {
0: "nvidia",
1: "integrated",
Expand All @@ -51,28 +53,42 @@ GFX_USER_ACTION = {


class TrayIcon:
config = None
gfx_config = None

gfx_proxy = None
profile_proxy = None
power_profile_props_proxy = None
presets = []

# Dict[Gtk.RadioMenuItem, str]
# Used to store original profile names for radio widgets
raw_names = {}

icon = None
image_pixbuf = None
presets = []

def __init__(self):
with open(ASUSD_CONF, "r") as file:
self.config = json.load(file)
self.presets = self.config["power_profiles"].keys()
with open(GFX_CONF, "r") as file:
self.gfx_config = json.load(file)

bus = dbus.SystemBus()
self.gfx_proxy = dbus.Interface(
bus.get_object("org.asuslinux.Daemon", "/org/asuslinux/Gfx"),
dbus_interface="org.asuslinux.Daemon",
bus.get_object("org.supergfxctl.Daemon", "/org/supergfxctl/Gfx"),
dbus_interface="org.supergfxctl.Daemon",
)
self.profile_proxy = dbus.Interface(
bus.get_object("org.asuslinux.Daemon", "/org/asuslinux/Profile"),
dbus_interface="org.asuslinux.Daemon",
self.power_profile_props_proxy = dbus.Interface(
bus.get_object("net.hadess.PowerProfiles", "/net/hadess/PowerProfiles"),
dbus_interface=dbus.PROPERTIES_IFACE,
)

# TODO: figure out order
# balance performance quiet
self.presets = [
str(p["Profile"])
for p in self.power_profile_props_proxy.Get(
"net.hadess.PowerProfiles", "Profiles"
)
]

self.icon = appindicator.Indicator.new(
"asusctltray",
Gtk.STOCK_INFO,
Expand All @@ -89,7 +105,9 @@ class TrayIcon:

self.add_graphics("Graphics")
self.menu.append(Gtk.SeparatorMenuItem())
self.add_presets("Profiles")
self.add_presets("Power profiles")
self.menu.append(Gtk.SeparatorMenuItem())
self.setup_boost_menu("Boost")
self.menu.append(Gtk.SeparatorMenuItem())

qicon = Gtk.MenuItem()
Expand All @@ -99,28 +117,44 @@ class TrayIcon:

self.menu.show_all()

def _humanize(self, s):
"""Humanize profile name, e.g. 'power-saver' -> 'Power saver'"""
return s.replace("-", " ").capitalize()

def add_presets(self, title):
if len(self.presets) == 0:
return

current_profile = self.config["active_profile"]
current_profile = self.power_profile_props_proxy.Get(
"net.hadess.PowerProfiles", "ActiveProfile"
)

titem = Gtk.MenuItem()
titem.set_label(title)
titem.set_sensitive(False)
self.menu.append(titem)

group = []
for p in self.presets:
pitem = Gtk.RadioMenuItem.new_with_label(group, p)
group = pitem.get_group()
pitem = Gtk.RadioMenuItem.new_with_label(group=group, label=self._humanize(p))
self.raw_names[pitem] = p
pitem.set_active(p == current_profile)

pitem.connect("activate", self.load_preset)
group = pitem.get_group()
self.menu.append(pitem)

def load_preset(self, widget):
# This is called when the widget is deselected too, so check if active
if widget.get_active():
self.profile_proxy.SetProfile(widget.get_label())
if not widget.get_active():
return

# TODO: use signals when they're actually implemented by asusctl
self.power_profile_props_proxy.Set(
"net.hadess.PowerProfiles",
"ActiveProfile",
self.raw_names[widget],
)

def add_graphics(self, title):
current_graphics = self.get_current_graphics()
Expand All @@ -132,57 +166,39 @@ class TrayIcon:
group = []
for g in GFX_MODES.values():
# Skip if vfio is disabled in configuration
if not self.config["gfx_vfio_enable"] and g == "vfio":
if not self.gfx_config["gfx_vfio_enable"] and g == "vfio":
continue
gitem = Gtk.RadioMenuItem.new_with_label(group, g)
group = gitem.get_group()
gitem.set_active(g == current_graphics)
gitem.connect("activate", self.switch_graphics_dbus)
gitem.connect("activate", self.switch_graphics)
self.menu.append(gitem)

def get_current_graphics(self):
vendor = self.gfx_proxy.Vendor()
return GFX_MODES[vendor]

def switch_graphics(self, widget):
# This is called when the widget is deselected too, so check if active
if not widget.get_active():
return
dialog = Gtk.MessageDialog(
destroy_with_parent=True,
message_type=Gtk.MessageType.WARNING,
buttons=Gtk.ButtonsType.OK,
text="Graphics: %s" % widget.get_label(),
)
dialog.format_secondary_text(
"Switching graphics could require logout/reboot. Proceed?"
)
resp = dialog.run()
if resp == Gtk.ResponseType.OK:
os.system("asusctl graphics -f -m %s" % widget.get_label())
else:
dialog.destroy()

def switch_graphics_dbus(self, widget):
# This is called when the widget is deselected too, so check if active
if not widget.get_active():
return

if self.get_current_graphics() == widget.get_label():
return

# NOTE: will probably be possible via dbus in the future
mode_ind = [k for (k, v) in GFX_MODES.items() if v == widget.get_label()][0]
action_ind = self.gfx_proxy.SetVendor(mode_ind)
action = GFX_USER_ACTION[action_ind]
if action == "integrated":
dialog = self.build_dialog(
dialog = self._build_dialog(
"You must switch to Integrated mode before switching to Compute or VFIO.",
"Got it",
)
dialog.run()
dialog.destroy()
elif action != "none":
dialog = self.build_dialog(
dialog = self._build_dialog(
f"Graphics changed to {widget.get_label()}. A {action} is required (save your files!)."
)
dialog.add_buttons("Later", Gtk.ResponseType.CLOSE)
Expand All @@ -204,7 +220,7 @@ class TrayIcon:
os.system(f"loginctl terminate-session {session_n}")
dialog.destroy()

def build_dialog(self, msg, btn_text=None):
def _build_dialog(self, msg, btn_text=None):
dialog = Gtk.MessageDialog(
destroy_with_parent=True,
message_type=Gtk.MessageType.WARNING,
Expand All @@ -213,13 +229,59 @@ class TrayIcon:
dialog.format_secondary_text(msg)
return dialog

def _is_boost_enabled(self):
data = open("/sys/devices/system/cpu/cpufreq/boost", "r").read()[:-1]
return int(data) == 1

def _enable_boost(self, widget):
# This is called when the widget is deselected too, so check if active
if not widget.get_active():
return
if widget.get_active() == self._is_boost_enabled():
return

os.system("echo 1 | pkexec tee /sys/devices/system/cpu/cpufreq/boost")

def _disable_boost(self, widget):
# This is called when the widget is deselected too, so check if active
if not widget.get_active():
return
if widget.get_active() != self._is_boost_enabled():
return

os.system("echo 0 | pkexec tee /sys/devices/system/cpu/cpufreq/boost")

def setup_boost_menu(self, title):
group = []
enable = Gtk.RadioMenuItem.new_with_label(group, label="Enabled")
group = enable.get_group()

disable = Gtk.RadioMenuItem.new_with_label(group, label="Disabled")
group = disable.get_group()

boost_enabled = self._is_boost_enabled()
enable.set_active(boost_enabled)
disable.set_active(not boost_enabled)

enable.connect("activate", self._enable_boost)
disable.connect("activate", self._disable_boost)

bmenu = Gtk.Menu()
bmenu.append(enable)
bmenu.append(disable)

bitem = Gtk.MenuItem()
bitem.set_label(title)
bitem.set_submenu(bmenu)

self.menu.append(bitem)


def kill(obj1, obj2=False):
Gtk.main_quit()


if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGINT, kill)
TrayIcon()
Gtk.main()

0 comments on commit dcffa98

Please sign in to comment.