From 31b1ddec0815871bbedf4c2d985b913a52e56be1 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Mon, 13 Feb 2023 14:13:11 -0600 Subject: [PATCH 01/12] Squashed commit of the following: commit ec7e087b343f33af7a92b190ed666845dab60f20 Merge: 32b6e63 9912f9d Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Mon Feb 13 14:11:21 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit 32b6e632830fe8a7c3a3ce601ef2ba5b254572d7 Author: shadeyg56 Date: Mon Feb 13 14:10:34 2023 -0600 increase MenuButton size commit 12a2cda82a5f56bfbb532c893d822341e9b2d7cb Author: shadeyg56 Date: Sun Feb 12 23:35:33 2023 -0600 set app icon commit d170d07e866644bcb662bf342530c497390f0303 Author: shadeyg56 Date: Sun Feb 12 23:35:15 2023 -0600 remove unused pixbuf commit 993333fb5aad448103e24e38bc85fb04e7e4dd0c Author: shadeyg56 Date: Sun Feb 12 22:43:56 2023 -0600 fix MenuButton icon commit d1b8bd74caab2cf216bf40bdfc35ef3520e891c7 Author: shadeyg56 Date: Sun Feb 12 17:19:32 2023 -0600 add icon commit 13f43fa0c7d59b2405bac688119690d01a7ec019 Author: shadeyg56 Date: Sun Feb 12 17:19:10 2023 -0600 add pkexec policy and change wrapper for gui commit 4ddbb9c6667aa97bb7cd242fb4a376e061c9b308 Author: shadeyg56 Date: Sun Feb 12 14:09:35 2023 -0600 add icon to destop entry and about dialog commit 9912f9d80935bb51c9cb759ab95c9ba7fde15524 Author: Siddharth Kumar Jha <101443068+siddharthkumarjha@users.noreply.github.com> Date: Sun Feb 12 12:55:14 2023 +0530 Update Readme for issue of auto-cpufreq not autostarting on AUR (#485) * Updated Readme for issue auto-cpufreq not autostarting Added solution to issue https://github.com/AdnanHodzic/auto-cpufreq/issues/463 * Fixed minor errors * Fixed auto-cpufreq.serivce Typo * Reprashed text as per suggestion commit 4379024adab282fcdb58a7091df2a6adf833e700 Author: shadeyg56 Date: Tue Feb 7 19:06:59 2023 -0600 add removal of gui and desktop entry commit 42693703324762418a7f810f04a3d77803afa236 Author: shadeyg56 Date: Mon Feb 6 23:28:44 2023 -0600 CPU stats refresh and style stuff commit 7149db72c0bb39af57ceccb16df8e9164c77fae3 Author: shadeyg56 Date: Mon Feb 6 23:10:29 2023 -0600 daemon detection, daemon install/removal, and layout stuff commit f7e03c9bcc2e2578703807ceeb7a652903571b71 Author: shadeyg56 Date: Sun Feb 5 19:26:42 2023 -0600 improve AboutDialog Please enter the commit message for your changes. Lines starting commit cb8cfe7b3194273dea229fe550fdf66cd6af82fa Author: shadeyg56 Date: Sun Feb 5 18:28:19 2023 -0600 add dropdown menu and (not completed) about dialog commit ade1fed790f2ebbaae4e92ce8879ab812c9b2850 Author: shadeyg56 Date: Sun Feb 5 15:57:17 2023 -0600 actually fix css commit 67c8c97bcf2f2f408431bc162c38dee5c278ed0b Author: shadeyg56 Date: Sun Feb 5 15:54:47 2023 -0600 fix css commit c65eac3472f724bed10a0f530b2a828463bbe7fe Author: Adnan Hodzic Date: Sun Feb 5 09:29:19 2023 +0100 Update readme, install psutil lib for power_helper commit bd3feae38824764155d31ccd6debc1fcf18830da Author: shadeyg56 Date: Sun Feb 5 00:42:52 2023 -0600 add desktop entry for gui commit 5426a6a443c90a9d65b858ae1951219898cba332 Author: shadeyg56 Date: Sun Feb 5 00:33:08 2023 -0600 add gui to install file, update required packages, and create wrapper script commit 84124dfa605b930b3722ed852486cbe11c254092 Author: shadeyg56 Date: Sat Feb 4 23:28:24 2023 -0600 create system tray commit 7b0d46d8ddafdeff9006a1ad10ccd99c24b9eb1e Author: shadeyg56 Date: Sat Feb 4 23:28:01 2023 -0600 small changes to layout commit cd51ea317085ac6ea5b4918ef057ad4c19a71640 Author: shadeyg56 Date: Sat Feb 4 00:44:08 2023 -0600 css styling support commit 136b449febece04c036b72b4a14ccceb422f62dd Author: shadeyg56 Date: Sat Feb 4 00:14:52 2023 -0600 several improvements commit f9f7170391e1abc1499b7d5ae93327999e013730 Merge: 242a8d0 8f343df Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Fri Feb 3 22:44:01 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit 8f343df8b8b82846364fbc14b2fc2e5144abebaa Merge: dadfae0 b38919f Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Fri Feb 3 14:54:11 2023 -0600 Merge pull request #481 from shadeyg56/master Fix uncaught error in checking for snap commit b38919f56b6329c81c4c96eabe9bfd27d590d16e Author: shadeyg56 Date: Fri Feb 3 14:48:01 2023 -0600 fix uncaught error in checking for snap commit 242a8d0401d1ffd00c982aa731c04ef7ea726fee Merge: f50b982 dadfae0 Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Fri Feb 3 14:19:01 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit dadfae087f102c0f69329d5ad79e3a648c35b459 Author: Adnan Hodzic Date: Fri Feb 3 20:02:03 2023 +0100 Update README with new config options commit 471611de7da97b7da8580646612d4f910311e16e Author: Adnan Hodzic Date: Fri Feb 3 18:44:33 2023 +0100 Remove GNOME Power Profiles Daemon performance install commit f574257dc475f942fa39179787acf09c567e76dd Author: Adnan Hodzic Date: Fri Feb 3 18:32:04 2023 +0100 Remove install_performance flag commit 04b878360c7dff4b0e44fc8935e9667a597806b4 Author: Adnan Hodzic Date: Fri Feb 3 17:52:20 2023 +0100 Snap tag 2.0-beta + governor_override improvements commit fe21ddf24585164ed0f8cfa8f9ad7d3db627ab79 Author: Adnan Hodzic Date: Fri Feb 3 17:28:12 2023 +0100 Working governor override on Snap package commit f50b9829e5438bb96ded73775668fa048fcd3951 Merge: a98225e 69ef913 Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Thu Feb 2 18:19:10 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit a98225e7280802c2fb09a9a52a40f7e55b26b3b1 Author: shadeyg56 Date: Thu Feb 2 18:18:43 2023 -0600 Revert "basic GUI" This reverts commit d2610c921b7209fde7d7be13f187510631cc33b3. commit 9606472fdbbff6dd551cd291b37349df17098d2e Author: shadeyg56 Date: Thu Feb 2 18:13:41 2023 -0600 basic gui commit d2610c921b7209fde7d7be13f187510631cc33b3 Author: shadeyg56 Date: Thu Feb 2 17:47:55 2023 -0600 basic GUI commit bdbe12018b380ed70f100729c9ea3732ba24f572 Author: shadeyg56 Date: Thu Feb 2 15:21:42 2023 -0600 rename folder commit 31095c472e4af734a3a601f804787de89ad47964 Author: shadeyg56 Date: Wed Jan 25 23:39:52 2023 -0600 add tray --- README.md | 55 +++-- auto-cpufreq-installer | 23 ++- auto_cpufreq/core.py | 22 +- auto_cpufreq/gui/__init__.py | 0 auto_cpufreq/gui/app.py | 87 ++++++++ auto_cpufreq/gui/objects.py | 272 +++++++++++++++++++++++++ auto_cpufreq/gui/tray.py | 32 +++ auto_cpufreq/power_helper.py | 92 +++------ bin/auto-cpufreq | 15 +- images/icon.png | Bin 0 -> 106170 bytes requirements.txt | 1 + scripts/auto-cpufreq-gtk.desktop | 7 + scripts/org.auto-cpufreq.pkexec.policy | 19 ++ scripts/start_app | 16 ++ scripts/style.css | 12 ++ setup.py | 6 +- snap/snapcraft.yaml | 9 +- 17 files changed, 547 insertions(+), 121 deletions(-) create mode 100644 auto_cpufreq/gui/__init__.py create mode 100644 auto_cpufreq/gui/app.py create mode 100644 auto_cpufreq/gui/objects.py create mode 100644 auto_cpufreq/gui/tray.py create mode 100644 images/icon.png create mode 100644 scripts/auto-cpufreq-gtk.desktop create mode 100644 scripts/org.auto-cpufreq.pkexec.policy create mode 100644 scripts/start_app create mode 100644 scripts/style.css diff --git a/README.md b/README.md index 28031318..dbd59099 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). +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. -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: +**Please Note:** +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: -`sudo auto-cpufreq --install_performance` +`git clone https://github.com/AdnanHodzic/auto-cpufreq` -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: +Navigate to repo location where `power_helper.py` resides, i.e: -`sudo python3 power_helper.py --gnome_power_disable performance` +`cd auto-cpufreq/auto_cpufreq` -**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 repository: +Make sure to have `psutil` Python library installed before next step, i.e: `sudo python3 -m pip install psutil` -`git clone https://github.com/AdnanHodzic/auto-cpufreq` +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. -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. +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. -### 2: auto-cpufreq config file +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 f7bf8555..9ca1dd98 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 4931b99b..fdc0a056 100644 --- a/auto_cpufreq/core.py +++ b/auto_cpufreq/core.py @@ -27,6 +27,9 @@ 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_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 00000000..e69de29b diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py new file mode 100644 index 00000000..b6718913 --- /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 00000000..9c9b8fd0 --- /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 00000000..de0495d1 --- /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 bd4330af..56acf556 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 822f2003..d3880dad 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 0000000000000000000000000000000000000000..a187457654585b1964eef64cc38bd4bbb35f528e GIT binary patch literal 106170 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�bK?5pK+&LuA8a;q;|;TR1k(UnNeY_^G+hS?}-b9;V&pVewH24r}Q>h_16rr>dFLx z^%KX6Y`*+MT!yJ9SE$Ws#r16;_xy1$n zdHI)sXtma+8Z(*M^~#rDPE1T#dhaba1E&Lph8o2s2u&fVj?4U^l_H;wiqSS}OuvWG z7H5Zl9Lv!8@!W9MCt1`ZFmpXIq~Ak+421ddSO$$Ez07JLGaK(G0JE|&a(*j9l;N^8 zFcz@#+NX1Q>}ck2=uA}=UCH($!BcnLlA^q(-R=C@uRZ3rjDcaJ-?e?M6g5~@Y@#eM+VMQWqL zRz?OrBb{J!QG$&Nq+!lbC+J(fc9oNY2yy|id8d@?A+bZ4!}nR~X-T{tleZSv(G>V_RBgGv;hgM#D!2QDuW0*=- z!wme3(N+3IAxr%k{qT~>1r*C}wVv>v)zE*Z3h-vDSikzOUoVlPV{QooMMI?>psm** z`e*0+>n3=f?X;^Q44`55u#5}PKM*2rwGv75m!Qx&LdE?D;a(53Z|=JPn_ss4nXe;d z0nQKhQRDZX$W_uzW?t9TVKNoQp#j<%M=8_Jzyh(f`YVxO)X)< zkpAIUjQ-vWx$4D!m*ED06{g*bXt0myLOl%GjEG6KrvaLALAVA#7KGmzhA^1a!NMn% zsm;OI1duRN3T`q))pRwRVOycia1Z36|D+mLI{NvA)PB2E)V>VQ+=fb8|Ngg+{`;`)0J4K z9{D6+fLl=zA5S!XZ+&%G%_Eype0GsvGypQ@esx`4-2jRtf);yd9=I9+PGqocaRMWI z8E;TbvjTsHCj~Lu%4?I=P;}7TTuB8po}G;y~d@dgKO&p$o3#t zyo7_RikO;$CPrNCvl5F%9VU@22xZaW`1d?z52keQ>kARF&&Pi}tyn?>b6&GHc zVP?>|1PMLtp=o5)nrAcpzqg{jtFNeVp7=5Mse(d!VkT_xl{>D?#}IfGmX_sHaDP>IDJC z&-YM~;qNSTu5AtaHA;MDD?x-j*)`N4MpeRsO%W*nsT=US0FfX#LTOi`6A8TJONbTX z*j4_}r+-{gKl0&2tPMX>T?IfvBlJjvRj1SJ9`s_|JsySSkWH4F8 z#oYj1Upaw&7L@5el)MoFhIf%SCuJhS#B$9>+(u<%6#J=}u@vQEdC68iBS=hXz=K55 z?azfNA<6(ctaaP?X2i`vpa0aWN$h=~f@VLLGT8)j+61xDzvaQmJ(PJRKfYzl1sbes>^iKs9o};k9rMd1R%OALb_aT2Oow zd~DZ`Ns)}m)uMP5jd0C`4-(N$cwRdR)B;rCYVdIZEnu!_{#p3M69^yO8W$G9l&jJ0 z!L26J2@86@M{*iwP>?+exDMky!-hT~d{hU#aAfy_34j8Z&50z~fdruLPC;UhR?QL1 zO*AN$VnC*H_a`Kxqealez5-)@=?}$8`*6`Kt^EA}u9V(a2E_`*t!8T5==D>u zo(G0dS{?!Fu?GRD0ruFY$U85b59&D4?JW>B?=chK7qk@l@yt;3;#_(XCx{l9yu;jM zf-Cf;joRYWOVB4)Et9a|iGukgz?`?OxB&Qs3VSY+ug?fFCBQ%%mrDD|S0k7$5bn5B zdVys?sTxq$bOM)t3{d+BjvdOA1S}cKtFSDX(z{=jASaVSvxaxdb)(>U$NB~)s@E7y z4=3T7qUu-cX>mXngIWa&BI32J zR!fyXl1xlk5y%`4`{j8jq=vP}F{D=pZ5pppoFKY*e@A(37u7EKOdIMXn2)W)GH`?fZ~;ykY)Wu6Jv}`^q-0Q{yAcKm z%;r?{f~4@H!=yA3V&uVNu7LJ=r8cZ#h2fDWpb*q}gV8HR&<@OenWJk^Wq&u}LA~tS z&q8P}fkVombT7diB(We`0D};R)F4^ITERTE0?zVOC-P)n8OUm{LGeMJBj6-``}VCb zycOg&lzT5&db8ow-tT;WbRE^bg-whA{~bP7HAqt^GfiCnu>U-CBTE!jRSBO0mXOOC zDzWILKy6$nC#O<7!STId-LVxHQIrX-W%g*ajpnhiHGC{dB&Em=NDp8lK#9+u@X<1* zqlHi0?;_y?Ucil{n-HHr5Zt7rp%I(92VsX87rE>sIgOMm_&Prcit;TXdvP85#B&G@ zJnowlX2@JQR2o#HcxE#ziG+Hmw39fXp!W@Oe0#iEO%PV7t6M}rZd>w*G{;+I~6FOtR!{4P7NSu9b!#rOH0 zKi+}r1_o2e_J_nT)tSFTl8Z4vHahG`f!-{}L)YNu_cjyy@VPeFb=0c@Ajb$W#;+J1 zzDiNZ(g4@15jSC!JPR{3!Vxr>ivSv}VDav*uDHWaY1)mA>z~Edc8tXDtfD>|gFLD& zm;~|jE^WZT|HadFfK%DO|Kr$_m4sxAjFN~-B3p>e77Ce(vLZP~8Icvq$cjW6MMZ=X z8bY>`Rb(Z}DC_@u-rsfo-|M~J-}SqSbIx;~=l9?-k{l|o`u)yT@V-T{ zRXap(`{8Am%ij%b29usNmpWVMw;5xv7n3?h8#5~dUk~kVdEly3`tG_FZ z8$bK68ExSL6bi%;7X^$HfR-!?CKL|+m&HY{td0#eKeHGqoJQ8wBsh9uxs~Onsr`Pj z?SaXE%F3GH8_~_44erYp{-3T9o#1=yi2!31(LQf8mY3J?9OtOTgvHnk? z3D5oDLq2UxiHnQ#OvSNaiYN{%NDowvIRV)>;66kaiK(e6WJfgMzN!wu8{YG6o?S-&DZ%!hyn< zHJ>(Z%I90Qe!%NuH1DHCdiOGlJCpCjSm)|2Iti}APQMa0${XA^MA@p7vaB0e9cVN91E}|oOZC0$LQGU#NUc~kLyGr*7onYpgxO~ey<@M z_icBVe9CNbsv}vDmMHM=-U)LvcSWSu6r40lvd8t#mf&EZ0mFpBGBz>s4IX>QHC~@B z1tusVB_+ZU;ePL$aYte_jx>b=Fd`_`8*mt(D05W%4`bViaxQb6>WiC?+~P@?6q))_ z`(W)sbNuV?6=0mV@o*rd0!Pv1Puuoi_*!*O30U+tz_0Z~?g%{s&Pd~U!ql{^k>(+9 zQ^@Xz6AF*$&lZ23FSVHI#Wlgv+w(IpyVB2M$BrFTIwsF#P*%aK=7Mg5jva{O^Qp)0 zAs;~J4{Y4|e`aE!V0*^J9D5&&oqMux$Np3Wmn699`} z1ft@h7!&qFS!T0En~ikTz-7d^ZXh41 zu%Kw8QUBL-PmZ5<5{VRsOtaJPDzDd8ug_|nnuoKDE^Y-?XJTb_m-7zlRp+KlD92>9 z7W)8P?i;_CfQR5dtY4=tH7lUu6=WMkm#|$*Y6GNoFgL!&-25(>b_1cX1bVuHf~5qx zt`@Utpk5lUdZybfN-dB9E!2R>0G#<(0zptiT z^#i#jgy~nWs=(d=VG$JcjQuvUQ)fP;8#?^{QgE`~QT|lNveoJ8$n4~(@q9^M)9$Ad zk$_ntbHw+Ni*f`FAu+OG>V5sAEob)~{kJmLCc?#)Ze22dM(S`l9Y8df@bJJ7lJ2Wq zcp0fY&dY^##6{DS4`R2A~Ym+ z?liQrI4{?he`D(XQmTI5eiux*7)zS-jbAylr7jE#@T4%b=CkHE)WE++uy@4!O>Q3D zKeW)@+9QB&`s(s@ z^_ms&Ft{qz~OT-Up5(D+qMe7&Dd)wZcy{f<~{NJ=&5Lh0A*F6@& zHC^0h)J9Xwo@usPcmRJ*WV2)28t+D)M?9_UnVM_eG7DT(<(;beds8+$ZHzC-_}A5= zopTP;oH1u_nNtF-4t1L9pX*!QB&N_JK9Gp?x=Mx5@cN!}w0@V>HHHo~hr4I7EE@l0Neljv#M;-^9mn@$C>c4iPAcIbnE8x=T zhf;C?#j18koz1UraMj53qA?SS6viL)GOSQZ(~Uy^=V^bQe{l@}@R{uC)$Y^I>NNRu zko*$*1*q`B)Z54=Ki)kus|1ldF*^|E7+^^zNS47I@_+#{OPnG98@u&;mCP{L;s7Ol z*@S-vbqn%Gv2OFi>EcpFAVv_9l~X@f&uT3}IiAIgF^9po2yfhP zVTv)9gFTaX9m#>PhAJ>IF#!gHZ6X$wwH!Mhz^3=_Z9xQphHi5d<+@?EtYj8v&y(;L zUZ)QkhXmBvl+Yp*x@K?olErrr=|peUG$!!rR4NfTmVT~4RMiLtOf$gWc&V!xK~U3F zfQJ!|WaloU%)f;440tiDrf+_G=&f=X8dB2Dgeb86*=-i5eDNH&^_5|Uyiv48`fOm+TtZ6)B8C)%yj62m$3cDAm#H zkYhOYl@SOL=30OfCc@-}y=ypr|Mq7~+Do-z7MV1e9Lv6L|3T+PX9K+esd z-1#3f2pC{1CZ(zgu#nG?Ehv&ii0I7ErxkA}u>==1tV6dkf=M+GK}fnH*~W_x4B6cU zM~Yu+v(aESDi5aw@T{Xj7mGe58f9z7j^l>V2eC&`0{5)>XSjGpP*!=?3)YJwDbO6# zDP3XDR9Mj6HZikZ7iI?28w@(-u>f^&_uH+28+rmE5T(vWPz0V>P1?;tq{gQ%@5^Hj zHr(lEt`|vRpuQ@o#mB>AEN*l3N&MX;&F-E3&xgB&9|Gm$J*b5C|+|1B6 zGSG!+ucV{j>vXqU)=IK3!o#W!#QO=M!@~myE%)Pi4&hR6>Y0igLd8{wQ$E)29CqDs zTS%f-pdJm+e0SrKV*G z8{x`mu@G^pl1w2jZCS8id1Nk?Wu&ubZT?o^CMx>5-VlW~vqL$Nl=;c-9Eo@Qy;Sts z1+xOs{Q^Z_2Ej(pK}G*Mi+jDg#f)%Cu4`+-2tp$E&QEDx9Za7q)pLk3pjsZpE z#+f6D9;TevKr6nKk^KjW6KySwSmr16M7c)lA~ro=>-yZg(UmDtI4*pC$lC3K8k^Bs z52hS@&T-GQ&O*gU7LUGxD75L{^&T84U`?fZ@J7 z28C;aNQ{FS3gA2nNA+AmVNuE-{G#_Z-^tl5U-hJ%IalmmhL5s)s>eT zwKZ9q%iYXyGXN@uA|=lLZ}{Rbl#Y`I+6%<72~Z{d*r`)#0WevKMZS1*c8Qv*=<1KP z(AgOJ2(8!QVU8q2PdUo}yJTQb z=Sa5MweW1Lm}mWZ<5%Iq0VN3Iy{f98P*Vcv*vR;D5P>1IH83$Cf`UigpVh*-b4K|X z@qqlikXFDKfsb>WffR2aQn<#0-pGYve$BZ3Sd6@5C5Dhr4@-Ze%SY{E1P-YjAAAI1 zrMI}ZUDJkLPd40fln-@(-cZ97FZg)RDZ>|e+LxA7i)V*7K4W7HgO?aO??@;ZP9Jb= z&145qGIdjge@YOL%VznJz#sW-)d1a1cw2cKSu}$FDdk8)uf?t#&pcsm%A5EL)NHYS zTk>Px%@56QxqiCGs5*%5>iKW{N9Rqlhe9Af$6^4AKji{)79Z?yiN`d5a18(k>DKqL zvHQ*+Uj6y==b(8yAQ_UTZ9`PWMa)qGhuwA z@sk(M{aku>yuxk=hAF&iS|-6U2m!22-iK8j#fN<1Gt)4$DBy9X0Ega_l$*}1MW>#- zpSdVi0tvk#@a{#G+oeY$Obch}RoNIc$?^(3@m5&AavwP5+ zXn(n(E>myPd`cE5$+ta4r2gWU9O6d2jDjmC^ViP^GYM--q(xW3 zpa8PD5=MYDS*TxX}q0 zEp!M;5X^1dhELoIv0XJ)R1IpmiJK}5otKLO_@^6hZsxmsdJSYGHh%gGZH%t#>uZOT zMI%_o?nVnp&aD)BHHii#XGq^jWf}WpGp3yoE6P~h{T@ZasV!S`5v?DZRU2Rhz#!2$ z{ueopxQfQ_7ZKqNog+YR2-U7$5L9CWJEgOCTU%5hxpT=H>1ZQp|GK9zQHYxKcD(hp zrgEK6Pf=m@s!HLr*!pBGD^eT*AF0i{F?vdTqW~mEVNJEE4`=t;D4=Q!|ZT3DHgPkhC>!JCd-skzI%HO8HHT z&0_qoGegYN4QaMhF}8F<=)MoU>jBrE5+Qrr!VVWIU}OmB{QLS5l+#331@e&rD?g?Y zlaC6k$uo{7QbG=1g}%BM{wsJWJz_Q&Ucat2o#m$pEDSUv?qPnvA3~r3FMsbTwIwi> zS5ObERXmL`DeeaIN8Cn`W`Q(9JR#n0*q?(NxTN#DMtJwSTE2(1IJ?d9Y$5qqnPbrF zyISEQ8!VM&$#St>za1&}!V2U%+ve~V%qAU)tk^?qMPHdnS&#eAf|?Nu$Eo@4n|(Q z3gm4bCZ?+=KR4cQ9|xucb%wy(eSeG)jPBwkkOHn{rgYW8eP63kR=tV!FBukYnR7I2 zU+)%5`?vzaJo0yG-jJ04U99=s9wbAw#kb9jL16iy^ri{0PhjVgl6s@p73Cp%L-;Ch z4ct>xNcZGHVp+f2dgIpz6O2ZSi#J7YpHx@3T{nI7DAW9eX|#Fgc@_wNdtC|Z8%qAh zWZ5%hz> z^IOr(Ih)s}dK2lfbV5>61By9ek_26dkb$lrl3$Q60^vVYzfuq?A?x>R*$ZWEd{KC; z8X?yhdH9_bhkQ(mi(3`gFj99d{9{&VU>R?paKH%B!G}^Zaa=tugrDcths1a;V`fd*4rx zbDhp&@DH(xd0*(&a;kHadwe3kPx44a#ooZz+XrV<|Gbmy`b?zB-@3^%uR$_x4luhw z!fDXv_%LzRN8$c+h$Xo(+HOBQHe9Uw+JO}+e<-$&AHYc*uh44Ymlji=4 z5l;|7pu9hP@~?~IJr;!xf>Kg9Fq)Sjoq^!BWw}A5&I~c)E_UrJi-1}>9sZ4`+JsnY6`!-ZeGb!oG| zYLI%CKFbZxeENp(i_WrWGr2$``le~L+v%pE&6@1R-ABMX+%ho~QdE4V=1QIKRcie| z7zMq(Zd`8VJZ>8NWFCYm08Ou`Sa-HKJ38KD>)e3b02K>D58j-%COCwiw(t@;4ul8} zA{XU9X#Cd)&_q?0`!3-Y11FG`WUnH66GIOtANviW;Y%P?;~FA@Hh3zws^_jFdJ|;Fkfx)yiIBrPDNy zPwyvAXZ&_-Z^EegRrVdAdw;W_5f z8S}|aKg1$vGp;s3Py6bO_wdF>x> zm`q;IcC&0>MfC;>gq|HjpQGqpCZk9c4Gm`;QCM~`2#B>2)cV}tiDiD@WmH-;pof@* zADomWV#p{&S#;*xis5&@T|782Adm^d3mSBnAf6v~&;C4)B z4)@MaV^#raaAT{q3% z*vRsg&@R5jB{)>DYI&WRC%Xz7k{H0kuUlC7l-P{pWhdtK{rot`PWi|@N+}msG>tUf zXb=RviwHw1KJ<(2|HF_#*Svw_Z-eYU&BJ2?9mlCM^Gc@wV`uE<~eQJkh+63Fbdd0vjR+`%nu+| zACXu8V3&L#iA6gUs&^GyZ9-yn;tzED+vbt79i{yJmPz;tX$&|tsOsfYJ;(C-g?x;vLPaUV~3mH&|uaXSEu zT?byKM@~>fx84a_o;2 z**f6hqM)xmv4sQyN9>`5cibfro5WT8KA(S9UI|%95gKjgaULFIk6c~)bLr>&!cgx6 zYxjq5n^}5~Ds!kQaSLTiG)X+ycax(yS?dqoDWqekmiyEWZ7c-e=pY$m=P+i!RdAY_ zxJ5D&F-KGn8a)fSU5{Tr%0?t1XrQUSa;Y8UiMD<9^M0#WC#6guDdlBl887zSRGQqQha*FDGI9kgw1 z8wuVJk@}F6LyVV^^0@eJ9aHeV*I7^E=)V1Z$Gy6gpwEARg}tfi1GX7#V!DR>Nn&K& z2WO$bh?JC+{n)cz#GCDeJzx^RYfhV`_{B9=_-f-lv&P28s5=zdORPKmzTpm-XZ2z! z*s$>fi-U^i{`4IOZt5-1(U(+1a0ha3@%(3m3tCQlBgqVIM#6qza`d2r1FU!IE;->R z5Tfc_@D99_8hokVFRHDy+qH$+p=}8kEJ25oITViNO40ue&bO#Tn8HBA^7qmXN4ijn z$fn2o_=#E~OJMyyAt8lMiy=6cdBPCM@SMzYGwnzJjT6|U^pUG)I#~}HS)Pg_A&Z2K1~dR3qYo(xH=y4iCHZu99e(za0VlT( zN-KfqvV`S4Wxam_fHMi&8wxlc{KL|nkQ}+{p&JhhAxk~%W(MK+HBjsiZ6Al93zHt? z{PytML5qeA4M?2CIar?f^~#y=I&y2aliE)=j3Z{#x0%2F65}{MB~(%IxS2maaX3nZ zmlACYvKwNuV0AE;OyS$c>2;V85WozF!jH3+nNIz@z_IeI2vT%q6bDOA{aFSrkzgz9 zii`(Yamc_BM9UZ#=h2k$=;vj0=18O)|609Fcth+knI&32>at4@CaOAy-jxcV$qORd zH@~5NZvT&ShD<3uJ~aF3;oJ6XG->fHy5eF-_R#2Mc9>Y0`IUVYVlM%g9P4?pOIzhSz3%KSLu}IaCMv6 zX|lSeRUi42Fw}^Ni9sZpSMb3$J+nYu^#)^Wz8OuB?-}+6H}Nyoml(3!H9hH-P8~Dg9QT!xE11Ui7ADt*R|7$9+23BT&gY{-dk@ z{RY{^Dz-NQwaXtIISV&u@ppV}&CkHGgzv`OWrIrjU0qzBnGH1Lqv^ z4iUOGEv<$^qL@f>M35L`>Y5b?R!$#3-s^+juKGU4Q3ucIzIpk~&F=Cx)MsmV3U0Ba zlAi2+GL`|T`}gmO3zDAquoDa!3TWprWTxvF=qjARyF8!c8}Os@ZfJ6%uJi?|COMCE z{?n7TadLZNMc6|*M^C!QTXk^M0%?Qw>%_k6Slcx4GN2gBwc>^G_AuNFQ0N5xXFDOT z-G1+>8xo^rP1e2_p!U?#v7Iw zSv^Db5s>~7L332T&IoCJ2LDRayUGABrXzDwIM?6ku-U2AU<$_#A%*k7Hl?bk=+ezMsg5+U(;z=H zv~i?v+>@h#^KRIlnr-hKIw;leUR}Ikn7&_uNHlG` z&qoyOGGmZ8<^6f5(}egsO`*OEme@`vY0e6GttVP?whgH|f3j;?B5jO*4e=jpD1>&} z@uI`COYFHH%vPs@tg!K>eSI1N{FkU%FqYyyVcj1zy-;XDW22%#2oMS+dIx+r@)(dh z)QH!)#TFxC`AEkgRrJ(~|A2`Hk3QYKJOkCF@&;92ZDLA!WRA#9xlFg2?x8DVYgW#g zS)I;=-01K??cUlkJ(^Ii$Yg91kV@&Ck>4Ft?z4p#_r< zoB@k@l(T16pBa|Mi^BcMjghY$EA6f!$*>ex*e-zUUa;{$Tr_?}XE`wXB?J=YdCK+H1FAbE8+ukfr&w9FZ-xqTy}sHl`|Xu*@SZWz zg>;2u#J(v7mz4ZD>&_8jhQOJ{Fp+R4w|gI$&mOo`*X8Ty>Bz?e=Dpfq5$7$8*(2^s z|H&1CZrxhntQiLd7clc4auX6k{)iE?6o5_im1y?h>u`WydtYq`qWaKv?w~^?QrkV< zO|E-SMkmP@F+!i=ceuKRC0s++bDBB4>BWZF8%JUzvJXAj)9QZO)r(d$iJjS>c4-Gw z_~GpN;=WCST6g9uO$UcD2`vG^%G)_O1d^*%Ks-^PM$2il^T-IfeC_oafOyUYHtL7xNF5>* zg_yTEWx3fP8W&5^QKJsO&d%peb7S9`TV>}d9qch8eXjoJNDO3};3oU;49%%x3oEo# zfA%+vak#ijGaT9_YFr1*{h4p&BvQsp~zyyvWQB-J+MX!nnAkO|hJM+JqcC!6GW9cSG4JR8Z{^*JCstp2bI~T3`3&$Mg z-yx$*aSapLTYgy^(%w{=>01b;rL5|E> zj~8libDy&pQrgx^I*{pP%1Up$LWL`g7z!d1x7w;E%zxF$c&ri|6E<6%+U-U}wh(C` zTsLSz2cw#%!&iwsVbVS#O{3eHG(5ya>O>gF!AD^s+es5U<{$V_a6H(H3BPAtul{6_ zU8?cD0t;CROhptVhGfsSOVgxo%)L0g`NQ^akLAv8-J5M{s%n3;oP$N=+&z)YpBC6$ zd3hzxa#nF1R#=a)MkM-R5hB-(Z63^&6L)=DGu3i@rg}UUVFU?=(Fx0%;CSkHb3mtn z#0++vS6g93jz2tNRKW1FuqhU6YbQ9({OLQK*x90m_r5(`aw4IDUguAaB2Qe(IW_8l z+6l7r+-7tOtw#bKNa4bIx;sk0D1RH~ph?&nFM?o_hlQHn(?t0wAHKaEZn;B@SoXMGY zO?L4z?9dZl)h`}+Qsw&HWBa-RE(}xf+vNOTTW+jZcMIHL-zA|j`!9T^FFMow_>D$( z=}`?q8|owepOoYZDp!&0c2yPEWg-9%4&2ZibK0J3x8GIIh1^O}TR6@b+1w5&=>M~x zLOqUg$Q|id-wA!92sEH_OGfecMEt0rQ~Xw{C|V{>p;JBmktmQfPn>ERG$ORvQ{~ml zPL#j9^guY?@wdo1d$H61f(^rt4QYQpO{_xoneN-OK|P)$IO~)B$uW&wt>{Z7{%ii& z`v_(*qs8am+_6AGB3R`Nx_p1rzo`d&cH&rme+TZ4R!8~DM()nttYF(P59I0XIv$XN z18#X`dA}5?Lw5`j(c{c%%w=>b)NX=TQO?JxUQiBE1`upgTD@@d>B562)zoKqYuw-T zK(|Zfzz4BmYB`R?F+IJqcb9gkGKR&a@LjtVx3+k}Mpp245(lnj$%)uGZ!z+2sT<@S zZ4$>!%Fny=-7E<$)LI>NkFNQ5T)}?EeQ_=5r|JUEjw`>h=K!-RT3mD#CVBcpz;K#~ z`3m1}!6r>(H`gr%{U>pA~(SAa|DK*1WpU@_8# zKn@6g8t#7VY1YS)Sbl-DUSKboDCf1{cjo%jQ@BlVswFDF~Hg&CaO z>OXj%y?oiGQ?kWOQBQ(BbjS0wrp;GLGzk&DYC;!v8MFHhl>-$Yx7_~4tdYY;JlMjO zX-z?-;O!({8SS$xW_HK7V{@e%+gf>6%T-5OyF&{-wqrH8*!Tq476j-Ao};+`(g_v| zJJ$zWFRiWz(&5OAr3Qpvq0p}rxtxA#czh23suidMD`7Esn_r^r68gkt^^_|j(cka|R zB}HjTP153Z~ZEYRHIDiQ8)CIm*SL~?y{ordV!nVZaLvXNQ zg(A8${(wKmZb-~pCl^E9Aad*7K0`#>aH*?jcZHBT1*XA_U#%!B^ZeTsClW?vJTPzF z8wJ-MFiKQF==$2n>(4wJDftYs1}OG^x5k;pIgt5sHnpUWN+ZlT?H2fcjUaH}(^h@C zj^%pgt!9J1IhPPLg>UT36_DNpv4X&1xX$+RH(Ho$J$$z;#*uKM z^JJ^5{PxL1#mApqhaUAmrDhq#(iwYk9Jg;bOj9Zw{h04&w1rHKwukLa=&bQ%^US2$ zG0ysP2JFn&HJbm?o$x9&&*x~%(68gfRyGc@Dw}G&k)&k!*mql5-c2MN7HG0Jzp(^EAM|k1Px>5o*ABXG*Au%aPvQe05csCX>KEzZ7#Vw=(B)-nao3~YF7`}Hqt>eJNcr0}=@ECP}zRb_3p@&yyAjt5>x14wFe?L4NI$ z6LBeK4-OSe1TKdG9HTP3h5Q4WUi_}J z)6;jJRr+0Y`t5**LHDhcy=twK3TL{)^W8>Rf%g#Z1!yHP*@Qp(GgKkHjnmrFW4%|w}_OhMQ)^a+|vhOqVswc`8FOW1gR zcy}E?<=Mz)Sh*ye@?7~x5O?PG_;KAS>!qMalk0_@3Av0W*uoOA<6z$h$cSijn@XSj3yr-Dqv1RfLv|0-ikfj1JR8m3Q5_0t>$Zz z0x}8#WrB4Q4S&HF4&gG%Umu- zzGcKc-eb$w-X70G3VwS_XJ3MSea?x@(8Gz<s`_4e}lW(7S{DSMj!h#RTz<}x5d61(+x?Gt2>P-E^ zhZ-rzop2eN^;}1`V=TDax-;%}(N7aar+5jvd&U70dcw7zY;>N_Z>CkdFY;1KK#Wbb zMEs{$ot?%`Nv)ldhxae^gxYKfV{iWwe&%7^fy>fe|m=Sc67T> zwY4lZyMZ5W0eTe;78!LL5d}?;Kfq27>6GqAV)ey6*ucQ_=M#CKPkK*XctQSI%w}{x zt+^k#buW<&2LnRq|$`3bA;F^0w~wEL;!1Mag|Cv!`w~Ku(G(Im88UMVX(Sq&rA8c)W8~$;acXPXS%=wq6HO;i#WlC#1D7^w z<^DcXaN@FI+eC{6Ya4fV|MiZ0ZwF4VYOss3>C=!V=HHI#$BN#vk%;5TQ8dF8L@V^_o9uX1(hD<%d6f(2ETKaM76TwSG;`}Oq&?5`4!qu$yLC!KV|)PYP- znP-+A2Ol7#nn-2AeV8yFx{=3hNDIOEYOMYc{ODiZ5CVE3YoeIoDkL=tOTf z0IFbY4Ls57ky-ep;XES*K!2t`E7Cu|Ke5Q?bSdLCF-7C)N}4*6A@y4id#2L%JC1l1RFm@ov{ zGVy=Gr;y|=7uUakcVyW(j+H3H$F7Dgt^Q9&+E0GUAy(7|t`^ zbYerFvl=6vRLIiWM-tnIyiiaXYYtDZS;eGf#2mBVT%;i!Zt$3G+k$g+i_zKL4B93A zmk)HgTu_~GCwHlJpLsFK60T*GeL`ACQrD|?V6EXHRB2pfURfK}{jyUSl3)HS@3vBZ zG-8dEG(=WcjOV3(C2Q63=h`@T%oo4JVq-oQgt|%^^cGFYyGs$DZ+q+h^v! zo%wO^ML}g@%VPSBf6d~&L{*2CzeJl>vd z3gag~Nv1V$u;JPhYau}6@s^{c&v2}Mi!AL2&tLSsIbC|{&!=68gy=r_8R`>vwS2$0 z)YGVPedfy8foD{f^fsT%?sm|~-tHuQ4x;|`5y$HhpIg&};=EPg>U218T%VS2sAVnGlRiaaU3<9$jy|F{GmZa$%OWF(l#|83}7@rdcpQ< z4Wq^^Wppkxz#zVMe!q@3edx!JbTOHA z64RF3?)1>QhMx#=Ps9_p*tb|UcChH3_0HR`ajV_L@pO=me58k8Yd<*sd%P)F?$KG@E9@*vqmNn*HpPo(nHLVhDoUn2BU}!$ z%(fr@DL*4Ow^NaNOy4M^jomoC!%8pD+f0*fY3a@xit)P$$J(3kE=}9mDmT_iGi6Jx z>?xUgbDE9R{w;$_tEWwe!}2o8yfQ+-=D6ThYxU+*v(}B_{ig-%1bqZTrw_5Etvv`i zz4@lhE|QC6gG$1+#u2qr%X%X1`8ztFs)gwbfd_)?G7EqEAlGI%CU8ZhHe@}0&&6Xe z7_?ldKHOrV=MmSBmc7$nl$}Q<`;IYS4*?@N3dAj-P62H zuLDinhy9`M42LCq1xb;@JvOvuiW{@19N+5EP?cGYMf9?b2)>G-c%SVmNRRF5w-0PM z)v_Ub!+TPVzt=)~ z?pL-Y`O#_LIS+$&&y#uhen#Ny@8}xF?n#LgA zGM)OOtOxYPNI`nJ!BB4?^x$lXWqtg?MGepKvyRE5>HO3aq+>5;=*W~4TnL&dsu<<1 zf|AQ&qEcnokpl_WvL!G~iswCT-Ri}VB^zq@NoMON-NzwE>0<47Y9+kA_6g>xNu!JAlnp6h1@vC~#vkj;0|)iU$|GrXfitg;4(qh@$>mL0)z z;9dynlo;u%QkM(&6H}hEQHX&O;bz80A_zp)jc*C*s^%Ue;$Y5jH^lCqgy)|agN-Df zn>XD2wgP2z>*vom^tV&j3>+Gf4B3-N5=~QVk~c^u1;??V`b+Cys=3XUOgcQo(nQMF z#P=`@TMe^4C!zSTG0sq*2OWwpHHLccbpDk z8%dx(cVXAy;wIgI>N=;l3X9@uMM9 zZX8ptL(4S`8bn<GNX?{L*eXBblil4uhaA&Oz@waA+s&Ud7>95njBQaz%P?4yqb2<5A9E5%y-ISa+ zt68;g$=A%X_)pO2hO{N|$WxbhCR}^&d~{8@J-D^4?nrHn%pb~qviUQgX{%jEl5^W){=CT_YTj6JI64N{${H@Mll}a-8jo#ufZq3E;*Qbf{Y4!)?!QO=tSu9fsKcS zKNhNvGuK#fwQ{f>&iwGWElDEMP43tO(_bxtYnvbkw4@*0GZL_-UpJ(WicNeZ--!qJ zRT+W`3M3K{#Lz4AmpYn>UZ{TKw_w3qd4mpT1^oN$@kIUIQLb9o`kXs@q{oIc)PuA& zSaY0X6{dZ6?etwzZL54T$?nBN-5p+Lce!eZ>|R8bzIEt}qRM;w{<+~Vs%}!rC!W?I zH3sir9-QM}j@9JwJKnzT@NV4YWzcRqVTQDjA?f6&YzdtSbPCKmIbRm*N2sJYRi7LW zmr4rR$nu*@RXcf(@$O}}i7|_ILGRwqh~GvAzHZiEs7m)xU7F((d_H)nRZ_MAPx?cb zz617iaE)#Qj8j(lTaiC%SV;CfR;Br3mDKEufmIgBSLrJ&D^W0nbUEyf(+SmoKeA8q z8p(UmNZ}~EQeZpF)>eGZ$btgI*9fK?z7}DSSh#2W_WRHggC}!aZH-8O78hR86f5J> z5Mgb^5jvW5kE<5HZf#(FIgnUGwte#z9^lceDzC%s%#ZTQGJl=*QW*CSWBOppHBeDd zuv|r}&&G18rZA%7C`0CCW_7io?R2&Vd)cvTovA^M#!h9@s%90(6GSGJ6I$4L70ZB9_P%KH9R@ZuPydk{=nA`qJVS*% zy(_czG?G-O*=tI_nvh9(jLrwNe$)R=&U@YGz-{u)ZJ$lu#P++>ZS7NC51REJLCR$s#eeI2dMi5tr81A zZH^So60|IDV01`l3@_#WF#TDZ&*EflkB!2{;otV^9n0a_uO`MzJGV6c7}`V*7w%?q zmtLNwRUWSUB5J$drv&(mks)4Awt zm`g2P8>BM-Ug5`fD5xO6M)CVp;dVd(4DJYkLugIDM%Fif6JLM!?B)C;x0vIkU#$Y` zp;wcQ4qnx7lozz+^l0*3%krQ7op4lWkX5x^ehNvGJt&lo zy{b*-jwGjyQ_FCvDcjOtuqZ7xz4(`>`6b!Jc>EG`WWAk(?P0s60mlUzb)~XaI!B?D zw+@7A`Rh`HqVVna&ApEoJTuW6a54$;Mpyoxo#WY)LL!p7vHDiC9P)C`S5hrC6#6w! zk;-u98)Zw;=O_|VY*b(Vu4cR#GCnk9I@`E7tsN#>JU?_ua{X zd&km|V;h(br)<0OO2}d;I^W>H>N)dgqCtu8p8n=5W|n;x*k3M4e?V6@VX>#}+n_cl z>p&;#&DU<-v#vkhD{$<6O<{{xozjouectHhrr>{1mWx(vmdmEH?mi2bXwdzQiF$7g z$V}H>?aZK&ZTLd;F<%A`4JcmQbBAPVe|)VCyAMbP?Sh|!2LJ7{4p!FplsIG5RVi9a zesi`hY*}DMe9fc1nPF))|MP)UFaDIdAv_!Gv=T@Fj}N;W1#yLzS^1J123xGt-nZ=M zU5^TC;$T%VEFPd^rz!Ee%O(XhUTZlIP^c;f~hw=OX zm&qBOqBl9n6dNjHxFFcpzVR#;gI5uhUZ2B_T9 z6T9*&M4%qz4FA%6ao}p^+#*^TJd;X5Y!#XNE=|sDwxwNvKzGvcSG+HI@XBR|+t`}g zZj;9-mo}}&@bIgAhN2HcaI~*R2Kr5Kl}8G+LR5}6x!r% zqTGHx^sBP4sFEopBt$TnLK%ojeYtMCvY=Q~%T%|aH^=Y+ht2K{yA_yrQH~r9-15}5 zC)`qc^k&Q{7^+AmhTUF9dTKk~)Yg)4u9Tqqe_To%*FU>I#^GN~9MrMlA*xAB5#%)7gyscAYo64UBJ1Y4}{7?eYNTqLkRmZ(CldN1j&TINg)Y>sm}I8fI!w0!n_A=UVRBh~0Erl-RzYg9p!4JzcYZcyQSL z%W`8XJByEMlKr>2%T*)WW@?EUOMp^*x!P)Jbxr6{yyzJu5vU_t-?U+{k9`mO3RUWk zyZWl(TD*0QaY@rH(fNmtD$C_RQr50A05h3!)DXN`tcRzhFDeC^+Azls^ZH?}I$6MP%ENjcM zDz=pr>eEl@)dfm_=%P%sLdA3xAsbhd^CIf%)Ivqr5gM^F^x{yhHmfqpXKj4@(omh7 zGmVD=b1_Ho!+f@b{5p~3m|=BQ#+;MWwQ@(fLjrhKzQq?0=g=27Tw*;G=_t?CE0R-O z%&N_QS=6m^d^^d~2N~*lK6o~ztzTYgYC*Ob;hP0d!#`KBQP!kRfMisxuG3rB__LoQ zhMe-2lJrib<>nEN{ArFK3qdj)>I*Ba7B+|t&Z55~yOT4e6}>+O*ZRz*?0w2)vnf2A zGI}wy_odd^QAg!_yu7TN_8_U)qmqei!T^C>_B8JY&A98me@aTc`#*I8uc{51vfXBq}w|!$OiUul9yV-R(y4`df z1O+b227WNUJf)}Lx3r0buE_f{NA0z27bA^Jmn}~|4c^Qo_&$kZCTZhEeOT#31#@xL z{vlf#n_>4ZVag;8pm6lKoRQgOq3knN*KNZl=MBG@>X|imzv3P50dUjp1%c}6a~}ul zT#r!2T%Th-FeN()VbM~3n93ial@Wn;NV?&e)$W)5H6;==*sIMSbelovXDPYOlr`yIoi~WU4xfSp5NZdY?vR!4lw`Dj|s>qkKwvwQ-QcAz^ZOeUl=QkZ3 zsh!T7dtJE@ZvKjjo{!>JebcY{>*JA4hcSr%LOg9wP0jz2blrhe?*IQe$HB2r$liod zQL;B>6J=%`$;!-@b!@WA&K^;cz4s9jGD@;JkuCFD8T~%z`@4VN?xp8>KA-pdHQ7P5 zRf3>(+5Z3$WpLo`;^t=tm#tMFtEj*Xr6}yDA1pUNq7DsyIpEPsy#ZEk)Ix#u4F+|1 z*`p@JuFAY}pLG7aMiCNp8RDh6S#NsCABk}hQU)82q#qev_8Z4zPIQI^NWmFm6N8f- zp$%SUoq9BEEOCVg2LkEBIFMhS-cBa{Sya&4$_!NjQ5=c9c#e+lMg|xi!5WQ1;`cwq zt~qOM4ul5q(Zz3kTFV}guKm3P5Q}xdvY@46vsoml<5c025#E$)Xm5dKS&|;#vJm)AC61wqF$Z#5Ol@Wt&l;=RhRrYM-gts@y$^5c%YOj@N3qz#V_~ z%gxS9GFe&KFU#&`+yxO`ItB(j$?CJ1#t9F&hTzjayQaK$1hmu!f$+A zFMS|e^!=vUa6>JPk|nkw56Kk~rw-blw3|x2Hw@}vQ3%|W95%gRTP$~1bQD53TBA-IWei#}H z0!36_qlk;-pxc(y+1VMs7ZiUM-0{nfiZpeXJdyKUFEAHzdkTs zRe{h>Jn1h11iG(v-r*680{v1jK{nPA>XkmK0uiPE`>KHxTkmH2yK5xqOv>jbe8;{% zwDYu7_669m5#ue7sN(s&u%-Y;c1}_9ZPIi#?3Oyg<$7a$?k1W^H&ac5+_V&`>JJD6 z0LG^WS9|vq^qk1-k?t$%rgg)x@pVS)>dF~;Y5D=c;wqldJc{~Ae$$| z-kDz4(eKrOn;x=NxAgls;=!C&#H}*%A^OAW&_oW*xajEXl+~7!_cD#K7#N0!(qN-@ zLAC51{rd^NfV?Z8t&dk~U!~t7WM-%hUuAb6E_;~p;AQBjH0cuLOqNEQk5-WB&W zEtj9D*FMV4-Ntodj+AGvK@gR(<@qB09dPtaxJ=irPXqZ-=lSxj94HBrf{dZ>kULPw zT3yf4;ZD`4j|lScz$NxA;hR8oBz__A4I#?#e_DB~yF55RxiRXKWDL;f;1Qhwc>Wt< z+v8WIRtijElWY7VCbAB4EkA#gad;LtCC!E2*qW>ox|uhV>SVt~21oz!OANe?x~Idj zrG4;S&>C6}dAzm3WH1;k!L$@(9=`{0e)Z7tvB5HeE(!Yrh4UM0xDd2p*eTF{Due9ygDj*_JOu&cww8CDWqot>S*oF^m7b(^%< z4Evk0|I2I#*-#%HGS!OpPi7V_BY{`uD!5et6Uw`$+Dde3E6NAEv5aV<3@VMhl;Onm zn*3gx`i3&`PF>}AiLu$s^%fwZCVegfA}hmNr82B`z9z>r26AcUFWdt}jA(fCB z4xvYHbBJd@gihux7XRJju}ul}ZAa02QL*ZNoM2wcOG0Gc!j#;J_kMjLQy{o3r8(>; ztgwc>`!o?Y?6Y^q5w2|JOL8X*5+x2+`l7VE5K&cCoC#vnFeMK7gID4DXTT4-fhP4| zwRsmpX}2sB0I=C&`Eja32a+~~13-IVc|T>iQu0LNdpp2eh>6fPHA|$kDh864`J=mK zcA1o%SE3kVF^mVAIo0Fn7D|yTQK;Bvfw*1%{8O2a9hcAjpg2Ts#$l*|qm2EPPkvA- zZ8#bo@b<19U!Fz_+ZC0hEt1!w9E0ccwL-@2Z|;+QQ5Vzqt`V6CqqfE?n(V_^GGZ>pVxbNAkKg-;nd&OQO?*` zd88Tl6x-zHOB{0ltIpO?r4}w{WF~gnKD~wbXHG*#f<=hv62%=uK~~Q z7zO9@&uS-78uro$Y<;f*pV4Z2&gU;y4I{6QJ;kt2XS19%P=gYC9wgjX zIw1~!%2@X(GPNJ_W)3l-QxnFjiuzEeFj`|A#CR~QVO;)7;gsaik(rp@RpSfE(SU+) zV(z_^OMCYntrJ4en0rq7e^;fYEd}wP*p+;*MxE0`b&@1itim+ER;h&mUI!z@WS2*0a_rar&sls2#cM&RUQ$UHWU*)u%+9h&a@+v3O?6s#dHn+_$|} zifjC*aPvTJhK87Te<);F)+~FZ&iCMG)41IMrK%87-5XsZt9X0GPG~S2`ob`)6)s{t z;}0A2%+r)%t5UimI!65bvrE9%s3@m*xF7S{x*eYn#UJ=PeLBi60othZm*Y)X>KGMY z!lz6<&ybu61MlY-7O3=S=Wz04j>GV20`MB}bA$t29IxC3Kq^Qd=bgViYHSS&1}p~%;5s=cmnPKAJviaG~;c$`MaiDHm~;ndnn#%t6L15Hy29p6D)$oZIO9EP=Hf(!vEl&JPY2fg zSK3WR0ixs$m5Wn)-azvMCL$l5-va)D(22i;O-=qX^@LsqJbGif(tsZm-VMaUd`F*Y zP}U+Dm9ETp`_Y+11^Cf^x=Ege1a}#fZiG{!ZB`gY*{LO;QJtJ;Uz4Y&fxXin8)t}M zf%ii1QpG41g8OXG!LAZ&V^Yh6WaaD7FAcZ+_jkfkBm{wd9*~0+8dTzX?Ka`DAo|E? znz1Mbf`QkXk|P{U%J^W>Dj_m_hNGulf@WU8zWM}vs~k!HPs2|ln+&ehi(kW&G3<3C z^S}ImT#K_sAcS}zX@HhmfmDD`pmGz@EsWL<9yRd+wOB!afEBYBAV`pbf!&`$ z2NOC#e;_$Wy#n8@ihSrPy8-(uN}(irdwYpSaZ=BZGQn4azL7q6P!{I^7165bG4W_& zcCxjT2e%p)FOqnw04(mxw9iR=uw+Vy=*-iECyJ4vX_P+So69C!@uxTcT9(Kequ%rntJ|qX{q7UwxVF=&{8LA-5gH_UCC98h&jU=g7O?02`Y_f&mR`5ZJulBz)nkn+zU zVdvY}*quLpXUCoY=KiHMw%H5;txf9@*w^Im8;rkEle!OF>tJ)wJL%!u1tJbMzz=Ql zJs?^%XeL&u?J0BEC&8#=)ToVe%kHLP?h+`am&@2?%1jwUQFB~Z^!tg3m1)gWWxvBu zpK>Ad@8=w^WCbvtY`u8)qbE*sJZt-=%j%40u(51=e9Y?=Yd@K6_E$qn(~j3Z?nB^a zd(H;S^>rVZxFaa+q*w%AE_w0AJmEPyt(W(kFot9_5Z{R`;3@GHwHviO&^Y?)& zp-aoT|8WXi;b3b)+mW0g6s|{n_+Si@#o8=(wjtXYW9v;k-L(PUW6=UW67CZn(VY_@ z(f~6akN;nLF;=TpI2|=cEQAAjvKuAewhlT|6vVP}s+UaDBGh=`l@qa*fqxu*!MfMJ zvF1gWNm*V;(gK8FO?? z@6^d_%F!-G$eX>rl3g_2)f|r7bF)zO!?J#Rn@Pm^1~d8%W?C-B6PV*M<{3RVX9k~K z_ZMUvy_ET=wGd6ig@CkXXwVELweoT~Xv2qDy*0R%|PLexQ1dVvYyyL(ZHP3Ew>!VZJ0z3CL zzHq1pk*VWB!1el;-dAA`-sE_(QL|7J;^)(%EtHtYoV6h^M+OEUpcwG*(S>XK%yhzX;G}Bh&kXyAZ+!&KLr7K{t5|93 zO7rWlcu(~$K8;`~$#p7>ZXv-nVXxbwx8bAuw&mnGT?Y=@B5YSzX2YVWdB@{(9v>#q z^tcF)I{lnmzcc34bRJ)qTz9Q7#>mTnD@G`6`iXAQH9kBFebxkf{o5CAk76Mxlw2Xf zin2Zt5_?OnL`x`YiP`)8(CqK@X|_`}$D+YIuZ?@Ca7jG$df5+PQ5S~2Juq|9_t|b%aL60 ztFr$jt@DT6!d+WhnSQG%{_cRwwg()`4;(f-=R4&|f5v`&@sQa*!2pVD(N6mhmf`_L zf56ba6c%@a3sN=Zr8?%YB*qUqJpaD>G!Zde&x2h(0G5#m>;l)<7jQ^O_^uskH%V|Z z8SNFk1D}27G>Ci%Znm{3J1!e+uupH-`*fWkv=b z=zE_3KHF_S5FHJGIq@x!8IGNJs>uB0zfndCAd|An%EOxe!Y?#;>{MvHsgpF6kS9?* zw&0xxt|Vz`=2hN7bn)$vp0-;awe39bm#&Gtm6vPeg)vxjz}eHo`P~-~C=6;efXz@! zRz)P4kw{JAYjLtF4f(TQ%|qZfxpL&dM=tK}DCX&y&SO(YhyADv^Yrm4GA##TBEv_e zUmfdipAZ}&ur7lfJ%tomhKca$8OL;&cW8woz%DoJn{f>b!hja&I}FT1PGc+To!-(R zdZdQspN>P0l_XE%-ygd~0(anUOk!8Sx(!36OE|+i!v!IESnObUD20#YJ=l<~B3W_2 zwpARg@I%#Y#`&yc-;-$DA;b09`{={fxg)4-{G|CamkS~0y}-v@w$Y9&JeaIRWn_kp zE_{^t0_ttrcq`-)A40dsTym9;aPN#MR6U$Hn1RT?f$F3FGF;O&Kw0_p`)b7KIH4Fp z>PE-vHV7?NLa@SwC7J3OSYd@{*N3n6%R1b&B# zP-g0?evv@wkmQeMY<%5&X2Wvi1JdaUs_Is<#cjc+i$4q`P%Z0ezCZ;j+8T~V@r*7$ z?=P8eYkDI2kA+;d_!i8=p9;q^_TJ<6<68NUMaD)I(LG;u@fbK~=$UGSbV@X(h*WF` zQ};^xuYI-We?jVe%MF~^6I&w zx+SqBrI~Sv;9!d9q9q;5ygeSEI{4zUThTd6teDg3lfn6IbgIA?S{1FP{p{+Z{cL{s z1r&QxnP~vQz)im4B8@;k3J$vQHKJ?gTgnI@d^YcVn!|L$usZPzf=|0=c z=UV`x91+NWLEhiJBBs>f zD+47X-|n5x?F1b!zi7VU^5$-XdA~In=!?U>xSBi$&dg zet)=cZ>?ft6KtMJTIYM%&BITk+5s1vmaZndc5$$p9-lnQt>Xck9o?%vhPh=E%r;{$ zSe0t`XIY5L98k$5$ZrX((as4$m+$kEY!s0QCK?DZ?PcRl1M{8;idJzj-}}kWG)Fy^ zzVf_cu%=1H)5vJa0)@zTIio?ZR%G5xFX&uwwh?Zn;PK`y@u9|u1SK|dD2U`3-3Wj1 z<;FVAxWgE&0z4!Vo22~qhv#Z5{41Mg8~Frd-C})*R*Oru{FFs} z9oGduJ>cwC@af<`*5^G}e2-ff;)4N=g^mM~pHDl-fV#oxkt$Cp-XIIgqq3-+$T9Q< z;srO*1@;7FjCG9!9o3EyH2w2mIGgXYlP1YLB*oBp%Cb@Q7$)4%M#YH$K^KT-fcHP8 z22KA|Y+ym>W;N7ozXvk1^=VVwrNkP+QREzHE zzl#&=qsgLI&)W6bAO%|Nhr6R@^Edj)su`knm$@`)B8pPK&3#s{{^649Cu8=`stQg` z+BI+#vf_@SzX88PUa`%PsI;Yp-5A*;{X$;QOkL%*X}H$1oiI}m4@UuN8BEJmrH`Ji z>So+SDebsVlIx0#8<1mKN&V%6K;R~idZf)82L4oGj9U?+A=YGp8Qi?=sJkoF{8wq25k3nIBw$v*Q%v6|xZmntV1PM6!!U5q1{=mh>1%v@`ePhQTXNmuYTzg;28Wno< zbG>J~8tIVVDIPY&EWX$u#r7%CXCbWoaRUT_WUNP(^!OSyun5V|g4}P%e}Go|g@3kn zyf9~HA{AtajR~MRYM&W^qI6Kqfx~ei6f>$lz;;;TpfpyOW~4|&L{tSP@THo;o87DO z|NKB0)>ZIPU=jXqX@uZX?p(=PlQfUW0Kx(wh=NUiT2i&Q`o?E(beGKSav}_3)3F2!#QCr)?AgW!o4;b< zU~t!8TvU}vUiTmI^DFX~^FGyn-S zUukV~2%uf69$|8P=wtuW@7D~!{wE1#915tE0SW4h7cWqx5pKZcVh!9=;5@aq#d3%0 zV?_&CPI*rf<*y7>yDq~ObS%e~VA^(ygUdfB4>hRQa2)nha~%F)K2DFN>JnWgkFi&g z5bNmwO?lyDo5>LZEx;0RfmjBD5a$H$!FYrcN`mC!m}wb&5>dE2FY|}_XV%UdZlcg3 zn_+Tp=&@+sFI@%y?|_SvZXrtW7D{}lDBvXn^UOKZ4L21;e*R#frkSpAS}ESmv=(*x z*EVB0h+mbR&|ylyYG6{Vj=Q6zVkX9{P+d@&NHEpx24WE1@X``kX4e|13Z% z?4MJ&$pJW^0Yz8hAbtL*%pe=E$(F;mwT{P=)>PmO1G;zj=D2+8NCnS$5VF6I~dcGXb?^LXmkq<*0#z*Y!V-XO}$x8`&v=AH&{Pc{6Zn>`F zf8FYMNZizdPT^WVtR!U}?d*8{749%TZg<^iOf8bJJAAA9AXl@Vw?E^a@Qj)&F!QiE zrtk#iaLc{f+fZ!Ou5qkcEgn{z^6X-?(zJ`@wUxW2;{Q(bX|p2AdAi+HR#v8&arHux zmeA0W-M#>qa4w=9g-TS8yZ=~*I)VuDvg zwtyoA=}eRoIOtK?xNZ(eZ{DFG3bQ{PG549fyU5sZj|{?*1(h2r3(<9h;5&+LVcYO< z@jTc6Y$ence0;S+U05aO~X$X+^0|vkZ0;{LA~*FKdd{wA(2;X%_|JC`leBHNDr8l5OwTOP;_; zlPE;X7ETd97yxk$4Hn8{o}u;aJ}PqE@1yjuj+(*hc{5E@U5W8k>C^V zNy?WR<)l`jj@fF+zt6(>`)|vg=N@x0#N%XL<*$5Jg8fRM1R{Z7K*=K!bda_1F>t*& zJ;+sC`TOxvV`pGg-?Yo&$MI)?+J}-S@Gtkj$jd~3PM#+>q%Av)OB6M2ETqN{OTIND=~*(1NFXg5rF7>d$K(F2 zE|!8~${CHu7^5yx*2VAF+Fm?x)PhEpLXw3ae}pEnxP+n3Zlpa)0 zRKFIIyCCU(htj0wUlsD$KbmHd;8>$svFJRx(L}VYi);gA5TSI;{Vzgmz*14 zNEMPL@hDClI<#`Gp2f>C@u+_~0?Kb~`27g}P=3-6;`5>VWmyjd=^#``KC{=_@jR7j z>8|_g)B7d&@k951F(66wkTDWp=S6X@)zfq27|Ca@Lt-7>|L#wm_9zRHdTeegN2dZ{ zn1_agl{J6({cGm}&4Y+_`Y+Fg@&@CtXxlUG43!BSN81a0qq!GRUrt7(w zKQLeIz0V~c0WX@hsS+fFYw`0Z64~m3EkTeWo`%Ew8t)WJlxe5HX+CTzWv#(MDE9a) zE0KFJU+k8`bg*UqBY3SAGK4>`?oF@vS_SPnE5PAEIrGpQdLX`fxqjh%Mcm2+@0$*Ur2kHzgFZa{;03;pg zEJSw(YS&N$^mn+gwaLa!yo+_iBeF#@IYD7O(Dsl9F`UWFBtRH|s zYQkS>h*5L)Tbj1FDiq5VBVdN=)B> zc&T}--P2DlPyP^G`N00AFwPrzxswFpkc z<~R`95)Ae_P-M{E0V-U^_2mD4OWbxGx5K}l40-3j`FwoaoYgQxZlRvwj0K)Z&{(!a zEpECuO~#PJAR*vnTZ4*KGHpRD7P>^ows77sA$Qp0LR!=xZUiJWy}EPTCXYZyIdye~ ziAk)31TESp9^2wQsBCtGt^fVGq9W3}jpCUgAGOsBKI7;@tN^W_>ojjo^$S)W+R#@U z;S8~N2j(H4D_hmqFnmlOZ5R!C2fc{5R&RIXH2k_y5Ay!DU=Ob?q86p8Z<~}8iM+<3 z9M*2cZQk_?<5P+8RfZcM0ievR*B5akoKqk(eD`%<#m&pcy2B;k)+f8fIE`a(Pfv{g z#v}N=4=_SJadShx{E)9-j+>;>!Kz2G#|#;^+jK32C?G8=9etMyi;X>or$#Bd>m@sY zZw7`lvokHYD$h+AoUcs-5d%>=h{cfyDsCWh;>#hFCiz_9U9>~aeGR5yZD!k`h~b<* zs^Af}ZbNMCZT-02$ogygr1wcY#=4O(65*xyJEX&;WvXzSpT`4e5#Bs46PLY1&w)gT z1$gK9I^9AJD9GG?d$m07nwF{M|uQC5hT&2=}Gc%4^}N zDtkQot=jrt@B1(nDPN8}h3mNv4a`qeqXrE)EY3{bPg(141FBw}K+G6c9pfx0hZ1@k+I9i@_Y<-R-yd7NDaRN59V}(chLHO!;E=%*Sw8u^%LQMEe z8BK1?ov9dAq8qe$TEgocR$%--VeuVULr`~0Wz-wqo_prmGA2UXC#}BZPXtJ0j(Sck z>s-X9MF|Lw>V2CYjA5a0d(SU;!farBol;Q~&B_~~2v(0A8I4cR~gHy+p zTc8yD%@{o!<6n@H{M73FBPoRMgOFui?g^K>{+;dlqHYg$U-?VSD%SNr~cz7-?_E{r_r$EMep znmFqONg`k`n*E+a`33}2ae-W8H#fHR!pHCsLH8fcp1nZXGpPI zYin367L&|4BG5$K{>PR5zp(L-k>oBy&m0Fw$`RQ5Jp)=Jt~B5FDvKb(x7_5^i3hB8 zwFGf)HSDi>6ec*g8_1Pp23UVX*dokfNwb75+0%?Z0p!ey09SK!w}z z9=0zk#2g)W4?N{vo#RkPUr4%8T=a_X_6Y$shG+b60mChT0S_aW)V5<&k|{qw`tJAX zM1ZlOxN>H!QBbCiZ%CCis-0*L+|^TZYIEl3@%x3`ywZBXHnPHmUaG2L!Qfj3zy6FC z>T7Q(k2ypd652G4yPY*pT4cMimPEVN`WM~y z-zPF~i+~Rnq~G;@FV;Ms0&&ZxNl;v5gDw;;E7irI+-&Ae80JmXEnEfks(2g?EJMP! zqXv##`2Uwm@8lo|G$eFs+q(}spG*WhAE(ueg4-U{yEuPI8ooVyrL;p%?PxMf`4vra zj<9$K5#q5ZnH&L5tRt5-+&y|x@uL47=XS05?`xV^A|j`6S^PaJ*;$_^?<^__kZ{t5 z8t}ubY%=aNw9b;Ba-$-4Y)wiLT>id(v!aShio*AsLkl=Whi`~CuOk3&dMIV@njFz` zksDJ=E6$insyGZ(9t5bC)|1J_-HGadmQ@DyeI7z!6$@eq*nR~82LdKcJ{-YN`k7aA zn+LN+Dzx`nwd+@imLm1!_2WFCgvD?2ww>E`Pn^@cY--!DTgNFHj9&#>KY*}R+{7kb zZRJGfJLyz%u+|Fs@}SpU2Imf};y9E0A*@R|bGY?H zhc20f@@op088&-_VyVwcS+?S^H5>?MzI)mp5$mUbSV7xUK~k?#3|0D`T(c;|R{p(CiyE-q<3zYB?hg86Vk1V%3AsQqg63`>QIYrao#`vGAgcXdU)zj zaz~}i5f2uNd*HY>i~#QZM`Ma@$y(4erab=W`^>`;V65|oJUMaiGTfK&QsjYlsiJAuHL|MK&b!5k7k0wv_6+JFZTPNl;4KA_X? zIV#Fx7Tnz0GN}S?|K$M-`9l~Q6 zc`aVXAimzHB#BL&{4e3%ENI0TnmLTdR3WHC=INHOp=T~ z0?neJROsV=0e>;@7=lmCxoa@Vh7p)jfGcY|vyUMRvp#o0;O)h%P#QB+;=lx^3<$kD zs!Wyhb7`a*4{-vw1n7YO9813hQmtX5!oK+_N=ZDPUS5C<1gBciNjLjO-sOMq7XAVS zy~iJ;j{Vd-HwH6P5m-)b(ooF7urw9OK=c5@W`*Q0LpTolhoxBx@s8_ctgLM*^SrAq z57(q_nG>Jq2YUM%>nV)Q9u9A8vMYVGK3X8_f{e>aMN${H-81jN(5-_uw;$TZ=pwdt zDuC5uU))J#l^S~wh&!+M(|EkGo?&_sV@)v>;ob9;Z{t=6fpj= zd8}zUkxAN&9PmT@0*aT*z|(-s4&Y1NbnKE(p8D&38!|EhvCf-^4aCHdH|Fb6$nyRX zn@>-Vl9Uh#Bj+LG6GvVSyQ3_8yI`JO?g=zZrosSNQJ z6(`ekK}f~G3NYD3Vs}JgIHi?Y0>jc4nMhmIyOOc-^k5>b-pO%VulY|y!ze`>N zl)&P4YX}9o`?xCKsqe;{81sc!n#DlOg?GDy_fj9KeIr5!J%dE+qUhqQLCjFPasf)C zn3_kiM{`6jcH->2ahjS{fQMajn1J9~R0Mrq5EHcUB~pnKu(bqOO@O=a;}14ayUSk; z@L-EddSHHfzzk9WP!TN!X4Xaw{Rtyn8<9 zt-dq4Vo-<#2=<0{H@9ZIz8TG=Xjh zWdKnH?I-7Ht(}7H!$Nad881_Tbqj+Nu%i8FnvYWQ_bc&PuL*M`BJ7K8?a4?dJGN^_ zhM?JD@t#QUE2wMX-vN(>?}`bK@qnoiFYm(ZeIS`E4eiQB2kEDl58=hM-~~?6$5R7f z7Rb(NcaG*GfRE}Yl0$gvZ7~(lu0XQ za>=IRM|rI=2|RruXG7?OJY=1z=ZAjuUZh-rhgaB1AbJ_*Lbn8>_?Ex^d&Djf8&}Zj;r&Op{b8o1kJSN>grW}79dfo5X&RK2lvB``p0qng zdNthS&mP~VF@OoCp*w2W-v7kdBZM7Y2G!&4qZ2sgKEETS_Ri|nfk2>>?kzsX?Y9Q^ zp1zqOG;+l7Lv(~=HRC{E|~>6W=T z#&H2CkS(_!y$ZCrwcWNna?1C{!Wn6k&NkN4#_`?~T4J}&iucV!OUNOyI)GZ)kN zoab4g4r0=6+~iXBGbaLKdxCkyd;Dky2xxe=ZaZt5a?ZlI29-tEWB8}X!~Xm4F_Z{_ zxi+#(bs|!5Mja@@Hncs4LQB~+4S?uE;lK}RZr(Q2mHP+*&rS!T*W_iF=z{8dWk59gx=%lb zk2{UB#1Ta;Q(E=+ucGNI{Eh`YRQSLE$omZs1{Dfk9AlwKkb^-J7G_qx4-^?kf*>#a zn=)vy`FUl+!v6+!3<9A2mpgap7n_35X*fT}Dz^Vj;>i*rO56%z$2NAjU3vMLYq6SP zKLS3#8!3(hZ}RjJovVINOL zB!s^cB9W$Xa}LOP<1*RWZP8I1A+(niG<_sjvKxF71}+($76M4O7OW05 z5bLDPB`23Gl<({0F!MTRhnIA54|6p(AZ|F9A2{SWSr$8x(>)o}aUv*_m0~Y{ChR4l zecAz&eM99HryH@YcX*SnV|DcgEXN!SB%PV;j5?DKqxrLmpBx^F&x`+0bEw6860Syu z3ROd7r5%(;QYlEwEMsfiUBcFE%G2vW!aVfmPNoA$a28w zmm(7T2n-B>)+LCo^YUP5Qri&B%L;tWhPQha5HP{3?3b54OA`O$e3Us5a-HlTy#t?j zK`Fge#pl}kr@?jj$JxUC-LdP~^@Ft*N&|zLAQrb4k?cS6??UCV5qd;|`Nh;KSu4*gFU66NFu?V4yLq`y==;>MxA^ZXm=+y&V=AacXQPpy>4?k5?XN-aU{LGss% z@vN0DaWUz0ioV641Itlg`)N#w{;U(SepFa8{RxzC;y-xu$-X)k>t1-e)zt666zB_spZkE3KtO-?AbG4WCH z@<4j41~t(B^1lQFFy7LB|9$2h7>EDK?Df!e9)7(u65s&1-8T6=HI9je#Dftz;t{zE z(VM3~8>`aAF+!Lm3mD_lA1mnm1N$6A1ev){H9lXzX9U`ieI#%AKIr443lziW`rsClzP5EY zvlrjeIW(z3LWW+sA-F~HFLT?vwyPo!TGU z(%*aBr~n**Kv9u@A*p75y-MTi*Z0b6ReUguxvHhjpPQ&w=cHArG8~mUXk5FGZJH5P z(X*OTQK4Rkw2}ptdY4d3A{mbxbjVhVBuOOJ^1~nqZ}J`#*G<^+E&J%g_hPGBD;iiB z9WwD7!btF{^g;}<#>+e6*(C0)ArLIpy?J1@^w(&w-$Oo*J#f{X#p4r+Z})3e^k+GL zZl+kp{V!D;yc)$^Vx1?qb6@<{J3rWN-P?Mo3xhzn8i<_*4t;Pw0Kz`j2aG(MP{2S8 z;hko&kuwCg6b`Hsl@l}GW{@5&cx>d|SP*__wy$0W9uHon2r^DUm69a$d5G0rt^0}_7Rq_)LmyCkRyY_9sOPnI+hFyO!clXtAxx|&B(EUhZ}AZ@Ou ze)3I5Jt*Cc>qE;N%e>4xmHDxhNO*%*?`$&%od%+wTul5U54bhJgYA($h6JfF90$qtA0}*K8LDUGf{T&eWJxn#)t!2MN z!8+MlUn6M^BCHtr>0|j*2e%y~yRE*|keg5F3DJT0F@ag%rT9sZyg5;BT)lXXpVonh zU^dQjvrQM`F9zVYT;9mtRtG+yeFxD%{~aAIp04Jp-b{CNfJ3`H+;p)8z_s6ccR$14 zFgF>DLi6q`waFw^6Ji7m{=+YR;vASHJ>ZnZGC1R(5B4)@w4`X<$Q>ikbM-9hxN6Cc5+-qia5uCYQsQpUQ4);1zW0|z{_sIg8bjHilN6O{8NmwG{ z=JJ}wUrfX;l$pc317w`DLTa>ro7nl%W@+`<(38N~mS**GRLihUE?>>ITA_1cN@Tzq zrx-Ez=*C)2J5>ouoCruN0PS7)5W!pR|4~DAPD{UepkjRzKqv)kT6jp|0y+TFa4a7L zwwAUN5Xk58$H2(Ee&<@cS?K?w7!v_6ac@=Zj2yS9~rK2#=?buMHTd6+I|Q$O)i zzpBr`a!lQe8y=y-Kx+w5{74}4Bn#?f|L~!~sXafXh1dFiYmlY0#BhM+>3~tt-{618 z!Qz2i*6e4CK#JKx2Cgu99MGakS_JIf1FDKK(O-MMDDo&yWGM4)+4zSUcl;5w3;tDl zykj%%rg;k`Hc@UbMz?L&$wr#=}1xW%PkLw0xLvygW+ zp|dt&OQw>?9D`=5BWaAbhf;hV-n_ugo!>H#UUHNQFV_4+f{Coer1q0vxy5wsrNNbl z7!^IXu#1FMmjRE2M}Jy*XssTAt;XoivqaKMYLJ}9t=`VOc_Ws--4x>IIBxdSFVg_^ z+bMjP`+^kY7D(g<9+`l$ZIZ_gPS49e;!{|_P3MMRx|4@hKzOwuM`#>crJ61O=( zE)hT!jt#e*gZQ90k&}y{=)%dg)_-q41!%y{mL=`d*l-mtbJtx+$La(f>4mjxZv;lw zbeB&w9*A@rBA2(gI9|d9vzA}JVxvkQ@X`vDl0q*Za7Z6O)k17)2KAgUp}~AvhlKvsr&~qbr>cmqy6!y3&zau4&-2`M?B*<*P{uu4v2Tvl=3E zdvvdO=TD`@3|R4*Ij_EQNNaGE6|bGWCB|oeZw+;LY?9r5XO+~$#N6rmk9&d^%F3Ev z6wzni865oMK4UBM2e-t{Mk+Xl~&23)M+Txc3AF9$vT#xgy|Z zkmT00O8RYb`}kr>tK(yUQyG}9%K){Yz8tKTjbjnQ#+38H)il5W4i zgri7pgquo;{XQ2J)cp?pbB$Yn?6p*8*viV3u;47&>*tDOxT00j-hp4DYNTqXC1x)< z5@;w%n@4tC2j!6!09O?puSJ4RJkLL$O_*b-5QyfWUif6a>0NnzoLQr>l67JYCLTa@ zf}*Mr>8Y+Oq#0r2=;q&PH8L~i^UyyH5nDOdX}7+F-YbtAUFf)&f;D~N zyX-8R=dn9rf`$mo2DxXcN!)lo4vb!^mt|H(JB|p z;G&kr(qGIh&-d#!UFXK9h<)FPeq}(fve#hNHQn0?(u0Wt`V(*K#o*D#!1ClM)cVA1 z@s7O5D*o38n%30)yhWl2qRny6+8OQqChD_ld+Age?yy+_3+5YOufM-YIL|YG7Y(?7 z_yqFpOZjqExi@T18@jNZJ^688=cWCNRfem}-rZD}_*rRj-Cr!-KAh8*ug8#b+du!P zU+9m}%PDfkZJdA9NWpP;C4S&mgN@7uw8evgegcSjP^K(1)E-Lqv)+p;wT()>RjVL9 zK1Ck(Rp43SvZ$oI?1cq|n|7hRU*8C7xHcg4kdP)a9;(1DxAm1f=sjbpYNI!d4egN7$M97ljH@DzCKMBbnD>v{=eg03M^ z>5bd9I(O#J%O$j2>mjqHV8F&i2$8$Khgm&(;{bPyG1Z}ZE1yQCQ?+fZvDTHDQ}bCZ zp?leT0?r7qWrV6ih3jS88bW-s6yCrHWN6W3i2w*J*)14kvNc z>Yw_Yrq%0`*JdB;u-qN7?Kvql5U`F_Aj1|B~uB zDyyldpv2JT?J4V#?M}@G5myBQGw}_Zw0qc_=enQ7Yuhu#YBnsnFeHEWEwGiC>dF{R zqy&4pL-L)V1?lKwnApNBjY})PVWpfEt|L0rBh{(~&8s^o-8n;xv#~iLQQw%FJ`r7Z+21c^BtspC*{xN5!>g zNA}VXm;BT|3N%S>VJ7;H{eFzp%1pSr#O`lqN2|WYV6k*ZGcWxzuH?RR`v}cT^2TNFbGx~RKM%vH!5JcN@=A%{QDc_nYqp#KI~n;bBB%2K(I2Ajr7Y{ zCPA=xrd~#Zltx9?5FP7V*>S$A);ryLu_uO-q}*wD8VdNrO+_+_FJm5DdMX(= zKF*Z!I&vb5otD}%832_-iZI0IiXM60OkJKFS{95GpW$E zMMB7}t^gqsWFS1}zv6rJ%#&q~<@$v$NW$OQJI^v=J-61h1Dd!aMK}ml0y}ydW zcjLlQn(t;@$V%^xwhJhR3G|(xh)(`{S<=tuCQfS^eAVupS>xB1^COb52&tv8oQ=yo9z;6hM-R-qfdWNs(*) zsamYuCIP~mfVL8SO)jw3J2D7`*35l;=A0L)JK*z$T}d(vh8}7X|8IaJY5u*L`_0 ztM`=1C0%gizq{t`yA663;BQZDg3qcS8O7ti+ZEY?O$Lt)B(8iM1ozsWoJU_RAjA~Tg)YN=@`Ksdrav)jW}(< zIjIBov`r}v6^@LIQWPxT-C~!fB!EgEyb-l;U|^dTYw7gaM1Pl_aN2IF<)ke}X%~(k zY#ST%xa^*V4dBy($q0LyJiAAhyRdh|oghPG)h}X?D(Ja`>BR7dOYp5g)+Y>s62ygZ zuYT)8;zS8ftQi1`I5S_uZ=!yPfSb#S=%A=Vw2+Iac)f zy}k2$;kZ;lDJ-3{XRPCb?V#Ss(?m7l3hS;nQUmWJ`0(f8G|Df(2~g|1*6aInouR3N zO2FUouqx}BcKTwN+~vgo=s!VN-(`v>T~08M@MeuO=1S(NB^!0*xAz~>-Wz2yHiMZv zi!q@749#r#O>GCbcx7IXn+DS$gOU6k{Rcdg5J1Uv0mti%YMxU)H>&Y)cs#2w2Y9RS z1+uO0_}P7X_>SYw{I>!W-bL;WewjM%+oQzxaV@hOInX$=J!nU=lBqr#>TvyZsWpcP z*-GV~D_=dn<{F8LQD_K#86F+!Wfxr4TN|tLK{<4H2U7Tx*ts zn*fd??GqB*%GLWNII3D;ErGILBR&`aWtWnDGvq+Sy6RJAkFQ?V(hje`bX$%`Ufvqe zGCdFa&t|^FE6-QDVjYZi61-;f8mOoNKeW;a`(hnp5- zcCV4~=$Drx^wbst6eF_!WK(H4Xt+R--7!yUq_(AMe0l|rwyNY0(BM^T;Z{5bbo22z z+9%#W2|*gjA!*w&Ck^4aIEPm_BKJ7@!-ZHMw0*)kln+w0^}S@a>&8LV*-msCzXpH5 z7wLQHT)1xXuDhe^2kjFc;`SL&oH?G0Mp*Z@%e2G+s|#$s8aeEdmgB!-a>WSZ&|I2) z*aw+Uwxc^<1zW!EqLa5-I3w77ec=qEp>~zwhCt_P@#x-JaeDH5wCmu9XUC1{qTy=b za>4wW1`GgnS3RM9E_tPA9OL1_NJ>l}X>FZEl2C_?JqnT&3%Sutir?eTpX$+r3Hw0PH2 zUD)LEHk2C+J5KN;I1L7Sv96RU>LJv-1lD&?{Md@pgBX<PTGUS#9NM zab|7-UJJPCc9CQNCv%mJPzbd$a@{}1)}Fiz2x$4eEljrCML6$B>%P{wE55R46^%a} zUPWnZd#+D51s`Nk9Rp_u53;+%`Rnte77+%kUZ&A!<}vVK|l)q72G)VI0)ZX0@(X;{)K$+zq6 zg+CWCa{Xz!8HqHxt^52EEn49!+Uw19BkKKQ8$;gHidpGtBAl6H!@_*z_fZtP4>R=mys$z_6=5M*OB;A}^J9Y{ zf~~V8a@|h3+e92n@@c8$HjCY!@Eot7jWB5#G8mcC?w?MFE~%i-I@)<~GUlnK@UeGo zUl0O3AN`$X$Lh|GV|$0c8oCgR9=J7gaX>qO8g??()!S=6cqf;)WQ64p-MV%5Dy16x_ zNzSGb8tc1gt>4LWFF$y3yIdZF8^I^V^6^Mcj{#HjTnn)e#aMVr+?`=5WI7@}=D z)s`bjLuUHzKg9pu4*m7A$pd?u_|qTy=pK^Va!(P}o=tph&C;?B&KP4##LkSv&8cEH z)}puw4|512@;`+V=LMkjA~%NUDr5OUuBx@tx?lo|HQ)qwMSXo4h%yHsinadMi!i;- zHElVS^%gOrtptCDI))JMG%k;@5lxs*U3q(!?IaQkZx2fbZf!qv3tqO``{UdE2*gc$ zNz&#?Y7Nrg4^fTRwBB5MplnZ0Mi|?id%;3HWdom!Q3ZtcI#4%md;{seavq2_dlO#; zvk!-dtrHf!(TKbM6j%gJO`&Ibw1XFmfhMWm$;kwmH)1cxI(H_wENs2wGvSU@Gc{G( z{&59#yy(x4TOxY|$kjmF990sJ;meZa2u#Oi$U(3>n~7>$qS6DTQGzNl^!0SDR(Pni zIFfbHMCI=+jMh4I88K=MdKHwEI9yqN(fv|h&m*~EXn|ko0fsVsiTG{c8c9B5v|O9Z z1>qFRKct#*?E-CdPx*WA|92+#UCV8*W+%Jp=4K^lVnmeeoO`Xz5xCW#AD&`eAGuw@ z-K#N4u0d+h|LJIVQq@6tDlNQU<9LRt*>4Uydz*JaL}0QF-Zq7oZTJzgy;VZ!ml zKw77&+9PvI0R==kvl2+p*~5d0^qM&f{%02#dIh9)qF3}#%ml}b8;@Szakgy3SGqu4 zcXhNTAAX$?i?Sl(2~@kt-+PB~CQB4GWV+roJSRWJ9xtoo$7MyBGXfg!AXL0lr+&%AbLCbLqX z$jt4AQH@{5I5hFpLYf^Pz}mAabx|X2nVPb{m>6poemSBql$Wlrcz<$O90}wPnkKzqiFKB8oujRiaKn`(D2ECDX`o0rI^v+b zDnd#U0k;ek966BhH$agLToqK^*E@u*`$=>9y$`Ql3M#y91raJIPq% z@TouW6@(oY94LEb%ACo-yBaaMhEIY=HJ==I5aA`Ra`s^F16 zoDh+Tl2WuWxyqDbZMm8Znw#Z8opYzr@OW2|r_P7#j?qh$>_Vge);(qAR(`veSqJt9 zHT0WgM}DIgqF;F1Ro%+8xgYf3KJ{<^CHO!H!nxiCl|A9R*_Nb}+0kXu?%iO59PWYc zU>*2fPf*>|6ox*}$%z)X6{rIGr~P_9GtKs1IcDWXSg_;>g@6$A^`~l&NqUdwwd7{L z={S6Qko~#;qzPigA@aw;qjua>lw2ceShN0hfL2ohdSex8vb8ec7?@oL`h?3{32C<) z-bf5M7XWy0T{`Ak*-x{UP&`QV)nybnAwHoDn228rx4uQ~wpu@?5N*}PCSX4*!+ubT&wA?(k3FZsXD5e4R>*! zcM6e8y7WmkdH46_&!HEgCS>wYc@7=?Ru3KQA+-YXWto@E2~0r_cR`Fw0M%s}p9rUg z#0rFeATAEK;-~G-nZsYXQ662wCy&X!?(~s=xy66=FRe@?TC+l9P_*bb|KDZ)JAJW2 zl_C>+$@%V|%W7t4N>sG)W-?~yxL3Cf7p30#!Z>)F?M;_h;DsYh+wiFrqIu4dfU@_j`$dW+mSoA2I5vG8p6rEnb06^Q+DC@Vy4i zhYz^8OmpYd0(D(}?nuqb6%OtVVCRZikv6+!dvHdJ;X2C5XWe{ z?28FLwBg<(a2GudSsNV}7eW13-=Cz~TQdG9p_N>I9klsGlPXNCv;12nPv>5no!jTp z1XYxkLvC>rHwG0M*BB)biIe_4)JDEuWZQ6%LG92pP_nT<8?R&co;t>l4La>Wz!I^A z;K)0?_Iw_%=pFts*od5WMo;}cc6u`;-gw~F7G#75Zq|(zEqM$en{Zyxcm@%!2Lp){ zH|>%yQGJR^-CehnRKv)X*wMG~`AHRS_9o4kRWhdJ%#p>GQBM{G_YlQVgH-{JesxMO zFEmV}g<|0lSX7|-LQ{!aa4)cN!k_&R6;cN3BYw08CmT3wO2_1^|NJ(R5b_tl?!|N) z(QgsyBH{;w<9^Fe-9G>Ohom~I59v*DQiYKg-Da_6@LH)k3ES(_q@Hq$VvB7$4gsD6 zKA8xkF9nX!!;N=%6MyJ`OepQcu7&PQ1r+7vk<}IB$n;C86{|>I5!5P;I`@b-|0a&q zC3IZa)8CEvLCA<7ZW5DDI|bVaW12%3N6^zA#ITu(&r9|wjXWl*J4f@w{IZ0DI6XplI1r;HU^&EdV;;* z28}0M;wblk6N>tir&1D$h*fw}!P$EWnI_~jyPoHdjsC-hA(M5ToAE2|?ISh3N;|jH z{--#96(mMdB4WOztn4D9^v(GME}ydp zJg^O*@!QQpUp>w^g^%f_HiUCTm#GAZ#P}|y6PFRt%r7t zM+xz7Mo^wK7cgqx>}HofNZrs8=89?Xa;uTkZlq?eJ|g%aXJAFy4&g{;h_7+gUP5P-$|0uF8$^3=2IGk^l`Lid%SYf4JR$<>`nwoxk2b{ zV|4=<7~4kE@$s>$k?-LP^D~IcLr?I(L4rMZx0x>P2bJsbct*I`)<$WIIwpoB0IY7Y;T4qYbt;G96jO)DD^Rn! zp$e2ZT4%aA{4R%DdWk(gN(&}VurK1vOPr4WZ6oe0XKKb^KWIO&@4FklTZ$sUbuFF# zCipxTJyq%3OlPb25JCjN3W-u#+q%C^@?5qIGNU9fM6rA`YU96n{bS@JFR+1FZvXsr5EODH zWSbT99-;`uC>(v6!1UIbsfHuN#$V%d!U}!-y-HMalgsn%=PK+|bwY<9fyLtxQ4{MV z^-MsY+y(dLkXRNK%5Fj1^DYYmeh*PN8}$46cY>^te>WkK+5rt#^&cuXzqA^oeH4Dn zT7q-)0(0t!k+O=_ZvNTax!r^5t^JX_O|DMqtk7eB z*B8Vl^d>I!om4}dG4bM*RWu^uXCiO@#3X50_~1hP{=8|2OM?;2|59unrf{h0(LhVm zX2d05j)h`bQ&TB9Il#1Wb)(+DnbLe|TrK>n|brZ(_gbj>#STRJV}3u628pGFD=jBE>oZ6(_gKb`Ee+{Q-ya?>ju@WaGiOm81^^7w{3jI+#u?ke^lP z$M0@nY!BG)4l?9t(}MC^3n*8Y;PZBUalXW^3ywp2&*rV|o?y_ajHMG^`AnV6oE1y+ zv%%Kmq0B3M>d=_ve)}t*)B>3UXctYi9*yKUl@y`*HS;9PK2z% zNkuidy^YNfLAcTkW}fPBz~BE2x!>{hdTa6gt0I7ad!Mvj0g*v?s^BefOpM&_QCKb= zs2RIY;VAWo#!igUrO_LBGz$gTXKH&wIptg9N<`a3q~3*|rzYhFP1v!-fi^Q82d4EA*$`MZ;)jdr4BXJ^NcT;GHo{geNULiYR^ z4P5{Ohk?@JQ<7!GZ~kllDX}0tef;3NS9E8xMzj{|ys|CB-=yuF(ZJl5_i+h-P3GT| zYyV)V6jMP>ZK3C+RXM4%4d66>xXo0FwG5{l!WVj6fG+Qn0n0NjqOsr4FTc+d8fx%A z%i;fv#`jx^;_Vi%X01pZTJp)NC+ByWQ;>clz|Kguvz;xA%05em<(k{zNIP->)> zJV$eJVD*R9En+81NU~QEPCbGH{OSoFLFl`B-m{Jk7?=`M01*o$!yz|Z$gwEg$h+H< zdoUUAsv|^-(Iw5m7mld)B1^r8d_d7YQq%q9a6=j=>9&2zUvjUit63YI4 z^O!fX+DCv`god85UU~hTRZ+U)-UT87K0U2*76<7fEv~$11?>qa~0DEzf7Mwz!v$xd@XRoXD{|Z0}>zLz^3sycuRcePS={oj-ex&G} zs#byuCxgG88*I4oLO|$lSi>selmY6KBVM6Oyrkl<(bK&B)5oiD_+~j!fSXZRZ%D6hH6qP4H_!z)9rriq@cky0KC9R8m3$ac~N6y`69(79ruT$~u?r zXe~{yJ?5NEJJ8qPxc23p!OK?{p)Mz|GhsAt$Ms_zKv*I`6@A|63?^7;Q@G{s%ed>q z;^6ruMi92vNnq--2!zrwJh^juI}gO7gVh>NeAphN(S2~V78<%`aAVeJ9ga+cZ{QC7 z-QG~4fAzMEwur1HtDaN((Iw%?KlCXxcbmlfBQfiqdD(wbccaG|1hYxboWkBXP-}Rs zb_r@0)3uL~bi~z)x}H$o6o%V?{$?V@Qn)HY3EuT;OWkKb&NuF*g`d^9_d)C{x7&geu{>}*{*M@Na=4{Bz4F_ej-nxdAl|(CezfVQ^~j4q zA~;}fizgpNMHJcg8YHLuIRed>MP#OblytGQV3-bVBB@!EUGZ^bg5X6b>eG2l zbFCO@`-I>SW2Cq$BW79kt1ltw7jHt(g~MB&Z+kY1#Tg&@!50_8WbH}03L^TpqdxDF zw8`nDtU>Bu*qVm9(}`=N?a2X^I}5y?!my9EAea2wX!^DU^W&) za3AmxS+ENC%AD%crLSoGC(;!9CyIt->Iy;5om*tIPV`l!_GBAV3zH4>2`5{&UXiVm zcXoTB@Gc#xyjMW`=6xw56dMkKPP{1~3aBKJjK+m4e=r6&6pCW1 zF!Lea)(ewTfMUTPc&a@Wu!6lo&*%}1XyFp;ydQDm_3VDeR5QR^GeVE|x5Qxr4NxFs zLf9ctDPdJ_T;mE+6+~0R_=V8ymLvJJ_9%`2b7WG5_>KPU0Kjg!RPI!F2yFCqfY z8Dklaq0}(o2cjE-B1Dy%MGPd`x+B(VR!-(6gcuDx`vyJ>SOf#2?;HZ8V1FH)`^|sg zt#5sRZ;jdTV*@eC_wXMJUy4|pi}=3k-(q&5+eFdVXRcg(ZcMnd}UD<2l2iQvpB`I20FM&{~OJLUZOnlw?MY+0Nyqs$L_{>#I>$}du#s>c^qPRGb+{lPl>PpMEo67Lsz%jmZdINW?X5+f;@g`^5l|9+oY@kBqYNF-%k z#t^6imk_!9z&x0!f2{fzSlen(Ba`?5a;WwM5n~LJEMZUd(Lh<#7G7Yw!26g`4NwNl z(|4og9hvH}HmvoRY1N>S-!{ZNvhc81H1cQ`S->aRD6}Ooo|bEGGQxv$y|Gu=e-qd&OSIAXL|mPlsV`7vkxsc3 zQwPm^pCx|61d~&Xj~2#(aQc&jbz}pC!fiL?=QwBz6^~u=av6pgB2=g1rnbYtBEBKV zw9)4c(k8g|=i3JNNbRfWH25g5O<&Fiq($QgGMN+t6NV7G7XsOYuJ37m1W-9DljCCR#W`EakL6Os^P{SD|)o$Z7Y`_jLAvk9Tou%&8)Ipp;2 z!Bbs6Nn^2@w(LXtnxnt^Y+y*V*iPnsQAwf-}#y#AK`g}J#PGm*NukOEH|6fJppQS9y0T?uD^ z1T9D6;DEMAdD7Saj?N))q4S4^tzdK00bZzmb+g`GgR>b7CCC9)0z0B;p&Z`tVX)G< z%TXq%B4202+SfGY=r#Cg)n!;7yz~QXpOmB|mVinQ!Jqp>;HREspo*H@z_-rN43F`3 z1rUMp3`S58Rx25hG^Jw$UMS!}a|;Vefcwrcn%+3z-W3BCZd+xB>F3MFU9y8A6bM)% zxPW9a%W&AlvsQ`OdF_t-+RZ}3NN(Warf2-R9aXmR$(y#E}CAmK>vqn>5D zij?$S<{b+z?y-i#!kVY_(3CKhF$Xzu{Imh|={iqrxDauB zBc~>FbQ=sa@XY_H(@xZr&;@*g-+(vlK%cwP9v~uHq1MYBhH)wN0;FhgojSkh6=fLQ z9D>m!Osnu_0Q^=u40FE)wh}uL{1=mRN=j~5yxEGTq~U<{1?*|z>SOP! zR)G3hXLQbqUuz6_Cu(Iap3=hH3X*Yxz2x3Et9x~G()}{ydSNh#y&8VfkhR4r7|pja z(O9sdhv?&Tx#qO%eq4m;v4(bsKNo3-a9px$H{Yu#^Y>m(TP9zrp+>Nl0+I2N%bS9H zjv)LWEBslU3MX7g&#N##!)jaYw1R% zJn7#6i~Vt7%K?hYAR27}K5W<#slB}X`T%83FeQusqNJ1c)oj@Rq{D&{0Je_Z-QCI0 zQ#08AdW|drBMFYX`$4UeO&(w)%!jHsjs*B~tBj!%O!xr2vY&wG=myp0=R0X|{=M!N z3-#Hd8SV^69Ry0Xti*z79|16J0alNj<96!5B66st!)$RPr%bYKM;)GcWJ#2mBnuX!GG&9Jl{Hl4pHno^RUv~ z2}J^tMPk8|LDVlWrcGHQ35RPZ$7^ld%Y#m>4^P2rpVjz?`5|=L!1-cxzhO3Xj8^V! z78O&DDGZ*Hou}E?{MwgbdbfJltrfN-3&n>wUp$1RmJ)iCc0b_lnc49dcqH!%4=r`= zFm@dCssT>zpZb9(CPjQ&IRM($AE_^r!aAm=p>FK5a1YK*jvl&8SDb2_NyHOKBO zf=6Q(6*<9_&az8|zN;B_xJ%&I}O zZ#s%I9>xo7AaCQrr*MIzj_s;!)lC}wiC>dhcIDiv=nk*#YYKm^hib@tq!hmUS9aln zJHRBgn^X+K$TfQM=#Q@)RA3DLSIM`q%hI2K*`gYqa{8=P*2}uK*z^C zrKhIU=KF64txaFfu0|rNDTg4nT*d;O?f)`z#K{4s;F>!`dQ6DDxp`jWAb1c4VZsjP zg2dfAut-1$c+N0cg{kZL`7{w`C)jJq;V%YVxij-fLgg4d4^Ry8;N}Z(epuApV~1?v zZW+P4a67r5G-~Ly6B{*S_z5#c%W9y4i6fyAoc<%YNz6?$AKpX_}?iPHPm+oLE_T`YvOTF5d0ZP&YP^=k9L=L$jL9=jmXVc%23Q!0}C+x6Lw1nQNZPQo2 zIg!l-nJkM&D*v7&uqF8t^Tqoo5SP?2U5~^WqSaGbWz|pGo2RU{YLiNwAFLN^a1edcp1Z`pOoiFM6Yi0 z>7i>SmWW}6h(W!QmX(~5p8f)jmb$Uphrd+ct#pUK-3UzEKi0j`+)MDa0N2oy81>|g zr;G1=)}-?8^BL|%FCzaV71d~#C;7Vm_q+^QBe>{m4}`xr2yX3t3nVNf9=`K&Yr`)u zs=Dt-naz_OK@1%x?`!1q^ha)+SAV(E_Zfd$&dIxE5;u{lg0P`{{Dob&A~D&*(Q+XUa7TRQayUxP)bhyy6^b2rX=$dX$pb^vE*=u zP)Q5pxl73uXab^K`;*WU^_MH*w77Pz&gk=^RXqv2TQNFqr1U>egDfjguTeQllgRd_ z6k$lJW-ukZ8q%$g0;1f#nsivlvwRdCgxn7Ig&ia9p1& zpQz{~*z;$=tDA^C?qtn9;9$3q!eFUe z@2)|#o`8gktr3QOe*xX+bUs}w{afw$J=hy5&3C0mn3b^q8+B_oyh^Bf^Zwf>#*a@< z=k#+gC{V=i$+%rxugS|U*{s*TQfWH1%bS(w-{V1KOJuNSVj?VhmB!d#{x-avNf*6l z5zq(o``lsr*OZZ%xD|PMa?Hq9<(xny-a0NDApuSQ3I#5J}UyT8k zlu#YerkH7fJl2lj)BC$JT%EwDx8}Xr8wS|dw&Fvg2z7|kX+yoZ^f6F)1utK|Ov?E4 zX#z<{NEOSforae}UX=7x<%w|D<8_mRlK|z*1KeNoZBeurPG3}8<^{O)$jt;367PR#c#+YLA9;vS-@ z)tjuw(Z=vU|54SF6j8m&`o=Uvv5Mn3q3jJB#;;^wLJU&73LDkUOEj!7`BiV7~dL2UDDRf=lV`}f1fA~ zX&2rk;9^&bWFtRm?rx5a)sK;XN@~3DCbreG?pNQEm!A>`knZ$G(VbOsxj3N)rF&zw ze_{W|7}ZK6`nSJ1H1r`A`#pkh-xj~HXP*u?z|w6tFRkPlGgI(P0}hR(~&&#NyZy3Znw3N`im6ggNP6W5O$ z6&hu>9lb9IgB(Xeiq@+4EyNEaODbA_6X} zvY?kf)%_J+j@7bR!jD~t0DM$n2X<5VNlMK`>+0&RR@i2yops@fotespFJQX6ob?SS zvnp~xy2&E1$e!wx(ZB*UEUznHNBZxi;xAERjOz`?XIH)<#wIbc`p!QyGT40njJ@u5 zW58CMA-EH(eUXQz`F&5GJW=b(Qnvfn-Ocg6+(wa-n1JySQDJ{fpX+t_Dq27$pb033 zyB&FTmrRPy$~vH=DNWXm!>q(~d{=^>Fq7l!l8F0u`f>AjrnlGd4_iZ1qB;3hcn%m~ zH0vr;6}Wrv{hi^<6Rq+K@}e~eeJmsTyQ>ToaNTR#pVxuLO;5lX@=8doMT>;Ooh!IC zP`uWYDm82RDoV|=?lovh_iCzL`*`yDT*U&_U;QFwq4~P3wG%+&T@c=5O052aS22^w z24#Bn>P-Bzyu0Vq;qfu|r(VcdDBVWbY)Ex6gnxhxt=Kxf9tg;7VK7$-o;$~o-J`>W zu?Ks@%U7?QyRwuI`l>ie#5(!-`5{bv1Li9aTI`L@PhP>dz;&2hEyALa<;BR&FAl1? zNhKKaesTQocgch%=W^3kS8D7tHz-4%3_S@(8&gQ4XrGX*EQ4seej%~uGUXS?H}2zb ztfPLpyVM^ADZeRqIY1!59SHfiBzW!powN?3h|N`Pz8_r$y;AVk{QR?wjMDPs zi*)L02726kgF%}*O<}JWPJNjsZE|bkZSWy@@%+~&M{aC~`~x2J)s7T$LF9STT36ax zI_zz12{u_Jd(E+C)X>(JBTwkrvuEMoZaDoJs|zJKL7G*e>2-^tRk%~S#r}!Cq%JIb z>By4iL&}@^SN9)X;DH5u3ofa@F>v?4mG6mdMM7ZApA=C*@J1FaL*Hs_zO>!_M>0FZg-=7nU_av{=>ig@U<~oE!i3WY__R==vfGKo&NX$ zy1(%7)oPDfZLr`#SWU=c+`Po>aob~7J0P*Qi+=D~RXBuwtrhk4zf1it8DgDqLHLXL zjmyESHvixek$wN6yR*}&D^&_F^_|9D)XtYmK4+W^Pj8ZlX{wUHbQBdkkCc>2qtr~8 z7_aL&*J9H%MI zW2l`^(`b7M&b(F@70PT>a16ye!4m~9X&3hQfp8}PhttWZD+LCp39mP7w6SucdlEQR zRf6H7_yh!aNYO@xiC}Ae1Sf+P)~O@Q>Kp84X2;Hr|72Mn!v`$lP4eTTsxvM|9}REO zqKu2r>yamXF>B2yD(t!^FtkZmIr`iGYi`Z~UjHnQnGFR#ws6@dL@k;;?91tQl4km~ zuERqE+ksYiEiR4KdCuas-w21+hvRag$A|X_h$(URYVrqeeeLcpuxk!6NR@OzJ=s{T znY<^R{%!8^J@aJeV)Ke_YSlv=^k_Iahr)Lne$5!xQD9*QlrT~6yG92zVWjzJht;ek z>G^Ydo@w{e5E->=W!J7=EO{_|i=XB3vy|oQCt1pG7F;hpy%0r!g`LAAqf7-47}I*C zOFEz#AMu-E^KK882SHFDIepfp`%tH3U}pEAlb;;gX5l`AxMtoWUsMvx0Kbx?^BRb7 zweWf`fMu69pe}Un_d0OGc zQ+Bp}MR1aGhNG^ST@xE{Oj!Q!I`Wl>aEd_q&}#;JXz!92k^H``gl%@hL&F!6e}|{X z{H*LQhhe6O(O>*tx~q3uN2ERUUiR=G4;bq&VPee+&5}@IYp>7ONlZ%8x_@69-o^aP zl}_#Phw#y+8Wgw)7`}K8AMK?PVaW-hyHa3S)Ooj_0Powh%a+TwLa(PqUTON1VOxfW zha=L@-F|3P$ji-*a=9lyts5DpNqpAvuj^%*YgEW6+BSaIJBoCMsOtJ@b$Y!Llf*t= zZTGFaHTO@(eXBj@L}=;gXtbWX%NdM+@P0G88j5gUd^^O1Dj#^nZa@V?xC8n35U75H&>(Y+626iTc>ezUbOKSn2)~dD0!R^ z{6bqDa>#_cThH3j=SoH?_OEeYp}#r$_B6$bP0M3*-`VV8Pj@*sK0ZFYiqQ|BY{|jj zrL7M}g{)U6O76~$jlR3x4!c}3;s^$<;aGSPMn1HC_A*x52)+%o?hOZK8*&PnMwhQ# zVF$TVH0o&*TVqqsO>;RmvxVHtIZA}MEjmG?JL{lHJcV^`a)FngYU^hq=hi1|*hfB4g2az&AW+7!=YXC9isJBi#>IGf`U$OENVfNuiNYp?T4B5PkGMZzkP2v zo3L-Moq_#}dVU{WG(@5l`M+@ zhMXsP>f!)7I`D8m!=*~fd0xUg#6fdsbyyRYGO#C8vMW5_m~Agyn`{of=3ig63#B)F zv<}ck^4y$kX0~z(rd0L!M&90pIGa$a8lN6iz9w(2!=oIpebaA#!w<#)?Vn_|cxYH; zT-6@4oZH#?Gagud*|@Aq>h6dPp?VGk&VT*-^?cV|L>VsjJA_-#f`a_|_3P`-&K@lf zm^ObZu1ymj^2;2PUsJzsj=M;Z|DZ-7Zed5$uSx~TVrN` zeber>4N+#o!WVD%dv|BZ<4!2n#(E27O~cB#RX2Lw^0?fQn4L_8zS^5t&X-#0I6Crx zr^fkUXH~|vcj|I}-`*uQs^DvYp}+OBR1Y?eo#TziATPTlu*u*5tx2u?skYrN&rL68bO_jQo( zL#eFwZzvqNwC)XCLdsJE--vq7Wr9%CS7a>oTLax#l{;YEN6& z>${O5ClL{m4Q`glw2!qt>a15rYZ72@g(jM)%hNW-5JDL!JosZUO+qB#6nrpuc9E_Z z{)PU4V?1^3z~=Sl2ux*0YTOb=SH)MSGq>R<(A_vVf-MbCr?iP>-mS;K`2++_7+KfV zbFMx9t=94lgyZKS1UDaqNzKK6z5a1_ELsZ!8j zh?dvxW4gMp3XobH4#L;Tl$gL*(yNQU3q8-5>3^nM8fh6C0T-Fzqp{K0#iSez7ENtQ96o76&Xb#!cmS$Ee*Rm>mj*EO zkMiq>>(#=-rfSc7-Ix!{a71ermy~!aq$0;0esuAKo`FV9I_zN4=m;M#PW4K?oH1Pp zG*(ee)jMq{#Qr2VMT)b~Hb@&BzcVf99~?0o^;+*wVcc2=MSEf>1+bpMxoktXF%;T55Hri9J(c0 zIlAXqP6Bfk3|(7J+?NO;F2efC-)`az3h9i9*^0c}+%;8-qX&Af%(otzN6v32?+?N& z3KDHWH!2@myptpr|3L%Ah#4?l>Z*t9f&V<5H_4uSb`Rd)wxx#@=<{1-=}dmTz2w=F zUedpo6nUw(qv9%XvW2~^0h|)t>L|QeT&c4>uoq2B07k;!KWL}5vP)m-E${_m^{4aw z7g6XncLZVXxsAl^U>LomW6OOrwHx)e%YPALg$LB17@IcfkTj$@TKh1U(#H$~;kRzx ziku)@V6J&{Wykxd5!ch7#%C$c-4%;m7iHVtiJTsMLdNyet!B@5O0&D+TA9dhQ$B(I z^!sn{YZGTihpE6iIfc=8N}HHGbkMer2F)*daJ_B#DZZ0uJhAbPB<)YU;D@#+=xj&{ zT7xJouw*E#rc~Z&RJNqBrjnjy#gN0z*h&8w*TA*3$79JU)}jV+fL@_?dd9W zAK0rLu;4d`uw)174M(Y@78Hn(fR*GHL~ErFTJE7cI^`?yU8uiNS4SrzTo9yP&t&xE zK{DWKwxbW5hwmfM<#%%^I`S~ojD#0%>KFFdm3^~o^sf9q%4L1h2${qrpBY_~xj6^2 z(oX=ESpYGu$=DInEg^V3rmHJ5^XQ4ON0Sjrce1Zyew)s+X_a21d)HO_?qv6m&s{Ik zY3Ni}m+-6c&c4lZN66ZH;K0_Lt`Dt;26T;#Iv=>dS$1?nhB^NCem2-h0ZzKpK+B}!6c`LLw@Izj#HHMwqR(t zKSp8zvPC10Lzfk9Y-bN0nAy(}eRsuw-~Rnl=RS*QMX`l+yVg(pcwvcNB)p3{>=EFXeze}Xtn_aWWi(4xr-eM=s3#pwZ;st+ zZ+Pu>EpP>If|3o#8?Vqh*+1eblMxk-h7!0jbe}MXa^OED9-4RaE-o(Z)fgYWRW|vl zD~9}ulXDm`_zdNU7%A!8W0?I+L9b+Eu-zTK%$Zl-W|X=DDr38xyPtHgVkDxU8+X~I z$ zsr1YnhH zLtC5Xu=wl@QQG-b=9!i3BZN#fW5}Qf=8A?xhToig!ea6%+5D~pzjg+`-5+1migob) zGhDuif%j~rIfOOCV$cM5CH_M}uI1?Xv(mAIz3f}GUe3vvQc+h{7TCR@NE!B4u*-ig zrFnPMRI7z3fF=gthOuqGVfxYO>Q$9s2NcY9eHVQ5PD~-JVLjpDdYtd@7E0AS3dlz7 z-Rw~9z=bc*_hLkA-9sp$b8)eXK2w9RPjfy$|4nwvM-ee3vi{x1`YKsmTEIkJBap53 z{;I&^qXGW|7$m)aecWGCTpS~)d&-m~GG0rs%4wJpmT*wVL{FN-;onp*P{loI?Tx6< zaU|Pup()NsMnwnCkx&sgLHd656h?|gof=G<=<*?Y4tr~mXE~l2 zp3x9ATo=ADGq6ch`z%43+MrmI_V|;BMKpr0Yrv-|7$)@%7vyI*tE^%hPueo3vuy$t z+~c{OYSi-cQ;RKX+M3`wvoRWM>Pt}=BcIkRh85UKbJ)%8MUwyg;=;nO4w_Ik+=Gq? zInzk>xgIgK^9I=&7bTlA9nUpjGs*u!kr&%tv+3S!^2)dJL2ppo2zh+sCi++>j}e{X zD7~B!rKceS_e6fa;ZkOV3$h7-2=R=5b>*~Q7+gkORQO51K>ChD5^-sEoW?pgouzdS zA0{X5+NWy+bfKs*g5&6DNug1;PPk@GN5jC(BKM-d=E+KgNVRYc{LQ94P=KA0!5Iyk zFM6y_DseO#l-@#>oJrE*Cy(IRj3|GRlVQ{$NKZCvf`Wg77|AnEud>9Wt*tG`Nq4tp zJ+f`jS1JFm(i~YPXJa`97-socymsDdl00?7WM@d>)s%*+vVJXo&b=}+iEC?@(JK%@ z&VNkW4;Oq9r+(27n$X~ZnONp6MhBFcM#O2ziK`!Jh)xk=GtTMSvzL5jVVr`=nv%19 ziTiumx`v%-IE~!brK7z2+h2CDeEuMJlF>tHr10{~{j|>Ba-4FhZTD%Mzr7SiW1uL? z=utX%deson)`LIABJQfQH54Vkt2eI#iLrCRF|8Z54y_O7>laVv?sG2xXkYJITBE&> zEL!h4bLMsGeZs#l9~zHU*4j)CWIQkx%1lEE&~y#e1OlS~k-qp6wM%(71=S;kvLlE8 zsqPt}r)tR0rMWNZKpP$&estleT<^J9r?tr4Lxs0U0@t6F7b-rSQ%zJq^->xA8_F>U zye3nTyS6VZ68Xo=8#awy9$`y?#h9nW@=*PRZ2ZOL`IEWRhVoKPlX}ba@0YO9qU7b} zQxUJUjEr`I+_L9ZsH?hiVRZ7sNJZcvg?ITsj)?~?&Ig<)c0E>m@k@{<{4aU3?^>hy z$(%p_TGS|Q-nmJhebMVt`5ovK+GSy!y&MsR`4g9J?<_ZA&!v$ewe5@ey_^;^ciKQh z&DGVlNSRDPW<#^_s=~uyse||dhYf=dEU+k}Q)kc{-zvy4>#NXE{cHKa!(jAC6ja~H za{f@}Hdt$pwj5}$9CfOZyEI`8L)NlhVbwnl$C4xa7OTv>)5VE##H6f1n+X+!X&f7rxhi4 z9{*_#m=v0igNz=5;t{M>}7KX2?FlWr1OZGn0RN8djv*5XoAqrYF+FCcIowe;N+ z$zP24)s6?-anMX&QizL>zvI9T3qMosO^xq0)aQi?>jM!@qXrKkVLjXlmAwI^h%7-=uz$O8~YD4lIiHy z{P+;>DaVOeU>afJvODPdu6oLZ$8)FU%qiXaU(2)X^6;*)){d?YWT6AEL;2p#}mo`AA;m>47jAX!cFaPUx zTEO*n$~-VK%*!>FBTEMji1MrO^~KR90UTG}yHh*vzMLoTQ}Y+R;2hO>YA~WqA0Pt? zHP@yTT7!QL*6NI80YF2)Wm{Q4u3|`7`0VFYA;_y1u594C+PEv*H&`okFB9!qw98j^ z0T66Up=G4-P!POfbzx=$3i4yA?3z%QzsvE;2jGIB{CW^p&gGwDCh8{Rf6{5JZ?Bbzdt7Fq%uh;aVHT8 zh`;lUR?Q_>%id|!|G$rCzSDJZBb}C%sB`1he^()Yw@c=Ke{qdtx-cUn zK7>-{tF@-4=BR1gr=_q5ROv=r4Zi<(jg3Y-N9ZwKFgrB!Ka4-o?m*t$mQ$o$W1COT z1n7^y*fBys!BQh1$wPB1gKcucqyuw8l3TT;IYuE$iFvvvJZuVWE1%BE{Z!d*1p%?A z+;p0k!Y0V%#%?<--`b{?-yMfw@WK9YVGmz~ZG|2Prbpeb+9ys3FwjVV@&86aB&sb!NIwmwAV4V#P#N}M9HIf9qi4tnCeZF?G2d#)E5PQ81k@@V8d+k{pVYZ zOzgTR|Nj3@>~)KYwq~l=38dc|YT$y@fRM4Jo5El(BjsM%J8aPnMpxXQnnZSu*Nx53 zfl@L;`1!)0c>g{I=)Ap9#@8$x>#p>GDlQb9K*F(GI6`?OoZ2{1Kxv}B!A<`9yYl6Q znMqHERpI`F2eG0)G&R|xYk=YawYEMQ50$i7w3VF|UQXzM^u8!Pe!cZ(ZR6SrM&pQE zA))?ynH`*`v1SX$@&wP1&@2MgOVfIR9ff0ah>^@GF%lAi{@0DCx z^}&QE@r|Vsdf5wo$FR8o0UQitFWjx=@1EIlt9=Xr9s!{})=SmF1wbQslu47&wt$O3 z_17#`83iO}2U=bPKz2!c8f;wlP#%wF(Xx-=)A6j(`DUTNJXjdo;jF=c9YO-~ieiy} z=U(h;6lTFX@&|L7xE(qZ9N1I!ZbI-7#~-D|hYNXM%sC%1?)~b^wfvq*a1R*WQvyC6 zB_vV40H?(}@|o#x&C-s?DYO`rvQPh^F75#-o}l55+@u3#Yx>Ij05MSnt-53qYIDzu zpG=0L+B-ve7{WD7A^R+GY_&!3)qSXxv^+nCKXKsf6f-lkslJL`)dSptXUx`8#2N$X z>q{Q>wHJ%*!r4@->qRq1mgkl)e{p+h>SpLr0djav{V&N_FoGj!lv-?hn{g6OyWg=d zerwoUHI&7;A}A10`N@Mo2Cw)SFxbIUd$v>K{S}2j&w>u=OOs_zSG~qDG^r$DIIsn^ zk5SZ&UPMvGK)LsGKMegqts%*BM1cw6CVQ%RXaA1qT!ZR zq3Z9Zf8K_DgSZVmC~t*5Kt6!u;67TQa6;*8oNuxN(5S7gty=+5nB>+QCKk&lLahp& zKmikn5mbJ^T4(pj;8tm=G;xJ6^x)Z+TRyXUsnxvOK?%$0R>m9x(F7on8K(Qo(HZB82Mh|Zw=&i78jB~ zk)V`;DIpo&#?>O@wpe%;FB(2eI>vt(fQtwnA(DChS_u-6nMI(#wt>aqKh+tI9Y3CV zW%XZySqXLghdR*btthfd{T5h-dO1@2F4LZQ@wl(A&o(u!xCwfCkUYbeMQ-4`>wK-8 zw=YnK4J9nzC|)ttu$1NyZ97OcI)5t0Ot4h9p1p*J2Ad(Hk87ElM~5b3mRG}L z*rT=N{bqTzGU0kKO0nAqo~!PeKxW5#td(O>6Dg=b03BHfn*J0Ca~pPpm9PacNenjZ20jKs zZB#kxV)pCk%??4a<+?fzyvdu*qZx=jY}=`?ZY(UPButlL;F5&^@!Hu12h1Z<|1|{K#(H{Cq=z(MNfrB?`s;`?uzL5MV11c101Go;Hn}Z zQ=85#ZGzs2;N2G=4(XMPWxs%W#eRe%$CBmyjiM~u09IY!##)SAwu?5Tm`jSuFB1C( zIl`{D*r6p&l^Yc@m`7U+DW`Xs&EnZteXo)Pc9hgu^|75w=)s(LL^1c14rgq1uw+Cz z4BiW(4yo0}nOOhr*hgdY^-)4hY^)`{oRPsQAka#j+J6$%6)-*EO6AibEQ@PD+fps4 z{9HEHmdAn7y#;{&!W={^G6Ph!+k&I;mRKj((`_X0@{hAEj?A$EQu+)KTEwpI7UQ_S z(i|nYpY7Z#oIDf}tbEXpAjIRwxqRO#(&Z!2Hlp=u#9;0Q9Fx|F#)C{Ufu}6~tOOgb z8#N^4BvzJW3`V?dj|NL5lXOXUhB>8nhJ(wKVA~ElXP7$gN+-?`xrgHp&lC9;+76%@ z1aoZf3U8p>n9@&MN=`mLFn1=z~XU${cZz#?Y(9;hvW;@BSM;fT)g z`#Bsw+^Vaq%MXAA;3Ywjn7ZBQaE|I$Iq<<0^0efUh+s&RBZNd}7E1o4^X}dp0Z#&5 z1UA&+M+Pme{*Yqh?4Uf0GBJ~U85V13iILn9{0XWcMQ=ayZVuEXqYq!A;zA#Ajz(z( zQ7;=5J}VeuD1to~+mjE)0tDb4LCY4f9(Pd5XDisU{3-`MUkW< z-uHwJ&~gDc@Z8t7soQ7r!|V!pcBxVG+x+X3_S|BP%z{f(3;!ie$_${>l$kjNF%bIRTR z5Wl~%a{wZ(MtsB)I+6YG$0}Jb!TrOqAXpbtLS^Qx+* z*+|^_U{A!=6r2hN_8DxJ-Rg2E546UgO262qQ*PlFr9_+~a7Sm5>bfb3|D5t#77<#K zTN{7?o0d8=5_x=Pal}LJ`L}7Lwekyhu!Z0l_*7uFE3@O61e=m@s6oUL2dRytC#59} zGCN>=bwLLm$}=X!>D(dzI0;;tEmXn5fSD}dTlQ(|Ka6`CeZm86rDs6Q6#2a ziLe|O$rc9i0BJ-AFu5ogTI>yUbaaYLQkwe9i{A;xoZUqk0xZ%0ik@CuI-<~w)Zj<8?bHM{|!?TKLDVv^)M zZCx?!|7P0UhXnE_&UD~izs^DJXNj|Wv$C?DJGFD65KT%-LIbY5uj?S=rch2}$6-QB zdbag}3~*vHysK{4X#dwb9Ey)FpEiFg%VJlGQj=)PdnN@A9xT!wdw(&O5i_3Z!qV2PMV{|ah~zTR(mr?Jbt3dzt?=-sxZfy z+Q`iCH@VtDP95`zjDU8NHkOefr3e!aDYorJUipH%68NJ19b|t)s3CJ;w%?INsymG%dpYT#F-L( zn3cs3{OHi3d!Q~L2jt|WYvd#1*0Ef_F@uK*ixbw5a#;txUv8sPPY1Ht9LxwQ<9o=t z70`B~DubF8I!W2{U!PxI{<8qlXX5x#M-ft0xa13ansNVV zD2Q7_?TL2ceS5nTQX~u&sEF{H;Eo_DVXkyc)O}EFK9?rAE&VVJiO1xvcJHR#H?eDyxA)07%>^PvtuW9`P1z zFf)kmroxUw_=o%6l&4UVJZQU8b$RsMRZq@>sm8T76Fz1 zy&V0aYA-Smv1@CGZXK9swekx=$Q@I(S;8h+JK^CWVf*mu zlQE)+86$l<$L1gPiUVnoEqQKZ71MPGe7A+3t8T~xqdX9z@o?@C9H!l1D?!X&)vPva zj*KW>IV~4&8k_+IJ}Pl)nG1b##OOEV2Z*V#9l^Tb^4ef@xxzZT27}JCLYjkQKon10 zPo4v#M0Ya`>1Y@2^$3f({KFZz20Nsf7}Pki7UdpiSC~&*lBuC!;ELUE;++lq1AEOa zteOJD>g17=9VVN!0(0awn=`F>kj6lr6L=n>kO&v3E*1im@#z?;6bH@AQo!IiSf&Nh z^%6KRcK=rwY*fA5N`lx3-RtYACRe#rBi1VKi(76n^0iTDp_7)cLi1fTaTPKa{54j0 z19ZiyWJ+H%kH%I__wh*UC7G@2=Y7UAU zOa~yEo3(GTLolcNoFr&!NoU~WcbdG@vc$Bi;(*-^#>6%$nkccyX{g^1tl8(2H|!-$ef^Pe8t+IW~f&DSMc8(M9;ih=ySG$RLW>hWZV#&iiZKx0&2pVUL+gM zytRAj9(^4D9GI=xme$rQgb;~PJm0K^ilTn>A|cq8aOun%MU&l+)fk<3>s=AAQODv& z?Bh~fGA0c}`ycP`BnR&&ET;rtl?XvGsBE9gRFE@hPzfRVZMxU@9zJ|%e1|HosiHN! zZ_{8aC5cJ*7&1O!`imH@ekUo3J^#6Je{Mtdl;OT_%)%zGtI@FKh+&X zoOcKBvkc{7SID7;D0Z(RbBSZw#fBR!rT0M%pxy!mp8@2e$Z`9qiwAb?Kj2J)5rlMS z1Zd~LO(?A!9v!)P?8J$BOvw2gu>7Z4+#d(RG*Gz5uuC~UTd877YkG;u)x|}Yb2yHl z7&NO6&Dhl-G#t+2&B@~5KAvN=;4JwNzZwVxNMeds3JR-1@^dI*2zO^+Q zm9qEb=jmH4(W3{s@P?G}yCsBOWdztmT>qOZft2cm%n@l96*@sCq3~}ZS)J?1sIQ(_ zG`gIi7sC~M6PSa^gYN2g9ah1eY?T++?d|QAzT)kdnG{-`0HXK4t*yaE!ltB`P03FJ zR);t2evY7Ds&pUC_Ivd?Mcxa1xIXpl?8iq(d{12y&1lkcxV5Ir7{O|@xv}o)Qur0qRC6Hx zoO`FbxmHWi7ubRsg2Nti&~VU~k3UX+rNw)P`yu2DdnbH?buo|i09c?oI&#-x?P^e8Odb(p;`otFy!mFQ2;Qg>Pr#1qDq23| z4J_)9kYO%j-o~M0>sD>s&F*4NBwm==bkRIp}F&y5kL~dWh6!UUmU_?i}=>ZTZkd zb8dGi+u{AvkezQ+|JpDWbkCmhtm+KEI?J zho^>biZr7=z>eF5-EQRx_Q848^@Wq5AV?tC`!I*01=$dl8(kI90c&u?X$KNz{@}N* zg0X-brmr$<-T|JpM)i6POUV(;>J~g7D7ElCK-aNVP+)7WL@>kinr^gL{@fGOt)UE& zWB^^H-M%o83VV*DWI`EbkH=BO7CQVU2No&x=mlpg@1j)OhPTecaBD0tWgZzE>)6Vx z!m=Hb^Hved^4r(0Q9@@gE>7fY!crqfE&0lO{Ld+u$5&D#L5%<{Yl}c`8>RyC0qj^@ zY%Ht8c?s;SEXuLeT5cSDCRRQex&T@cEIuqMfaM557TktV8WPuU)t%# zzRT;|^k8u#OQCVt8L5>CD;EGl>}=dal-7uL5Wbm^Q8+pl16qfw$+4v3%R`F9uA>Jx z(1>uA0R`O)KZO&T6LduI4Sj&06-jKYp4EDd!;d)gZ~4@v{JYp>FsBl3gQ?yyy&Og1 zC_MdMbS*&j%l8~*pd>QPG8P;+0D}@wD0svHi34_myBbwamVTN_)E>ouWP`o<#!D*6 zDo%+_&1M(_&*U&#jYf(%d(5g3EK1s`U3Qv3urp-W@M$9$iJ#D)p}7rkA66dXG?I+< zzvug?-6iAz)hIklZbI``1*CqhSd<|%W!QGd!M3zER2+&OE!h?b>BsTZ@B zD0T>Jjibg3BA#R15;i387vL@dVz>{WVzT3+y^aS|FBZ0& z_Bv`WD4bJ~dC?RB63!9urczhS>4M1x5f=Vyt;_?bil*i(bmv;W8v1 zT&|s2ALK51J~H~s)VNOngmlZZbgpOId!k9T4FLk@j&#x8fc&~Q4mV#f}VgXD-`m813wj71Az55x3JWS zif$Ys2oQRgr+nXkpx#*Xd zmrI3fKcKf?(^= zFhFTgJ%E%W_#zNvEBh`SLO20bf*(TEc*I6SBSAmF?!URm?;o@@AN~RR$xo?3LXBvSt~C>GDek)tpw?$HB1MYfFj?ak^?@5Gg48oAjFl> z!%hb>B=~E{5W4u_6hfO$qGH@+*oWiDC^WTK!n#`DlfINV#rmMAK7hjj9HwD)QvzIl&RG0GQ@Gri zElDq70@Ff5yR%L53GweeiZj6`YME+09}VKNOo#4Qc#T~k$x<{2%M&i%rueEt9vVb_ z=RRcZE(<{8a-926f589)K#q?uYzz10Sqq*KVkD#oMuyd|TMF+wTDdN)fEEwWx`%Rw z>^ro-Uw`+O+(bu@k|+YI3b2l@-9jk;{+U23Z-JyRA~fi0V}vZ#Q}1;dVGnLlcr^tC zfS}q{X$*(+(4apN{aTH|Dnzl3BN&fWV=O(C8N6`OgC#>=CMLRd0Vg@zjDF|df8~&T zf8c?hTHwp$q-W&MNMhhV@7+B7=!*=HM1piut*Ne2viS#+V*1orWy!s+UUIn{%3zM^ zpvwSLu}L`w)dwGfNO_qeWFMNMhf8x;hPs1qYuA)(v zfk2-~3u}JSC-YJVy%k)r4ZsJ2$M)Ov`_>4!%?q!_AFJbG+c|@f2?ONcMATx(j=`TT zC1G8Fb_1L_iqmIZIDMqY1#jd?a1^M7=ynvbtfJDd&jGuP9aY}W*|4>M^tz#QK> z?yZt|=MH$j%Qfh-k)c36NpqYi`1Jguo{oF8vvd;{E5Y=|v^r)2vMW=@u_I+*YeXsh zF2Pv8WkkjYn$OyyL4cNg;BaB-lNmE6D*4#E95{N5c|e^`)-7pH1TupzWM zK-zN|0}sL8gRs!Rafe-m7;yu2zawdem}zD)5>PDU5Wzv|$Mj@Ql?A#zNe7)qFz;7Z zf!f5rnksGvVzC7d(W5W~3^A$SorC^2O#N^ONu_M}Q$ZG8V?v(s9wDU;V({a@h|S&l z%ID&VF0KzO^f!{<9C)PT)qn<2<^o>;8~_v^q&@M-qlm-$y8Z%%S;I(E?IpQg zPG@WGY8N&-mhpYq2~c1V58Mvk0%8`6?+PXezvYWnI*bk)Iv5zHAU%W2ff~5&2QD14}p#csrr@{?+=#GY{xwL zJ?FBY=6+8ni&2na^rpVe`-8j1e&FT#wX(|sSYA}LFYmkgD3VpD0Uo|awT-288D!Wu z0vo!d3OMrgh$mOmVgi6*;5*1Y^k|#WAT^A5G;*$@#$1`jMAVC$NVMYwe3Oi421$w< z@HJ2^yV2I!V*B%Xd3lY0e!83J{Txfcj@t=w5z6vgQ`Pka8R}BwkD#b- za=Ul`HpBc*!i~Ol*FFhA7iGwlzE=0d&U{j!OuXOaxh|a2O|B-h(Fi&X%YM@f#atoU zv3RUdLKcYxB)rPDJ>3%`qfIFoyqd# zRAK%!XySw8I{+jM(l7brxDVo0eyQ+s5_u<&su_)!1R@pmiINjgQUyK%Hk{bMBzyxy ztUP{w+zXhv8LlrBxeAE7k>~wWo-qiX&_C>wSw2Tt{l7V2*ezzb*)=5`D4G=9FT8E5 zbLSI*(-NGNOiiA%fYS@?OcVn3zMDe4Yrsyk%S$@CMCFNZbY2TuQ)sgyY-79>WBhC1; z+6zD<$k^QT%a;p@y@w17>o}oBghxHU``w<{9gn`PQ{*O_u#(M^@8!`^w<_*_pxf%| zQhLE3X>ODB?WLRT&zmDlbG7tD5g;$mUZqFajxdl4dO>=O&A>`^t z>|PIJW!DIUJYEHCL3OTe+|Z68j>O-+YvQ&y{W4cXNPe#EUjQ8!w*E(6=q5>C$EF2S z26@^R;8xQ~$!QvPw^es+v9i+Aj#pLlOv{>9p-WnI&sznRU>>tB|fV)t?D z(+&Nu=jtj1u#!*%5i_q7@#rtjoVLCqvzIF^(n#dJvvlY4vqu0qM?oCF>Juzk{U0V$ zg`zSLheFj~0nSE@nPPyU5theE=lkf_q!e#HXUaa6BV0Zj-ss|g#FtZzG)uv#cKDq$ zi?&X|yX%+4emt41j8`5$b$3G|`aovCZS9|+gA;!Vl*Fg90$S5$v{s!U$6Ao&R0R7^ z#J44!47bJbH`f>0{RvNsy?AG8%np!r6u(+SGz)(qA(ln&mGpGG4rHTE7!TV`c5DW+ z!;rZ`qwDDdv5Kl`EUATq;jyZZJ=dv!kXCCi{P;DeSm+<@W~TMYC9f-;v9>=j?b-Uu z!HEqrH|t6+sK`FpN619_BR;arU*yja>|aDVbcKuVCe8}L$Rk0f!@bn7s6c_3V564( zP>CU*c7Ft~j*hK=I@?tcb4cE%&uz$nH{kVbOK$bK_U+5qJaI$<_WQ0~s5MfZB#xb7#15xwh$D2Y^0mD{#g4ZGhbqz#7ty1M+D3>9LGBYA-66OTGHn zQq)v3JjmYyC+raHcjj6yj%ssGKexzL`uZnF|Et9djzRN#WFxPC5;{e+aNsWo2~8i7 zAjcqf8w(u%+ly3(ZzsAg7z7{-NL>g7a|~g{s#s#M61?Nfm(fe3Uk29&@;_1$7oiri zMQvPeWv-Fi9mj%gvEr6?W3T2F{ zR+$LjHXCWviZ{O83jcoB_J~*4E#0%)=eHzr;M5bONJ2=4=4>1!<+dx3%^cC0ml72v z52?}SK1~TlXJtk95s@hK#0YAf(eoC?Be9T;b^b33KS z*qGOPO`!kpkvWZ!>9@#|LM*z1S)Y8XbXPkX**!Sqjw&xwW-`wiY!zyR0aK*1E8B*}Vcf8$g9TfRghxu{z6?(%=La>VZldzkYy+NUuhIkY< zJT4!hm5Bu6fl*BuIMwjw1*RC`a3^TmAXKrt0raZ=%_c@*$#H6Bb{J&qFhiaK4K4Bp zXb+5q1klJ0N7%VbmoX&~(^~+3DgFCmx7rZ1r!oVYv7^$Lo4~EOeUx%-n<;f42(QwZ zWu-0H#U4bu7J4*HU0l&&YyQDIN3Ac1ZxgGy&l=FL`){Z=%OvkG9E|w)(4qTzK=HhR zi4#)`5wh(LPEKhHo)D-$R1UP|I+hskWwJHcZ+cmnN(P|&2VlpnlgF*hZaXAl3#ya> z3weWHJYst9tM3<{Rc>-wE&C}|1WOHS@ZIv@I8?<$!}DQ^PA<>YTO#16$PLTxq(rX` zUikx13+tt+T=`NJ=(~c5@^|Li)?E#U-1~_+%>y$zs9cL46E5$I2he2j?A=QS87~UfZNc5H*{IT`T|d!4*M+v3)2_Okzy( zyD<|B*c=uX4^5G!hePKnS8j0BQ$DMV&o)q4;Rnwhn;mIYakOKLs}NG`K&wQwS?GKP zJ}3P~{nLnvRvpfAei9eV+yxHbb>PAtwbzb&NGQPnlUeJVn6O}FIV{Xn^O05)Cx31K zNW)t}w*Rahd3%gMtMBKtKQFKE`EZ?`3O7KoKjRo4KUqi=1mbhj29R4e+Dq1x|9<5= zqXz(sIxGalB}~M?*Z-gaB<8G$G!sXppae%ziVwLZ!!?1}0E`YAMI6=;u9p*eCJ;df zx}u%o8X(&bs0ASB!tREZcsn^hOg-PBWx%Uqs@TVE+t?9lbGMLe9$MahHLgvU7nZ(u z`U@pNV;`QK9=WKXeN2}aI|@m|JkrMFL4)BOxPA0l}!gh~60FVU^6nM76fkE09-A+rRXJXsy zkbD#th5j5lFhZ%m{qmP_kT4%%HUvb9lZaw_nwwQO)!KIl7Rmh3O33QkInk&XliuMG z($-Kn$9>RIO~EI5hhgU1(Tjxm?SZt5CMMD+KO68cF_=eMhwX|8Syg}^8a>8(ilYf}yMU;jQr zo9#-FSg4-*eqR>R;)@Mhl-ZeWw?-x%dZQ&cem;thD_QzZqj_1QwVWBKxoZ6{v!tvWuBEW4X|6e2h3rJ(XX(mNqOu(9q~Oap_!qR)61KlT=>9@X$}wF(XxHE- zoJjGIRKHGlJ3fAw#b0Pl1~(Jef!9yTG|hzar2ON2RCihl`hKdO=a8BPRLs4p9KUKO z?xS&q2BWN+zWi`b`2K)|BRp<1tG3&3v{&CYVyl(ByyQXD4+|(xgUs*ztU!p}vp54E zWl9P`142%srkkFZlAfp3K&V1kp#QxQfcggD_}SoI=BWyO5=(}3dlMth3PotK^s@f#deqfjI_)D9d37_hX7YAgJ6bjU zfPj@NAbr~FR?UrsY$7t)76-c^HTky!FXSilB3mky3YW{0v+6W{`%PZ5RO9DVI$yRK zj>g*Nz5Yk%tj#ZFrYe*4uYZUB)IbuqMYSYKP0!Ug0fflf(C}yZh(CQ|g*>#tAl^`G zS<~_5rx?4E8GL@R&Rn(tv<)D8QCdXH`#XHezXz(T=YSoW8H9@uMAtr4D?7^lZYxbk zXKE>N2 zjwOi`fx=|hk%owwC&=lW33q-nU$ekFV%)|MeQf8MncqiuJWWb{Bo}2J>>(GdXE)pw zvRM3UQ56j_j4_1YJ0KuHKelmRSE-DQDWuTPxyhz^AqJ2U841+~8(r6Ye`Ri(acb&W z-#l1R4H-T}`MH=EGM-+jxNc^x|IW4q6-N?8j5sIu{hqfr9-4VvrIeiF+tlV#TGLe1 zfO5wtBpBz}mg>CA`?sOr%~9ToVpkJM=kJp&Pl@61=5i`^{^N8P4NH!@^gmK=-h7CD6o$iMczrRnDd=g>WUBGR z7BMQOdxih``h-nRDs!Q{gH$9M+Et|f99>T#eTWgY$;$ttrUnVf_-D0a8!y^o7H*~= zi;Y9(JE`ti+CQ-To(L>C&VP_+U;cGW#DpxZYY<7IaJ5CMeM;yr1-EjH?}(oiK)sHg_0r?;Lj8{8szzR2np=hnUFL`!GB-5jWXs8KHAGep3X(8P^9N+t=l z_~h(7X$bbK25FdhpBUhha&W_AZSCr)dyeFWfi{(zzsb@`Nm!B{Q9?qP!uQ~%F+Xi| zU^5|ByVZTXOIR_J>WAFVq@#zgx=~Y{D4clrr+Tc>BpO(DIs~DjM;;kmXCmpG4@Lb5 zZW>&?=jsJ2^%`es!XbhnM|d#0)pMPRvtU?%|8HIv3@UR0N}tYsxvkKeJoIzHNJyVG z?MLPlnd>X}&NOYfbir>3(gg^~_{T@J0%(Q@m<#QMLnQGZ_#X$SrRkmXfI_7@#-;Yrsz3| z?AqH8Q&2SOe!>k&z@R|B0qParN+NZmM8ph9;9LCZU$3*;^n80Cf5lVzIVWA_A+^8M ze?vmM%Ns`wT?cc{1dM0TDf$et1oyg$MT!P%fef#i_93Xy?Vm36M^Sbh^O4?65Et7@ z_Ca6g@Tm#Qz9>Mbb{(!At}lq3hJxqNGU~8lUJ{gy7tPx@@rU5h`|t%FF*i4NJ7+R3 z=KsvVXi?Qz5>FyZl}VQtG&rhMg!A4ayQ2jHuA0MGVFz#24n_L@?hD62XC|84MlXc4 zWzI49I+gG+kjG7Co&7u>eytX}0uDoxfgLRHb6o00hO}F+*AAH~2Zp_6IA^_EKVfno z(j2zHMes!!6bWA>^K`%yvbraoWO7C)x9z*_{QAr@a>>DC2W}Z)BG|FBd$#e2K4ct- zkT9m*sPoS+jEQqexAZ;L>B@Yy!SfAW=Glde@E@h-HJh3yk0uBDQRA0$RhJV2eGF6V zbwfi7ZSSs1r*?r6dZ*88W6na+q+G#4KR=y2=Tt}&464JF!-ca5 zDh@72FWVE(GaqeNv16yKaTBHSOLRz5<4K>YI8B^>ITE;L+li5b0DEDj0!c?GiYS=N zoV1i+#=fb;OKKA8p7Ay`p@f{^{G(?+KHKoHZ5JAq4}&(rQ+4Bm!AnmFD9}L&bXL5UQh*2w z0PqODuijaj7$h_&Fe?8HYI$ySvlz+!MJ^>N=^9BrGej<@mR9Zeqn6NtZJNGk-z+`^ zAJ8~m4q|{& z)9B{2?iHhVG3O5&B$$mM)=KHZl$!j_VUD2OLGpCoSL^!dor|<&F_z&G+(u9Yo9=)9 zXAp*i9e{h+H?80|5yb?buzI(Kq|_6i`>Rq;=cssgFBC_Sz5iMiuO_V%s&{k0cq|HS zvZ-pwXPaJRrojcpKnc0yJA4GIMXw3vy-JLM1gr$r8RkfUosE)F6>6ND&IfM`OF10n zS`2c}J*C9-Wm(Qis_0quSMc8oP`+w2(9o(DGWZ#~hg?Gn8v;2S_HoO$$d22y)5+(+ zXhC8ipV#6AUZz_j@-caS98bwqZ@=(jRTEj4K~%0eW%}rOA6$pKV3I8Q%E{cplE9i% z^5=B!bMkaar1b2EtD#ie2&nzi#Ha8}b;7aXm9zJg#a{i&xC=?CW=>E1%-r*Hf6bpc z4iMU^$pR4|-DR~p>8b9bT7__pGhJU7#_zytyWjegyd$KhjXD24UAYAN zuGyn~IahB=)rp{3a%QbGDXdTL##~5%CzcSRkj@bfr1Cwh-o$W=B>ORA?-*8+-{(?_ zXHgB@8`g1>oOG!6IDj5u8;ieB7BjMaDbh$GG6ADXb;fdYe$aZV3O&7g<{{qeBPCA` z{%ZZP)8Vgs5L3DMHZSjO`zPyB4!T>$1WE$u1)2cuI7R}(Lig#@C)#Aif}Tmdi?eV* z5vMK``FyxqU2SR06ub-w?VJRdT9A8&Hsi6X9oh5U=^bjE7k>!w#|4{7UFG|hbUdzB z{o%J(kWm<(Y9R}oO}SP)$XJ9PV%shLR0fdq4FL73HCoShYmEOhNlyH(=v(aYR@J*= zBH&-W_Veaq4RQ(J{>#M|y*&8$#m&q?aJL1fhfYi$loQtR+*4&0q{qA>&zQ|fqG2{5 z8Jmn0rn9|5>-F-&cVcK1;Xs2x0ple92<};HMh7b?hMa*}3cb$u^;4?7<^DUOcgyZS zNtdva#rt9BsgDPJf4jSxmrz%B-{_glMtdd=SjMIwayQE-jY{k~PHLF?g$CXXp13&v zL>;>{zl`!%@$vD*R8{cK*jvPD3xMtJWG%4_%F_*e$C(_9^iY{;$4UJ1ZdqNi2&i6D zI>p)jeuv!gMe{fZUFvQAH8UeBWNLzLv!5_XFBk#ZYkbd)9ITb_LuN-b%+HIxrVEYgFm0e zQxlXx0H_^72-AcbWca*shz9P|PX;65BLb^(`*Pbr(D-p)4grbuAz1u&>w4Vk4>pGP z!w`?+e)!J^DY##d1{mn+oU*XenHf4Z9k&`1lAEZmWM6l|- zh6gd~MP`hYHTvb%A@t-BQ5@VI-IBbfteh5f!*R!hBGqA$?yDq2Gu{)@bBn6;rfZFE zZg3B)k?>A|Y>;M8xGcUi$dfZ>BQs#nSl4I$PdW^c`t!3h&42{U;a<(Pu2w-4=+L=Z zH9OzThJ!_VAj^!viJ|pwKk?&G)52;+Ml16g>m@^T#pF0a0}E+Mhd(_c>t148Wp@Lp zP-kF+JWvfI#Um>*p@f*x0Cp2)?ij(mzt}3uVSg&gV5MafXex2a2uAePT?^?f*!NJd z2i}dUh0g@ewcYgpdO8zmtk$=Uzl6-0NfQ}TID`tBGiM$W5s74$>>b8XZ$Ok-0>sDC)fUuHE_0-&(C&tF`vp_TJC)+{1PK?l)&{H>?PHde=vb|Lwo; zq5Ix_M|wc(*9OczS-l#pC|imLUDsZmbN37c`V-yh%!l%zSW=R#oX5 z8}He>_seJ4mpw9Sr^~nBG+M~f0MkEhXqGIKDoF*+V)qv(Y!#4Zr9t~BKp$!LM!1~r z91Jby=y=wBBf!}cIA)9V)Aj{~s(fttZp80pZOmf6do%Tuvr#FAL%`W4HZ{GdKfLOo z7SvSeDRcG0sxRLZ@(b$-x8CPKT=dl@s9wk0t-@X_yYylI z4xfB=`P0T~7KvuPnVMVymBGfocIC&nIXoSDcRER#F4>eXZ|9*@2LoV3;E80ThiG?f zUo~Tb=L_V~syC2rqL|$hMU%d+kg@2UFw$9kYf|;zE!{$=Mfxg7#cs)i{c`y4?! zSB%(^fL$#Hj2)~*760?QY+1s!VeAf(UdLx45rwR^;FWJZmVFEB&9ptzqUS~o_IO!q zbL1v=Hl4C!HU6`n|54i!oP0lNCv5^3TY0HuKq49xp;24XU+dddq2i1hvDY$HB-ac8A_eozzDa5B36Zme9D5dg&AdeEzWA ziR%3ZuKDzmzX9G(&{Cs_`xi!xp!f^q!a2j2H=(^)g$LkC8(xY3I8HvMDs_TE`Afm9 zyX)~iZ?eDZCYnv_vVXHCJ271A)ZS4Y^6MjC1Nk4jG9D)WY*v?JJUFoPv2X7pL&g38 z1x3zSSqgavYg*T5KQpT6Z@0K~61NAw-?ou2R-|>_*B3wv$dIl>M`uzWbOG7dSXDgf@q->C@MN<83**NAET79MF;Hllaks#Y9&S{rA+seNXWY zTk?+JM{7FzzKo-~nNa^C_%}wxAp`NAbcuy=S^Lkdx1Wx)XA^h>i12XKU<}D-2pxkQkxxzoL{9b6D@D3H>WXj-zf@Ut@ph5YwV&G4L@`E&D)^o=XeN7a5e9{3J=@DVn$EznmP&IdSY zh<(t8@DiR*wB4car$wZTjy9}tQ$2S$5h>CD7(-PwUw_Rx3$Nto-#+pbcho4`K{SC} z&Bcgy^lWOIN=VZB#hpzI6EwD$XfKMr|K}C8#Q|YQIu-s!nH=R64(*FnH-?O$V9Buc zWq@Q*i!JJk%bUzf(VaP!COwF1%+qW)Ev6hRZHb;eH;4uJI`ED zbO~62*NulyM_|vPjGk%Bt(4JN+dy+EQWZTr^DlNM`P*JTqneH~{kmF5=vOC~i~~11 z1q13;5IF**1TO&^RIif-kH`=XYW1QgGM2VK0Zzb+kdc+Oh6$cHGZR;wka@O#l;&CTDoP+@t9fa;LnkmBQHmPA(d9u*VM8GI8y=hwf zZHE;`u;6U(1N~?X(3K0&M4`cH`{YT~Zuau}ySp~&G#X~7?wg>VE7`o4LtWnJs`u8d zrp3<%ZFFgB<_2Cq7ED@PNm4x}!&li~hP?zW3^d>b6chqi@$gr*UHQq*tvRWwngIQv zXN%$LQ8;6#t3T>+lW12uzVL5w*53wr1gQV=BA0xHuTe>jT@0`HyP3me{H5bAoBCGNS z*j4YIp}dM;QFR!oYh;U<=&^v{9{orH7>&|*2yHyfSD#|Kq+jfjq2QT0LzMQrBQ>LN zYBdR9_I@SclSb4B+VZJ~}C^d@6X`!;YtDPE!qQ0W(q{KNwvqfj z23qXx!(J}OC;2GY3HQ%VG1Gk;@`9=W$5JjbQ|}G*?~Q+jG?-;2aX+7jtOoOeo;PCT z5NzDY;fueAm6GHiZYCv4(d;WEP4!ofE4Wj*Q}skqp;i;{(GWE0dU@jI=UKqBCDncb znEhcAmFzahN|5L2X(Jyi(_TA%?N~j&=t?J02aLz>QyhWf9^YvLY~ zH(K96oFVBN=&BX>VQ>SMCHy7nXyuVLR9=B z)f`Vbzr4X=-A~wF}9FgqszfDbhKE8f)xU+1HN#hnNUkAAKxt`iz>-* z@rpXL>}n=cQD*0jn(F9qu{bC>h`IrmS1+w@j+|?kB(6y=qe3r1kc086mD7RK9Gc8$ zINL{70vUJ$I}i!UMMocjNsNH9;DZ|>MBSv1#>U3Oe$QDBVVw9@CQ|Fd9#PShtgM$9 z;f1X-%Wc)T>19;-?t>l6HfD!nv|xszOXG8S)|wW(3O_PT`vf{8RDv&0(Q6W2Yu2@C_nz z_ZdLv^;Rz`mCKMAE`d)CtQ-Oq`R$4QG+Yd^4BW@0drsWTyr9_99Or@a8mTWB=vbdF zP;(*`Y*<6{OXUd$Y!C(*j?kT2EF1A#WeJN9(TzXzz?1;rWWVS2cCvl}qS3({C_B4g zlOgKC2%0+aU?U|GI2d6+A_B-Es17f}`@1v`Z%Y?^V?(~Eq5#ez@LsnqCE3Sd;!in# zEevs3js6WqhF@`{vTbL9_izI^O1zty`4-~FOy$iJ{}rr3B?&3$I3JadVqZ#?l|3{f zKzQMq8Gl`pluQcu-SQC%TVrF0uZJL_=t9)-sRknzUGO$>OfsL5{nUJNTc>W+r7l?w zA#_MPxzcMf9{BOnH;6b8J1$@^=tlFzK0F>h@eP}*>SiG;3S{fbd81bIeQ`&gFp<(u zE7;JZx|&|ghL#&@L4iP*sGNmzQ590Zr(aO3t8)dAj$rs<>t##OCcJQKVTih_B*wSU zo%X~O2^PqhV9`+-^y@Yx>M7$9@u*@@PMEFajZLeDtloE9J*sOe2yBhM9lcluRNlj@ z4$|^)yf>1kuu$~vtIwK_qe=1rW+ilsLgP2v2A?p4AR)%atiorQqo;%VE`c&&(^|YkAT=y-&UGAjxD(SDBJUnd zRYJ+h*8qd3wN(hG9=d8dvwn&$m?&7_iLhoqaQaR$9e^JZ2o~?d3jwztxH#A-A&f;@ zA?G!i3Eo~;iK!6gLdcI4K%^Ej1d@bl{*q*ETZKV?>8R8WqX~9G)`9B9-byx;It}8T z$D)0@i!dek!JAdH*+&aBg6A+l`G4(?7@lu)kWNld*Tz7nf5Dd_EN@6>nBicn=owPI zc8;rUp-t$3+(!+By@bW|FB}v0!4NGC&!OTHije7XD7sq&Gpikk!fQ0c92Xg|O+qL{ z%WJEKgwu8KG2#CFXWJ@u<}v`g(H(wQ#x!Hd_zc^t%J|oP$NcV6l~U(&>s?6chtoiLkS z1V2GxJng#M>T90KrJCas#JoT_h^lak3n@{Hu<*od7x=-#{0QBmVYqG(z`MUqO-*G& z=RVUwT}Ce|^6RtNXNl1M*?k1j1_63LXX9mG4}xiT%~wmKdt$<0mO zF=B9Tpu~2 z756`!o{`b!4C;gS-LMO-_LU0RyVY_T;WV#{4sS;OKKB$Am9X>0I*;1X1CIe2 zxCXH+IAB4+Wk$&3F!|EPErtx}i;m{^yD_Q_T-;DH#Ucc2VYYOokI1ip0I6*q@_@M< zNW%7BE2Fg2&Z@u8Evx2CS(I<8>z5BTmuL*?YHn83f0Z%iz zgs0F6P$l8o$B9f-$j~6mg>VfKy{4%|FLxszdhCO$ZMpi3@}~D4T$!k7;nhD>^DS7f zjqK-R-&qpYyO1HN#<~zG;t*&U} zS7~%C@S?hTgwRIMSMKy&#lGOu+iM>opIsL5jgn|o)8>tl2l1n-*Q@AxB;HoW<(HJN zzTP@{-BiWBC?T-oWPRd4AqAZQgN-ZGy+=ujKl3f9e0WR-m(?GAuoIOuOoCDesWM8S zZWd&bTzTtFsNq_O4|9VP0{aL0wdQmymJ{w@{FJGkl@FS7!J?e-aK9A``6uM)FkiqR zxfz21z`Sshp;`bTYCkHu{f%m6Pd!ZOnE3=2HpY^5LR=BfU6HqZuZFw^^g(?IyAp{P z1>IjjNKr)Ii5h~H-J;cQBVif>h(zQ#2uUTBkUB7q38fb=lpKLzY6s@VOZ3I zqypYpOfr^HkSe-yMnglRtRjZL*S-M`L`)=OTM9CSat8FvIq)ztJFV6&!{*WHWuM3W zkwC-MG*x=RWFknb);Vp0j;ivhhhmeJ`Mr6m8@tw4ICp&ZD3iF5@6ePcZk1oXQf+hZ zEE7{y%O;p{FlibN1}NJhaBo2s)bhDKgF=w|2viGB*ufCxL_w`I}C1&ht*c>LXJ)9L*cXzV_%rbU`#>MJ0xBBGV%hA?So0rTO%Wq#ntri}Bw1E!!ftu;yqc zm}^T%D<-(!@}qN%*TaihCj@l)5?W{TE8xg?v`dSNhnjNXUg@KYBH%hD#&Qodgu>cx z)vvss;+tOYA{SX(1N` zt`Zaku-qmJJ79ru`}(IkTL8jO4D%_76tJk149^$;(G^f}ZEa(!kym-{WgTAejF|;!6@{2Yp#v_;arwSuDpZ}X_n z*B}V_vW+&g$tr>sDvDY-(QXw%2Pv$D@7*!3B+LldhcX{b7BAKt0W_vjSG(K0Q}AON zIW5?njRv=)uP%bii^UOu<$0LAptvJU=bW|>W}}t8EmprZZ>rvJ_f8By6j~>jw%Q@) zkY9^oD+(RtWJn(K60s2FIwJX+cI|CiZ*6236LiD~6JX25Jh4m?r0eMYW1|P<7oh+s zL1AdJz%q?97Nru!>~e!2v6ioRwZzbL&&)ds;W!AJ=-6Y55cs#SlY$xhCe0K1Xiz;q zO}r(@L@^Wv_0oYy-n$SHROU8vY8}j(71Mxcj@NJFydpj^f^P}*WKccKoiyr~JeyEu z?yukX8w-jh0&d-JA2isM5;oPBC=^Pjx@mlvmj053LP~RJNYL_B6DoavgP*xPQ$_SR z#@mv3ovhOnAN`7DL?8Nwc_8Dfqq!Essa_T&M7t&miJDp>VI?cI`dT{duE;c03E&lb z61Qi=v8hwnXw}&&9*;5pBy|0s>Mf{pK_$=6H`61)WdJyh6D*BSm9Riz9$7IpxVdE% z>yPlao*SNd6YNn6?m2!^x5mwE1b|bIm4v!#=Nu(m+WJ2h&7LM@aD?T5-aa45 zEg9^DF)KtKTWN739L2$%OG-eg6F(M=apI&n_nYGRi+}9JWH4?7Sgjl+H~w6V-Hp>P z%747(XyiQD(X(o$Il9n)I*Blu<2Sgn#{a)7B)a&$e$HkkH@?v>l+^O-UXIZLo&W#O z5(}BR+L=F~D$g^SigFDr)<~c-A@0&tVY6vB>E9u?@6`W$0luvHj3i>REO**@w*M}1 w^CD%7D}8=fX@Q~?i9}*`KCXAh(2%qb|E9+u;-|~}8u6E&mXT(;hC}540c~G%R{#J2 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index a2b8a361..f2bb829f 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 00000000..6201b1f4 --- /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 00000000..69217124 --- /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 00000000..d5cc3eb8 --- /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 00000000..e56f3cde --- /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 348dad90..461b3099 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 @@ def read(name): 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 @@ def read(name): "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 bcb83335..9a26f358 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 From e8dc4dcabc2abf871cb695896deb12963da2cac5 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Mon, 20 Feb 2023 23:35:11 -0600 Subject: [PATCH 02/12] Squashed commit of the following: commit e2eaffc32b382f67f97d2e25858b5b5a4abffeae Author: shadeyg56 Date: Mon Feb 20 23:08:57 2023 -0600 move text to be inline with menu commit e1dae9d9de25f13c51dd983f5b4b0fbdf1bf0349 Author: shadeyg56 Date: Sun Feb 19 17:50:26 2023 -0600 move distro info to top of system stats box commit 959405bedc52dce14f5fd70d2fcd5fbee57e59a5 Author: shadeyg56 Date: Sun Feb 19 17:49:15 2023 -0600 fix app name on GNOME commit 346093dfce6ce0bfa1954ed3bb6f5364250d05b7 Author: shadeyg56 Date: Sun Feb 19 17:01:03 2023 -0600 fix snap dependencies commit ec7e087b343f33af7a92b190ed666845dab60f20 Merge: 32b6e63 9912f9d Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Mon Feb 13 14:11:21 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit 32b6e632830fe8a7c3a3ce601ef2ba5b254572d7 Author: shadeyg56 Date: Mon Feb 13 14:10:34 2023 -0600 increase MenuButton size commit 12a2cda82a5f56bfbb532c893d822341e9b2d7cb Author: shadeyg56 Date: Sun Feb 12 23:35:33 2023 -0600 set app icon commit d170d07e866644bcb662bf342530c497390f0303 Author: shadeyg56 Date: Sun Feb 12 23:35:15 2023 -0600 remove unused pixbuf commit 993333fb5aad448103e24e38bc85fb04e7e4dd0c Author: shadeyg56 Date: Sun Feb 12 22:43:56 2023 -0600 fix MenuButton icon commit d1b8bd74caab2cf216bf40bdfc35ef3520e891c7 Author: shadeyg56 Date: Sun Feb 12 17:19:32 2023 -0600 add icon commit 13f43fa0c7d59b2405bac688119690d01a7ec019 Author: shadeyg56 Date: Sun Feb 12 17:19:10 2023 -0600 add pkexec policy and change wrapper for gui commit 4ddbb9c6667aa97bb7cd242fb4a376e061c9b308 Author: shadeyg56 Date: Sun Feb 12 14:09:35 2023 -0600 add icon to destop entry and about dialog commit 4379024adab282fcdb58a7091df2a6adf833e700 Author: shadeyg56 Date: Tue Feb 7 19:06:59 2023 -0600 add removal of gui and desktop entry commit 42693703324762418a7f810f04a3d77803afa236 Author: shadeyg56 Date: Mon Feb 6 23:28:44 2023 -0600 CPU stats refresh and style stuff commit 7149db72c0bb39af57ceccb16df8e9164c77fae3 Author: shadeyg56 Date: Mon Feb 6 23:10:29 2023 -0600 daemon detection, daemon install/removal, and layout stuff commit f7e03c9bcc2e2578703807ceeb7a652903571b71 Author: shadeyg56 Date: Sun Feb 5 19:26:42 2023 -0600 improve AboutDialog Please enter the commit message for your changes. Lines starting commit cb8cfe7b3194273dea229fe550fdf66cd6af82fa Author: shadeyg56 Date: Sun Feb 5 18:28:19 2023 -0600 add dropdown menu and (not completed) about dialog commit ade1fed790f2ebbaae4e92ce8879ab812c9b2850 Author: shadeyg56 Date: Sun Feb 5 15:57:17 2023 -0600 actually fix css commit 67c8c97bcf2f2f408431bc162c38dee5c278ed0b Author: shadeyg56 Date: Sun Feb 5 15:54:47 2023 -0600 fix css commit bd3feae38824764155d31ccd6debc1fcf18830da Author: shadeyg56 Date: Sun Feb 5 00:42:52 2023 -0600 add desktop entry for gui commit 5426a6a443c90a9d65b858ae1951219898cba332 Author: shadeyg56 Date: Sun Feb 5 00:33:08 2023 -0600 add gui to install file, update required packages, and create wrapper script commit 84124dfa605b930b3722ed852486cbe11c254092 Author: shadeyg56 Date: Sat Feb 4 23:28:24 2023 -0600 create system tray commit 7b0d46d8ddafdeff9006a1ad10ccd99c24b9eb1e Author: shadeyg56 Date: Sat Feb 4 23:28:01 2023 -0600 small changes to layout commit cd51ea317085ac6ea5b4918ef057ad4c19a71640 Author: shadeyg56 Date: Sat Feb 4 00:44:08 2023 -0600 css styling support commit 136b449febece04c036b72b4a14ccceb422f62dd Author: shadeyg56 Date: Sat Feb 4 00:14:52 2023 -0600 several improvements commit f9f7170391e1abc1499b7d5ae93327999e013730 Merge: 242a8d0 8f343df Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Fri Feb 3 22:44:01 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit 242a8d0401d1ffd00c982aa731c04ef7ea726fee Merge: f50b982 dadfae0 Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Fri Feb 3 14:19:01 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit f50b9829e5438bb96ded73775668fa048fcd3951 Merge: a98225e 69ef913 Author: shadeyg56 <31134255+shadeyg56@users.noreply.github.com> Date: Thu Feb 2 18:19:10 2023 -0600 Merge branch 'AdnanHodzic:master' into gui commit a98225e7280802c2fb09a9a52a40f7e55b26b3b1 Author: shadeyg56 Date: Thu Feb 2 18:18:43 2023 -0600 Revert "basic GUI" This reverts commit d2610c921b7209fde7d7be13f187510631cc33b3. commit 9606472fdbbff6dd551cd291b37349df17098d2e Author: shadeyg56 Date: Thu Feb 2 18:13:41 2023 -0600 basic gui commit d2610c921b7209fde7d7be13f187510631cc33b3 Author: shadeyg56 Date: Thu Feb 2 17:47:55 2023 -0600 basic GUI commit bdbe12018b380ed70f100729c9ea3732ba24f572 Author: shadeyg56 Date: Thu Feb 2 15:21:42 2023 -0600 rename folder commit 31095c472e4af734a3a601f804787de89ad47964 Author: shadeyg56 Date: Wed Jan 25 23:39:52 2023 -0600 add tray --- auto_cpufreq/gui/app.py | 16 +++++++++------- auto_cpufreq/gui/objects.py | 3 ++- scripts/auto-cpufreq-gtk.desktop | 1 + snap/snapcraft.yaml | 2 ++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py index b6718913..4eec8cdc 100644 --- a/auto_cpufreq/gui/app.py +++ b/auto_cpufreq/gui/app.py @@ -27,21 +27,22 @@ def __init__(self): 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.vbox_top = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + # self.vbox_top.set_valign(Gtk.Align.CENTER) + # self.vbox_top.set_halign(Gtk.Align.CENTER) + #self.add(self.vbox_top) + # Main HBOX self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=HBOX_PADDING) self.systemstats = SystemStatsLabel() self.hbox.pack_start(self.systemstats, False, False, 0) + self.add(self.hbox) 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.hbox.pack_end(self.menu, False, False, 0) self.currentgovernor = CurrentGovernorBox() self.vbox_right.pack_start(self.currentgovernor, False, False, 0) @@ -50,7 +51,7 @@ def main(self): self.cpufreqstats = CPUFreqStatsLabel() self.vbox_right.pack_start(self.cpufreqstats, False, False, 0) - self.hbox.pack_start(self.vbox_right, True, True, 0) + self.hbox.pack_start(self.vbox_right, False, False, 0) self.vbox_top.pack_start(self.hbox, False, False, 0) @@ -84,4 +85,5 @@ def refresh(self): win = MyWindow() win.connect("destroy", Gtk.main_quit) win.show_all() +GLib.set_application_name("auto-cpufreq") Gtk.main() \ No newline at end of file diff --git a/auto_cpufreq/gui/objects.py b/auto_cpufreq/gui/objects.py index 9c9b8fd0..cb398a22 100644 --- a/auto_cpufreq/gui/objects.py +++ b/auto_cpufreq/gui/objects.py @@ -112,8 +112,8 @@ def refresh(self): old_stdout = sys.stdout text = StringIO() sys.stdout = text - sysinfo() distro_info() + sysinfo() self.set_label(text.getvalue()) sys.stdout = old_stdout @@ -139,6 +139,7 @@ class DropDownMenu(Gtk.MenuButton): def __init__(self, parent): super().__init__() self.set_halign(Gtk.Align.END) + self.set_valign(Gtk.Align.START) 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) diff --git a/scripts/auto-cpufreq-gtk.desktop b/scripts/auto-cpufreq-gtk.desktop index 6201b1f4..bbe928f7 100644 --- a/scripts/auto-cpufreq-gtk.desktop +++ b/scripts/auto-cpufreq-gtk.desktop @@ -4,4 +4,5 @@ Exec=auto-cpufreq-gtk Type=Application Terminal=false Icon=auto-cpufreq +StartupWMClass=App.py Categories=System; \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9a26f358..6075c44c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -23,6 +23,8 @@ parts: build-packages: - gcc - python3-dev + - libgirepository1.0-dev + - libcairo2-dev stage-packages: - coreutils - dmidecode From 3a4fd1dc3f1fbd2c7e691020d8dbea08c761d9ca Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sun, 26 Feb 2023 14:45:05 -0600 Subject: [PATCH 03/12] fix undefined var --- auto_cpufreq/gui/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py index 4eec8cdc..97d55b42 100644 --- a/auto_cpufreq/gui/app.py +++ b/auto_cpufreq/gui/app.py @@ -53,7 +53,6 @@ def main(self): self.hbox.pack_start(self.vbox_right, False, False, 0) - self.vbox_top.pack_start(self.hbox, False, False, 0) GLib.timeout_add_seconds(5, self.refresh) From 3a8eaaf03e620aa00b1e1a0e3c43d8ea6031a1d2 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sun, 26 Feb 2023 15:34:25 -0600 Subject: [PATCH 04/12] app no longer needs root to start, only asks when needed --- auto_cpufreq/gui/app.py | 5 ++--- auto_cpufreq/gui/objects.py | 19 ++++++++++++++----- scripts/org.auto-cpufreq.pkexec.policy | 6 +++--- scripts/start_app | 20 +++++++++++--------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py index 97d55b42..6f0c81ce 100644 --- a/auto_cpufreq/gui/app.py +++ b/auto_cpufreq/gui/app.py @@ -15,7 +15,7 @@ HBOX_PADDING = 20 -class MyWindow(Gtk.Window): +class ToolWindow(Gtk.Window): def __init__(self): super().__init__(title="auto-cpufreq") self.set_default_size(600, 480) @@ -80,8 +80,7 @@ def refresh(self): return True - -win = MyWindow() +win = ToolWindow() win.connect("destroy", Gtk.main_quit) win.show_all() GLib.set_application_name("auto-cpufreq") diff --git a/auto_cpufreq/gui/objects.py b/auto_cpufreq/gui/objects.py index cb398a22..0f9b8041 100644 --- a/auto_cpufreq/gui/objects.py +++ b/auto_cpufreq/gui/objects.py @@ -9,11 +9,13 @@ import platform as pl sys.path.append("../../") -from subprocess import getoutput, call +from subprocess import getoutput, run, PIPE 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 +PKEXEC_ERROR = "Error executing command as another user: Not authorized\n\nThis incident has been reported.\n" + if os.getenv("PKG_MARKER") == "SNAP": auto_cpufreq_stats_path = "/var/snap/auto-cpufreq/current/auto-cpufreq.stats" else: @@ -32,7 +34,7 @@ def get_version(): 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) + aur_pkg_check = run("pacman -Qs auto-cpufreq > /dev/null", shell=True) if aur_pkg_check == 1: return get_formatted_version() else: @@ -77,7 +79,10 @@ def __init__(self): def on_button_toggled(self, button, override): if button.get_active(): - set_override(override) + result = run(f"pkexec auto-cpufreq --force={override}", shell=True, stdout=PIPE, stderr=PIPE) + if result.stderr.decode() == PKEXEC_ERROR: + self.set_selected() + def set_selected(self): override = get_override() @@ -170,7 +175,9 @@ def _remove_daemon(self, MenuItem, parent): confirm.destroy() if response == Gtk.ResponseType.YES: try: - remove_daemon() + result = run("pkexec auto-cpufreq --remove", shell=True, stdout=PIPE, stderr=PIPE) + if result.stderr.decode() == PKEXEC_ERROR: + raise Exception("Authorization was cancelled") dialog = Gtk.MessageDialog( transient_for=parent, message_type=Gtk.MessageType.INFO, @@ -250,7 +257,9 @@ def __init__(self, parent): def install_daemon(self, button, parent): try: - deploy_daemon() + result = run("pkexec auto-cpufreq --install", shell=True, stdout=PIPE, stderr=PIPE) + if result.stderr.decode() == PKEXEC_ERROR: + raise Exception("Authorization was cancelled") dialog = Gtk.MessageDialog( transient_for=parent, message_type=Gtk.MessageType.INFO, diff --git a/scripts/org.auto-cpufreq.pkexec.policy b/scripts/org.auto-cpufreq.pkexec.policy index 69217124..d59e34b1 100644 --- a/scripts/org.auto-cpufreq.pkexec.policy +++ b/scripts/org.auto-cpufreq.pkexec.policy @@ -12,8 +12,8 @@ auth_admin auth_admin - /opt/auto-cpufreq/venv/bin/python - /opt/auto-cpufreq/venv/bin/app.py - true + /opt/auto-cpufreq/venv/bin/auto-cpufreq + + \ No newline at end of file diff --git a/scripts/start_app b/scripts/start_app index d5cc3eb8..a532e93a 100644 --- a/scripts/start_app +++ b/scripts/start_app @@ -5,12 +5,14 @@ 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 +# 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 + +${python_command} \ No newline at end of file From 2802ebad4a4800689e5ba40d18195395c4e5537a Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:09:13 -0500 Subject: [PATCH 05/12] fix wmclass --- scripts/auto-cpufreq-gtk.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/auto-cpufreq-gtk.desktop b/scripts/auto-cpufreq-gtk.desktop index bbe928f7..fdc49a73 100644 --- a/scripts/auto-cpufreq-gtk.desktop +++ b/scripts/auto-cpufreq-gtk.desktop @@ -4,5 +4,5 @@ Exec=auto-cpufreq-gtk Type=Application Terminal=false Icon=auto-cpufreq -StartupWMClass=App.py +StartupWMClass=app.py Categories=System; \ No newline at end of file From 864d0c3b253bb41c473a979430208a4f46f961b8 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:09:53 -0500 Subject: [PATCH 06/12] create file in /bin for gui --- auto_cpufreq/gui/app.py | 8 +------- bin/auto-cpufreq-gtk | 18 ++++++++++++++++++ scripts/start_app | 2 +- 3 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 bin/auto-cpufreq-gtk diff --git a/auto_cpufreq/gui/app.py b/auto_cpufreq/gui/app.py index 6f0c81ce..635cd9b7 100644 --- a/auto_cpufreq/gui/app.py +++ b/auto_cpufreq/gui/app.py @@ -39,7 +39,7 @@ def main(self): self.hbox.pack_start(self.systemstats, False, False, 0) self.add(self.hbox) - self.vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20) + self.vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=52) self.menu = DropDownMenu(self) self.hbox.pack_end(self.menu, False, False, 0) @@ -79,9 +79,3 @@ def refresh(self): self.cpufreqstats.refresh() return True - -win = ToolWindow() -win.connect("destroy", Gtk.main_quit) -win.show_all() -GLib.set_application_name("auto-cpufreq") -Gtk.main() \ No newline at end of file diff --git a/bin/auto-cpufreq-gtk b/bin/auto-cpufreq-gtk new file mode 100644 index 00000000..17f757d9 --- /dev/null +++ b/bin/auto-cpufreq-gtk @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import sys + +sys.path.append("../") + +import gi +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk, GLib +from auto_cpufreq.gui.app import ToolWindow + +if __name__ == "__main__": + win = ToolWindow() + win.connect("destroy", Gtk.main_quit) + win.show_all() + GLib.set_prgname("auto-cpufreq") + Gtk.main() \ No newline at end of file diff --git a/scripts/start_app b/scripts/start_app index a532e93a..02c1fbc4 100644 --- a/scripts/start_app +++ b/scripts/start_app @@ -3,7 +3,7 @@ # 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" +python_command="${venv_dir}/bin/python ${venv_dir}/bin/auto-cpufreq-gtk" # if [ "$XDG_SESSION_TYPE" = "wayland" ] ; then # # necessary for running on wayland From 60bacfe15b198c3c27438bde8906847ff57aa53e Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:11:18 -0500 Subject: [PATCH 07/12] fix bug with radio buttons and pkexec --- auto_cpufreq/gui/objects.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/auto_cpufreq/gui/objects.py b/auto_cpufreq/gui/objects.py index 0f9b8041..60eaa9f8 100644 --- a/auto_cpufreq/gui/objects.py +++ b/auto_cpufreq/gui/objects.py @@ -51,6 +51,8 @@ def get_version(): class RadioButtonView(Gtk.Box): def __init__(self): super().__init__(orientation=Gtk.Orientation.HORIZONTAL) + # this keeps track of whether or not the button was toggled by the app or the user to prompt for authorization + self.set_by_app = True self.set_hexpand(True) self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) @@ -79,9 +81,13 @@ def __init__(self): def on_button_toggled(self, button, override): if button.get_active(): - result = run(f"pkexec auto-cpufreq --force={override}", shell=True, stdout=PIPE, stderr=PIPE) - if result.stderr.decode() == PKEXEC_ERROR: - self.set_selected() + if not self.set_by_app: + result = run(f"pkexec auto-cpufreq --force={override}", shell=True, stdout=PIPE, stderr=PIPE) + if result.stderr.decode() == PKEXEC_ERROR: + self.set_selected() + else: + self.set_by_app = False + def set_selected(self): From 5dc7ebad47cd5fe33461045cd23a816675c223e8 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:11:47 -0500 Subject: [PATCH 08/12] snap: update snap to package GTK gui --- snap/snapcraft.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 6075c44c..fba47e00 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -59,6 +59,18 @@ apps: - system-observe - hardware-observe - etc-auto-cpufreq-conf + auto-cpufreq-gtk: + command: bin/auto-cpufreq-gtk + environment: + PYTHONPATH: $SNAP/usr/lib/python3/site-packages:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH + LC_ALL: C.UTF-8 + LANG: C.UTF-8 + PKG_MARKER: SNAP + plugs: + - cpu-control + - system-observe + - hardware-observe + - etc-auto-cpufreq-conf service: command: usr/bin/snapdaemon plugs: From c444f31e447717bd6e4828c846ae20251d51c9c0 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:13:01 -0500 Subject: [PATCH 09/12] update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 461b3099..ea54a9e7 100644 --- a/setup.py +++ b/setup.py @@ -40,5 +40,5 @@ def read(name): "Intended Audience :: Developers", "Operating System :: POSIX :: Linux" "Environment :: Console" "Natural Language :: English", ], - scripts=["bin/auto-cpufreq", "auto_cpufreq/gui/app.py"], + scripts=["bin/auto-cpufreq", "bin/auto-cpufreq-gtk"], ) From 47c4b065067df971ae3d3b53067999da74783496 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:17:04 -0500 Subject: [PATCH 10/12] requirements.txt: fix conflict --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f2bb829f..f492cac7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ setuptools psutil click distro +requests PyGObject \ No newline at end of file From 2b30b6078ddaeef95dfc8c969508ed854b9a2c7d Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Sat, 12 Aug 2023 17:58:47 -0500 Subject: [PATCH 11/12] Merge master into dev --- README.md | 127 +++++++++++++++++++++++-------------- auto-cpufreq-installer | 60 ++++++++++++++++-- auto_cpufreq/core.py | 77 +++++++++++++++++++--- bin/auto-cpufreq | 37 ++++++++++- requirements.txt | 0 scripts/auto-cpufreq-runit | 1 + scripts/cpufreqctl.sh | 16 +++-- snap/snapcraft.yaml | 1 + 8 files changed, 250 insertions(+), 69 deletions(-) mode change 100644 => 100755 auto_cpufreq/core.py mode change 100644 => 100755 requirements.txt diff --git a/README.md b/README.md index dbd59099..62d400eb 100644 --- a/README.md +++ b/README.md @@ -12,34 +12,37 @@ auto-cpufreq is looking for [co-maintainers & open source developers to help sha ## Index -* [Why do I need auto-cpufreq?](https://github.com/AdnanHodzic/auto-cpufreq/#why-do-i-need-auto-cpufreq) - * [Supported architectures and devices](https://github.com/AdnanHodzic/auto-cpufreq/#supported-architectures-and-devices) -* [Features](https://github.com/AdnanHodzic/auto-cpufreq/#features) -* [Installing auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq/#installing-auto-cpufreq) - * [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](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) - * [Example config file contents](https://github.com/AdnanHodzic/auto-cpufreq/#example-config-file-contents) -* [How to run auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq/#how-to-run-auto-cpufreq) -* [auto-cpufreq modes and options](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-modes-and-options) - * [monitor](https://github.com/AdnanHodzic/auto-cpufreq/#monitor) - * [live](https://github.com/AdnanHodzic/auto-cpufreq/#live) - * [overriding governor](https://github.com/AdnanHodzic/auto-cpufreq/#overriding-governor) - * [Install - auto-cpufreq daemon](https://github.com/AdnanHodzic/auto-cpufreq/#install---auto-cpufreq-daemon) - * [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) - * [Paypal](https://github.com/AdnanHodzic/auto-cpufreq/#paypal) - * [BitCoin](https://github.com/AdnanHodzic/auto-cpufreq/#bitcoin) - * [Code contribution](https://github.com/AdnanHodzic/auto-cpufreq/#code-contribution) +* [Why do I need auto-cpufreq?](#why-do-i-need-auto-cpufreq) + * [Supported architectures and devices](#supported-architectures-and-devices) +* [Features](#features) +* [Installing auto-cpufreq](#installing-auto-cpufreq) + * [Snap store](#snap-store) + * [auto-cpufreq-installer](#auto-cpufreq-installer) + * [AUR package (Arch/Manjaro Linux)](#aur-package-archmanjaro-linux) + * [Update using installer](#update-using-auto-cpufreq-installer) +* [Post Installation](#post-installation) +* [Configuring auto-cpufreq](#configuring-auto-cpufreq) + * [1: power_helper.py script (Snap package install only)](#1-power_helperpy-script-snap-package-install-only) + * [2: `--force` governor override](#2---force-governor-override) + * [3: auto-cpufreq config file](#3-auto-cpufreq-config-file) + * [Example config file contents](#example-config-file-contents) +* [How to run auto-cpufreq](#how-to-run-auto-cpufreq) +* [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options) + * [monitor](#monitor) + * [live](#live) + * [overriding governor](#overriding-governor) + * [Install - auto-cpufreq daemon](#install---auto-cpufreq-daemon) + * [Update - auto-cpufreq update](#update---auto-cpufreq-update) + * [Remove - auto-cpufreq daemon](#remove---auto-cpufreq-daemon) + * [stats](#stats) +* [Troubleshooting](#troubleshooting) + * [AUR](#aur) +* [Discussion](#discussion) +* [Donate](#donate) + * [Financial donation](#financial-donation) + * [Paypal](#paypal) + * [BitCoin](#bitcoin) + * [Code contribution](#code-contribution) ## Why do I need auto-cpufreq? @@ -85,10 +88,10 @@ auto-cpufreq is available on the [snap store](https://snapcraft.io/auto-cpufreq) sudo snap install auto-cpufreq ``` -**Please note:** +**Please note:** * Make sure [snapd](https://snapcraft.io/docs/installing-snapd) is installed and `snap version` version is >= 2.44 for `auto-cpufreq` to fully work due to [recent snapd changes](https://github.com/snapcore/snapd/pull/8127). -* Fedora users will [encounter following error](https://twitter.com/killyourfm/status/1291697985236144130) due to `cgroups v2` [being in development](https://github.com/snapcore/snapd/pull/7825). This problem can be resolved by either running `sudo snap run auto-cpufreq` after the snap installation or by using the [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer) which doesn't have this issue. +* Fedora users will [encounter following error](https://twitter.com/killyourfm/status/1291697985236144130) due to `cgroups v2` [being in development](https://github.com/snapcore/snapd/pull/7825). This problem can be resolved by either running `sudo snap run auto-cpufreq` after the snap installation or by using the [auto-cpufreq-installer](#auto-cpufreq-installer) which doesn't have this issue. ### auto-cpufreq-installer @@ -98,12 +101,15 @@ Get source code, run installer and follow on screen instructions: git clone https://github.com/AdnanHodzic/auto-cpufreq.git cd auto-cpufreq && sudo ./auto-cpufreq-installer ``` +### Update using auto-cpufreq-installer + +The feature is available from version *1.9.8*. For further information: [--update](#update---auto-cpufreq-update) In case you encounter any problems with `auto-cpufreq-installer`, please [submit a bug report](https://github.com/AdnanHodzic/auto-cpufreq/issues/new). ### 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). However, if you still wish to use AUR then follow the [Troubleshooting](https://github.com/AdnanHodzic/auto-cpufreq/#aur) section for solved known issues. +*AUR is currently unmaintained & has issues*! Until someone starts maintaining it, use the [auto-cpufreq-installer](#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](#aur) section for solved known issues. * [Binary Package](https://aur.archlinux.org/packages/auto-cpufreq) (For the latest binary release on github) @@ -119,11 +125,11 @@ auto-cpufreq makes all decisions automatically based on various factors like cpu ### 1: power_helper.py script (Snap package install **only**) -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. +When installing auto-cpufreq using [auto-cpufreq-installer](#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. 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:** +**Please Note:**
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: @@ -135,21 +141,21 @@ Navigate to repo location where `power_helper.py` resides, i.e: 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: +Then disable GNOME Power Profiles Daemon by running: `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. +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. +See [`--force` flag](#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. +You can configure separate 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. By default, auto-cpufreq does not use the config file! If you wish to use it, the location where config needs to be placed for it to be read automatically is: `/etc/auto-cpufreq.conf` @@ -201,22 +207,25 @@ turbo = auto ## How to run auto-cpufreq auto-cpufreq should be run with with one of the following options: -* [monitor](https://github.com/AdnanHodzic/auto-cpufreq/#monitor) +* [monitor](#monitor) - Monitor and see suggestions for CPU optimizations -* [live](https://github.com/AdnanHodzic/auto-cpufreq/#live) +* [live](#live) - Monitor and make (temp.) suggested CPU optimizations -* [install](https://github.com/AdnanHodzic/auto-cpufreq/#install---auto-cpufreq-daemon) / [remove](https://github.com/AdnanHodzic/auto-cpufreq/#remove---auto-cpufreq-daemon) +* [install](#install---auto-cpufreq-daemon) / [remove](#remove---auto-cpufreq-daemon) - Install/remove daemon for (permanent) automatic CPU optimizations -* [install_performance](https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script) +* [update](#update---auto-cpufreq-update) + - Update auto-cpufreq to the latest release + +* [install_performance](#1-power_helperpy-script) - Install daemon in "performance" mode. -* [stats](https://github.com/AdnanHodzic/auto-cpufreq/#stats) +* [stats](#stats) - View live stats of CPU optimizations made by daemon -* [force=TEXT](https://github.com/AdnanHodzic/auto-cpufreq/#overriding-governor) +* [force=TEXT](#overriding-governor) - Force use of either the "powersave" or "performance" governor. Setting to "reset" goes back to normal mode * config=TEXT @@ -228,13 +237,13 @@ auto-cpufreq should be run with with one of the following options: * version - Show currently installed version -* [donate](https://github.com/AdnanHodzic/auto-cpufreq/#financial-donation) +* [donate](#financial-donation) - To support the project * help - Shows all of the above options -Running `auto-cpufreq --help` will print the same list of options as above. Read [auto-cpufreq modes and options](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-modes-and-options) for more details. +Running `auto-cpufreq --help` will print the same list of options as above. Read [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options) for more details. ## auto-cpufreq modes and options @@ -277,6 +286,12 @@ If the install has been performed as part of snap package, daemon status can be `systemctl status snap.auto-cpufreq.service.service` +### Update - auto-cpufreq update + +Update functionality works by cloning auto-cpufreq repo to /home directory of currently logged in user, installing it using [auto-cpufreq-installer](#auto-cpufreq-installer) and performing [auto-cpufreq daemon install](#install---auto-cpufreq-daemon) with [latest version](https://github.com/AdnanHodzic/auto-cpufreq/releases) changes. + +Update the package by running: `sudo auto-cpufreq --update` + ### Remove - auto-cpufreq daemon auto-cpufreq daemon and its systemd service, along with all its persistent changes can be removed by running: @@ -302,7 +317,12 @@ If daemon has been installed, live stats of CPU/system load monitoring and optim **A:** If you're using `intel_pstate/amd-pstate` CPU management driver, consider changing it to `acpi-cpufreq`. -This can be done by editing the `GRUB_CMDLINE_LINUX_DEFAULT` params in `/etc/default/grub`. +This can be done by editing the `GRUB_CMDLINE_LINUX_DEFAULT` params in `/etc/default/grub`. For instance: + +``` + sudo nano /etc/default/grub + # make sure you have nano installed, or you can use your favorite text editor. +``` For Intel users: @@ -316,7 +336,20 @@ For AMD users: GRUB_CMDLINE_LINUX_DEFAULT="quiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0" ``` -After you are done, run `sudo update-grub` or `sudo grub-mkconfig -o /boot/grub/grub.cfg`, if you are using Arch. +Once you have made the necessary changes to the GRUB configuration file, you can update it by running `sudo update-grub` or `sudo grub-mkconfig -o /boot/grub/grub.cfg` on Arch Linux. On the other hand, for Fedora, you can update the configuration file by running one of the following commands: + +``` + sudo grub2-mkconfig -o /etc/grub2.cfg +``` + +``` + sudo grub2-mkconfig -o /etc/grub2-efi.cfg +``` + +``` + sudo grub2-mkconfig -o /boot/grub2/grub.cfg + # Legacy boot method for grub update. +``` ### AUR diff --git a/auto-cpufreq-installer b/auto-cpufreq-installer index 9ca1dd98..05f7facf 100755 --- a/auto-cpufreq-installer +++ b/auto-cpufreq-installer @@ -20,9 +20,9 @@ fi #separator function separator { - local COLOUMNS="`tput cols`" + local COLUMNS="`tput cols`" echo -e "\n" - printf "%0.s─" $(seq $COLOUMNS) + printf "%0.s─" $(seq $COLUMNS) echo -e "\n" } @@ -37,8 +37,8 @@ function root_check { } function header { - local COLOUMNS="`tput cols`" - MID="$((COLOUMNS / 2))" + local COLUMNS="`tput cols`" + MID="$((COLUMNS / 2))" HEADER="$1" printf "%0.s─" $(seq $((MID-(${#HEADER}/2)- 1))) echo -n " $HEADER " @@ -272,6 +272,50 @@ function tool_remove { echo -e "\nauto-cpufreq tool and all its supporting files successfully removed." separator } +function tool_update { + # Specify the repository and package name + # IT IS IMPORTANT TO NOTE THAT IF THE REPOSITORY STRUCTURE IS CHANGED, THE FOLLOWING FUNCTION NEEDS TO BE UPDATED ACCORDINGLY + repository="AdnanHodzic/auto-cpufreq" + # Fetch the latest release information from GitHub API + latest_release=$(curl -s "https://api.github.com/repos/$repository/releases/latest") + # Extract the latest release version without using jq + latest_version=$(echo "$latest_release" | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4) + # Get the current version of auto-cpufreq + #installed_version=$(pip list | awk '/auto-cpufreq/ {print $2}') + installed_version=$(grep -oP "(?<=__requires__ = 'auto-cpufreq==)\d+(\.\d+)+" /opt/auto-cpufreq/venv/bin/auto-cpufreq) + #Check whether the same is installed or not + if [[ -z "$installed_version" ]]; then + echo "Current version not found, please install auto-cpufreq first" + echo $installed_version + sleep 1 + exit 1 + fi + installed_version="v$installed_version" + # Compare the latest version with the installed version and perform update if necessary + if [[ "$latest_version" == "$installed_version" ]]; then + echo "auto-cpufreq is up to date" + else + echo -e "Updates are available,\nCurrent version: $installed_version\nLatest version: $latest_version \nNote that your previous custom settings might be \033[1merased\033[0m with the following update \nalong with the \033[1mcurrent\033[0m directory. " + read -p "Do you want to update auto-cpufreq to the latest release? [y/n]: " ans + if [[ "$ans" == "y" ]]; then + header "Updating auto-cpufreq to the latest release\n" + tool_remove + echo -e "deleting the current directory\n" + script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + mkdir "$script_directory/../auto-cpufreq $latest_version" && cd $_ + rm -rf $script_directory + echo -e "cloning to the latest release\n" + git clone https://github.com/AdnanHodzic/auto-cpufreq.git + cd auto-cpufreq + separator + tool_install + else + separator + echo "Aborting..." + sleep 0.5 + fi + fi +} function ask_operation { header "auto-cpufreq installer" @@ -279,10 +323,11 @@ function ask_operation { \nOptions:\n" read -p \ "[I]nstall +[U]pdate [R]emove [Q]uit -Select a key: [i/r/q]: " answer +Select a key: [i/u/r/q]: " answer } # End of functions @@ -295,6 +340,9 @@ else "--install") answer="i" ;; + "--update") + answer="u" + ;; "--remove") answer="r" ;; @@ -307,6 +355,8 @@ fi case $answer in I|i) tool_install ;; + U|u) + tool_update ;; R|r) tool_remove ;; Q|q) diff --git a/auto_cpufreq/core.py b/auto_cpufreq/core.py old mode 100644 new mode 100755 index fdc0a056..99397a92 --- a/auto_cpufreq/core.py +++ b/auto_cpufreq/core.py @@ -18,6 +18,8 @@ from pathlib import Path from shutil import which from subprocess import getoutput, call, run, check_output, DEVNULL +import requests +import re # execution timestamp used in countdown func from datetime import datetime @@ -162,7 +164,41 @@ def app_version(): except Exception as e: print(repr(e)) pass - +def verify_update(): + # Specify the repository and package name + # IT IS IMPORTANT TO THAT IF THE REPOSITORY STRUCTURE IS CHANGED, THE FOLLOWING FUNCTION NEEDS TO BE UPDATED ACCORDINGLY + # Fetch the latest release information from GitHub API + latest_release_url = f"https://api.github.com/repos/AdnanHodzic/auto-cpufreq/releases/latest" + latest_release = requests.get(latest_release_url).json() + latest_version = latest_release["tag_name"] + + # Get the current version of auto-cpufreq + # Extract version number from the output string + output = check_output(['auto-cpufreq', '--version']).decode('utf-8') + version_line = next((re.search(r'\d+\.\d+\.\d+', line).group() for line in output.split('\n') if line.startswith('auto-cpufreq version')), None) + installed_version = "v" + version_line + #Check whether the same is installed or not + # Compare the latest version with the installed version and perform update if necessary + if latest_version == installed_version: + print("auto-cpufreq is up to date") + exit(0) + else: + print(f"Updates are available,\nCurrent version: {installed_version}\nLatest version: {latest_version}") + print("Note that your previous custom settings might be erased with the following update") + +def new_update(): + username = os.getlogin() + home_dir = "/home/" + username + os.chdir(home_dir) + current_working_directory = os.getcwd() + print("Cloning the latest release to the home directory: ") + print(os.getcwd()) + run(["git", "clone", "https://github.com/AdnanHodzic/auto-cpufreq.git"]) + os.chdir("auto-cpufreq") + print("package cloned to directory ", current_working_directory) + run(['./auto-cpufreq-installer'], input='i\n', encoding='utf-8') + + # return formatted version for a better readability def get_formatted_version(): literal_version = pkg_resources.require("auto-cpufreq")[0].version @@ -416,7 +452,8 @@ def deploy_daemon_performance(): # output warning if gnome power profile is running gnome_power_detect_install() - gnome_power_svc_disable_performance() + #"gnome_power_svc_disable_performance" is not defined + #gnome_power_svc_disable_performance() # output warning if TLP service is detected tlp_service_detect() @@ -1138,10 +1175,20 @@ def sysinfo(): cpu_core = dict() freq_per_cpu = [] for i in range(0, len(coreid_info), 3): - freq_per_cpu.append(float(coreid_info[i + 1].split(":")[-1])) + # ensure that indices are within the valid range, before accessing the corresponding elements + if i + 1 < len(coreid_info): + freq_per_cpu.append(float(coreid_info[i + 1].split(":")[-1])) + else: + # handle the case where the index is out of range + continue + # ensure that indices are within the valid range, before accessing the corresponding elements cpu = int(coreid_info[i].split(":")[-1]) - core = int(coreid_info[i + 2].split(":")[-1]) - cpu_core[cpu] = core + if i + 2 < len(coreid_info): + core = int(coreid_info[i + 2].split(":")[-1]) + cpu_core[cpu] = core + else: + # handle the case where the index is out of range + continue online_cpu_count = len(cpu_core) offline_cpus = [str(cpu) for cpu in range(total_cpu_count) if cpu not in cpu_core] @@ -1159,15 +1206,26 @@ def sysinfo(): cpu_temp_index = core_temp_labels.index(f"Core {core}") temp_per_cpu[i] = core_temp["coretemp"][cpu_temp_index].current else: - temp = list(psutil.sensors_temperatures()) - temp_per_cpu = [core_temp[temp[0]][0].current] * online_cpu_count + # iterate over all sensors + for sensor in core_temp: + # iterate over all temperatures in the current sensor + for temp in core_temp[sensor]: + if temp.label == 'CPU': + temp_per_cpu = [temp.current] * online_cpu_count + break + else: + continue + break + else: # if 'CPU' label not found in any sensor, use first available temperature + temp = list(core_temp.keys())[0] + temp_per_cpu = [core_temp[temp][0].current] * online_cpu_count except Exception as e: print(repr(e)) pass print("Core\tUsage\tTemperature\tFrequency") for (cpu, usage, freq, temp) in zip(cpu_core, usage_per_cpu, freq_per_cpu, temp_per_cpu): - print(f"CPU{cpu}:\t{usage:>5.1f}% {temp:>3.0f} °C {freq:>5.0f} MHz") + print(f"CPU{cpu} {usage:>5.1f}% {temp:>3.0f} °C {freq:>5.0f} MHz") if offline_cpus: print(f"\nDisabled CPUs: {','.join(offline_cpus)}") @@ -1236,4 +1294,5 @@ def not_running_daemon_check(): exit(1) elif os.getenv("PKG_MARKER") == "SNAP" and dcheck == "disabled": daemon_not_running_msg() - exit(1) \ No newline at end of file + exit(1) + diff --git a/bin/auto-cpufreq b/bin/auto-cpufreq index d3880dad..b89d925b 100755 --- a/bin/auto-cpufreq +++ b/bin/auto-cpufreq @@ -18,6 +18,7 @@ from auto_cpufreq.power_helper import * @click.command() @click.option("--monitor", is_flag=True, help="Monitor and see suggestions for CPU optimizations") @click.option("--live", is_flag=True, help="Monitor and make (temp.) suggested CPU optimizations") +@click.option("--update", is_flag=True, help="Update daemon and package for (permanent) automatic CPU optimizations") @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") @@ -35,7 +36,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, live, log, monitor, stats, version, donate, force, get_state): +def main(config, daemon, debug, update, install, remove, live, log, monitor, stats, version, donate, force, get_state): # display info if config file is used def config_info_dialog(): @@ -45,7 +46,8 @@ def main(config, daemon, debug, install, remove, live, log, monitor, stats, vers # set governor override unless None or invalid if force is not None: not_running_daemon_check() - set_override(force) + root_check() # Calling root_check before set_override as it will require sudo access + set_override(force) # Calling set override, only if force has some values if len(sys.argv) == 1: @@ -85,6 +87,8 @@ def main(config, daemon, debug, install, remove, live, log, monitor, stats, vers set_autofreq() countdown(2) else: + pass + #"daemon_not_found" is not defined daemon_not_found() elif monitor: config_info_dialog() @@ -211,7 +215,8 @@ def main(config, daemon, debug, install, remove, live, log, monitor, stats, vers auto_cpufreq_stats_file.close() auto_cpufreq_stats_path.unlink() - # ToDo: + # ToDo: + # {the following snippet also used in --update, update it there too(if required)} # * undo bluetooth boot disable gnome_power_rm_reminder_snap() remove_complete_msg() @@ -219,6 +224,32 @@ def main(config, daemon, debug, install, remove, live, log, monitor, stats, vers root_check() remove_daemon() remove_complete_msg() + elif update: + root_check() + if os.getenv("PKG_MARKER") == "SNAP": + print("Detected auto-cpufreq was installed using snap") + # refresh snap directly using this command + + print("Please update using snap package manager, i.e: `sudo snap refresh auto-cpufreq`.") + #check for AUR + elif subprocess.run(["bash", "-c", "command -v yay >/dev/null 2>&1"]).returncode == 0 or subprocess.run(["bash", "-c", "command -v pacman >/dev/null 2>&1"]).returncode == 0: + print("Arch-based distribution with AUR support detected. Please refresh auto-cpufreq using your AUR helper.") + else: + verify_update() + ans = input ("Do you want to update auto-cpufreq to the latest release? [y/n]: ") + valid_options = ['y', 'Y', 'yes', 'YES', 'Yes'] + if ans.lower() in valid_options: + remove_daemon() + remove_complete_msg() + new_update() + else: + print("incorrect input\n") + print("Aborted") + print("enabling daemon") + run(["auto-cpufreq", "--install"]) + print("auto-cpufreq is installed with the latest version") + app_version() + if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/scripts/auto-cpufreq-runit b/scripts/auto-cpufreq-runit index bada8062..01dc25f6 100755 --- a/scripts/auto-cpufreq-runit +++ b/scripts/auto-cpufreq-runit @@ -1,2 +1,3 @@ #!/bin/sh +export PATH="$PATH:/usr/local/bin" exec /usr/local/bin/auto-cpufreq --daemon diff --git a/scripts/cpufreqctl.sh b/scripts/cpufreqctl.sh index 8818d581..67040890 100755 --- a/scripts/cpufreqctl.sh +++ b/scripts/cpufreqctl.sh @@ -100,6 +100,12 @@ function driver () { cat $FLROOT/cpu0/cpufreq/scaling_driver } +function write_value () { + if [ -w $FLNM ]; then + echo $VALUE > $FLNM + fi +} + function set_driver () { DRIVER=`driver` case $DRIVER in @@ -136,7 +142,7 @@ function set_governor () { while [ $i -ne $cpucount ] do FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_governor" - echo $VALUE > $FLNM + write_value i=`expr $i + 1` done else @@ -178,7 +184,7 @@ function set_frequency () { while [ $i -ne $cpucount ] do FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_setspeed" - echo $VALUE > $FLNM + write_value i=`expr $i + 1` done else @@ -201,7 +207,7 @@ function set_frequency_min () { while [ $i -ne $cpucount ] do FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_min_freq" - echo $VALUE > $FLNM + write_value i=`expr $i + 1` done else @@ -224,7 +230,7 @@ function set_frequency_max () { while [ $i -ne $cpucount ] do FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_max_freq" - echo $VALUE > $FLNM + write_value i=`expr $i + 1` done else @@ -276,7 +282,7 @@ function set_energy_performance_preference () { while [ $i -ne $cpucount ] do FLNM="$FLROOT/cpu"$i"/cpufreq/energy_performance_preference" - echo $VALUE > $FLNM + write_value i=`expr $i + 1` done else diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index fba47e00..ebe16bc2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -20,6 +20,7 @@ parts: python-packages: - setuptools - wheel + - requests build-packages: - gcc - python3-dev From 547872462c3f7b52c92120b9481c6b5cacec4686 Mon Sep 17 00:00:00 2001 From: shadeyg56 Date: Mon, 14 Aug 2023 00:05:53 -0500 Subject: [PATCH 12/12] gui: fix pkexec on launch --- auto_cpufreq/gui/objects.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/auto_cpufreq/gui/objects.py b/auto_cpufreq/gui/objects.py index 60eaa9f8..af0901ac 100644 --- a/auto_cpufreq/gui/objects.py +++ b/auto_cpufreq/gui/objects.py @@ -51,8 +51,6 @@ def get_version(): class RadioButtonView(Gtk.Box): def __init__(self): super().__init__(orientation=Gtk.Orientation.HORIZONTAL) - # this keeps track of whether or not the button was toggled by the app or the user to prompt for authorization - self.set_by_app = True self.set_hexpand(True) self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) @@ -69,6 +67,9 @@ def __init__(self): self.performance.connect("toggled", self.on_button_toggled, "performance") self.performance.set_halign(Gtk.Align.END) + + # this keeps track of whether or not the button was toggled by the app or the user to prompt for authorization + self.set_by_app = True self.set_selected() self.pack_start(self.label, False, False, 0) @@ -98,6 +99,9 @@ def set_selected(self): case "performance": self.performance.set_active(True) case "default": + # because this is the default button, it does not trigger the callback when set by the app + if self.set_by_app: + self.set_by_app = False self.default.set_active(True) class CurrentGovernorBox(Gtk.Box):