From c5ef37f44b0ec6a57548e11295c6a89d8c757be6 Mon Sep 17 00:00:00 2001 From: tmzf5455 Date: Sun, 10 May 2026 00:43:28 +0200 Subject: [PATCH] v1.2: Remove AppIndicator, simplify --- cpu_monitor_gui.py | 173 ++---------------- .../dh_installchangelogs.dch.trimmed | 7 +- debian/changelog | 7 +- debian/control | 2 +- debian/cpu-monitor/DEBIAN/control | 6 +- debian/cpu-monitor/DEBIAN/md5sums | 5 +- debian/cpu-monitor/usr/bin/cpu-monitor | 173 ++---------------- .../usr/share/doc/cpu-monitor/changelog.gz | Bin 209 -> 166 bytes .../icons/hicolor/48x48/apps/cpu-monitor.png | Bin 4476 -> 0 bytes debian/files | 4 +- debian/rules | 1 - 11 files changed, 36 insertions(+), 342 deletions(-) delete mode 100644 debian/cpu-monitor/usr/share/icons/hicolor/48x48/apps/cpu-monitor.png diff --git a/cpu_monitor_gui.py b/cpu_monitor_gui.py index a2486d5..a333cd9 100755 --- a/cpu_monitor_gui.py +++ b/cpu_monitor_gui.py @@ -3,26 +3,10 @@ import tkinter as tk from tkinter import ttk -import subprocess -import tempfile -import os import time import psutil -import gi -try: - gi.require_version('AyatanaAppIndicator3', '0.1') - from gi.repository import AyatanaAppIndicator3, GLib, Gtk - HAVE_INDICATOR = True -except (ImportError, ValueError): - HAVE_INDICATOR = False - - -MAX_POINTS = 60 -UPDATE_INTERVAL = 1000 # ms - - try: from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure @@ -31,147 +15,28 @@ except ImportError: HAVE_MPL = False +MAX_POINTS = 60 +UPDATE_INTERVAL = 1000 # ms + + class Tracker: def __init__(self): self.cpu_data = [] self.ram_data = [] - self.disk_data = [] self._last_cpu = None - now = time.time() - self.time_data = [now] - - def _net_rate(self, curr, last, interval): - delta = curr - last - rate = max(delta / interval, 0) - return max(int(rate), 0) - - def update(self, dt): - cpu = psutil.cpu_percent(interval=None) - ram = psutil.virtual_memory().percent - disk = psutil.disk_usage('/').percent - self._last_cpu = cpu - t = time.time() - self.time_data.append(t) - - self.cpu_data.append(cpu) - self.ram_data.append(ram) - self.disk_data.append(disk) - - net = psutil.net_io_counters() - sent = net.bytes_sent - recv = net.bytes_recv - self._last_net_sent = sent - self._last_net_recv = recv - - def get_latest(self): - if self._last_cpu is None: - self.update(1) - return self._last_cpu, self.ram_data[-1] if self.ram_data else 0 - - def get_disk(self): - return self.disk_data[-1] if self.disk_data else 0 - - def get_net_rates(self): - return 0, 0 def cpu_percent(self): return psutil.cpu_percent(interval=None) -class SysTrayIndicator: - def __init__(self, monitor): - if not HAVE_INDICATOR: - return - - self.indicator = AyatanaAppIndicator3.Indicator.new( - "cpu-monitor", - "cpu-monitor", - AyatanaAppIndicator3.IndicatorCategory.SYSTEM_SERVICES - ) - self.indicator.set_menu(self._create_menu()) - self.monitor = monitor - self._update_icon() - - try: - import cairo - except ImportError: - return - - def _create_menu(self): - menu = Gtk.Menu() - - item = Gtk.MenuItem(label=f"CPU: {psutil.cpu_percent(interval=None)}%") - item.show() - menu.append(item) - - item2 = Gtk.MenuItem(label=f"RAM: {psutil.virtual_memory().percent}%") - item2.show() - menu.append(item2) - - sep = Gtk.SeparatorMenuItem() - sep.show() - menu.append(sep) - - open_item = Gtk.MenuItem(label="Open Monitor") - open_item.connect("activate", lambda w: subprocess.Popen(["cpu-monitor"])) - open_item.show() - menu.append(open_item) - - close_item = Gtk.MenuItem(label="Quit") - close_item.connect("activate", lambda w: Gtk.main_quit()) - close_item.show() - menu.append(close_item) - - return menu - - def _update_icon(self): - if not HAVE_INDICATOR: - return - try: - import cairo - except ImportError: - return - - cpu = psutil.cpu_percent() - if cpu > 80: - color = (0.93, 0.33, 0.31) - elif cpu > 50: - color = (1.0, 0.55, 0.0) - else: - color = (0.40, 0.76, 0.97) - - size = 24 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size) - cr = cairo.Context(surface) - cr.set_source_rgba(*color, 1.0) - cr.paint() - - tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False, dir='/tmp') - tmp.close() - surface.write_to_png(tmp.name) - - try: - self.indicator.set_icon_full(tmp.name, "CPU Monitor") - except Exception: - pass - - GLib.timeout_add_seconds(2, self._update_icon) - - -def create_tray_icon(monitor): - if HAVE_INDICATOR: - return SysTrayIndicator(monitor) - return None - - class App: cpu_data = [] ram_data = [] - _last_cpu = None def __init__(self, root): self.root = root self.root.title("CPU / RAM Monitor") + self.root.geometry("1000x200") self.tracker = Tracker() @@ -199,14 +64,6 @@ class App: ) self.ram_label.pack(side=tk.LEFT, padx=20) - self.net_label = ttk.Label( - header_frame, - text=f"Net: ↓ --", - font=("Segoe UI", 20, "bold"), - foreground="#ffb74d", - ) - self.net_label.pack(side=tk.LEFT, padx=20) - self.disk_label = ttk.Label( header_frame, text=f"Disk: --%", @@ -299,10 +156,10 @@ class App: self.cpu_data.append(cpu) self.ram_data.append(ram) - if len(self.cpu_data) > MAX_POINTS: - self.cpu_data = self.cpu_data[-MAX_POINTS:] - if len(self.ram_data) > MAX_POINTS: - self.ram_data = self.ram_data[-MAX_POINTS:] + if len(App.cpu_data) > MAX_POINTS: + App.cpu_data = App.cpu_data[-MAX_POINTS:] + if len(App.ram_data) > MAX_POINTS: + App.ram_data = App.ram_data[-MAX_POINTS:] self.cpu_label.config(text=f"CPU: {cpu:5.1f}%") self.ram_label.config(text=f"RAM: {ram:5.1f}%") @@ -313,8 +170,8 @@ class App: ax_ram = self.fig.axes[1] ax_disk = self.fig.axes[2] - self.cpu_line.set_data(range(len(self.cpu_data)), self.cpu_data) - self.ram_line.set_data(range(len(self.ram_data)), self.ram_data) + self.cpu_line.set_data(range(len(App.cpu_data)), App.cpu_data) + self.ram_line.set_data(range(len(App.ram_data)), App.ram_data) ax_cpu.set_xlim(0, MAX_POINTS) ax_ram.set_xlim(0, MAX_POINTS) @@ -328,19 +185,11 @@ class App: self._running = False self.status_label.config(text="Stopped.") self.root.destroy() - if HAVE_INDICATOR: - GLib.idle_add(Gtk.main_quit) def main(): root = tk.Tk() app = App(root) - - _tray = None - if HAVE_INDICATOR: - _tray = SysTrayIndicator(app) - root.bind('', lambda e: GLib.idle_add(Gtk.main_quit)) - root.mainloop() diff --git a/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed index 8cf162d..32bc293 100644 --- a/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed +++ b/debian/.debhelper/generated/cpu-monitor/dh_installchangelogs.dch.trimmed @@ -1,7 +1,6 @@ -cpu-monitor (1.1) noble; urgency=low +cpu-monitor (1.2) noble; urgency=low - * v2: Add GNOME AppIndicator systray icon in system tray bar - * v2: Autostart .desktop in /etc/xdg/autostart - * v2: System tray icon with CPU/RAM status menu + * Removed AppIndicator (Wayland incompatible) + * Simpler, more reliable autostart -- tmzf5455 Sun, 10 May 2026 00:00:00 +0000 diff --git a/debian/changelog b/debian/changelog index 8cf162d..32bc293 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,7 +1,6 @@ -cpu-monitor (1.1) noble; urgency=low +cpu-monitor (1.2) noble; urgency=low - * v2: Add GNOME AppIndicator systray icon in system tray bar - * v2: Autostart .desktop in /etc/xdg/autostart - * v2: System tray icon with CPU/RAM status menu + * Removed AppIndicator (Wayland incompatible) + * Simpler, more reliable autostart -- tmzf5455 Sun, 10 May 2026 00:00:00 +0000 diff --git a/debian/control b/debian/control index 8bc4ff1..7408072 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Rules-Requires-Root: no Package: cpu-monitor Architecture: all -Depends: python3, python3-psutil, python3-matplotlib, python3-tk, gir1.2-ayatanaappindicator3-0.1, ${misc:Depends} +Depends: python3, python3-psutil, python3-matplotlib, python3-tk, ${misc:Depends} Description: CPU/RAM/Disk/Network monitor GUI A system monitor tool with real-time matplotlib charts and a tkinter GUI, showing CPU, RAM, disk and network usage. diff --git a/debian/cpu-monitor/DEBIAN/control b/debian/cpu-monitor/DEBIAN/control index 9d92cdf..268be7a 100644 --- a/debian/cpu-monitor/DEBIAN/control +++ b/debian/cpu-monitor/DEBIAN/control @@ -1,9 +1,9 @@ Package: cpu-monitor -Version: 1.1 +Version: 1.2 Architecture: all Maintainer: tmzf5455 -Installed-Size: 32 -Depends: python3, python3-psutil, python3-matplotlib, python3-tk, gir1.2-ayatanaappindicator3-0.1 +Installed-Size: 19 +Depends: python3, python3-psutil, python3-matplotlib, python3-tk Section: utils Priority: optional Description: CPU/RAM/Disk/Network monitor GUI diff --git a/debian/cpu-monitor/DEBIAN/md5sums b/debian/cpu-monitor/DEBIAN/md5sums index 0982e61..a8f4d85 100644 --- a/debian/cpu-monitor/DEBIAN/md5sums +++ b/debian/cpu-monitor/DEBIAN/md5sums @@ -1,3 +1,2 @@ -f0e9f6392ef8c8b1447d39da0622a0d7 usr/bin/cpu-monitor -c8cc9827601e0cc0b156979c4caa4980 usr/share/doc/cpu-monitor/changelog.gz -fd11337fda885b79dc49d83a15abcffd usr/share/icons/hicolor/48x48/apps/cpu-monitor.png +f2f2755049314fcc90af2529be7731e4 usr/bin/cpu-monitor +34f5fe29e2343c3c795082bbcb93bd0c usr/share/doc/cpu-monitor/changelog.gz diff --git a/debian/cpu-monitor/usr/bin/cpu-monitor b/debian/cpu-monitor/usr/bin/cpu-monitor index a2486d5..a333cd9 100755 --- a/debian/cpu-monitor/usr/bin/cpu-monitor +++ b/debian/cpu-monitor/usr/bin/cpu-monitor @@ -3,26 +3,10 @@ import tkinter as tk from tkinter import ttk -import subprocess -import tempfile -import os import time import psutil -import gi -try: - gi.require_version('AyatanaAppIndicator3', '0.1') - from gi.repository import AyatanaAppIndicator3, GLib, Gtk - HAVE_INDICATOR = True -except (ImportError, ValueError): - HAVE_INDICATOR = False - - -MAX_POINTS = 60 -UPDATE_INTERVAL = 1000 # ms - - try: from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure @@ -31,147 +15,28 @@ except ImportError: HAVE_MPL = False +MAX_POINTS = 60 +UPDATE_INTERVAL = 1000 # ms + + class Tracker: def __init__(self): self.cpu_data = [] self.ram_data = [] - self.disk_data = [] self._last_cpu = None - now = time.time() - self.time_data = [now] - - def _net_rate(self, curr, last, interval): - delta = curr - last - rate = max(delta / interval, 0) - return max(int(rate), 0) - - def update(self, dt): - cpu = psutil.cpu_percent(interval=None) - ram = psutil.virtual_memory().percent - disk = psutil.disk_usage('/').percent - self._last_cpu = cpu - t = time.time() - self.time_data.append(t) - - self.cpu_data.append(cpu) - self.ram_data.append(ram) - self.disk_data.append(disk) - - net = psutil.net_io_counters() - sent = net.bytes_sent - recv = net.bytes_recv - self._last_net_sent = sent - self._last_net_recv = recv - - def get_latest(self): - if self._last_cpu is None: - self.update(1) - return self._last_cpu, self.ram_data[-1] if self.ram_data else 0 - - def get_disk(self): - return self.disk_data[-1] if self.disk_data else 0 - - def get_net_rates(self): - return 0, 0 def cpu_percent(self): return psutil.cpu_percent(interval=None) -class SysTrayIndicator: - def __init__(self, monitor): - if not HAVE_INDICATOR: - return - - self.indicator = AyatanaAppIndicator3.Indicator.new( - "cpu-monitor", - "cpu-monitor", - AyatanaAppIndicator3.IndicatorCategory.SYSTEM_SERVICES - ) - self.indicator.set_menu(self._create_menu()) - self.monitor = monitor - self._update_icon() - - try: - import cairo - except ImportError: - return - - def _create_menu(self): - menu = Gtk.Menu() - - item = Gtk.MenuItem(label=f"CPU: {psutil.cpu_percent(interval=None)}%") - item.show() - menu.append(item) - - item2 = Gtk.MenuItem(label=f"RAM: {psutil.virtual_memory().percent}%") - item2.show() - menu.append(item2) - - sep = Gtk.SeparatorMenuItem() - sep.show() - menu.append(sep) - - open_item = Gtk.MenuItem(label="Open Monitor") - open_item.connect("activate", lambda w: subprocess.Popen(["cpu-monitor"])) - open_item.show() - menu.append(open_item) - - close_item = Gtk.MenuItem(label="Quit") - close_item.connect("activate", lambda w: Gtk.main_quit()) - close_item.show() - menu.append(close_item) - - return menu - - def _update_icon(self): - if not HAVE_INDICATOR: - return - try: - import cairo - except ImportError: - return - - cpu = psutil.cpu_percent() - if cpu > 80: - color = (0.93, 0.33, 0.31) - elif cpu > 50: - color = (1.0, 0.55, 0.0) - else: - color = (0.40, 0.76, 0.97) - - size = 24 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size) - cr = cairo.Context(surface) - cr.set_source_rgba(*color, 1.0) - cr.paint() - - tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False, dir='/tmp') - tmp.close() - surface.write_to_png(tmp.name) - - try: - self.indicator.set_icon_full(tmp.name, "CPU Monitor") - except Exception: - pass - - GLib.timeout_add_seconds(2, self._update_icon) - - -def create_tray_icon(monitor): - if HAVE_INDICATOR: - return SysTrayIndicator(monitor) - return None - - class App: cpu_data = [] ram_data = [] - _last_cpu = None def __init__(self, root): self.root = root self.root.title("CPU / RAM Monitor") + self.root.geometry("1000x200") self.tracker = Tracker() @@ -199,14 +64,6 @@ class App: ) self.ram_label.pack(side=tk.LEFT, padx=20) - self.net_label = ttk.Label( - header_frame, - text=f"Net: ↓ --", - font=("Segoe UI", 20, "bold"), - foreground="#ffb74d", - ) - self.net_label.pack(side=tk.LEFT, padx=20) - self.disk_label = ttk.Label( header_frame, text=f"Disk: --%", @@ -299,10 +156,10 @@ class App: self.cpu_data.append(cpu) self.ram_data.append(ram) - if len(self.cpu_data) > MAX_POINTS: - self.cpu_data = self.cpu_data[-MAX_POINTS:] - if len(self.ram_data) > MAX_POINTS: - self.ram_data = self.ram_data[-MAX_POINTS:] + if len(App.cpu_data) > MAX_POINTS: + App.cpu_data = App.cpu_data[-MAX_POINTS:] + if len(App.ram_data) > MAX_POINTS: + App.ram_data = App.ram_data[-MAX_POINTS:] self.cpu_label.config(text=f"CPU: {cpu:5.1f}%") self.ram_label.config(text=f"RAM: {ram:5.1f}%") @@ -313,8 +170,8 @@ class App: ax_ram = self.fig.axes[1] ax_disk = self.fig.axes[2] - self.cpu_line.set_data(range(len(self.cpu_data)), self.cpu_data) - self.ram_line.set_data(range(len(self.ram_data)), self.ram_data) + self.cpu_line.set_data(range(len(App.cpu_data)), App.cpu_data) + self.ram_line.set_data(range(len(App.ram_data)), App.ram_data) ax_cpu.set_xlim(0, MAX_POINTS) ax_ram.set_xlim(0, MAX_POINTS) @@ -328,19 +185,11 @@ class App: self._running = False self.status_label.config(text="Stopped.") self.root.destroy() - if HAVE_INDICATOR: - GLib.idle_add(Gtk.main_quit) def main(): root = tk.Tk() app = App(root) - - _tray = None - if HAVE_INDICATOR: - _tray = SysTrayIndicator(app) - root.bind('', lambda e: GLib.idle_add(Gtk.main_quit)) - root.mainloop() diff --git a/debian/cpu-monitor/usr/share/doc/cpu-monitor/changelog.gz b/debian/cpu-monitor/usr/share/doc/cpu-monitor/changelog.gz index e53c908f8458d86812ad0359cf4c33d233e0d9c8..a711c4cde7f9c6a5c8880be62440954ca068b96e 100644 GIT binary patch literal 166 zcmV;X09pSZiwFP!0000212v323&JoMg!lf68?CmI7VF>_I6Ao4!TB{!C6MF|F;VKT zHxxW>IJkRG`czwXIGS+^w|KaAQVg{N|IIvV~I1+c_TS$?YlXYVg=(2{24PLm-w zKs(9SQ`8wQ=Y6lOH!4)H929~wnsNt9kzbKQ%&&EgXuo5(={g+#=>+u2RcHjRG@%vk U7DDW1VZo)7SMDO{EIRYibi3kU1ks^c;hd>4D2N>H0tHh4% zHIRNiArhslJ?!kx8?C5ATv?;ZI`q;}+J~`+vzj5eqcWoTINNI!0RvpJDGDjE+HA`u zin?AKsRe(4zVYOE11+q9Hl0=#y4``h7aquZ;*Kzq%Kwn9AMQkvNN)0&@aOpt*#8Ua z!mpZ6SbTmZyP`zvrQlI16WSN!7*zd!=i@xb`*WHD`(TDh8Ok;zW7!0ZO}oY$Yb&|~ LCR;{80s#O3y}w_3 diff --git a/debian/cpu-monitor/usr/share/icons/hicolor/48x48/apps/cpu-monitor.png b/debian/cpu-monitor/usr/share/icons/hicolor/48x48/apps/cpu-monitor.png deleted file mode 100644 index 8fef48ac3c51bf03579370870a778abb35222f89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4476 zcmV-?5rgiDP)|ltd~~(W6SEHcbc+b_iPuOOhrO6OlYpN--p0 zb~`qt8)4BW##70n@U+(nNnO_>{C@$<>4P@hD=#mX zzW?Xt(xpoZ5s7MQYJBW=yD!)MSd-B9aLGMC^u-vp{ht{SW>?v^ZJV-w{d#3!U;xmN z;&!{0wI6?~yfS~W^0WW^wePXmgeNmE`5x^ZWbz4~>qDTyomS z>fDa;ZgPxUA=J@^d+39{nHMiT)o!vVjh#L6WIQ%e7hm6Lv0AN|ZKbob(`R^i*z2ga zv2nJ|W=mo?3pt(ScDdYRP8an%T+}=6qHa6nL|xi&4}Hk#AgA4K>F2Sqq1qcB9`S__ z_=3-WxFjVdd1u{dG)_|UyQ;Q!mTh3*s(XCg?P73?-9gnujb_$bz%5cg?A1wd{0l0@9zD9N=SIv4|;ly z5j^8Ar^7+5Bjc{CR=XqJYzAAPIa- z#I{(YZrFwo_^PPV{>E&z3WLg%lauqe1K@2>G6^ z8b|4@X_V3-28S|45(opb017zn19{s3eZRjt9=60L+h!jlPWXaP`0ii^uy}UAzsk-X z`xKYUrL?uTPpZ^vmBQlUmu>z1){&7B$}^9+5_;iigwC)tmM0JffV@G71o-kWi8A{p zf7z3j=)<%Nv@QK2<&6=g^$dByWsC|ATMyWe?HLi@W6Ypu7WL$wF=GZ;AHL`7 z_4-LVolen|mHm3a+DC2u1MYAkay89Iz3Xzy?OJE?BU@%a0FoaF|RcrAVXst<7SlI&;7Kbmt($ z4^mRs5R0$f_$-d(a|+MmIjcjUat2s@J=f=8pdodak(tisl!|HYr3Ht=DYm(rvbZ0{ zWN=^1I&8r%B>ujp(|ivsU;-N$!8##;Pd@oXQC}1IF{Rk}k)7|hIb%Bq#CRg$Rdw@c zZin+To&6M2rlrp>6;r^4YqT$4LlJiyC>72-ZAdWz%`}fv7BA|pye9sbl0z}It+bo_ zhuy5Dvt2d>4Vzx*M6b^Q0z(e7*sNC4>Gdh<>js#m`1ttXsP-u+C{RAz6)?58tIOPC z?xVO4n>&Fa64*c@8-fD}ZJ61EoZm~1gma1{(nqPe(XKmC`Na zoJLN{;A;k_(^n%z37jTdlQU@v!)=U9r@&Gzozj~qv)@JQPo>f41?BYB81WW5Mxojk znzuiM4r?kXjcvmR{6X*yzhD6q*b0h@ma97w*pMX;?w)KgP{T$#s$4*iz&H(Z2^(PEq$?oWLSj;8@qZ$C%mBot}dv~9v#>RrqULz&7 z_c>z?0AukLMj_0`={5^34L(698aqjHl=}cIN~@z~hr?+d!$mju;AzN$Ob{TEq}H+O z7FzaYl=^gKT}3u@&=R(ZfY7G;gKsF-(9cX(C)mKK?nYo&LPA0~k)-^vRoC3C>oGE9 zp9^4+8ic65UvhK#m(dh*r;!)jkj(D>t2gM)-G?Zk;1+_xHtBgX2;K@P1R%M6y2UK( z;RHj&7WVs~+x4_OBpShpkNf_-5+<;L5v%|f_A6Je_{7J@D{n4bG}X|eH`R6-DW=up zj%(}V06g;4Pq$hqjq~=y!-EpnujVox+$u~?2cua(;~|RooX@3Nf}gDO>gZvOtG9237*uDnd3gl?{MCsX8He# ze#r{2Zbcfwo{U``ewscnye;oVD>9c-k-S2j!c2LD zKu+q7^n2dte;<{|b(1Fr&2mt!3_VHvHB}VKPjX#U64nRqL4byj2Yg5Kq=F5MUsjj$omo zKOiJsWbqnosPhxD07TlB2cJLySOc=i1ycOn2N&r@QjgJ_TwntuSOF~Tt5&VTmV_W4 zskn2eSl@1-IK9ysBk)F+Tp{GTY>O*HqIo|r#|{Dqva75+7DLIcCV2&j*7b6`5mEtE zN!#K>VHAA#p1?;p_abnfbzZ0#=k@0m;F+n{2{tf-70h5)9((LD??HC0u<%5CYb%}A zbvvVU>`cc7AiZ2gar}4}>uzG?m!?kOtx~nrAe{AZfsRdKSGEa1ep; z6S+f)Ek;W2w8`nbh2KU$;ypjrI4bAF;)9`dly|f}`K4Sx#yi*mMRTx#hu|A5V5+aF zvCdn#=y~-dR@8Fz=uwG?tA0hr#n~-79i=vPIwPCd0K-9;LF_Ic^IPZQeMf0YU@&b- zJ&zfayggE0N2`M)>9pR6K;>ZDl9o#g1Ha_{LG*{HcnUawl>&0|>Cm-u3Ms9k-RG~+ z?{<7a%Q=Wedymq#%sf0L_`;+JJQ&iNI-FoCW;QT_H7F=Z@s_Dmr+U*UC?sTAQ+*w2 z?zOm&3%nR(bwdKXEB^-l<+293t+r#G$P?Lnk%pwAj{`(pBkNn+u6K|<2ImjhE z)0p{{yp!$9D`k776mX>siyY%ro8lV`rT1D~U;>+(K4A8iDO0APQAtWpMpFH-q~y9r z*VIHQjRt4rJr3etm&}TY>#?3~5_J)$51!7SFP6}P&ks`~FV@J$E{d!-;GIVndK#~h z&8Jc+w6YliVu2$4U`rKjNdO|Q!#5bf0w%B}r=(!R5#|Zyl~-O-`0JWHg}{Rcm-1Sm zTMbRFNH3)K%21uq18s2(zJmAoO`Q8@x&{CxuxXtC8^cf0qJv@B7!WXo;2_oB1V0db zg8?jo2M#U;8yIKKoT)reo__jixyHrC`{ALS&%KbMYiyv4bvj3QBhQHHlm#au)V=K; z>eG;g{?36AI*_lS16N9EC31X!2<}T-L^1S4TT)lA;){K(`s_HPqADx)x_RJBn6kOQg=CSDZ2(c=T9&@|v-P z!IAIyjR+e7QUiUY|HCCd!W+8Sr{3Ab@L>3l7haeSCa{4KtPd^EJoAk5`s=SN!NI{; z3zN2N-8$#?ty{x&b#;_n*WwBXG=lh_5-1S~AWPCmZ-P+w^fOHCKT%>kjD4iAFZhIS zFn|S2U<2b{Ew8@%s`CE(?<)ZT0goW+fZco6-Mw?iQCnL}XKHjVH~;`(BE9N(pnjZR zk0W_2Juc$&~6qj$ONVuLA>Ezy!9xS^WI`lr?MCC}Ck?%71O& zJ{h_9=bby>y;)v9Twhy5xz&x%h&ls>BM81i0#o-5R6p^mj)$!XZi8(;tf-uQ?O>nq z4F<4)32fh}Jonsl%J%Ks6^q59Y~H*X`_<$P8#m48pEFwPtF?4pTjPwc(YwOnT;STj zl`s}IU<)>3yWrZj)(x9B&4*9;1_M~0efC-9?<%ur&sJ8fSb+eP_uhL?p2W22(_ctR zNy)6ZeVeMaTDn?Q>x!>#c1F~;yN@9lPMZkUaZwvZdL*Ac_rh4{i!m5`g>AqVY{K?* z_5ok;3EyA<%eN|Tyzz$oYIpeXVa4Cy-*3*GIgfJllOJy0yyn7%3wJAS-6U;QC26Xv zDZ8rP6|Ze}Mpd`CB5K;)VXR{e35C_RxzUDu=!3qR%4!)48?ePT*TOb@Y~8xm@5qrO z_}j5C{KIA6zJ2m91nKGNKFgLZoAToy{RnCGZ=1JlS)ZPfk*6ss9;&=`i>fLr=yoLs zRH>yx2IN0ILa3vS?^Q{kN`}@H7Y||V<}Lo~VGA~48$NE_xFJ5j%Yv;HM&<9f{EL9! zYp=cb=#)nvMQZ(c#!D~F+8Ge=F&{KZ=W}vy73Aj`t``MJ;Y*6{t{gAb