diff --git a/README.md b/README.md index 2803131..dbd5909 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ auto-cpufreq is looking for [co-maintainers & open source developers to help sha * [Snap store](https://github.com/AdnanHodzic/auto-cpufreq/#snap-store) * [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer) * [AUR package (Arch/Manjaro Linux)](https://github.com/AdnanHodzic/auto-cpufreq/#aur-package-archmanjaro-linux) -* [Post Installation] +* [Post Installation](https://github.com/AdnanHodzic/auto-cpufreq/blob/install_performance_rm/README.md#post-installation) * [Configuring auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq/#configuring-auto-cpufreq) * [1: power_helper.py script](https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script) * [2: auto-cpufreq config file](https://github.com/AdnanHodzic/auto-cpufreq/#2-auto-cpufreq-config-file) @@ -33,6 +33,7 @@ auto-cpufreq is looking for [co-maintainers & open source developers to help sha * [Remove - auto-cpufreq daemon](https://github.com/AdnanHodzic/auto-cpufreq/#remove---auto-cpufreq-daemon) * [stats](https://github.com/AdnanHodzic/auto-cpufreq/#stats) * [Troubleshooting](https://github.com/AdnanHodzic/auto-cpufreq/#troubleshooting) + * [AUR](https://github.com/AdnanHodzic/auto-cpufreq/#aur) * [Discussion](https://github.com/AdnanHodzic/auto-cpufreq/#discussion) * [Donate](https://github.com/AdnanHodzic/auto-cpufreq/#donate) * [Financial donation](https://github.com/AdnanHodzic/auto-cpufreq/#financial-donation) @@ -102,7 +103,7 @@ In case you encounter any problems with `auto-cpufreq-installer`, please [submit ### AUR package (Arch/Manjaro Linux) -*AUR is currently unmaintained & has issues*! Until someone starts maintaining it, use the [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#auto-cpufreq-installer) if you intend to have the latest changes as otherwise you'll run into errors, i.e: [#471](https://github.com/AdnanHodzic/auto-cpufreq/issues/471). +*AUR is currently unmaintained & has issues*! Until someone starts maintaining it, use the [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#auto-cpufreq-installer) if you intend to have the latest changes as otherwise you'll run into errors, i.e: [#471](https://github.com/AdnanHodzic/auto-cpufreq/issues/471). However, if you still wish to use AUR then follow the [Troubleshooting](https://github.com/AdnanHodzic/auto-cpufreq/#aur) section for solved known issues. * [Binary Package](https://aur.archlinux.org/packages/auto-cpufreq) (For the latest binary release on github) @@ -114,32 +115,39 @@ After installation `auto-cpufreq` will be available as a binary and you can refe ## Configuring auto-cpufreq -auto-cpufreq makes all decisions automatically based on various factors like cpu usage, temperature or system load. However, it's possible to perform additional configurations in 2 ways: +auto-cpufreq makes all decisions automatically based on various factors like cpu usage, temperature or system load. However, it's possible to perform additional configurations: -### 1: power_helper.py script +### 1: power_helper.py script (Snap package install **only**) -If detected as running, auto-cpufreq will disable [GNOME Power profiles service](https://twitter.com/fooctrl/status/1467469508373884933), which would otherwise cause conflicts and cause problems. +When installing auto-cpufreq using [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer) if it detects [GNOME Power profiles service](https://twitter.com/fooctrl/status/1467469508373884933) is running it will automatically disable it. Otherwise this daemon will cause conflicts and various other performance issues. -By default auto-cpufreq uses `balanced` mode which works the best on various systems. However, if you're not reaching maximum frequencies your CPU is capable of with auto-cpufreq ([#361](https://github.com/AdnanHodzic/auto-cpufreq/issues/361)), you can switch to `performance` mode. Which will result in higher frequencies by default, but also results in higher energy use (battery consumption). - -If you installed auto-cpufreq using [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/edit/master/README.md#auto-cpufreq-installer), you can switch to `performance` mode by running: - -`sudo auto-cpufreq --install_performance` - -Or if you installed auto-cpufreq using [Snap package](https://github.com/AdnanHodzic/auto-cpufreq/edit/master/README.md#snap-store) you can switch to `performance` mode by running: - -`sudo python3 power_helper.py --gnome_power_disable performance` +However, when auto-cpufreq is installed as Snap package it's running as part of a container with limited permissions to your host machine, hence it's *highly recommended* you disable GNOME Power Profiles Daemon using `power_helper.py` script. **Please Note:** -The `power_helper.py` script is located at `auto_cpufreq/power_helper.py`. In order to have access to it, you need to first clone +The [`power_helper.py`](https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto_cpufreq/power_helper.py) script is located at `auto_cpufreq/power_helper.py`. In order to have access to it, you need to first clone the repository: `git clone https://github.com/AdnanHodzic/auto-cpufreq` +Navigate to repo location where `power_helper.py` resides, i.e: -After this step, all necessary changes will still be made automatically. However, if you wish to perform additional "manual" settings this can be done by following instructions explained in next step. +`cd auto-cpufreq/auto_cpufreq` -### 2: auto-cpufreq config file +Make sure to have `psutil` Python library installed before next step, i.e: `sudo python3 -m pip install psutil` + +Then disable GNOME Power Profiles Daemon by runing: + +`sudo python3 power_helper.py --gnome_power_disable` + +### 2: `--force` governor override + +By default auto-cpufreq uses `balanced` mode which works the best on various systems and situations. + +However, you can override this behaviour by switching to `performance` or `powersave` mode manually. Performance will result in higher frequencies by default, but also results in higher energy use (battery consumption) and should be used if max performance is necessary. Otherwise `powersave` will do the opposite and extend the battery life to its maximum. + +See [`--force` flag](https://github.com/AdnanHodzic/auto-cpufreq/#overriding-governor) for more info. + +### 3: auto-cpufreq config file You can configure seperate profiles for the battery and power supply. These profiles will let you pick which governor to use, and how and when turbo boost is enabled. The possible values for turbo boost behavior are `always`, `auto` and `never`. The default behavior is `auto`, which only kicks in during high load. @@ -310,6 +318,21 @@ GRUB_CMDLINE_LINUX_DEFAULT="quiet splash initcall_blacklist=amd_pstate_init amd_ After you are done, run `sudo update-grub` or `sudo grub-mkconfig -o /boot/grub/grub.cfg`, if you are using Arch. +### AUR + +* The command ```sudo auto-cpufreq --install``` produces error [#471](https://github.com/AdnanHodzic/auto-cpufreq/issues/471) please don't use it. + * This script is supposed to automate the process of enabling auto-cpufreq.service so you need to manually open terminal and type + ~~~ + sudo systemctl enable --now auto-cpufreq.service + ~~~ + for the service to work. +* Power Profiles Daemon is [automatically disabled by auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#1-power_helperpy-script-snap-package-install-only) due to it's conflict with auto-cpufreq.service. However this doesn't happen with AUR package and will lead to problems (i.e: [#463](https://github.com/AdnanHodzic/auto-cpufreq/issues/463)) if not masked manually. + * So open your terminal and type + ~~~ + sudo systemctl mask power-profiles-daemon.service + ~~~ + Following this command ```enable``` the auto-cpufreq.service if you haven't already. + ## Discussion: * Blogpost: [auto-cpufreq - Automatic CPU speed & power optimizer for Linux](http://foolcontrol.org/?p=3124) diff --git a/auto-cpufreq-installer b/auto-cpufreq-installer index f7bf855..9ca1dd9 100755 --- a/auto-cpufreq-installer +++ b/auto-cpufreq-installer @@ -63,10 +63,18 @@ function install { python3 setup.py install --record files.txt mkdir -p /usr/local/share/auto-cpufreq/ cp -r scripts/ /usr/local/share/auto-cpufreq/ + cp -r images/ /usr/local/share/auto-cpufreq/ + cp images/icon.png /usr/share/pixmaps/auto-cpufreq.png + cp scripts/org.auto-cpufreq.pkexec.policy /usr/share/polkit-1/actions # this is necessary since we need this script before we can run auto-cpufreq itself cp scripts/auto-cpufreq-venv-wrapper /usr/local/bin/auto-cpufreq + cp scripts/start_app /usr/local/bin/auto-cpufreq-gtk chmod a+x /usr/local/bin/auto-cpufreq + chmod a+x /usr/local/bin/auto-cpufreq-gtk + + desktop-file-install --dir=/usr/share/applications scripts/auto-cpufreq-gtk.desktop + update-desktop-database /usr/share/applications } # First argument is the distro @@ -140,7 +148,7 @@ function tool_install { separator if [ -f /etc/debian_version ]; then detected_distro "Debian based" - apt install python3-dev python3-pip python3-venv python3-setuptools dmidecode -y + apt install python3-dev python3-pip python3-venv python3-setuptools dmidecode libgirepository1.0-dev libcairo2-dev -y completed complete_msg elif [ -f /etc/redhat-release ]; then @@ -170,12 +178,12 @@ elif [ -f /etc/os-release ];then opensuse) detected_distro "OpenSUSE" echo -e "\nDetected an OpenSUSE distribution\n\nSetting up Python environment\n" - zypper install -y python38 python3-pip python3-setuptools python3-devel gcc dmidecode + zypper install -y python38 python3-pip python3-setuptools python3-devel gcc dmidecode gobject-introspection-devel python3-cairo-devel completed ;; arch|manjaro|endeavouros|garuda|artix) detected_distro "Arch Linux based" - pacman -S --noconfirm --needed python python-pip python-setuptools base-devel dmidecode + pacman -S --noconfirm --needed python python-pip python-setuptools base-devel dmidecode gobject-introspection completed ;; void) @@ -208,12 +216,15 @@ function tool_remove { tool_proc_rm="/usr/local/bin/auto-cpufreq --remove" wrapper_script="/usr/local/bin/auto-cpufreq" + gui_wrapper_script="/usr/local/bin/auto-cpufreq-gtk" unit_file="/etc/systemd/system/auto-cpufreq.service" venv_path="/opt/auto-cpufreq" cpufreqctl="/usr/local/bin/cpufreqctl.auto-cpufreq" cpufreqctl_old="/usr/bin/cpufreqctl.auto-cpufreq" + desktop_file="/usr/share/applications/auto-cpufreq-gtk.desktop" + # stop any running auto-cpufreq argument (daemon/live/monitor) tool_arg_pids=($(pgrep -f "auto-cpufreq --")) for pid in "${tool_arg_pids[@]}"; do @@ -246,10 +257,14 @@ function tool_remove { [ -f $stats_file ] && rm $stats_file [ -f $unit_file ] && rm $unit_file [ -f $wrapper_script ] && rm $wrapper_script - + [ -f $gui_wrapper_script ] && rm $gui_wrapper_script + [ -f $cpufreqctl ] && rm $cpufreqctl [ -f $cpufreqctl_old ] && rm $cpufreqctl_old + [ -f $desktop_file ] && rm $desktop_file + update-desktop-database /usr/share/applications + # remove python virtual environment rm -rf "${venv_path}" diff --git a/auto_cpufreq/core.py b/auto_cpufreq/core.py index 4931b99..fdc0a05 100644 --- a/auto_cpufreq/core.py +++ b/auto_cpufreq/core.py @@ -27,6 +27,9 @@ from auto_cpufreq.power_helper import * warnings.filterwarnings("ignore") +# add path to auto-cpufreq executables for GUI +os.environ["PATH"] += ":/usr/local/bin" + # ToDo: # - replace get system/CPU load from: psutil.getloadavg() | available in 5.6.2) @@ -59,7 +62,10 @@ auto_cpufreq_stats_path = None auto_cpufreq_stats_file = None # track governor override -STORE = "/opt/auto-cpufreq/override.pickle" +if os.getenv("PKG_MARKER") == "SNAP": + governor_override_state = Path("/var/snap/auto-cpufreq/current/override.pickle") +else: + governor_override_state = Path("/opt/auto-cpufreq/override.pickle") if os.getenv("PKG_MARKER") == "SNAP": auto_cpufreq_stats_path = Path("/var/snap/auto-cpufreq/current/auto-cpufreq.stats") @@ -86,20 +92,20 @@ def get_config(config_file=""): return get_config.config def get_override(): - if os.path.isfile(STORE): - with open(STORE, "rb") as store: + if os.path.isfile(governor_override_state): + with open(governor_override_state, "rb") as store: return pickle.load(store) else: return "default" def set_override(override): if override in ["powersave", "performance"]: - with open(STORE, "wb") as store: + with open(governor_override_state, "wb") as store: pickle.dump(override, store) print(f"Set governor override to {override}") elif override == "reset": - if os.path.isfile(STORE): - os.remove(STORE) + if os.path.isfile(governor_override_state): + os.remove(governor_override_state) print("Governor override removed") elif override is not None: print("Invalid option.\nUse force=performance, force=powersave, or force=reset") @@ -442,8 +448,8 @@ def remove_daemon(): os.remove("/usr/local/bin/auto-cpufreq-remove") # delete override pickle if it exists - if os.path.exists(STORE): - os.remove(STORE) + if os.path.exists(governor_override_state): + os.remove(governor_override_state) # delete stats file if auto_cpufreq_stats_path.exists(): diff --git a/auto_cpufreq/gui/__init__.py b/auto_cpufreq/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py new file mode 100644 index 0000000..b671891 --- /dev/null +++ b/auto_cpufreq/gui/app.py @@ -0,0 +1,87 @@ +import gi + +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, GLib, Gdk, Gio, GdkPixbuf + +import os +import sys + +sys.path.append("../") +from auto_cpufreq.core import is_running +from auto_cpufreq.gui.objects import RadioButtonView, SystemStatsLabel, CPUFreqStatsLabel, CurrentGovernorBox, DropDownMenu, DaemonNotRunningView + +CSS_FILE = "/usr/local/share/auto-cpufreq/scripts/style.css" + +HBOX_PADDING = 20 + +class MyWindow(Gtk.Window): + def __init__(self): + super().__init__(title="auto-cpufreq") + self.set_default_size(600, 480) + self.set_border_width(10) + self.set_resizable(False) + self.load_css() + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename="/usr/local/share/auto-cpufreq/images/icon.png", width=500, height=500, preserve_aspect_ratio=True) + self.set_icon(pixbuf) + self.build() + + def main(self): + # main VBOX + self.vbox_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.vbox_top.set_valign(Gtk.Align.CENTER) + self.vbox_top.set_halign(Gtk.Align.CENTER) + self.add(self.vbox_top) + + self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=HBOX_PADDING) + + self.systemstats = SystemStatsLabel() + self.hbox.pack_start(self.systemstats, False, False, 0) + + self.vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20) + + self.menu = DropDownMenu(self) + self.vbox_top.pack_start(self.menu, False, False, 0) + + self.currentgovernor = CurrentGovernorBox() + self.vbox_right.pack_start(self.currentgovernor, False, False, 0) + self.vbox_right.pack_start(RadioButtonView(), False, False, 0) + + self.cpufreqstats = CPUFreqStatsLabel() + self.vbox_right.pack_start(self.cpufreqstats, False, False, 0) + + self.hbox.pack_start(self.vbox_right, True, True, 0) + + self.vbox_top.pack_start(self.hbox, False, False, 0) + + GLib.timeout_add_seconds(5, self.refresh) + + def daemon_not_running(self): + self.box = DaemonNotRunningView(self) + self.add(self.box) + + def build(self): + if is_running("auto-cpufreq", "--daemon"): + self.main() + else: + self.daemon_not_running() + + def load_css(self): + screen = Gdk.Screen.get_default() + self.gtk_provider = Gtk.CssProvider() + self.gtk_context = Gtk.StyleContext() + self.gtk_context.add_provider_for_screen(screen, self.gtk_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + self.gtk_provider.load_from_file(Gio.File.new_for_path(CSS_FILE)) + + def refresh(self): + self.systemstats.refresh() + self.currentgovernor.refresh() + self.cpufreqstats.refresh() + return True + + + +win = MyWindow() +win.connect("destroy", Gtk.main_quit) +win.show_all() +Gtk.main() \ No newline at end of file diff --git a/auto_cpufreq/gui/objects.py b/auto_cpufreq/gui/objects.py new file mode 100644 index 0000000..9c9b8fd --- /dev/null +++ b/auto_cpufreq/gui/objects.py @@ -0,0 +1,272 @@ +import gi + +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, GdkPixbuf + +import sys +import os +import platform as pl + +sys.path.append("../../") +from subprocess import getoutput, call +from auto_cpufreq.core import sysinfo, distro_info, set_override, get_override, get_formatted_version, dist_name, deploy_daemon, remove_daemon + +from io import StringIO + +if os.getenv("PKG_MARKER") == "SNAP": + auto_cpufreq_stats_path = "/var/snap/auto-cpufreq/current/auto-cpufreq.stats" +else: + auto_cpufreq_stats_path = "/var/run/auto-cpufreq.stats" + + +def get_stats(): + if os.path.isfile(auto_cpufreq_stats_path): + with open(auto_cpufreq_stats_path, "r") as file: + stats = [line for line in (file.readlines() [-50:])] + return "".join(stats) + +def get_version(): + # snap package + if os.getenv("PKG_MARKER") == "SNAP": + return getoutput("echo \(Snap\) $SNAP_VERSION") + # aur package + elif dist_name in ["arch", "manjaro", "garuda"]: + aur_pkg_check = call("pacman -Qs auto-cpufreq > /dev/null", shell=True) + if aur_pkg_check == 1: + return get_formatted_version() + else: + return getoutput("pacman -Qi auto-cpufreq | grep Version") + else: + # source code (auto-cpufreq-installer) + try: + return get_formatted_version() + except Exception as e: + print(repr(e)) + pass + + +class RadioButtonView(Gtk.Box): + def __init__(self): + super().__init__(orientation=Gtk.Orientation.HORIZONTAL) + + self.set_hexpand(True) + self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + + self.label = Gtk.Label("Governor Override", name="bold") + + self.default = Gtk.RadioButton.new_with_label_from_widget(None, "Default") + self.default.connect("toggled", self.on_button_toggled, "reset") + self.default.set_halign(Gtk.Align.END) + self.powersave = Gtk.RadioButton.new_with_label_from_widget(self.default, "Powersave") + self.powersave.connect("toggled", self.on_button_toggled, "powersave") + self.powersave.set_halign(Gtk.Align.END) + self.performance = Gtk.RadioButton.new_with_label_from_widget(self.default, "Performance") + self.performance.connect("toggled", self.on_button_toggled, "performance") + self.performance.set_halign(Gtk.Align.END) + + self.set_selected() + + self.pack_start(self.label, False, False, 0) + self.pack_start(self.default, True, True, 0) + self.pack_start(self.powersave, True, True, 0) + self.pack_start(self.performance, True, True, 0) + + #self.pack_start(self.label, False, False, 0) + #self.pack_start(self.hbox, False, False, 0) + + def on_button_toggled(self, button, override): + if button.get_active(): + set_override(override) + + def set_selected(self): + override = get_override() + match override: + case "powersave": + self.powersave.set_active(True) + case "performance": + self.performance.set_active(True) + case "default": + self.default.set_active(True) + +class CurrentGovernorBox(Gtk.Box): + def __init__(self): + super().__init__(spacing=25) + self.static = Gtk.Label(label="Current Governor", name="bold") + self.governor = Gtk.Label(label=getoutput("cpufreqctl.auto-cpufreq --governor").strip().split(" ")[0], halign=Gtk.Align.END) + + self.pack_start(self.static, False, False, 0) + self.pack_start(self.governor, False, False, 0) + + def refresh(self): + self.governor.set_label(getoutput("cpufreqctl.auto-cpufreq --governor").strip().split(" ")[0]) + +class SystemStatsLabel(Gtk.Label): + def __init__(self): + super().__init__() + + self.refresh() + + def refresh(self): + # change stdout and store label text to file-like object + old_stdout = sys.stdout + text = StringIO() + sys.stdout = text + sysinfo() + distro_info() + self.set_label(text.getvalue()) + sys.stdout = old_stdout + + +class CPUFreqStatsLabel(Gtk.Label): + def __init__(self): + super().__init__() + self.refresh() + + def refresh(self): + stats = get_stats().split("\n") + start = None + for i, line in enumerate(stats): + if line == ("-" * 28 + " CPU frequency scaling " + "-" * 28): + start = i + break + if start is not None: + del stats[:i] + del stats[-4:] + self.set_label("\n".join(stats)) + +class DropDownMenu(Gtk.MenuButton): + def __init__(self, parent): + super().__init__() + self.set_halign(Gtk.Align.END) + self.image = Gtk.Image.new_from_icon_name("open-menu-symbolic", Gtk.IconSize.LARGE_TOOLBAR) + self.add(self.image) + self.menu = self.build_menu(parent) + self.set_popup(self.menu) + + def build_menu(self, parent): + menu = Gtk.Menu() + + daemon = Gtk.MenuItem(label="Remove Daemon") + daemon.connect("activate", self._remove_daemon, parent) + menu.append(daemon) + + about = Gtk.MenuItem(label="About") + about.connect("activate", self.about_dialog, parent) + menu.append(about) + + menu.show_all() + return menu + + def about_dialog(self, MenuItem, parent): + dialog = AboutDialog(parent) + response = dialog.run() + dialog.destroy() + + def _remove_daemon(self, MenuItem, parent): + confirm = ConfirmDialog(parent, message="Are you sure you want to remove the daemon?") + response = confirm.run() + confirm.destroy() + if response == Gtk.ResponseType.YES: + try: + remove_daemon() + dialog = Gtk.MessageDialog( + transient_for=parent, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text="Daemon succesfully removed" + ) + dialog.format_secondary_text("The app will now close. Please reopen to apply changes") + dialog.run() + dialog.destroy() + parent.destroy() + except Exception as e: + dialog = Gtk.MessageDialog( + transient_for=parent, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text="Daemon removal failed" + ) + dialog.format_secondary_text(f"The following error occured:\n{e}") + dialog.run() + dialog.destroy() + + +class AboutDialog(Gtk.Dialog): + def __init__(self, parent): + super().__init__(title="About", transient_for=parent) + app_version = get_version() + self.box = self.get_content_area() + # self.box.set_homogeneous(True) + self.box.set_spacing(10) + self.add_button("Close", Gtk.ResponseType.CLOSE) + self.set_default_size(400, 350) + img_buffer = GdkPixbuf.Pixbuf.new_from_file_at_scale( + filename="/usr/local/share/auto-cpufreq/images/icon.png", + width=150, + height=150, + preserve_aspect_ratio=True) + self.image = Gtk.Image.new_from_pixbuf(img_buffer) + self.title = Gtk.Label(label="auto-cpufreq", name="bold") + self.version = Gtk.Label(label=app_version) + self.python = Gtk.Label(label=f"Python {pl.python_version()}") + self.github = Gtk.Label(label="https://github.com/AdnanHodzic/auto-cpufreq") + self.license = Gtk.Label(label="Licensed under LGPL3", name="small") + self.love = Gtk.Label(label="Made with <3", name="small") + + self.box.pack_start(self.image, False, False, 0) + self.box.pack_start(self.title, False, False, 0) + self.box.pack_start(self.version, False, False, 0) + self.box.pack_start(self.python, False, False, 0) + self.box.pack_start(self.github, False, False, 0) + self.box.pack_start(self.license, False, False, 0) + self.box.pack_start(self.love, False, False, 0) + self.show_all() + +class ConfirmDialog(Gtk.Dialog): + def __init__(self, parent, message: str): + super().__init__(title="Confirmation", transient_for=parent) + self.box = self.get_content_area() + self.set_default_size(400, 100) + self.add_buttons("Yes", Gtk.ResponseType.YES, "No", Gtk.ResponseType.NO) + self.label = Gtk.Label(label=message) + + self.box.pack_start(self.label, True, False, 0) + + self.show_all() + +class DaemonNotRunningView(Gtk.Box): + def __init__(self, parent): + super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=10, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER) + + self.label = Gtk.Label(label="auto-cpufreq daemon is not running. Please click the install button") + self.install_button = Gtk.Button.new_with_label("Install") + + self.install_button.connect("clicked", self.install_daemon, parent) + + self.pack_start(self.label, False, False, 0) + self.pack_start(self.install_button, False, False, 0) + + def install_daemon(self, button, parent): + try: + deploy_daemon() + dialog = Gtk.MessageDialog( + transient_for=parent, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text="Daemon succesfully installed" + ) + dialog.format_secondary_text("The app will now close. Please reopen to apply changes") + dialog.run() + dialog.destroy() + parent.destroy() + except Exception as e: + dialog = Gtk.MessageDialog( + transient_for=parent, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text="Daemon install failed" + ) + dialog.format_secondary_text(f"The following error occured:\n{e}") + dialog.run() + dialog.destroy() \ No newline at end of file diff --git a/auto_cpufreq/gui/tray.py b/auto_cpufreq/gui/tray.py new file mode 100644 index 0000000..de0495d --- /dev/null +++ b/auto_cpufreq/gui/tray.py @@ -0,0 +1,32 @@ +import gi + +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, AppIndicator3 as appindicator + +from subprocess import run + +def main(): + indicator = appindicator.Indicator.new("auto-cpufreq-tray", "network-idle-symbolic", appindicator.IndicatorCategory.APPLICATION_STATUS) + indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + indicator.set_menu(build_menu()) + Gtk.main() + +def build_menu(): + menu = Gtk.Menu() + + program = Gtk.MenuItem("auto-cpufreq") + program.connect("activate", open_app) + menu.append(program) + + _quit = Gtk.MenuItem("Quit") + _quit.connect("activate", Gtk.main_quit) + menu.append(_quit) + menu.show_all() + return menu + +def open_app(MenuItem): + run("sudo -E python app.py", shell=True) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/auto_cpufreq/power_helper.py b/auto_cpufreq/power_helper.py index bd4330a..56acf55 100644 --- a/auto_cpufreq/power_helper.py +++ b/auto_cpufreq/power_helper.py @@ -129,7 +129,7 @@ def gnome_power_start_live(): def gnome_power_svc_enable(): if systemctl_exists: try: - print("\n* Enabling GNOME power profiles") + print("* Enabling GNOME power profiles\n") call(["systemctl", "unmask", "power-profiles-daemon"]) call(["systemctl", "start", "power-profiles-daemon"]) call(["systemctl", "enable", "power-profiles-daemon"]) @@ -256,95 +256,50 @@ def disable_power_profiles_daemon(): # default gnome_power_svc_disable func (balanced) def gnome_power_svc_disable(): - if systemctl_exists: - # set balanced profile if its running before disabling it - if gnome_power_status == 0 and powerprofilesctl_exists: - print("Using profile: ", "balanced") - call(["powerprofilesctl", "set", "balanced"]) - - disable_power_profiles_daemon() - -# default gnome_power_svc_disable func (performance) -def gnome_power_svc_disable_performance(): - if systemctl_exists: - # set performance profile if its running before disabling it - if gnome_power_status == 0 and powerprofilesctl_exists: - print("Using profile: ", "performance") - call(["powerprofilesctl", "set", "performance"]) - - disable_power_profiles_daemon() - - -# cli -@click.pass_context -# external gnome power srevice disable function -def gnome_power_svc_disable_ext(ctx, power_selection): - raw_power_disable = ctx.params["gnome_power_disable"] - gnome_power_disable = str(raw_power_disable).replace('[','').replace(']','').replace(",", "").replace("(","").replace(")","").replace("'","") - + snap_pkg_check = 0 if systemctl_exists: # 0 is active if gnome_power_status != 0: try: + # check if snap package installed snap_pkg_check = call(['snap', 'list', '|', 'grep', 'auto-cpufreq'], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - # check if snapd is present and if snap package is installed | 0 is success if snap_pkg_check == 0: - print("Power Profiles Daemon is already disabled, re-enable by running:\n" + print("GNOME Power Profiles Daemon is already disabled, it can be re-enabled by running:\n" "sudo python3 power_helper.py --gnome_power_enable\n" - "\nfollowed by running:\n" - "sudo python3 power_helper.py --gnome_power_disable" ) - else: - # snapd present, snap package not installed - print("Power Profiles Daemon is already disabled, first remove auto-cpufreq:\n" - "sudo auto-cpufreq --remove\n" - "\nfollowed by installing auto-cpufreq in performance mode:\n" - "sudo auto-cpufreq --install_performance" + elif snap_pkg_check == 1: + print("auto-cpufreq snap package not installed\nGNOME Power Profiles Daemon should be enabled. run:\n\n" + "sudo python3 power_helper.py --gnome_power_enable" ) - except FileNotFoundError: + except: # snapd not found on the system - print("Power Profiles Daemon is already disabled, first remove auto-cpufreq:\n" - "sudo auto-cpufreq --remove\n" - "\nfollowed by installing auto-cpufreq in performance mode:\n" - "sudo auto-cpufreq --install_performance" - ) + print("There was a problem, couldn't determine GNOME Power Profiles Daemon") + snap_pkg_check = 0 - # set balanced profile if its running before disabling it if gnome_power_status == 0 and powerprofilesctl_exists: - # 0 is success (snap package is installed) - try: - snap_pkg_check = call(['snap', 'list', '|', 'grep', 'auto-cpufreq'], - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT) - - if snap_pkg_check == 0: - #if snap_exist == 0 and snap_pkg_install == 0: - print("Using profile: ", gnome_power_disable) - call(["powerprofilesctl", "set", gnome_power_disable]) - - disable_power_profiles_daemon() - else: - print("Install auto-cpufreq in performance mode by running:\n" - "sudo auto-cpufreq --install_performance\n" - ) - - except FileNotFoundError: - print("Install auto-cpufreq in performance mode by running:\n" - "sudo auto-cpufreq --install_performance\n" + if snap_pkg_check == 1: + print("auto-cpufreq snap package not installed.\nGNOME Power Profiles Daemon should be enabled, run:\n\n" + "sudo python3 power_helper.py --gnome_power_enable" ) + else: + print("auto-cpufreq snap package installed, GNOME Power Profiles Daemon should be disabled.\n") + print("Using profile: ", "balanced") + call(["powerprofilesctl", "set", "balanced"]) + disable_power_profiles_daemon() +# cli @click.command() -@click.option("--gnome_power_disable", help="Disable GNOME Power profiles service (default: balanced), reference:\n https://bit.ly/3bjVZW1", type=click.Choice(['balanced', 'performance'], case_sensitive=False)) +#@click.option("--gnome_power_disable", help="Disable GNOME Power profiles service (default: balanced), reference:\n https://bit.ly/3bjVZW1", type=click.Choice(['balanced', 'performance'], case_sensitive=False)) +@click.option("--gnome_power_disable", is_flag=True, help="Disable GNOME Power profiles service") # ToDo: # * update readme/docs -@click.option("--power_selection", hidden=True) @click.option("--gnome_power_enable", is_flag=True, help="Enable GNOME Power profiles service") @click.option("--gnome_power_status", is_flag=True, help="Get status of GNOME Power profiles service" @@ -352,7 +307,6 @@ def gnome_power_svc_disable_ext(ctx, power_selection): @click.option("--bluetooth_boot_on", is_flag=True, help="Turn on Bluetooth on boot") @click.option("--bluetooth_boot_off", is_flag=True, help="Turn off Bluetooth on boot") def main( - power_selection, gnome_power_enable, gnome_power_disable, gnome_power_status, @@ -377,7 +331,7 @@ def main( elif gnome_power_disable: header() root_check() - gnome_power_svc_disable_ext(power_selection) + gnome_power_svc_disable() helper_opts() footer() elif gnome_power_status: @@ -401,4 +355,4 @@ def main( if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/bin/auto-cpufreq b/bin/auto-cpufreq index 822f200..d3880da 100755 --- a/bin/auto-cpufreq +++ b/bin/auto-cpufreq @@ -21,7 +21,6 @@ from auto_cpufreq.power_helper import * @click.option("--install", is_flag=True, help="Install daemon for (permanent) automatic CPU optimizations") @click.option("--remove", is_flag=True, help="Remove daemon for (permanent) automatic CPU optimizations") -@click.option("--install_performance", is_flag=True, help="Install daemon in \"performance\" mode, reference:\n https://bit.ly/3bjVZW1") @click.option("--stats", is_flag=True, help="View live stats of CPU optimizations made by daemon") @click.option("--force", is_flag=False, help="Force use of either \"powersave\" or \"performance\" governors. Setting to \"reset\" will go back to normal mode") @click.option("--get-state", is_flag=True, hidden=True) @@ -36,7 +35,7 @@ from auto_cpufreq.power_helper import * @click.option("--donate", is_flag=True, help="Support the project") @click.option("--log", is_flag=True, hidden=True) @click.option("--daemon", is_flag=True, hidden=True) -def main(config, daemon, debug, install, remove, install_performance, live, log, monitor, stats, version, donate, force, get_state): +def main(config, daemon, debug, install, remove, live, log, monitor, stats, version, donate, force, get_state): # display info if config file is used def config_info_dialog(): @@ -185,18 +184,6 @@ def main(config, daemon, debug, install, remove, install_performance, live, log, print("Show your appreciation by donating!") print("https://github.com/AdnanHodzic/auto-cpufreq/#donate") footer() - elif install_performance: - if os.getenv("PKG_MARKER") == "SNAP": - root_check() - print("\nThis option is only available on non Snap installs.\n\n" - "Please refer to auto-cpufreq power_helper.py script for more info\n" - "Reference: https://github.com/AdnanHodzic/auto-cpufreq#configuring-auto-cpufreq\n") - else: - root_check() - running_daemon_check() - gov_check() - deploy_daemon_performance() - deploy_complete_msg() elif install: if os.getenv("PKG_MARKER") == "SNAP": root_check() diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..a187457 Binary files /dev/null and b/images/icon.png differ diff --git a/requirements.txt b/requirements.txt index a2b8a36..f2bb829 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ setuptools psutil click distro +PyGObject \ No newline at end of file diff --git a/scripts/auto-cpufreq-gtk.desktop b/scripts/auto-cpufreq-gtk.desktop new file mode 100644 index 0000000..6201b1f --- /dev/null +++ b/scripts/auto-cpufreq-gtk.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=auto-cpufreq +Exec=auto-cpufreq-gtk +Type=Application +Terminal=false +Icon=auto-cpufreq +Categories=System; \ No newline at end of file diff --git a/scripts/org.auto-cpufreq.pkexec.policy b/scripts/org.auto-cpufreq.pkexec.policy new file mode 100644 index 0000000..6921712 --- /dev/null +++ b/scripts/org.auto-cpufreq.pkexec.policy @@ -0,0 +1,19 @@ + + + + + Run auto-cpufreq command + Authentication is required to run auto-cpufreq + auto-cpufreq + + auth_admin + auth_admin + auth_admin + + /opt/auto-cpufreq/venv/bin/python + /opt/auto-cpufreq/venv/bin/app.py + true + + \ No newline at end of file diff --git a/scripts/start_app b/scripts/start_app new file mode 100644 index 0000000..d5cc3eb --- /dev/null +++ b/scripts/start_app @@ -0,0 +1,16 @@ +#!/usr/bin/sh + +# load python virtual environment +venv_dir=/opt/auto-cpufreq/venv +. "${venv_dir}/bin/activate" +python_command="${venv_dir}/bin/python ${venv_dir}/bin/app.py" + +if [ "$XDG_SESSION_TYPE" = "wayland" ] ; then + # necessary for running on wayland + xhost +SI:localuser:root + pkexec ${python_command} + xhost -SI:localuser:root + xhost +else + pkexec ${python_command} +fi diff --git a/scripts/style.css b/scripts/style.css new file mode 100644 index 0000000..e56f3cd --- /dev/null +++ b/scripts/style.css @@ -0,0 +1,12 @@ +label{ + /*font-family: Noto Sans;*/ + font-size: 15px; +} + +#bold{ + font-weight: bold; +} + +#small{ + font-size: 12px; +} diff --git a/setup.py b/setup.py index 348dad9..461b309 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(name): return f.read() # Used for the tar.gz/snap releases -VERSION = "1.9.7" +VERSION = "2.0-beta" setup( name="auto-cpufreq", @@ -29,7 +29,7 @@ setup( author="Adnan Hodzic", author_email="adnan@hodzic.org", url="https://github.com/AdnanHodzic/auto-cpufreq", - packages=["auto_cpufreq"], + packages=["auto_cpufreq", "auto_cpufreq/gui"], install_requires=read("requirements.txt"), include_package_data=True, zip_safe=True, @@ -40,5 +40,5 @@ setup( "Intended Audience :: Developers", "Operating System :: POSIX :: Linux" "Environment :: Console" "Natural Language :: English", ], - scripts=["bin/auto-cpufreq"], + scripts=["bin/auto-cpufreq", "auto_cpufreq/gui/app.py"], ) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bcb8333..9a26f35 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -43,10 +43,6 @@ plugs: interface: system-files write: - /etc/auto-cpufreq.conf - opt-auto-cpufreq: - interface: system-files - write: - - /opt/auto-cpufreq/override.pickle apps: auto-cpufreq: @@ -60,15 +56,14 @@ apps: - cpu-control - system-observe - hardware-observe - - opt-auto-cpufreq + - etc-auto-cpufreq-conf service: command: usr/bin/snapdaemon plugs: - cpu-control - system-observe - hardware-observe - - etc-auto-cpufreq - - opt-auto-cpufreq + - etc-auto-cpufreq-conf environment: LC_ALL: C.UTF-8 LANG: C.UTF-8