pax_global_header00006660000000000000000000000064137052716150014521gustar00rootroot0000000000000052 comment=c76573ac7dd08cf08fbd576768efb151fcbadb6c bashtop-0.9.25/000077500000000000000000000000001370527161500132565ustar00rootroot00000000000000bashtop-0.9.25/.editorconfig000066400000000000000000000001471370527161500157350ustar00rootroot00000000000000[*.{sh,md,cfg,sample}] indent_style = tab indent_size = 4 [bashtop] indent_style = tab indent_size = 4bashtop-0.9.25/.github/000077500000000000000000000000001370527161500146165ustar00rootroot00000000000000bashtop-0.9.25/.github/FUNDING.yml000066400000000000000000000012061370527161500164320ustar00rootroot00000000000000# These are supported funding model platforms github: aristocratos patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] bashtop-0.9.25/.github/ISSUE_TEMPLATE/000077500000000000000000000000001370527161500170015ustar00rootroot00000000000000bashtop-0.9.25/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000017761370527161500215060ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: aristocratos --- **Describe the bug** [A clear and concise description of what the bug is.] **To Reproduce** [Steps to reproduce the behavior:] **Expected behavior** [A clear and concise description of what you expected to happen.] **Screenshots** [If applicable, add screenshots to help explain your problem.] **Info (please complete the following information):** - Bashtop version: - (Linux) Linux distribution and version: - (Linux) Data collection type (/proc or psutil): - Psutil version: `python3 -c "import psutil; print(psutil.version_info)"` (version 5.7.0 or above is required): - (OSX/FreeBSD) Os release version: - Terminal used: - Font used: - Bash version, `bash --version` (version 4.4 or above is required): - Locales: output of `locale -v` **Additional context** contents of `$HOME/.config/bashtop/error.log` (enable error-logging in "$HOME/.config/bashtop/bashtop.cfg" if missing) bashtop-0.9.25/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011571370527161500225320ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[REQUEST]" labels: enhancement assignees: aristocratos --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. bashtop-0.9.25/.gitignore000066400000000000000000000000271370527161500152450ustar00rootroot00000000000000DEB/bashtop_* DEB/usr/*bashtop-0.9.25/.gitmodules000066400000000000000000000005021370527161500154300ustar00rootroot00000000000000[submodule "test/libs/bats"] path = test/libs/bats url = https://github.com/sstephenson/bats [submodule "test/libs/bats-assert"] path = test/libs/bats-assert url = https://github.com/ztombol/bats-assert [submodule "test/libs/bats-support"] path = test/libs/bats-support url = https://github.com/ztombol/bats-support bashtop-0.9.25/.travis.yml000066400000000000000000000003401370527161500153640ustar00rootroot00000000000000# travis does not offer python container for OSX or Windows, not sure if we can get automated testing? os: linux language: python python: - "3.6" dist: bionic install: - pip install -r requirements.txt script: ./test.sh bashtop-0.9.25/CHANGELOG.md000066400000000000000000000202511370527161500150670ustar00rootroot00000000000000# Changelog ## v0.9.25 * Fixed: Crash when using "/proc" data collection and filesystem type is 9p, by @bolapara ## v0.9.24 * Fixed: Psutil script crash on OSX * Fixed: Error handling for malformed osx-cpu-temp output ## v0.9.23 * Fixed: kill/terminate/interrupt process not working in OsX and FreeBSD ## v0.9.22 * Added: Added handler for mktemp failure for psutil script * Removed: Secondary mktemp command for psutil script * Fixed: Insecure test import of psutil changed ## v0.9.21 * Changed: Config file comments for theme locations * Added: Check for correct theme file path prefix * Added: Support for application cursor mode input * Fixed: Incorrect value calculation for reversed proc gradient ## v0.9.20 * Fixed: Psutil script security issue when placed directly in temp folder ## v0.9.19 * Added: Option for timestamps with python on bash < 5 * Changed: Reverted "date" command timestamps to not using fifo * Added missing # from hex value in monokai theme ## v0.9.18 * Fixed: Errors caused by process scroll change * Fixed: Process graph creation ignored for process below 0.5% ## v0.9.17 * Changed: Process list now scrolls instead of "page jump" and shows number of processes instead of number of pages * Fixed: Inverted gradient on dark text in processes box ## v0.9.16 * Fixed: Errors in v0.9.15 psutil disk collection fix * Added: Additional graph creation error checks ## v0.9.15 * Fixed: Psutil error on disk collection now fallback to df and iostat ## v0.9.14 * Added: Additional processes error checking * Added: Additional sensors error checking * Added: Additional psutil error checking ## v0.9.13 * Added: More robust psutil error handling ## v0.9.12 * Changed: Psutil data collection now runs a python script in a coprocess taking commands and sending output over coproc pipes * Added: Psutil data collection now replaces most external calls including sensors, cpu info, disks info and io collection * Changed: Tree view is now a toggle instead of sorting option * Fixed: Cpu temp check not using vcgencmd when sensors is available ## v0.9.11 * Fixed: Processes text color now sets RGB instead of RBB... ## v0.9.10 * Fixed: Humanizer function now round values 1000-1023 up to 1024 to fit size constraints. * Added: More error checks for psutil * Changed: Terminal title now includes original title if $TERMINAL_TITLE is set, suggested by @theytaz ## v0.9.9 * Fixed: Fixed theme downloader not reporting new themes and corrected comment in config ## v0.9.8 * Added: Nord theme by Justin Zobel * Changed: Theme downloader now overwrites default themes, folder user_themes (safe from overwrites) added * Changed: Cleaned up monokai theme variants * Added: Base for testing with BATS by Maciek Swiech ## v0.9.7 * Changed: UTF-8 locale check, try to find UTF-8 for current language if LANG is set but not with "UTF-8" suffix ## v0.9.6 * Fixed: UTF-8 locale check ## v0.9.5 * Added: UTF-8 locale check and automatic LANG variable set if not UTF-8 * Fixed: Filter out zero sized disks and added some psutil error checks ## v0.9.4 * Fixed: Missing path for OSX df and correct swap usage reporting for OSX ## v0.9.3 * Fixed: Resizing problems in iTerm2 * Changed: Removed redundant error checking in print function for lower cpu usage * Fixed: Memory in OSX now shows active memory usage and /private/var/vm as swap memory * Fixed: Disks in OSX changed from using "GNU df" to "BSD df" for better compatibility ## v0.9.2 * Fixed: Correct prefixes for some missed GNU tools * Added: Startup progress screen * Changed: replaced tput commands with escape sequence commands ## v0.9.1 * Added: FreeBSD support with python3 psutil data collection * Added: Check for gnu tools on non Linux platforms * Fixed: Increased graph history to avoid cut off on high resolution graphs ## v0.9.0 * Added: Mac OS X support with python3 psutil data collection * Added: Ability to switch between all available network devices ## v0.8.32 * Fixed: Error in theme error checking corrupting default theme ## v0.8.31 * Fixed: Theme 2-color gradient generation * Fixed: Theme file error checking ## v0.8.30 * Fixed: Crash on missing net device ## v0.8.29 * Fixed: Cpu temperature colors not working when above high temp value * Fixed: Unescaped "\" in process list and indent fixes * Changed: Changes to net graph rescaling parameters ## v0.8.28 * Fixed: Ctrl-C and Ctrl-Z not registering after change to "dd" * Added: Option to switch to high resolution graphs * Added: Current peak value for download/upload graphs ## v0.8.27 * Fixed: Use value for "Inactive"+"MemFree" if "MemAvailable" is missing in /proc/meminfo * Added: Option to toggle update check at start ## v0.8.26 * Fixed: Escaped delimiter for sed to fix config not saving "/" character * Fixed: Detailed process view missing info and slowdown in certain cases * Optimization: Fork cleanup ## v0.8.25 * Fixed: Backspace not registering when not set to send ascii delete * Fixed: Broken cpu temperature graph when is value over cpu high temp * Added: Possibility to run date through background fifo for bash <5 ## v0.8.24 * Fixed: Input error freezes, by changing from using "read" command to using "dd" for reading keyboard input. ## v0.8.23 * Added: Support for Raspberry Pi cpu temperature reporting * Fixed: Decreased chance of read command stalling on lower spec systems * Added: Failover to nproc if lscpu are reporting 0 cpu cores * Changed: Moved page display for options and help to bottom and changed to Page Up/Down for changing page ## v0.8.22 * Added: Sorting option "tree", shows processes in a tree structure * Added: Option to toggle process cpu usage per core instead of total available cpu power * Fixed: Possible fix for stalling read command * Added: Multiple while loop fail safes ## v0.8.21 * Fixed: iostat flag compatibility for older iostat versions * Fixed: possible fix for script stall on bash 4 ## v0.8.20 * Fixed: Update slowdown when not sorting by cpu * Added: New version desktop notification ## v0.8.19 * Added: Disks read and write stats, requires new optional dependency "iostat (part of sysstat)" * Fixed: Ctrl-C not working when showing resize error message * Fixed: Network download/upload offset auto switched off if /proc/net/dev resets * Fixed: Removed trailing whitespace in script ## v0.8.18 * Added: Pagination for help and options windows if items don't fit * Added: Option to turn off color gradient in process list * Changed: bash version check to use $BASH_VERSINFO array * Added: Filter for shown disks * Added: Option to reset network totals in options menu ## v0.8.17 * Fixed: Not showing CPU temperatures when "Package" temp is missing * Added: CPU temperature support for AMD Ryzen * Changed: Minimum size changed from 80x25 to 80x24 * Fixed: High cpu usage on systems with a lot of mounted disks ## v0.8.16 * Added: Bash version check, by Calinou * Added: OS check, by kpucynski * Fixed: number of themes reported in options when theme folder is empty, by deluxe * Fixed: README.md typos, by lucaskim1233 * Added: CHANGELOG.md ## v0.8.15 * Added: deb build script by Jukoo * Fixed: load average and uptime not showing * Fixed: freeze on reverse process order when showing detailed information * Fixed: single quotes on associative arrays ## v0.8.14 * Fixed: disks usage runaway array * Fixed: disks used not reporting new values * Changed: memory and disks update frequency increased ## v0.8.13 * Fixed: get_value() regex * Added: 2 new themes, flat-remix and flat-remix-light, by Daniel Ruiz de Alegría * Other: general cleanup and formatting ## v0.8.12 * Fixed: changed remaining ps thcount flags to nlwp ## v0.8.11 * Fixed: ps flag thcount changed to nlwp for greater compability * Fixed: regex and float to int rounding in get_value() ## v0.8.10 * Fixed: erroneous regular expressions ## v0.8.9 * Added: functions is_int, is_float, is_hex * Fixes: error checking on internal functions ## v0.8.8 * Fixed: load average max length ## v0.8.7 * Fixed: load average clipping * Fixed: cpu box calculations error ## v0.8.6 * Added: load average and uptime * Fixed: cohesive window size representation * Added: unset LC_ALL to not override wanted locale * Fixed: cpu box calculation errors ## v0.8.5 * Fixed: cpu frequency and /proc/stat error checks bashtop-0.9.25/CODE_OF_CONDUCT.md000066400000000000000000000064251370527161500160640ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at admin@qvantnet.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faqbashtop-0.9.25/CONTRIBUTING.md000066400000000000000000000032651370527161500155150ustar00rootroot00000000000000# Contributing guidelines ## When submitting pull requests * Explain your thinking in why a change or addition is needed. * Is it a requested change or feature? * If not, open a feature request to get feedback before making a pull request. * Split up multiple unrelated changes in multiple pull requests. * [Shellcheck](https://github.com/koalaman/shellcheck) your work. Current shellsheck exceptions at the beginning of [bashtop](bashtop). * Purely cosmetic changes won't be accepted without a very good explanation of its value. * (Some design choices are for better configurability of syntax highlighting.) ## Formatting ### Follow the current syntax design * Indent type: Tabs * Tab size: 4 * Use the longer "if, elif, then, else, fi" statements and indent conditionals, loops etc. * Use "[[ ]]", "(( ))" for conditions and "$( ), <( )" for command substitution. * Create functions instead of repeating blocks of code. * Don't stack unrelated blocks of code, leave blank lines for better readability. * Comment new code that isn't very obvious in it's function. * Name new variables and functions in lower-case and after what purpose they serve. * (Exception arithmetic with many variables, make sure to comment what's happening instead.) ## Optimization * Avoid forks if possible. * Avoid writing to disk if possible. * Make sure variables/arrays are cleaned up if not reused. * Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact. For questions contact Aristocratos at admin@qvantnet.com For proposing changes to this document create a [new issue](https://github.com/aristocratos/bashtop/issues/new/choose). bashtop-0.9.25/DEB/000077500000000000000000000000001370527161500136505ustar00rootroot00000000000000bashtop-0.9.25/DEB/DEBIAN/000077500000000000000000000000001370527161500145725ustar00rootroot00000000000000bashtop-0.9.25/DEB/DEBIAN/control000066400000000000000000000005411370527161500161750ustar00rootroot00000000000000Package: bashtop Version: 0.0.0 Section: base Priority: optional Architecture: all Depends: bash (>= 4.4),curl (>= 7.16.2), coreutils, sed, awk, grep Maintainer: your_name Description: Resource monitor that shows usage and stats for processor, memory, disks, network and processes Homepage: https://github.com/aristocratos/bashtop bashtop-0.9.25/DEB/DEBIAN/postinst000077500000000000000000000000641370527161500164030ustar00rootroot00000000000000#!/bin/bash echo -e "[\033[1;32m done \033[0m]" bashtop-0.9.25/DEB/DEBIAN/postrm000077500000000000000000000001041370527161500160370ustar00rootroot00000000000000#!/bin/bash echo -e "[\033[1;32m successfully removed \033[0m]" bashtop-0.9.25/DEB/DEBIAN/preinst000077500000000000000000000001051370527161500162000ustar00rootroot00000000000000#!/bin/bash echo -e "[\033[1;32m starting installation \033[0m]" bashtop-0.9.25/DEB/DEBIAN/prerm000077500000000000000000000001201370527161500156360ustar00rootroot00000000000000#!/bin/bash echo -e "[\033[1;33m removing packet from the system \033[0m]" bashtop-0.9.25/DEB/build000077500000000000000000000064251370527161500147040ustar00rootroot00000000000000#!/bin/bash # this little script is just to automate the creation of the .deb package under debian and also the automatic installation of the bashtop program in the system. # How does it work? # ---- # It parses the bashtop file to retrieve the last version specified in the script to allow a fresh creation of the .deb package # +by also retrieving the most recent version of the script then it proceeds to the installation ... set -o errexit #set -x # just for debuging readonly file_src_location=../bashtop # bashtop location ^ readonly ubin=usr/bin/ readonly file_name=${file_src_location##*/} readonly ctrl_file=DEBIAN/control readonly architecture=`dpkg --print-architecture` # for all architectures readonly root_uid=0 declare version build_version [[ ROOT::PERMISSION ]] { [[ $UID -ne ${root_uid} ]] && { echo -e "require root user" exit $UID } } [[ ARGUMENTS::HANDLER ]] { if [[ -n $1 ]] ; then case $1 in "--remove") [[ -x /${ubin}/${file_name} ]] && { dpkg --remove ${file_name} test $? -eq 0 && exit 0 }||{ echo -e "~ nothing todo: bashtop is removed " exit 0 } ;; esac fi } echo -e "+ building package ..." sleep 1 [[ FILECHECK ]] { #+ require bashtop file to read inside [[ ! -f ${file_src_location} ]] && { echo -e "undefine ${file_name}" exit 3 # just a basic exit }|| { echo -e "+ populate DEB folder " [[ -d $ubin ]] || mkdir -p $ubin `cp $file_src_location $ubin` } #+ require control file to write inside [[ ! -f ${ctrl_file} ]] && { echo -e "undefined ${ctrl_file##*/}" exit 3 } } [[ IO::SEMVERS ]] { echo -e "+ fetching the lastest version of ${file_name}" get_current_version () { local watch_version=`grep -i "declare version" ${file_src_location}` local semvers=${watch_version##*=} echo ${semvers:1:-1} } set_new_version_ctrl() { local catch_package_version=`grep -i version ${ctrl_file}` version=${catch_package_version%%:*} build_version=${catch_package_version##*:} [[ -n $1 ]] && build_version=$1 version+=": ${build_version}" echo -e "+ set new version control" `sed -i "s/$catch_package_version/${version}/g" ${ctrl_file}` } set_new_version_ctrl $(get_current_version) } [[ PACKAGER_BUILD::DEB ]] { build_for_debian_base (){ local debian_package_name=${file_name}_${build_version}-${architecture}.deb #echo ${debian_package_name} dpkg-deb --build ../DEB ${debian_package_name} test $? -eq 0 && { if [[ -f ${debian_package_name} ]] ;then dpkg -i ${debian_package_name} [[ $? -eq 0 ]] && { exit $? }||{ echo -e "Installation failed" exit $? } fi }||{ echo -e "build failed" exit 5 } } build_for_debian_base } bashtop-0.9.25/Imgs/000077500000000000000000000000001370527161500141555ustar00rootroot00000000000000bashtop-0.9.25/Imgs/logo-t.png000077500000000000000000000206141370527161500160720ustar00rootroot00000000000000PNG  IHDRna)zTXtRaw profile type exifxڭב,D!Ŋ8fQݗ {oI_k2E2N5^|Zzr=h_]=_o<đ7_6> ]z&~_IgEx<ׁ׊joRׁ Re}~oUڅO ɿ|Gb _)Q."Xq|"FOӏ_O~(D-t9_v\ȕ/DbWH/_XYwo{:\|'V!ҕB;pyWX,11.ZG/h=[8zSVhy^ ozݻT^k'!~jX@W??%Vz}N3B: ܅ńD| h!PF+)IB)qȘSYlQ=kc5aFTћʹ J.b˨ZjVErÒe+VͬYR˭ڬ'8ڭ N48GfiYf6s,*.[m5viCnm=Np8Svt-^;~t?ZZ|Gxԙ}"NzFbt3BQS| E,7nubG~v+[WsjGsk[:g5OL-ꬭG\(pk'S={vk:cw};|ZSQy-*yfִuʊ4l6稷zZulqKd=$d(_JN;[g?_1Bnglgȱr}]P{66:w9ϗ͵-zKq=Bw%q9wu!s (d:d2/2O˥qsG]]>9 E i? PWҁn5pINX状޲zY-aV =uݚ)8X>1P(p8 K6GO@̑5je^GQvϽ Paހݘg=zԠڰ s׍m rN%v7"wIbQܓ=:z]2sm f6gD,NsֹDRykOb264z'-n劧1;'e%l6ސ4ZcucAG,JU=y X;BU ȹCԻLH0bNc-=,w>/_ ῳ&x)bV05a 'kCݖ_JpMNMzjxhS3ה\csxr_O P2LN]"@/upfJ̯$#ZEM@Y*;G+ʆQ*FqpPF ]иFxD==ϑ^8ŧӼkl?M.(}mh9Cl Vx^zjwz)PӘ7yϚ<Z( _=Mǽbop22:lb%qvhN073Ú ^u<轘LQ1gQ 1|Vj,hLb]lh.Za TF%RKc/L⺈KPYj޺v.~>3^υҎhG6샪N* 4bxq$YPs}$б!`bh~:Pvn8\ VjD&?_ERƤm ϟGnxtӁQ%YDlGuؼkI=4ĭ6  TY%z| uԏobi *;˺#< YT yMag$v߀MI+A'Eh⶜AB5~a!z IrBÑc<(uϸx*Fmx,U0 L7Tߧ%e 3 i%(!I<%Yx.bFRb,`>"HO"gВ,[`@ǶiKcz5D^]wG0+ +^-1 xR\Rv+ |.(c F;(w"0‡9U5CfJ?)[{_([Ҿ ^dfبѥ2 [Vydp r#ESx3e"ixf_."$\)~6 [s&Ő]~D {E*XDQZ@r;p``jg "NySd0'¡RbJQmOvfc8p…׃`M?C𓡥X GK(фR<ļvMC Z -o"iP8Uکg[$h[&Ɣk ٩Q܊Y#Rͫm@lsa7bPjînl 1yT܆8ТKĠVd`Ns,\gH&&ZFȡ^ZT/7C~kѨ&7-\DŽ%2\o/Eg{v.ڰ(6yi8fepƀtZ%D͒L͂0.sY\8O0̭}N,񕁸)i>l\HN۟hABze<0q#h_**(. V_sVL: "K^K k@j;eh0cn~ՕSD]GasEj J%hV*XYH\K׊Ӟ(!o8L3gcm ^a4zsf$a-w .+q'%i}"t(-|OSȭ hCFVO2V5PW>[0pƄsZzD"Ê[S!AiY7L9%&t_c JZ};K1Ưy&+qkǓN|?jw6+!sDd Șv!`q{6aKTuny&SŚ(񳄾J AT*bHt2#9|Y/3+DŽ~oq@[ÿpԌ=kSEdĭ1:0d浣l`* Zt?ò?o, ^^ҴLb͇+ PpQGqSYwLM-q0e|DAkLR4Xn h6%b> x2rQ>-mmG!K:5q 1 mׇ3+5iאº(׍G#Q@t/GwlvMN,T$QVi×:`3,*ۇBb8LS8GAREڴ=]CLR:.AqaFPoadí+ 0q01*n-6$w 49,( & 0ls_[vV2KX4`Q#ɟ]lM?]pCx%6|2T\ij CsІC3A5 Va~mIqu ^7W"n`Ha6}ajC&Y 8hã5mqKpA\4W3׭M@TL[˾ @-[1,/xAH>6F.O:&F2iMݿEgٮE#^Gm!ZH)F󗴔d1혖wO_,;ǂx3Nl X@ A W5YH$&Tl[Qv 6[\189w4&0,@PG*s}۸v?1, k@3 ̖𗎞q <"bnڎD֎(Y%M2k밤hb"T3KCcҳ^cUdDQVH_vnkw]J4r mf_HN1.ÞO #QbG gaȣ{|1q>So6/bb'Ֆ-#i2++L/s;Ĩ; qHa9)i™ pLS#HGm!`aKL#.xWIM\u7Z?\n]ţB^F%+ThGK$) u&iF bX(Ezf娊>K76@O^˒AY1in%sBx뗪z@x0cJ8~9HϬflҹ O\}[) cT}!]^3vM:N}2pK"Rس^ԉi3H*kVN78oF Zv'Cqn8`ᤜF9JSd+Hk(DwNVi./뀑g債W{B X|W;ݯr*~>w4A*+1?(8/b'܉UfRS##CB[6-ZRcF')? wm7ծA.bn ж f:4p|vhSkwT}6%ӪϛntRP*}m֖$1-L˫uL(#z崋|Q+=ԿJ5xJd o~õ0`|U}²_mIAۓ_j߽/*㿒>"g\.}ȊQ,ؼ/~U-de\˴1B GJqyvsi'owAn㯔aMh.nuh}|Ubo^ԑ&/̐ +dQ%eD>Ee_e?섃`ad1wEљ g}bc`lmm9cȤiKg&3Td K"j62~;<W ]yWKiCCPICC profilex}=H@_Ӗ8A!CdATQX Ъɥ_Đ8 ?.κ: "ƃ~{:S8jM%BqE " "#,1SO_.9"JdO$eaOoZ:}(J 9A$~όqXìjS1E(_(psOpI[qRXD@ uXHЪb"KIϐK&W ؀ n䄛NB@iǶ>oOқ]-v l]M.w']2$Ge no}>yj88^xw_oo>r CbKGD@><*W+ pHYs  tIME)IDATxݿOgו, d̐cJzh2TQ"H%6f)[e!$bl]2dCO$ubH,a;rTUm   ՝Ӈ>{NLuk[_J%F}%m Z}΀H@ @  ٹqv*kOi[v>omm5QKSڟּ]SJPz}mמԷח 'o]{JSߚ^ @@@#?ӧZFr++!㽽*{7Kf5L}۶YW\>W0lnnpmkL|"9 FD{fs;8k~ϟ?隷HcNgi]Kּoz`4uYcvZf/ٹ5{##π Y'zzy|pPcDþ;r1i]>!0FL 惑\^f4 C_Q_|͗zK}_~z1$?{]w@@@#3Ydn(d햴3^zeDܟ2u[ΰHٗ.u[gT@뮋 @@@#^}ZHx#XZgRaBtrk_s[__Zߟ Q_PLi\0FD4쵵6u_[__@/_vJomNPN~yt̻y5J?a'@-50wNg}%|CAgB}auu5KkO3 %@ @ hDBVWW&uI^y-Y# | Ⱥ`BѨky'|Xnr|M {BO&\!ɱmٖDŽqÙs5SUUoա] IENDB`bashtop-0.9.25/Imgs/main.png000066400000000000000000004456051370527161500156250ustar00rootroot00000000000000PNG  IHDR[q*sBITOtEXtSoftwaremate-screenshotȖJ IDATxw\l R*XPĒDQco^]S4Ŗ^Ēf% ^F1K5* m3s?ŕ33gfssfBB!z)1[U[h!kj_zTYݬd!B!^#B!BGB!zya!PU!+~Rr½NW kY_QwŭOVhĦkǖSo[I~Ԉ5(B*$vJ~J 'g69?ɿv!zH|avlnݦnX1~CFk2#ʙܟߛ.B5DNg_̧HN< 'f}ň}ˑr'6^߿e_k'>)ۦup~JJޑms,qjCq{ɠIw_XG*B%-JMΣDf}&l|LFF41w4rL|nxs!7?>O=}S++j'v mt?%%9Α3Wq;ޕ]ı턵^OL.}ߩX#dWcCذMBf7Xqˡisx$8r"׿.e4q?|x^Բn6^^\636>#G2'cxp34+څ m΅0>lqr~gUkhL}mDBQꔞ_@\<_ V%n]uohk<0}2ݯ<Ûxxz 2C'}#MdNEu?++HdV?1&ՋGM@GFu'@)%!9^!7&6GxnT!}gZ:)Pwj6Us#tv7qvub BSOl14~Y;GE !,%iѦiA4zgՎwC L˗; VL^%4?-悓˓yggБc?>jqlD%^6L' O$pʷؔ|tZi;;?B d PO  di!a߸6*^;>xާm~%j;EB{|.;a Hǯ*ϗMt82A) ug5o{^|NӓaȶwyGu 0=ʑo}gw}6ej_aHɧU&OxvN@aw6:VycfZ?II#8T `r i'7GJm񺳼ߢB{ {wƉBN^)qh\%rO;nj.odPHW̿}ʺk) Vo&x4l t;)9aXVl|۵P %7qO\moO ͯsG{yH-ܺQFcnfv|,wLJ3#^89/yD#Sԯ_~}!ےy0Xƛ5<!^4m3nq7̜4jywROQAx8>fZg6ӵg+M!k@4tdG>'ď+C:.t #G'GgX0P ;f|Sh !z_ZBnfܝ5oZs뉟حd '/0 !znc=h;a{oڎB;[ꡍT0ͼe v7<JD6!~rJ9CA?7_pZ5 jػJtws?rzfQ{,ohॗd˵?-7{'|-*-+E#B!z1YxuhM|G'7^T ;[Nx>>[u',=^U2{pi_?';EmU8W }6f f◝OѶNl7jo^/Kĥ-s;NGVJUڒE9B!ınKƯ&`k=:T0E /aBNdƐq1J,KH=Pm6ژ2IwG61?|VՑ5pP(ל-Sb4P=J@ c!B/MZ>\Wv=/:Zr^OM#TQPVMu W!B!GQB3mZN@j!B!1LV2(pjۗB!B4|B!KLbs]'B!B/5G{)ⵄil+Q |?XB!W\ݲ^xs@Djev~B!B6TcR>"porbaAU!BꐩvF8;~ܹ ݼ`xs~Nm:e ڭ m~~@75]K]1xz >b↟zJl5? &Ԅlqj?iGcဆAU%[:G/#JÖ͉ B!Pua}Y6ea$f/ŏ[n0Um_鲅SƬ\XIY^ Cȓ!aʆ=^0q:)P5RRNlPV_c.o,NZ_͉U ׵+gg|IĶӋvO6sݨw|2!/|qucw>{ /^79ixK>aS=""Tͼ%{Z;t4jᦷ.A!Bf?˩׎ ?;녱5wg$T >_-+ ]-jڬ\_G۸GZТLȝsOT^TW+k ,LM̵e-5M{j{5qp圁# v/|st">8, aw:;3Eʙy3???,0G:u\>?aEJf <;Uֹe=~r {zp {vϲ'-'=7yijO2)<{i&>{edU8(eRF8JY!B?ٻiBVeFA_fܓ=ƹSo#whqv4NoUXҳc*BE!aC;B lIU)VrwrQj?= ]V'5= [reU="wKAG.d)* "B' s8|6ˊun$i޺g̖Ec4hM $gJr\v/$ hOY!ҦOFlz~fue–%AB!P1U.]4~gc 1^Iv+AdiGKz?&guO_G^yEr P/諕֛>n3z5W<}MU:-1y8>%D!BUe-x$aYvĭE[N<+2cT>;_!b3/=b[lWĩÂuaW,a]؅ԢaВį]J #dE.3SB (~CM,J Q|0 oua\!ĥlv 5& N0>5D!BUikHB<}[nvfގ5F.;)87v~tۇ~`聃]&Mn߽ ĩ󴏼[ݨ;Ξv&mF&Ko,d9^<,(қ7d-ifkt&mV&[YqܪmBW.A҄D%aD3iyK~Dx|Sל~!Bv5m4**Y.SgO}^ ]r5 ;8Ƙ5eU9w5%]ڿv[rȩY:; Dsyy8-&Kʕq/2m?:B]vrK꽳sጃ){xI9%֫o rbow֞P|'[rɏʵ Tk+X*=a<䌒 di(*!B/˯ыQ8)x]V*\|\Qf2o'ɹd+N,e¬ZUGTl~ҡ="q}Ұ5)B!B !p;i*{5v f_Yͥ@^Yțlɥ%?:h%?y~Jau>} LMWdr2EW3B!^/LFmJD*=WljE)cstXlfdajD S#Ti8!B˦.C!B:{D!BW#b ٛt1 *Iǹ7"WXw e o~;Z`]B!DF>Mº'*Ym 77ڄ-.{D/ϣƢ[WH6dUXߏB!TEXuȤYSR WC׭}#Jn}m%սK7*sDYJW!bxBhյ#m:i5ǘh|ٛm텸{߻z#u>|AkƜI6[%=W.MO?cPs[5pTGKg!BG1 طi>GW{qh7;b3I\D."aRCLjk/ePR-8ڿANsn -yF[럼p=)e iVg1GI&VT;Jduu#>B!P f[Y}3y;BA -9$i-y#|KI,f zm::+NRB(H5gXf#P$ڵ?0˅!phqv4NoUXTnNJ RBŠYAdˆnJ2Rsٰ Lxן{$C~aڿOiB׿}IMqG%[žR^͈R^BJB!İZ5yp+iZ '̿>s%Oe1&Td\5&X: 7 N0tw{<`f RA *8U ~mB}ׇuu6I|}5wӌ8TR &]b(o "a M?-,C9S rvKL __z);nW?6]+oUȹ}~B!PJm S5B/$) eƕ/)k;PsΝ4[d VU) (i0/yѳ'/{Gw/SJXB,wO2|hFݽIα6bfA~&70?SU<"+pPՃڦUa*$BBgy^ JB² s7Spu.u{oh%=VO}$lcM8W|+x7?mC$@Y>mA@6 kg.= T9 a(偨'[˫m=]_o2_ ģ 4UM\kcH>GqYF4q6'fӃ;~k!e^ܷ?|UjaX <8NF^|_.#otO`VD[h}s9\[׳"agy^|lDL^CڨJBw#ގ5F.;)fe=*)yނ 0GcdRE./d@K ssx>W匔AJIh\+J[]d>mwNN8{ګ1 IDAT{|Mޤd9^,⫸dko9Co?|3ܝM.,JKn~^}oaX̵jTX֣F:{ׯQ`{260#Wxd>/cR./U*!6@S8!!JɂTi+;z8}N:RI9}~Jv2SQvg+TZ>"~r :- [-!BD?PD?D?j~<5>`k}7~_p/CS\ĵUfC|4cOW G>#S&ᦻ=ʶz:ܬhmt+vtn(|+tuj>i-ro>ѹܪKE+`s(k@dQھE?L= KK~ \Ll~J}zqCob<G-R>.&)SWY!,!BD?PD?D?j~<5A(D旙R42{: `ҥdgj>n]nt9mA8# E.#:#1@OL LMO7PSQNt2+R!.V㔥+>#p/ʏJ񩆔/LHScajd*-Wljz^1RcAxeF/̈HLԦG&ڦTJP1GB!zF!TjyB!B=GB!zyD|S.>jeæn~NVc0Jl7qu$yOUoHdaur5VOT|㞺xm n:Q6uʃ\d-_ʒcJ%~jUՙʭ[JY~lcKebWW׬, >HC0i,0 gXhB~eD$uTC,oz=Uƫw C Ĺc׵w 8VnꉪoS[m[Bl:nA\XQr/neʱq`-o`]2KY|lcKͿuFp,PzMQ/|qލ»VrXt[Gu_U=kNq5AɣFk $;7Xu-YaXzU-+߸/^arC>+ΒR uʃ\ɰ£d-_ʒdʳN3[U{* ˰b+*zYuȤYSR WC׭}BO?sH`}!˖FWw7Q"D0yBBPS.I"mG:~C;-}T.hS̋|:N/`[.( lƠ6.Yklq몆ϚC!zyjKevE2m&-nmܲp+,6|ϑ YmH2ks cڌ-ԏI'N c~0;@N\_͉U ׵+gϩ R- 8U;'>n'ޛc lks9ih-ڟ5G\Z=1=nwH+:6:HPt nrۣ$RB|;j\Uz?L`=,A6o|/S k [Z|ɨ;M]3 7W;çf)P$Ra;+³9䈘\bYm88,P, (ޓ d9l,z JCQXU xr 3A6t>٦ ds0;}*,ؙc@oi|gMw|zcG7(㗡C͎X̀fR /"W>ѷSg(o3ˈk:a-ضw<"/%Kx?PJtʹd+YkR;),3H;yfdFj#e(oqJl L:G=|MYTf2Rz`Ems!Y67J_ c}6~hK.QMRf`5w%9 b5ۣ:ߎJdUjJRO 1Ny^ ,C4|IYۙϝ;w!Į5綍]?e+bdJBQh 묆*w/$yYH9 Jɍe3&guO_G^癟4HmrlYBV>|*V,^rWթ>):}saݨW m~8{q܃$nzeGEBhmĕ}+1ʾFytyuϼc=)JXg܂#'rx{2@{bˑ:JX\Sm֪p})70Cc4f<b!b!u#pӠא6j=xcz iQ>ND1_JK N 7nT~`*.h\P][.Khǯ( \nvaFN1XI* =oJs\A?PgA 2 k'QR!3ɒ.ҷpyG޿ngO{ps90#?/[o,sQuy&mV&[F]Vna-xAva$ީz[M$qI)"Ê^6El5|6B! ܜ ֽSjWB׭yMCzy)*fi<,A>v[twxE/Uhqx缿Hߌq>$%_VoP@_o$bs6mn:MK̈́D[rJ#G=mmx}JN=9wC 1C 1İ.:k>˯k #sx8}V:oW*tsT %6@SD 4ӓ!+}|XG}[%ONՋdJbC-'Jd;y8}N:* $UގZ(}|QN*d\QN vryr Lr(;Qkd.>2SaVrkc iA'sApOMttU :;ۯJ >w7O 1C 1C DU0' eUN^PXtށA+"~_;B&b;qP(v#bavR(}:3;ի&JOҹwz}~*'xQĢn,bѳڪJ;@l]k>IcAw&S(z/w1-uW?hR(}OpxIMXرod>b-QSB3`rSy|U?lR(}|pU'76km\9Pݏθ[&]\LSC 1C 1İNE*R3)I8]Z08>#q~t\mC'8k &-\SW23Jas0-&Lj`CŒFOD\͉O) n2#F$).YJZMT|zn 4w㕙9&^O}xxeFS$GB!zF!TB!BE="B!ˋHO%r2Jn^NL`6qt$<C 1C 1ú\]]BZh 4_0m$RRKƗ@ECWȢm|.((lBSĢ?zwC 1C 1İNAWqmJ;S R+2󬹧Ysژ |M0j%#b!b!u" UL5O+z5t7}64Z)W}a54("^I<z eJFbʆ be3Hm~P.y7wpSMF7Jo6qsE5l l޺A=;:bnw_Q N'Ο1MŭKV/^[2>k!BT[-}b-ɴtqŸbzǧ&抾jV6j>c^%="֫!Uۮ%t货ICN}1Wsbku>z|ʙk"]{tg}W)kcÿkqks9ih-Z5G-}ZI\ub!1C 1C 1a3ֲKPZM_;nHFiꪝaǏn^04dޑ>5cO߽O"ڸ^4sGv*n3:*k3YvzqM: R^zqMTD]b'u +E m}zň"Mwmz{W;oRS'PAFcpkgȂy=v6}">㿣K;qheԊsZH;."T&./g7Qw'j&jͤ@_D}oFsI+}MH<1*#0K&&>LLKRug.. +wu1Ȥ,b!b!u#,5a e3ƭϬYO^m2})qc\$-`_0C׭Բn1~f~fZ9R"{dPʨ)#H ]\4d.jU8(YdC3o%<fdlɝ K⟾t9Dtue#S9B YR `evnNsLfJ R^!16u׷J=K}S 'Gvڤ_ض#֒V9F07yk*C 1C 1İN^HDҐy3ԥsCGDPIv|V4ҎyϜ9nSJ 3fz. T z-c (y--iiȔ:Q(YxNx]<_=m+G@\/7!b$þ{`ƚK1JR8+4#27qׂFo((0]I%թ7(}Q ۢS+S-;/6і\!B=) eƕ/)k;PsΝ4[v֘Pl,B)U2ujt:H}Ct@d ݋X5(2ŷsEr P/諕֛>n3z5ٶT<"5f{#PDzùC 1C 1:[:ye_ Pe(/+ХsnmWjR<"igG$ݧM7Cۦd%}ӛۅ Rӛٵ'}Rt<AmD^tq߹aoH[<7#]^webN! fLL0UOte[sdQNxqޗWz@Wg<F\ # Hf]?WoH;q:ۨm֪p})?0[c4fM<b!b!u#pӠא6j=xcz iQ>ND!cJ.0~$zAs4 B_BIJB Ff5-~ FEC]1rs 3rFe pٺ XE߫ F)κRAĩ󴏼[ݨ;Ξ#sQ[@0s 5:7ddM 2 rofkƂ|Шd8Mڬ|M޲1s\[Mh⪲%H(r7½$Ah)dӷB!^@T]ΞVK5Mnk =C 1C 1İ.:sOcwJlAh2'9kC'C*lNMTă%d N?R'O[^3rO%ON眼llxCJ^<TRNRır{O[I.(l%^\TGQy9J|tr4e%hNf*L|NU˹^f*J*4=G IDAT6ի="P7KWY/LjEgJ"Eѻ'b!b!u" R) 0A);qP(v#TNǂp= \[u71;WNSP*Nٹr*nE,R(}OE,3Xܥ=U OSrw48돯 {ԁUe. lR(}B>l鷛x[ e>b-QSB3`rS{h=/zm}^_~6nLBoFl#2c) \Qvz*>Fy]RtFtntI1"C 1C 1:ajp1BJL9A TәL|!$qTpPtqѐݏθ[&]\LSߏʾ[&}l"Δ'5;7*r(InY`uQi9TjssT1rE)cstXl So')QJ7F$)052Iǡb8!BV 3 Y^ dy/멨GD[ۻ}1{:ׯ#zww~S9!b!ba35uGDtQT '%|"2* pe[ɖmoծJ*~֜6i^ R> Y=!b!ba-~4kJVj!j躕oUճXQo~-|ҔD)"^I<z օoo虐z!f-;~5#bQ/7:w* q}ɾsI}s35ɸuɊ_cϚC!zyjKevE2m&-nmܲpmU}ygԤ ۟c?}JNe]Rط!>_ksW}ܗ>7M;/2t͜}z{T5JI;;l}rڜxooZqK*5gbу);stԀ$N;!b!ba%הA$i=}ϋv%Xׯ9``ԝ,\6]AL#HCmKłYzEs\L ZxōFlb`N'Tݨ\+X>Jr!"+?iGbCg)%i)fѥ?NJSaReօݐ%-39ֽh8CQdwM}.MA֭ںԪjkqZQ]v8b]U[{O@AdB !! Dcsw]ɭnY ʍrAО*} II)mfRX4ϏCيI;U;oСE}eqwm_ #fm͂wpޖ\W͕yBJ>꼁Ǣ2'l~bPC;v܄Da,9>xջTOowaf߅\YGv$?);ˎX^W6O=<,et;mR?}5'>kcN{oNl;z{mL#^K^rzHJ:5:n~v":pa1-KMFF1Yk24!B!BaIooX{Xmȅ0b^)7xyhhCG%EEn[ֿGhhhKSv%Eo3C!8=21g{*Y1ۤr'&fG1^km6WRо ]:}ZV{sxGfdic3eh-4c3j n2-4c7j  zalLCNvCNalSLU۸^ri/SIiE戔kɕOx1u\]quB}o`+{Vモ9Od"3 [|k#=^|C7,]*AȞ=;Gm=r>"ڢ]:%}De"@NіUM2nQ3R!B!BkBnvtќGqX8,qiyV&$)LXUf!hKVśLXUf !Jh*rTy4)rQ٠r: !ty\;)񬯠&m:R{*vS)Dt6h#%^nB{^*DL6h'%݄tVEJ3X^5#R߱a;뽃ygp|tB!B!ր'f7Ku*Y&v[%+YIDW5fDƻgޚ2;u^͖࿈T6;eԕKOEcwn7 " 5<v6QћogQ)7fN}Cx(o>4_6Z[Di|B]9q W+sFo#Ҭ}QJhmu{cXu`RMB!B!fOB!DDracΈ|ϵpr Θ2&!A|h-;!֬U VG6N-ɬc1)ZCf̌Ie-)=:VgcRz3mRG$t!)#&E[SftJr~h3cRdjͻ>"гpT\:xv@P{dDH|xH4 B!B!šlT󟬀n 2bdC K!q Oz *98p ?Yg͵`Ľ] Z9"B!B!~5&Y&$KXt$ 2u'TAUg1ƴkɔA!dM;B!B!5#tyǤ2d)(kWSgQ! 'xm8F9!!9_[ɪ:=u'D *Cp.Ǽp`\86`~Gs9E9aиcK#$j;3mN奻8N. ByaӲ_X Ϛx.{+l!OZ>Cy3IR! qs̬5$Sݣc,cX7(B}J9;ugm>j~լm;;e=F,%s&G{lhj޶gKǕݒ n-&5j`q'|{D?*|A!B!Bk@?/Ů /6_ͱ&_dBSX>yx(ZƆuޞh_]=${l?qgAy`N&Y$l,>M/JLF'Rj-v~cu?_X*>!;;lfDJ_,XY2vCY,,$6YTȶmӵ%{'e6?aws}{#wwft) vjvDWbFِ„Ș bߐg"ZM>G^މ9w{6O*}F;rY ih/ѕWr_ݿn-[r*jLDIId $!B!B!auF$fǟٳ>yI4dƊSǬ $h95 F'_9ˉJVaH)*B!*18$B4Bn70[Be1i(b'_PDf#QxZ/,ږH]nCZ$A}%/B_kwBA5Bv~V!۽GJ7/?^kɕVUs%c0ϜB!B!2N 7ۼȑ1f␠[?[8ikJWc6eƝ߽~1O|aqf"|7LKHc⭼ɵCUsXɓҚ$čOjZ!1 I#NˠKvٳgϞqT}I`d?}͎d.A\j ՀJbqgXBĤWLN Wf(j:zv俳t%ɪ)'{J* RdDc]3[_;m$Ov9_Jv)r0{)f;اaefB!B!šyDÑ:,$ a2kkz53c癦-vQ* As^Lmv ;:?NV Ά=лh<4!^׽7uVΆ8лuy ֓Ybl9aB4by.Ϸo+kKh gϒ6D^ފBn蒤 š)y¦>=ؾ1 3q-G657~So4Qv$.hI!cAⷿyEMw3 ƇJXp-{QܛU&ޖ\YeMF1<ɠ1 B!B!5#t2*&!:aab_#rÈIooX{T=DwUzTRwV5xyHwA݌'c.sǟ?tk6tb'XZB el6oBXz'HCE?DkgiZ[ӺΙ❃g#5]9 l ͜c63!f1j k3erKLCNfFA?8k7fZs!D5\Z?gG*9>5n-Yx[rӷC% zL3k%~5rmuBd)sǞziջr((ٶzص>"Ɯp`>ςo#5 B R7h1u2诇N'D}<!Dx cVl8f-gp#"2!cի_*-:5#B!B!&fGg3?:>؏Rĩ0gA.%KȳtHRhUf;! iV dc1=^_&b,*yK%KxHU2[Ha]nIKNH/3EEf$r!cQI{=Es?}+H!S6RMY*ƍ'd%)ڔKE;e7d M1Ig}7!U Ӧm+MhN5=<)Z{7 b#m8ta!B!Bk@UTο`naIQ,$V j6Ȩ1m>:֨ѭn!''H%o7=/=/x-|P_4>!Μ8_VLcYqi\ ݬϔj6B!B!š>˗Uq{@Q3"H3yY\8Lhe924zCх1%UrL[jF#ݒԘLsz\fb-9;NZ9yYqj&7x^陬ncJN}Y8lTyLƨ8lٱIL1eF$K1)2B)Iݔ"W-rSCUsWYOV@a3@O¨FM}{2B!B!šlUkU H\DSRsdӃ_%F/w@?B!B!5"  v+usjA"^=E#Qc$8 7Mž]>4¦e+'X򇬂 Ϛx{+l!OZ>Cy3FI O~uU=ڀa`O^$quKoqb ݱ՟;cnWfoWw;3ΰq!DxuwcQY )m 7N>폍+%Wɳ[qsw[rl!m< B!B!akҹCPq&q%dL;n=Ӣ7[1B Yp臙Gw,AOI{i5;[=Fu-',BtJH=1OfC :}zUaߐ7E>/>g:Ή^㿰ncV86gm .,>/}v?Fs)"=: X(zւBl2SҨ& :)k[Zr%u"(o/R!R% $B!B!5#9 ߁g|8iȌ$Y{AHrPkO _rt+YJZC5:jMJR(!ܲYPue㲏o4^1@*pL 2_U,B*!IW$I}4K|2#rolx]+8GB!B!ֈ]7@1I0 v۷5% d {v8~#Sq˩7sc4 \τۗ~;зgI"oE!\7tI҆saM}zqA}G];=G]۹ ;F8p$J>7W}X,B}q,g=6+1fے+v}I/њ4zB!B!fN^F$D77R,6cs^n1 Q kK׭}'spFAɶEfL>3Sgq/ d4r zɉ2z?w%F/|Ur:nE{Nl&4<;b1k﷐%ACf<<¤{2zCc4B!B!ֈ0YLcͺ .P9^Fo(r93.3Ek33e$kƪzCȞSK2smL^dzAIsvZɵRg5F3m(g5F W:8ƔY8lFuLL2Ft!d͎Mdj)=:6)=&U0Fݽ$^kb$1%e &I2V#}Dϡg9\ GO zDڋJ=Hua7j0jm6I3 k A!B!BXʪn 2bdC K!q!r:S|/) :~J\, |"S+!B!B!agYs5DvX"r;|htس_UѳLWS%9!εTc!B!B!D%!fOFɦ_\j͜> Yppe?9.𵭕k0M0 w`0 NF+ Crh fq7rq_>fG!¿C_asNyž]>4¦e+'XtyKg<=wPYK'-!<~ˎ !oo3FWyB~%@08tcǺ ER#W*L| ݱ՟;cnWf襼 ,1Զ!W9"<_֕뒥lnZmߧYsqej~vkĔ?w b)]y<0,R2r zfQUO*TE]Wr\*6G NbkUSw~B#8⫧,WUbWU.bmV}Vr­ٸUGn2Y7~HB}miћ-!D,8w¿; ̿cD>.Lu[-HݫKK0'b%ڄ]œ`Uhs: }gDw$s\._(l=uEru >='aEm EgӽgVFܒBXuIzw4ݧVv]x za)ǬqlP㲮<+^%֓V{Aw!$o*/2 /كtR82-\9B-Uz!R%Uz ne^*9 SPUʻjWEԫչj+/Ul\>@֦]GWqVWOYXŮR ]Tڬd.݅[qLb)qE;pLHv)ԟ:`d؇'EdxGnYK AP2PX,VrX3>AIhp 0_9|lǩƋ"VH}P!;GL!H[, )R# Ha(*<}$I}bMzwPS!tZ no)DBe؄d1vo8Ywȯt\kɕ檫$9laǟ9S &r zfQUO*TE]Wr\*6G NbkUSw~B#8⫧,WUbWU.bmV}VrDTȂo3렮Ty#c&ܗ!A~p֔n 5~]{9% L`G B,Sb&gF-Yt`k=)«`'5s/Iy6D i hė T;fH"m7E&2[> k!BXc5N|2H+ t{t"[\wbH1Fܔ%+ے U9oUbfK.;O;*Vɩ3GUM*TE]kUڄ窭"|Z;U1u'T:rJΏ/*gwٷaTV TyF^U܅[q?s b!Ī2%f;3+/&a 茀8?'U!Z ]#E$c,}\T`rc0oIY}4Ë엿QH/QpЦvuҧ!y_z4qa kqxE78M}gaeu]cy5K@Rt QY s9#ɒ},$ au7XzmMX|>~s.)GW!6vol[Uh`VφE펳l*l@ʶ{φى",2U樽*9vtBpeq 9Mowa}_Y]B۰Ov{qA}G];|u6uCV|u.Fk ạ+8?A W$iC买5"(3頦Zu:b8vٸumɕ>;/Sk!ZlLrp$*E>^O= IooX{Xmȅ0b^)7xyhh~ͣ7`m9EsM:.(l&m 5i6cg2uݔ8NLB(3!Cg+t&Hs 9{u3;8ZGp6sޤ53!f֛tffj,vcj-4c7ftY!r5ftYf!؍Y]ŹkީkbS=:Sq),U~a.M%W<}9T ү9SVrW#ׯv]!D 2|쩗v[N.>m뻋CroSgHӷB]!`pfɺqv7oكNsν8!|-9_#rQHU2[Ha]nIS -֊Tp>8 !t ^e")E[tjF/2V*NBڢS3 d6B/Җ,5+ QŘ!%CaVB/IJeR mIKxrX8,qi9r5nwAH<(dH3Фߝ {7mJ6HOОIc܂LA&":/;%?TTTjQk'6{K`Jmkj >afM]5>-&箁Foɯ %t l6sM[C i1{M?wm٢3'rݢŀMx؄7}Z|o깟lþv bӾL ~Joۄœ`UzFsΆm2,v6v @ػ#Os.πN.y}څ_YfΟ\Y`&a.-kM)g]Zˁ&rnc-b5QzCnIxjL&RSeSGT11k2S!Y)m9+.3xhq CU|\c0{]Y/#S[4ø}CnNMA\f>!d7'gzj&{@Q3"H3agüıFݽ$^kb$SE%hsB6*:-CoMQZCfTEՎZCfJIg-#x=WR|Ŋ4>w+q–p P\9:Zmج9B }'ӄ6S0Y~`@B*C&͞2M~Վ9?"'Ϟ:=k1p?kr/b#C^>ri>Hbp"ÎC??_]7-6-[?R0[~v,0CJ堹sWLYC|+TB=;瓗ICfX<%qګUG5w"MS<v}k4BX [ɺ36|5Aj֎ꩲ`ițcɌGouEmjTV6Ix>.Jt>㪤%9-Û'V sD q#jWMƴ3ƏXI(O]#?-z#~N׿=~tA?$_h(؟29{[-HݫK =Jmq/V7u!.>h[N߰nAӧ^Q ~cÆzwza)ǬqlPUvv?5*̼4 {y+3"nkpW\I.j bRC,P!QzDI0L$75;xJT ZOY02Ó"qPI[ǫ)_W;Qj[ "OػWw-qǤR2PR(qw HW# HJ {GU<$2_w0q8souo~Ou=\izC+ 083ͱ?#aBlUϤboB}YuE~P͋3a, Z㼅t[=ͬ)Ȓ̈́9-?N_D@r#IAPýZp|2?v[r#\'|J1CRRh1V3!B#aPbqNՎthQ_A::o8Yu %7r5a~) 1 I#NˠKvٳgϞqT}I`Nd2upWF;0fTw^K21Y9pG;kz#}; $t$%$Μ!RdIJ 8KO[G/-$nll2ntc|8|`G[-կ !z}zpNcZ8#K X- 1I:eքk 4n~:9?W6 8>;D޳?zv H0)לs폱v۷5% d {t0ǟνՔBدWx?X W$iC买fʮ$~-)z,H3[ΏlN!kn-h3F5"(,1utFE mɕU c:BЙVE>T߰߰Jįی͑ {a$D77R*o*<*)*rVyp71RyB5r֮6el6C뵦L8d2z@*+'Ц{NVU;~0/9x:r499Yf!LY99Z ،  l ؍>?d,^ka)Ӑ]8Ɛmaq:Quk%R0VdH OXlp\i % zL3k%~5rmuBd)sǞziջr((ٶL–6gxu:57F֐Mw9۴> ؇ ݻ|>˜o7⫑=;zЉwz}DEs t48_KWG ?@ʱ*#mD]@2iygX]6UGՆ(bHˣXB_.d,9<;)r'h^RڒfbIJeR mISTLPjnr!mQѤH/5##+k[U GӥIg}e7iiEBDem;P!TNKvڭ`fmZɵ|bJ5c1)ZCf̌Ie-)=:VgcRz3mRG$t!)#&E[SftJr~h3cRdjͻ>"3p.UzDګJX\<7asq񄜩*Ys`Mz&l_ۤglT+eUga6`$A!`ܪ8VW1 1=WX&|D0+Ūͺ؃JZ @U="$OYf]{YWx.ݻw GGGGGGGGGGGGGG%޳ZO]p`h][*'PM6PCjݺ48?ջ@jꨤo)Y<sge+\O8GGGx`ocΡ:|v.E||V BQVJU=<^ ~|R%僟Z5Xx1AjZ[v.D.l8dE{ wn[U4s+WL+1^U]Aw\|B'y/]*T凕G6y/VxwtR͜3Mوz!o8o*TwUz6,/~CXYCWwc;4a|DZ91Q<h?kꆕ#:~!lf/D`51ǿ|=JkkziB!qێh=j-V7<&~O?lWUlaB!qo|YkJu&$cV9y^|#>@$5V_ⓓN]=:L\{I)i7}=0ֶ1|%+5USρ(0R :.8|ӻx`h=W&$ݏϯ j@=d 7M\?yVRjy}8J!D }k- e KOᰰ \[wG7%ΜҴnS?7\=k3Awm/ƈ|aȧ_\1%_!'%ZKwoܸ5Ek?:\}TUr,o>l?WPM??nxTƊ ]?ܣ%˘:(w_g?{B$8= #bT{N8);|{;?έ}5}D|%O`vɰ6́mۼ+MPxpS`3U/w `qI}oN~+jGr]^.r`;6F;*h? :#ioaA?q!q횆,_Wv?ǶmcYL`ߩ?F!pr^苝> o6`v:|qT%ٰm}_4lѐև.&3%wX%O]c.>|wv.CL/>d4,":lK%Z/?bq+89~ƝoOSć4C; }*UB-.k~)=6_6r%-% gEVWqwy}ĆA 0\u3mU~D>\S.~ͻ`u؃A/T1o;{b'Lī]QBO`"Nó>>> ?Yǁ5nmDV56ik~IQ~ EF)R2u$--'huAwr?ܣi7u- gP~i_~Ã7tD&s~n}'6\?ҺiK\~q?}4oOo 񌎪D"4G׻Kkn=zPxw\0yV*JJe|\dջ1_O"Tvj+L~}_b?zENM͌E~t0J||VTgg#~ow ;_qo7qoLT丆Ůuw*Լ`Sܱ"X`Kҵ|Vev QPzbmbOt;(/H *(bkԟEMTQFKXjCP{Wvqw 2cʛf6yCs iYx!@ Z[+$-^: -5P/9;+g֕BYWJZu_lp@ ]V @7 VKm|@ @78uo@ H}0ki- oWA x@ M#@ @ ZG@ @.#@ @ my<"|~ǦPbAK*U2vP 1C1 *&~A]:3 P yC<{}+GwV2n^PxA8{"!f?'ZKhިѬAUZh `2=k s!!\5Zv5H}u/xOwrp{2KUWA,﯎UǛ89{ [/'S V/iM3Z [A!f ZKV7o(/o,WJ-kuEB'v}X;-f8ڭ" yz/YQ 5~jeMB a:J$'W=cOVE>pWXfvAt6%nЭ ͨo)R3#ή觎 z v>E wi^?)==!h(F-JL)&q 1YZtzYU(O!lqF\0lI%;YзٚU@f-B%W)Qyn խ\Os 8z/Sϰ=o]1ښz1n̿YfÎzsp":9-i}s~җ҂}FpE:W"PU_,CFY$f LCHgjGx<7D'A /5ZK'lgKG$U A|vu5xx7LG[WYV#h7)tvZ0穏DbtlR_'.zE$gqi37،B Ҝ-WvgH!|P1ɸqS\=] ;88qѣrǶMA[гnj+Y )ysM5QnƛLBbrM]n|b1jZ׎,)ɂ{z]b$GOͿn5ՏW@dq?88ϥ}ӒfL9gĚ_VPRf IڃnotPt/m( LTh5,YZeۀ֒+՝\5Pc3y1va|;}2JncP^{%ra6poF>r|ʮl;v鎞Eˀ;DDMȂ`݆^{J?y@xJBGˮz(@ lߌ`&=zh(l=fle%r&dju6if˶D w@S:nƋu7Fz^ ߢd1r% -puW̺^ÍG{!H:MJ?ۏ,8(8 GOɍϻtѤ:"C5PA76olw fEr+\99 K_"Y֢+z>1hI[D\_9 Pķ:r=/IyEBɴ /lhEźOEl3a륿g%+ki- o-}ZDˀwo{,aHHۥӰ01TG(^@ iIӢ(]y0 @ |ߴVbH-@ bSh @ @ i@@ ]G@ @.rzD\5,_8š2;`Fx;P8p;!!!o:R+;%/{{I6=@`*RZyccxp}d̈́Ȃs8ZC /WXӤy+X6]|ު띡;H JRe/]^5}ѫ\E`=Nޚ0loJD{׹-OD߃3ALnMSE!-}'4ɩ9D&[OCBBiSB[V;h3{ ڀ,w{S&~w_]rd{tM[U}T[/($[t󄶢f77tĕ~z devxneYߝ=GW|BP+V=/;@,U΂se@:hTQMGuE#j`!g@~*z;.5zٹk)k wP~nL3:4{cŅ\Y7"MdHRJES.ƦM;XʳYYq68$Hf>sYyv:ٯ}TuڻŚ:Mqvifܽ^ͤIiikpLPFEy߆bpwײ=_%5  aMV]z>WGef+a>8Q!qs""XO}3cBoPi+^qk7/}F{B{i&Bć#G}60ԦɉS+ZʒS6?OrYJr;BnUnƲ J1i}2NsrjJDrֱGu``/j4lx)udZBL'Lm3P<@%6AR bk7pYb1mOaX8fS%氣5ufv#u= +U2π:!;yߡ&u*xtp))Iy _A,12p(] .|V% Xvg "  лmqo Ǘ8]F|Vˮ 7d$Uq?^EB7Y䴏>cHKQ5܆mKs*>W'ͅ%$yG`+&s*$T{vƒ2fYVq t!J]{eoYB W|>2a1LgY 4hKHU0geggs*G+ őD$YFtq{QW=0ђBӕ3/x+W&'s!YaV{&\iJyE߅\"Iy%a!-JSxDD彷ɇ= bL[;!!!!3 "#A<8ac៛@HӃuduE!LV%" nqN# ѝ1bg7tc5oDk1U疸6es3u:EO>|xiV~#n!͔yY{(NP@({x3s\oASoK!BgeCǎYs- 8OWZX{%J8=Y,V=B(4t8L^ÔDLqf?u9N/|}E2TBP %_|Cm5d!-oV na(&)W=^i5sёkn;k;E"7#AvDJ15`s+Iw喺hX ~喺hxE-q,D"*5N3oEN ^?yl Ps^޲+ܜ]Jk;I).Hd̜ĺ-p03NXCtY2p!6 A 4W`hE[Q$QoΊK$F֐hۮ:2+mC{ $u.{憢c4F$KITlp~!O$TsKO_Zɥf+򸌞여~21 5𨺔 N,Aޘݷ|NOFCQR19Ų=L6@ rkA59E9$0v)fHm$Lc@]I'*8`,|26*騪 EO]C43Uf)VWqQkZBW\N*?81L9<2^][UZi@2iݘyB nJl /d\tC=~=s>K)7ty>&Tڣ׭Iܽh[]v%ج I--mmGQ5}LA5DQ>Ib 3v*ٙ<cvWVωIlάPXR1+[:ji /*.޾T羚+9ُK8 ZӌYvI[A{Ofk j;PEָ_LeF]|(NSp_鸃3!+*Qqw3x g|;U{<=VosiK%grk-F.T7.ov+eN1u@}6.RD,U:O][#HCMGQ%PN:Z:Hbg!QT&P%KC}/=MͅSbܬ@+ʯaju ṕp# Yṕ$b K]4,5YeE*im-{1TW:?ETI>IbMB?'䑸6KҤ& \j{[M0Um'wyܨ*ָ@uh9Oðe}/J:2 O8J)[ 8Yvsv!_~/xIUWeW7)끲\F9WYXL`= ˭9n8㳗IBT&}9y7]4,+Qf6(!<<Դ(@g9H+4oXqHއ8n;/I2jHin7eNvu?M&9s!JRk!9Pu%?*>X%517m;DvPaE0,OFaA!7tĕ9mc%н^Jd^VnPi~;y4d`("A`Z55tsK\ ܙLz΁l,^'*mF:>yR{ B.0ކxZt1M>G;cf/ @9(^fEu^ݟ8)eY£ E”$ ?f$ֽ#S3l.MHP]e!1qʹUl(^PaJN%WuŠ(^WB!w;0įNXZ 33KT!Ʌ`]lm ; y‚$^zu o_R7/! v׬wDtBaX-VذV~W]E¶v](ܞI . kx<p2P)~~"dS.xu0`眫=xkTIg PUgݻ%VQLsBaA~%O{;+ mϮUkSu yLQ^TF='<`Qaw__n)1$H w8j R 8"@< ( _0@ oVbk,gEvSDiDWgVpŅyeBNNxE"TMz؁&-O\XS-gMU%6kVYYҺQ  }_h~?kg1"'Qz<._2Z!2}KMD&+R=p0rz{3r>]$Q#.]'GŮ8:5B:U}zS%& 3n{r1aٻ'eb’'% aۏH? Fs@FAI1?Bup]n&)#etmu'-pZk@ m y="%wKy/Y7wյ~s}|^k>4TS@"܂$  ˼Ӂ#a5ൡ#42[mH=xٻ\ ѤEVEԫ-{^OT&V9Wj^/ʓߜ^|6@ bQ]x&6HaaaZ@ [>k>>ȗi4oV@ @ mA @ @ HzD@ ="@ v@ i@@ ]PK_ɭS-U$- Ua R5n^PHw=8\51;+}%dFx;Pgk?ur$ MQ|!!t&k }CGo!_`>v٣GIӻ9 ;6 -S&{띒љ L_[&swJV_Qo3->[ ]7_%ņ\\-! pfAZVF^vO~v &" ;f,DΩ=z:?M&9sb‹<$=v  N{}# ISzD믪e;xUW˔`u= XƮTiuws~a= ˭,ḴBj:ť(vkpU5ΆKfL'xT@devxneYߝde!Hy%J juM%"Un>@X @~w_]IRI.N,@TOt]|;8*{pj1)QAYCGR T0 Sdnm@pUNm*|8PwFO=`8YOYpl<͍O*nLE)$n4aA ? i FNipQDܧh":e=+=vwg.CmfWS&?e;e( IDAT*NN2p_dd˷9]&\OӺJMs_䒸iwe鑒sg-SDZ]RP;u9ƍ_$WzJӕ3/ȶ fɀ9h\Vvvv6S@F֧TCQA"2[W_Cv)+(j8.˾CU?pҗTzc9VIvqz-pE]}ɏs$ h8t|/%)XM"&AM6&!7Y#gy'k=o]1ښz1n̿YfTǞ4B#<WRqhà f6bd7z(anc%KyTSq=ȶB&%JyRmsi/vWLah6V| &x)~A fo7*&Do˻\ag=⯒Et"Yx^Jvboqi}zvuc4tS;"@orw+QֺjK5~69򳷏yR$׆bٻ֎[DT@#;>uˎajP0)=1wMr%2?Us(A[ d~zF8-Bp-m -:4n5r)Kz*!U&DMC ~~T3Fu o>K.*!Ń(Nup{rcesWTwKgچ.ȷ6ɿo:Jf&T |:*=2(:But;bIJ`~J*&\GW2')syNg}}Տ ͂ -uP7~)$@[mw.ƨzQҐ={Y =α;hDz=䎎x,>++3cu:HQFiրM>3^yԿiorR{]Vqc_Et[ôoPJ>wk!!3 *הQBv5ء.i~I]v2  oD`Lչ%CM`L) d;l,/}B.0M֘^_ ):3Dw^MsL(IRBIII=Pp}P6޿ĘJ(}c k'̞Oھ4U7S_74x0ve 'Ew?Lmt|%Tӝ+fQӾȬ촻1` `4xP>m=Dʬ*)*%Ү÷~) & ;Ҝ+-Yg)Xr }KUhw1'G4Pbvh # bLl 򿣛\ԻUAx '&ZZ4mhKX{T -5D9^}4?ұ7&5Ա$+c_eP %HX`X=-T~a؎fxsϊ)ꂋaH$H8.͏#"-$)ճKh ( ~\Ձ4wrH/1C[/U*BX*/ Pu3sokWR\.8F˄@ W_w" p~21B;7f-qDrA& ڠIݸ \ ? 2$NYr892̸&#Bu p cCuR:ab`fC: NNUnMTZ\qVxۛzqDȨs8&-DlG6[Y6 ꙙ0:i v>J)N;^:}ߙ&}A!yR ∸dwsxY W/ *MT!*ME(y %bbbTEQߺHLTq3^iѪMIU{:EDܵ[^yG@$ b;˪6\Ȣ5u9;ɩM?@g6X?Yv',> b`C8P$<ͻa+WhX;ǟxB/< 3氣`9>zI0A^Q+r^@/{vЂ@ ͇E\\\Kk|;ho"@ _{9;K xGK2p~ ! Keq@ KM/nm<i4xc@ ? &JQe%F*~s:uh(BU=esDm}N@ '%`˻uB氣 Qz;%mȦ\oĢd6GNFH#>?5')}'CP 5Nx$2p)67GnG%^%P1NVNE~aԥPXQ)w K e2 8?8#N/jXœ\cPXm 4ˆ%(KS؁}hupp:韛M4G/Gє4Fi~pPLGKA{ +IaX*u32FFjBO,-*BZB',$9CeGC&0%^&Ba6Qc(g,\?%2xS>x{[.BO#5>*R(LUpu-%pVpMgמ6BaʸL!{ ܏"K FN(.Sgzۀ[ [n6wQ2nyjc tng TguKF[ks__;|FL%@N@B2˻aY3mV=do;`\*tbչ4DZ@, MB/!f]!(>*w.qdWp[5)1Ϛ^OP*sI&yzZ!n8㳗YI)j!#}79D͞f}~yS/)^Dq5qWGu=%S.fE8'7 ]O˭R .T捧ߕSaG×Wy>[ZLI.Rk='?8h`NntMDNO5cƩ}I:$rylhE!].E~hUiRD{72 ؃#GxZE M2d;>vXDJ|)K^1X%5QҶ\)S1;$<&/.@ k)h0qPTQg2f~ c <D{Y#$ rS' >ν_q47;,1FvpS7c e eJϔ rC;^Ee2pmAm3.ꉪng@՝}yNK@USNy/sfOٻ{_ү3EfޣkްJ ; ˨/.LDK-yj> 69{Wܔov ʹ58p`p:M)2ё*iK֍y@B *8!j&;DcQ]C5Pܯ=O/ 5w?S2^{}Wc'@$YF4Q)2) ()tL1eU@ONBDL3)xr*YY\OOڊ$ ZѩKHiS& 8OP{hGi~GCj]4PY{%I)LѮ+*?W2AxdAZ;F::888L{A.SunaS6=7;Ӛ ]sI\4Ç1jwF'ë+1mx4d`(!)Gnd΃F;cEċwa! A[9prmґŋ$U hiSJ[Dձ@ yL&wٯnR^Ӏ;˸Lq ENͤ˔ǭ}vH2% ^2C *@PLᏈ*LѪJWxӪċ<<;' -[LH]l(^WufY hi[jVS}o\+&fZIKYȗD(#L"x/lBZ - 3 ͞=zOX8l4 pTT\zvwVÞ?i#3&adǸm󸌞엻L9-adMe([I_FDuDLI 4ѪDuyul*Pl7@ڽuf״W3-]lU{> 6D[ͯS뾶LɃEU}cQdyX!E-DP"$0_Lm#.dUDbšwqQl_?3Ҷ<[gbc3؂ "ٝ XvYY؅ƹgνss&J !J"ą!bӷďbDQx@0Kae!gqaAG7P"jpVCgJ% 6{|+aM-MYҚZDXf;פݶ6\[do﯄560@!P4I0Q!/~Ibxٖ\k.e:[:vR0tl~X80u3Nu]$^iyb5*Mʢ\qib't0y(R`6Eo\!k yhm̎o߶)Mx>ȈUß:fr8Dk<{6=7{my; z.vcSY7Vl|p{;B%ZiJ y/*϶POȒz};Z2{N/ҭ7Fa<ӗGޥTx$z/J*Df(*-/4l3zr*BT6=J'9ժ++.h\d:q+ "%t32o2FřQn8+'ɯƚJGρP69wyWU 9$QIiJ_|N7?-"m?dISz=[koyEOxsfA#}vvnTQH "\ IDATַwܶ3H`D* 2҂ Q* "qnlN۳r ǥ tWR4:@a-ya9|InwZ7]0J5HSD)*oqaCI  Q6FFA"(pvKFDIJrq XWF]R,$]<[o\۽fJai~#,-v-}WbZHWJOCF@ LO3-9"Y O6ClXD=C*wrԴDhD@ ( O[PDp#2 yW=v%B hOC@ȝJOC="@ FD@ #B !NG5%^QBd~1n&1f-*#7QG5"@ `*]{ܹO{`M;2^m/lh"?GD M%.=zݳ7SS`nu=8V]H0\|vlHpK\zi)UdvyqfAU^W۽[D3yr .t۷o=W,ņ:H,24F{|숆BUe#AM0\o"^W>@TEQ¨/r8M=%&) ph<`xuysIɚ MJ"fA\`ae * N<8 B0feO k3ӵ⹳=OiRAjhLmM=B:kӯi`J-52!UX.!_tUEytk( ABC>?q^S,A;ق㚣lG^\kMޅhw~}=76]aq샾߶"$j:{j'v495j8atK+5_U$4sʥ1KvN/8a,̔Cʂof+6.Cs]ХMt V*!IlH@ Ky$UIY $DFFm{6 sXJ4k*YY_,#Z44kdZק|.[NKHI~&lTmAC$=|U))zrr JzVwWi1`'0˽CBIku-S( DF]]wnĩ?BX 0^sf'~ ȗ" i:Ϸ):+o9>ET1:^KlÖRJ&y\抄W.ꡐj\IמvUc}e;s>|zVs7+i}hHz˥k>R);f_2kT_t2!Uc R6~ycЍޥtbYRM Fc|N)*L>zBNBF?|Ot$IZ2jGo]qjY@X~W}S>WI4LQ_nFu0fX6]jߖb[SfXJ],̌`lU ,ʮ, 0uә]'.&Wcc)67_{k `cכؕ]LnM4EPSġ5@mKd.ZS <YR7}SŊ7ufh7vP}-䙭.06otM/t+w W={@e)sD-Z&_BtI/͏<:0j )\?(w/1zlj8S ^( 2x„a})`4URWc3u N nxކTpTM k# "`bA=B_bt8QŒKT^'Uxm7<1yY#Ii2:]WNH.(ݑ$n~iy!˖1|q|'_74ҕIv !YSfh:[˲^՜aعzz~eۀkWe?ͷozw"9Wvv+/)__m·/ݪZb\Ym20{+Q#ӟp 4\s:5⦥fBɐiώڟ\T6yJo*=`qB/ N*ɭQ/^`fzU0 xL g 4IƑs%G osB]t(Ʈ&`谶Ɯpr>(H*/_$Mz+"~ O:gIc%y Vo=;?^IY/nVqc4L:'e?hOUYBuF\yI`;N9ΩR#{nozp&6 5( lu fU)GxQsOQg`͆/]taء7yΞaĖ%$a>gP앻?xOX/WY=e)Qi ^x%NPoɴJKpZNhZ0aj%&::Z?,y֤O^>Zv8Xw'X1a/?$ %K[m.|M_Omܯq 20_OщF&u^8QUtYt(Aii::bHcUTHǥKSzJ5hw}h\Rrr,~T+ݕJebɱ`8ClW\%Diڻ)Ҕn(K44LyPG[0`^%=y*+`9urͭU FEP`TF1:M[9,p,p[ F~N֫dɐ)c0pLNǸ5ٮaב _|^f]цL`]oٓ0.(,~)YX,);L[~* V\(L&ZHDt%”{,Z6|vrVJ<]yDj`iA8Q9 Ba AO…R.p?]CUq*G(,(,*xm yy%P?RnlxLP99?(-{tO(,Ȣ\aQXkbx=N{ia|5j1wQ/v̢[Us_Yr=)^fzYF&7z`#Tw&CIu!Qwtkx,6ݻ61GW^32fA<^(ZtAw c2d YEsCxL,wf%;%mymerz+59kvB\+=˂AXEbYSd>U+OD ]J1vx[Hk#'t֜DLj 柋4$0 %oAsn⧛; [vH)[F|hVU`-QAjIN[AHN+t,ۻNpŅ1g o0vҍ:.)BV}#1aJF4: 4#T#B~d0Yzk:zk{+*GwG@yŵLM.UmtyDܴkŗܘקob2EzVʸi}{'gm1[t &hrl{+* įl%s=JQ}<>-QuۍyE%j<  nTBYt}Ji&`8.ZAđCO'5t{fitxAk,ULQ|++ahhZ mhddTP/nHd&spf:x%i܅)Œzo^xM#2zٰ7l?5,@ Lpᙟ[XsDRAƟl`-͆ͽaK԰eA#"@ PDqJxztnA -NHW(,_XRo1.a*TOXJ@ hOC@ȝJOC~@ @ ~)Ј@ @sD@ $hP][a4nQ)̌t02!!S8%.: v{c>-ԁEE 0hD`nu~+Bg=d8(ZwK]*Web뭧t]T~eXCvzDg;Ym9uWg6Kxc;{U UdTɗ!<>g@/^#r" S?dST߸R^k@ٹ Pe݂ȄxuUoK[A6t?4=Ue_kҬ+jQLBIc !e|Ё81{ҕ.Z5hw}h\RbL˼% `_t'XV']F.Ok3W]ܺ.kTUÅ:ps9Eŀ))4kV.dRJ#M PXQ]@MtX!UGj*tBC13` Cg%dM;U. ܨ}0/"%*dlðPB0Z$'LʅفсKOo@eR_ ^!9`n30zm뺄urmo23buy!8oe}-l& &+[\4kǒ]㒶j|SvKApi^!5\>P ]@1X(*-L#jz+*JRrFfG)rHAEBqFlii:P#=*d !FHK!XGX /J I&mZ%%vm'o1B D@Ao,yrް<]n-~yS4̮P1WWl\>K/(%!\{HO>RW~.6OOz8 QzGYQ€\t\تaKVA\KxʝV1c3*-6[UeՕ ^޵ (NAZ??ۜ@e=L;@}s6Й73=0ed9vm]qt֗ʠaQT/ݵ*DTLzw/קx$uv\?,Z{% ͊&G0mZ03w]9/-%BYtrwǡ2)`vw(`0;{Gn15Uk;?K-&y4cL8GAQuމӪ^ PBbo[W=>|W=9Ű˥v7Nō8u/gN4~ʛQ\Z+h @v!EFZej4-Z>&Eg蚘zJD{i)} "m/}[4ͦ8 I`uo%'Yq{voIxt՚ 0Snck2djߖt!M>eSl7cŤS5.D@ |hoom>ɛg%8;y-p>H&k->4ft/;'Yp-d֎wmHC}㸱 gv](bnç:xq`Q@FۑXdҫg%ś$} H(֗:Cc5jLIQB9m(k#Kٗj?)X+ǂncbr` 'T^2؋72y7ǣhC rgkdԾDk/6 ^z~G ƘuѬMf^ؿǭl+Ix1%7}w~U8q /-TL):Ȕf;NZ2Ixp X+'$v^v@rn!?k&8\P_.@p) % 7& {Zp ($М+f~\Ri5<!~&#C 6WvX=o٬bw/ i\M;a*zb3m'-_@Ix꥖&m7fX舟EQô0nۋXƴc"zΜhP@3*"_{qώ[_6&~hӮ{5$/ooE쮻 UXfNOf" {t忱I"f&?ĊV*[G`=V(F&uozq/na1DHkZ!t2 fĚZrb1x&q$ V ?>.fh/wM 0UC?|(`@MVΕAkO6 .MLbbn8*lɗ*#㈈㤆gN. (+ ()SK+C. DrW9`&!dI|y; ip_I)4"1dpBï9 I< {QxPtroPX˥~=-D\ gmpa^$(2 U_ZJEBy%$1ª! sKܗU6vlCcض᳓@gWBr H{jCkN 1iWp oZUF\`G lf-y=M_FH[LѩL3ld86af'UGЮ9τ[Q掣e[p!mQw޻nt!ۯqHǧ "‚vkz,c/=/u*BZFTr<~c0 o",sŅ1g,=\S|0O~{ a`v SyAoHn~܇)7Z;y%/6,G.7垞()FaWt)K n˷y_YWư][iy{xp+eܺ"C=VZy^5k\ai T[~5gj&{S`oJ^dz+;C%V[koyEOxsf|k667=>w @4pcfiz`;:ls-044@ &6(AAl]e$?.L.mD7y}@ =J OlV]Ѥ'63R!ڳ@ !"rK)F%h8Ю9ğw@F rC D85 rFe U7X wW\z*56!@ ?4"B @ .hD@ @ \dLyvQ heLC==m?2&IH tG`޴/D}͓uR)=,ӭǪc ŗܽkrE1 #WNL7<<_iAQR*zyT`tZ-\~מYeoHLs]JpBiqwi)HB:m\sk2Ξډ}bN`(z}KhT<[3vQyG.Jէ@SL7 5sf+6.Cs]%15 :T#1Zcao*b[MUUA<4{N W꤫T87PZPX\e4Q@H04vG٥K(jX!iSd+&14mհýV/Z s=Q$̟Πz7/|vȻ@}zѐ+f^V-A@yLYL>>znnWFYf%.=r{&:I+D0Zj.֫$ ':vjW#1/{gKY&zϗ˔fwr~vj3u#,nQ_nFu`LmUBf݅Ɲt.SM%&P1nvd\G1Lv2*&Vc`fC{]vLcj13]>Zֵ"M4EPS\(%;֤Uw'DIix@$}mҬ]C1.1 _^&G~u?oGM^f=1}^\:50U{D[Fb`MvkhOm@=6!)99999}]g)uZvWQUL J.I9vq*}.oڲ^@'L#]ٴzՖ]{ؼr F]ey'ǽZӉ,$%I]+f.khchx˻U~S\ Ļ69=P]F[mjfikZ{俋&M[XQp{=ܸ|A9nZWj5d\?FXH*Yd;fg 8N\q'.4iFf8l33AsEչu7YJ\[XĀ8;y-pDS'myFD)[[LI@T u辠&#Y`TMfzolcLV;;LEޟӭ〝×3E*sMh_g#kO9zu46&F#O 9uٮRS9ft5уF!m`^ 4vSFv{^>X( Zm<^qΝ| 76u<\Fy!OI(b>Hbzx&6ݺu~ ZJ[M4͚cFW]hAI 4mrSyPG[mn6./nmX.ڣ(gXv ֬C~ϲX&vv 95g8[&c~B yZUMF_Z6u9_J$ %@=;jQNM]RuW"Zd1HRtǐ½gֽY&v޶7\HIe.Lu)__·Kb>8iklYM:U'ڂ·sF˘&<*N5r:GD%VluzD  SoXv a>gP앥<]sQ9q@G3z+ /{0 Q@LEi"kVyA/J%7F"xHޒЙNt=yӎ'hHH鴌- !MWnb4sX?q妒\|^+U P?)BOQ4O2!UUH{#_c0Iwnk}+x^|hnGWDUtʗMtڳ )o ^VX+zQ Y$jsn cFzC ݵP4 m;_cp 3]g RcP^"ٰg6>ZU&o&q3^w>_]Dgk-z$QdAQYtB/ qwfOF^`HAǜa-qX*a-g$A2 c0p ojeÞգϑc5ܬ#R"_:}hmklYrγ+te81yI <S"_QTʁbyG(P|y%7 Jr~P ?,LL-ϽlpaT`L&ZH%vy\Kvɒ"^]#2IòUkXş$IP3~A3&V +g(̸YL8 {{!-1. IGVCPmJ!jS924kRC|~mn# )*Q .v9$GCrTՕ'^HKAn-p)3Nն2YMqR&diLpqqjoàKUEe[ Aӈq]Dp#ཻOI:o}V(ӕ)єiNQ]at˂\mfD!o`=-DbS0/ g+#+)$mfq Y)aG5rXM:r;ppӄg%20q?[sDwlL6>PVѧ2 YPWS5bw8t.CCІ?htG##X?1ue6KͩI~B߃_r)7ve̓᲏jΣ{@ 𰚵k۔g^WG@ Q&b+ yn!xqk @ o9/WhѮ9@Kl K'T =U*F FD@ @ ğ !@ ?4"B`NG,ӭnoF+kgf?J"{{>{~JgeإO[xRT أOF&$$$$E}xr€-P`q 1sBf^ 1/vm=UdXn]w  i6?}k6l1k=uez:ijWҬ%'##,=c\&3N22SPQSb tE"a 5NV K;n=;oR 6[ǮsWFycgmN^dOs)ˮ]=d֤×w <;c¨Ȟdi;}.P5dK,'Z_Lx֬Əg̀0Y?{xno p'M<XGiRغ)Kc˻hKG^Qet1ySDSS8jR J $DAjGjhcK],ݣ??)-,ӭg^l5ּZv=_2H摃 ) B`>.0]A?.|.1Z/}xv,U'I'IKJs8'LI,Ŷ!M\jlT)RbZH![xB!;7n:u}'z] \(v"⒖ IDAT~N-qX!6 VXuNau=Jޗ.`*ڹtdW *92Cy";fjxU5^fě֝{۶q֠zj~Ow8>Nh9v!quIjZ7?2X}YGY)Sya[Scؼ<Bߪ#eh~2Wɠ,__maol̺cǽ%zeB1+E)wqtyQDKd5k.͌eOQ~;?3t$ՌwS2B_Gz07ڧzX-Ps8jq Ԍ<ԅ"7Aee<йU0 &a $yu5>>kF\g&97p(܅iZ 4jRj8̂3:௏UgTnj]LPlm1mrldjG˝s<}S~Qb]vd]F[mjfikZ{俋&M[XQp{=ܸqua瘧^k>#yP ײ#pVCcV;g Zp}'FKjl޶USHn3eD#ua3'1EFDԳ>/*˘{|TndhvWm1`n΃w [thd~KgI`hpɽRp^꬯g7ְ'/V"IO鎫oPMPEQd8*DD f%}I/~UN xO}7kd8bDgK;Ͽ[@>z8Gm9km^[U-2m{پ{6 柚&^ 6P A M|xzvR!PK U~wZ\[O]I6P_, S K3"HٲdjL\1/'*VT! 23/]ŭu蚠p04Vei+}T+&ӞƧjr\a?Ѹȓig73(_TyV . HqJP/ox6 ہa?xrpOi2BY?4S{OkXB{LKJ3x".!xfk{ei$O׼[ }FCNʎq@G93zobiikR9T57}f]ϔfGuH~5pcA5Ȩ]]Ƣ@+77gƙ;Tb'tVW'#D(ž `bK>ܞ"GWR0{%xQZ;]K9k7~jɳ_,8_ Z$m1eE] fBavy\KvFĔ{,Z6|vrVJ|DY"N(=F=y‚M,$]H)<"54S8! qs8?$ ť\T~ꦘCUq*G(,(,*xR^^ &/W=CByQJ [sI;1-"'iSŸLM IPy[5"*5Vg9 )|yn"~!HcVR-䴵t0QM%H4]" /%}ò/9 q稆F%bڝ? ͷk +6:E[g5XD˧OMD,+-.·Ov޵x TEaqyo 2bsv7WB$nnֵ:5Ĝ:Z>OBw2Q<:iHSQ#}+O=D` bN=W#2oZ$kKIƿ?G|܅yf&C9k=_Z4R=4TaBmG(gR#v פּž^j3/::So nTTTsk4]Q==fѧ` J3,1u"ez;mZZ%5@ V Nj E2  \5@ V Irƾ<]@ Ձ#"BX/6뺩h@A s֚* @ @ m8"@ @ m8"@ @ m8"@ @ m8"@0^:3G 0>`_Oawq}/]l8; uPPPPP`k4")߰]Ư9z+ϻvkOU> KU Dp~ Q!./+J(5DXB"gbL۫?}}\3)ȓ:Tj愩OCx{kG*7=+_;6Ad=1>1mEV3F:ݯ8QzjF[.{G%H S"cXo ӧyK h\6ئ z iO^KRO{ZLg۷'kzJg8"@ z32-'եm?532NlϤoSҞQft]ʇa9FMg|Bδ=g/<|?=^ܶ5R]4,0((;"Pp^ŗdYNN@jvvB_H.2XĔ0vRZKI+ zI4Ħ%N< UD^pY%"_YMR6 XRjaB`=1t&OBXߡbn%7E! (M&o-P~w (*A[h?,FDjܙɽ/,{sAO;L%*Dx"OQP΂c^Ny2 Kyr?gh%aagV ۥRI6_>VԄ!7:6>1VhE*/>no&$ϗ7o5M-2ݓ}UIBuezZ0A]Q {;75zZ?ekpDIP_oDuc[L;vns;1/oލStW;fl~uw7N3Uo7N&cEj@; 3 ä́Gl RCwD_q?h-Dպj2jG߾eIRŔӉܵ*Վl+)FVӓR( 8:yt!v=kNgٟF"NkE͛2Ι|vȅݷ'+,J`͕?P&X7@E^S"瓣 'WSj‰t'Hf0g~^_=4–u>եBʗ{ۓQfp#<^{s Jdz^qOV>a(W|/ H;G; jSI#V󸹤]IB?%Ԝ"Ed{>2{]yw/J!%RcJHLLn c5rیjB cM_߾{~aI^N)*`=xƮ&Q5?]a,Vq̘(c\ΦE^S8_^pr<Ŏ4j{^%r0`uQTdH1Iм׀6鷸)}ĬB|0;db]t/.{q/NNθt{Gv2g{Zid! =RzWjyʭA %o9>w-!9=05 r;R$$!8]v{wުKR]'^8CKw_oR 0qְЇ 1MsߏnsYwUEUEIYA7O)\gΟ]d;:1Y萈j&kck"JOꞖ^I"#Sѡ^iAFEE5HA٥ӳ o} fskɳyi^~?BLghJ6"^H&q7 i7hKZҌ(3| qᤊ&3ɈXa . @jٰsj|윓xrPp6U EFFU@ von% (ͳ*26Ϛ@ @ m8"@ @ m8"@ @ m8"@ @ mFDBߙ{Fk$ -^0<]s#LcAaQ̑i:ՙ9(@fضcu8_ xuF] ```n^{xrL f<|ɻ^?4!MϿ UV6fMg/'NIIϟ]3[rMwW`ϳ@˜Ngn/Z#\Z3;<~'_=x2>UB>k :.xRƇ.YvWa;g빋P;jDS ^gUI}Z(djcwypXķ/AO.ۮ #W*fMul{-Րn0)q>&_iðlVB-i=}_˼gp IDATo9Q ŕbfFGke,sfnXz6U?#^3 KV&dY010u3kL5[ N \wb\(mGvn9"$ %*ȶ,Q3սhx^_@\/F`U *JI rvc7rX̿Լ5?ͣP L?U7Raˋ^~/LQΛcߕ5_L״]hg_}4{')AtaV,\߹!0w;/8eʼ\v,sEw肨_{y{`ԥܓO'ПO` =Nma{^(jSY}}S3="8>ڤlqM>Y<{^<%42_YU'9uFopInxrlDպj2'F92G/rt^''b{._1 !7ebc?,Sf{eb=e/Cŧr2o`~4c{|PE!~h Nk-&khh8nɕ87z*} n?knbe,2wZAi2;iۑM<~ɤXg}CpGdu3APDDJJ UWiƢ#iәρkQ8A9rxj  `=La3qE|liSoLea7 o?djy(X|7?EzjIUJjtaǐ7L4Nfǥdg8lk}cm|d6++ Q/1JHY:eJ<)ml҇INX%$gߏ9$CCh𴨨Ҵ\Ay++ "iI1ta(6o2;ʉ,->%dWxʛ.c{P`exd$Z Yh5U63zҲvg_!ךO6ߗ0(W4Pԝko:ҮOX]ORk@-3#<}F3`ZF㴕 \}zXVg[.d_'gw9#2^.s9%e%ťeE%,P-%^}5[OħiUJ )&IT|؀x~EifM<񿝎O"5[* J!~}f5!U)E)П OJ%ګ@CiA~<ڿt๳3\p?V*;hOTK'aƣ#4x9QwIM)\oٽ㪟;ǟ&#mNDfjFu)eABP *2eoua}.BQD(ۅ]GY!x1:Xu<V@zT{OTM$:`NI.k/\rZ) ~O5VC*>{HNP cu@?x$Ħ+R*-w0:z/!p0 HpWav?i_<'c 6?)0gָi-Kv^}'6D~ʭC*l07Be;J+H$i%ii[('ڸ>XoZ ='syMfdgE\N/l(SQad2i w:! |t}QD{k4w8^K%c`D`}Dkli`T_ mlr/1LpyGd^ʠ5 <'0{ eAL*EķtlX6S6}D̯S՞2{XȈjy@CVFD]n^_FR B 7<#}"l' VnyO6AHNyI1rN[҇=pI/<[8!†2%%Sgd2P9mc1FG|>jD'9&s15Rslo0&s1"eԗK I~Ԛn=F5قdO$$?GHK]'C/OȂz.N]?֟^MQ̲ClڨDKa3zrQ}2߷A!SHnk/s"_L>V$yG4vzd,IKr޲?s5{:Z /7,f{ *WxwW ܿoAO!G~S*U0m ]!)6easŊzgHx:ӴrqGvZ]ǿ?s?Rtx뇟;|.+'zhw233ѼvS<3Krzԏnyd?vأi_4^eATD)K_P QNNٰ?4=Oi;emTL}@ԐʥuԚgSwVTm*iR5%@TxE8A[rm ߏhfoT{z2~:@ѡ^[" >2kG+iFEE5HA٥ӳ o} 5Vc[Qd(*D wÖ5 @ i1`+{*:CiU[–GOV@ @~7^_T* )/-)e2SDyw~zivoʌL=#ZV}nTEckU)=x~n?8ԑg%78>Q1o?Rit$ zUkDSHHȧϛ( 05/-Ҫv7Ru~4?QY3l|='sCK!6Ϫ8U*ʓ.guq^'8] i솿듲e"Qaݗ\T[ZoChx^_@N.I*jӄ3 &1>pfI?D| QtT/]/|=5oMGfl(1qU *9< ¨\/F`cE,!ɅweםX68eh.4#91zJdqEZDai\|hsb{02SXKZeܼnOK~BZuZ?e:M6_sIVٟL$LFA°+ORi%9&vvɯ RHY7N&c|-FD]-1xӭU[IhM°))0GgB(ygK-OSWW<\׀St)9pB͗.^7P5;:,%kOOʖD(,/'y9 2"Bd{mںd|v ,jcVn_=MOjeCZJˑKou\{Ŀ_`kJ{#7~Ry^ !*)F]##/Nx};kL=߉Ӟ1iRn=S8ULՄtz9$Y_gSPsP2Oop0F[Fmv1v@?Ys'eٓ?*i-?} OX=vݖ=JXr=]o2QȒ¢ R(^j 0Lg`gG>e{UKhR g3|UAE>nAn'BʇKOs_Jz{;ߊH>=S,;:'Dd̲a3Z;q.C{T]ޖibJ͜Y)#GϹmxG;9]! 2pZZ_l)~f̡Shvn1X\e&:z_-i i\vK-O ňSK%ڱꔮgH Syx}DzA%z*]7'%S;u /$PN 2 5%z.[d# ྉ?dSޤhlH2-5oY勞ҵ'!^bwZ3m5aXCCqKy+-?$,VmwӘrT @/u^ߩ-;[AuLw+c8Q/į0f&\ϖ謍B崍FcёYּMM{WE#ہ[@h7>vn{L2 aj;iˤ8i7=!8#:f{홠a("B( 󼂺O0dD},RXgΗc[\?dO>lia1c 1B "'.o4?FYz)fX. ))k^u]|@X{Ǘ Meoqai/yweY$ʟSWֈѮgwGٟN?r1w8D% 9l= 6]7m;ShUEwt0Մ+ EqrOA:Lyw? )L IT;oSVqA+@VhMafU7\k7}*2zLW%c'/v<Փ7; V"o[?vA];}sLp <8xYlAq?-)\!fx>=_}i7]B+!ךO6ߗ0(Wba@+vݧ8’z-'J cǀn^X&ZXO8<\3 }u%EB H]uCm'o_D';4WQESJ%ɓ;}C53^C%<RTl=OG5%H?k uـuD%;/?J aI L⢒2v?"zNvf>#S> OٚgX՗?faC4<֑Gǯ^{9ir7i@1)mH:tŴ%gBJEiyKryb4:A'l, 'AS?U+y$IUkD?1pA[i.nW$4 ӳ8Ӊo !FT&ژ\[=Isv\l'%BtE'nSs:{xs..><+"oaUU;UnX$ ^_W,{rR%Wֺ?sKH@'^z/SL8kW[r2H@G{*aZ o: Hܴ_|=9!OFଈ۝ beh:QhQ5aAUw'oI'- r9Xwv C]v-vTA~ i22͐y+}4`#qAByRR '$ʨ17DmD $l:"pͧRXt@ĉq2k~V Cw~ש^OyBʵoSfU6+QH!X,YRuoM@MyX7#jE8p_qa+q8"bN9DMV^l #&2\װFyvCE5 Aćt#'F&t܀EPnXy5:G( =3*) IDATDN?\O!I0"73$#!8+; 1')E*b+p^%+<[\}z[Y8őIo*ޅs°Wq~>?_ 9vgޕ?/B v~Ow|6ۻyH@rKsHQB0Ũ3RW6MM9tN 'Z @)fHfӨHpyqUx\hti'r, I`A|3د=tmi[O+ Nt J<]GhM:K{Sy o2kHvu- :s'؅x\hjV1aval ;nȥ7Oϝ/pHt>i໎*~y$_=O9RziFy/Wa&An%v4[sG-^&Մ8ޔB²" h׭nk߭ XڤYq ϭcwt}ske"wV<RB1osy։wcp{s\+.J|' 2(ਘv?x[ԸAD'%USq?ऱמ#o/"mo'}Y:"{ʌ /PyI Xf{;V$ܢV~2[  7)Q)[ 'dfAdd;:pC -]]ݨi:(zz͢7buLk7܃Ucټ7ECv%v/ ]CfWq7_h@ B"uE+ȳyKo^ 1;1B'@g2;iLa @ ˫n#l2*d# [pD@  ;)h 4|sk<rBhgh@ @ HGD@ GD@ GD@ GDϿ 4,0 @~{S.UmQg~p[2{g( {9@e֞'s{@ "6Ϫ81xU'-E\al5DW 2Tmjϼ^O^LTO2ݤ$A³TY'ECUَ`N/,?%"#RJ إE,"J *pT"r0"VWwD/Tr,SN Q/,}UwWf1a:^Zڏ 3_$baciSy%i%9&vvɯ  hEJerdq2U5s>7(&[N]pd`u':%e} i CW;/'mfgiB]g8$`mmN?4ZGgmdZՀSt8_O[CI-`3T{Nh2j0ub> i%E80z^+9,GZ?Ƅ)>m)nd ]P Gߤ5Em+kJ{Q+{=uba Ų`{f}]#kWFyNIrO¡SژWOSeTvt!w -a;mL15w e)]G;:`)p ̉dK2A%z]Wn'-H]f+ɣ(\4qx"wZuu;\h3O#I䪥4O TsOSL$}!QdR{Q,>ir{rN;iˤ8aA @[,w5N۬&nbe,'* gl UG͌N]%zYf>2kر\Uy.o4?FYU {RLTu1[^}%o I8a%H?gm|]]CѮgiV'g_Q{ ʳ>_6s.l2"n-2fs)vni1{^w;KS.G1qDVl=1 ~V Ҳ`εVy7O~g2x, k'Kaw+"i8.tU2vzsZmG_=iY~sY Yhܒb!$I5rbG>Myyۤ)tg6WDAxʃǟG_2INx?TLLgn7i%oN-?PG5%H?k uـuD%;œBdDR庢|3x5Gy&h Oq%[ya)[3bnRtҒ@U'-0#?&c$"]ϢRϴ+tU?˫=k~.U#lț]$A jl=B)7=/gotA m %eo<|pi 9 >.-PQ6ѭ;f#)}T )ahXYKZEK307v6WkRG6Z |^x#6\u7NU kz"-qx/(Tm<%ͤ贪ցuϓ3VZU5"׵z$>aY隭H5 %"nR1Gf{A"9b;CЪdoL:@U͜ {<~o.__zj zL|aUmLA`o\62w87;91g/tm<n˓V0yx¦2Q5ټy`эڮJ^ݰPI&jo/6ZXWL ;dSDذuxU.PeYQyJFk"0zԏ|p_ڑ"D~͞l=Z(ˈH,|*2  |RBۇ#.\s'WO.]߷3)-}F im˗V3#2p" ŴH{}sv =髎~7C83x>)MTuԲM6w~xxbupyy!J=Lzqsv) ޞYVtQvwk%uX's}ljr}eFT75̾S'WNlsm;f 麍ֆ:c v͍vwJ'tfW >OP) ۇ)D]tQK1W#OL{@ dSDPIQhv3: g֦' Pa 76 mR Ez0tH0^K\IŪح:\Ed~x`Kd ,BG\'1@'ʼn%$1(fA?L@MM{WE#N۾}p镖VeD!7?Łv4wLÄ˷YptTm66EGfҰhO4K)qɑ 3w56# EeMFH6+g/l\dcbC4zB%/E8N0O#;~|=c=yz^O쥘Q(s9 O=k8iӶY׷. vF#:yV?/vPgdwzKY/%.wQ4mݽ;T,?DEn8v=8%3XPp`m Țo~fWl4UFCGZ:*{9*}yMJ)sDr 8b.Py,jݶa絗TvG5A\!$H-RAJ"&i^ro /<$ rOfS 5v&Vޝr,bk'/!7+3@-ER zMFYw;\ADznA^஑=wCNo[y9Fm%8Kf]z?x90AG"z GiҾo,vz{*D( Fw 1h9ཽ6@+bPˌ`J4IDYr!Q.*h5Ytjث-?#86ϽQncAy9uJ@YFt!+Sf)[|WP;N,BXy`5h֣uGӜ=Kp. (*h2j _-)Κ$ίSkO"Y V fP𺽆2D*P~o?=Gu z1t7UM=#@nBOEQڝeE L`̴ֽz{)[IHIX Z=sR}KKi:0&X=/, AoUfZ_Q)YitamPq$ѕV]r?.ÉbV˕2)'NIMeNw|;#NqLS^pW+-63[ Э_Sc-3up=z3JfʰxQy^B8ƣE /|V'xtpeS2B =T(7""x`g"x)GP q *v[{wK/=ċ3"_޸Gpd.1 &8Kjֵw/W6Uよ2!L2Bm4vEW,>Dvucw]ċ3>^rPpsi2iiko NN(%2[QKwd={l;tF_-{ tfxgi:ɴZ/w)]$e:/p? mc/諾pƁbϞu:$ѕŌt*4~VK&xRLha¤yE8JLP:)R*#JϓT1:eWiߘQ.v2O=n_M}M moZiR_YrBQIdT!( jʺ./M*--- @ cHHHM[(.}9 msijR.:Ad0 IDATةֈO;$!)j|Q@ @j"@ @ /pE@ @^@ D`}VS` F.v]~͋{7 kU""I5g *#خ /ݾ#澱uHPSt~zsbY'}f]$>I/# \՟tZ9sXGV6g{WYIBwc6>L(wUWID Zx۷ݷ_=)S.w!j{M=I5RrgM@5ɂ+"D4r@~cIpBfia捵 6-j Dr`inch!\i$ \UZ(MlmǮVˏH^Dh ۤ}Y>ݰBİ1v:;g߼Y"^ O-4znхh?v]-\uቾs7<%`< z١ xE .$t2N:X-U+7Jg uJI1bǏ%k8M1=XpI5U pC[5ֱnUI]$$eh &6r 3"?:8q{~fk tIS'<ox_ycMN͞)gJhT^w#}dRyIrQgH*ZGwՋH zpt&OW yi^~'N E݋`FEQǁ}E[v20~Jǹ^o 3/\k'8du]mdw¹"zR<ՆV;EgҤ%g eSnb]{#?,ԸŘN^rf9g9hCkû^uDyΓJyAIf~x!.<;X -Q9"*0"KDCĿ.v=>;Wf H0NA' w&N<6,W|\@ 2PZGyo OIE,C!Y%IHN|M{TFIx!;%n0hś9 =/cq(԰ɐ6\p}1Uٔ OA 뿩wML^>3&\rq>t#G~z9`DWi|iuTRDޕN5:u{ l%)dS0kjCmbb-t12;%t>ڲhuJWReeDWpJslIOKw~IvU>YՐ%!'A^_$$<<%!> 8W/ Y%cאKyҢÄ!9\ Z/aR@iއ?! 'į' 9$ K^0d~dqƣ] C啐,N-P[B8Ɔ7u)&Y|iųeW"0#!!GP(!^;ϹN&V7uo^[yl݊O;Ԛ7sL |:Ti3c 8!7>N!(zZd&_d$=B(<Xo| HCnX| lCKWa.c2DU:5zw7M7nQ)i|*UB5X1*!9Af{]ũLm*>Eɡ2 k4d6|65{nb;* ~`o;}{4dDYF09\++ {!F͵`hF>q?8CBA vu {q4GDŽDYxhZkOߕ3#Sz?1!Qv}4HH:XVn9ƔlWK$I@rRf:eհdiRlzvL8۽>2P%Ti{p[=[vɞ堌懈y[,?Xi۔H3 gؐ(Z?5R^ީ.e 62gJ$c{M>^s+ sXֽYF`v<N?e}4CkFkX&umG_'篫btE֝~:T}!q#6rw%[UԩA]RK?D~ 42?WcHB=rК@b$xE L jX Ye0/&7Eo4":Y}Tˮa$?ҋHYЉ *fhlYs*k֨V3;JtRD:/%G)l >NiGQea7 :oXBԡmka"Dr2N:(8l{.9!7QbWLVu"HT;&Z}V^Mj1?/S{n=R72}6̼}pV:I!R8'9#D}39#j@>r1#@ %I~Pm-Ѩ Q]ǎz^h3Nk5͚wlF,ح]ì}._<ئl<QFXF];^F n%; @.KL(X :'ޛ96dKBJiwpM2+ćpu,~{ݓOC&72GS(Cx_I)_f 4 m=8#$I0NA' x➧ЩA]HP_*~/#ډoK뤃yO+ b$MX 'Qs nC]nYUWHՄ5̿k[ Su!5:#̺)f\e?twnw=Mݸ~S\|}4nbcs4DOylS*3C(&8߄(A( w5I/0˳?b~ٻw|?Tx҂{{W۠a-r$N٠:%PTeP;!KRbT<}'mu}+r1F@ 1A`K˾Sn6ȥA%q_;%x1nש 12|+Y:nUKE|`$mt/ϻdϛ q~F͟Bjx8Ǽ!2%?c<%-$+J1 oo<{Ye['E* bgY / %l2?dL;PeSrehiiU @ ۜQV@b{*[ˏUY]t*u8O.ٵ:s&(K8 __䑨qΘ8G WT6@)bqz0:Q=*20A#zJQ*XMvx~zU,[^M F7v/~%^.ތAXm`ΰ ++81;C30j 2lJ*Q;ZjЀ^[/74Qd*8F)Cl4oC _ [D %X]3/%rd/ubhu  F='9x(Vع3'>̥ʸ*:q'/OcE͝::iݱ/y9A:|:u~\0Pxȹe>Uԉ跛09ϡ"MgEtʆ +0۶U_R7%QS2t3t][G#عlS3 NzbU^=jFܻaRáGh:|N~1`.2S[<Gw2CAFvqg1,FuO,&뛣)mSN,&g'! Ը0<>id lfkJ5<8_έBb V%&uIA}렍ߓk6O&䏮lX:wG}=B#f?|'~xxB5Of]tiWVΗ{:im廸nU>S2.tbꦽTqI ՂNjC"$baNon4z'zfÅPyđMAڏ> 'kuЅR%|x}({S48Ͷ_z}Иfhp"u,ˑ Pdf3]ڒ n}4շ~=;nG*sA1ACdkjI1^VTwfj쭶c%ˇٌs~hw\{2￳]{3&]#6u06z$ ?MzoCKE ]<><ƴU{Z, vM Y^ōzFt9I/] &7N<†ʅoG|"/)9+2;Ŏӽ<==Ag7ld1 + ӑg\-j0vSB锴(uR$s>\{bl1Uar#7)3nxͼGAju :ܐ3,*mҥ9I<1*޲]Z:.]ZnZ%;dhj 9¯lٷM Z9[`U2TY:DmeC߈E "1,"E502j%~/Nf}D $T#T-"kW/E~*@4mܦJ" IDAT]7ƹ=zk@xdMX%E}X_;'7GSWmR@W:匮dQ/fK;{0@)O~zʪvdN~l{UY2v(J'ei&x+f-2EƫbX>Է"|;IlrJȜ 흦*2>3k~qY~N[La Č\{*٧v(SRTTRZ).aqCArQ K߱u(BFmvvsN}w[8#҈_C1,%I9g }1fhݙuVˡ=xSF":9NR4qEGYBԐ) ) :FW&XN5Ow@FI2AYprꔄyTckkڪ@4Nii;ٰsz+hzj2H]Vfʯ`1{}A,!"rh#dTĴ*<$P %q.'+yFCxv @ Lu_Sjiƃ|V˪/+62apE@ :rpE@ Q:oGi#$U]V D~JL2GߚSąx@ (a @ X')OԸNvXAvejhkm )7ԩT(N2wQ$]:Oj@TPoDH~k_;)!hT1թT;@ q@a~ZyQy$!~Hft>Q JNEC *jmf[/|/gmShŅeI$;+_F+U>UПu[S_P#coK fӢ=,o})qٔ!bT QqVV3]7ϯ8Q7Ɔ3cJ!Tۺͨ86] )7/Ru51tC™OⴜGT"-'wFfݭ,߭kjgŢuvze=e5=xHȁ5mW?!4]W.Ymu[uP#֯UHVqarXn=Siqm>k!>̳Yۤ~zU\-eDjhT[ĨibccNEC  eʭ)C7#mMlyw\=5Fb~؆ ׶^w%y`$FVǎ7oŸܰ>+V)HZ4{֬Yfx5$#"m2aYfߋLM9 G^ٲjmHI(BвijM- 8RU'/bʳ`M<1"Τk=Kc2*2S~I=܋snd)ȴ.d)`*zqZ"B2S+ğ"/A mU"(>Ȓߗ6}mX'i$ďk[vʇs!Y$|gt=S4$ S\Rx"rK\a$)C hd[ϓn n i2^}=V |xʨ) DúĮkݹ[7)Y0|KkOW}0&;˶FYԦVD9`ijkd)nA utjS"I^>|y~=\ .N 6>1j3vﳾ_r@B 4Э# [֡$ݲd1Pʂ1t2D\yU#LiҿCɃ˞'i6:sp_˴(z`~wm׽06ZmQTDHv*xکBJ@ CpHibIDA1$*1ɶ5P;%ʻ5&PUٛ{ۄRzj;44we=]VgӦaob򹜌 LQyYi9\ԩT Spx@ bvmtR/,0PǴoA )!!!WKڌyή Ol|~j_o!m.htD&<{^~-h78k0!%uݺO0FB*h̠<]}[":\~P)^-Ҷj9J[T-[&!:c{~]WD{jK5x$ѰSs] OF>dS'S5r&4EH R~ k4`$ɑ߳5GjkbHٲaok= ߜyL`ЇX/r&F-'8~l5󆷶665bg?w]:!n<*]{VPZV`U'cT!: o޽{4C~D;IˑNܹk '#S)b'2B͝8o+uI1tp-<̺莚\4G)[z͝\t (vQ{ZܹKl/ rgPGR 5ha-=s3!.BiU $pYx{T6}#-8Mߙݪ˲ڎ,fCշ)u|Gkg=>￳]t[߿aX3Δ\Hd0cv, #V ɘ,ҭUWJ#(4ۍ7e-+=^Gr"nl\hn-b2bDۻ7n>GPkއpkF gN}sv4E_N BDkCEx/T.$@ˡsRQca7,_xѢEK H\'GڵQŸ =Oq}d PeBtGo@,o&gWNV''^_*O}7YsT9tr`Ӫ5J̦{D7|RȬ=o! ʪS΄fA>ԃYZ`HGf=m-"! Q$ 9N@rR}8qNnYc-$4r( CDždaЅmQ\Pƍ4'y땦Vz[e|wۄ7;u-uԺm%$&hX:xߥkq_nii J<ۊ:'Q,)IR˹]؀[vH1HB#Ixܧ7aIy*_GqplE!+ꨕו[MXiH&:Qgw'K=6xKF$T:{D _ "z-yw wv4idb}.8=_ܬf>/ ٬o'YSGv;" Afgf3%"~l E^>G7(MB$oB@M[3ۡgzeGyr^`J'N(CRtGze+{D]|!eQN@Z< -{8<ߗ$H }P&Þ|a 욜[g@i?!zCV gm c֝ cSa#L=tʙLR!C j@DP%)eH, xYjs<-Zy0*T ^j6r"o}<=J!(}|>Ж::]Z_n\9h?fXSߝʢcF3EY"Vn%8SI9R#nv?}Ÿ8 Bs!ȏ7~'irD@,\oHdTVIJh4biW}^d1Pe :%#uwڶ8AY*}Oz̳ #Y="DTL*EM5yZ oN9RI"tDY0PZGy j bi ;V|Gi:+d Vga3}^.YQZTP$b5,#i?|n8jS\@ ??˔P3!.BJ@'}x9a*~}z+zTNLA,ZYp5"3dLPS"ekS=8P 6J=5GO)p~e0R~^uʙLR!C amuKD}k--WRPzj<5@ '/EWjm<@  @ ߋDQ}SKK-<;=-YXhi} f´R*S΄f)@ $Rߚ2>}ca>7ocFxHP ?~ɕ|MuʙLR!C Jo~/5@ \vrL8L B KpBi*3!.BRGnkn饇´ww ]]9su?I` IDAT\# kiCF&Vw#~fjaҙ}͸qv[{c跉Kf#]7  H]8|ZsrCBѫdԦC/ 2qZ×ssBQEu`¢Gc~2(Z= ؞Ad~!R_ܯE,z6J-MZ]`^m`9䏢CdE=[CV@EQ@W8ӭ}ϔw~VZ0Yg7g_0n~SJ'?RL_f||i)s};>^9L7+q2ZwC79Ԯ:]7Sk0uo4(N&TˢY=UYf]}xA,O \ NW rщS4mqBAwnA[5pI. "tHy5'7"D} 9|2NYS9q.lfc% HuʙLR!C 5؏ز9 $7iG ~9%9}ٯ$g\8xl;ƛ-*4_ƍ?lBؠ4MiѱzM=R@'sD?lvв)\.خˮY߈mqP'I^ɚ`. :.XIH;͡M;mDۑ=@[ dT30M48~M/;C'GG;5It^F&t'WHHrێSsؚm / Ak`65,U'yYz{'o9hCc۷~^|x_G-D!&uX-/wn9Ӥ{49bܵqmVt/eQ1M@пkxoq2Ut2^D%TSMVI䐚ExT4HGTf?rq@?<9Jr\jWEr&4ED6 ~^rYi/%0bmR@7&,jj~"@jph(G~K؎#Z=eVqRcOv r_3Ổ+ 즃<\q+S>Nw}eH 2lzu`"vpI|vCpe=;{Ji/o 0g |-5 *w`Egw7e+C!B't$4Sw=YNE!JBI!ͦ}ǖlwn_'<̼ӻu-gz`OP3&7}Z#=ƌL7 tr=@-II9: ij޹rw"ԑ +tz-2)\$H  8|}XjJV ԢSG;ȭj _~#~ӡ_F"2p$Dwj^pdFp#mDFF.;vB"CEM>jULDy6yگ{7ix"uU[\֮?q[KxQ t-9kMxJRjs7Oi%+Xgf?}E>dۋ*:pjq譽:EN"pҪ|MQ9?8mpdWidO}l:77'ӔITB*rє[GdtS??C %\{xi C4$q>N!Ba(?X߭||"#xd$NMH3N@3=yԍ3UW>!ܹ#f@W^8p5eK;=uN!$hJ3{ZĒ:uLsC="Ggq*=.#DȖz+ 5,#|AKS`Ωeݎ5`imt3M>"Y7>7|Q,zhh~+8MLb&يL?BWƋU[\bJIR.$ jH)&HR+;㣕'A4|N! $Qv{9Q4Jip5-F?9חo]dxKhCLg٥_]P|EF2ScI]?87~~!]ǯYA1eKL\8Js^\A255l~;Q\5v>& \FAig{c[{-D: 41!d+B{<S L!BgLݯUx7?tѾ"琙K yqV~`ұU˃_ށ/>"Tv;AI]z{ޔ~.zewHN8(dx_sEyк#y5t2!!yFcq&/J\AARR} ]SV4~\sLZlj^ K{fPe$]SV?A;uma'K_a>&-#8[S i9b|4z$k"+έ_Md6*.}4eÿVG*QKuC-SkQ:oΫ lk>"(rpMrB.ۚ^*Φxci3ɚ2X `?nxl}- uT}DvE ^J?s^d $4% !) ,*>:x-)vқ~a07-Lř~S4>˦M Qe#%BgY;O]1vľ˕F4  :-a!f誸u%89"}F̜Ք~1X9\5!|`WkܬB+@mȹji-H"QkD}j&o1YC M?JvX`0(jdV%a0V]iTJmvjZ59 `LDmhKc0 `0L߂{D `0 kN^vtksSpvs-M2b!.DF?G/ `0 \gӒLTk7scјXp9i~rƚga0 c"F5Fq@Z'eK" 9%3h]}%]wyﶭSo-aW_BuNpܦO߰_l'Ǡ!}=9"|'=#|deWUyckDtsᄏ?}Ҟp?,굣{ֿX^?*&q]uUeėXaOw_y !&=8@я ݑnI/xxǪ3_xU`"dsӛI),8vB爩 fŇ8uNY:qk62Nc#;Iw3117kƅ/0-~6t>kHhDEƒKNqqY#P8~ݧ;zֶM6(5'SvT|ཷv&̻,湟t.6/{~P**ץ䜿SK#G=>>kNӖnٟ絑^c~2*_2Z6;]ᘖ?>>2Uzܳhj]F-_;vC>2ķ'i7tF1="F{Sty]'NNe\ssMb8mNc+="fM2g l:tB6#=}ݗϞ<&?sAΉGB,dd6lj&j.G('a`#ЗF_j+:{S/#nЈj6 7ko Ⱦ?eN;,C{]v?^L \O l+-&cnuh& W͘fqKj[p|Z-;&>T534Z'Főܶ;s;מ/3c]/j:x}D!,dϟ;{ߞBc?NǠ ^'P~7kg:Pi?h'hs^}$/'DƉdZA3f搚sjv3lN@W𩆐?ϷZJNwA9kbptnvpK躪Z v1Q {ˣg?_oګX?G‰j44*pwkDv?(Ɖ O:o.pIv9Ey9t䔜B,f8uDM#55lZ \c1FyAa`y ]_>f=! `FB =zpӜXq@WfkN9'\&ٌgiI31N}eD%aQM]ncFTu6lNީ#C3$%9b};,5l8F̘}8{ [! -8F52%2iWkgw `dݭ]]X-UM4P,455{&N3R& Ь:ck4 xa#R@-Kߝ'Tv;ﻐ<%?)Rl9tƒ)9U*riiTnj(/+~P(l"iurwL^ IDAT|QSԄWzj8vp71WwLPAU_|kkGNF$EC44Y@HksG1N6l~FA 81TFMmY1?~w~ⴡVV:gpQ1n#L@ =Yw6g|rckg4zd>_}vv+>yBhqEQS?71p#yMkX-4 Zg8-#WA"MTz6i8񇡛_`wAc 6Hq'v}CʃN#l_"Tn8퍮9 _lz&;6a܆PוN@; /i5Pu=-kmKZn8z#5lV\#m\;=A r Wqa TC gcg9Pi)yg;Ndi8DShADCjo9ܓMQ٭>@xqEytcO'"T!+-ܡck?g77~{zd6c'9fBw9;mCw3DgIpVΪ ) !)i?:oCR & XT^}Gň/MVks0;y",BvavS`ZXI4hi(16BWUfz~cz8́i^3Xa{2-֥N"2 yrc:Z* t{KS}gB,8ubd `0Hz27\5`l jnWa0 c":oC^`0 `,a0 `0}DUttvQхd::B,Bag5T]_͑9`0 1γCbnA!Lc}(b!q .fNYs qg͵Vf9ih [ȿ.f&JP mD 䮸5'/%}=5}}>{mJ8_y>9ڰ{]/(T*o]tؔU#*~sCq8scU%Wv\,6G(.IS8S7?Q B3A&==NoZMs1(9ݪlг*vm}e'osw,@&=4Ρ_E&eޞr̦V&b΂;5R f G֊n[g&,UdM+bQeyebO,Bn!Կ7I(.PsNqqY# Q٥My[td?\tǽD;Bg믏*x'o+Zr![S6ujMm˟R] -N_tYOΉR%i_!샧&7ew@ؤ ɟSvSL8S*+<{LJ[()2 JT%OwKr3&Ns `7sA.vj2!g\czvL⎟70}*e^u=ֿ5+2 <qO%:CHv)jǩϑL?b9Z58"FIFXނ;!S7"GV϶xhtW\tc[#'6,5I]mwV})) i;X-e]9j9rd_kF8@@7PQгN~[՜)S?$ i |^\s-r;g,}j AB/4)tFD2~_GNK;#k v>#f/>Tt BTo h94.ζt;o,qc߷#yk_NA~Y6Q|hCSܯUKDpށL}H#N9*!8eP%xΥJvqwC~B{2mZV8lN]i4@l" vxÙ,{YH8`%WKM+r*drnB@S4X%DV?u[K92QGw~d`?ksm 8{cET[M NCKҾK]oRC@7;. ?ܛ6o]}LK C<͍@=,Cd~HV1 ^>0ۣG'=4NQ:A$ q9oMafF[*IԜ {tD%?G88)jDPu|5ٷJP&i}λi+ M,['S\ CfYziX*=" 7AB@S4{SS3NJ{&5Q T69{D!#g2.6 bMs뾕XBwi*̾8m IR$t+_MrcUd:o rTtm9R\MKQD&zϏ?B!4EU##2̴F~. niF^u&A0rͱk>"XƲ*#uw(`”+:AM@SY*#/=}ߝv r@';ym' o!-ZNcE@ >\{!ʂ;yҿ Msd ]F|t8^jZ!&O:dr) rJ!K,~t?\gL5~n~e}EΡ3LɮRXQt {gC/x@F7 sBf/9X4YöGO'Wl~N3R& >"h94.ie-{D vV݆*"ɚj~2Ga8b+rP*{t|@Eup %dTpݼ !fC;JzY]HD8㴢;yҿ Msd &_Ҵ ,Mӷ2egWB,O9R7-m!oYŵTyXpC7^}W>ժ9Έ!6Hq'v}Cj%d>j̓^wyqMT Wf>Jy(2@3gܓf=Ιj,#Vi2TWSEs5\L7J:is|SD_ٵr* سOCFm^ja g ډ4GނkXH#BYs"g>Ιn,Q"N,8[wYkcP٬3-G~[dq[E6fB/X4i X0AǤ/g<_`.9LTJ  ]lktt5a,"k#6ee.3@O]=KztU[9(n,tI}.N؉X;)=?:oCR & XT^}GW0dh0OA[0,zDJ;llt X*+gH$fj>0"m$ tZ,6]} 4)%ZQ_ko5 V`B;) !9C!oLWWD k ߅z1@y;a+g+vb0Me2ljx܀Z5`0yy8`0 `(G`0 `\OԷww2-9tps2-R  Y 1 `0p5GN҉jX+l`yYs9 `̆Qgu6u֒t t4uBMX/ӏ fОoz`PBC&D;Mew.^SIJBR_[ؽNSzmMD'F8զH S@B8E{4AuF4V6cScΆgϒhyoga0 |܀gq|CN1 d=S,s6aY' :oCγtp o,!:3jC*9iKܣ: ="bB5<"bB:1>t6-~:@t͟~},4>H1jGpVFTkW_~f#*IH;NoP;O,dt"S؟ ܍&=sVsS?TQui E,:*®Aƒ%! c̊њVM&>"bq)dh@240XBhuި1r5OGm" bOi`rΟw1呧6@7*F(KVDik۽}sϕJ(_:H)ks3[-N +L ln|[pqnVE[i"؟ l>;?p4\~6d2T&ɤR ( 9eHkq͠ =g.Swݸ3oI>} E\_wJ#N-`Ur/F=C:뙶+GK`:sJ)܏_S{[1>!$_: A!Ҫ7/ ~^eDP=*N`A7V?Gz=')ɹLYũJ 'Yȼ>eGDO O-MQB ;]X!,)Q >]5v2j4{O `0@A^_z̟l Hd9G (\5`0y `0 c`0 ypGEtY \VI Cڂ Yf51 `0}YsP8bUI~daI^}ӞKU 9-z ]fo59g xYs X#Κcb @4.,Bn!5{'s!#o]޻moX۴DRwҳ?GVvU'Gd/~uJY\ Xz{kG}U Fmؽ~N&|R|>w/dfG\x[ JG#/ -;z,1B!R&ECXNO}tX<4%= y\l*wo҄w[x=GOJu+]ɮ҈ n6QԌECjS*旖חsӒsob6Ys̀y) )95ٯtU[olGD,ۅオ;O4eAשoW8r]w?KK?|p2},@K5EC Bb2uɌt95MŖ?X-t[0bʘV8+9]#N(2;9za+T1B7DGKt~a6˟|x㳩6#˦. G+]2y b!zM .=Z>N~ xnۧg}zڊԋU m$3ߗNPLt^|TQd[2jhp5/'nAIJ'J3#㆖FMYTlq ZiC mQkO<#Rտi-(Ӈ8 ?B׊U6sXWsm;id^"gH9#lm9ElVK1INv$ 흝X94V&J u$:#e"S/vG7?Y:5q39g믏*ه{}?u gx1Adr6*W<{q9tFdR~yL_T %AB(2/9˃$ Ȯ, Uv{?[3#җբ܈rӸ)N@F KgM8mut%5WCl-5ukG~w: &Mk~7av !hb i/TOt2qpA.Rk[Zv@YSG&f>LKe  }}* bG~ݹ}w@(zo1u`;T0 H{N prk|_OkYF"dh =L[0%̑r%;p2*8-Fd ,t%lLS][x=tH`l7{3E}D{N%%cucשdX%D=i`hj2C Z{d#sM m>zq3iOߺfBV]]4fTܨQ6ũ[> zc}j1a+珆badž<¥)]J ;Y純Q~V" 0t^MD1$JHt车.t]aNUMuU|,²Tk3"8@q Ww磗[rT*(LFP@n'k'Y75udBl#*2[D*3; 0֧ݔE @*XKb\:u{4:=GFg+wIJ^O~!Q {In^c8!ZڨUzecFp=}S-b& rĴ_:ybrj'QTMT^ZNq̀sM&G{|#z~2y_X~ɮvEɏ3 H]|MU]1BWےceK_mΓz>Iz@d0 6ea_}hQ" :pjq譽:ENV0*_STn|yۡ7e5UZi7l]EiʵP>2)+v ヲ> jK)BHӍ!}trYzs2::V'& %j<ZRQ<߱OZyuYr&|..}%2I#GT0t-bI:6f#c<ӡ_F"Cگ6%|p"r=m0?;Bx2b=>K}D~W:EL{'.RS/gt3M>Q%-J<13%B,d;U!SJx7kЁ+Nk!;J?!|f. dfpUmq#2*i&I*HT5Mh+*ƭ{&{50$ :G񿟶۳ϡ)iw}$nyOvv~h:@0tCg0-Y{zfB᏿+oo"d¿py2]z{ޔ~.zewHNnmHKdWq~Sк#y5t2!!yFc"tόJj:G+(UQv+Hj8tX Q1IhB։[] "#:$qKޥ9ı:li{Q־co={K [V] dh q^DuJ 7-maYi8񇡛_`wAO@y<͞< *Kӈ}A'ѵV萷!\5'~3MO\}q{۪۔ǾVw`?nx9NmzjuX>"-]͝ur! >cM;znx?0pq(EMy Aah}!tކ@3MO(ȬJ\?_4w&b!ŢU@?VwoooDǑN3׬_sK6s;ϨEӤ+H4XlA*))ogBP6*>-7([,fl@7g fZ:oCUsLGcKME ;ZkZX>VoM?JvX`0(Tjj`l&4%NChup`0Vaj`0y `0 c`0 ypGjHWG^ꁅX!9 `0k`019"ۮ@5VdÎ K؉CJPy8m[O0juKLM{(\aت*?7ɖN@ z+vg*eɬp?,굣{ֿX^?*q]M<$sIuW.L *nʽ[/*`Ojb&u2Uw]oDƌ q[{J;y 7o,s%-1qa>"6f=ӄ؅1MgU~cSE5>~KQ- t:˻ԵJ"NJ8t |GϏ8wCzi~&O ?\:1DbyMc4Ys@y) )9L>wغj+Y۶#ҀICk޾QS띙߮6#Jg9{>Y9;ɬ?nT|A;щ'n2/95 w~Q ]$ l“W+oo^gx 戠WB=|7Jh}D)9_<\DbFW9_st"`ܠk* "=U)7hn^Ew [gýWqҒ+9(Ӈ8 Ԟ<˯isYaQ^N sEpʤU)N$BgL$ssjh]pj5ްKo'zD|S?"@D}3@%q;p!:_K 6GXeýE=/}gyY%oKpP 7YhZƒΚ2%11qjvt^ۈJ ]#`c_[XtѸQ`#s 3֧#ȉqH?hʹBs#{JRs.q18DP{QI+ۡǰDÿٳgϞ'Kh]wS!tM?t+7N:fl000?\1蹼vAϐtY˗]XjC! #&Κ0؁4!BB+,:14!p#W Ҁi; ,B.-N `V/'Y9\Dq^ `ӷi% 7>fN|7iF?hHZi0E˚Nq{'~Kic@(@v;F 0t7tpL^mO5X;@d57Nc?6Vss" :e^YA9دlMM!xQp$CS6vH]QHvӘGS_tJ@w8<~a^tCia]trO_Pɕi''~t`iYW3Fe~e2Ee2~BkўYCT'BCK ?F:D<J&S}1Q=>g?/emɪ}DkWğ8èN]}oKn%p5oJS/dޞ7m>wZWj Oi%+%Ou 1s[{ t~.hH*(R9?8mpdWi;D8C{b\b56]~horQoqF-Uyu@Hi3ib8SqJu˗NO[ٯBϸyc(YFyd.UYUK g,gOKJf)m2,q2Di\lڟ҄F7V(4n5wdDg \ A]Z4#iCBs:L}];֬F<wXxb%ķZeS];e!; GDGq$Ei/?yZSEwjh! 缓z7+Biӑ`חoJR@ B$J9/dZ93>_k AciAmrx+1(]^62޼$0*!d8חQq/GXHcYJT{%Ⱦ_V&~3N,8) ܇K2hVaW e\'o))C`x?F:Dη,#mkojhi{GSsgb%4b ]wK橘䧆})yk?fYIKh@HhkG'?sYЊ~.z;y[~h]㑜}"yFCg,b]8":iH/m J|1͒&H_58"hl\0G%I?T5H$8Md$iR\ɆvvCfIsH1*E0(bpwQƪ#mZ'4!,qD8jf Φi B`l& f87L)8Wu),1eɼQڋC}K*rc%&&o8u_K?:}9N;8s^qp@BŪHr7%qQXi$cqc'T,$@{Wvw'N oٙ9a ",q쥱;*>h_?t޻ybM1h0JɷHήI!xյ/?p]h[bC+΄Fު޺Ff= tDX ?{y19yJ\Z2>jhALA^ rZde`in48V}6<"mc<̇3%nI8CsJ SdBti혢xԵ:جu6vCmy`m)WL#r5旜7;|ENaNO;Ny'!StAȱ.Y|%u&޸bMbk|wݽǢ rL4_y y1YyIr1V~Mo%aVOglܤ:t@f-F,3-{[ҁ69$q6Q|0+HRGGg!,nc{`>"b39Jn|lzGF^M?"<%?v }Od TQ^C4smNznzM2gywmadnY uf(J-ݠ:Xm}}B^ڍQkf^7-I(XeṋQHq" ?y&TuF.`5V}>8cjn=u^: )8܄-iSO6N彀 ;pz:8q%ey0ş/J!%ٜnӲ\҅ IDAT?7 v _juɊ8 ЍD<) g6L'9py:8_*2LjPS4T\/9st)|O#1qf<]:9.E=wp4%M*Xυ%=i; 5O1:m!E U+0! ,]v+z|j@o?>rXUϮ@[Ђ|79o~ZfYywk|oɑx5ȕ(JN6U}w"U@UEw\mq;$-0Rb$,05"J 9g}3YɷF6\eLs0Κ>-@ImI |YA0\Rm'FUF:*#ICW㠪djhjaK{9RYvh`0I|:Mi"#t鄌4k9(t#nlvfFRHhG! .Vܘ.4uH"]b>F_pw*gmN ct=\}rRĂ;ޠחhF́w;\wo~v[B9C3\{|XO<ӵkV~luE쨌%IDoQӜTq 1z e}u$ *Xau!/uvCH]*CI&D NcA U=R~`".15RoHDB0Ie-H'te ]e{ͩƾ93ab'pj<1gctPITEF@^BV툖&7kI0{i_1WepH`Z`QI#:Qnj$)c|_qK/xɲ̧Z@!L^v&ɔ`0֤._c1qG#q#w 0ܽ=k/ظa[FmJw!+,)Rm{#_&9޸9p'dcDv"cQYx1CVzҪ!#"pI GJtKr#n#IXW,.zs3ޝ<ѝsGt&1L0i5T#PdG}w%+>,_No]e#S`b'ԁw\ỳ2Q /ݹ)][%[ }5kTFi]dA[kg*IϷ,i&&F+&& F+<)@!b^E׿<-y1Q7r `o/bu,׭ؽAwr8M_tez7lذamv 7.02G1`䭻ֳZa&&)vs*oȻ9cڙ촒#ܡ6$&uZ*EWb9+SfnE,Alm%ql7FI0!ENjF&xtrm}ܼys  ā/~AYp 2 P44;JVpb'ԵqbzcӁE$$x8B |o߾}>9ܮW7~8iY!l  K]5/{KRF;HݤFL٤d :}8`~43^'en{E<k0뒕7ov2jOˋ\fvx]+w*}qvlM9S <+7)V '%7j/Gd7wT{,\MZ+ ?eyO$mfHG]m-oIJ0E"xA oج`|EnQJ&p+z_# Qq(?:> G婢xe'ߐS-n6qt&8 7 :E-: 84{IX&zk:9}>"Iw43;(HKRꤨ Z1qرN#&\j5LN@^lvanRLu׶-|aJ}!`y[ ,'j(DxxMr^xgU!m+H*ݘ+?y̸ͧd@Q?U6$xA"AÏ| p%=̦GD!͠@  9ywEb.v" B0"H;0y6oi5Nش}-}YRI!͠@ " p%ͺp:-n!G@SUsLx9̍?*tQ׽qO_~R,-H24!Ev |mmwoovH) #Jn~çϜ8wp?5 w5_]JVgmPFTCU֣?&OiuV-ʖ4x0?FJ$ C'&L x뤱P餐\eU~}V,\dҊm.+ۼvEr$6=U9E}&0?FJ|ZmZ[~:Aigٶu9I)+7m)MzSOC(t6V\ga1sdLL/@J7R:df3hC+'˞foT-{j݅YiLX X';^/9]-,r}ͅ+~ Օ)bYTnu%T$DC0wj3$*ss#-'PKVdpgG @xhuIMK#X~d|}9A%K1 Ha_o8pǜSs4^ 9X5"HOvs:M[VY&)uzIYF:)%顶>,)#'Rej{9?͗O>| ɦ wIJםi U$*<53xuW}nEК,z0x%6<-8;Cㇿ滣 xu eDWp*gFvSɧq~.e :Q>2=!]~_o0>"¤-գž<)H!͠N:%GY!O^"R0#IZGyȨE'mc#$jq#!٬)z Ҡu_>e-k7:mжy0DqG:::(G4h(GqQ<KP`Z-qR1x8ΏZ s  *oHg O;+ }o~AYp 2 Pʌ5Uc8;WDd9dYk7ǰBH. u^g C: q ?huQ=! =?6>ҳ~zgN1mb~3oo._:*?~g>,嵝V<9+>q䯿J }6\}CIY~7wCOn߰93F`kzvǎ׸?.̗CҬA :}{mqR6= yLKN47ukn~اc4s]ۛsa+$^7qr6HsDƠMȏpsGyOs7FeG 98'&1ѝ%µ1#q% V.>R]ava5FLIٔ:ԧo{GDUp:ffvEIJr),2kMh:ޢ́H>447upgz<}Pw5 $։ΎȉAisR:df3hC Ĵ~e I N%X#B )!D-M kЪ9@ (c8 Gy`M̳ kH=M ^y!hD@ @ /hD@ @ /hD@ `,ֹt{⋅F 53\ckj5svBl-Oi̚İ =g̾ `b.ArEzqhAQ\FNtx_#cPEdEcSËw5 gƋqOqp`PM]=>/fEayoz5P~#}w™oթ1"|Z;xֱnw఍.%}kB l͍JIxk0yI~$ՕC2-9F6 >^Yy,C/:$46PW5z)qp(Ltv}ort<9|S~Ӳ 랯(lϝdS4)&= X@,1PPXi& "AkKn}ʔػs߻c;vᄂ4=mk8W Qw*(K&L+aҌ8)ˢl_ @moWY&<^; D{X)lV+bCj wuʘ AIDATm@@F+eXrqHXM o,qBV`tx&\$ Mˎ%c]LJ!`6n|w`*S-& Xj uuSVe0f(ye;:6mrOH&޺3&¤@M:`qw@c6>80H{GQMQ(0O57 2HH:8G pYrJ&d<|$`֤;?]Y"?qr];9bҿ9YDŭ}5ۋ%ӟOjn7f }jq&!b~H@S]]]aʹ[϶~еڹsΝNwd}sGٛ#zs8Ta4$?몽w]?o6ܼ쩫H[7y:VsK TѼUY8Pr5J>P$ӽSȸhJ8CY bgs\._UR?گ% F:[',<-;EvFgdδ욮U5]:L8D-2vCdeem02>#K%mU-vH]wMeAImR,`MO j,04 iyKGHmψH-8D+ZOV5 MJ1k}vlbe2\0B ϮQҚkWp-Ǩ?{OӰEN@ Kb_!p-2 E靲z5dql8H)l0@ LY4?yNavCHd#l&\ SU/06 i1.I6ū 1IJ1 ΋\ SS66eS@zwx,I#B CHNN=}V_s_ //վ߇$$>ya.z?<̞O.G~hHdWS) TT$8>5A1 9k$)s z}f!jr<cylHFkiQw5{oE^6+\5S# O \4UZ,$H&扡1&K (8KfΪny"! ㉄f')RG g-bGk?M;(7Ζ Fe8u܌ .)3 ItF@:T_&!"@8 @vzz? a!C }N!'_xe Vё޸sx',H@jO=N.=+≱% HX͚`L'GHQtRF xwHg'/U*oXuDEl^ҙ_`G7+1.2>U"T}Ig}!Nb,^iWO/|!CzyAb"oU̴:PXJVz|@z@PYrjj;q W}ɨJcs+/Iwijy_y7IDNP/vo;jh!l@ O,̳ kH=M:8;f^1stN3y  ⸦e+~'گSk^Z*XY71>|98몈@ s\5g6G ||**ın.Z@#"@ \d:i@Vzб2Lp#IEww-BM4"B @ &NT-+8B~=SW|"ijWz6(&}D@ bipIENDB`bashtop-0.9.25/Imgs/menu.png000066400000000000000000003313221370527161500156330ustar00rootroot00000000000000PNG  IHDR[q*sBITOtEXtSoftwaremate-screenshotȖJ IDATxiCLۆFBB]jV[}BɼpA<>A&-qYt]'xHfmexBɣrd#7zD zDvlcLa9V91s.qNKJ8vk~<WSϐQKb3 urr)9EA`^VvM*UvJEϩJ)UQ/Z V~Z"0=mLx!x"=""1̱zs/zD岖*L3l7JKZB7^;r[%EZ/4wiRŰT[%wz]7pܬu=y{Ceڍ0**)ƃ) !*NϏ3 N*"[;yQiBl:).yF2#m' Y|;~C|G{(׈97\UEbD7N_GČr1گg;%rH8͓O퍇ݵc.a;9Kyh?,CD$_YҖD^J6Ux$,C&r4%gXf2Ǽ6[mtJ%1?n~缢'".=lk\H+n23V[QU" if*Ōuϫ́)RҒ/Dă~Woٙn S6TT>`Ǎsx|nb?b͚|LIWJ{O?9娇eאṙ;^z-qqy)]/"cQ_-EFߵ,U`"3rwOO/nY[Oݶ'$g>II$|0Q3R霟v)vSflY}DS߿+b벤LD#mNW<l2 /=W0&A4wt|r'D'MuĨǏ z~ 5M& ]׏|yok7WrYkܻ;w/ۭH^"gg?~n a랟k53p[vāio]`+_ND3s+&>uDč'v|?%g|͊5Zr{j;| IpoԾh^DQyP2=]SνNWm`#굻YH;[A:U7O7mݗ< n: 6ΆEb{߶ kGXV#_f'NSᵜe/>`}^k_u䟍Gkɺ^5xGxGx< 烌4**)O_>xYrw鹼<9iL. qvn(;˰ݏ9j9E!\n63Z}׬|hTUFH]z^#3Q׏!dp9e1yMpoz(}V?S|@/' E3w[N]LO:ޠ?W ]:(YAYr@ O YiH\͠D~?odq"HɎa&egAʅA\D0LxQ0#m' LhyTT*56Gk5 #?vZgxwnV~yU$SB]7>B2)xw+f B*VC%#{CY="76t;=;6p34Ejůߋ$7^[ :C9m@ >^^Nivyr*R(?]∈XCV}XCN%vʥzWG6:ܔi؀}/˽{=ǂI~]xu2_cal؈sȂy9??ˀAG="="="wͫ~ϫZud=e-Cesfw^9a`UUR4B#qG}we'rXI۟};-[Jmaڟ(f^"Mlʅ!RTMv7^6JtPٟoW>K2?$gQ|NO狔ddsvO2IbaNImv3/S2D &&`N'%{QHt?{ɛ8lڸ|dMI}4LgΠ'2= ]3WbL$I$Ib#i=yQ1$3RI80֙#t/j4o5|i, s{ů w#@wI 83_IpoԽFYTʧR9]~|Hpq' \|u#ȩLJ!dkjvQ0j~7WHG^p%`=tnՎx)Qi> I y+__Ũqvޏ9/ܛĈܿu$WS;ƈO=baH$NRW׎ ")Uѣ="7D,VBl|;%3.C6tY)J.-mzVƿhHLJco@D$\bw}7L&NQW4}x^8zH63)`0ofi3s ]xvS gӽ"4MiBj dCV4Of;9e]żŻJ F֫^%܉+l]T1=1zMIeL7 k,"פ\>[qo9o.C3=ʗ ⢧ȥg}45="<Jf0du^ 3)8 :l>5G*F2QVIuIK"Fzǃv5jAF~Ѣ &E߭t$#3;˕kp~4Yp"UQՄ`D煂{'R 2Ro(g2Ț~^]9M{a.Efݍ`b2;tR"D,"ldM^5I$1a+<=l-Pf iC jNT._c)K)JtPL[>Ws+c$Iۛ#(ITI7qHTɚ&9T'v͠S_ ;G̕ ӾTHf:p}aLu;3 G^ ij41Dt&ox8D[*t;("Oۛ"T9)M\ L5yآ}AW0;AH]>sLJ\&KZjXbԬǜƭ^o;DDe=95]T :ˏ=/Ҿ\ȩLJ!dkֽo$iQ0j~7WHGVXޱTp2:5U"{cKƈO=-#@JƈYx-c6CDV0ڻ' qSP̈kvR(^LwP{I÷+ nrp1I y+__Ũqv_nSdڹڱf]|$QGƈ C"vjr;=n8dVIFۊ{9Dz/g2G1S$=n"]bAĢf#apz[Td){]>,:rMy{80#_h~b|'K$ 3qy'.'S1 .-mzVƿhHLJӾ=}w\RIH63)`0{9&i3sb˒=% ۅ+ A'N\AL۰@QUFwgM&K[[o=,t?1KZ ?DDJ*cG^wtM@7|Ǐs'1&ĝŒ*>g3凑yEwNNֳ|ҕN>tUb!A&*)z3a](l]Z=?}]gQ1i,7vq}[sLYB!35-kJ3ﴅW#uzS}[iduFk "uQE{iG/e` _J#SӸ^P9+;E ]=nnپJy-w"k팬W(z?[&oAWa[F^;zJ Q3 &7&2` _)./Et qyV}2y̚x]5Kv1J*p2;˿Nlap)aIw'oB#f27xΛ 7|xF??դ+˥5?G[&2f/ ϧz Q_i?C'sͧw8~M~XvT\[u_5x͚|'ex=" #38˿{ap w܅G~dn7v>?E<F\3W~b^.yWRJ2?k>kSǚO`xͧ {YwOq͚|'ex="0E3\2ԨZg KWe ͡@.}(Y~ڹz-|1gw/Q|hsG<)r!cHe_$5^sPe&rjQp&.de5sfE$~~+IҚW|#Wb-K3GFʗMQVS\=~{(ů! ;]&?Eqdx V|*rκ֥v?,Yazەy39\'~Qv[壊F?Zkl$, 6k9Us#bzǝU§6z~ J9YTP֖vYqW;Ƿo܎iӷY/+5{H擌n옌nLegtn_ߕk yv9/6Z[(א#7uOK/{>œjȪ\afM~ʰ<|{>fR&1q&Atj?;S-|.k^ ۍeb뒖Ez?̏Fk;??WN*hl?6Ƃǿ7=<&>d%Y;yQiBl:h`H-x"kj{j)j^wKSi;dw|6&$ILH$II쎌<2~=goO"CJ8!0_6JW\¬C-q`f-Ҋ۩RNy=""GϮS{{(M=Ry_IڦJ73i$fq0=iDn  CDck : A2)xwHNbX*~!'bQ5y <ppgk/{ߞDlDqk[\dc'X*{ΠhojEAL W۪˩ٟ:9iKMuZBv{cBD$&㓶7g{ѳwCK{vRE:v5DUo|ݷTd1;?Dxw Eez܌O=tO__DRxzuPg7Xxlyds~?G"\gkǫ6Ά2>5x#ި qSz=Q0qyCBBBBBBBB\y-`sMM|#|}iuDUvxo눘fMQ8]ٲ*#NBM|y9 >9D65gS$%5si#W]=" #3lpw=HHHHHHHHmgMIZ|dJZdjǻr+2\{nHOPh^sPe&8Sg.dr v?,Yazەy39L)f`G;7IIdSs&EL+̥EWN/ NtSɅkUJUq0=lF\MĬ|*(j+y$bV~Z"mÒ8mv 98>5yfCdwk'DC>v$$$$$$$$N23̤LbL0~vzF6˚*Sxΰh^&*).i Yި[> `'&*9cxѦfd.-z.ەxUz2)L\.q"%ti;dп'2ʙ'߭)ÈWɼ9gOu/D$.yAȚ>g1&I#1II!6W[.S*G{DD.']9*FsQ<({6L w Vd%siWbdRXi;EjH>*3sb˒=% "ES%&kBDA9IWDuD#V:%S8 L'i~*v>on|XНUgrt2ݷ%EU$&|ή?B!!!!!!!!!@#7d=g-]ylsKW%raQWgڋ](l]Z=/VX}3h7ZsTSS1ƄOqGjh!1-IM+d3kĤy|8XTMvNJ#SIw !!!!!!!!_T uNE4(7W{] 55?tqt犛L6Άu29^ڣy&-Q5y}0k~ל7Fz^gL\//W}{ xM+K{l#{cuD5xGx]G4+mɰʖTqwj:$$$$$$$$`y͹WX+CV?gNěk+bnH22L~"[(nEe:xE$c Y!xLm';-xz&Z^PaU܅^:, &F3IVUnx;FYÜ{?Q8Of\RpW!=UMל&T0=Tm YyO̴a g ߮|̛a&DUVIzDL^+CV ?gRY|<^VmzQɺSL1s;9=nJ9YTPVWn+L}#HfKr;F$E߭t$#3;//;1Z023  Sٹj|.kLa9vy(X຤%dxnvRr7oqZV?+Xš Fx^(q"%Pҝ~-p$dRke3OdM[m/ugn&GD^#%H &&#˨5y}ϚcL$F#"b$1!1 !!!!!!!!CƁMml2c\H+ntbļ࣋gWQ\&nxb7|V y(\p'J4M& ǿML w]ߙDck : |cHf:I^ KRp7D,&o=";AH]N(p=>pHHHHHHHHȗ+o ݎTD\l E;#"brR&.u9j2#2miBf+_w3NqV/ag"D*}O_'[!fWS+JAt{)dtה"2)RRD,{DܛĈܿpHHHHHHHHȭkZy1"f|/^}ܞw7^7 w/۟qCMe4j' ߮|*X8O9?w5s A=b|D"65?oA~>Kqo86adogkϯhMTM^i'WQ0z>`^χ|y[ljn_q<_&o_c3x]+#¬9="e:"YiSeN}WʈP!!!!!!!!!_^Nkm3MW":#L`1; wۃ|QvΫ]|)="L. 5꽖R7ob`!.}(Y~ʷ0g^O}ΫK+2\{nHOPh^sPe&8Sg.dr ѝv?,Yazەy39\%9mҨ[}[dSUsr2W)g~v11+ JڊFmOw5szp'EM1PÒ8mv 98>5yfCdwk'DC>v$$$$$$$$N23̤LbL0~vzF6˚*Sxΰh^&*).i Yި[>xnAf""qRtܮ]o>h[o3|Jf3Ro(g2Ț~^;4KDA&+9n|H &&~Div:)B"6&YsIHbDDL$&$F$$$$$$$$v8a-Pf ikzQiļ࣋gWQ\&nxb8.0^a7p=QڵjC3y\X*8LTA}SNLx8$"t/g)lr"U7 $.'ᏇN8$$$$$$$$˕7!PT HUy k[\dc'X*{Πh=fJ9[WV Um'>!#7]7]G |N q?Y쁌&qddFD$&㓶֞*I&uFk "uQw7_>۩Qbw:Q0$$$$$$$$vc=t{霟ۿػY4~o XԼy_r}\R9gdd猉<<\gkǫ6ΆTk/>5xkz#p Q3 &.|HHHHHHHHȗyEMMuduO{-۝sJЮa2wӬ2 'þ+[vReI鐐//'5#L`1; wۃ|Qv[gnd=e-CzhsG8;Bҥ%oW;g KWe ͡6&Jf\RpW!=UMל&T0=Tm YyAty]KV?^vc g/mI2=wN}E;YiH8W="f?nzQf^R&AbbV>i㍼LdiӷY/+57wH6&#[;& IF^gv !!!!!!!!_^vba&egAs3\ּD[%%-!ugڍČ`b2mSr;Eqy_M|>7iј~fATEUFFmLYVKgLV2*XLYp&f]z&YsIHbDDL$&$F$$$$$$$$v8aMf ikzQiļ࣋gWQ\&nxb7\3RI}7Vj0J6Ux썩Sڵj]řYV)DJNbX*~!'bQ5y B"rx8DCBBBBBBBB\y[EU(Pp TEB~X&w]NLjI)D}ͺ7GK|EHZjXbԬǜYTʧR9]~|(F&GU0J8'"%fwsD/&.1A(>r+݇ܕ1{[Ƙa SNG͟njBQy9.@ y+__Ũqvdvl{ND#I3ʈOcDdwӹn"Ee{/n AĈ"5Ř$k&Tv&*9Ԍ3rwOO/3[^(v!onӳ2ECgRj~&XUjD4&B81',iكo߿)Y"R4Ub)D;q1MO4'dx?S"2{g/i-ޭzo L6^]7oKHL]$CBBBBBBBBn Goiz6Z22J,3h7D[%EZϴ5Pٺzz^r=;"pA.7뱦fsbL=?y8r~MDb<>iʷJjvChٳwVH>&|DoG%ߩ$JGŻd/*f|ҭv9s~zob"ԫ}=l  /=M9639?xLw3327UEETHMTM^̚_5zw8ިz>$$$$$$$$׼WJMM;S |·_G^%hׁ0k~A~Y^:w#%gkL175+'TFdweNIsNBMwהvg--,|f֌wl|~>՟q̳{OQ_K=A.W^="Y/m'ae&l\KNw DP)B)ϖ[[5oMEقo)qlU݆:6pй_v="L. 5꽖IK>,q\x_(vl㓶XW0g^O}tS 2D6sBƐֿz j2-k11"a(n> ֑LT0=~H>'B)ϖ[[XFgD42#`ʶՐGKO5o|(=՟q?JA29s5gZ3fdPHٲ.*~.#}v?,Yazەy39@KrzQ 7uη J_pdi?I\MĬ|*(j+y>}͛m#3Ò8mv 98>5y  XK.d IF^ښ=ggvLF~vM̿P32E1T׹ez&vCqmXYC߉~5dGir̥NM/qOZFX7gZ%Rz;,#b߰m| .; FR1f zLp'.]Yl.Cf]PF r#epx.)xcQz/Rgg{s(Yě%-ZX҂Gpw=E-JQy"Oۛ"T9)M\ L5qU2miB檅GꯦbW>zCDO^SBD׬`Y\3q63ط 5gv䩺ƈO=G3ʈ;8x(>Mm?wX9aqvHew%\="/r~)mMwAQWǯ?j;}& Y<:ƟΛgfٌ^:J~*p/-y[c}:{A*<]xwkӗ*>]sP3!ߥnM#Ix{4F9n۷B!B <"<0ZzBԊE.x#9D֋E:>qtGc0C!^.|6Bh|++gz"1꧖yӔiO穝 y%\t"ЄpB!BB!BB!ByD!Bk~}r䏂d@-j1{YGSߥ⹑C'vP ݌D ZI@w8$c#Q.Itpٺ9yr"BWݿ o8 6/45aOm/A>5ˉ4cuW6c1rrg!B"k][j$ ]zanZq/+do I 5N˴{< _ J%@bb7zbWC⢳|0N󄂢Q:6h-,)#54d k0vR'R]m_ (!ET'͞ ۭ2l!XsK:MMS|O rkbPKMZbn YpPyD`]>7F\n@<2SLK9em2vo Pl.B|N/'@3w:y,$'#B!ШT@e34t2"m]J'6@eG4s׊V˓^He R2XjyNB!BbJ׮]瓙lU74yW6Еj>.i6J!"5]\p%(^L@Շ𫏑avZl,{p׮ݜd@+j:i;YM%PkTuPPv0B3۬l&sDi$tu<"B!F!W ɽZrU.T)Top%_IO*ֻ|="PmZ,M'\}mvs?P59IwQ#&NpNlch,'OG!B!./#B!Bg*bQ#K:JXT h Y/U@jiͮa@}:c}B!Y0¡E[-W>6_}t7cBԴ&=o"DZ?-"2]4gS;V0J:MWބ'җxGH[3=r:zjԼ ӉByD!B [D!B [D!B [D!B [D!B [D!B ߾K_1Njb.ndKi5sS5%'n:BB"}C{/v:/1gsXhKN>t^[[[gBc:2k9̘h>⚒>/9,yܸq㬓|Bh|VJF}t"PY!B!tya!B!tya!B!tya!B!tya!B!tya!B!ty۷BpiC.?|ω#zn&c|Z"B!-"B!-"B!-"B!-"B!-"B!oF /zы: Y}{s6@hDxg@֙!trNVg]s҂i}c?'N3`!4c-7nU:BgifUEcsҲi}c??C/Xe?Gh~!4;fB!-"B!-"B!-"B!-"B!-"B!5۷Yl1mV<ؓ6/+&m~e>~EDgEf2$`t98͹rIU$ߣSsSshxcB!BB!BB!BsvLaCc#B!/gww2 _[Rz'Nܱ͘j;~EcB gMe**1 <^bDl,{Ip{00uӟfzR6ܡ;t>ck36'Dj[.){{O Hu*Bg8!//73]-DJ#+JN+'Vڊ=)pdFqDӦyQQT,N7~Aa@/:x+r.h fEƂhAfBX?<*gv81|%cj{E޷d !d@Q-IuU$޾nŨi:l$`4Vk[!Ԫh R+Ry.lZc"5p14V8LzVlE4Di~ǐ.φpfF{Gt6'j:OtFǓF dZj֍$XY^NzHzW},1ϗfQʽ[}ßҮ@a|WZ5T/3[彂jMUѱ>eՁw&?qɒ2qջ|5h;:8a?/w2].D@kU|vFq# Nqa/ֱ`j)77"kmmm5hjgX4dY,NA2Y!G;v hTo:,!֣#N(g$X/vkDwC{Tf%`HSLxˤ@Iꕚ`Na,GFp/C~;e\]q _i oyIMQ ouR8UQt+ђDz0Ds{j!x,P ]jh,b?kQ .Zk/:ǚՂ/`T_ds_wBqo89ŷМrA)Zwec5\m8|/]Ѭ<|Xq*M^i:HNEbrArPpE`@O(qJ!ϔe:pƹ3zfkB*'v_}xb:^LՔmCOgK[S {I*֗S9w/ɯD}Bp{lv/ @-u7Q\D͟!TOpZt!JfOi;tKfdd2WFmG49Hp}DM|.ie$+XTۻ-m/y:<2+PL_HT %g. otx˸Y D4KA<өXe!4+z sBө8ZϥǿS{ro\I2c=H1geO ]4og\cׂم0[DrfsRTe%:F``*]ݔjjfkq%BbVj{los 2;e܌cGC^ |@夋efͿYV>INRW`E'Mpn:'Xڿvs {߱5c4Mg\9}~Ff3j!B!ެBJ8#Tw2 ߂Bh4"B!4oƭ!0E4ױ_Wl~iW"R[?' ժjst> #mtдb|Y>࣏> 9䄋kFWr 6.^Eb=:}?I:5o7/>v:qB!BB!BB!Beq6ctWPqV~`oJDT;b'oi1v( y MTg =tER +NuUs&O#^Ro^!]'nN9 }_tC]H)aogxK*agh4yJ~6<͢tfs(qgn5V|YiE9#XO_z:]#~ ̵0<ѕf_c17T,N7~AwWPɥ3U[t3&,`c T F%G' ӮQMxh}g8D Pj{ߢn>ڱx*FՆw:y&y`zr<+$ϴ ] >/G\ zί1{YGSߥ9>:r 7v)sSѪ_R5(0f0bb6{j0wclU82v8(XTfWP]QtE:R9IF8}ϳ3-!n(YX/4AVI6~.󣷈M5~ Z8Bv%Ph.pNc@eNfŬПifۉ{q$|N/,ۺxrFbKz..Z>q|cg~Q!SxvuVoԴu# sV66YҴ8!/'ngzɩX+5 evwjнTdxtr*\ yP,Q(jZQO]JҰy^>g1q/-{ϑy1=S3tRϨQb7;;ie3:F1JIs٣0S]n=$Z6&xy4E1(qRTSj[EvYGO#V lͺQpoL"0vϟ,;L:+7+L2ENcK47_pAh37k&z-<-CurŪ.\pɳ'֨ O۩VԴt # :hJD5N7TFwk ]jhX!&$*ϷVzCUU~[!]빃tk\Q'(zfYfPOV9ݜa:5E1s|9 IDAT9 &;GfVW VInF f2xi 9l BBcτ#K@nҴYBO(qJ!ϔ[.~k ŎĒy#>b]o.s j!Q:6h-,Q ϗcy5]+*~%.1PU6Z |.ߪX9=H3Bj 0ieO pݫַ̠'Qɉ?kcSNQfNε0~1㴈jqTWj]sQxΚTs/p6wmG`9I={kgqPݰ`3rϭ/Eū`ݬEQDzf>7-\}N.wTHqPLရs{2 [ćq{ץ|r/Dע Q5}{ N<9ZtnUΤt\j @G>Y:r]Y^#x2"m'N4 8g+hnڊ7tgt⇔΍LC[%M9xU;T[O7˴V9Y4 &,6ЅG+n޸qƍ{9g'+`P lTT]]*UpZ:SNj uo}ɮ g\Hb2Eex]7̪[U8MU`'vP>=P^7}V;Z⧝ Ns4 ۘ:wQ ɤ x7|39ϙ,;l+|.]l&Sh=._zmy8kWI;Rf:LќR2uU5z*C&VU A ťՍ 9/TAhfk"M zu)&LnԹT oK;* )mED`Zܤu4_%ac.WsٲKU!^]irOY<:ؠ=1tLfxNu1DW>Vv:gvM/*d]30Jk@+{sY&l huQyd3O?qB"*f'97 Ihmb:]d~xYc W3jْ&v߸5e\Z_⛃\;6?|Y eT`iG`~i⏖쮩ZӮUDcq6OH|YU163ڣ87U]o!B!4G0-j4;|q$:B!BhkdB!hk!tP3̏~͟EQ:?a945a:xNlcJf\]q!t<"B!l!B!./l!B!.Q5wԝDh~&hoPVGOX\] 8bb;U3N\֢)ЂhY04E*gSj~7X88RfK V|މOs&u7#x#W"M»BK^CkT t^O B)f ; lY˾b”I[Gnh5uս+v ',5Ѽ,^R2ْq;c` mZ9pŮ^<0/lno߶Z+Vdj8,9/:Lˣi#XgdqFDQ5+&LW6+vG/|+ae'汏X0A{ wy%jz$Bv%59od%,ꉭ#+N&/DC>;>bVڊ ? 6gsME:e9ޢݯRE x9B^F4Dq.=|W9di:Ct E1Vwc(ͫ Yt ie#6)AuE8Mp>x/| B(f_(e~=N*vɀZ,YXA.gEw:,І -ow fWX%Tmt5RZ9#@j9 GcU~ 6mX4F*<&r]IL""hɹsm5zanZq/+do⎆x@ĸnh\O\~ %T׭K5W2V=M~Y֩. PЩސupuENL/t.ngp}¤\ 3#!2I13t튟?id*q7F=Qfo-,,n.tn萉pՈ߳x` #yVM$t)A"q,TEoTϲA&Vwܞ%Ymn(>{S9Dc2.{}==W7}e*ۥvQk؋98Fz{ޣfvx6z*{#^R?ouP^i m{;3o5Ut*)Μ4yc둥hquh-"JtqJR>ykх(Q@-uk=qZFaݳYDI;!,!W7X[#C& jaoAc}dyDAuyD.{n'BF}a |)gF-_9a,iMS;4M$t!EDuI{NS"Q,[cgCf q-K|]&Z'pɹ[m۸nPMr:gbjujj@L=nc豆]p9s/ 9+I:ٛ*nkNR9O8(ꅽljX8DkվSQx"0tmg6 ߶E4tqŨ{W zPRR-]sW+гY mGvxbWC.S4 kNMz{K&^t3cVw@Eݤk1dZs Y)L#]Z)`A캏J=wJ2 uϥKɤ x7|D9=8‹+>M2#:ǔB .yV6T3EswlL젮 F՞b͐$i\g;oGp*)#%(骡0|n~½FKs-nT#:um'TK2@hͩsbkDkyRƤ<^'ZTYn DWd&[ |ޕ ?1lR/+lV֜b1Rh=rusPGKJك{:̦#4m$ՕZ03C/[[[7n4B 7Bͣ{':zn,U B ls@B(ZBh5txBa!BhZD5B֐!e-"B!-"B!-"B!oV@牭EE5#x#W"މVӱ//&~޽Z:ZZHЂhY04E*gS/p=K=Yq@WN|{E]*l0+|mJQ]\:;}_tDWJ!-}W}}O?L{~W7wlG>h~7 _ƍ~39}?kwyx?ծc|rJ!EDlnoT+2@ #,b fTJe/G!:ߦS Wj+ųɋZKIGhtѮ3; @):!//73]-DŠtOm!`/gJ'ѹʊ}ˊrfnoe=좲Rbz*R}PEDV"O(bt-Y탊̊7R=ZN+do'=79od%,ꉭ#+T g{>^_?o5S5_ov|~ǟ?,ol/sh~ͷ1l!N߈-" N},Wd1p@8;VڊmeQO~Zz026jJJ$zii vŠI;(ñ׿)`~ܳ^s+=^g3@{?C`?c}o:GY_V"xNbfc#Q. lbbP|v EڐupN'$]r@RC3|V~mQ`YӞ Ƿ[ >{_#+Koȶ _/}3*|?ȟu w3\`<2ñjY{JAQw4OBM#B /~JMrbKd.-FЀ;u^Zrs7ۻ9ݵ] 8i`s}tڨ,I' a$s~Rž;6`(0o{-/?CC/=^} /ǫ}Ӟ}ݭg^g'4oyx?y? ^LY??<_z۟c,|+^oڧ=iOUO^uz:%o=~foz+>doyKOq[~ c[OɗF?=9Dϸy{(BgDF>#{CAySew2'v5?D\፭0Ffg7jhbP@Q' АeriM z j1[_YXX\0\k#!2dL}ߙ3^g|+_Xϒ֓wD"Ͱqoyyᗿ'ed$~w>r#w{ע[yb_~Pw?9hIlj!!Q:6h-,)SGi2!ެPBPxBļw'MՐ,J' 8gGsާF׽*_#?y4j._YeY^_ Y~={y zn ӭ@/Ezxf; P97g<tmu^#r-.%3A)x=bԓ_{_g~3uk{;}CcKoH@<[[x&]J ݷA|2cpz<4 l^;lvR]>6>ҿ:NzSo$>_?o}=퓯~fh|؂O}ٯK" "2jbg00 Gxus쁵5b󄂢^(O=L3A/N"PKDn Yp1 jao{z>xh'no~?t=Vcs}psz"+urYmeݳD9?Z8dy~єPxSl+2c~K bn}š|#CO/[>p~f;_0 Χ _;ںl5l[wFk1 _#oϷ>!r3w~rG/Ӟr*gx3e|x:7FlQQb N!_]ZGoyAQ/eTs!ZKgU%g zƂ lάՠPO[v*nkQ9/O"Zos',- 0=s6 )W?ʏ!+s\,bY/lf;' !v TMju! ݏ,9fళer}|)1 pvƛQUnԷ7=Wg{ vV ]̕>^CJA-ߗe/}⭟~ w/v[{/>?;à+%旽pEE_oO ,Q0(u=a{o~G>+_X?_9iQsw dg7h;rwOYJ!"51]\C#R .]Cktpz{H{g+?3b =ޫo(ՕZn?޷>7~{~OIVu2Jv-}6W6T빃tbȜVJ&@FO!VHWnsx׮]87^>}ُs>}՟u G  ?|~TH|Sx͛>鱥y~_W8wIϯl2J IDAT=/VYo'e6ngg,#wv_keM#w'7cdgzf?hI:nw~ںqƀ_ecB@s3-Bhzn,mB!-"B!-"B!-"B!-"B!-"B!-"B!5ZY/JKg+$+wy! 4Yی(vM{^ -yK Q-yKc"U laeff TWbo-2[#^R?+G]^{jy~DYp;fCBpv63܌\3+ff獬]Z>q'c ItZKݹ:RMNk+>vLkut2.ڕ|vcl';modE+W<`bt-Y탊nm(嫡q"B#{7۟r|+ae{ xm7Q,I8W`ey1j?wӪvt$1oQ_ /̞Q{],Ţ~VN6\!6r:h]bz*R}ps>q5dbP]Q4yf#ɪ赘T+csH濲1R/eR}YvZ]l6wr%d6 ] .e6d\Ƀ4ya_c`,"UrLE1 ڢ!еmdvvscyA Xುi%RJgTWjڂmp$~ \4|s Nu!{Ec17T,N7~A,z4YRMR< y0^3jmT׽N >x/| B<7ls-|W  8r餖H} 䚮fg JvvTuZM޺qͻ".FW:ELWRzvF\ ǼAy#+a/_O޽]ݫO ʒdpVXML.4uX P)E_k:(۴0d3XZ޿}k;- B$5 d+n Z?{Ml]\ U*AN kbDp89.IeD=p̩mR^TnYcQ&.tQ\Q0+z o936Ғ{г!jA'9שRH}%N2EO=DaC0p@+Jt$A5]## ol@m, 6jM4^]JC֩.5tpr<v_,T[Mvd leaaussKjL0?v1*8C׮!uQd͆፮7<G^qƛ} {meY!rR,GeF=78j%v"Wxc+r=wΞ ] ǯwb7H@FUUmRM cQ@;P+*:!jA5ӂtd8OjD ?@zhN1pV1AuV8Lekss o:LKEDaT>!rZ |.jRB=Nw@qwZnh;xO j1V=!qqY>4kY,7 ghkJ' ip;Nz%d:7it4Xw1Nr*]kw'[c'OI>)YyA:~ʣajA5Ę8O((#zmɒbBbsE(%]m@@yOJ8ol= 4T'Y\30z!1,UJ587'neUWR7B FlNTjȺ7d\Cd"%ÃQ+-ǓQ @:G4Gr6թ T3}@t.V.Qt 9]`*Rכ۽'Oiưwyi:~X;6`ዞ nkPP Ei&s%ˑ|$ۼP%SK. SCx!={E/V54zZ7FZ ۠&pTU: nB.ͨ9:e9|^f5zE +5m;B]p@ Y]p9<0p3#o'ϑD].'Tm:xi*ZHlRȫ q+?*>F$Qb\]]NVuG4 W eb/.)]JG7v5q՞5jίG#^;zW&zDM/X.ťKb:!rs}N8syqS:\] 2}'Mپ黴j"%CaS6k!e I|r-{@6M5'[B|϶5P2*)ܿr'krZ7􇓲џF>)xoLj!@l'hd:%L#RlŚp p %3[t_?횄>>~_!8DŽ1n yUO(#F9X'JVuYa@_m @ RLͯŞ3"Ź\2_P wW}@Ǭ`9XIr nhƒTmºMvĜ9T! ^\و?Ty84"J] t]w3vsbRFOrD&JBE2"dʗzJz;ϝY;)ڝVRD<%jn<{w"~K E҆ h7vFf00!>N#CXDH"!咊mWսW5qL!?!@k^ I-WC0~/e#>e8<,$LʤEb+%iϹ%k\R 1#2=G!3lNq+[V rqzL蜒)U^:rXL!^b®149*Zk70=J t0LJ9C@| NGۖK'ǹM箬F>œP\6u|N5J1IZzl}5]n%v֥F)"sgk8""mL  D8KNHs"'j BW=jZ& fձ@c ؝B)Ugte\^vnJ^E,MTl?GڌѽmufXIqG1GO;#6'yitښ&2.-2sQC <;8D v&鏘pfw_f|nA5x0ZPa5|ð {zK~۵ Q~SY \׾ d?ql,Nzh-~nkt#²{-U gK;e-E(\m }9;J6yצ3 f:ե\LgdBziR}cY7"r!4?IIюհphI[73D`zhgz5Fko/qE}ǐOMT}_?47pg74iUE=zZY$Ƅnثb(7)8Gs%|z@~F*jG9=n-\lXw״$N<^ RBIQ`S4߬alC|C,ZiBG`GlYܧDz}C]0C9Ո\]QRlD`0 =V|Y^6E<aMv<:ElD`0 Ɓ 1!pC <&"|۷wÃ$ΤyW)b#"`0D`nޜ:3v!xh{6{h |5˨c^QrQ.SPS`071 `0 5`0ǂ$5xj:)"6. eӱIa-B{4v `01 p \Vx={r9۵rg@/ 5M3ak-vxv^,fՄk0`ğ^+~ Z\$%=Z=L0{u˗CxsLO'5w:C;@#Z9:co?sj?>)&ΘW,NT?VUovKuVqI?Ue=36ԣ I% oΛ\%WZjWjVVDtzˮmY^ƅ0vːDZ'Àܓw^ы*K"]niidz{r@hŞߵv,%KbZٿ~pv QE 7 /]?.Ux|xHZ-mWVKupVRq<>S;nt_mWԪGBp~;ޏʵ#tuѲɎ#CLZA y Wλ/>Yײ?SOe5DY[Fi&t~Z-%ZZ,jB^E?̇3 J\Kj\}%raEmn7w%RWhR_^V*yUw}v˂$EF.dYGJgxArbP?G 5خ~L`| e:;;H2O|u-UCXLZF \ѢDF:$n3)hfatD@|sl9YL&%SZWoIc|2YmdS<@X/§J+)^/?^. eƳn"vb^69N{8R۹xZs=ƷfMCw> bO;HЃD#̗jXrYYcDeb/.Y+uL˥{T=dO׻lM5ȯ> IDATpKQ H>-)E!يr-~1\EęC qզ7T?{: t{>R?"OR2]W !|2="m)k]s($V^0oz9j'sUp·4ڞV?j9 GnڇJ(ͯ{',=&f%i2@ IA9ytFÀ3ρQQ F~ ?%h|6n~Z c$5 V~,RMb'75jMSͫ~o/(Rvu8XA tGqC8D8˧1ϐٯxrLC(^"`31,) "s@LÜbD-m3+hmψ)RVpKEť9TUˎu3B'??nvͭhݮhrǭ,ij [y6-h;JkwȽۍX$D~VKD)nJ3Jc9g@krR I2YR"!(򺐇zެ8oj"LWHSa& \yO$<8`g:~N\B4w5Ag& Rҙ-ؼ报l!fEYqvU}fs也a;83BM %S4, ,e7F-럪~*z}qxv&th9Ze%:?cYQ 3# S3?CNxx[O R;P|QC`1򳾘R+m7 6&YN~.8Fw=Oc9tx`MlA*xʗzJFWIJU yKx_.!op}Oe5OE\2^v(Y>pO99έ Xqp"/Cl];o#LUM&)]7ͧDt$Q|FMs|FLrP꽣v\ފkh<¥2ic8<@QmLT,TP.CxXKpJDHK*]U[\Ia20:77 oL.ܙ٢Q;Ͻ nLL)bQE̡#g"n#WB|l ]ch7*U'q-˷ GD2LVtS9 o..:=@ ѫ&!OvH:!)2%i)ѳCyq"RxՕr^h<8B {$ cP!16\$CH7HHJ*_uO+eb:YƄ wfzh]U5zQJm/eYi@Y u($K:'b-"iݶۊЗSG؊6od䣌ttWCvfv4irb$Ď%|9]p.5u5}t]I޿9͸Ezqxw 7BS0X~CA3" R*JK]bQ;/*>^VU^qޓ"n=zY|$Ƅnb6ԣUnWW:,Ģjpx`0 ƫǶgeC,9o6"b0 c!1!pC۷c3Ony6㝱=,h`0K`n<˧وh̄ۅ F|^^:v' x\|Q]F}Q)>/_>"t`9؈`0 `_خ9`,$5xj:)"HJ,='fϬ{B{4IE8lD`0e^.=l|c6a׊'fϬ{®5 e`#"ƫat]*>.vUeD8'΢RG:o.=d<TUPNɖ9Uy|uc'eI6K+Ún`w^AXʪ$h^ $KbZٿӷX6a{ȦV3d;"d9ыx??N @HOj׾azY5!ķ'V.Jb`Xeݯ#V<GDH8S5C !Ț,J8%ZVGw7v,/Dl61hyZV>xs,u~.Q3m|o{jV\z_a0 j~T֦gO//;G{wi>& KB>4o}= ݽ9(S?>mX)ZYո. an)zU:)*ժyfFH)fpce2i&t~E"}GvGvR>?!S=O:ILZA CGD)Թik4Gc3!>B;#3I t5]wI6ܡLV;z82vgST̃I@3!|*mץL=} SR 5I#%yF-럪~@C~JHI%yq0tb^D  Ոs!f.ƲEIZR*h_7w-Balw&f^{}h+,}NOj.RN{m'S֧6?l^sRWVqM絲&@tzݲՅR39@ 3a9VHW|0i[tP6e:lDx5,lFB~Z-Ԋ$.ILRrI IpMSI bNF!$z _ʗzJӟdKAѺu.+y] R:lEZ͠}]wߥ"PgC(r= _a./߶<=zʕGѣ'1%Uz\ -M dDPw|z.7vՒ Xii;z ُQG!?M(doLf{lӏYmP]7Ž-^+9_Vݞ1̭%xA?h7aŎ# 1 q"L>khYrI}+8Lac-!`CX[xdR{m˥㜸a]A⇪.y:s:=G$Ȳѫ>L"zБ3ɛ/۸hܮ)R C!{d{vyQ!{OJ\㈈6I„LF'%MOǃ RI΀zLV)n!tBRd|g{m!&YӺ1S?.I? <"a i%I\:` !$dl9+Z^+Iq.]O!ޡ 4UMԎoP|@X7}y/-bH<tG4|Kl5hz x5LFcʠ:;w=Z;ihҜωmZT9[Ss UkjXzF`޷'L84fݻ{8fS7syٹ*yw|=A]G(/OUaY׺)hUK(|B ^Tk{ٽmu<;8D v& i3t?_pӰoֽg~pڱ؝\Lgt)QqZLCzU$6w`#[l6Q7p5/D]ǘeQ0s)v&MdH!'T;(Τٰ Reׯ_[x0f0g>UUxyO𹲼igĮ7̞YZoG=ۺҰp2 `0 k`0 |`!m^,!e/woֽgN ہcz3 [ ]s .J{T*;F{V*מM;FD `0 FD `0 FD `0 ]sK^A^butzsRzFlSwQ=(Jjݟ_=RL0U0|>ykpsQաzqN|z?VFW>88`R;#Q3<#6KYUE,Iք 6{Bm߾Mv{WE2Z+sIP+gDi=W5JdAӲLĸn)1~]OhH^ ч|MFW]Tײ?SOe5DęM3_À kx7:&@JV֥h5. WD4^G:d07{G4$'kX̪ aw>I2.\2_J@V;} (z$eɦxYL`=Ȗٔ" \8?t./;ɜT̃ގJ|sl9YL&%SҹN.ɦ~KdJ%u>0pE3 %2i{4 Ovy// 9R o{ 3/ bΆ>,%˟=]N_ }ޙ>O brq^r;O}ĭۺxra4Q@wq՞ȵxʞgfMi,o9>rZ 6n~Z c$J o~\Iv.zRu]qqIi7Rrv $)gj8q\X6: ^x8N{Z9#8ݫmkBÚ&_Z9-y_NIYzeFBRK q<jMSͫ~/jyqr>BTFCo,_ue8(WgRĐ&l(qkNȬzg`0X6m{byYR!E怘j:RLbV=*?m#bF% JVtz׿ڮZ;[ˆ JS ,ecu?uc]OOŦ_?7@YAGD2vƳæH0.+2 0U%aPۭ"pJ^͈nwiYyF~d05cl>$e/nuYrVEo綃K IDATmG5\Rs\㚋jB*7AU@/Jxb!='Ir nhV\0i7Am g*R:l~PDm+dzez>Y9%z?\$dBhnNɕ\rL!].9I>ul`tX=WAJ"qz׍Oᔞ-cuҽ88>gSrfG/T?DnƍMJ5/#B_||!46Ǚ`<J}akb`=$T$#L&jZi]<|,G^̔ vԩ Ōy_`7 !E,3MOHWK*_uO+eb:Qi'mLpp9JK3TED!E{:x&|' ɪ«6 Ya11MUdz~U-C`w4bBMV9%E44i爐(<&00| K>URjG~C@F>&xotguܣy5"lJbÙ>]&s:]0\U`&I' *g_sm5yK_:n'h!^5]jP㓥y. ѽoOBs0ĺ ߽=S͠`˥B>:*bkV×.wKYp|VBad0t6m!0l&o^KjզuQŎ}?n^7 V+fGgY`wFXDLv&ȵ@._toն1wg)@I]@KqFwP18\wv6v-u" 1Z}LB`0ļ"t yrbw?"n_ݿZW%" zo/vh{茧f&qǭqk͎kLNiByjB 1b7_}ow^M` $.")Fk otkj[>s7CqgS `0  >5q!f`0 ñ {,1 `0ߛ?` SQt m]H}8y|+|F{V*5o%ƣ3 `0  `0  `0 ݬxpJ\ΩC֨s{qT8ܫ!'5k_\vW D)U?QLݠu(U|Vsw+zU"+ѐ(dޟ_(ƋAm]qk魛`wF>gp ?i/u e%؂Y~3tbz8v!YSLщg aB&|Z9#:Njŵnlͽ* F޿} O^ǃWE,_"GqKc+C#!UoN⎉GD)Թ=a4Gc3!>B V[+>QL&tn𧓨K)>Iu5ww] SbRL$9C$  #2/tcb2)M 0,0=қ&A"[*fS5q{i@[̦l_pvD*خ>b2)ktZL'Dx#tFÀXϚZJ³̃~.I'mM)/I|(UVjʙqr$5W-g:ƭ't6"f}u!>LK٤?6z[gr)#xt00fo/.緐3k<#.|Аa|Q1% rZ!?~^ P B~*r`ЧYbYY W+:m݁C[9j_hڬ9?-IZR*h_7&h7~tS]p*g_+NgN7pcZLgeK^J`9sKe3^IvdrDJ:##bΦu~B`7Fiw=ˬָ0bDA:epPǪ Y4, [FWsO(ux,wi-yF,Z!1zYgn/Y@\ Fs*K);OU)Q5k+Ag=Eb{5\lBTrӈ$.f=z8g'%50'3bn9y[!5<Dopk] doLf_\2|6IXdxvx(!}C3U?vX=#JEGq?sgQc#Q9ZcF>q*k,$Y0.^@ܶ<˭|9؇=ʕzJMZW0z<)}BLMsZjƫ׷9wD8=(0:77yvL8aLPK^\NHK*]U[\Ia}MH #%J[ 쪦i G ,d=bz(*Jca/l?7^HHNjrl2,YP{m˫eSHhEy>س" !;&AZ/~GDķ 2qDyv!]*?U<ծıNH̃7 qIi"f(Y>G@x<vWXP9"4WJ4-Do}AD$ f8cTطM1H7ںu|' ɪB<#lI|W4yqb0q:'LjUVBQ{i N s^ ?rOT0 IQxM(<`P$c6;QgwcMZL8C4axsb:Y^2m3ŘR+RM *g_s̥(͙eQI>4 1\Lgd?͈5oBkCen[|)J]rRg"E={%??OPLiV> \#--7B7įof|_fLT Lv&ȵ8Jcud"EQ|۷o\3oތUzd(0w͡޵d0`<K. x >kqC ]v1 x\Fr`0^6"b0 =1\`0^1Kk`0;~T!lom`0 *lD`0 `0/lD`0 `0/lD`0 `0/fޫU.U99Xy*tj]4zWSrNUDߵFcw߾"V^h5GV^D . j\$%=Z=B]a J*ޜ7'IfWՑ嶎Y'RV%G$(^Sv1Jgo@J8k12ѓq0xI5|ˮ4Q~"%W+R`}$/AJ\bfT= uϩ{BF?jUz@D쥈--1 2YmdS<@XxE/jBD؝MpJRɫ2wR'f…u51(O 96,&"X1'w݀`,0?8H<)J!o[?DȚf \n=k_$'kUmZRJgx˛!(gD"H)~:Kni43 R4]˥u?#ÍZ2氽޸ 3/ؠ9O-CFk0Q@dZcUj<וa! i@=2N?-{iz9E-J~9Wo _6U(IeDaREgSs nDf慴ݻ3ApRJ"p{WMN#ZZ./)#qb- tF\T*lyũ|Z9џ_?|=L?צɤ۷oߎ B2xZf+ !8IcY %-k+.SOʙqr$5W-gy?MN MUmZtFAW2 U=-B`q&HRI Gh5h~Fj8o;B*W-~"C@_)/.gZJjl~8E5W-&)VG~ bҶѢ$짟o!/&ˆ47#z9Uqx*}Lq-=;CqCO.X--RrǶlXh9ZEJ`ļd"qml;R۫œIZR*h_7u|33HngӾ|}ѿޠ3~县I{{yf{c_˺~FyO{}?> an@͏j ֦uZv2"h"h )lbeE"!b Sdj.&ز1$Qe?Ϗ9 "yNyG~v!JPy\t磍\(QBZ]=s2 k/2DH ,cѦDki<A U&@;ugKngVK9LYΰD0i7Ai_!!udgB*_.)Y!@H.f=r^2c 2i֜nh%u}V #t y˖*|rtA9TTvaqH3IuBsտ"j0jӌMZ- \2Mk'ϊѓxd:BP3x~zSMloHRDm+dzƳ }'ޡ<*n8D 1%Uz\Ubvg!HֲÁ=Z.}``82qZZ!S,$~d2;B|?x؆JwvY >eW(5^hr8'GrKy< 2EIͼ--]g/==7PI$B a @$ ^ ;eJS[WW##/dFPek[#C,8`Хx3Qy|h =}n&hG*# tO9Dj Ј'Lg^g்ц:דtnH6e]=cgr"bH_m}\۶orrMqSv+jx|9~Qs)F&vǕL&NlV:[acб#ϧ.s4'$k )B<dVaH H.. ̷qU,/gYu ]f <:>ȣ 햃O0_6 袽%5BŒ@OLpqS tڝ^S\`BέmqG3uϿ `@d-^oD"2g'PQ?ܹ)oy}avovkc9Où>asn5"YתTBF?:3kn~{/S懯fllwu!cZ'_S'?=Yc,<0hVWB]0 ~ʭM/ aZyϟsA=jm1,RlT;ph/Gse)uC t{r PH h0J!T Ck륍unϖ̅ޠY <̩qK{"WXװy[峖>9ˎi+K1 Rc|RKN>JW}ytI$lFt6ma7'3-`NOgr*w3Wojhc4`ԭ\aݵp~he8Wִ[ݖEQmk9\мFcAK'm__"CmD8RES>;ay :^O|[6F_z"ۀ#"AYZ3a/2A^ u(ޠ C!  i;?=g{ខw!  ~   !  ~   -oV3$p>dLgߪVCu񦦑K'2(}-iU.?=6±x;妷mT=yv8 0==IO5o9(|(h8brIMyjύ'|tZˆDҳpnJQpɤszz3 5l~',-6`Oxii>vn.*yxA' gGYseE,cFF!_G{v"+*5Mϓ]%d.ȭFDQ8ǃ6>q^ ׿.]-wQyI0?u}(F\|eU?a%Dw,eo}gIFbbF?zVhA~/} r=r`"'̸82 ^d4.̰^.l\^O_p ZмYip,=z 6l;Ȇrۻ{tAg}>6]`kt.+5D͕lyijMd T IDATȆpS(TQ޺fDD =o#ג*卣/yqyYiZ$p]dޠuװ0Q=DQUpRF="B2tCowLɭuY2Dϵ<ȔN1R*>|f"EVDBTX|Jy QiT/UG&A@DA՘ɵcE[ʷR8f`P\lYJR5ř)JZbW-C]2 J??[\>Ik)P ,'sgwjwϢ3Z+7H,_yMh3 ak-\H>)m:Wx; hרzmYPD02kSo|(iDbm)yq5<džT۴"-d1 8YF;gi\Ю]ߴ^J^<^u+7M6#" ]+$r<6#*c̩]}c6N"hNROO65$r$N(:vu.#4) @P}e`Ks6~Ԝi8_U/qt/^%}ՏNs҃qo۞ djyy ZBHC'c܀qαMb1`*_ mYn;|)`No& 9׹mq/\D ߹)DSaL]pFY=򥧗>stF2ټQOdxúT[;]kMo:t;[QTzFMF嚧-$ݦ:G,L}0dQSYKf\CSŽj$3R羝[&+gK Oi"5*B6Y$YXMr7Ґ:T92 d:k.J_,#̳E7d†ݎֻqqye]l'6$6Z,u]ʨ;Q7CB|l=˼9), jוMj5A/},ͫClR6k3<0?4^ek!jK|pl??UZm:; üKuTAyȖfӳQ xo\4?%!$2꛾yIϕ*Z}XsY-36T:6j/`d1UniJ)4|NP-\ /$~KCG:#]F\FjtWA鹢Z0UVzzMt>(\\{Dq6Ry*؛Y`-ygnARݾ򮹩!g&Bb|&!ﵡ]hCӝfBp\ nGg*Y Yd u&A/E]ݯN~ةaX`J~~vm<\LA= !1M I}%szJ.lZ'y`ㄱt蕟%{Aum -ֶct87[61via3/G}MaÈ'r˿ZMGD̷m?)8ANE۝"KSx4sD  ~7 3-{XvfLy3>=$Dt.c"0> O6oA 0ug?lVzR>`8XWDӘ^e3Y`&H0P1BV HQ:nvf3|n7u BݙCJxA9Bd = fLP{~i1AM5 u>? ` ZB鶆Tb2!Sf,y:-M83k\4zVsDOh"knm,u\:~8 v8]]Șa|:9F~>]EsD0n7rt:gC[;z#(kbRlT;TGgؼe^ȧ_ F6=H~_6hN<9&߼ՌV̩pLTvn[Mg fm,3׬Xv @x%s7h?XNO(T=fBk?`vږ3u'6E茄٣dE±N#w\ҧzxqoy;iQk/ YcFOXZLY0췪զI4ϟBH+[wARΣ1S{?hq O\4OssQѝYlrCo|Y esѹQW(FJ8(|(hl٬^i=7x6,x-I|lS>\q^ ׿.]-$1ˊ~JXw?-8e:+߻xUKŔ5Լ$RJEKw7xcNh]ȸ9aIve(\.ۃ%!9>JFt`r>fG #>yz5G'BdT9ݽA_q>5:g}J6Ӊ4D+|Q㦭6.3YٌWDM`- zLN&x TV~tCowLɩ<ХzdEɰ=׊)=] qQUEpv>3\$g7h5+ M=NSI$9BL+Ǭ*7C)F6MJR.e H+ Ӓ ^c(i1^ Ӕ",7Ϳ9oFbOޠuװrg9 =S|vezi( ;BebDskTk=j_E #6f}OOχ9n&hA IΧQϏW~_6T=u#Yg =&rNg8u-Wt1k7ת#Vfs{ɦh>{DDbbՏ3&|n~UixL.{bN{W?:IZDtU{gSMKCdB+U`躔Qw<)92:t)g<')Hf6) 5șvDݐ v;:k.J_,#̳u6:A\tD^Y?%@<@h>9iWm,h|oDqټeGyp\oևdk Wv8B Ϛ>!Y<<[Xe`<#1MpCA>R%l~Op; ܤEx^B D|S`Х:C <` ُhlBxvo]?ѴRB r8;Ɇ\<: 4<0! 3 @,f-iS"~륏9-RM)Bxo 蹬XTo*2RTW@Ts> (#; 6<:!e̸,{zٌ6"=W!ʁjW[ztngK|ltT 1BأDlzA41@fFf DЋdxm`pk0YaAb5JfBp/sqC!teAFNr;:S!ȲieYqK j,&P깬F[5O3*5bpPiO"ae:& mZ'y`C,] &6J5$q6'Mkdl Kbjopi/nBo߷hh Mc |˸& ?Ӝ $8׌a.љ m4h9"o9)蔈e/F[W6 | Uqgp4-#1 H0P1BVe!az}}h.iaSo=mzrqS vR "O}3lG"=.i1AM&7< $k R+R@$E9/@i%mK^MM7 n}4!&ݸH5y%Qj'R O!8#[uC t1a9Nogs$?;QpeލHwld^r+dzA`حV(!oo^jW<s̩pLTv"`oewu!cZ'_Sr!:0d.}IZm9'BL@{}nTrFN}ƣ0{8>H8{XTXv/"hBznu&aC#6lVOβdZJ$t&sFx8˞~$9Ef,٢>Oѯie6iBalpVgܳ䕢(?Ͽ}5.h{##"YuhC:_%%R4닻C:Q񶐇{gG_ Y^=`5AAA Yg׋/m}!;]s#"AAݦ7hԙ96GD  5^xNy =AiZq%|KA   !  ~   YCtZˆDLsiCx~Zm: V/Ìn"UkWw^7H+[w9咚"ӭz|hpQP#""'̸8*=OJx-IFd)k^yҧBE5Ltnǜк*ׇSA6C8ΗU%]*a\fb!nG I,](SRi4h/Gl9"#զ]As) Q!SSY]I$E&煌zdYI&SMcVo8Bc mT+g3yd,-&uǬlƊ+p)@X9F֗_K .H2oкk84ңeaޠuװar2ah*w&H>prnYpFvU-] M[Nq8tbz> 6l2Ύ7)?REnuD/yN%$%yu?Q\Iן.lj DԳ9{'K[/7\*qNNK2xA%x2NԢsߪQ DnM`ʏkC $ҐG#(:N$r$smV(mk)-h-!BSaeҲmiOD:mjb0sSdxlV$:D5#3J'3X[|jӔ}>Rn^ťup2?R(!dfҰYs9M]o[ɓI_m!ZgMK A ׇN>]dIYTU AX'_u)K!eX_ 0WMG$4 7#aQ=yY( =2yGTR,EQxla) qd\z:$8dUa[/GoLO˺(_"Sy4M'" ʫ(p0,"7Wy`$ N(hM>7MԕFd~PV`XϷ?`>WlYCd(j9hbC$=Wʛתk]wH)[Dl9" (|p4ǯ)P2\b0gҐ] q!z.VLë́vpA6׆r$)t ~=~D:UO".&LGxD(ai[#tvH s2D&HDח'G3tvh&33Wo@5^{n6lh;Ul5<>[xI,sT0%uuuoh.rDĂ`i6(|p,Qs>X0t1 czf=r{e,"!zKemcэg˾{ #>fLԨv6o|'gY;*ғE茈0#̩GvѤi* ѡ܍ ס0ޠq_2F-gtE}C03Sڍۻ; Gs/!|-p/=HF(2۷o[A4oiXAIJ'IGG_ Y/=`58EAAG(  kAAAf7_FN)3on3s%AAdխi;!?p   !  ~   !  ~f'BJcI3QuW$JSs/Z>p Mީ.7'Fd{8(|(hpp%5E! <[bdyd3ij`oU͡ibnkuI$9V5SUC[wha 3ue\̙ DWk2n>bf&K?-s8_+^:-j~mRfb[t c5K{`)̰^.l\^}TeU?a%DwƣowQwxcxL~ӻ_reԵIKs6|,m.Gy-l_tҧBE?kM(絰ssQIp "ʁnvʏk]EV$J3pP-n N+tn@‰kLl[C;֛y)g{83eF(Ͼ}{<, \$g7h5:;Q7&"4e@IKeȝ!\=n'a%zFnȊH`ХUUg9yװ-sߪ $[*l8t)GY?`Iy۲Ll}8\ٝ]- : ߛc1+U^ZůA@7h5xQ}>ϱxv9"HZi_3 a q-%z!#e+]p"u @̐`YV]( ӂƺ~W\iFחU;:7rpQ:!VN($TƊ[mL!t1oPԒ.C`Nߵ!/'8f)nU%M!DMe-UZ?9{_w@KJj&F a^ռ1g.~gyGBoHCץ)|ّ8.C10 Zwuy`$ N(hG Vv^'+~jڵ; / '%AҰÄ K}t*\&kXGgڍe(ӯݝh& ]ʨ3 6'Dƍ@~G'B"b+篍05h[31oʷѹ茛zlsd.XIq2~ޟ.HJ8+e@QGwқ 40U4͐lR6k3ǝ!O|=.DĈþdg*"ǑrE7d†5 ǖȥFj+Ϟ}hj)L翌: Bo~no13FS!] /:qNt|p_L<sDTZ뼷"Iϕ*Zwt/ATyUaD}` Ng`QR}lr,qB(O<_L=' ʁj+3~[2,Qjtg9vsºYh[ M,O,\IVTH:'޷j"0nw8";> ݯud(. gz@C-]QV<0q!]X= ,@!3!Q}ފ>=2 ]pΖ9eHLWmǸ' cd˧. ~1>vڋqNr"bHUsun9ODE.He Ko :!sKtga^ULxBR@ɜ˫~>>n&ݵ轃E*F4R2w;tIGc3>B[mj:.wAsWQ$|wť3},5#Chw{sep|豲_LL, #/b@suKAhA> YK(C내 7u BPņ8,?a 4B,etγEE%j^ˤ5`'qpnJ^:;?zd{P:(<'( ج鸫?:ҍ!3Ef GDZHqi;_ߐ5ȕFjC^(V"2љ9)6FptsW3 ~T|&wRq{E wxnQYb~Lh;k!-EP9:f[ !-[/!|57Um L8??ry#Hǂ"%ON1ھݺ%8p2[=/GїFo//ɽpXh rEAAf;J8bkGD  2|oׄyv; @d/!?pD  .37&+Bu&pAAyAAA AAyAAA ެ sld>4\~AݲP+Dҳpn+I>eUھ|<\T}S@4w:2.$|lΜ٬ym%H{ yΎ b_\6A/56yhr%5E! <[7i?Mz F2=V刈 3.NE=O RI—Q;]x5S*ZrнKsB\r(,+S>\gQe.̰^.l\^G?wZ'D5U M| s0Uv{w鹠_q>+6-GnkLKs6|,=59|je"xh2s[8fivP\B>AÖZ_3L+Ǭ*7p8 a ʁsZҔk %-1ƫ!w|fKLQ.cV6c'F֗_K .H2oкk 9=[1cpĽWAb6t6JƲ'Iѭ_^8s7Su|R\Jd.\徭#pг;i9qtZXk~8(~ʻ0qX" =yܦ;} ߢlF2MrD&0[h)|5<džkVi=z~TV׻/5,<,Ѻ(ς9Ջoq(SƬm"? Hʢlk9@mB)Mm[KiAU n(y=N5Sc&b<4ذݴ|\o59Cp?j^,w}Ktzm*YKf\ӂ>$xE @\LqL$JȝU07?ft"tjTt1t Ų0ՑXDyibӐ7Sa|n~Uix~\4J'Y\-fT#(2H'}f|h;YGχ(ɬ%4r1bץ<F /7T=.m;rx|?<&u#=gy`JϚ>!ȫ@ݐ vW/<4 x-9,'~;(2y(Ky! 7$Q')4BH ;y8\ .eRPQG؋:׺o')+-W} d'W8z!S??c^TyRF]B\TU T2ߦlF>lH,.H3d}>&a9RGm6[ly6HsJÎz:3nԷ=#"do9@.fVy\V ~ 7#yA8E`vwR@Ts> VwD-# &XͱJpdY(avu3~[2,gt-iɆ̩$赺:%*duuF q[y4/$__ :~vx>l"$ēѮI87{f.toal _"RV\뺫;dRMvl9" @H0 Q§ ¨QAEGv=hi_fB17nw^6j*y%i Pް;Swp3>1vڋkxDf DЋdxmf-µNXfƠyEf|;`p+coS64 5wIn{m(gOOEy iOH(SIO͸Cb)[<;ʤ _ > QZ/I<@]/Af5ںY/slP)r 3;[m7acڭΪ^%M^ֵq˔  I (<u.Ah|&,?!"tnL1ߧ 8mY'%jxT"!*ojuϵi1A榾3w2=&)2Mϟ#ڦln -*$k \Z@!jKƒH {h Hn^)sD̷-، {ܛ'z} eO?ߗ /65,Q@ е.SSܩJk^EJ=d.n&hÌq|fzf nTrFNZ4x7'CH h7J D_KHBx%s7hƲNAϦb\Эnj=\Vi QScNXԣzx }UH~tfw=r 9f^ƃ 5BƴN*Y |G[tL6qEs']ѳb_BH㬳j}em(o߾q 7ވ {Nٝ#%ON1ھ ;sc _ţlu_o})hfbzK׈A-+Ծ>^ϮE3^ɣlu#"AyMBBW3:yv; @3P/!?pD  Q#W3:&tZk|AAA AAyAAA AAy  N2  _]nzbrIMyj}8mT@yR&3Z:6T'a0췪զ^13YK%yʐMޙi+u+D4;7^&)F$ΕZ8g9 =SMmu1J8% ]&l]cDԳ9{'Kܴt @|k)Au-Wt1k7>64e@IKeȝ[эF=7 ߈qo{&uB!s'$#$DN_D5_Si/99N'( Ѯq٠T8tX3b{EՕd!gApB!N2VjY򚩋N(!A:?̩^|B|:p{dsX0~Jv r,W/dfҰYs~/ϟQo0<0?4^"ڐT*w {Hz7U)׺MaoJk N5t={)}}YǜJd4 WBD|s6ldN%A}s{Hrk̩Vi~8KNwd>Izؒ]Rp(Dquytx!l`d1Un*"Ot13;W^J)4|N*+MsD Lo]]Ec&[Yq3!@8莺gDVc0gҐ] q!^SI0_t/"e Ko :߿yq;Ů|ܩӬ\5z, ѬֹE^@ڎ{((.b(9jk)YwO~_ , )S*ĽzLreᩍbJO5;ZZȖa| \pW߻.s.#̀neх0/OyE7d1A|2wnB anʉIWVS2gۑr틿Bԕ )7BzD4}bE<^+ N :ʹo9b( Rזջox;j?-&3;Qq!]Sk8Qoɧ #&\DƳNRDEfznM6fxիרbqjyF*9 AxLBt bCppتE1\AdkZJc<>f`A G&q u9\#c =O;,ŊHN顪{'%ga!2fޱt8Z 0 IeAKײ.# ̤t`ݲ{zop%~^T!<ke: +d)-w]1Nk<+cT<޿_;_! B9^8YSx OFHb"v{H#,v0"2 x_1ڭGݯ)BN CCϳ%XtZ,bZ.η )燆UB < y?b;Z3n.L-81H})aer1jh:$"VaƩȹI z~{5y5Yj1bok&;@.t2gQ儮&dmoo9{_8jUBv8ES7y0fj1ʼu9+E Ƕ1,F"2 `Z&ز0(, 뇤I" B!777=-t>ȠӳHb""fc@,A3&4ėdG!)s׻)>m@l&yvl`:={m-|;CE* ǘ˿W{0ϸ0I߯2EyW}D@k+vJEz q*r.1YU/7x~"Rmw(RKM{0]O_S \=Q{ [/1,;ݳ 93T̾" g~f( d qi=>.X%t=1bNh̷6d؆ቩ0-a pz.A00ʧB omVt\Xqcib컠07kaγqcI$) 18$a@[ '(nT/^c`F!O܊o+~#"i@$.ls]ը~@kP(Syı.qV~u;3 9oZ%)ˋPhgWq?<-\e 5}q=K) 30_^c Jj fXQsZLft:b0LRzveZ{ y٭V+gznbxY{#w;/OL&7_j>*ίv٨v\Q 4R }~6ƣ^{j[hqrG hI^07e)a.X'z8,alW~9n"Fd$zw,#(z%2[FrΠ\!륙"Q,[dž Y4B,هz|[q 1*$$Hß#}IP(Jִ!yq]egJTP}R([x-[h!2XrC(Fv]q&_.G[Z}ad\1p@+'bb'2r*mlL̀ #FRʧ!aAa1hG 89pز|w9HOݫɫBV"b.!ݿ& tuWM jMĨLV?gc$?o۶ `x+T 9MȜ*ޖOw]љq,GS#,v0"d6yxY2Gh1 7M.]#lq^.p-zr %uA sN^|OoڶG.#m8q;Dc`Ihئ=l.2 AbXNs#gP}R(+ U1, "]8plѰte}SXM&Yb7I& D""f^ 6-@]m#rF\.f?//%TZܷ_pY5WȠW?A>)F\71Yeh]2*7X21w; _AUJ bL&I)<N*7G|m [ zͷ_(۩<7?r'Lj1 Hyo 6_DUeǗeZ< !r[.kD Bxet88<' xY]u pຘ2vDbcϷw*RVAFko''u՚1;[dj[oD[+]drUfǐ%'uvd+,^m56ʝ BKr~o(I`{xAhئ.;: պvnbԟ+L&_%~^vdⱉcqE%XHluiA$dFFP֖!z3ZG|1^|&w)c\m"d@q=$b2n3xN^$I?777+|S(s,5ųoyQ۷)ǷJ ꁑBYEI gux,BP( 2]sAG) k.l`A pMz6BP(dx+~TfudQ3Z?vUOa(a:]Px|]s'Ex;'- LheQuj7Op5prO*nP=:"l><\``x}*2T~?uPPμ#.KW/Il><; A<* `W=0X49=)TQ\[8Vdl}ulVӒS}|ydGAGD-`염i==ܗn>&(۶ */L"I(ςFQ⏒*{ l{oE1"CkZ[0{6&N2R"O" +`$lEv$LGg˨ɸvcJiVNĎttR}s4kJL&Fx1jG)\AdkZJ(EFe ߖ29h>nѢH>F"5%c"fcobqpeĪJq@fÄ(_r"zV^ArZieGr\V"˳03."Ies'd8L+#l!Ex8ˈ3ɸ sY!5iI2 6 0 +|0 =BQ BHNsuW5YVL}wEgh:절RSL9M"o5A,8g+,k0J,1kH#,v0"p +cT<>׻__nIWm;Ql/Xe?6@wU̩q:O7C~ޏ) ջ_w5Smaok&\'v**+Qjki*@R2YAˆ;93A+<@leO1M]o'$?^=6r g^%V`Y79X)(,iY` , sm#n6*5%Ս`V, ̋tpѸ{+T>*>M_6-Lab9e|5=l.FoqK"!eʣAE)O=B<f͊z ĖÄ)\& Dlx'!sTpy(q9˩G" T. >i1YehM6!f[]w#bq+*ǿ\%&3l.Ÿ0&HQRNN} yNĽ.IGViOh{vzL+}-/6(AGD-`X&"pzctt:*:uhe?&(g5L1{y|R0-V"^g.RZS7pBdհ$ 66:^C *fhit-?ٹ;gtjcib컠eaRN{/_pAD+yOQPDUYRHD[;vhG FsD e b躍]I8p]츘0 '<׾ak\YI96-HD$e[EFI" u|~mJa uKjTf"$$oB,HF{s>T08Q|C9 sȌBl0 I,G$0TJ`9|q7ϸ9`>u%Gz~Os=bC^+jZ>V~u:ee{oI9ܦW3Dv ^-- %uÎ7u#scW alT;պrPtz'uŜ2 d+u.O\HនW.w-nˍݧe h]1.r{DDoTbFK\;rG:E>aA4kM (z%2[FkXv{|BgܬBc RR K(+jZ4 c="A㵶nB,هz&ۣ@$Ms}}iS;^P8]SB9 ut<>]s' BP :"P( BP(kHPc 3{U5GnwhQ( BP(;([D⹜i]GLr "v9 BP( eQ(v,ۨ{Nvx_[G|#%r*,xmtZ_*N# _ S/7zyGæ\f"n2H;Jy{Y|R!-hk>pLB,q~Zmps)[Ǿ|gWhߣ L9i R0^^@>[2ױTHBx興PGsʃ"锴Ë;m<DzȰ[8/W,Ӵ08Qo-l]q\{z4t}8kTk=I|>,;جH%yV)/`ݲ{zo^么e -3ı]FIu1M6O2]p_wo% >EbLBx-1{00늋jG)\AdkZJ$Rԅ}?oW`}]L|ʉgZsrP d5CZ̒2XE;ˎ,lֵ =zv&630 3l0 b< McǽF{mXA`eN ޿_;_|h }h`%U5s48k8jUBv8ESUJ7Z9MȜ*ޖs߅,}# ~b. AX$H"_t1WW ,9׻__ ~)/|>S1wj$ 9 h)/_777?<*g"\'XT}*Ȓ(}Yeuo ]#lq^.p-z1ay'f_>uk'o" KL` g߱t>Ƞ2$i14 1He zvqtQCW.WYvo4a ҂h"*gMN`9OqOo_V&J뫶g8 mgY9pm;r<MlZI+`KC\Zk'%K6"*y!"e9xvu!"b ~bn2.J"$s0p+2ϏErn(Zق@z'vZјf=򻜢M>wb&01ϳ+h9;XַfX̒`}G3y=E{Zä%3ى%`u>Yg tDDm>/ޠ2hYf9⢤j>>`08۫?Hr|[/ZךY`ѻCLH<.kE J=!pZᐕ*ǿ\%g6\h a8FV0`Oq#Jʩyp~Jf$~Ipff ɫ`,j֫*X; O5)"w8>EfRFa/":?(D8%!V|A''!qB1l~4k hQbiKe2 "vc蘰vt\&G\8Q<hkH8VG9^ϜDqN3J=ڪouړ%+l4_8xYLj$&Y 1sWx֖E>" xoOYcK+޾&Zx^L؝[dS1!l19Bh5(g5L1{ysMo}7N$cfB5ћ|CQ(o<25 *u.O\H_~ T 4^k[涶H4u`~ݘ}8l&!$I?777+|S(s,5ųoyQ_s8؇as>{)'jQ#$/.~ E->}}z[KDo.&cXh BYxTeY|o!(ҚT]Ђf:|e =h]كvA'r> 4~R(| >%vcx|л( BP(煎( BP(Dv1<lv\ sW A)<uORl-Кߒ} P( Bʈ(D[}0+TN'.M#Z[OZ! BP(Q(mT= %<rs=NFJrIUY\+ A3`ZASM}DCeT7}TK+|)$}4sZ鲨:l6 >eX&"`Sy+_Yaɟ >~~W߻g( /"35lnrRw&[ᡎ~w6>dB^ΞcفUdحO+A7oP$]xQYsRͺhyAuOkZ,906E+}-Æ-|~L/$2v5>oqvf6?&\\}kmQPr"rR,Wk yk?yB driGRxe#".~vr|{e׼i==VS>[bKpo:YVU_vSa>4Cvf{~a&s Pn"iaPxYpfsruV bTnÿbYǑcd\1p@+'bbgդ-(Zybq]L|ʉgZ֗?JQZ"K\{z4t$"3\ۜ1YjC`TVBqMC! (*,iqn(MŶAe˵υh%W* cbQl4Kx#ϰu'/ZL@$DN 43k>rEqYKx9xdv1Lߛ,9#0Y|˂J_ˌ4wRr3of9XQ"캈7l޷_NpQ7β&>L1 db }kckBF qlfR:|SDnYrB=up9l# hBb:0 \}n nbxOaG MCFF{m `r}˽|cWޠ~/8YSx ϬN]QKd1tWUH+pX9 lQpbeu9Nl4{5qVf*組d6ycECU٬9"qtis?'xZ~|4tB1m͎duOd*YK@kZZzѯb>9[\SfSqLA ,WuHNe2k5-^i )V(:h>|ylçAY[ о!8z\k]׆3sMoA*v]ҾS<0}?CvMyblH_]VE0- lVP_A,n,编6 3ewbZ|YM<~]pwDk+vJE<9+k~ah m#áS:d[}T-nd AukfXvg;x;NJ43 zG!,Q[۾fe{hB`6cZoy(I+<lt3㓐}ӎy-mPxlac:hBh dϦ3 3Im{fВNfhJM;wv$hR>٭rkyAc 1 +Vb/Gq][?(ב*ǿ\%g6tˤ#NܴbY%TZX_+ ]\^$'YMGAj0#BHHߌbq^W?Mafwٸ%07tgU›ZqW`Uyls_$8ycd'i6h}X9"bX&"pzct- UdEvb;fȋi/Of\{sCطf rxV'KjT?%J߯.V*# 57y[m MWV"HV2r*( 3Xi6>Lwlj3>WgHh[*=WLB8֫]lˋPhgWq/p|lf\4TDoTbFK\;H(Zߒ𮨄 sSma.b᡾o+,}>9_:.A,Wk# јvzK3E&ݣ 0mqד܇zHp[0-W0H\__Ldcg *Ykg "v|~|0<鮤9ọ,5Ń>\,ŔcM϶b_B>(B2G sѾ[=|2#`5<A BP(3' s}15;)b刈؃vE =h)p>-n;;v|x07q;BP(dx|-!釐:+wQ(S5GP(ʖ,{-BP( B:"P( BP(爐g:j2H[LOB9o+>3طsu#ZIDk:0q7 BP(1+GDL$˩uD. b7Ѩ-}N^[ lwoަahB&u܇|d"-P( BoS(6Gxlx_[oC} — h:<췪f#2FUwL)oqvOnZ9̃SLJ+3X2J)o%I>ƾ 'sME}"0R"KςF^VHв&1XoV>=6+GDPGsʃ"⹶ƷIφr|W|g6>kiK+$>T&>CN;c>KKOiKH|; }8sV: b1%ͧۚ-}-dK`߽}^tyAuOkZ,9Ƞ *:l\=Y;ԇϿbY+UR'isxuY(Hx_-Q '売UE}"HVůd!oOmAҥBA IDAT$_Xp'3^퓹rD*/yzzh/Y|,mKOr|[ɻ6ձ#EarK9K ^K> X"+gQ1'f?-FLBOAZ_,Vܷ1k {k{w8IDm{6D `ڶGcJqL 2q, ZLfiwD,Ӵ$4{7) x-[h!2Xr/W9lSzYt-k?G{%*&_vlKa<м]Eu!ϵJAIvu@9/InYrB=u %F:u1C `7aDLo9> dk!aAa1h/>Ўo sWqZ fx}L!+ӾAmUσͦ1ýF{ER\,BoU-AMr1ߎ@?77?usssy&JB>V~u?U2 x0-:Y؃,#$-&!b:GL]siM\'G.Xx$ ~sCݯ)BNDLג^~~1NGX<? ̘7??W>5.tO %qn|{[n΍pպ&^pH)<G}'"$GXYa $D"q#H5"- {] Z~繷i^r|Kdgq]Hќ`0vtPTs,,iY`kd'6ɲoP TJ+jeadم$wh , g4O!Ԟ^̲,Q9or*˱G2˶ GD6-@] Ƽxn|.0#Uor\_2E&56;mc>܋|%QPxXq;Yݓ"VhB}|:4[t7SRL\Q +wM|,~z:Z鿜JU"ޕErl9"%V;>;#wpl :RSuڵ(mKMN؏|Uݮװ k'jgxU3ھGrbiaPHo}B qA[7ܳJ<ʁ7v緛0JLt" f]p-o Tl3CndTTO'[@Y!.85;bNh̷6d\^]$lP( Duau]HTf##M$e8IݞN쩛p Gfц{o".WXvo~Ƣ,xz7'QHH,G 3Z"E,9ĚdqcP}R(hÊ=xq,šA`Dx/Vd}W3Dv ^t=Bgx}.bs E:"VBAscW alT;.*M鋫,iT\&<`8G4= ^ԛq[/\6ǐjClz&NDJ:KoX},YTjnvv"$>)!Tr==n:w0u%F .e7+爛q=+: I&ξTN|ϾPGHɱQ߾X)Y-EIww'='/Xh ? BP( egwaOGV5gm60xvޙ%s;@Yqfk\fe_y?C EAs I,MxM@:%`0 ƛn8?7c9oZ .l`0 ` `#"`0 }YDTD[kqUDm4l4j=A?KxF幘/)v{tIE zn GkɘK*רuݹ J;Xp*MLC1`0 cuiܺvd.7< 8EN͔d2C31s+nM2Ws9\+M\\T~ui mJ;Xp!W `0x q8)QQ}ݙh'FVv\g2I-"ijr_'n>޼6waH(d<4>r[GeץgxD4[O7.EA9U 9)eт=ǿE=3m݆^q )g kt*1weswKzg-v馇OP$l3R^麍 C2빉}JE/MLh9ZiRez*bcUV]vݤ|9<{GЏĶv@fE=֥q^lg!{M#%Wq-cׯ| 98fn:X2w}ѓuۺ=)ݯߓb{lҳ>l.|xkȹYӨTM].ىl}#]sGO|es9Z%u342%89rx]9ZmQɬV>P ƪe ٚx$y)YTUɐTe٘`u"n-$dT4\ϗyc"PssĪ-[;,O;sC͆&oTӚ̃xKBy=x{0TsN\8\Pft q L<e n "VkQ‘q91ZJgwZܾ6Mp+hX֍,{ 5^_kv`}mtZS$Bx5[1wfs98CЯz8D8u]sJ0piũ^M(1W9ZmMʤc0xBAL AElۮ8$C I<.@v^,?.]{P0dLvq>ON4_J9k9ßA=<8ok)j=F94MDx7l7 GD5;mkt-p>t t@h٘֎YsIes9 /tgmඦenwxbrg|;F=(=rlwRq cjYr->+"QllJ&nZ6{ r(ı,Lec;éls<ω0 @AWZ. @j珤`Z(zbcp["FetYj焦 1)1;O@Y`*wIUWד8M2zTKGJ*n}{ٌAef틞`hQ6-%f0|qf=zsj6$2 [7Zd8^Q^bNn{̶ th".kC07Š*IIzHO"rnвs7n@}4Qt>UŒ')qemحOX.s' n_\OrE{mk@,ɸçx I0|dܻ Y^ #"a J @l&$rHPd~*f|a8/IKxGZLs޶e@"/z< 5=/p+뜞tf'rD\2?Eϰ|ѳ5VAe8>m4m<5Ah\_L>;ͪҘV!Bp`c(\(fxkj},i)Go.Gkz;r䓰T= MȴU}%X1 ɞ"ʁjSlڽK:c(`Qz$lc2L2 N^+\WnʙxO{(1=/e>Nβ]JR!?:Ӈ4!fMȧtȊK$*rDՓQ{ =y'=P~w {A|4+)sӫW9;Yp爛adsټP>z[̩pn>0m6q1%.ɝبN4Pؘ Û\&9 Re󋋋|_[Ixg+-*slYR_\pԘSiBegk11COCJf|-d~B틞 31t;Lo>  7`lN2?EO,>k5lb^",{s]swL7nw"~J;bM7Z{d_ n_d0>Wc9oZ. 3!;k`0 `|_؈`0 `|_Ъ$dDѨ .!v"IKDEiټWEj/R sz.\\ Up6ѵyyּͩnwܛ`0 +pDd-S3e-"z}Q\D4njr2"VcnB-L}zKfx{_zM&3*xR ty7`0 W`|{f4ڵr4Ԅ~-SdABx{VQ.KB𴨹2ŧf"^P4X} adQ?>GMdn0:f ȗnlnb=~Na,LϴKeޔ>|&phV{D_6INek$ē>n 6u#"~ >!u{iLU=`="sҍ#.P>"+ϞKd{x{.Í֜JzS tMԎM !/&1zݥ{<_OAB4s|.)ӊS8ß!8W1^db<#?kjkHAFFrBW9p͵ݮ>V(aG;e1[eҗĖe߄pNmBtzOre~77> (TC _7Lw;5"Bk6(YSZsCcprg>S4>{.>|4#E6Bˇ*Qǩ 5;Zsnڱ =@LS[!*K75C~5XNA;'vEU YF;dY l\ 'tLrLyB(sc#`B= bZ&#l&UD~ΌO4 cqbC|5^k ΃9fVt$&9<=m1K~H<,2rW.9!fVq7*O gBb<[#"G\d5E\Ԅ(2S2%zw[uc_qyܓ(ULRSD/qp0z=a y 5^_$EQR>^S]N8ɜkv)dr.SyʁD&r^oUݾˈ*UtK׏ױ/ yzNq{GuZg>fpbkS] 8ġudq׷칂Ln3Bt\ORȈ$6utZ>1]\\7D q>!w5M3A኉gYuND ض]4}AA=t)lRq4>N*`7tK׏̀5y#7-E\"BZ}.o?aR 2 ><_]}589vpyo?]_ݔmIKr 1rP~{t5Sԇ%Fvi)SnB4q{o7WgNkLGMB..K2+bE#j3  ]]\\\{ -];"6e/In"M|6"nߌ6睸f``%SeZN̪M|TO1N\P<oT]_ E*`6&.1ٟ=ܩujwQJIr$=zN$RTQm.]5r?T6T_DJ]5qsNUEAn2/&]Kx_jI{VFe=֪ZuD{zcli,ɻo^_$v,`']"p#OVl ㈑w7Q@ZwDZsP4@~66?MHtuQlNUt ǻ?/j&f"nn'Œ eud3㌑[ %tbbT_\F&6lpj".1g!h1flA˫`b*kN*Onq%!u*3j`6d z(SrJMG}L1g\. IDAT \F`'A(ݨ;Jw0鉭C#)X}*tX@Ep 䟛wNId2nl9Dqj1po\q|X2r)To'., <@_>c8(Ȳ(iLG"RDxI9=EZ;:HfM`jfP$ċT9[-cv@- c}{ŗF_% M83D4ssZh<]ۨ^)gɓ?IUb_|6eQTZ$23}a|6}G4sCRX!{?@g{%%[mL,s==m8ջ:s^d=Ijag2B&cFڲW;僞<:LԎAԏOQtSzuËevJa@aA$_X.50(5I&#ͲKD{~)n =LیP${KvjşB1A] $QÄf淞nIp5.tK׏ױo+Ű 98nל)$bs=5/vO&~3%8?sW굩?#ec*Mԍgܯ*wxF~(rZJ<@"W U(sjHJ$c;q8q91ZJW)9!϶Ay9k̀H2IFQ2۝CD\Fj懐K|sQ< I~h UVtž}#'},ך 2;PPZ-L.L48}kIq>x^&1]\\NӬ[ XTFx5ʙHDZJb;bq`on*MrqaXX;8Kt1EmpࢉlsAGE a2;dG k?]_ݔmIKr njpr .{2o11XNkz\hҊV8Lޅ2<9g,/.9Y9Q "nz }{i*R&Uk__קlI.W=-%uMWƵV=W)񄂈̱:Xb<SipQcqZPx"`v-A8:-/*v,,nc٘`  ȣA}lZsCC~L)cٟg'X{*  Nosb\R!ci &G8aj,(7QFq09OU2Czm!km.-{I9qf=yJnbWeNĬ8Km' R"]lp[6imOGBg᠘zB|&wbtסJʜꕷhCd`[nVnms!{?>Ko 0Ȟ;[G&>.Vd_vzRb|)s=q|Dtr*`ޓkhF"sݹYBL ;,01j妜'Ob+9J}f=czNU'7a-8VpR=rkNQcE|\S^{,b1P9Z^U\Nl~Fs~~~qq 6qX-'ud"dER'{%siE@n}'c-||wACoFwm"p `0SQKS+y :gFD `0vo۫{7:M6'DFD IL `|_*wc|sƛcb0 `0mFD `0 FD `0 FD `0 NV`0=S[5 桱щ=,ם8)QQ܁K$O[SL:;fQ,EuޜO`6ڵrn섞lpJ2Kiȃ9fZ*⬞֗&ҭMg2I-""옝FڲW%{ou!ǧz)ѯLP=Mj$W?\̏}4{V;dJVu&Vw$ųL\x{ 1_%EzXN-]?_:i[iwa=yGm{ ^db82!^8.h^oъ±߼t0xNz[|\=nZlA1eՊ?SsHɻӮ^Q(+m!Q8k~ލd <^`?@rBW9p9#3jS=9L9]{ۆ5>w53)\QʞɘbM6kcsWaM5nbaϵ ఠ{/[,xwMz?>flD`|yD>彽^GL2:Tʕ<\+Pss, xBmoQDk?_V҈k`nbm>,\yQ@ t63:=/%OIES_9,'sc*(Uy iyW\\ sz:7ERXǷ 2IB\$hϳmdxNZ3t nV L e&/#PeRǏN i?ߖliFqwe+=CWDL[i3fuM^֬MxGD7l6Qy|jlӛ_*l?NK28Hj2![c'6 N©t\OjHKDHlKZK竧@>-]]\\\{ -]$x8(f lD`|e85rB<׺B(D)DӹA_F|z/#pj".1r!9H@{rzZ<8Q4}1 ü.8}=y.LG7Աs1$Y_&G#_=;>Brv)webV_\FFKb n]2,*4S9yF{<=)dڱCYOVwV.zܬNJ6yqf=z] 36"b00B,|HJ|[y|7RLLkWK.}kۃv5G(ߟ4(SC aG ,@w;[P$sT%0+3yPs6830|r"#di!{7i&t%E*O^Vsvx:جx*"&h"X zm@42H%%Gdk`@aZMbJ6y ad\QFS<ǤR>f0{`k=p}p{!^f}gpy86j;-y~RI d*sHo.%fk>0Ȼ \[iV(#3 )K`7j2oEϰx;)%HR# 36M惚9&LSm~=*mNwM p,OF681ò`0>b$ϒN^+</e>Nβ3jU!'O̱hGab'`0 1|6!N`s{MlD`0 .|{;x.VfF=1up~~*̲*1 `0 `0 `0 oF|{_[0l&$s귞nJu]3)4(u7'lR%vFe~$ﻬً.!1v;H|6庹W \Mg*J<ü#)g kG{t؈`閮ɯclMiW s Ѯ_V\0U;cCJ" [F%{|NtQFx-c[BHzw'18*Ufs}7 hHd"%U+6ɜI!@RDOKHVuʼn,da3hlm򎔃ÂWIl=6 36"b04Pl$'k If3pUS+W:&MNqJ2Ok2M囋roy@D$2"V58NKɢ`:4wD?r2;F2\E*2-W 6)ٕc{ݻjyX.JaǷ 2IB\\]'36InQ@! eLZcǏN i?ߖliFqw_ O\DF 5Ý(f+D$< v ȲGIDAT]{)$ƳL<"ru$RTbY6]}ᴔ.SyʁD&r>fpbkS`|AYX;8HKDHlKZK[...t.g"\䠘+mg@\p MD fE{XTRR٤A q>!w5M3|suSh~^0?˿˿'Be<RǷ q|I+)x}v@p[?G1!L5)T7%%8~￴P欞2h25bL2v6f싞Ab>B͛%jB6.B2 k H"xI<Ӥ 5M׸OLD]zZJꔛk$2 f0ljVXiy <ϡ>(xGcNNԙ8Iecε@Jb\R!cf`>P?ϒOƗoԪ]@ҳ)ɪWDN_ %sG4EUmLmc*ılLiaPQ;Ru< @]5We*Lb2 n5' [vG Sus (#J^;(̜V.LHNn`aɅJgu]7-=[9zx#&'0S?&]Kx>f lD`|)..^vA& sM2zTAh&@fxIa^FҶ}@J$]'CSj@kUAmD"Lj$K@|? ۄ`iUr6 ޕY})s]qo./uB<@FLTuHg_<دi2W f=Z_ۥpKm>ޕ9-Vo+}P[ׇ 5GRZ-ko218U̚{>f lD`0]3!M8(gb]{Xr$%d(9*xxx_8'Ce@Aň%sz$EF0)BobM.ydZn1n5g?;6륇: !^<-ȹ ^9 (.RIIB,Z>";X;2+0.rbZ6"@^'Eq^y '*6j8&1a1b& @\跙HR# y087zsݙ]CEĵ'&eA ?.<دO\p:ϗOS.* ?nۨu|HI<|mR{2" I? G <#d- -R)ۄq |Y'q2Wpk9 eDw$%sIFZ@֒W9J;P;B2`]rFzr"OMT}Ӭ:k|+wM p,OF681ò`0xc^Uh"g2GݭU4߄8>K";zr*dɟcwbV_ބ|ZGl|yb>=1Dgm0vVz$쾇@~n^Zڞe"NT љ>|Jʜꕷ6}wГ~#Rž9 VgY RǷvcNs\OQ+7Lv1/\&ɬ۞19j љh.߂N ߳r~DIbo o(KD9V' Bvk!f3S׉g fH(M 7aI}_ZX=(R4;@[> |kߔs\fzCF`0 ƾ/T&=bkn; 1 `0ш`W8fC͉6{A:ϗlo`06n2+|fn#p`g1ߔdU|rgs 5^fm6"b0 `06"b0 `06"b0 FX8e ᥈"`0k5# IrpdL'boTyo7O9 Eu~gIMc׶"9U6-T! i N0[p<à}k\ bn7;<'BzRT}85"b#2 &|;N՗2g~. A4ꌤr= H"@S\Asc鳇C`#^%yu̙.|}-DZbrB}bV1aIr$) ω}Bc|"Eq^6 HtR%1+?;6BD)ҊӾiog&J{x+JDODĒ& w2'>YBx҅?z~9:*Ɇ&wN 5l""\ >ׁ%.FU{$2 f=(&D9W9I*Dϟ~:|h~ *7Ӧ1_d5"b/Eu뭱,"\*2&ҧp ׯwm!7 0OJ1Fŏ_SA0FOxU02-U3۽6N@DsNHqS۞"">30$NVmi|.b6bHBtv2%]9-s_vamӐr̻O{ܬᑸ^}z5e6B|,O|9 Qgͺ%+ 2R``FѼ481ôM&%SyDحݹSR^yDSRO_|>LOW2MeI5t88w)c]N8G,vEU qf;ar#wnv|t/.67ٟͯ#F_~[4@4I 5M&  m4;ڡ[R.:ewVc&3rbΰ껶԰bTss| -nJ+R2Um$v{ ;r"m~_I\$?W#;Aȟggm^QOY;F [ IceHus[$+ssM!8wvGm`#Xy,FD$Žy^׿*Y?FߦQXo?l;l*>__ɏMq9hl3I'K?|9֟5o"` V#z^5{#5`}^1ǯ/FhSZLڡOoooYRX(#ۍj%bJr<ΣÒCS;yˏeril`CUDt 9~xOrjBӹ~PjDs%I=u/,ĨcgV2Gńp,>ܝydW҇GE3]zqt$_WC6G̔Dpcg ;!3=ҙp񢧠JG%~4]|R.)J#00-1T:.g '4(Bc$2=SY *F^LUݰ$HbLL9=dfB&n DD, |#-bx_M|8ȏTZg,Gh{o+oy^)dB!㇧Jb9ˮ B*f]%#="6Tzvln4ipZ6K?}){]oi xy^˹:;ͽۭ9d&Q<)|xûC^ʥYt=DDR<76 W߿^=Hu:)i頜y5g f5\Z2ԼӜ#ڋZL7 өUnrZ*-Xq4WUw l*856!`m 9I*}/Jr]I:?kfhx~눈X,|DLSq54Se˥:w="%IG`0Ydq\ov91eI}cD\ܬ#x|KUӕNͅ3qS<>MJYKJ>i0k,FMфG$;j_7>]դ\>[qwܹj[VyDFn"7v(ѧ{W/x&1"#s.#"":o=hyG|ҭ_tGDuÚMFˆ>snx(n :...i^y>嵘ʉud=eMCeܵf&9oV>W4Y?9S_̷MyvfO%-RjvXP3uUw~xHNʅ!֟EFIfлUR|HgCaKWNK#vqn\$#KiD&I,)R`w}""\R&?#Uq<)1_$* Jښ^Rh5}ฐVv[3˧Rѵk7 IqKrey}"i$&N{%ӷu9jEKyVo$BF~r&lVOɩi1λI>vކ:k|0z&:"6焫L47{/X7 Y%7JL8vʁb~WbL$I$Ib#'o5gd%JSW$@-|xO+CDs=ye0FVa/9F 62=mi:B~0T:Ĥ߳5?Jw.>>DD$<{IxA}aG/j%.FU{̉QB zDo1Ywӓ3?X%;0n$)ĸsR y3ObԸEw!Fgo^^dZP)f{Um7UAG"(aiʌODDzMB߿ ywR>T{ț7Iz:3yqωsneۣ8ZA5;,e4{qq=^O="a(LB&>4M޽hYOT1j^ZTUFsz2{W5)?|dwax8ck6ecqfqJp׶-g _(x{'q^ot{GSj|PeKL \KLnbisID7Kڊsb ru[ޓfNY4}v1NCNOK&#SՉNmNlWϚQ~xTM1Cy ƈGȲ|F#[a ok?nz|?ؼj9="6`Nɠf*2~~)IϘ]-&y#@22L^,[($p~F͔w~쒱`A_G3\4T]{mz&t]gՎn& 26|3UUݸ!Rb.K|ܽF>ɉ\1$S˓#Qҥ.UT8_:ᚑ$3KV^|Op~>|7deݔ\eE|j?rZWu5)sr6דD}A^T[QAaNL8*LЪVE}\kG+$/U>%=<Q2'8##%L0#ILTl>MLڃv9?WwbQ^o|qh 10d"awjû4o5|Z"ɛcDkFxAs' >(4]7u""br\&8q8%x\#;7'"<{×x)]> g|͇g+Ĉ;)YTR%s5)gE"'3IOYg:˲LDb'I$+2OY\O=",3U`r*PݳŒYSc'=!FGD#b/ś2Fl? xo ĭ's5yR|xJo>{k>O.x]Vz :J7($F˫^JKj^QVД#!p:jMVNK#vq>Hd=)ED=#!驃-'>%pۣIOnK%AĈB5E$?kf: []' eXל[D:R0+ ywR>T{'mCf6\锼ۑIN+ͳ)=vۮ'py:-5:"aƜf貤e>dJj|?tޓp& 鱕P!f:)S0էyw-gybA{4:E$ ]܉O='|2ԓ7P.| \C0ƄײHHL{|l5b5u峦̜"{\UܵFs+$iTQf~gZPʥtfpZ-7Qtƣb<;O$ϸ4ӂ-H9{Mew͈9:)&D?ks5#oJTw%sT\v5pżB?Fݣ;s)FoCWs;[ݓq2<k+\sM/v< |1v:k_G3\4԰s-WCҳž2k[C% !ϕ$I4=- n~GiA da؜D\?l[N@ɴ^sSe&sJ,;Um Y~jn !=G9Cn5B+yLK^P+ͤ [R{t{"EwmȒjzG{s<5[`xVqkV5ߎg~a#s&S҇'%3]VT8z<fOT]=ozfb|;nPӝYJF5@91_$* Jڱ#` s5sqvs2w|NtӒ9OaNJrGe/[8F$*#[;*'8#2{Q2 RF ʾ" "+mqCBSnto'} C%W|Z55=ŜaEF}^fHe{~s|.adz?̏Fkٍ__7}N*h~ ;B#(.bÌtF~r&lVkm"0#11Lcw@pDJ, xc$1$ &˶0a8^t;h);h4jC\σM͆/Ҋө7a~3HuMUNɃQJ ޽p'yx*&=+bt? oR倏w6{o(HtR%1j:⊦J4M&m#vzP*|aπ0O {D³;79|řnKR)LFnBテVAӡF;UPT0"OMu[-"",epSB5"IK Y]XVHltA2CD,QHu5Gꏦ/ƇV*_)$"O?eFjYw{{ǟa=QfG݉C>,r5Iϲ|#" S" n"}CPwO׎C/p_d%o1% bFqWf,̚eR)(&T">" b=F#běhGv)i頜 y3ObԸOS7Wb10$ݒ82yKUxzk$=˙θҹ8!B"D!nUlz塨D(t^SNBѨsffOJ^\jZN,}A)upS$kt; 0fZ*MmfƻnHLR/YX#Y^̒!Sǵq̐H|pJ( ɿȓ@N!ј ]/_>L>)*1Y"yO™8UQUFs6c阬i2Q8Mޭ% 6cL}iuIx91;Nh5=yZĄ@H2D!B"}o{qgs+3^>1uUbw~ʟMLKJ$i - IDATެNڎ>?9BMĘ~fDa*F4KV=tϞc9^/u{גA*w~26w|ݷӒTuba2!B"D[ճf|ҭ_thvjYۯW.C/+Fybj sKF~+ wи4"y`x9wdY>#ns ܑey-˃"D!B}l6?jx9#^#xx oaptBeL=G6Sqw&C"D!Bܾ89.Xj.NAѾ:4$g,$#S(ΤŲ B"D!nUlhdDm!!?cyGd=eMC ;rfsv5A@|3'xJOrjꣀɃAaј*'rBƐaOrzO&e{jL|Lզ!B"D; 4uJd굗Ap$1%;:ӼY^9grj.I9ѿ3PhH3M1"u])JǾ4Ǔg9$Ib$1""&I# "D!BnQ`d*3˅tjM+1""|t]=wSb`Իţbw/6I|fO&"8./s_\Y.%J> "%Iĸsv+*14ȷmHAxA?N(4]7u""br\&8q8%x\#5K7'v^wƶ 4Dӗ" 5?Jw.|BD3jYw{{ǟa=QfG݉C>"D!Bڿ^K?~)1b$W߿^.'Mzա&WS)(&T"nq0v)i頜 y3ObԸOz,̚#>Y]!p Y7vјg^`ͬsݷt.g2:Kr7%"D!BU=ꍗB1yM&; ADϚ.Qb1%lPrb'<Ŧ ywR>T{4rK#Y2d6 {ON 0T;!yH)=sb˒=ǒ)q'"ES%&kBD:I8Gv0gƿG阬i2Q8Mޭ% ^4:E$uLRL'ូr-}[RTEbBps $"D!B7gƽNֳ|ԕS\dc/˘*vhbϏ& \JWoV UmǎձFk\d9qXyDSy [UlNĴd.k26w|ݷӒTuba2!B"D[ճf|ҭ_thvjYۯW. 5<13r ^\QA '̓o{͹#qS,cOnYD!B"kuPsyqxkoo눘f*`293 4"D!BIo٧2aOKEu_=4%faH22L^,[(n"D!BVŶ=_fOd,)86 {D@3\4԰s-CIUNʦ<;o?KJ8vnLĴTKwQ⿈|3UUD9+2[syd5Lrj\ D!B"]w@L\OJfл^{q>.] 7'ô*&ğ~X˲|ҧôD$sV. ڸ~^01*l/'ҽ"Ȼ0=x_:g [,;)ɍ*g=ēWknȿ+&yy7Vx3r/"D!Bqb'B̌D\&1'~wj;7 ])]{n4JCZL;m0?'~>F@CToY9 fכR>ëjQF$s/#=M9Mz؟3e3Wd^~rwhƪ9\`0#11lX xc$1$ "D!Bq7(i`BZq:nrZ+1""|t]=wSb`Իţbw/6I|.`^>LR!xSֹoDADY\Oeqq?{7}"D!Bq'ĵ-0%o܁u'^녚\MT` QHd=)ED\T!oQIW=I|$LBe'O4F|ҳԝ!p:td|.G!`z:3yqωs[ B"D!BܪCQ!Q輦 Qgͺ%+DDNNyyJPyb5Cn!&cgSz\]yO&ڶ?E~ݦkf놔$vxx>+J#L5W0j.56|=S{44C%-{˗%SODJL4|?tޓp& 1yFׯ_]t="1i=;v ]܉O=GJ*7y޲z(t7`w=&c7L f'o\wߖU8I"D!BYq/l.5ue2J,o7Xs%IgE-U(Rzz^8 /*PS1Ƅ=N q~woӴ8KGb\Wg-,|+Ba|g};Y<-NW'V:-&"D!BU]=kFXp'Efw._P _W_ 6rC  E:6/GsEF10O^̚]5,gmN;,?qey!B"D/8^ȻW,^G^%[cuD5xGx,\G43PAϑT\eĝI!B"D/N}/ X#L`2: ۃ"D!BUmW]`az&˚v*ds%fc[R$-]zW2Y1{j'9[_эN+'rBƐaOrzO~^sSe&s9Sg.d"D!B3q><)Az*Dлu5\'F4YTYt}GОr(4]7u""br\&8q8%x\XKwG3?_xm=˙θҹ!B"D!nUlz塨D(t^SNBѨf  HT,O=y7y!3Ivr#$1;2Tq5y6nەd=E҆v aƜf貤e>dJHTɚ{4=5M& {Zf쩤yw-gybA{Wm/MNc2 ok).eL]X]n4r؞LKJެNڎo[WU1Ƅ>ϊk.ys}Ĥyvf#ZǞyx~+ޫRqMcۍ4<<ϴvxZ2yNtZ?L"D!Bqbz֌O\]<ؿؽ[2pj@_z%ÏCy{^͊)k^.{!W߿^ݓ/GpAܿ_/N'f.sG36Yȟ8ܲ<!B"DW}{XjRF׼N?wYJׁ0kvAvYif:2 &#ʈ;@!B"D!n_ёbOllodl5$#S(ΤŲ B"D!nUlh$mUGd=eMC ;ru㓜گh{#05yP8H5dm4:TlzͳB>#fAD\?l[N@ɋ՘*3˙M?s!+!B"Dw@t93q><)Az*Dлu5\7dzu.-}0y=1'O2w|NtӒ98d0I\$Qy_PV#`wn3 ȝEK_+5pHNn+ݎك"D!B}!BfF".?;@sل=h7Xs%|!-& ugۍČt*ĤRwHNUNo#2dϛIǡ1Fn r"%SiI|JHg$w(g2&nVwHRHRK/#gCds=exݟ]8⊦J4M&m#"JYy^v}l`x(/-WvE3׃Ry&X#=D$ω|>"D!B}qmU?DD·.[DD,YD;kI*Dϟ~:wG/Lơ)\=g컽{DDDZ&M:M']D|Ԭh*߹Xo|hZ樾D\d1?j~6׬t;q!w!B"DwB\z"cD,QxWƘa ntED$LBe5~vhʉF@x%>h̳j /Uy_0;0nR+B C5.zbɬ1ⓞe/%&RS+"ܦ 1XLF^gsb\Ώ!B"D*GPT"F:)d'!hTY,YfD3k?L5W~B#PDiiw =qx'b2c?;9a-,MmfƻnHLR=m#N6=6J٘k).eL]X]n4JF5ċZPʥtfpZma& ߱Fk\7fL eh{gC4[Ӓ7c?u#b黬2yD/?3n ?>idu:i0D!B"ĭY3‚{>/4su`bRz_([/@N~j~xTͻuIKSαRQ-RXqwuר"< ̓o{͹#qS,cOnYD!B"+ᄑ, 5E/&.+=3=2W :xuD-V['>z9kPB /,vy-ocDL3 Q0LUFܙHt%ӌ1H4&БcѲ)Co4F"fk|Fѯ]YHk>Rjpgүm5RnGᵔ3lT?z߷S dd יXP1`Aێl WsR>bwQtT<ܮl˳xRmS`135wň5 #OQr5~!W `griaZKV>WlFNw IKޕLqVܜʴTKw뭑>IVW}15yP8Hߜ:6ZD\?l[N@Dl9Y2﹜3H I`1=FB0M\H5>#3I"~RT@0e<$Fϑru+}oMVfiIV)r>l*kPmgH_ p޼?+,qߋ-}uћVs8;1qrŮwBf:%}xR2eKUwjHrzi1!l6,}: IDAT:LKD¾1=x/hU>5+*l/'ҽ"Ȼ0=x_:g7j{'yԪM7Y9-SF$7.^|]O~p&a.gv;^f/λsQmMn #D O*}5f/~oD21H7nd9UzW\F܈l џQ"Ci;{ψ&6 r48j@6;PBNF $"XBMGӇs,*f$2=Sޙʲ ])]{n4JCZL;m0?Z*ȳz#t""Wć|'I^&Fn{r<'dnLل׭)ÓlDDČt*Ĥ߳o3M1"u])JC7$o'f1II$1=qK]\k(:oȌmFҕz7Z^' jHgX'o="}N;.9wlIߒˮ]\k(:Kn³`#5ژDv]&S@ne·dKQ"E+{F$ˏob:b ]o#~-,;`P"~n5˹]^K᣿qӶP[qOl*}w; ^57+8QT0"OMu[-"",epSB5i/`%dY&" S$[ꏦ/ƇV W W3I*Dϟ~:7㍞ղ?F{ڽG暕B܎W\3kf_,vM(:Kog(emP\C"h"GrVMw,}FыowNb@MJlsXojLn>Kaմ/dn9WD* F A,vy-RΛFu7n#){󹃓0K?~Y[&#F$x:gR{JLݑ/!s"Eq)B C5.z:F,̚#>;0>M^%mNNlKbB`B S80-`liN)6 c)t:I}wj5ڪ]?w3;of޼2L:UTJ@Vj=l!Z`42@)C ԏ9tDJ,E$[2:jle#5څbk3F ҂r v@;ȫDn0x.#g~/i!Tu=[?! ]hkja]I-ƚV|&c,D!سաBQq &>X,*a+`c*gT.޻x>]صU0j YxIWŒJIP<Rrq;B!Z :etΉc> 2p:!N,k{BI@t}X0Ssv=)Kl$J!(gs-J2KS1ٽrZ*:ΠgJ(n&03YE4OS;% ^G-%v&[^u#rzLl2]^R*Bh^Ϡ#F?HiSDNV=z(b!rZ\0.}bITE%⑴7k.&C!evT浗?=&t:zy7^J6PdY6[:Prշ:L"bl-x])RSd+C*+g;[Y!P`J^2lNL9mbIe_ĊʁcvʥL$i4VJ:lvA[Ԋ+&ҋv#o)k4ϭ d]X1Zek5>BL8 K?fǶ8?>"+P]XH[f'[DsSbfc9ѥllm~z.Fw_j6B6=5B!:MA!C|*%PJ<%AI$ʶ-teC3(pCS"B`S Bܧ˚jl2P@M>:<-l! $HB;=}_%>_]3]vr?xj޺.tde暛ng[y75vE~'Bu !B!.#B!BYfZʦE$9l!B}1:< )Q[BڰEB!tc i1Dd4%c1IU.咑HŗZFgYb0!k`&`%TB&esrB86 d.ad\l=TKJl!B8okQӫ At&Xe!&s1r&*ZG|fRPUoMb3Tl۸3&Gz'cS/g«%sఇK.cuf T̤ j@bXdOմҏ -"Bs1M,R$rbe.QfNi6,X#ќ?Y@g,pԾ/w_:ȢREN!'yf!\YJSn1d`m#m7h.tfp&yyDϛLbF!RYJmǸܣE3p$cK9ۺh -eE~I{σlcNSToD[:6OwkVg#u| ˋbQ8XXbC0ŖG8( K) )e"':km;w ´ڹn2ia@F:ȹzV Z)EDge年xnB~79J%%}<p<[<2ql)J\ X&$ UA3o2"Xyhddrf@Eⅽ2T*f3*"(@B6ȂQ[n}"eT(g ɉ; (-nv'kuX90L;ӥ@ r>r6I [DuRiMps9L>#=;~IɃ'ڢYH1{z+JcR+K8챌2{(X"*R oj)KͶh.4w*TG4bC98mQ 94GVCiל;0PJE "+-*uĦ#ҩb;דFX {&dy)i:~ǰE\v^x@tG{'G/)yP)-gʻ$VvڮWgtrvRY(TNVJ@Ml!BR2 +&BulUm 3ld5&V}B@J# bI+Q E)C.=u5Rok?PQ#hjJQs!B B! P1JO$rIPR)ioV: B{w:4-"B!4j1Q(TyfB{y-l!`]wկ>m\.Ġ:͋_g} !%GB!:EB!:EB!:EB!:EB!:EB!:pmPx^6(.ﭷ:x &B{[DaXD ݘtE"XY KaЙSN : "B Cb_3 WV@!B\"B!B\"B!B\"B!B\"B!B\"B!B\6BB&Fa෿0g 6M'\OAG•B!BB!BB!BB!BB!BB!BWoM:0ؤ8KR[pbT@m6l+{Y:eTm!(c3(1tDK7Wi8)rPL\^Nt}EPGh><*'8feg_9"˄fW4KQ]-zD+M-^ ,w~ҿ}GG1a9BeGYQ3nV=-oA`c}ra-ӻ7}DR& #.]z#e:m>x&f%z6"Zǧ T2}?BBc~M'V7GW"G;'ėDj5(098!޹%K& ͝ ̮Qҫd Y }; HQ HwG-r&* 5f`Y%uW_cՊsٵßR%*h ZhXJf=-i>)i$V(yi[C`O_kԁuD] JGG|T#> 5ժQbLTF|ҝ^_ >ql||ƁU)bUsC Th\ XͻXDQJm/o-)w6;uEOOt;yjbJ$k)T=C"gdmUbcjADpY0NL-֍Ch~~W;!fJŷ>Gh͟h!a\ǡͮW YS2kʀ"-w7Auvh.0̣ì+4(P4~Di3 DgNLь6ܴ-mAQٰ|fRiuBO u*-'p6OąrO"ˌCIc˳8zK[ Q hIu(eQ"F׸ϡY`U]>Q[O&=ZonfQNj}KR An|P(A?,Ix:]g/h)oe{p@J2xm@hdY@tYQFi==J@(ڢ EGGjF-0=;;3n׾3,J,+ZtX΅!HLB:6>uh1yGf oD5JE%⑴/]zHw(0k-|Zc᜜}:V?AϽ(6n<[~h.9٥^0zzܫH~F{vC=sQ˹wOGB!:EB!:EB!:ڙGD1{&*~`tv!E,BOψb1 Gva vkf@d,.=:`;NI(dX;B AN&uC>EsFW9 }=59ˢ|of _ItF.-I%_Vqmj{ebt:duZyqBCX|ʹ8cxtTF|^wn|>X7 YVҬs?A#}!tX+wLxudp` F{&f92XPI=F YG'墜G|X^yI!ZsDgwZSm5 Oė-PJ &W ѕդbt]Ns DszLJdAO ;lv ӚQYVx;&1Z,@IU*gTE]~Jfd {I1^6+2 S2󙃎BK,]]OZ̤/0 Q1QҴ_ @Œ@mN3 W 4Q\hTgvMdy1f1=vN/%{Rcbѥd?춎Q^#}|g.k{l:):!Ƨ:^'P%ulYn\@i+y15}'ҫd\lRZy1RԸGZfOG;:Mf(Xv5 (T*ouEr)'Ib^0 ȢREer#J\ X͍OQ9ӳ^OncƍFgz]ڙ,;Tgiijl>B9|3z\ wl;L1a\ǡ鑚K+3uG4^ч:؎v_q<"4əG:Ou=y:PYVꪔ@>|f|9[7 4۹1}\^حgR}#zr=Xo{zxnmQQ6"a;TgiijAnfb_]_A}xVs>hy-\H5 kv]J}凉VVD㌺>s˚ ^Haege EժniWdIvi {OhG5J"xy<ݣ&F;#FLB:6> `\cjߙ}bITE%⑴ט2D HJ9'g\Wqjo`tO&KTLJ*b>q4$C+ sPIj8whA#J"XnelrMyAzhf2WLM .z耫f1Q1pNΌ֮Cwnܬ8$RHk฿r ԔGMZЎ<@y)i=C!edkx`GדŔQvrODNU(2>w ٯi^p;q^3~W|;F6fXPc v}JJ*&4w6=v^OG%H`VGf*oU4w/jk}|k.g,/ EM.%[``oÈЩHp1YRz\ =:`"@K-"tf1 e NwԢgAeݦ'IPcLnQ,f$*3kg2QJ%A3o2P۔K;3G3 ,*UDQzQ`Rk1,%q0mlChgWdw;?7k˯ys}4 P~*W=(}w}|Uj=J{ȅl^&y;x Z eG{B*˲.|B%rGUGҭ} p<_q=7o UW>Ĉ#jk׼?Ɍ뵓9ݍ̺y9/9G^D=ڨq'.}w盾V62 ǒF_+ҟ~g6;{n**eAO λ[ ODeqD|r#`3xAlVTe}.g$ʌ^Ǵ8 aLG)krШ]# " rtnb@JySh1M PE,ɤj-K*'VܗG9}ְ3rG lMsylh#Ϭ} 53[R]j @b{F^9sbL` /ΞY+Qf,m'or4-fҚmaT77* YI5ȼӱ &o**iŢX̤#0U˨YNt8sy#G vWuYw0a}og^ ^vku7͓E]t_K/{ϛpyW1/JڞL~AP"T^g<@RIJIP\spjJkuX90L;wsGtDT{ӳ^w[xVXy%?|]+uiDrl}ZK/ >ԯC*D:Pԉmrƫ:iL D7/z~ucpB{l3A״i͟Rju>mTP+8k(% n|tm]7zu_|͜8tj`\36T ~:/3t=1qͷzqL{?<3. g"ܿ&?v7$T:>RفhPT6OEv*Xg]>NJ./׿a+j>i6mdk}@e-\䆥vy.׵['Q hIR˭T׬1hϪ?ǥ|AF ͇ %CddIkdxD},>~4@rBuiCWǘRL?dat_7~+]';U.7WlGKJ@,&(d u $X:,(uZؽlh)_P9'ffg,"˰"˲2pڥvCd<?woT8[`zvvff]uJ[0.3pA_Ċ` ~%*K#iYPHsƧD![༣C3Y2#bb2ιlN* 9EsmzFAjMR] ewz֣HuQԠ{0Y\+8f'\D"|erbl˳n0֫/_ß|u*b>?_XA_y'&W_~}2?r%Qc-nl74HUݷ߱~O?仾|>oVP.8q,tg ^7_cRL?챥`{IԍGWO?ı)03ܭchF~p>GoΛF=v%p4nʣGoɅ7mm[.sr!g==Pq@NE`0-B2)z8%h5JmSq9ĀN:Z=jլ{QB"a\ĴZ5ULL,/gc ,.c[1x8ƹəQr,Ìiیg,icU&6u 1 3;;{ԩ&[#aFf;;'Ju SC!.EQ9(ZBhkU!5Y!4p"B! G!cycx"BB!BB!BB!Bg L-Rtq).cpz<#ς*L,J_t`v{^7ˍ9WW|~Sg?tW^{_jnh` vkf@d,.=:``BB&9` i1Dd4{Bf3_5R) 1@h\Vh1 d bq]tN ń.o|x~%5Toz 7ȉhO}UITnsϯYa,/Ff̋?|<˂bfJR9v&S ~ɄVjP$(`M&mf T,ɪXKt[!>P!SԜfJXȤ#0U˨YN{=#_|͗>{cSn)?^Ʒ\_ '1|M k/+QTwagolѯ]W>\UO~6/ͅά+t,"]L#B #BDpJ`8j&y=NGWB G9[ON,ӳ^,M|م:s6>c!ȥ7>z17յ@ȣ IDATƔ뻪6>IAosmf6B'Egwd0![DgNLּoq[B?:MPZNJ./ojlEIFECڈ0nOǻSGWCj!@R1˶4U7>}xe7?_/=D( ]8\wi#B6[DT.s=܉fN &ϑCN]\KOJ¶E9mQ˱#1i#5݈(薧0Tn$4/i >t2݈*0g\zoS9$~IDTq3F - xNZ~\GDB4_>Eg481U\k!~5}:b},Wb.%]vW [떏_yܡ$~޾'nxIQ 7O]Tş뾶ݡ"C+ sPIuwz֣HB!uvѽGE,$B87 !ЁD Ξ:ujALޏycZGlKݳ?}KdMgzkϮf|;hB=W!B [D!B !\=zб@!BeGP9%!B}-"B!-"B!-"B! r9-F(b!IJRT!f [,b93&PcGk`&`|x~%Ԇ;c1IU.咑He Xk;YĪr9Pb6K*dSN *YRz~([k-l{roN_yvX3Kqca9)cNMӱgij>tlF)$7±vyP-˨V6,Ү\}V&f92#>Y^tϟ o ɅάУhw:?j+gq!m{-NW?9&xTY &8ua=h%(YaO'I@N1N+ TB,͗;B~އ 1|5 Ra'/$hZbN ń.0ct]b tysX|} Zn\}N&$*UDQ o2 N--$hFg2jf[c wU,hf}Yh@wywRgRiWh1WY;7K6T* yb=-&֧?u >=Nw3pC}3/pѵ3QgS;Jک|~[.cgz=bȋO~.=oޏ~IftrǩǾyK/5_wO:ИU%DgwZ^o*ҰyYc-PJ &W ѕդbt]NgAb6Ȃ:=Ke1 wsȃjtOMfh`,#=lQʚv}T.d7tIŌ~CN [mq)iCa`+Mݦ'IiaJjo)הhMsy``ΠgBsN:}v=K, TuzY[ yi78ok g⊹vw86=6QXT8VcbQA-f7*fؑ,TBoϭ$sxrS>go|ew_vr+| o[-"GU?=[Is~=CAG'uӼ}/O&O}-7:>8w}y=uѯ]W>\UO~評s}?}E=*Bwm4o& "P7HHL貑ęmqifBDYF|{J~can~ccf%W b0YE&3S,@tF.Ud™f@IQG`+Q=DaC0F-fҽگwz[;ZޠgAGh)_;޴۾XK 8g ;j_ (T*MIk ՐRimI 6{k/)OV9q/{%fS~*UUy 9Mf(XՎ~ybNzI(#̀9dN Z5"PQ}1/xyFa<DZ REʩչ,eYhኹg`9@"-ⱜ@48l>9r ٌ 7U F8Vȴ3yZrA-"Z @'T$̣ì+MXJ/GK{klblm]JΧ;a:"V"-pc5e6EMdLvK ZXjkT%9}-$sJ.a\ӿ|ZrگX#{]`9=E_L~cX@]Ͽvr]?ҼƠsPmIKcSeV+ EQ8daq"7"h.4w*TG4bK㶨t#(DouZtR:UT0p7[ӺpߘUZ P!| YrA/F|Ab:/Өe\wZ|*Z@>|z97ŒJIP<45ޒ5;L٠JHKxkTN דNJ' !T+sG^oos>>ϿڍYvkЅ*_Q/R\&=3{._1W})rg2c]Xf仪U%~z\6+wӚU-BWl}䀢&⃿ܷY2fd:#Ov0 =:zBAnp4,=vVL&F2mv9Ǩ:#7vs@+Y$pEKEG "R*< \dA@8t1%e4fjK ͥ-Aisޞ_7)hrB8hkha'V{IkOPG˜s||W|!D.3ǤSq<%QA[CSǵp͐tE'2XIdK(H(D&@HBW ̹>]ld0~Q;H }[>yh0z(\zqDjA@}4M!!MKpcMfѐvϞ#lj je(r{Xa쓟Y|8\}w<.f9snڰE+-[tn >uia;]_ytwlהe&g7ξMVU<|?[WmeӅ!]s@"9{w|yjyY@xaŇ[Wm!/4i-z fEaO{Uee67ҤL09[t(6#C'Sf*pTuXMZN.ќW7;}N^V7u"VۧqV8E묭5  )e9KĮ:kPI3Eκΰ"-#eIl -XC&J}`re9K֚&GwDIՑLiT1|Ocuucwʄk3%2>xMmuN&nKqL:9JrC+a%ـaFl\N?]5]2#n"Cc v|/.j .Xx꛷zݽ HYx]P';Hb@@xW6[AM;('LPk#U<:xW%̢Q!8&%g @vډ y7/-ys8!7(&i|fvVT[<)73;-lekUMoިRJ"̎;%$W|uM=0s{49R'bx)e)j ,8NBAvB2Zc1&!Fd2 r;Zf <njdm"NjcXs@P:iz[HH0?ۖ%P(],E+E~~LSrGD4`w5|[fdMvJc\GeejDGi.*^kK3?a KXLWmg<f7b>u\A]{>8DOW;iZ"Pu "h__?pO3bœ?{pk>$(IX;>$#$YJ”c>N2r6 CPd~6)JRLoPQqx/LDIokR&!$m³՞'IO"NmiP8eu4R^[K" $ ,p,xڠe@\a1Fz8!i2 ZiO8 y1nj|]wU[U&x6+?(hnK*C@2 yQTa?(:b) m+\5^:O_7cBi:q UzIMU]yfaAڐ/>V6fQZRt:>+e@w{S7 Fg.4W'VR՞r!#emi&%(L9f)0zH AALF`4T7=M6Ֆ,.!HFŚ Qݴn,qG1UOt Rso~բ3{-A amjdM%Eio/̚j{:Z)*԰p/r|MǼ 9ss(=.Į. u~qWU>1كKRSO L?nC!6Ә Zimq&yX)4CyL*\QjN5Vʤn`{1鑕Nbmmɨ+1"Q<6+S_vYI[b(  t۬tQQP9-ѻC$Y)Ah:y:i#M~"zl%cmmLv!yۭIg,(5 .ݖkSޣ+YtstH>R(c IDATWVVK0a^p!٫@oKcsl :l40YN Jf*Gs]4'7HYXƦ|V% p @ az}R3A8P6;GGD @ Lzr, Af g9"="@ ܇Wh+0^U>V&#mmahS8k< v w@ J @  GD @ /GD @ /)#ɨQH.37QHR)bYzF"X~*Lt&|=6o RMt{l6g@@<7vsrB(s٭6whdͣʰɎ*͒`w[GMYRȎ?g*ߒ˿my{Gy>=S5pjRYJ]Sku%~z!w/p/ Mv,a?k[{C[ooM#0:KYo{V= ollk֝=2VLF\(a!}޼{iO$s$Eh?>S%U&j& ֈΖƐRG &)2ٜປ8efN8zeXr g4 3]N^n4BsN#ֵ{;j1wF95[#q=Ic9:>h'E8VLHi4*֐֨eR3ns.~ˡ-C/=sGwl󏁕wۉ6@)dҀ:ZX$Tp]a+Nݭq2j &EE4sA&z=a~EH S*$>^d`JbS!ZEw7WEo;ZO; AnbYMsޞO%7fgk4OVM,H#D,p,x=7J3ȁ{yE cZe˝R HQ1jvEãgc P* I9)2`^g8eOb:tZQԾ/F\z-[Skφ7:?_V6'O=QXzڽT tr!<2eK$4UݫOXF`?Vņ].>#2G$[n}9M}V,w]4D@3u@$7d)9:!Weg@W} rӌtgk"j;QNaZۃB"`q2VҁLQ`i\j"><%}E"rJ{* Bg0pIH$}Z&knz|gHW-K^UYY͍4Fuiʚx&|͢>ڙwQ{O޿Hjϵۈ$3-^{rZ@yʲ-fL%];э!Yl,_:zEi 7;bX^< ^ Þٺ1(Ա9d0-9~h w]p?uyX5}ޚ5GG]p3wy=k]k;kʓtH:&3ڧEl?a^^:0$/S2g7ξk.85}2(: _jVia@L## _o 2괜,#֪}cCƠcA9x/HPHS^vh32MF%]VO)}Gts^]-Uu֠LcYWU77&R 9'#4WO UVQyyٌt٠2 3%;dN.|"n2J&W3x}n/&gU*e!2W;؏4M+*  XF1XPǘj 9lc~ZZAiy%Si#{fhA?*7C-WEle^ϦOzϩή_|Mcakw8h:l Qgr3YhmG x @,7dHhsGezJOio z{4u ɫYPH  [xoM_~Pͷ'`vnkqa7_@opn?nޅ᳻|8f +gq O.I2뤲ҧ߯5 % !3M A *ei|J͌֠Ap9̱~z4\!Bp3 xma( +X/hr YW`cD ^pL[ZRK=w̩Pt35o44-UJj1 ="Fe2l0BcC?!.R 9Zj)'3 2r47XC:ˌ,Mz))v C4t r¾κF\X6peh}h,~}`z!a߹5[.w(~ٴid2GRf߽V?Z;z& |r$od6SC@/(Aa ` e;(/%LoPQqD3~{APj.@ßBJ,s0~H+JLbJױ}fn$эG2qEj21{ګ*ZbMJ}G*:UW3zD WeHm2A{SUW@9Mr1oGmYo N! $W))@P#]|>"L/u c40OpRT>"F.g3t&=tck@N?5G>"dJChB'ES#W! ̈́Cb@ĀEAY:._~&GņNHf:g_^u _k%m)$4TZf "Jl)M(؁ PtP Q= NGd?_'>Q(sNo@ Auv3$)s|E$vJiAˀuR|wtb}d2.TL*T`')4%3敖hfbD0:KIyyi^ $SiL֯F;#QÛv"Eꐎ)zzA * :pGnRꋩlXĀp0o$&p8%k|"8E߉Eŧ_qj~Tw;lg&eݭ+++++7‹꣫c8Jp<5ZtRjNeDJ# F2cQ5 --SځxKFkP x,KūLfcLE FҫLLLJfʔi.*Fny1acLcAiz/췵uP&.ĈDt۬.A.)ze9&]n!ZWG~[k;m*T >{[G Rl.Rgg fӁ買/m? xݭM˯7G0Aox[/i'. 0MAGN{9.rr5n+e}!&\ehOz%kW~(z2vҮla)B|DrJ]D۬tQQPiǡ0h܎u xQ! `Fxf *IY9mJ ^_nurqbϛxVWQ;ށ˄|Hs)RtR@:pߛ_D[A8 m]>7wD[A [Â6@ 0}!="@ 0}!="@ 0}!="@ 0}!="¤i-e(+P$cpM"Z,.+Eyy^:搓13zG`dO1snƅkg/LȇwlփF D'?%~z 켻|p}P'SI4&|tYE%%3s3}%%[ ][< uvqUW #"MNi8arD+ %eC(JEHƜ¼L-Fat)S^PV^^NC {G<1)~\kP%y &$piv^cț`R,fwutԖYLOSmF"ѨtXCZTXfdN_ǟW_td>k> Zͥ()2_:_>g{?z{\mL"SRdXr g4 ͎ MNq605i1gy)L ٫#.O{u3.ssi,LZ{ H2GRfl޸+1ϴds,>hɤ-ekDGsmCPci#"^@s9}S:RQ }f׵AaBtz!ӋYjS աF蜡dz4iI|(+.x\n2^|y ,Sуeǟ=YY|=G֖~}yP/*S:ӷxEo{P3[,.ѥGz7~v@c E*^$T?=]:o/p/"[vco^l3kT.m^bn]nyKVA-g|zsd˖re̽Bןv/՞M4_͜5'ݛ`< ^lɺ׻[rə:>^y՟ޮ e3VQȖ۽x7wvNzS_4)G 9N2t!7pݡm/'vVŷ$KRBst*ZVv>a$mHMi r^^!ØVr-N0(Hqz[Hk.萻 d(Y$p>jsKE`dr󜷧1dT+XB8hkhq꜒p'oƤA9nͥ-x1 ya j<Q@\c349`z1!:B +2#H2ZWiyeX:  1PLgBA '0r NaETv :ifY6 g3aph-&Jϡ@SS0 N@s;}"``&]wwPXY3+.x鶙 e`,9KJE6wU;ݟmu5K[Ǿ6tix]Jвts^Օaۂ8<%}FQ$irLDvMGXN[S}XH‚ϑR*<,q%#3>@RQ,wiFFzU8 -E(^l"e9ۨwkMU]gդduCΖ!I%N10A{ktm)i6 H]R^^^6`4RrA XSUoJ91JC>㘼SÔe-L}Бy몿7?@PͷS3o"EvzC$Oy/ =N< ކBϑ$'j!֡Mvh0LJmw7Kx< BN+2^/j   Vlt9NļrVydX&\81 N8!4M n{ $#D$j)`_gme;0X%OSIɴ3r+$2dnW;ۛ;R\Z|> Re,)a1ۅ4dYZ]YYhi $L9&lou@@9 h%Fˆ[fdMvJcb)]VFy쵶;v`Ӵ jL[+h|iQ{6{pV!B(14)aaEDB.Pwh'7ZX{ץ{:Gr[>, xRŒ9JVO\hDRi(=Db,IilxN 9A'@(?;!]+gkR&!$w-{OoO@@x-#W@FFKd,Kz\A1a 7e(C=A>&hʚodNg%8i-3sy*~.<"#Ie3QraW=儓T'A="kkiqZDH8(kX,xڠe@\ѝ.+S#tqCJe>ن}T({4 bJCQ9cP X@ڒzQw|U`$\^vc7+_{{#Sԇȷmh0v6 9m`[_\Ig{ǧ(PޡV`w>|K3R._~QS7ٗW$R.(bR*ϑ2<Ā=#l`#dx 7GWd I86/芤O& QP=vNs~wj~OԫƜ1kz]n@\hN3#Vj%X/#;*ĸ Qt&)M9YwK@F(}Fi:(L>Ru,^Ssа* Tݭ$}V'\yJdœ9^@8Aa5ihgtp:B,L5ggP1PK[lT+uGqg?_ԙN\SK}s/YSL\T"@qF׾7k\12Wϛ~zuzMMŮhSSbO9W.JN÷}/ﲾ޼OQ{eG*$iXĀp0p< uҊ@6KRJNl|ΦiJI=Y4e2)3&+sd F4I'B8E!m"zzA * gc[Gz|"P1L!M9Cx>Q8O`HcQt)dXݺ?﫩']hr(txRD,%她z|Hg3qrw)}NIշ_PT )X%)+?y]X^EtzwV9{"xmfA-_m_r ٣&n[Wcʕ{WYbcߓWyϾ㻞FV;3Kҵ5|:qʑ)~[Csty ~_5(%qb+V]*{7޾P{-LS Őq?^/k=V 6HHp+yl/^m%U(M9מM3)+TkTuH20lV2M1AdK֮hoQ:SdJ ~[k;m*T >{[#sd(3 gTK^okTzÛrvYI[b5ڥjc,nuJ>Cu#1̌ $#1>H]Dޅgъ8IV@p9;6&3CWb|goR8rmmɨw%ȥ: 89t-3nvHiY"m{HPQ^^^YY9Hla2p;v a xbڞXCNxoOn{ǃGuȶ4&E}[/ݾ*yMb*տy2Sb㐣@K@pORj[HΚ#Zӷf@ҍ/}2cvEwb|99! Y5G &\w=a!T?=|߹o`Gθesr4B& u'ҀWjDZ@ ٍs>h#3rB[]>JL xbo6GD o!0 *%*Ə.@ @ L_H@ @ L_H@ @ L_R!eM4EX) 'MI'HfYE} dih̜9Y mmmmm-{O!y;ۚ6zT 9lɺ޺(;-\_]w4S1f?MXL&7Ef縙49b?cK[%-cmm@n MNiC֦?8&}z FmM7QSCi6+.Nki]*5ѝHi4*֐֨eQuގʎ6x;j1wF9o0 ܖ[_.sދ[u>8t"¤#!uvQ[Sl@8pm]>'0,&X0gqaaqYyYiq%]I0dK6< Vrsh&dzV7{AGp(y`Cn?򈙧ookkM?64uu]{_oݧݳPH7T6xofH\x2jL@C.{/vUVP1WlijkϿAt֪W2 2{lhnmkkk H$;wꦦ/=%@ [.Y}CL%6TUa:U=7u[=(h;_߼r__L 6 ƬNPK5ZcYzi9J1*)!Inj7^@:[]<i~}1ФV'[2{]IyB>-R 3\R^^jђ1eJcNҲ~ O¢ҲҒZSE|Lԅ+X G(tNgf,x;j:}\e}N^V7u9ϤDR\R"bTZ5 /02BLF~H*&Xrɬ8^H$hp&M/.LB~*`w]z5,eY΂-;'^,oڰE+-[tn >ui_:df{Xa쓟Y|8ܼU`f=X=X vgKĔK 9{碃 ,fl?amI"aUs\V+c6+;lh9eб]sF{#/}E;V;seCp Ǫ֬9r}-8_ss-E< rmuSv"%\{yi;GMI$'Q|w~;I\Inj7~E|r*G<ѳ۵֭<)YtoXr~vV}vcyM1㸕_5}·5 )=^H=2mpO A$S(h$S)~OH*Yz%v6WW_o ҉`93M35Ha6ZSU`5i9Ht絛 rmFnE^UYY͍4sda|7"Z7< FI} Kʳ8*ҚU4@  fU*_(ʔirJrN<3 Bݮݎc/V>K"ov_0l|W/5/|{BkO9 Y fW{붖ؘQowUџ Ux"ʑd8^ȂPbo-ܧg-[>wa%Es?Ʒk"M鎺+mz*zd8Q^^>1I2iBN# #Z"_J*+*M%<' PtK C jv(<F^%&ir6E2\I>޲C(>;+B$0@R {ګ*#T0P` ,34*WO>HFt6H= n˭/~}">h$O1%_?pO3 3fv ߷yKsbJ }w^Gy;*>\%qvb߯<oymxZjEI9$ɘTr7n %e4I:r~ڣ:F彟HQ\$xA.#$$|ZxN 9A'H>[2 %i mvyŦ5/VxOɐl8OèӳLV1oGmb'5<~?#rQP3J% `\L< },W bhbun$3vͳ/:pEmR9أ$ YҤfߤ d ҬD늳s&1H:^nkX(g 0i^Wk 97 &Zd n@\hN3#Α@QV.ߤHwI~FO"UzIMU]yfaAZعCr9ζg)EIϊ1:G 1 BN#DvqUL1 yx{O>"FG4 26AEr4:FN *STQ괷GYә~ii~A @ػ fXl.:xn)նu>~,>丽_RRy翼sϦ{M}DW2+[U{JϹrQ"a @qs15.;5.,b@8,b/|09JQo`ɛԿwlJ&&fDgD%统JϿ*>n>"3/9"Kcu4$R鏹{E꾷~JyOR$CyHBUz|"PH;CRD Aat\}/S(hU*hQޘrQ/6QgZ2okt>EL6"ZQ!WG%sf戸vYI[bLm v@[r SPkoۘ XD>Z%YB(2e-$p#sBF;͟/>5R1f(BR%y/\pǎhaڒ|[N}[/ݾ&s9U λUޫdeHd]ŕ;8Q$ôx L4SsԌw`\:ipzJ I&| 0Aҍ/}bh7zpah`(y;@DW X:V'+0B[?V T]Xś6E׭%`(y;@Ԃ=@8 m]>7w>qg9@8t0r˄AV@ )Sb$s?n @H@ @ L_H@ @ L_H@ @ L_H0١Ɯ¼L-;&p"eis'_fD̺ #~|嶭]Ulvmjˆ sS$~3V'[.@λ{RA*}kgqRtX ZS,eey1zΐ2sh|Ƈ-#i-eeFRg?{Wq-3lvEj tJpB1pL5P` $# `c:ۀUmvWrҮ~4;3̙sLӯ$Ak,⢂,Y=#CjE7 B8aHɁL3˵ז zJ>H(ȶQ_um?*: ki,.ٝB0:,D"|$w zeڧ;f=W.yc'qϓ_b د^_~Lf6aÿ<Z|=WI']tot hѢŗ>[!e;uŊC+z'WNH~_%(=3:53sLܖۖ ҥOܶH,QvűN(ǹ=t{N;<?\ .+kt@oW2>[1 Tϣ4 Vh:0y`!P^Zƌ ­Ue@#Ø3i,il|-lBtWMKi3\& 5V+Ҧ2Z/)uX(߮Ku䫜'B`\{Leg*/u͡ IDATT]M&:#Kr#sK]8V]^4aQ_Lfh2Sְ$j+ʪ۱V-vL|;UEe7n/ZrPGE?q%Fey`Ѕ~h c>p!e<:UΥGa|EߴvowKt_s7n uPkhuȓU(]99}٘L {?ʼhb:O?oÉ$S2x^@C[Zz pE:]dX󱘄XΓ(J}<z.Q F@$FDұ2 sEQd '0I2 j6Caa)Ln;kB\ߨ1e r[*(a_Cs'爦d=wn90ARs磊޼sEh~skQ-:|qKO cQ4% XX$*{%1RSM u5`Oqߣq7^477*bES* L®[ܿ]h&g5ʉ$3nQq Filϳ4(yϿQY,9Ei |XSʳ[YOx"w5fF>I/95 ĺ*N8G$ c2 $c Le9-*52 cʴ6`Z}bdT@7E'~.Ҟc8+\ 6 yNYvix˒L 4D M1%i` RuPmTï~ړ.a<t$wWglgZAO0-+tY6#õוܴ[hGs] w?0dyb X!ϪyE)H%>oa>]=",$N@`0, 7pØvY`QvdϟP0`Rpo :䌋o{M'd$nS|e 7=痞C}լI8 &}io( Sכ}>1=CeVj@qؤUi4:A(,b)*YεBHyY G$?O,D"IAL/`-oPi5t72r4$I$ fT,ZSPLk0@zGQ0篩7UvWvLbsuw\»6>xaYnc?wWZyӆj,oyη˦?{憕0.q?^n*?yU_D_+Wyࣝ+EUM7?MC_+gPׯܹuhҿ{節*}#?-z0Fff*l55-~fQS+H[DNYa0K\pXv_09Kmi"[N Zȫ0wAFwEۓ0q:dXHny"@ s@  9GD s~o 0]Sw@  @ ܅xD@ aB<"@ 0w!7+s!ZU)wgeMşOAC/˖g|*}zc2'USǜ\ѽ??@<[@XKvC/ǥ5jPԭ!(Lਯ֞ƎWLMJ>3r1RICIXʎ%}.ab VDLGD V6V# M:r8!?g6޳ekDrL"~; -) \| nw&͕iQa_mugLaPqA|pKʧ z⋉4nE<)%}׵!FoJwQ]/:|z5X9Fp璬@R.ΪfZ*̡Z(;Bg+y@uY̿˚ (Vozw5I{}F0t_C2ؘj~-}쎳~j{®NXxԿl'K^;{oȋm+n2/zf{XVͪװ K0 X0bY AX].MNحvH8Pi,ΖDkY$ #I 00Qåw)H7iyqv((߷3`"+q |=,50ʐfQ5*iլ"HWXLS: d1|QW LBOɬ@''`gm.pϳ f,>Ɨ.oB9閕geKrQd#߸,a% Wr{~O4+˹=*CsUɥa1?˕{RϲAj޸{uW{ϋ58!+o~0{NeYn 9Î=W-2La恴L˽qJbbAfVt5VUXCnbz˪|ʐ4gűn*hPjcFal4W״KtWMK$}Fwq@$/]կape48XQ^RCQye^ ]6m\1,K+Z3,?_Huz*Q!Fg3QE! Mz(z"ɬA F&orFf  6*+:deZ;bW[&/9nn' rseU:Dcӂo\߆RY/[ xߏm]9,H5wwtj2[M )~Kt)tbA K 2C!dt3ϋ,,1 exLq%@tEYf@ (]ò$,8 5PEzGX (*? UDrD4M WVf}Ǟڹ#vN)&9O"]! @lDYp,y_2UۚM3N  PƇ"P1?tFf+z%:A:<8KQ0@ nplKNQڐg*EG|BqD BH(8z~EGJO)Ɯc<}gT1fyn(Ɔ]9ɤSڷGӹhpsc XcCc I{z݃,g}&??O,i>ت˝H&q3$P=csD@-&@oǕ(X 1&wSe%Q U*DQ&58 HQ'w5 T'Dh9.`F_R[*12*ww8:IξOT=sSP=EOKV7*&kD~i5[vCU|Z|B̚oeޣK@'ڻ:_rꏟZ{[?9=} o=ϾlC4ߝv'޸;XJ'.h_A}-HeoDQ9JD ?؂%hE`#RWGM-j."Y;w7( j$Y崙,=8m:5\zjvǽ[KW72tKNED<-` G[b-N1HJeУ(76S[fN7 z{Pu O0Rƒ%*B!%w&:pQB=YYG 2Q1R<4M@y/X`֭a$Rl#DZ0P۵ReCDBQ[ա*p 6qmoOCd@ a6wԖvL~@ 9Á@J @@@ 8)&8P4B<"0߹*utkA BvF{;Z#=k@ @ ]GD @ .#"@ s@ K8 K<)P$UCIQA3MK2{ZF/2<".XYJ(o`H(xJrT1{$mFv0gI@Z9ּمwm|ds5/8g`βOI"AsJc̞WT\\Te7kR4j_0{ĝn0ȍ v MMT~rr@'ܷѫ]zk&V7 %i)`KjPY{wBĬL9EE2#>ǜVq])KOS cvf;bG}Eu|lØ yNo]1y=86b&0ZڀO0Z,-ޖ!Qq>H[2]٨7g crf; bG}Eop廜ٌ\598/g+}vtAL"+?@{dž i^oO}94HktX 5{љKn~;ޝ>4n{O/~Wn XWūw74֖G[`+H992䒵66 ]KoǻUJSYp+%\*6y?ͻ?7M z x゚g)( E΅ooh}oqO?u5~uW(f $Td 'Իヷ6߱%[Pm^o,3tqZku+,.)>Uhpwsy}@5bM9E%yEOI~.xh[uyu[V32zg¢|n5X$"U*)#ɬA Fc*eyS$"W.yc'qϓ_bﻵgh=:>%GkIN]Њ^y+^ϸ#4 aPc&/|ُAqߒeWHמXGyy|F--=|%KmQK d ϓtr\e7STT>y?\ .+5{E>1{9cURVJ>Ua}Z_v<&q0#e]ucsj%R"V?gCr ~]RWwrꔋq9׮8s_y}<3Io *)(y : ^3Nx Ҋ6fdƲ #^oiM;L˪|ʐ4hc0B/HnBMg/T]M)'4$bFD@*cz&nediÊ\# QHWXtq$ј4Mth91\ B,D"Y1s`S1`1EB#DT<]θhht%;X/~2f)݉:IWTtңo0oMkw ϝ'2{/?arêY,': _4:3 VJ:U`W8\og]~Ɓy~J)1\P+Sx#SqSx<R2$<#Y>X MHyKۗ'<5>iƖI]uUV,h͢w665 PI1fBY)䊖GM<,D'Rj$@ji] 3a[JDA*%ψIf̑VEE`AF$ (v A{Y cehn̵4Qd߁5xpMw،j[:5KPz9 V3#0JD`15~(IOKFfP7MH3YiB߼,q;֜~ܢ8/}6mQsanB2%fjwj14-K2,K`_6B˕s_W?W?~hkSi<לzDq~ɒ,YuyN ,EMbJ12J'WPy&M"#+JOJ/O&b*1`a%JAA@ܼ'EE,f}p,itl0*>4ǜ @#uz^q@[~.3o`!DQŨXbYD*X{"f+lAzh ](1'Mq|hxy'LnكXC{GfG|8N:pΐ3wΦSS˰V71H{}_|'t%|msDCθL_޵Ni=g_i0-ԜT`Y1d>/l ϗ?A(zb'V7 f"[)䊖Bdded#n1,v?G%bRcHK</s4NDQĀDw,w1Y`G puz-"ő!#bjw5z^. IDATڟb9Smdt'$p&?*c-tSHR-~f=7yEj K\wo#F)۷ɚ: `0,s ȓzf -H'&y+{9lR45Y+oMj j I][L4 "I?ޜНo}M +? aP]VU~@V_G;bMigW-^ܵ~;OL,= Qgγq=zIkO\tϟmBXB_r}D{44KRDYIYbZ%]PݗgeW4VUudq޼}Eњ[ΔXŃ,? TdƐJb2pGۭ";kjM>f1gY,$ (L֞kg;1I&q@MbzG=>QFwᖊq6 lݺu!YoKTKml.z?ݎ>{ĝ?|/zgޛ`X0K=a sX_Sj,6g: ڪH[2]٨z"2טsɞ}r' ћ?BnKYJ# BG}յQ'w{KA_OVHWL Yf z+p;-g]N>\cDR814 ZVgμAs)ՕWX\RR5:ƴjN 3d+i&5\(8q{DH4kqfgF+k%}+ۦL+ `¢|}M Mw Ҙ`DƱHD`T,h,\&<%K恙` h&'/*> SVܼ̆t*8f{+T'5{BVp. WiJ=.{Rrܽ{sNuWJO4+˹=VO7سy"xm |,wl.}EZⷫNL 8{iGsj1W"eܶ9\%~]RWwrx001?\ .+5{<穨|QvűNg?ĺ<\wM(b&_v=~ 0'_qB /Q"XKIيlhzH`d[bru?Y w`?g8 LQOkNam'/c}`!P^Zƌ Xp-if*293=5UWh1#CyFs.tv;kk;R,EC0eDDSшHU>|Y7YPpa&zdHIXzU `>oLBa%QYiXk31u1\ B,D"#y/bW0wF_DzGiH?6@zGQ|=gmDn_ο%_Ӌ-?oٜ%9 I\Yŵ~Y߼֡4GޘvGbrێqoW!a C y~4}<6|?07_;lCT1Y5 X)穤|?ڞkN9MRʓC]u\z p*Ly{?ʼhb:O?oÉ 2D#3jm*n9ٖ<3Np;F#0%xy^ϋ@Ӕb_Ȱah3hlst !4XuG9Ξ4MFZ1ЌrJ>欆6Z x="7Qd)_{8}Y@uB712Bgk7M̦Al(p,ۚ;q[Z)]zaBq%ioxPPE7{imCcWI@9/{5KX11WJNgdf7Zt r]v'$ߠW[T|w#w9%)@QÑdT;Ek=o~*|}gjRqJ=z**|1@ji<(Q4% C3'(kⅶ }iHbHJ6M7N鉍TKNDeJm0#$ 2$I@ l[@ǜ VK:ʪb|oZ\jȞ! hGs]UyYiYUk52 `s~VަD$`:=8 -,Zo12 GB(Ă Fs5?GZMn $*N:G]e^8(+wٓ=2@\nzb\V ~8510^}/wq9#LxcYzCӉk+á$c U# ~yanWMb8}5Q_/KVsR0=OHr1aYi3b&]Dg,=io`-d<y-yzWdXKI}BP JnD%@iGOg1g7u1/O#! `!Qb.a@*z`G"  L>rhKn]|᧭e= R85 f# r$5wa!=WL9M/*H*Dqgíͭ֒08Y[ڔf (gAdžQ{6ߵ#{I-I{)>N_s٥1Uqљ:Si>YkG *;7j 8)qF0@OEdLEAҞmw s=n[w =Q1!o3x88QI-dlҕ<n5dQpG,"+{fj=@"5%;3,2SqPLbkM376S[fN7 P{H!fBçA� WbM=Ô]d؆Hߨx^ 8 n:,ɷTKml.z? W~Vsz!&@Fw7&禱ƿ_$m*tU}_ya!٦F @ ӻk-+=@ ѿM smo!`>S8 @ Ft[򳺘 1 w5cI˼ӭŴ!G;[S8 @ ܅xD@ aB<"@ 0w!@ @3wvk0T4Xw%S؅wq_߲k>tk|]xO2 ,}e[#'' '~ Vuq&OA 26"-pҟڝP*3i|յP )]_XJ&)zDHm^X8LI!!f3Ҁ]rk6紻nU-ZZczw^&YW͖|LG;ֵܴ_D <|ˋ@в[6Koo%t0x HvZdm-̳bM}`_ ,3}eTf%{C9N5*)zDlP'@8@HKؾ?JF"Xy QUZ 4.VYNܡ9_i޹?c^*L_]zj{tjJ 9+ifΒo5HKbY AX].Z6^ v]n#D1 E>A;zuLY{jY$P%I&"ǁJMA13d@(CE |{Xd0uV:~Xi3iY HUmWK@'X9VЙhf B'Be2(ٮ|MIɕ@#Hf2b.K!Zh`!]/vIAe7o y^Aίm+.S(?;*"+$'C32mV-C!@ êƊkHsM6ݕiPCyYSҜT9BM-Jm01]e AGZ+V4vHseyMOwe۴(qp-F=~ ­Uef $F$T4"Riҥۭ:F-/4D,EC*9C@Y0LL3WD@f*=Mh̙)kNZZnqkV1&wS#CSXs+w͛;V+(ڶtiԒ@ C0iMfAnw'^8ƜegÅj, ]"0tܧx^ i )Iu4@$,qzVSEM[XLhL3g,4MFZ1P4+]iBtu4iXB3t<4&X(*|;<a1fٖ:k;8/5gXTH;/Ӑ Fd`Lvֺ pgc>Q\℺9=qޭo|#oڏ]*a@4-SĮHGpwsy`2{~Rt`@zGѓ!"?zA1R`($$I`( $q MfҙM,H(ȍyt 8.{3eU6wG$ .dH85h@cG~q,8 VBD2e Rgo>n1;wAî`[uk0^w_lBlӳ~yE Y  X-Zjjr9jM%->+Uƪଔ5ud5itlg;Oр('&Dá\qgYNK#b,MIGy9V&~pSaVQQ a7HunG&1 ~'Ku¶'64ɽ//cC0 5Ӫ7m6 EQ+eDUU*DQ9"`Ԅ:c\ K[*xI7<,uz-"eIdݹvU h uf٥rwŠS(f32ǥ8,MKM=.4#Y|>_,ASE({G}ej8_/~78νcz+LD ck)19QgHwᨿYMEV$K|w&e}AQW!VCFnſܬrLYE 8m{%yEj K\w?$к-T)-3_'EڛZ#f[%qFPuehk2(pr4B/*nP1}K|--s4M@y/X`֭aΒ|["@ϝ@| $4vv}tBQ[뽄a2gCm@ Dw͍{#a@v@ wag#t4`>hQI y@ 0 1y19lNẼ#@ s@  @ ܅xD@ aG J3C?E$l He)(*.クC1P9ϒ+ @ OInj<Ιv:m}u 6r^ZTqy=aٲWn< w~¹g=5f3٦cFz6ūwvk&K&RiF!VpA%f֦ɽ'KY{O])KWM*&:s J-+/,ӵ4#;xb a2YG¢F˜Yj82[ZU7yqxg$@N7NM|O6iع7q7>魮سZRLө'0|-{ʾn-huJ voعyuG[bok 3wz;?|jiTK|_'8_'4TK-*OWh.<~rڍj0+&W'(M%71eOxL Jg(Ziyww3z߰ջkK/r.{CS{}+TSF^ TR0e/wgs+ lKKd'"L6ykxpՙ3dJoM.2SHYyFYtv(#c>J FR LG}U^oiu[ ,bXFJiw7+{zP: `XNMb)ZUVƌ #Ø3i,il)ӳfwaQ]7 #4:- XJeGx_`gm.p}U*@TEGsʊٔG{Λ/y!%ݻ7ī{L<.=R!Ϭcd>+/Ph@YwzmX%}GK[Vg* ;l^0vJM|~ Ž,{Bg?ĺ<\w.mm . ]m)b8kW{䯼nrC紣ϓ9pcU`~rrNXWT(S3zg+柳q@ˑ*?\>y4fü|ʂM#蘋WMTl!Y#I?—c~+ԾKQkSQ%E#+Q#:cd+ dSlJ4N'(E_oDtWY8~VV w7yʦ.dHwe۴Xp-iS4 Hf2(yȘQ)P^VT4gY1N;@:>uc4yfggwgw"(,AF#0lmÀ!L2AHc>, lpY` ivrs ugzotO9? r[\vV*nbq-lKhƪZ{ť m<(JlXŠlRk 궩<#VP. x7ulϖBfL족 (%n~_#=֟W7UGk3u/FG13DŽ?X+A@Ĝw<Ic}ngRq8XI^YRE]]s]7]7~#ԟ}s?(d&v.gE:?/=7U/{ң- q0-9uY$N}SG=I8k%-G]RE쾠cZjrv+%ʫӏЪRݡs<| FCAM-sJO7p6KVe=LLqFQU, Rz]Nz6;L׍NOUYpӗ U1s}&4 jWA"Q10FGy[f长{xݳ"/ݙ$urĹb ysu@[h9Ȧy)uVc_^+}cCo~/PiwRJt^f"uNcށoזՉ|g9\ka"ʘSk) +2E:1[قC~$'90:IB\72pXd~kcLLeLHupnEo⤝#2>㟟k1Ⰵ~7~ѫ爤\'mj)Ї} ^yE>el. 0EnO}qiNwUa9;R-Gu~_WF-΄:)v4쪐E꜆+ڵ%R>Rj;Qtkneʎ!E pZظj2111qq1J|IT@D$S̑U6Q$4ds'0Q-f{ҟiHMIakhS3UI3jϽ}qFGbUɍx('Vָ?8jzFfx2CcA)j,qކ9[횏tPW󿸎$ɏx~1si)|ߍxu_y|<[i{>9#*S_~:-}So9ڇڀ2O+?p!}b:e/2Yx?|Ϻu?y0C-n˱RiP;72%,o=,=q[?~6QJrPf'Uw_Ͽ;ٺTMWo|ح_[54kvl۴Wl !ja8vqdžXeMr8г'vMbl1>>>117/lޗ_~=s ~e>:oK꤫>z+czxS? . o_[OuN Srsa-0a8;h ";˦sF{}Ɖ ;;=ZxA>n iJr+>Dн}oyfC_ SN,cVsIxCp@CAAAjF   5"AAAN.!  rrA3za|FeI #V "D/}GIoG'ppEBC}镑W|[饕Ż_Jx)4 H@Nyй7Y]1DrO;?vfhf,ٱ Ο8žyƧ9]!c,K3y#:ZKƧ #|n͇y?zn}yb ŅDuDK{oٷ<^,Mo{qKq<4.b=FbaO"a}~)FnQI.\kș/RK>yؼlv]jD86ŵ\^舎{pa%vvسX.F<yZz!#^q,|O?Wn񊛿O_s#=E]<]4~՟}?4Goyͳǿ*`-8|ozM/y,'q'~b|>>1|7c@|ɻ]$DuWw{߫=g\],N“=w)]{Уݵ8>z+}}C}Cfwq‹W~Ͽs|7O7}>܇'_{Wn#Y#<6QM)咱D^c{Q LWd,^#N Vp`BJ IM%4glE3+9Fj U w"a*rLSuN8%G"NBu1tXOQ'aI+6K4JzDrrG5Y-c떳 ͬ2@Dq6:A Ve':( ǀ]O~Ѿk4՟[06S5G.E}㷇O?-oy_ ~-_6ՏH3\|[;E xCSO~7ܣ/,]/dgG'-O}+?|OA.M{=ʅ",V S  BH4&ԷSld{R p6WNj & FCAM-sJ_ft. ]jDF1z@Gtܛc[UU Ȳ̇ 4Wm#~~ pZ]`Z, b^,( ),%hG jKRQtFaԊn[ -[DwhVQ$46jt6>;wk9qgv=0p=X|RsӹLDanG^3oF>t 3˳"n#t׽m {X[|7O|7|#Fo}7׿{٘~7`[&oYfqSN [Czm@ٌ!}b:vNPxs =4x`zB{#jVsX]^T-Jj5$[]E s؈^,V5)w yj Z]4JXF#Te+k\RruiCc6&!t6>;X,?'&&A}6/{9@yKmoum#b%>2} v :vR 2;<`Dz~BAA-VsN@9AAi4gU DDRfj1 2e{F ]ox]- H69vRF9a8h5   5"AAAN.!  rrAAAA ެ q?oW=zȒɿ}ڿX꣜)/j/5  fcBRtD^\шC'UM N$W wY8ZJo',`簈<Z%yLF ~G>gO{s?[n{oiof_2A9BX~bM3|&dgs;[-(wv ק2Hd:Pe0<Yfu0tJrb\sDD}Z^ʴҟ@9U^y3M~EC}7+u 7}iu!z_K+w_߾/p..sn~Λ1[3+xSQ/}37i|߾gˋ?~G~f`@++s?>-$Ǿ_S}"\js'O lkr8٢cDtO0~3cF-sΏ_8n4:`3za||Hp04:v!Q=m 8#s<2nI6[&fY96;1qq.^a`: (zm؁ !0]݄'_ǫ_GD#H$rk1~' EFi ny1~zk}/ w~hxnܼi>clp;>+W.\x_<_7?G7N^sO|X9Pzx42t)O29}G>ߍg}<}O~~Sɂ {XlJ\,YBkVm!Lg q[(@kBJi';%tO2`w9T;!_+nbTg6;W.@$seiRۀ;?َwA=4=?0vH X+C-Ͼc8z/3e IDATHy&,UuCn[herzS$UO)?wW^Nf"W}3~d~bx_bKr5m]~G( SiȀwPZXK [&˖Y{p<P5ڸq*eTUu9L4m@. q਌$O'URqzCc!`T-=k<XBPN:/pUq |&Q~Խ{LAzVIgʾtn#'vJc7#j酔vp`_Y+@l8PC$W0jzm!S?CRpvRO>kxxg/f}Gn1yIKׄ'믹,wcC#||ͧfPwoxl^09[` C,^^Ho80JFԱ=n]@OOyEbقQU,KaRrAZ..?zzeqcOF w~53{upqe<˙Kg gGrwvafm`7ڮ:oh540v1=[[Y啩;OяF X6˶s7:}_*24} 7WP[tGύܺAxI EE(Zmv+t~ml Hi!}Azcox_w~&f~3Yxߍxu_y+!w}1]ɏ t3_]÷]}uվu|%1O~_\S;IVͯ~~$/G>@~ҍMM}f9;GGmw<EBݰ17omo;zPmW?ևtqy' A\+~W` [ M.=-]`d,#]ޑ^inGĨZLnw3#4rbe 6ZJ7\m2׎KUCvX,] '/u#=王Ox'zκg k`t'{w ;hXp  lQ[?A9AA ؐfPLKZdtpqa8!?}h- VXX;n) (cGxE9lpEA9"AAAN.!  rrAAAAެP6f'd+e=ro sG/$wTءR*g>Er./X"[ow !Rzc=Q mO RHbًw>1ھۊ[<~a Ut< }NR=2ljѐ.Ϯd˸Xy{찇;Zq-%'[+KO{Yæ/zGÃjJ)%t\/Fhġ742i5 rR!ҶB!H-*Ʌq9C3vgLEc⏆=FbaO"a}~)CȀEM-MUgL8 !]]\ё@xGz@dƁ,;,M-~/V\X[=Q-̥+[wvZe,}"',_` Dd8t84m~s@,My:ي}kD5u:bj1,uU]ww[xUMOE`MK[VNĪ&NDAer82٢9|dQ J6[U,2U`TU) "E2;ScA3R6k&Fbx`rE7d{]|2/ȑ+ŇL'Ygb-U2k<t%G"N+ H2t^_Oֿ72VG-*ng `J!ߠ4Y[r4\G3Ӥ1 hr+skԨߢc%Eǩr;R٢)vKEM*ɝKDt~U䘦,q4G3iuF)>)hQX|`оMgS sNQUDVPQʨP6ݦ|(5&&&v {=6`Nr^ +Ɨ֒Ybqe*ly:r"8xג7r`vYXeVhB&"m(a@j>sq$tuTN5266u]U g'3'˯L.wsdi#1f'1o LIo#Aha-Qܐ9ܶՂXHH\UV*Ԡ6aӧiSmy7JDH U&9bzirXD#ꘑ<TIwggeLgs-Y6N&6@ йPWB`W(uۛE$W0jzm!k]"RU@)8+'5T;Yic`hq,q!o7wӀĶH8ҙ6keVN8sj̷e(lOsTu^r&3e_F|:& kkvFq;;L{z5sD>#gnUK/eYf>[rX"cP\پ _AV(쑴o80JUJeuYlb:>Z.V(Ԡ]ZE(,2NI+fH {ډ[Ԏ(>N˩"Dm%u]uqn%GHJ@pz]bV'9{ٗ -#dYƥ V'dGGIo^f،,ct5S* "YF|3 i/ֆ r ")MV NZܴ2 fZJgC9"U#6qDK'_Zu@`fz!+Kd>?>~.ਮo>IH?J8%Qd7;P˻ {jg4UĹ.ȼ1ʠyw.5tZ;znl4` るi Uss25ٷgN9VkkRԻMb~>TR1>u z}A50z'k족M3@-6lݍ+k\Rru=@zmU +X,[=H$[玅$^3b*3׳kK? =GZJjWcT2eidJG6 NSlYSR2.y 4gB` $ni88wb|t&Ap X9=Wo@S#S/VNs~k3D,c5?3&s*d"2dΎXXqx74D9q0wtة!eaĉ _~=ȥ%AGK72C/q"g?Gir :vR a8;fþDA5p1uB< EN䰨[!ӠF   h̨HG SNΓc  r޳mDap=xai;   Gܾ   \OB$.0*;\6r>7   H[P#I8'tR&{A;ajCi  wF ix߰3 ;BjM;G,咱D^;/9:;D '"!Rzc=Q4z 8x[@g<*F'a1Wx ]뾋 HL$!7v[Gdqhc#L4hb=FbaO"a}~)p;mx?i6-YD(},밈aZ'cYny0j9r>$3iuF)GDWdj1,ͷDXy۪.%y,O7Dt~U䘦,qJb!)D:bT{G5QpG5Y-c떳{!g#2 6Lht~$E B888‘! D9US+b6ȯLOnTD/p ցHme٘"9|[rIXF.Gmng\AXS2)ʼn*-eގ;'bxm^ZH)?1[8^꽣e?:9-6+@ufsDZML}^]jOw٬q-lQO´r>gPJVᘹˏ[9=46FbGnF)(K(s+S9 uG:(<W 6 @}.bjB Z`x#-I""aeVVӗ~'ayuChƁޏôMgy:r"8x0͓ J*>[wM*D2a\{[>+jET7Z8'sq O5632B1{60ȶ n:u>ڱ`oRlep$0AvaρKg(gspƹ=QpZz!G+y6p6KVe6ZޫaY~A]@9A FUpS9"^y3:H썄=]_M]XҚN_oJLXlbL-XIOOyEb=:g o80J^.="AK`(^&9"JuMI8"')ͦt@:JN2b9jRtuр+n ,O݆ٝ5R&F!QZmv+tuBtGGRne!V2Lj-xWgg62nmhh~}ƨΨ6[BgMbF`4FOm\%͹Y91/1ZH%b9]-8ǽ阌euZ\̘1 ςH?}_MR AZjXkwyG{7rbe 6ZJ7IMDalg> &m@f{%e񉉉6#w ;hٔBDt6'^)cTuwZ[ifivr!'ç . GKDBJ͉Cg=Z!tĈѐ.Ϯ?C8 s-^.&gs1 X {FZtHuZ';Pt>ԈzZؘOf|'~iH_ŠKY⎞ GܾQc%vvȑ y#Zp Z B˖6>WUz F#>9Z݂ק'֏4ɞn5)J}[J0[ȻYd{z|xտt7Ҳ~}|lG]QOBvZzi>-OlBJr0:8DM^Er>^xй\H#'NZLo'Kc/om3nRZ 20G,2U`TU) "T%Ve&J/ydPE2(1ۼn9k9r˥-xv<*{VPtG|ItEI普)!t>$3iuF)Vw&k 5VWKhH& GEJAOb-U2k<1ZӜ#(Z/n8 @d况j)_5yɷs[)>,|MNVG"̅ Aˊk5X1/>YD(Dž }E \. .1M9Y6+n{@/gGt<7dsaqћqURqGC &BlrS1ErAwO(Vg.N898h>y6911qqf5OaF. ˭LO-f4lGSe@DsYmpbYy3+t j:?r`U#|"~k-nNNoTSrIXF.X3vՁHm q`2-kDs%Fʱٸ"}KWcyeU\6]4? qX|`{z6=05VflR GC#m_s!@ L+s@WZdwA`k@ohJ>y# EzgZ,LT(i:OMLcZ&FB,Y2x)-pf!  <@jE* hIէtU5Y镂 b9VWWLt"26E^&Ee#8ZQ(  [Ӓog\&T}v36[W\H^0JDۣA\;2!y>+ʗylKm8hQWrt9{hl<]P5QQoTpܩFa99_g'q ciZ|ZAOsȪHD,4Wt10`VZZ.j}ҌR p6WNj ލ1!:7L筮#ҁ#=6tкY9ϩ1ߖ9ٺo6xqvN' kksD>#~/Y#?h )VP% IDATx:&DpGτ#]rwB ԈzT)nN,fK!Gcc?C箣oXB~$'90:[D"۬rEFmwjKEV-XT^&*ど宎0;?_;h̃NԈTV O˩"Dmi)'1s{j{o_,@DHvND#V'dGGIziv4s + [^`ZR]$:t8NDEtk8?ntM9ZQ"`y(RQX:J%|B:0jE٭֫]ԬdoV_D2o=m{\VBdy$* 4i',(5:2EjZ7~/X~b4bZ`\ uhz6#莞 ؚe{{>l#QQemTq˅\{hls'V{~.d[z-ͳ qAA5p1uY_8TqyV$L-f2:Z`LF#WztAA-3fTMҷVװd{Fԓjjt  pAvrVmy5 '>L# SF   z"9vQgᲉh%;AADQOY=4 S|oJCAA5"94ƨrX"оbo,`簈<Z%Kd:kMl{ҮP\^FV=4^:;˱A!ѐ.Ϯ|ށaRJy\eh)(-7[<~axW X,u'nlKsTBh#l z1.uZh12t-$wTء|yoC'a1Wx ](K4`hrUޡ0YwԺX#A$.5GLK[Y 3!;Sy>R^+DX]Θ[2j&st*Ry)+ F#>,"C,&Xh802K3#z&z,KۓKc[GP/yBMǥbiUbB]7V#lkKΪ̅ GGr&ؼ.(ŗjBޖ]e9:'KpS|4!8Msg\BsFF]$:RLL~" m%yuG|ItEI普)3QR7 Ի=59C-eƚǥ,N<2 5l)ӁE:2 qn5w#֦a.aoõub&6rqUGWqG{40TdfсPE1+Y6f&&.'U[,L'} 2֚莞 ؎ol.L+VrBϖvQ:;Pұt kS-?52D0J@ʲKSU.uV)P\֭6]BRղ hSvVg.N898jy-OM ݢu0tYXnezj1՚VVw֖0-)hFkxm^ZHmwThPkwBQ"Y,<,2\,f]F FB^W'g7*Ch`oF@Yhw6+:VN ]Bqɬzd0Khrbbj8"~+g\o|O]dߕjɽ^ xi5Gԓ0TR>GtU@G$ra*dCF)(Nmn*BU[LP~=fr -h1<-ZIu%$P`jd7CzJ]>-eላ}`Б+t!:W]0B:1hL\h#鵅xNiإlnkkR*6c+E; 9V0$ƁTUVVJ8sj`sߤ@l8Pn^=G :kEi%b 9*.zm'Z̎Ԟ݋u!pBЩ\(Rbu0PЀi&]fs;VRoQt&,h]tVyo %$~]k-NC]tmh(32]ZφOh_>[QOY~*e Sl "{L"NN.G\Y 6yL$VP#iōn(r'| z852og;(PP~@-yȎ3}_)t:e[s4U'Ql>?6 z岒 gzXӺgx 9WܸVe18xRtͫOOECP"Nh1D[v!;r8\5.?Lu P,['fɅˏؑ/W)cx`?,x}L]8ճN*5+GI1D\2\A\p}a dyy$k e-yy[#Dq0! r(vH!v85Ww38t |vK~ն x ac.xF눈knג |[o`>< u*QcdꦋNd\lo}EFޠ\To6~v$`]T.tL5V-BFc%sR/脵K0Y6i:ߞ[fC[(f+'Y{59DoomWdBwPt]hMj[؛6j򅴖xU@]R9_Hrt$AOx`E; I:-3OsKt N& 詄מDC0vFae/?\)_TvS @L$n^'1ӟWi:Pʦ>|KyGݷN+8MR,Q:ٓQRFhP]܆""p˸js;0 /{偮C|%-s3h^k-$Ad亃O?~ˎ:B4%v}֑|8&;Bp }إ7ԁi[H\ -l&JF)2߯CO-kmS>R#4VQ8aS>&.>S )௨EW1UMbi/m\>\ǪukGӐ1'`OS>p?om]pXZF6fc|(!Π` mg0~Fi8}0'>- ƿ k.<[H<>Q⛭Wsj1t2mY d>`0 wa_D `0 %9NtUDnwC6IZ2."^N-[O! `0#&Q,],qf0KXXMkQ<`|ho>?i0 `0ϳpmT=xE7wo}xz:x8%],f4E[ IN Re~řCS~Ǽ/nF}TCKe]V4vwpfAҡd;)g1{zc):=m:?􄐐HS}L?rKd1nuc~N^I iMM\nցsfώ&WR|ZI<~Zm9&'2]v$R^W8l^_ꃰ9c@/"{xD|9muD|{͖;v[‰Ou] V#b=UM2L~9rbR?jV|)󩍵S)I]Av(r½z}0|֜RكZ>G¤l;$ي<I:^(k^W*ơg˕ x&E Ӊɔʁnt'"K:S?>ܵfk> ҹ1fY!Y(i~{[;\.7RL*]v)@ w(oTEgKqn%?TNn-ްO!u~3^0,D1_0>ߡNj1wU͈vJ6'V5D\UU] /ߋ 86YFDAx;K2"VACr:|Zx3'Z,vWES{ʷl$'3h4 t\n8ޱ>cT@/lAiZ<720lŴ8`9O.Jq0hl:\Jd>-8aN< 5}( 2I$Vag<ťTq!X* bu:&^А?Vu-'cv:A@@~o`Y ZVGT<}g;2ͧ+TG on9\v@ .5_.pP7&f ),[dE|1B9D7;%"ll5Q{:pT(i Nc)9^M'N@!!qa8CC%]gmʾa.uR/vf 1p^nxIm{ޟf>ɳa'[!z\F4O&D F'iFu8 lYw rѾknS!X)&xS3ҕ^BJ|b!AMąq؄I_65 &H\pAY >;R"tRMI}.gȱRɰU R~Wu痥z% ߾x-RO:ADywgYgPWKE;Ii+sn>uZHr!SmLSs5([o!/gtPsD.gtif8BuPbNIHħBZCMT.T|z-p&k;QJjde?Bx#j3 OӤX{?~u}lc bϦHY Ǜ}W^= tn"%x{ 7j ItXpR"W*2==7 KD*!Z|^) ĨWDz1ADq@>6-T繱#߰[*d$Y1=|Q! CZTB]hMg= MW,G!aj,RڵE3RY[ԇ̄=fi^\m=4druD5:5*1^od_v@aA^GVLvȻ2׍t9RGqXLbf"NSrޜ;qF׹f!pFǖ|1Kc[J6n:G6-'s+tFS( tÆ ?0#Թ*6ӹU ik؋D#dY,)m=>kq,`:}<.;vOd & A\V2ݤPb#- JMWȌ ,Ɣ09@>x$ ջIHu*Vdɋi/~ϗ`A:װk:QHh-S1B"7\1J&^tY\<:U~<0qZWTp 1OZ`6Չ{vE@PWɳWxAAl&$rHP^j_K_2;f)?_vQz!;f8te K^Zy I@! IDATnO#!3;Qc%d4-yqFɮp>IW؁gϯRSuXq4D*!Ѩ弞-#'xD $ֹZg3=A?8[mOz!1e?Q7W f}9=OɈ ƿA; X|$9@,7k#dfn4zbR>+勗*6_f^,fC"e߯?e |m_JqDLytV~0}p2>7CPw1ULdN2/ +N~:yCPyWk>I2 i>h:Yso>q s|gn7J(2 0|ED6̼r& Ʃw5`0 Ɩ,< w[(`0 7싈`0 `#B|ױyMWEvGJږ/);N$3 ݓu3댞JL2ZQursKT; `0 "bbQC6+8"N-/JNw"]b&Yfy23\gV:d}1>Is`0 X63s戠C9a2^o[;Lv!fYio'%ʚ;gR"b.J<=ߪVAwzL옽Vޱ|/}Hc/-|jtz=/5f$&=9ϧēQ-u+C'Ybo2̟mSbFSD|1Z`S$P K}p'3_KroC8]Nqb%Y%{iK;$z>\GՉ{-uRCxSDY/k;%%|D\]h;0H+~ιRR*N$> g>)': zyͧ_5G\V˂8zOz;vdGw􇐺\vd墤mmR??[ !8VOcc?rbR?jV|)M@JP){O`n "8tߒ *tSQbmr*}$||[O-K4SH_R<\S2Bbt/QrgSm|tf p]C"x~PܲvQdJ6'V[ t|kTk}2P_Uo87ϹD$)2'q0 "%(yq|u܌?0BG҈k⢪` 'Uvg@,OʮLN<72rH 9:'K]h 9(sctj/K4˚ EDg #s*ތ-3:珬Aip^u@lU[2by}}㿿=e+XbeA}[]4}A9Kf@d HޱCylG2)zRAu Xğ"AMą`㑚-USǛ bVol!)u%B Ej"5^oz589%B|QJ7  oe( wa`d0P#k=<4羘Spպe^ q]QU@\àvD"E#j3 čcD5{]kblϞGBrr;߶DsvFGb5⍶27UӮw)XFiӂ(SGcfX@6m||z;!- #|&R_>Z⥫%ߨ=c;`v>6Rg]7+[9-uzNJJŴBA:<4[ < ;(fv,`'=lV}\~̄&ά#gsZPSއ ɳ\%o7̝SvF_j<҂X?G2J̿mDPu: Ocj*Hҋgഞj];(cS5l;M%bᆳ$y 4:w >Ơc,6t.mܛIͽ=#u*ik[ohH͖ nH/]}/b6m=wΠZ Id xb4m9W מs/iA N(XiHQ=ⴞr^?J{[TRy̟ Ɛm}-cߝ{tna?\)_TzOȽ&&Re߿___O_p۔ϿD(pWᄑx3ѫ*ybˏOc{hr>;`0 `9ygA!&.ϲqD2q `0tG|l[,|3f^Ys `wΚc0 㰰Ys `0 1}1 `0uDHҒqo:6骈i2"h5B*KxF%I˗KlNqb鈒xijHo\Gwuodj(2 `0'􋈋E ۴.{\kNq? xAm0{^\d w}7RF+!u"5 `0s2 ̝ 0,]V4vwp#K:;4bO<ߪV4$zl`_Dz_G\{G^Y. ݿbpr"ӥXRLH۪-ir1)r_7mwB|QJ7 V.1v_ZJ7ڝq;oV8Ypպe^ q]Ü<R*xܻBԌ_HRI1?*Qzȏ* ?~޾Z)tah|^oz:$Js_|ܭMs1?okϔ0*h/%R+jQixX\cr!F#vnEA ضĈe@DOd(46 &tf`q^xZ"~ ](&FB^$putzGٶ =)#buGRfNS†4\; <'xE8 l q)qll⢪J`F)mfގ<dij́Sӌ\<ݺPŴ8@_XIɊ#EB <G/s͢ c.Jm`xe]xbiux?#sy`Dz1ADG=7h:xb`-A>< ɤ]N`N" PF=`"y~״!p햀]2/"X6 OvGˢTIGLtɝ4uI|U5G:e};5[}ˌ=B\ ;zk새W85@YHs1д K@O!/}^u8)GX9UNqFxb8+1 Ɉ ĭrTUf:04^?_rp%wJ@~9@H|PtbN8r:` l1h:A(rqG`”Q(elNVH-~Ntf;@wVm[N6^tWܣg4s23z[J-; y{*އ` TB(iưXG1^L},ӷ<>(2zӰ߿'y|[uyfJD\wִ*6AX!l&-d4V2!rjQ/?[G#"avV[ex-7Sl/$"ݷ{/& GٞVwI's}HzG9c sN2Ϸ&b!}wbRĝDȌtsULwvc0Fԓ*^3X]2NJ^\~Q*Raz~y*5Zsg!J{nɞҝV2"Fږz46Xb!\eD2vzjY"yDcTɈ EQ&G>y>2xijt^d#y0ɕbRەI+ZTEw1,B)Re!O/=gUFrz0p,OJ<'0"`0 #yaLL'#}U8a7r|gn<=1U}|:%Z17J\aۙI+[T*nc0=1D|lU;K<>Bg1S6k`0-Yx `0 ƿ "b0 `0.눐%"ul^UC8%Kʈ`.! Z>uJ%Ci[DJ{t]K%k4Qt63~>l%S8q2 `0'􋈋E ۴.8 4 Z:!pIaZ:G;(]]2:TW%F=KЌN2Vg:t|A`0 X6ϳp֛mT뽠Ͻ>RQ1{`ZC/_dJiDĥޟ y_g+2z7F%ɡXrOG $/JqkXqFEI;o\c|C~LrcTK:d5#܍)$\j %wVNRT6szϥ8'Qd_D|Gඞ[rCAE4֒/ '/\/]lRa:'ʟǧ6RpW[sGRd:[M7F+~&fFmnf$}Jv:w`b^_<ψk⢪J̧Ç@|/vwpxe\bVoÿaQEY8>YĒD0Dr:|Zx+8Nb t\n8.s.=RvEUiRT' "v X\Qux*2BJ\X^<ɉ#TF֗+{{ΠK~әE/ IDATHvm$s :{yX<hqڋe:!􇫢ѩz.YQE'Z4AXJ>M+`!?,;R(1y3Bˎ5#d=|1QvәKNd␹f@M\Dy Cwlj<֩9Om8hY%:6_ılLiaPQ sȲIN شGi :Ƒun ˾/QuNw^uCQ;3| -كB"|Kɴ~ /-qdY?#t(?/$8Ĵlac1..fPF;ϥ ꍑf }g4'_\l>?6 z岒 pB>^G42~\,)V!( >12yl(4e 금sv"Zm~sC5^4wb&N*8f!|<t'ElbG6`7wQK\k1LL;O<#d-fL۲ I':"Aמ xNPǶl2*#&v85W"8t |9PIxG2Z۶Ks^$߾Z8y08t)鬥M],#܁9k9$H'Ę>񶭸SƼk;6~mFkRǮHyٟHbHF%|M 6SB݅yg:{/樼+6G,yCՍuD7>IMzFwᛱ⥫EFonUr^OXͿUr h ZY*i7ѭ#1?4Pg_nZ /9$n[f"L@S p==`jp1\vHohw>bXtEt,e E;vEY9zgn{]#Knmnߴw؜f2XNjM&8mX8b"Žk{CZ #;e)w~>ӏR'oEfZXj.VVܼҚ, ZOS>o&o Rrg_x OJٻjӿKB H2Yd)㽾Fy/$ec*.O{Eb ߎbVoÿaQBxFoeCJd:ѳq`x D2;F6{l$'3h4 t\n.=juT9p۝mh; w;*2-O?GWc{j/W%<$zɗM1i8b$` ťl47// / 7ZO*nQ |,5AKv.בK:)˲Fԇ˼6RvFf"cw쥉d@(F--џDP1#qMj6-[MoԞ~~G{Hm[i Nc)9^M'a]28`0NdT6%EF$ض\//|beA=FEP5Ub4I$0vHv|E̲?۟5[gE]'nj/'r9]e+dtn{ EO*8kKty&h*xb`-AE }1NdgS~ȚߙuDlN묻mk_ I/Rz~u%IhvF]_{1`H*O>\ef=Ă]ǩI]btC^Z d|y9B(^:8 _鮋Vt( !G0A}As?a$J8!|<tĖ(8YgE ^f;y0C ut\؝b/|)vԛs sJQ 3IIbDZo :)ٹ&Gxw7[b#- J=rhONhmQA,i]QV3<Ǥ/).̑Mg0usgpq #<=3j kS Pg$r$p5ĘoUdCD*,ϝZR2r[ؖMI<900 )𜔾ײaϛ^8uDӕQɅvY$>^94k]OP,JzKo|7Ξcә'tw-cXv'UNg0l@E0^^=UTW+W:/K_"Z}o˧Wi)ǩ h2)CH^{ LF-o)FhUuY8!Ehas__/'F ~q^wz!1e?Q˧z97;F ˫ 6Oy#L{ǻA` @yƭ&GjY̕K/3j/3D2"HQ~}}"`0v,B)Re!O/;[TtS9sГ+|kYC6n'#Xx1"`0Nb'c_gͽLfƁ`_D `0 \b&I8vC͙dH<>``0I\޸7[!`0 `0E`0 `0]`0 wa_D `0 ߅`̜6"d>b"Žkէ9qS}Q::IM䐊WRBZ%9vAunScsߜ.!1+撪#߳jiHkzONH phǕGþϊâlD?˔KDw^mJrMۅ5,!^\98@z/x@ȣ41GA8Kg=#$&~!t.]|+Gʁne9!Eje"9Ub0^_w&s.+J=V/R)-;(W̕a3۷ərQ$3W*zWmz| /,Uk@^1rY#6Y+5TO_'y2L>bMcX:ʁ{irHxޟw'V҈;} *EhzqZy>WȰY 43J13DGy S,[́ZC/g6-<1 V1<3jPL I;jY["VfXBf(L>D&ay󏆏٦<'HtğqǁO%pdĒB&JRNN3-.9'-8L1xj p"Ig yjc:=_)RPwi(SŜtvy~YR"O*KmYn\zE]?@j:gk]_7_\[{ث]o$dh vd*<5YHT0ptR@HHʱyR0,+󄖍$kQϥaf'z _? aa-@I 0kL!odP4LO j#yqwF H fwWLqO|k胊GE dq#s4;QUGz-DOƘ' ^PR&ݟݭr䥞(QZ#J@S+y62J?vE^2 LU,SDKqC?*b4(mG*upI'.PbDauӒ5e\}*^resF9@)7<ı8y}FD WSSPq[F% ۾z+%pjRfuݴhtqMRggҙx}#Q˗ :vZZɊף]MGSj:%Á4 !+4}~A*mbRW9p[%}eb\V7JM&Y̸uɜ,"|@ʫ뵺#]ѓlΆ(ej~{I_8m^U]3O +lTsǛ 5;D}̩qjG/}FD 'WŤWo^FjM`qsVJW; AwAJ<#y9{xBV~@s9",I1ǑUXIDAT )ٽ.[8/6M$ 9A(K8IE A"$ݲD~2Ӟ?2լ4atXJs6G.s6 %q,>э # ^٘˕Cc  )6#jaLI @KzX&F۾kwgĵg/&&c,ͫ4ޯO\t"'F/๙(Kg.BQ"#QyN4ON~\%SY0e*E#b&B`;|&'Io\[̗Gtc@7$[dz8Q\w"k壓$s"/f%g'eit8^Aoax2C`v{0V&䛭iiQf05#o6͇*3♃cgu8M<`8G]DK-ru:VFD `0 n47}J qfΓmq`#"`0 Bzoߊj=Y}eO c| lJ `08ξڂD&I"ooUMHt>Z2Jţ$2.nZk6 u}aa!t.*g()Ts k3/e=3⒈mߗt6iY6T CyBmT=/BuPRk>$jBV9l==׹WI *~ք~P{ԵP)哂ݸSsʩblŮ-㞷hJiiޞלDb sPT@'ʁne9!j R&S.ek|G{9LRvDO_p,.IbPyI{4ϭərQ$3W*zWmx:0؈`@J 6OCLqg^JTU~3\1)G\$jo[jhB`nZZ<MSNc^5RZSFf0_b./kC^Jd2@t)THjT1WNO{xU8%Sk2=i!lƖU-&qM3dK& V3۲, E,ky:,Yw~c~V g5RżFՆD& Ն:1j8K"~ߟ&'%2Mwtğ˺MF춌]:&@`;aP:OgFz&pdq/dDx/ 5?Rzrɤlu) z.BUG;^bA<0&N멕uAf~F9qGĖȌ?cC Q˗ :vZZG8Y)e]Eё>lI]Dnjx:vE(IXIal(ȰնRyyVwIƿ[*GM2#"&u^W>݅DY̸uɜ,7#|j6'~"D.sEǗD-Gˡo.㸏ͤt&ީPjoܪ7hc>8g5\Nߣ>Ca#" i hrf %]&ݸ9 AJ*+ۺcfNq#CqAUESR8 2`e '㿙=HӞ\$A+pNXqY@/f kRaS9kH؝q*QB^JnR]lvE:9z9z]5 X0'oܙFд|CUpıGʢ #b0= 5QuDY9Ay/=f?f^|Oƶqm>8>ضcm2M,KlMsXx߶8EĞ~س Fސn5zf#9@R"R+fk3Us Ffn4RG9uOO&6A =5 lMK\460^resF|lä#DȼM8xxP>#dYqrr63߄W+~J'K/:8;dn]sVf3|rw1m^_L_-ڄ05"`0vyMen]ѓ)&ָհ`0 1KU3h=5sl1 e`0v2+d}c0)[xd U|rGs d'[-Tz1Xa#"`0 }a#"`0 }a#"`Dy, `0kUS tzqpeN'bU/TFO7O uuyY挸$b~7ARqnn.r|J?<>iEKϵO/quJdG1RS swQ1E#OesnsLq؈`0)齽KAWoɯ}_ gmW!9Ub0^_o y>b ,*O N'c7Zxh`qrdK`7xrXpuqѺm{zt6"b0 k hO@4ҚYye,`xIēlֈ+ۨCg yy'7rXF֗J5$O˫ {$2X F<1 `{MSi4okTk&dR⾻''b{q9<Q ZM\8.'vV M]ˉ wEPi}8^Qֹ}ہͬD$b528Z4 AJ gxal6/3aqZ#q#Eҗ EOʈ ;^VXG[(+)?Zc_o1 uȰq{yyUzUHۉt)THtğ˺'`V_5XU[1'dJC߳Os׻?|wz;g_9tG&&{I_''S{w~vQxOOJfo/Akh 5h)1<]}(nSbvqL DrzCӥ+n*ul7 Wctx Gw Q˪]_ֆ[ˌtP׺>?nϾUK&@`; wj1k3ST7*Kל`06Ѹ:!%Co>UZl>-AquϻkSz :O|G 1ĘΛJz.R@,sʇl@TzָjoBoK+gܪ?C~ [Z1ވUV? Q˗ :vZZITssD  `y[MJT 9mB`I(QVhd{~Y=C9i]AJM`qsVJÃJ<.%&Ez2j(_mq||J ct-2vum<oJg  bծګn7'&DYz~.OuV>:99Ky$+jqw!`#^1xޒu@V.zT %vR0)\4;IDY9AyP|YPy6"b0 `av#iY-P#b0 `0ߗvA@vIENDB`bashtop-0.9.25/LICENSE000066400000000000000000000261361370527161500142730ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. bashtop-0.9.25/Makefile000066400000000000000000000006211370527161500147150ustar00rootroot00000000000000PREFIX ?= /usr/local DOCDIR ?= $(PREFIX)/share/doc/bashtop all: @echo Run \'make install\' to install bashtop. install: @mkdir -p $(DESTDIR)$(PREFIX)/bin @cp -p bashtop $(DESTDIR)$(PREFIX)/bin/bashtop @mkdir -p $(DESTDIR)$(DOCDIR) @cp -p README.md $(DESTDIR)$(DOCDIR) @chmod 755 $(DESTDIR)$(PREFIX)/bin/bashtop uninstall: @rm -rf $(DESTDIR)$(PREFIX)/bin/bashtop @rm -rf $(DESTDIR)$(DOCDIR) bashtop-0.9.25/README.md000066400000000000000000000266531370527161500145510ustar00rootroot00000000000000# ![bashtop](Imgs/logo-t.png) ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![OSX](https://img.shields.io/badge/-OSX-black?logo=apple) ![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-blue) ![Bash](https://img.shields.io/badge/Bash-v4.4%5E-green?logo=GNU%20bash) ![Python](https://img.shields.io/badge/Python-v3.6%5E-orange?logo=python) ![bashtop_version](https://img.shields.io/github/v/tag/aristocratos/bashtop?label=version) [![Build Status](https://travis-ci.com/aristocratos/bashtop.svg?branch=master)](https://travis-ci.com/aristocratos/bashtop) [![Donate](https://img.shields.io/badge/-Donate-yellow?logo=paypal)](https://paypal.me/aristocratos) [![Sponsor](https://img.shields.io/badge/-Sponsor-red?logo=github)](https://github.com/sponsors/aristocratos) [![Coffee](https://img.shields.io/badge/-Buy%20me%20a%20Coffee-grey?logo=Ko-fi)](https://ko-fi.com/aristocratos) ## Index * [Documents](#documents) * [Description](#description) * [Features](#features) * [Themes](#themes) * [Upcoming](#upcoming) (Python port) * [Support and funding](#support-and-funding) * [Prerequisites](#prerequisites) * [Dependencies](#dependencies) * [Screenshots](#screenshots) * [Installation](#installation) * [Configurability](#configurability) * [TODO](#todo) * [License](#license) ## Documents #### [CHANGELOG.md](CHANGELOG.md) #### [CONTRIBUTING.md](CONTRIBUTING.md) #### [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ## Description Resource monitor that shows usage and stats for processor, memory, disks, network and processes. ## Features * Easy to use, with a game inspired menu system. * Fast and "mostly" responsive UI with UP, DOWN keys process selection. * Function for showing detailed stats for selected process. * Ability to filter processes. * Easy switching between sorting options. * Send SIGTERM, SIGKILL, SIGINT to selected process. * UI menu for changing all config file options. * Auto scaling graph for network usage. * Shows message in menu if new version is available * Shows current read and write speeds for disks * Multiple data collection methods which can be switched if running on Linux ## Themes Bashtop now has theme support and a function to download missing local themes from repository. See [themes](themes) folder for available themes. The builtin theme downloader places the default themes in `$HOME/.config/bashtop/themes`. User created themes should be placed in `$HOME/.config/bashtop/user_themes` to be safe from overwrites. Let me know if you want to contribute with new themes. ## Upcoming #### Python port: bpytop Currently working full time on this during my vacation :) I'm aiming to have a first release by end of July. ## Support and funding Bug fixes and updates might be slow during normal workdays since I work full time as an industrial worker and don't have much time or energy left during the week. I'm looking into ways of funding this project that would allow me to take off time from my day job to work on this. Any advice on how to get funding for open source projects is very welcome! #### Update You can now sponsor this project through github, see [my sponsors page](https://github.com/sponsors/aristocratos) for options. Also added donation links for [paypal](https://paypal.me/aristocratos) and [ko-fi](https://ko-fi.com/aristocratos). Any support is greatly appreciated! ## Prerequisites #### Mac Os X Will not display correctly in the standard terminal! Recommended alternative [iTerm2](https://www.iterm2.com/) Will also need to be run as superuser to display stats for processes not owned by user. #### Linux, Mac Os X and FreeBSD For correct display, a terminal with support for: * 24-bit truecolor ([See list of terminals with truecolor support](https://gist.github.com/XVilka/8346728)) * Wide characters (Are sometimes problematic in web-based terminals) Also needs a UTF8 locale and a font that covers: * Unicode Block “Braille Patterns” U+2800 - U+28FF * Unicode Block “Geometric Shapes” U+25A0 - U+25FF * Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F #### Notice Dropbear seems to not be able to set correct locale. So if accessing bashtop over ssh, OpenSSH is recommended. ## Dependencies ## Linux, OSX and FreeBSD **[bash](https://www.gnu.org/software/bash/)** (v4.4 or later) Script functionality will most probably break with earlier versions. Bash version 5 is highly recommended to make use of $EPOCHREALTIME variable instead of a lot of external date command calls. **[GNU coreutils](https://www.gnu.org/software/coreutils/)** **[GNU sed](https://www.gnu.org/software/sed/)** ## Linux using /proc for data collection **[GNU grep](https://www.gnu.org/software/grep/)** **[ps from procps-ng](https://gitlab.com/procps-ng/procps)** (v3.1.15 or later) **[GNU awk](https://www.gnu.org/software/gawk/)** ## OSX and FreeBSD or Linux using psutil for data collection **[Python3](https://www.python.org/downloads/)** (v3.6 or later) **[psutil python module](https://github.com/giampaolo/psutil)** (v5.7.0 or later) ## Optionals for additional stats (Optional OSX) **[osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp)** Needed to show CPU temperatures. (Optional Linux) **[lm-sensors](https://github.com/lm-sensors/lm-sensors)** Needed to show CPU temperatures. (Optional Linux) **[iostat (part of sysstat)](https://github.com/sysstat/sysstat)** Needed if you want disk read/write stats and are not using psutil data collection. (Optional OSX/Linux/FreeBSD) **[curl](https://curl.haxx.se/download.html)** (v7.16.2 or later) Needed if you want messages about updates and the ability to download themes. ## Screenshots Main UI showing details for a selected process. ![Screenshot 1](Imgs/main.png) Main menu. ![Screenshot 2](Imgs/menu.png) Options menu. ![Screenshot 3](Imgs/options.png) ## Installation #### Dependencies installation OSX >Install homebrew if not already installed ``` bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" ``` >If you got python 3.6 or later installed outside of brew: ``` bash sudo python3 -m ensurepip sudo python3 -m pip install psutil ``` >If you haven't got python3 installed: ``` brew install python3 python3 -m pip install psutil ``` >Install dependencies ``` bash brew install bash coreutils gnu-sed git ``` >Install optional dependency osx-cpu-temp ``` bash brew install osx-cpu-temp ``` #### Dependencies installation FreeBSD >Install with pkg and pip ``` bash sudo pkg install coreutils gsed python3 git sudo python3 -m ensurepip sudo pip3 install psutil ``` #### Manual installation Linux, OSX and FreeBSD >Clone and install ``` bash git clone https://github.com/aristocratos/bashtop.git cd bashtop sudo make install ``` >to uninstall it ``` bash sudo make uninstall ``` #### Arch based Available in the AUR as [bashtop-git](https://aur.archlinux.org/packages/bashtop-git/) Available in the Arch Linux repository as [bashtop](https://www.archlinux.org/packages/community/any/bashtop/) #### Debian based Available in [official Debian repository](https://tracker.debian.org/pkg/bashtop) since Debian 11 Available for debian/ubuntu from [Azlux's repository](http://packages.azlux.fr/) Or use quick installation: >Quick install go to DEB folder and type ``` bash sudo ./build ``` >to uninstall it go to DEB folder and type ``` bash sudo ./build --remove ``` #### Ubuntu based Available in [official Ubuntu repository](https://launchpad.net/ubuntu/+source/bashtop) since Ubuntu 20.10 Available for Ubuntu from [PPA repository](https://code.launchpad.net/~bashtop-monitor/+archive/ubuntu/bashtop) >Add PPA repository and install bashtop ``` bash sudo add-apt-repository ppa:bashtop-monitor/bashtop sudo apt update sudo apt install bashtop ``` #### Fedora Available in the Fedora repository. >Installation ``` bash sudo dnf install bashtop ``` #### CentOS 8 >Installation ``` bash dnf config-manager --set-enabled PowerTools dnf install epel-release dnf install bashtop ``` #### RHEL 8 >Installation ``` bash ARCH=$( /bin/arch ) subscription-manager repos --enable "codeready-builder-for-rhel-8-${ARCH}-rpms" dnf install epel-release dnf install bashtop ``` ## Configurability All options changeable from within UI. Config files stored in "$HOME/.config/bashtop" folder #### bashtop.cfg: (auto generated if not found) ```bash #? Config file for bashtop v. 0.9.21 #* Color theme, looks for a .theme file in "$HOME/.config/bashtop/themes" and "$HOME/.config/bashtop/user_themes" #* Should be prefixed with either "themes/" or "user_themes/" depending on location, "Default" for builtin default theme color_theme="Default" #* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs update_ms="2500" #* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive" #* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly proc_sorting="cpu lazy" #* Reverse sorting order, "true" or "false" proc_reversed="false" #* Show processes as a tree proc_tree="false" #* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available check_temp="true" #* Draw a clock at top of screen, formatting according to strftime, empty string to disable draw_clock="%X" #* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort background_update="true" #* Custom cpu model name, empty string to disable custom_cpu_name="" #* Enable error logging to "$HOME/.config/bashtop/error.log", "true" or "false" error_logging="true" #* Show color gradient in process list, "true" or "false" proc_gradient="true" #* If process cpu usage should be of the core it's running on or usage of the total available cpu power proc_per_core="false" #* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space disks_filter="" #* Enable check for new version from github.com/aristocratos/bashtop at start update_check="true" #* Enable graphs with double the horizontal resolution, increases cpu usage hires_graphs="false" #* Enable the use of psutil python3 module for data collection, default on OSX use_psutil="true" ``` #### Command line options: (not yet implemented) ``` bash USAGE: bashtop ``` ## TODO Might finish off items out of order since I usually work on multiple at a time. - [x] Add options to change colors for text, graphs and meters. - [x] Fix cross platform compatibility for Mac OSX and *BSD: Working on OSX, and FreeBSD. - [x] Add support for showing AMD cpu temperatures. - [x] Add option to show tree view of processes. - [x] Add option to reset network download/upload totals. - [x] Add option to turn of gradient in processes list. - [ ] Add gpu temp and usage. (If feasible) - [x] Add io stats for disks. - [ ] Add cpu and mem stats for docker containers. (If feasible) - [x] Change process list to line scroll instead of page change. - [ ] Add optional window for tailing log files. - [ ] Add options for resizing all boxes. - [ ] Add command line argument parsing. - [ ] Builtin updater. Relevant PR #96 by Jukoo - [ ] Add support for zram in memory box. Relevant PR #122 by perkinslr - [ ] Miscellaneous optimizations and code cleanup. - [ ] Add more commenting where it's sparse. - [ ] Python port. (Porting started) ## LICENSE [Apache License 2.0](LICENSE) bashtop-0.9.25/bashtop000077500000000000000000006232241370527161500146550ustar00rootroot00000000000000#!/usr/bin/env bash # indent type=tab # tab size=4 # shellcheck disable=SC2034 #Unused variables # shellcheck disable=SC2068 #Double quote array warning # shellcheck disable=SC2086 # Double quote warning # shellcheck disable=SC2140 # Word form warning # shellcheck disable=SC2162 #Read without -r # shellcheck disable=SC2206 #Word split warning # shellcheck disable=SC2178 #Array to string warning # shellcheck disable=SC2102 #Ranges only match single # shellcheck disable=SC2004 #arithmetic brackets warning # shellcheck disable=SC2017 #arithmetic precision warning # shellcheck disable=SC2207 #split array warning # shellcheck disable=SC2154 #variable referenced but not assigned # shellcheck disable=SC1003 #info: single quote escape # shellcheck disable=SC2179 # array append warning # shellcheck disable=SC2128 # expanding array without index warning # Copyright 2020 Aristocratos (jakob@qvantnet.com) # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. declare -x LC_MESSAGES="C" LC_NUMERIC="C" LC_ALL="" #* Fail if running on unsupported OS case "$(uname -s)" in Linux*) system=Linux;; *BSD) system=BSD;; Darwin*) system=MacOS;; CYGWIN*) system=Cygwin;; MINGW*) system=MinGw;; *) system="Other" esac if [[ ! $system =~ Linux|MacOS|BSD ]]; then echo "This version of bashtop does not support $system platform." exit 1 fi #* Fail if Bash version is below 4.4 bash_version_major=${BASH_VERSINFO[0]} bash_version_minor=${BASH_VERSINFO[1]} if [[ "$bash_version_major" -lt 4 ]] || [[ "$bash_version_major" == 4 && "$bash_version_minor" -lt 4 ]]; then echo "ERROR: Bash 4.4 or later is required (you are using Bash $bash_version_major.$bash_version_minor)." exit 1 fi shopt -qu failglob nullglob shopt -qs extglob globasciiranges globstar #* Check for UTF-8 locale and set LANG variable if not set if [[ ! $LANG =~ UTF-8 ]]; then if [[ -n $LANG && ${LANG::1} != "C" ]]; then old_lang="${LANG%.*}"; fi for set_lang in $(locale -a); do if [[ $set_lang =~ utf8|UTF-8 ]]; then if [[ -n $old_lang && $set_lang =~ ${old_lang} ]]; then declare -x LANG="${set_lang/utf8/UTF-8}" set_lang_search="found" break elif [[ -z $first_lang ]]; then first_lang="${set_lang/utf8/UTF-8}" set_lang_first="found" fi if [[ -z $old_lang ]]; then break; fi fi done if [[ $set_lang_search != "found" && $set_lang_first != "found" ]]; then echo "ERROR: No UTF-8 locale found!" exit 1 elif [[ $set_lang_search != "found" ]]; then declare -x LANG="${first_lang/utf8/UTF-8}" fi unset old_lang set_lang first_lang set_lang_search set_lang_first fi declare -a banner banner_colors banner=( "██████╗ █████╗ ███████╗██╗ ██╗████████╗ ██████╗ ██████╗ " "██╔══██╗██╔══██╗██╔════╝██║ ██║╚══██╔══╝██╔═══██╗██╔══██╗" "██████╔╝███████║███████╗███████║ ██║ ██║ ██║██████╔╝" "██╔══██╗██╔══██║╚════██║██╔══██║ ██║ ██║ ██║██╔═══╝ " "██████╔╝██║ ██║███████║██║ ██║ ██║ ╚██████╔╝██║ " "╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ") declare version="0.9.25" #* Get latest version of BashTOP from https://github.com/aristocratos/bashtop declare banner_width=${#banner[0]} banner_colors=("#E62525" "#CD2121" "#B31D1D" "#9A1919" "#801414") #* Set correct names for GNU tools depending on OS if [[ $system != "Linux" ]]; then tool_prefix="g"; fi for tool in "dd" "df" "stty" "tail" "realpath" "wc" "rm" "mv" "sleep" "stdbuf" "mkfifo" "date" "kill" "sed"; do declare -n set_tool="${tool}" set_tool="${tool_prefix}${tool}" done if ! command -v ${dd} >/dev/null 2>&1; then echo "ERROR: Missing GNU coreutils!" exit 1 elif ! command -v ${sed} >/dev/null 2>&1; then echo "ERROR: Missing GNU sed!" exit 1 fi read tty_height tty_width < <(${stty} size) #? Start default variables------------------------------------------------------------------------------> #? These values are used to create "$HOME/.config/bashtop/bashtop.cfg" #? Any changes made here will be ignored if config file exists aaa_config() { : ; } #! Do not remove this line! #* Color theme, looks for a .theme file in "$HOME/.config/bashtop/themes" and "$HOME/.config/bashtop/user_themes" #* Should be prefixed with either "themes/" or "user_themes/" depending on location, "Default" for builtin default theme color_theme="Default" #* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs update_ms="2500" #* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive" #* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly proc_sorting="cpu lazy" #* Reverse sorting order, "true" or "false" proc_reversed="false" #* Show processes as a tree proc_tree="false" #* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available check_temp="true" #* Draw a clock at top of screen, formatting according to strftime, empty string to disable draw_clock="%X" #* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort background_update="true" #* Custom cpu model name, empty string to disable custom_cpu_name="" #* Enable error logging to "$HOME/.config/bashtop/error.log", "true" or "false" error_logging="true" #* Show color gradient in process list, "true" or "false" proc_gradient="true" #* If process cpu usage should be of the core it's running on or usage of the total available cpu power proc_per_core="false" #* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space disks_filter="" #* Enable check for new version from github.com/aristocratos/bashtop at start update_check="true" #* Enable graphs with double the horizontal resolution, increases cpu usage hires_graphs="false" #* Enable the use of psutil python3 module for data collection, default on OSX use_psutil="true" aaz_config() { : ; } #! Do not remove this line! #? End default variables--------------------------------------------------------------------------------> declare -a menu_options menu_help menu_quit menu_options=( "┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐" "│ │├─┘ │ ││ ││││└─┐" "└─┘┴ ┴ ┴└─┘┘└┘└─┘") menu_help=( "┬ ┬┌─┐┬ ┌─┐" "├─┤├┤ │ ├─┘" "┴ ┴└─┘┴─┘┴ ") menu_quit=( "┌─┐ ┬ ┬ ┬┌┬┐" "│─┼┐│ │ │ │ " "└─┘└└─┘ ┴ ┴ ") menu_options_selected=( "╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗" "║ ║╠═╝ ║ ║║ ║║║║╚═╗" "╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝") menu_help_selected=( "╦ ╦╔═╗╦ ╔═╗" "╠═╣║╣ ║ ╠═╝" "╩ ╩╚═╝╩═╝╩ ") menu_quit_selected=( "╔═╗ ╦ ╦ ╦╔╦╗ " "║═╬╗║ ║ ║ ║ " "╚═╝╚╚═╝ ╩ ╩ ") declare -A cpu mem swap proc net box theme disks declare -a cpu_usage cpu_graph_a cpu_graph_b color_meter color_temp_graph color_cpu color_cpu_graph cpu_history color_mem_graph color_swap_graph declare -a mem_history swap_history net_history_download net_history_upload mem_graph swap_graph proc_array download_graph upload_graph trace_array declare resized=1 size_error clock tty_width tty_height hex="16#" cpu_p_box swap_on=1 draw_out esc_character boxes_out last_screen clock_out update_string declare -a options_array=("color_theme" "update_ms" "use_psutil" "proc_sorting" "proc_tree" "check_temp" "draw_clock" "background_update" "custom_cpu_name" "proc_per_core" "proc_reversed" "proc_gradient" "disks_filter" "hires_graphs" "net_totals_reset" "update_check" "error_logging") declare -a save_array=(${options_array[*]/net_totals_reset/}) declare -a sorting=( "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive") declare -a detail_graph detail_history detail_mem_history disks_io declare -A pid_history declare time_left timestamp_start timestamp_end timestamp_input_start timestamp_input_end time_string mem_out proc_misc prev_screen pause_screen filter input_to_filter declare no_epoch proc_det proc_misc2 sleeping=0 detail_mem_graph proc_det2 proc_out curled git_version has_iostat sensor_comm failed_pipes=0 py_error declare esc_character tab backspace sleepy late_update skip_process_draw winches quitting theme_int notifier saved_stty nic_int net_misc skip_net_draw declare psutil_disk_fail declare -a disks_free disks_total disks_name disks_free_percent saved_key themes nic_list old_procs printf -v esc_character "\u1b" printf -v tab "\u09" printf -v backspace "\u7F" #? Backspace set to DELETE printf -v backspace_real "\u08" #? Real backspace #printf -v enter_key "\uA" printf -v enter_key "\uD" printf -v ctrl_c "\u03" printf -v ctrl_z "\u1A" hide_cursor='\033[?25l' #* Hide terminal cursor show_cursor='\033[?25h' #* Show terminal cursor alt_screen='\033[?1049h' #* Switch to alternate screen normal_screen='\033[?1049l' #* Switch to normal screen clear_screen='\033[2J' #* Clear screen #* Symbols for graphs declare -a graph_symbol graph_symbol=(" " "⡀" "⣀" "⣄" "⣤" "⣦" "⣴" "⣶" "⣷" "⣾" "⣿") graph_symbol+=( " " "⣿" "⢿" "⡿" "⠿" "⠻" "⠟" "⠛" "⠙" "⠉" "⠈") declare -A graph_symbol_up='( [0_0]=⠀ [0_1]=⢀ [0_2]=⢠ [0_3]=⢰ [0_4]=⢸ [1_0]=⡀ [1_1]=⣀ [1_2]=⣠ [1_3]=⣰ [1_4]=⣸ [2_0]=⡄ [2_1]=⣄ [2_2]=⣤ [2_3]=⣴ [2_4]=⣼ [3_0]=⡆ [3_1]=⣆ [3_2]=⣦ [3_3]=⣶ [3_4]=⣾ [4_0]=⡇ [4_1]=⣇ [4_2]=⣧ [4_3]=⣷ [4_4]=⣿ )' declare -A graph_symbol_down='( [0_0]=⠀ [0_1]=⠈ [0_2]=⠘ [0_3]=⠸ [0_4]=⢸ [1_0]=⠁ [1_1]=⠉ [1_2]=⠙ [1_3]=⠹ [1_4]=⢹ [2_0]=⠃ [2_1]=⠋ [2_2]=⠛ [2_3]=⠻ [2_4]=⢻ [3_0]=⠇ [3_1]=⠏ [3_2]=⠟ [3_3]=⠿ [3_4]=⢿ [4_0]=⡇ [4_1]=⡏ [4_2]=⡟ [4_3]=⡿ [4_4]=⣿ )' declare -A graph box[boxes]="cpu mem net processes" cpu[threads]=0 #* Symbols for subscript function subscript=("₀" "₁" "₂" "₃" "₄" "₅" "₆" "₇" "₈" "₉") #* Symbols for create_box function box[single_hor_line]="─" box[single_vert_line]="│" box[single_left_corner_up]="┌" box[single_right_corner_up]="┐" box[single_left_corner_down]="└" box[single_right_corner_down]="┘" box[single_title_left]="├" box[single_title_right]="┤" box[double_hor_line]="═" box[double_vert_line]="║" box[double_left_corner_up]="╔" box[double_right_corner_up]="╗" box[double_left_corner_down]="╚" box[double_right_corner_down]="╝" box[double_title_left]="╟" box[double_title_right]="╢" init_() { #? Collect needed information and set options before startig main loop if [[ -z $1 ]]; then local i stx=0 #* Set terminal options, save and clear screen saved_stty="$(${stty} -g)" echo -en "${alt_screen}${hide_cursor}${clear_screen}" echo -en "\033]0;${TERMINAL_TITLE} BashTOP\a" ${stty} -echo #* Wait for resize if terminal size is smaller then 80x24 if (($tty_width<80 | $tty_height<24)); then resized; echo -en "${clear_screen}"; fi #* Draw banner to banner array local letter b_color banner_line y=0 local -a banner_out #print -v banner_out[0] -t "\e[0m" for banner_line in "${banner[@]}"; do #* Read banner array letter by letter to set correct color for filled vs outline characters while read -rN1 letter; do if [[ $letter == "█" ]]; then b_color="${banner_colors[$y]}" else b_color="#$((80-y*6))"; fi if [[ $letter == " " ]]; then print -v banner_out[y] -r 1 else print -v banner_out[y] -fg ${b_color} "${letter}" fi done <<<"$banner_line" ((++y)) done banner=("${banner_out[@]}") #* Draw banner to screen and show status while running init draw_banner $((tty_height/2-10)) #* Start psutil coprocess if enabled if [[ $use_psutil == true ]]; then print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -b -c "Creating psutil coprocess..." return fi fi if [[ -n $1 ]]; then local i stx=1; print -bg "#00" -fg "#30ff50" -r 1 -t "√"; fi #* Check if "sensors", "osx-cpu-temp" or "vcgencmd" commands is available, if not, disable temperature collection print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -b -c "Checking available tools..." if [[ $check_temp == true ]]; then local has_temp sensor_comm="" if [[ $use_psutil == true ]]; then py_command -v has_temp "get_sensors_check()" if [[ $has_temp == true ]]; then sensor_comm="psutil"; fi fi if [[ -z $sensor_comm ]]; then local checker for checker in "vcgencmd" "sensors" "osx-cpu-temp"; do if command -v "${checker}" >/dev/null 2>&1; then sensor_comm="${checker}"; break; fi done fi if [[ -z $sensor_comm ]]; then check_temp="false"; fi fi #* Check if "curl" command is available, if not, disable update check and theme downloads if command -v curl >/dev/null 2>&1; then curled=1; else unset curled; fi #* Check if "notify-send" command is available, if not, disable update notifier if [[ -n $curled ]] && command -v notify-send >/dev/null 2>&1; then notifier=1; else unset notifier; fi #* Check if "iostat" command is available, if not, disable disk io stat collection if command -v iostat >/dev/null 2>&1; then has_iostat=1; else unset has_iostat; fi #* Get number of cores and cpu threads print -bg "#00" -fg "#30ff50" -r 1 -t "√" print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -c -b "Checking cpu..." get_cpu_info #* Set graph resolution graph[hires]="${hires_graphs}" #* Get processor BCLK local param_var if [[ $use_psutil == false ]] && [[ -e /usr/include/asm-generic/param.h ]]; then param_var="$(/dev/null)" -k "version=" -r "[^0-9.]"; then unset git_version; fi fi #* Add update notification to banner if new version is available local banner_out_up print -v banner_out_up -rs -fg "#cc" -b "← esc" if [[ -n $git_version && $git_version != "$version" ]]; then print -v banner_out_up -rs -fg "#80cc80" -r 15 "[${git_version} available!]" -r $((9-${#git_version})) if [[ -n $notifier ]]; then notify-send -u normal\ "Bashtop Update!" "New version of Bashtop available\!\nCurrent version: ${version}\n\New version: ${git_version}\nDownload at github.com/aristocratos/bashtop"\ -i face-glasses -t 10000 fi else print -v banner_out_up -r 37 fi print -v banner_out_up -fg "#cc" -i -b "Version: ${version}" -rs banner+=("${banner_out_up}") #* Get theme and set colors print -bg "#00" -fg "#30ff50" -r 1 -t "√" print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -c -b "Generating colors for theme..." color_init_ #* Set up internals for quick processes sorting switching for((i=0;i<${#sorting[@]};i++)); do if [[ ${sorting[i]} == "${proc_sorting}" ]]; then proc[sorting_int]=$i break fi done if [[ -z ${proc[sorting_int]} ]]; then proc[sorting_int]=0 proc_sorting="${sorting[0]}" fi if [[ ${proc_reversed} == true ]]; then proc[reverse]="+" else unset 'proc[reverse]' fi if [[ ${proc_tree} == true ]]; then proc[tree]="+" else unset 'proc[tree]' fi #* Call init for processes data collection print -bg "#00" -fg "#30ff50" -r 1 -t "√" print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -c -b "Running process collection init..." proc[selected]=0 proc[start]=1 collect_processes init #* Draw first screen print -bg "#00" -fg "#30ff50" -r 1 -t "√" print -m $(( (tty_height/2-3)+stx++ )) 0 -bg "#00" -fg "#cc" -c -b "Drawing screen..." draw_bg quiet get_ms timestamp_start for task in processes cpu mem net; do collect_${task} draw_${task} done last_screen="${draw_out}" print -bg "#00" -fg "#30ff50" -r 1 -t "√" -rs sleep 0.5 draw_clock echo -en "${clear_screen}${draw_out}${proc_out}${clock_out}" resized=0 unset draw_out } color_init_() { #? Check for theme file and set colors local main_bg="" main_fg="#cc" title="#ee" hi_fg="#90" inactive_fg="#40" cpu_box="#3d7b46" mem_box="#8a882e" net_box="#423ba5" proc_box="#923535" proc_misc="#0de756" selected_bg="#7e2626" selected_fg="#ee" local temp_start="#4897d4" temp_mid="#5474e8" temp_end="#ff40b6" cpu_start="#50f095" cpu_mid="#f2e266" cpu_end="#fa1e1e" div_line="#30" local free_start="#223014" free_mid="#b5e685" free_end="#dcff85" cached_start="#0b1a29" cached_mid="#74e6fc" cached_end="#26c5ff" available_start="#292107" available_mid="#ffd77a" available_end="#ffb814" local used_start="#3b1f1c" used_mid="#d9626d" used_end="#ff4769" download_start="#231a63" download_mid="#4f43a3" download_end="#b0a9de" upload_start="#510554" upload_mid="#7d4180" upload_end="#dcafde" local hex2rgb color_name array_name this_color main_fg_dec sourced theme_unset local -i i y local -A rgb local -a dec_test local -a convert_color=("main_bg" "temp_start" "temp_mid" "temp_end" "cpu_start" "cpu_mid" "cpu_end" "upload_start" "upload_mid" "upload_end" "download_start" "download_mid" "download_end" "used_start" "used_mid" "used_end" "available_start" "available_mid" "available_end" "cached_start" "cached_mid" "cached_end" "free_start" "free_mid" "free_end" "proc_misc" "main_fg_dec") local -a set_color=("main_fg" "title" "hi_fg" "div_line" "inactive_fg" "selected_fg" "selected_bg" "cpu_box" "mem_box" "net_box" "proc_box") for theme_unset in ${!theme[@]}; do unset 'theme[${theme_unset}]' done #* Check if theme set in config exists and source it if it does if [[ -n ${color_theme} && ${color_theme} != "Default" && ${color_theme} =~ (themes/)|(user_themes/) && -e "${config_dir}/${color_theme%.theme}.theme" ]]; then # shellcheck source=/dev/null source "${config_dir}/${color_theme%.theme}.theme" sourced=1 else color_theme="Default" fi main_fg_dec="${theme[main_fg]:-$main_fg}" theme[main_fg_dec]="${main_fg_dec}" #* Convert colors for graphs and meters from rgb hexadecimal to rgb decimal if needed for color_name in ${convert_color[@]}; do if [[ -n $sourced ]]; then hex2rgb="${theme[${color_name}]}" else hex2rgb="${!color_name}"; fi hex2rgb=${hex2rgb//#/} if [[ ${#hex2rgb} == 6 ]] && is_hex "$hex2rgb"; then hex2rgb="$((${hex}${hex2rgb:0:2})) $((${hex}${hex2rgb:2:2})) $((${hex}${hex2rgb:4:2}))" elif [[ ${#hex2rgb} == 2 ]] && is_hex "$hex2rgb"; then hex2rgb="$((${hex}${hex2rgb:0:2})) $((${hex}${hex2rgb:0:2})) $((${hex}${hex2rgb:0:2}))" else dec_test=(${hex2rgb}) if [[ ${#dec_test[@]} -eq 3 ]] && is_int "${dec_test[@]}"; then hex2rgb="${dec_test[*]}" else unset hex2rgb; fi fi theme[${color_name}]="${hex2rgb}" done #* Set background color if set, otherwise use terminal default if [[ -n ${theme[main_bg]} ]]; then theme[main_bg_dec]="${theme[main_bg]}"; theme[main_bg]=";48;2;${theme[main_bg]// /;}"; fi #* Set colors from theme file if found and valid hexadecimal or integers, otherwise use default values for color_name in "${set_color[@]}"; do if [[ -z ${theme[$color_name]} ]] || ! is_hex "${theme[$color_name]}" && ! is_int "${theme[$color_name]}"; then theme[${color_name}]="${!color_name}"; fi done box[cpu_color]="${theme[cpu_box]}" box[mem_color]="${theme[mem_box]}" box[net_color]="${theme[net_box]}" box[processes_color]="${theme[proc_box]}" #* Create color arrays from one, two or three color gradient, 100 values in each for array_name in "temp" "cpu" "upload" "download" "used" "available" "cached" "free"; do local -n color_array="color_${array_name}_graph" local -a rgb_start=(${theme[${array_name}_start]}) rgb_mid=(${theme[${array_name}_mid]}) rgb_end=(${theme[${array_name}_end]}) local pf_calc middle=1 rgb[red]=${rgb_start[0]}; rgb[green]=${rgb_start[1]}; rgb[blue]=${rgb_start[2]} if [[ -z ${rgb_mid[*]} ]] && ((rgb_end[0]+rgb_end[1]+rgb_end[2]>rgb_start[0]+rgb_start[1]+rgb_start[2])); then rgb_mid=( $(( rgb_start[0]+( (rgb_end[0]-rgb_start[0])/2) )) $((rgb_start[1]+( (rgb_end[1]-rgb_start[1])/2) )) $((rgb_start[2]+( (rgb_end[2]-rgb_start[2])/2) )) ) elif [[ -z ${rgb_mid[*]} ]]; then rgb_mid=( $(( rgb_end[0]+( (rgb_start[0]-rgb_end[0])/2) )) $(( rgb_end[1]+( (rgb_start[1]-rgb_end[1])/2) )) $(( rgb_end[2]+( (rgb_start[2]-rgb_end[2])/2) )) ) fi for((i=0;i<=100;i++,y=0)); do if [[ -n ${rgb_end[*]} ]]; then for this_color in "red" "green" "blue"; do if ((i==50)); then rgb_start[y]=${rgb[$this_color]}; fi if ((middle==1 & rgb[$this_color]rgb_mid[y])); then printf -v pf_calc "%.0f" "-$(( i*( (rgb_start[y]-rgb_mid[y])*100/50*100) ))e-4" elif ((middle==0 & rgb[$this_color]rgb_end[y])); then printf -v pf_calc "%.0f" "-$(( (i-50)*( (rgb_start[y]-rgb_end[y])*100/50*100) ))e-4" else pf_calc=0 fi rgb[$this_color]=$((rgb_start[y]+pf_calc)) if ((rgb[$this_color]<0)); then rgb[$this_color]=0 elif ((rgb[$this_color]>255)); then rgb[$this_color]=255; fi y+=1 if ((i==49 & y==3 & middle==1)); then middle=0; fi done fi color_array[i]="${rgb[red]} ${rgb[green]} ${rgb[blue]}" done done } quit_() { #? Clean exit #* Restore terminal options and screen if [[ $use_psutil == true && $2 != "psutil" ]]; then py_command quit sleep 0.1 rm -rf "${pytmpdir}" fi echo -en "${clear_screen}${normal_screen}${show_cursor}" ${stty} "${saved_stty}" echo -en "\033]0;\a" #* Save any changed values to config file if [[ $config_file != "/dev/null" ]]; then save_config "${save_array[@]}" fi if [[ $1 == "restart" ]]; then exec "$(${realpath} "$0")"; fi exit ${1:-0} } sleep_() { #? Restore terminal options, stop and send to background if caught SIGTSTP (ctrl+z) echo -en "${clear_screen}${normal_screen}${show_cursor}" ${stty} "${saved_stty}" echo -en "\033]0;\a" if [[ $use_psutil == true ]]; then if ((failed_pipes>1)); then ((failed_pipes--)); fi py_command quit failed_pipe=1 wait ${pycoproc_PID} fi ${kill} -s SIGSTOP $$ } resume_() { #? Set terminal options and resume if caught SIGCONT ('fg' from terminal) sleepy=0 echo -en "${alt_screen}${hide_cursor}${clear_screen}" echo -en "\033]0;${TERMINAL_TITLE} BashTOP\a" ${stty} -echo if [[ -n $pause_screen ]]; then echo -en "$pause_screen" else echo -en "${boxes_out}${proc_det}${last_screen}${mem_out}${proc_misc}${proc_misc2}${update_string}${clock_out}" fi } traperr() { #? Function for reporting error line numbers local match len trap_muted err="${BASH_LINENO[0]}" len=$((${#trace_array[@]})) if ((len-->=1)); then while ((len>=${#trace_array[@]}-2)); do if [[ $err == "${trace_array[$((len--))]}" ]]; then ((++match)) ; fi done if ((match==2 & len != -2)); then return elif ((match>=1)); then trap_muted="(MUTED!)" fi fi if ((len>100)); then unset 'trace_array[@]'; fi trace_array+=("$err") printf "%(%X)T ERROR: On line %s %s\n" -1 "$err" "$trap_muted" >> "${config_dir}/error.log" } resized() { #? Get new terminal size if terminal is resized resized=1 unset winches while ((++winches<5)); do read tty_height tty_width < <(${stty} size) if (($tty_width<80 | $tty_height<24)); then size_error_msg winches=0 else echo -en "${clear_screen}" create_box -w 30 -h 3 -c 1 -l 1 -lc "#EE2020" -title "resizing" print -jc 28 -fg ${theme[title]} "New size: ${tty_width}x${tty_height}" ${sleep} 0.2 if [[ $(${stty} size) != "$tty_height $tty_width" ]]; then winches=0; fi fi done } size_error_msg() { #? Shows error message if terminal size is below 80x25 local width=$tty_width local height=$tty_height echo -en "${clear_screen}" create_box -full -lc "#EE2020" -title "resize window" print -rs -m $((tty_height/2-1)) 2 -fg ${theme[title]} -c -l 11 "Current size: " -bg "#00" -fg "#dd2020" -d 1 -c "${tty_width}x${tty_height}" -rs print -d 1 -fg ${theme[title]} -c -l 15 "Need to be atleast:" -bg "#00" -fg "#30dd50" -d 1 -c "80x24" -rs while [[ $(${stty} size) == "$tty_height $tty_width" ]]; do ${sleep} 0.2; if [[ -n $quitting ]]; then quit_; fi ; done } draw_banner() { #? Draw banner, usage: draw_banner [output variable] local y letter b_color x_color xpos ypos=$1 banner_out if [[ -n $2 ]]; then local -n banner_out=$2; fi xpos=$(( (tty_width/2)-(banner_width/2) )) for banner_line in "${banner[@]}"; do print -v banner_out -rs -move $((ypos+++y)) $xpos -t "${banner_line}" done if [[ -z $2 ]]; then echo -en "${banner_out}"; fi } create_config() { #? Creates a new config file with default values from above local c_line c_read this_file this_file="$(${realpath} "$0")" echo "#? Config file for bashtop v. ${version}" > "$config_file" while IFS= read -r c_line; do if [[ $c_line =~ aaz_config() ]]; then break elif [[ $c_read == "1" ]]; then echo "$c_line" >> "$config_file" elif [[ $c_line =~ aaa_config() ]]; then c_read=1; fi done < "$this_file" } save_config() { #? Saves variables to config file if not same, usage: save_config "var1" ["var2"] ["var3"]... if [[ -z $1 || $config_file == "/dev/null" ]]; then return; fi local var tmp_conf tmp_value quote original new tmp_conf="$(<"$config_file")" for var in "$@"; do if [[ ${tmp_conf} =~ ${var} ]]; then get_value -v "tmp_value" -sv "tmp_conf" -k "${var}=" if [[ ${tmp_value//\"/} != "${!var}" ]]; then original="${var}=${tmp_value}" new="${var}=\"${!var}\"" original="${original//'/'/'\/'}" new="${new//'/'/'\/'}" ${sed} -i "s/${original}/${new}/" "${config_file}" fi else echo "${var}=\"${!var}\"" >> "$config_file" fi done } set_font() { #? Take a string and generate a string of unicode characters of given font, usage; set_font "font-name [bold] [italic]" "string" local i letter letter_hex new_hex add_hex start font="$1" string_in="$2" string_out hex="16#" if [[ -z $font || -z $string_in ]]; then return; fi case "$font" in "sans-serif") lower_start="1D5BA"; upper_start="1D5A0"; digit_start="1D7E2";; "sans-serif bold") lower_start="1D5EE"; upper_start="1D5D4"; digit_start="1D7EC";; "sans-serif italic") lower_start="1D622"; upper_start="1D608"; digit_start="1D7E2";; #"sans-serif bold italic") start="1D656"; upper_start="1D63C"; digit_start="1D7EC";; "script") lower_start="1D4B6"; upper_start="1D49C"; digit_start="1D7E2";; "script bold") lower_start="1D4EA"; upper_start="1D4D0"; digit_start="1D7EC";; "fraktur") lower_start="1D51E"; upper_start="1D504"; digit_start="1D7E2";; "fraktur bold") lower_start="1D586"; upper_start="1D56C"; digit_start="1D7EC";; "monospace") lower_start="1D68A"; upper_start="1D670"; digit_start="1D7F6";; "double-struck") lower_start="1D552"; upper_start="1D538"; digit_start="1D7D8";; *) echo -n "${string_in}"; return;; esac for((i=0;i<${#string_in};i++)); do letter=${string_in:i:1} if [[ $letter =~ [a-z] ]]; then #61 printf -v letter_hex '%X\n' "'$letter" printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}61))" printf -v new_hex '%X' "$((${hex}${lower_start}+${hex}${add_hex}))" string_out="${string_out}\U${new_hex}" #if [[ $font =~ sans-serif && $letter =~ m|w ]]; then string_out="${string_out} "; fi #\U205F elif [[ $letter =~ [A-Z] ]]; then #41 printf -v letter_hex '%X\n' "'$letter" printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}41))" printf -v new_hex '%X' "$((${hex}${upper_start}+${hex}${add_hex}))" string_out="${string_out}\U${new_hex}" #if [[ $font =~ sans-serif && $letter =~ M|W ]]; then string_out="${string_out} "; fi elif [[ $letter =~ [0-9] ]]; then #30 printf -v letter_hex '%X\n' "'$letter" printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}30))" printf -v new_hex '%X' "$((${hex}${digit_start}+${hex}${add_hex}))" string_out="${string_out}\U${new_hex}" else string_out="${string_out} \e[1D${letter}" fi done echo -en "${string_out}" } sort_array_int() { #? Copy and sort an array of integers from largest to smallest value, usage: sort_array_int "input array" "output array" #* Return if given array has no values if [[ -z ${!1} ]]; then return; fi local start_n search_n tmp_array #* Create pointers to arrays local -n in_arr="$1" local -n out_arr="$2" #* Create local copy of array local array=("${in_arr[@]}") #* Start sorting for ((start_n=0;start_n<=${#array[@]}-1;++start_n)); do for ((search_n=start_n+1;search_n<=${#array[@]}-1;++search_n)); do if ((array[start_n] [-ps,-per-second] [-s,-start "1024 multiplier start"] [-v,-variable-output] local value selector per_second unit_mult decimals out_var ext_var short sep=" " local -a unit until (($#==0)); do case "$1" in -b|-bit) unit=(bit Kib Mib Gib Tib Pib); unit_mult=8;; -B|-Byte) unit=(Byte KiB MiB GiB TiB PiB); unit_mult=1;; -ps|-per-second) per_second=1;; -short) short=1; sep="";; -s|-start) selector="$2"; shift;; -v|-variable-output) local -n out_var="$2"; ext_var=1; shift;; *) if is_int "$1"; then value=$1; break; fi;; esac shift done if [[ -z $value || $value -lt 0 || -z $unit_mult ]]; then return; fi if ((per_second==1 & unit_mult==1)); then per_second="/s" elif ((per_second==1)); then per_second="ps"; fi if ((value>0)); then value=$((value*100*unit_mult)) until ((${#value}<6)); do value=$((value>>10)) if ((value<100)); then value=100; fi ((++selector)) done if ((${#value}<5 & ${#value}>=2 & selector>0)); then decimals=$((5-${#value})) value="${value::-2}.${value:(-${decimals})}" elif ((${#value}>=2)); then value="${value::-2}" fi fi if [[ -n $short ]]; then value="${value%.*}"; fi out_var="${value}${sep}${unit[$selector]::${short:-${#unit[$selector]}}}${per_second}" if [[ -z $ext_var ]]; then echo -n "${out_var}"; fi } get_cpu_info() { local lscpu_var pyin if [[ $use_psutil == true ]]; then if [[ -z ${cpu[threads]} || -z ${cpu[cores]} ]]; then py_command -v pyin "get_cpu_cores()" read cpu[cores] cpu[threads] <<<"${pyin}" fi else if command -v lscpu >/dev/null 2>&1; then lscpu_var="$(lscpu)"; fi if [[ -z ${cpu[threads]} || -z ${cpu[cores]} ]]; then if ! get_value -v 'cpu[threads]' -sv "lscpu_var" -k "CPU(s):" -i || [[ ${cpu[threads]} == "0" ]]; then cpu[threads]="$(nproc 2>/dev/null ||true)" if [[ -z ${cpu[threads]} ]]; then cpu[threads]="1"; fi cpu[cores]=${cpu[threads]} else get_value -v 'cpu[cores]' -sv "lscpu_var" -k "Core(s)" -i fi fi fi if [[ $use_psutil == false && -z $custom_cpu_name ]]; then if ! get_value -v 'cpu[model]' -sv "lscpu_var" -k "Model name:" -a -b -k "CPU" -mk -1; then if ! get_value -v 'cpu[model]' -sv "lscpu_var" -k "Model name:" -r " "; then cpu[model]="cpu" fi fi elif [[ $use_psutil == true && -z $custom_cpu_name ]]; then py_command -v cpu[model] "get_cpu_name()" else cpu[model]="${custom_cpu_name}" fi } get_value() { #? Get a value from a file, variable or array by searching for a non spaced "key name" on the same line local match line_pos=1 int reg key all tmp_array input found input_line line_array line_val ext_var line_nr current_line match_key math removing ext_arr local -a remove until (($#==0)); do until (($#==0)); do case "$1" in -k|-key) key="$2"; shift;; #? Key "string" on the same line as target value -m|-match) match="$2"; shift;; #? If multiple matches on a line, match occurrence "x" -mk|-match-key) match_key=$2; line_pos=0; shift;; #? Match in relation to key position, -1 for previous value, 1 for next value -b|-break) shift; break;; #? Break up arguments for multiple searches -a|-all) all=1;; #? Prints back found line including key -l|-line) line_nr="$2"; shift;; #? Set target line if no key is available -ss|-source-string) input="$2"; shift;; #? Argument string as source -sf|-source-file) input="$(<"$2")"; shift;; #? File as source -sv|-source-var) input="${!2}"; shift;; #? Variable as source -sa|-source-array) local -n tmp_array=$2; input="${tmp_array[*]}"; shift;; #? Array as source -fp|-floating-point) reg="[\-]?[0-9]*[.,][0-9]+"; match=1;; #? Match floating point value -math) math="$2"; shift;; #? Perform math on a integer value, "x" represents value, only works if "integer" argument is given -i|-integer) reg="[\-]?[0-9]+[.,]?[0-9]*"; int=1; match=1;; #? Match integer value or float and convert to int -r|-remove) remove+=("$2"); shift;; #? Format output by removing entered regex, can be used multiple times -v|-variable-out) local -n found="$2"; ext_var=1; shift;; #? Output to variable -map|-map-array) local -n array_out="$2"; ext_var=1; ext_arr=1; shift;; #? Map output to array esac shift done found="" if [[ -z $input ]]; then return 1; fi if [[ -z $line_nr && -z $key ]]; then line_nr=1; fi while IFS='' read -r input_line; do ((++current_line)) if [[ -n $line_nr && $current_line -eq $line_nr || -z $line_nr && -n $key && ${input_line/${key}/} != "$input_line" ]]; then if [[ -n $all ]]; then found="${input_line}" break elif [[ -z $match && -z $match_key && -z $reg ]]; then found="${input_line/${key}/}" break else line_array=(${input_line/${key}/${key// /}}) fi for line_val in "${line_array[@]}"; do if [[ -n $match_key && $line_val == "${key// /}" ]]; then if ((match_key<0 & line_pos+match_key>=0)) || ((match_key>=0 & line_pos+match_key<${#line_array[@]})); then found="${line_array[$((line_pos+match_key))]}" break 2 else return 1 fi elif [[ -n $match_key ]]; then ((++line_pos)) elif [[ -n $reg && $line_val =~ ^${reg}$ || -z $reg && -n $match ]]; then if ((line_pos==match)); then found=${line_val} break 2 fi ((++line_pos)) fi done fi done <<<"${input}" if [[ -z $found ]]; then return 1; fi if [[ -n ${remove[*]} ]]; then for removing in "${remove[@]}"; do found="${found//${removing}/}" done fi if [[ -n $int && $found =~ [.,] ]]; then found="${found/,/.}" printf -v found "%.0f" "${found}" fi if [[ -n $math && -n $int ]]; then math="${math//x/$found}" found=$((${math})) fi if (($#>0)); then input="${found}" unset key match match_key all reg found int 'remove[@]' current_line line_pos=1 fi done if [[ -z $ext_var ]]; then echo "${found}"; fi if [[ -n $ext_arr ]]; then array_out=(${found}); fi } get_themes() { local file theme_int=0 themes=("Default") for file in "${config_dir}/themes"/*.theme; do file="${file##*/}" if [[ ${file} != "*.theme" ]]; then themes+=("themes/${file%.theme}"); fi if [[ ${themes[-1]} == "${color_theme}" ]]; then theme_int=${#themes[@]}-1; fi done for file in "${config_dir}/user_themes"/*.theme; do file="${file##*/}" if [[ ${file} != "*.theme" ]]; then themes+=("user_themes/${file%.theme}"); fi if [[ ${themes[-1]} == "${color_theme}" ]]; then theme_int=${#themes[@]}-1; fi done } get_net_device() { #? Check for internet connection, name of default network device and create list of all devices if [[ $use_psutil == true ]]; then get_net_device_psutil; return; fi local -a netdev local ndline if ! get_value -v 'net[device]' -ss "$(ip route get 1.1.1.1 2>/dev/null)" -k "dev" -mk 1; then net[no_device]=1 else unset 'net[no_device]' 'nic_list[@]' nic_int readarray -t netdev #? Optional arguments: [-p, -place ] [-w, -width ] [-f, -fill-empty] #? [-c, -color "array-name"] [-i, -invert-color] [-v, -variable "variable-name"] if [[ -z $1 ]]; then return; fi local val width colors color block="■" i fill_empty col line var ext_var out meter_var print_var invert bg_color=${theme[inactive_fg]} #* Argument parsing until (($#==0)); do case $1 in -p|-place) if is_int "${@:2:2}"; then line=$2; col=$3; shift 2; fi;; #? Placement for meter -w|-width) width=$2; shift;; #? Width of meter in columns -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 -i|-invert) invert=1;; #? Invert meter -f|-fill-empty) fill_empty=1;; #? Fill unused space with dark blocks -v|-variable) local -n meter_var=$2; ext_var=1; shift;; #? Output meter to a variable *) if is_int "$1"; then val=$1; fi;; esac shift done if [[ -z $val ]]; then return; fi #* Set default width if not given width=${width:-10} #* If no color array was given, create a simple greyscale array if [[ -z $colors ]]; then for ((i=0,ic=50;i<=100;i++,ic=ic+2)); do colors[i]="${ic} ${ic} ${ic}" done fi #* Create the meter meter_var="" if [[ -n $line && -n $col ]]; then print -v meter_var -rs -m $line $col else print -v meter_var -rs; fi if [[ -n $invert ]]; then print -v meter_var -r $((width+1)); fi for((i=1;i<=width;i++)); do if [[ -n $invert ]]; then print -v meter_var -l 2; fi if ((val>=i*100/width)); then print -v meter_var -fg ${colors[$((i*100/width))]} -t "${block}" elif ((fill_empty==1)); then if [[ -n $invert ]]; then print -v meter_var -l $((width-i)); fi print -v meter_var -fg $bg_color -rp $((1+width-i)) -t "${block}"; break else if [[ -n $invert ]]; then break; print -v meter_var -l $((1+width-i)) else print -v meter_var -r $((1+width-i)); break; fi fi done if [[ -z $ext_var ]]; then echo -en "${meter_var}"; fi } create_graph() { #? Create a graph from an array of percentage values, usage; create_graph #? Create a graph from an array of non percentage values: create_graph <-max "max value"> #? Add a value to existing graph; create_graph [-i, -invert] [-max "max value"] -add-value "graph_array" #? Add last value from an array to existing graph; create_graph [-i, -invert] [-max "max value"] -add-last "graph_array" "value-array" #? Options: < -d, -dimensions > [-i, -invert] [-n, -no-guide] [-c, -color "array-name"] [-o, -output-array "variable-name"] if [[ -z $1 ]]; then return; fi if [[ ${graph[hires]} == true ]]; then create_graph_hires "$@"; return; fi local val col s_col line s_line height s_height width s_width colors color i var ext_var out side_num side_nums=1 add add_array invert no_guide max local -a graph_array input_array #* Argument parsing until (($#==0)); do case $1 in -d|-dimensions) if is_int "${@:2:4}"; then line=$2; col=$3; height=$4; width=$5; shift 4; fi;; #? Graph dimensions -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 -o|-output-array) local -n output_array=$2; ext_var=1; shift;; #? Output meter to an array -add-value) if is_int "$3"; then local -n output_array=$2; add=$3; break; else return; fi;; #? Add a value to existing graph -add-last) local -n output_array=$2; local -n add_array=$3; add=${add_array[-1]}; break;; #? Add last value from array to existing graph -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom -n|-no-guide) no_guide=1;; #? Don't print side and bottom guide lines -max) if is_int "$2"; then max=$2; shift; fi;; #? Needed max value for non percentage arrays *) local -n tmp_in_array=$1; input_array=("${tmp_in_array[@]}");; esac shift done if [[ -z $no_guide ]]; then ((--height)) else if [[ -n $invert ]]; then ((line--)); fi fi if ((width<3)); then width=3; fi if ((height<1)); then height=1; fi #* If argument "add" was passed check for existing graph and make room for new value(s) local add_start add_end if [[ -n $add ]]; then local cut_left search if [[ -n ${input_array[0]} ]]; then return; fi if [[ -n $output_array ]]; then graph_array=("${output_array[@]}") if [[ -z ${graph_array[0]} ]]; then return; fi else return fi height=$((${#graph_array[@]}-1)) input_array[0]=${add} #* Remove last value in current graph for ((i=0;i=0;i--)); do g_index+=($i) done else for((i=0;i<=height;i++)); do g_index+=($i) done fi if [[ -n $no_guide ]]; then unset normal_vals elif [[ -n $invert ]]; then g_char=(" ⡇" " ⡤" "⠤") fi #* Set up graph array print side numbers and lines print -v graph_array[0] -rs print -v graph_array[0] -m $((line+g_index[0])) ${col} ${normal_vals:+-jr 3 -fg "#ee" -b -t "${side_num[0]}" -rs -fg ${theme[main_fg]} -t "${g_char[0]}"} -fg ${colors[100]} for((i=1;i=max)); then input_array[i]=100 else input_array[i]=$((input_array[i]*100/max)) fi done fi until ((y==done_val)); do #* Print spaces to right-justify graph if number of values is less than graph width if [[ -z $add ]] && ((value_width3)); then input_array[x]=0; fi #* Print empty space if current value is less than percentage for current line while ((x0)); then print -v graph_array[y] -rp ${count} -t " " count=0 fi #* Print current value in percent relative to graph size if current value is less than line percentage but greater than next line percentage while ((x=next_value)); do print -v graph_array[y] -t "${graph_symbol[${invert:+-}$(( (input_array[x]*virt_height/100)-next_value ))]}" ((++x)) done #* Print full block if current value is greater than percentage for current line while ((x=cur_value)); do ((++count)) ((++x)) done if ((count>0)); then print -v graph_array[y] -rp ${count} -t "${graph_symbol[10]}" count=0 fi done if [[ -n $invert ]]; then ((y--)) || true else ((++y)) fi done #* Echo out graph if no argument for a output array was given if [[ -z $ext_var && -z $add ]]; then echo -en "${graph_array[*]}" else output_array=("${graph_array[@]}"); fi } create_mini_graph() { #? Create a one line high graph from an array of percentage values, usage; create_mini_graph #? Add a value to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-value "graph_variable" #? Add last value from an array to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-last "graph_variable" "value-array" #? Options: [-w, -width ] [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] [-o, -output-variable "variable-name"] if [[ -z $1 ]]; then return; fi if [[ ${graph[hires]} == true ]]; then create_mini_graph_hires "$@"; return; fi local val col s_col line s_line height s_height width s_width colors color i var ext_var out side_num side_nums=1 add invert no_guide graph_var no_color color_value #* Argument parsing until (($#==0)); do case $1 in -w|-width) if is_int "$2"; then width=$2; shift; fi;; #? Graph width -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 -nc|-no-color) no_color=1;; #? Set no color -o|-output-variable) local -n output_var=$2; ext_var=1; shift;; #? Output graph to a variable -add-value) if is_int "$3"; then local -n output_var=$2; add=$3; break; else return; fi;; #? Add a value to existing graph -add-last) local -n output_var=$2 add_array=$3; add="${add_array[-1]}"; break;; #? Add last value from array to existing graph -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom *) local -n input_array=$1;; esac shift done if ((width<1)); then width=1; fi #* If argument "add" was passed check for existing graph and make room for new value(s) local add_start add_end if [[ -n $add ]]; then local cut_left search #if [[ -n ${input_array[0]} ]]; then return; fi if [[ -n $output_var ]]; then graph_var="${output_var}" if [[ -z ${graph_var} ]]; then return; fi else return fi declare -a input_array input_array[0]=${add} #* Remove last value in current graph if [[ -n ${graph_var} && -z $no_color ]]; then if [[ ${graph_var::5} == "\e[1C" ]]; then graph_var="${graph_var#'\e[1C'}" else cut_left="${graph_var%%m*}" search=$((${#cut_left}+1)) graph_var="${graph_var:$((search+1))}" fi elif [[ -n ${graph_var} && -n $no_color ]]; then if [[ ${graph_var::5} == "\e[1C" ]]; then #cut_left="${graph_var%%C*}" #search=$((${#cut_left}+1)) #graph_var="${graph_var:$((search))}" graph_var="${graph_var#'\e[1C'}" else graph_var="${graph_var:1}" fi fi fi #* If no color array was given, create a simple greyscale array if [[ -z $colors && -z $no_color ]]; then for ((i=0,ic=50;i<=100;i++,ic=ic+2)); do colors[i]="${ic} ${ic} ${ic}" done fi #* Create the graph local value_width x=0 y a cur_value virt_height=$((height*10)) offset=0 org_value if [[ -n $add ]]; then value_width=1 elif ((${#input_array[@]}<=width)); then value_width=${#input_array[@]}; else value_width=${width} offset=$((${#input_array[@]}-width)) fi #* Print spaces to right-justify graph if number of values is less than graph width if [[ -z $add && -z $no_color ]] && ((value_width=100)); then cur_value=10; org_value=100 elif [[ ${#org_value} -gt 1 && ${org_value:(-1)} -ge 5 ]]; then cur_value=$((${org_value::1}+1)) elif [[ ${#org_value} -gt 1 && ${org_value:(-1)} -lt 5 ]]; then cur_value=$((${org_value::1})) elif [[ ${org_value:(-1)} -ge 5 ]]; then cur_value=1 else cur_value=0 fi if [[ -z $no_color ]]; then color="-fg ${colors[$org_value]} " else color="" fi if [[ $cur_value == 0 ]]; then print -v graph_var -t "\e[1C" else print -v graph_var ${color}-t "${graph_symbol[${invert:+-}$cur_value]}" fi ((++x)) done #* Echo out graph if no argument for a output array was given if [[ -z $ext_var && -z $add ]]; then echo -en "${graph_var}" else output_var="${graph_var}"; fi } create_graph_hires() { #? Create a graph from an array of percentage values, usage; create_graph #? Create a graph from an array of non percentage values: create_graph <-max "max value"> #? Add a value to existing graph; create_graph [-i, -invert] [-max "max value"] -add-value "graph_array" #? Add last value from an array to existing graph; create_graph [-i, -invert] [-max "max value"] -add-last "graph_array" "value-array" #? Options: < -d, -dimensions > [-i, -invert] [-n, -no-guide] [-c, -color "array-name"] [-o, -output-array "variable-name"] if [[ -z $1 ]]; then return; fi local val col s_col line s_line height s_height width s_width colors color var ext_var out side_num side_nums=1 add add_array invert no_guide max graph_name offset=0 last_val local -a input_array local -i i #* Argument parsing until (($#==0)); do case $1 in -d|-dimensions) if is_int "${@:2:4}"; then line=$2; col=$3; height=$4; width=$5; shift 4; fi;; #? Graph dimensions -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 -o|-output-array) local -n output_array=$2; graph_name=$2; ext_var=1; shift;; #? Output meter to an array -add-value) if is_int "$3"; then local -n output_array=$2; graph_name=$2; add=$3; break; else return; fi;; #? Add a value to existing graph -add-last) local -n output_array=$2; graph_name=$2; local -n add_array=$3; add=${add_array[-1]}; break;; #? Add last value from array to existing graph -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom -n|-no-guide) no_guide=1;; #? Don't print side and bottom guide lines -max) if is_int "$2"; then max=$2; shift; fi;; #? Needed max value for non percentage arrays *) local -n tmp_in_array="$1"; input_array=("${tmp_in_array[@]}");; esac shift done local -n last_val="graph[${graph_name}_last_val]" local -n last_type="graph[${graph_name}_last_type]" if [[ -z $add ]]; then last_type="even" last_val=0 local -n graph_array="${graph_name}_odd" local -n graph_even="${graph_name}_even" graph_even=("") graph_array=("") elif [[ ${last_type} == "even" ]]; then local -n graph_array="${graph_name}_odd" last_type="odd" elif [[ ${last_type} == "odd" ]]; then local -n graph_array="${graph_name}_even" last_type="even" fi if [[ -z $no_guide ]]; then ((--height)) elif [[ -n $invert ]]; then ((line--)) fi if ((width<3)); then width=3; fi if ((height<1)); then height=1; fi #* If argument "add" was passed check for existing graph and make room for new value(s) local add_start add_end if [[ -n $add ]]; then local cut_left search if [[ -n ${input_array[*]} || -z ${graph_array[0]} ]]; then return; fi height=$((${#graph_array[@]}-1)) input_array=("${add}") #* Remove last value in current graph for ((i=0;i=0;i--)); do g_index+=($i) done else for((i=0;i<=height;i++)); do g_index+=($i) done fi if [[ -n $no_guide ]]; then unset normal_vals elif [[ -n $invert ]]; then g_char=(" ⡇" " ⡤" "⠤") fi #* Set up graph array print side numbers and lines print -v graph_array[0] -rs -m $((line+g_index[0])) ${col} ${normal_vals:+-jr 3 -fg "#ee" -b -t "${side_num[0]}" -rs -fg ${theme[main_fg]} -t "${g_char[0]}"} -fg ${colors[100]} for((i=1;i=max)); then input_array[i]=100 else input_array[i]=$((input_array[i]*100/max)) fi done if [[ -n $converted ]]; then last_val=$((${last_val}*100/max)) if ((${last_val}>100)); then last_val=100; fi fi fi if [[ -n $invert ]]; then local -n symbols=graph_symbol_down else local -n symbols=graph_symbol_up fi until ((y==done_val)); do next_line=$(( virt_height-((y+1)*4) )) unset p_val #* Create graph by walking through all values for each line for ((x=0;x<${#input_array[@]};x++)); do c_val=${input_array[x]} p_val=${p_val:-${last_val}} cur_value="$((c_val*virt_height/100-next_line))" prev_value=$((p_val*virt_height/100-next_line)) if ((cur_value<0)); then cur_value=0 elif ((cur_value>4)); then cur_value=4; fi if ((prev_value<0)); then prev_value=0 elif ((prev_value>4)); then prev_value=4; fi if [[ -z $add ]] && ((x==0)); then print -v graph_even[y] -t "${symbols[${prev_value}_${cur_value}]}" print -v graph_array[y] -t "${symbols[0_${prev_value}]}" elif [[ -z $add ]] && ! ((x%2)); then print -v graph_even[y] -t "${symbols[${prev_value}_${cur_value}]}" else print -v graph_array[y] -t "${symbols[${prev_value}_${cur_value}]}" fi if [[ -z $add ]]; then p_val=${input_array[x]}; else unset p_val; fi done if [[ -n $invert ]]; then ((y--)) || true else ((++y)) fi done if [[ -z $add && ${last_type} == "even" ]]; then declare -n graph_array="${graph_name}_even" fi last_val=$c_val output_array=("${graph_array[@]}") } create_mini_graph_hires() { #? Create a one line high graph from an array of percentage values, usage; create_mini_graph #? Add a value to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-value "graph_variable" #? Add last value from an array to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-last "graph_variable" "value-array" #? Options: [-w, -width ] [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] [-o, -output-variable "variable-name"] if [[ -z $1 ]]; then return; fi local val col s_col line s_line height s_height width s_width colors color var ext_var out side_num side_nums=1 add invert no_guide graph_var no_color color_value graph_name local -a input_array local -i i #* Argument parsing until (($#==0)); do case $1 in -w|-width) if is_int "$2"; then width=$2; shift; fi;; #? Graph width -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 -nc|-no-color) no_color=1;; #? Set no color -o|-output-variable) local -n output_var=$2; graph_name=$2; ext_var=1; shift;; #? Output graph to a variable -add-value) if is_int "$3"; then local -n output_var=$2; graph_name=$2; add=$3; break; else return; fi;; #? Add a value to existing graph -add-last) local -n output_var=$2; local -n add_array=$3; graph_name=$2; add="${add_array[-1]:-0}"; break;; #? Add last value from array to existing graph -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom *) local -n tmp_in_arr=$1; input_array=("${tmp_in_arr[@]}");; esac shift done local -n last_val="${graph_name}_last_val" local -n last_type="${graph_name}_last_type" if [[ -z $add ]]; then last_type="even" last_val=0 local -n graph_var="${graph_name}_odd" local -n graph_other="${graph_name}_even" graph_var=""; graph_other="" elif [[ ${last_type} == "even" ]]; then local -n graph_var="${graph_name}_odd" last_type="odd" elif [[ ${last_type} == "odd" ]]; then local -n graph_var="${graph_name}_even" last_type="even" fi if ((width<1)); then width=1; fi #* If argument "add" was passed check for existing graph and make room for new value(s) local add_start add_end if [[ -n $add ]]; then local cut_left search input_array[0]=${add} #* Remove last value in current graph if [[ -n ${graph_var} && -z $no_color ]]; then if [[ ${graph_var::5} == '\e[1C' ]]; then graph_var="${graph_var#'\e[1C'}" else cut_left="${graph_var%m*}" search=$((${#cut_left}+1)) graph_var="${graph_var::$search}${graph_var:$((search+1))}" fi elif [[ -n ${graph_var} && -n $no_color ]]; then if [[ ${graph_var::5} == "\e[1C" ]]; then #cut_left="${graph_var%%C*}" #search=$((${#cut_left}+1)) #graph_var="${graph_var:$((search))}" graph_var="${graph_var#'\e[1C'}" else graph_var="${graph_var:1}" fi fi fi #* If no color array was given, create a simple greyscale array if [[ -z $colors && -z $no_color ]]; then for ((i=0,ic=50;i<=100;i++,ic=ic+2)); do colors[i]="${ic} ${ic} ${ic}" done fi #* Create the graph local value_width x=0 y a cur_value prev_value p_val c_val acolor jump odd offset=0 if [[ -n $add ]]; then value_width=1 elif ((${#input_array[@]}<=width*2)); then value_width=$((${#input_array[@]}*2)) else value_width=$((width*2)) input_array=("${input_array[@]:(-${value_width})}") fi if [[ -z $add ]] && ! ((${#input_array[@]}%2)); then last_val=${input_array[0]}; input_array=("${input_array[@]:1}"); fi #* Print spaces to right-justify graph if number of values is less than graph width if [[ -z $add ]] && ((${#input_array[@]}/2=85)); then cur_value=4 elif ((c_val>=60)); then cur_value=3 elif ((c_val>=30)); then cur_value=2 elif ((c_val>=10)); then cur_value=1 elif ((c_val<10)); then cur_value=0; fi if ((p_val>=85)); then prev_value=4 elif ((p_val>=60)); then prev_value=3 elif ((p_val>=30)); then prev_value=2 elif ((p_val>=10)); then prev_value=1 elif ((p_val<10)); then prev_value=0; fi if [[ -z $no_color ]]; then if ((c_val>p_val)); then acolor=$((c_val-p_val)) else acolor=$((p_val-c_val)); fi if ((acolor>100)); then acolor=100; elif ((acolor<0)); then acolor=0; fi color="-fg ${colors[${acolor:-0}]} " else unset color fi if ((cur_value==0 & prev_value==0)); then jump="\e[1C"; else unset jump; fi if [[ -z $add ]] && ((i==0)); then print -v graph_other ${color}-t "${jump:-${symbols[${prev_value}_${cur_value}]}}" print -v graph_var ${color}-t "${jump:-${symbols[0_${prev_value}]}}" elif [[ -z $add ]] && ((i%2)); then print -v graph_other ${color}-t "${jump:-${symbols[${prev_value}_${cur_value}]}}" else print -v graph_var ${color}-t "${jump:-${symbols[${prev_value}_${cur_value}]}}" fi if [[ -z $add ]]; then p_val=$c_val; else unset p_val; fi done #if [[ -z $add ]]; then # declare -n graph_var="${graph_name}_even" # #echo "yup" >&2 #fi last_val=$c_val output_var="${graph_var}" } print() { #? Print text, set true-color foreground/background color, add effects, center text, move cursor, save cursor position and restore cursor postion #? Effects: [-fg, -foreground | ] [-bg, -background | ] [-rs, -reset] [-/+b, -/+bold] [-/+da, -/+dark] #? [-/+ul, -/+underline] [-/+i, -/+italic] [-/+bl, -/+blink] [-f, -font "sans-serif|script|fraktur|monospace|double-struck"] #? Manipulation: [-m, -move ] [-l, -left ] [-r, -right ] [-u, -up ] [-d, -down ] [-c, -center] [-sc, -save] [-rc, -restore] #? [-jl, -justify-left ] [-jr, -justify-right ] [-jc, -justify-center ] [-rp, -repeat ] #? Text: [-v, -variable "variable-name"] [-stdin] [-t, -text "string"] ["string"] #* Return if no arguments is given if [[ -z $1 ]]; then return; fi #* Just echo and return if only one argument and not a valid option if [[ $# -eq 1 && ${1::1} != "-" ]]; then echo -en "$1"; return; fi local effect color add_command text text2 esc center clear fgc bgc fg_bg_div tmp tmp_len bold italic custom_font val var out ext_var hex="16#" local justify_left justify_right justify_center repeat r_tmp trans #* Loop function until we are out of arguments until (($#==0)); do #* Argument parsing until (($#==0)); do case $1 in -t|-text) text="$2"; shift 2; break;; #? String to print -stdin) text="$( <0-255> <0-255>" if [[ ${2::1} == "#" ]]; then val=${2//#/} if [[ ${#val} == 6 ]]; then fgc="\e[38;2;$((${hex}${val:0:2}));$((${hex}${val:2:2}));$((${hex}${val:4:2}))m"; shift elif [[ ${#val} == 2 ]]; then fgc="\e[38;2;$((${hex}${val:0:2}));$((${hex}${val:0:2}));$((${hex}${val:0:2}))m"; shift fi elif is_int "${@:2:3}"; then fgc="\e[38;2;$2;$3;$4m"; shift 3 fi ;; -bg|-background) #? Set text background color, accepts either 6 digit hexadecimal "#RRGGBB", 2 digit hex (greyscale) or decimal RGB "<0-255> <0-255> <0-255>" if [[ ${2::1} == "#" ]]; then val=${2//#/} if [[ ${#val} == 6 ]]; then bgc="\e[48;2;$((${hex}${val:0:2}));$((${hex}${val:2:2}));$((${hex}${val:4:2}))m"; shift elif [[ ${#val} == 2 ]]; then bgc="\e[48;2;$((${hex}${val:0:2}));$((${hex}${val:0:2}));$((${hex}${val:0:2}))m"; shift fi elif is_int "${@:2:3}"; then bgc="\e[48;2;$2;$3;$4m"; shift 3 fi ;; -c|-center) center=1;; #? Center text horizontally on screen -rs|-reset) effect="0${effect}${theme[main_bg]}";; #? Reset text colors and effects -b|-bold) effect="${effect}${effect:+;}1"; bold=1;; #? Enable bold text +b|+bold) effect="${effect}${effect:+;}21"; bold=0;; #? Disable bold text -da|-dark) effect="${effect}${effect:+;}2";; #? Enable dark text +da|+dark) effect="${effect}${effect:+;}22";; #? Disable dark text -i|-italic) effect="${effect}${effect:+;}3"; italic=1;; #? Enable italic text +i|+italic) effect="${effect}${effect:+;}23"; italic=0;; #? Disable italic text -ul|-underline) effect="${effect}${effect:+;}4";; #? Enable underlined text +ul|+underline) effect="${effect}${effect:+;}24";; #? Disable underlined text -bl|-blink) effect="${effect}${effect:+;}5";; #? Enable blinking text +bl|+blink) effect="${effect}${effect:+;}25";; #? Disable blinking text -f|-font) if [[ $2 =~ ^(sans-serif|script|fraktur|monospace|double-struck)$ ]]; then custom_font="$2"; shift; fi;; #? Set custom font -m|-move) add_command="${add_command}\e[${2};${3}f"; shift 2;; #? Move to postion "LINE" "COLUMN" -l|-left) add_command="${add_command}\e[${2}D"; shift;; #? Move left x columns -r|-right) add_command="${add_command}\e[${2}C"; shift;; #? Move right x columns -u|-up) add_command="${add_command}\e[${2}A"; shift;; #? Move up x lines -d|-down) add_command="${add_command}\e[${2}B"; shift;; #? Move down x lines -jl|-justify-left) justify_left="${2}"; shift;; #? Justify string left within given width -jr|-justify-right) justify_right="${2}"; shift;; #? Justify string right within given width -jc|-justify-center) justify_center="${2}"; shift;; #? Justify string center within given width -rp|-repeat) repeat=${2}; shift;; #? Repeat next string x number of times -sc|-save) add_command="\e[s${add_command}";; #? Save cursor position -rc|-restore) add_command="${add_command}\e[u";; #? Restore cursor position -trans) trans=1;; #? Make whitespace transparent -v|-variable) local -n var=$2; ext_var=1; shift;; #? Send output to a variable, appending if not unset *) text="$1"; shift; break;; #? Assumes text string if no argument is found esac shift done #* Repeat string if repeat is enabled if [[ -n $repeat ]]; then printf -v r_tmp "%${repeat}s" "" text="${r_tmp// /$text}" fi #* Set correct placement for screen centered text if ((center==1 & ${#text}>0 & ${#text}tty_width*4)); then cpu_history=( "${cpu_history[@]:$((tty_width*2))}" "${cpu_usage[0]}") else cpu_history+=("${cpu_usage[0]}") fi for((i=1;i<=threads;i++)); do local -n cpu_core_history="cpu_core_history_$i" if ((${#cpu_core_history[@]}>40)); then cpu_core_history=( "${cpu_core_history[@]:20}" "${cpu_usage[$i]}") else cpu_core_history+=("${cpu_usage[$i]}") fi done #* Get current cpu frequency from "/proc/cpuinfo" and convert to appropriate unit if [[ $use_psutil == false && -z ${cpu[no_cpu_info]} ]] && ! get_value -v 'cpu[freq]' -sf "/proc/cpuinfo" -k "cpu MHz" -i; then cpu[no_cpu_info]=1 fi #* If getting cpu frequency from "proc/cpuinfo" was unsuccessfull try "/sys/devices/../../scaling_cur_freq" if [[ $use_psutil == false && -n ${cpu[no_cpu_info]} && -e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" ]]; then get_value -v 'cpu[freq]' -sf "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" -i printf -v 'cpu[freq]' "%.0f0" "${cpu[freq]}e-4" fi if ((${#cpu[freq]}>3)); then cpu[freq_string]="${cpu[freq]::-3}.${cpu[freq]:(-3):1} GHz" elif ((${#cpu[freq]}>1)); then cpu[freq_string]="${cpu[freq]} MHz" else cpu[freq_string]=""; fi #* Get load average and uptime from uptime command if [[ $use_psutil == false ]]; then local uptime_var read -r uptime_var < <(uptime 2>/dev/null || true) cpu[load_avg]="${uptime_var#*average: }" cpu[load_avg]="${cpu[load_avg]//,/}" cpu[uptime]="${uptime_var#*up }" cpu[uptime]="${cpu[uptime]%%, *}" fi #* Collect cpu temps if enabled if [[ $check_temp == true ]]; then collect_cpu_temps; fi } collect_cpu_temps() { #? Collect cpu temperatures local unit c div threads=${cpu[threads]} sens_var i it ccd_value breaking core_value misc_var local -a ccd_array core_array #* Fetch output from "sensors" command or psutil to a variable if [[ $sensor_comm == "psutil" ]]; then if ! py_command -vn sens_var "get_sensors()"; then if command -v sensors >/dev/null 2>&1; then sensor_comm="sensors" else sensor_comm=""; check_temp="false"; resized=1; return; fi fi fi if [[ $sensor_comm == "sensors" ]]; then if [[ $use_psutil == true ]]; then py_command -vn sens_var "get_cmd_out('sensors 2>/dev/null')" else read -rd '' sens_var < <(sensors 2>/dev/null || true) || true fi elif [[ $sensor_comm != "sensors" && $sensor_comm != "psutil" ]]; then if [[ $use_psutil == true ]]; then py_command -v misc_var "get_cmd_out('${sensor_comm} measure_temp 2>/dev/null')" else read -r misc_var < <(${sensor_comm} measure_temp 2>/dev/null ||true) fi fi #* Get CPU package temp for intel cpus if get_value -v 'cpu[temp_0]' -sv "sens_var" -k "Package*:" -mk 1 || get_value -v 'cpu[temp_0]' -sv "sens_var" -k "Core 0:" -mk 1; then #* If successful get temperature unit, convert temp to integer and get high and crit cpu[temp_unit]="${cpu[temp_0]:(-2)}"; cpu[temp_0]="${cpu[temp_0]%.*}"; if [[ ${cpu[temp_0]::1} == "+" ]]; then cpu[temp_0]="${cpu[temp_0]#+}"; fi if [[ -z ${cpu[temp_high]} ]]; then if ! get_value -v 'cpu[temp_high]' -sv "sens_var" -k "Package*high =" -m 2 -r "[^0-9.]" -b -i; then cpu[temp_high]="85"; cpu[temp_crit]=$((cpu[temp_high]+10)) else get_value -v 'cpu[temp_crit]' -sv "sens_var" -k "Package*crit =" -m 2 -r "[^0-9.]" -b -i; fi fi #* Get core temps i=0 while get_value -v "core_value" -sv "sens_var" -k "Core ${i}:" -mk 1 -r "[^0-9.]" -b -i && ((i<=threads)); do core_array+=("$core_value"); ((++i)) ; done if [[ -z ${core_array[0]} ]]; then core_array=("${cpu[temp_0]}"); fi if ((${#core_array[@]}100)); then tmp_temp=100; elif ((tmp_temp<0)); then tmp_temp=0; fi local -n cpu_temp_history="cpu_temp_history_$i" if ((${#cpu_temp_history[@]}>20)); then cpu_temp_history=( "${cpu_temp_history[@]:10}" "${tmp_temp}") else cpu_temp_history+=("${tmp_temp}") fi done fi } collect_mem() { #? Collect memory information from "/proc/meminfo" ((++mem[counter])) #if [[ $use_psutil == false ]] && ((mem[counter]<4)); then return; fi if ((mem[counter]<4)); then return; fi mem[counter]=0 local i tmp value array mem_info height=$((box[mem_height]-2)) skip filter_value local -a mem_array swap_array available=("mem") unset 'mem[total]' #* Get memory and swap information from "/proc/meminfo" or psutil and calculate percentages if [[ $use_psutil == true ]]; then local pymemout py_command -v pymemout "get_mem()" || return read mem[total] mem[free] mem[available] mem[cached] swap[total] swap[free] <<<"$pymemout" if [[ -z ${mem[total]} ]]; then return; fi if [[ -n ${swap[total]} ]] && ((swap[total]>0)); then swap[free_percent]=$((swap[free]*100/swap[total])) swap[used]=$((swap[total]-swap[free])) swap[used_percent]=$((swap[used]*100/swap[total])) available+=("swap") else unset swap_on fi else read -rd '' mem_info 0)); then get_value -v 'swap[free]' -sv "mem_info" -k "SwapFree:" -i swap[free_percent]=$((swap[free]*100/swap[total])) swap[used]=$((swap[total]-swap[free])) swap[used_percent]=$((swap[used]*100/swap[total])) available+=("swap") elif [[ $use_psutil == false ]]; then unset swap_on fi #* Convert values to floating point and humanize for array in ${available[@]}; do for value in total used free available cached; do if [[ $array == "swap" && $value == "available" ]]; then break 2; fi local -n this_value="${array}[${value}]" this_string="${array}[${value}_string]" floating_humanizer -v this_string -s 1 -B "${this_value}" done done #* Get disk information local df_line line_array dev_path dev_name iostat_var disk_read disk_write disk_io_string df_count=0 filtering psutil_on local -a device_array iostat_array df_array unset 'disks_free[@]' 'disks_used[@]' 'disks_used_percent[@]' 'disks_total[@]' 'disks_name[@]' 'disks_free_percent[@]' 'disks_io[@]' if [[ -n $psutil_disk_fail ]]; then psutil_on="false"; else psutil_on="$use_psutil"; fi if [[ $psutil_on == true ]]; then if [[ -n $disks_filter ]]; then filtering=", filtering='${disks_filter}'"; fi if ! py_command -a df_array "get_disks(exclude='squashfs'${filtering})"; then psutil_disk_fail=1; psutil_on="false"; fi fi if [[ $psutil_on == false ]]; then readarray -t df_array < <(${df} -x squashfs -x tmpfs -x devtmpfs -x overlay -x 9p 2>/dev/null || true) fi for df_line in "${df_array[@]:1}"; do line_array=(${df_line}) if ! is_int "${line_array[1]}" || ((line_array[1]<=0)); then continue; fi if [[ $psutil_on == false && ${line_array[5]} == "/" ]]; then disks_name+=("root") elif [[ $psutil_on == false ]]; then disks_name+=("${line_array[5]##*/}") elif [[ $psutil_on == true ]]; then disks_name+=("${line_array[*]:7}"); fi #* Filter disks showed if $disks_filter is set if [[ $psutil_on == false && -n $disks_filter ]]; then unset found for filter_value in ${disks_filter}; do if [[ $filter_value == "${disks_name[-1]}" ]]; then found=1; fi done fi if [[ $psutil_on == true || -z $disks_filter || -n $found ]]; then disks_total+=("$(floating_humanizer -s 1 -B ${line_array[1]})") disks_used+=("$(floating_humanizer -s 1 -B ${line_array[2]})") disks_used_percent+=("${line_array[4]%'%'}") disks_free+=("$(floating_humanizer -s 1 -B ${line_array[3]})") disks_free_percent+=("$((100-${line_array[4]%'%'}))") #* Get read/write stats for disk from iostat or psutil if available if [[ $psutil_on == true || -n $has_iostat ]]; then unset disk_io_string dev_name="${line_array[0]##*/}" if [[ $psutil_on == false && ${dev_name::2} == "md" ]]; then dev_name="${dev_name::3}"; fi if [[ $psutil_on == false ]]; then unset iostat_var 'iostat_array[@]' dev_path="${line_array[0]%${dev_name}}" read -r iostat_var < <(iostat -dkz "${dev_path}${dev_name}" | tail -n +4) iostat_array=(${iostat_var}) fi if [[ $psutil_on == true || -n ${iostat_var} ]]; then if [[ $psutil_on == true ]]; then disk_read=${line_array[5]} disk_write=${line_array[6]} else disk_read=$((iostat_array[-2]-${disks[${dev_name}_read]:-${iostat_array[-2]}})) disk_write=$((iostat_array[-1]-${disks[${dev_name}_write]:-${iostat_array[-1]}})) fi if ((box[m_width2]>25)); then if ((disk_read>0)); then disk_io_string="▲$(floating_humanizer -s 1 -short -B ${disk_read}) "; fi if ((disk_write>0)); then disk_io_string+="▼$(floating_humanizer -s 1 -short -B ${disk_write})"; fi elif ((disk_read+disk_write>0)); then disk_io_string+="▼▲$(floating_humanizer -s 1 -short -B $((disk_read+disk_write)))" fi if [[ $psutil_on == false ]]; then disks[${dev_name}_read]="${iostat_array[-2]}" disks[${dev_name}_write]="${iostat_array[-1]}" fi fi disks_io+=("${disk_io_string:-0}") fi else unset 'disks_name[-1]' disks_name=("${disks_name[@]}") fi if ((${#disks_name[@]}>=height/2)); then break; fi done } collect_processes() { #? Collect process information and calculate accurate cpu usage if [[ $use_psutil == true ]]; then collect_processes_psutil $1; return; fi local argument="$1" if [[ -n $skip_process_draw && $argument != "now" ]]; then return; fi local width=${box[processes_width]} height=${box[processes_height]} format_args format_cmd readline sort symbol="▼" cpu_title options pid_string tmp selected local tree tree_compare1 tree_compare2 tree_compare3 no_core_divide pids local -a grep_array saved_proc_array if [[ $argument == "now" ]]; then skip_process_draw=1; fi if [[ -n ${proc[reverse]} ]]; then symbol="▲"; fi case ${proc_sorting} in "pid") selected="Pid:"; sort="pid";; "program") selected="Program:"; sort="comm";; "arguments") selected="Arguments:"; sort="args";; "threads") selected="Threads:"; sort="nlwp";; "user") selected="User:"; sort="euser";; "memory") selected="Mem%"; sort="pmem";; "cpu lazy"|"cpu responsive") sort="pcpu"; selected="Cpu%";; esac if [[ $proc_tree == true ]]; then tree="Tree:"; fi if [[ $proc_per_core == true ]]; then no_core_divide="1"; fi #* Collect output from ps command to array if ((width>60)) && [[ $proc_tree != true ]] ; then format_args=",args:$(( width-(47+proc[pid_len]) ))=Arguments:"; format_cmd=15 else format_cmd=$(( width-(31+proc[pid_len]) )); fi saved_proc_array=("${proc_array[@]}") unset 'proc_array[@]' 'pid_array[@]' if ((proc[detailed]==0)) && [[ -n ${proc[detailed_name]} ]]; then unset 'proc[detailed_name]' 'proc[detailed_killed]' 'proc[detailed_cpu_int]' 'proc[detailed_cmd]' unset 'proc[detailed_mem]' 'proc[detailed_mem_int]' 'proc[detailed_user]' 'proc[detailed_threads]' unset 'detail_graph[@]' 'detail_mem_graph' 'detail_history[@]' 'detail_mem_history[@]' unset 'proc[detailed_runtime]' 'proc[detailed_mem_string]' 'proc[detailed_parent_pid]' 'proc[detailed_parent_name]' fi unset 'proc[detailed_cpu]' if [[ -z $filter ]]; then options="-t" fi readarray ${options} proc_array < <(ps ax${tree:+f} -o pid:${proc[pid_len]}=Pid:,comm:${format_cmd}=${tree:-Program:}${format_args},nlwp:3=Tr:,euser:6=User:,pmem=Mem%,pcpu:10=Cpu% --sort ${proc[reverse]:--}${sort}) proc_array[0]="${proc_array[0]/ Tr:/ Threads:}" proc_array[0]="${proc_array[0]/ ${selected}/${symbol}${selected}}" if [[ -n $filter ]]; then grep_array[0]="${proc_array[0]}" readarray -O 1 -t grep_array < <(echo -e " ${proc_array[*]:1}" | grep -e "${filter}" ${proc[detailed_pid]:+-e ${proc[detailed_pid]}} | cut -c 2- || true) proc_array=("${grep_array[@]}") fi #* Get accurate cpu usage by fetching and comparing values in /proc/"pid"/stat local operations operation utime stime count time_elapsed cpu_percent_string rgb=231 step add proc_out tmp_value_array i pcpu_usage cpu_int tmp_percent breaking local -a cpu_percent statfile work_array #* Timestamp the values in milliseconds to accurately calculate cpu usage get_ms proc[new_timestamp] for readline in "${proc_array[@]:1}"; do ((++count)) if ((count==height-3 & breaking==0)); then if [[ -n $filter || $proc_sorting != "cpu lazy" || ${proc[selected]} -gt 0 || ${proc[start]} -gt 1 || ${proc_reversed} == true ]]; then : else breaking=1; fi fi #if get_key -save && [[ ${#saved_key[@]} -gt 0 ]]; then proc_array=("${saved_proc_array[@]}"); return; fi if ((breaking==2)); then work_array=(${proc_array[-1]}) else work_array=(${readline}) fi pid="${work_array[0]}" pcpu_usage="${work_array[-1]}" #* If showing tree structure replace slashes and pipes with actual lines and terminate them at the correct places if [[ $proc_tree == true ]]; then tree_compare1="${proc_array[$((count+1))]%'\_'*}" tree_compare2="${proc_array[count]%'\_'*}" tree_compare3="${proc_array[$((count+1))]%'|'*}" proc_array[count]="${proc_array[count]//'|'/│}" proc_array[count]="${proc_array[count]//'\_'/└─}" if ((count<${#proc_array[@]}-1)) && [[ ${#tree_compare1} -eq ${#tree_compare2} || ${#tree_compare2} -eq ${#tree_compare3} ]]; then proc_array[count]="${proc_array[count]//'└'/├}" fi fi pid_history[${pid}]="1" if [[ -n $filter || $proc_sorting == "cpu responsive" ]] && [[ ${proc_array[count]:${proc[pid_len]}:1} != " " ]]; then unset pid_string printf -v pid_string "%${proc[pid_len]}s" "${pid}" proc_array[count]="${pid_string}${proc_array[count]#*${pid}}" fi if [[ -r "/proc/${pid}/stat" ]] && read -ra statfile /dev/null; then utime=${statfile[13]} stime=${statfile[14]} proc[new_${pid}_ticks]=$((utime+stime)) if [[ -n ${proc[old_${pid}_ticks]} ]]; then time_elapsed=$((proc[new_timestamp]-proc[old_timestamp])) #* Calculate current cpu usage for process, * 1000 (for conversion from ms to seconds) * 1000 (for conversion to floating point) cpu_percent[count]=$(( ( ( ${proc[new_${pid}_ticks]}-${proc[old_${pid}_ticks]} ) * 1000 * 1000 ) / ( cpu[hz]*time_elapsed*${no_core_divide:-${cpu[threads]}} ) )) if ((cpu_percent[count]<0)); then cpu_percent[count]=0 elif [[ -z $no_core_divide ]] && ((cpu_percent[count]>1000)); then cpu_percent[count]=1000; fi if ((${#cpu_percent[count]}<=3)); then printf -v cpu_percent_string "%01d%s" "${cpu_percent[count]::-1}" ".${cpu_percent[count]:(-1)}" else cpu_percent_string=${cpu_percent[count]::-1} fi printf -v cpu_percent_string "%5s" "${cpu_percent_string::4}" proc_array[count]="${proc_array[count]::-5}${cpu_percent_string}" pid_graph="pid_${pid}_graph" local -n pid_count="pid_${pid}_count" printf -v cpu_int "%01d" "${cpu_percent[count]::-1}" #* Get info for detailed box if enabled if [[ ${pid} == "${proc[detailed_pid]}" ]]; then if [[ -z ${proc[detailed_name]} ]]; then local get_mem mem_string cmdline="" local -a det_array read -r proc[detailed_name] 5)); then proc[detailed_mem_count]=0 proc[detailed_mem]="${work_array[-2]}" proc[detailed_mem_int]="${proc[detailed_mem]/./}" if [[ ${proc[detailed_mem_int]::1} == "0" ]]; then proc[detailed_mem_int]="${proc[detailed_mem_int]:1}0"; fi #* Scale up low mem values to see any changes on mini graph if ((proc[detailed_mem_int]>900)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/10)) elif ((proc[detailed_mem_int]>600)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/8)) elif ((proc[detailed_mem_int]>300)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/5)) elif ((proc[detailed_mem_int]>100)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/2)) elif ((proc[detailed_mem_int]<50)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]*2)); fi unset 'proc[detailed_mem_string]' read -r mem_string < <(ps -o rss:1 --no-headers -p ${pid} || true) floating_humanizer -v proc[detailed_mem_string] -B -s 1 $mem_string if [[ -z ${proc[detailed_mem_string]} ]]; then proc[detailed_mem_string]="? Byte"; fi fi #* Copy process cpu usage to history array and trim earlier entries if ((${#detail_history[@]}>box[details_width]*2)); then detail_history=( "${detail_history[@]:${box[details_width]}}" "$((cpu_int+4))") else detail_history+=("$((cpu_int+4))") fi #* Copy process mem usage to history array and trim earlier entries if ((${#detail_mem_history[@]}>box[details_width])); then detail_mem_history=( "${detail_mem_history[@]:$((box[details_width]/2))}" "${proc[detailed_mem_int]}") else detail_mem_history+=("${proc[detailed_mem_int]}") fi #* Remove selected process from array if process is excluded by filtering or not on first page if [[ -n $filter && ! ${proc[detailed_name]} =~ $filter ]]; then unset 'proc_array[count]' cpu_int=0; pid_count=0 fi fi #* Create small graphs for all visible processes using more than 1% cpu time if [[ ${cpu_int} -gt 0 ]]; then pid_count=5; fi if [[ -z ${!pid_graph} && ${cpu_int} -gt 0 ]]; then tmp_value_array=("$((cpu_int+4))") create_mini_graph -o "pid_${pid}_graph" -nc -w 5 "tmp_value_array" elif [[ ${pid_count} -gt 0 ]]; then if [[ ${cpu_int} -gt 9 ]]; then create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+15))" else create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+9))" fi pid_count=$((${pid_count}-1)) elif [[ ${pid_count} == "0" ]]; then unset "pid_${pid}_graph" "pid_${pid}_graph_even" "pid_${pid}_graph_odd" "pid_${pid}_graph_last_type" "pid_${pid}_graph_last_val" unset "pid_${pid}_count" fi else tmp_percent="${proc_array[count]:(-5)}"; tmp_percent="${tmp_percent// /}"; if [[ ${tmp_percent//./} != "$tmp_percent" ]]; then tmp_percent="${tmp_percent::-2}"; fi if ((tmp_percent>100)); then proc_array[count]="${proc_array[count]::-5} 100" fi fi proc[old_${pid}_ticks]=${proc[new_${pid}_ticks]} fi if ((breaking==1)); then if [[ ${proc[detailed]} == "1" && -z ${proc[detailed_cpu]} ]] && ps ${proc[detailed_pid]} >/dev/null 2>&1; then readarray ${options} -O ${#proc_array[@]} proc_array < <(ps -o pid:${proc[pid_len]}=Pid:,comm:${format_cmd}=${tree:-Program:}${format_args},nlwp:3=Tr:,euser:6=User:,pmem=Mem%,pcpu:10=Cpu% --no-headers -p ${proc[detailed_pid]} || true) ((++breaking)) else break fi elif ((breaking==2)); then unset 'proc_array[-1]' break fi done proc[old_timestamp]=${proc[new_timestamp]} if ((proc[detailed]==1)) && [[ -z ${proc[detailed_cpu]} && -z ${proc[detailed_killed]} ]]; then proc[detailed_killed]=1; proc[detailed_change]=1 elif [[ -n ${proc[detailed_cpu]} ]]; then unset 'proc[detailed_killed]'; fi #* Sort output array based on cpu usage if "cpu responsive" is selected if [[ ${proc_sorting} == "cpu responsive" && ${proc_tree} != true ]]; then local -a sort_array if [[ -z ${proc[reverse]} ]]; then local sort_rev="-r"; fi sort_array[0]="${proc_array[0]}" readarray -O 1 -t sort_array < <(printf "%s\n" "${proc_array[@]:1}" | awk '{ print $NF, $0 }' | sort -n -k1 ${sort_rev}| sed 's/^[0-9\.]* //') proc_array=("${sort_array[@]}") fi #* Clear up memory by removing variables and graphs of no longer running processes ((++proc[general_counter])) if ((proc[general_counter]>100)); then proc[general_counter]=0 for pids in ${!pid_history[@]}; do if [[ ! -e /proc/${pids} ]]; then unset "pid_${pids}_graph" "pid_${pids}_graph_even" "pid_${pids}_graph_odd" "pid_${pids}_graph_last_type" "pid_${pids}_graph_last_val" unset "pid_${pids}_count" unset "proc[new_${pids}_ticks]" unset "proc[old_${pids}_ticks]" unset "pid_history[${pids}]" fi done fi } collect_processes_psutil() { local argument=$1 if [[ -n $skip_process_draw && $argument != "now" ]]; then return; fi if [[ $argument == "now" ]]; then skip_process_draw=1; fi local prog_len arg_len symbol="▼" selected width=${box[processes_width]} height=${box[processes_height]} local pcpu_usage pids p_count cpu_int pids max_lines i pi case ${proc_sorting} in "pid") selected="Pid:";; "program") selected="Program:";; "arguments") selected="Arguments:";; "threads") selected="Threads:";; "user") selected="User:";; "memory") selected="Mem%";; "cpu lazy"|"cpu responsive") selected="Cpu%";; esac if [[ ${proc_tree} == true && ${proc_sorting} =~ pid|program|arguments ]]; then selected="Tree:"; fi if [[ -n ${proc[reverse]} ]]; then symbol="▲"; fi if ((proc[detailed]==0)) && [[ -n ${proc[detailed_name]} ]]; then unset 'proc[detailed_name]' 'proc[detailed_killed]' 'proc[detailed_cpu_int]' 'proc[detailed_cmd]' unset 'proc[detailed_mem]' 'proc[detailed_mem_int]' 'proc[detailed_user]' 'proc[detailed_threads]' unset 'detail_graph[@]' 'detail_mem_graph' 'detail_history[@]' 'detail_mem_history[@]' unset 'proc[detailed_runtime]' 'proc[detailed_mem_string]' 'proc[detailed_parent_pid]' 'proc[detailed_parent_name]' fi unset 'proc[detailed_cpu]' if ((width>60)); then arg_len=$((width-55)) prog_len=15 else prog_len=$((width-40)) arg_len=0 if [[ $proc_sorting == "threads" ]]; then selected="Tr:"; fi fi unset 'proc_array[@]' if ! py_command -a proc_array "get_proc(sorting='${proc_sorting}', tree=${proc_tree^}, prog_len=${prog_len}, arg_len=${arg_len}, search='${filter}', reverse=${proc_reversed^}, proc_per_cpu=${proc_per_core^})"; then proc_array=(""); return fi proc_array[0]="${proc_array[0]/ ${selected}/${symbol}${selected}}" for((i=1;i<${#proc_array[@]};i++)); do if [[ -z ${proc_array[i]} ]]; then continue; fi out_arr=(${proc_array[i]}) pi=0 if [[ $proc_tree == true ]]; then while ! is_int "${out_arr[pi]}" && ((pi<${#out_arr[@]}-1)); do ((++pi)); done fi pid="${out_arr[pi]}" if ! is_int "${pid}"; then continue; fi pcpu_usage="${out_arr[-1]}" if ! printf -v cpu_int "%.0f" "${pcpu_usage}" 2>/dev/null; then continue; fi pid_history[${pid}]="1" #* Create small graphs for all visible processes using more than 1% rounded cpu time pid_graph="pid_${pid}_graph" if ! local -n pid_count="pid_${pid}_count" 2>/dev/null; then continue; fi if [[ ${cpu_int} -gt 0 ]]; then pid_count=5; fi if [[ -z ${!pid_graph} && ${cpu_int} -gt 0 ]]; then tmp_value_array=("$((cpu_int+4))") create_mini_graph -o "pid_${pid}_graph" -nc -w 5 "tmp_value_array" elif [[ ${pid_count} -gt 0 ]]; then if [[ ${cpu_int} -gt 9 ]]; then create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+15))" elif [[ ${cpu_int} -gt 0 ]]; then create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+9))" else create_mini_graph -nc -add-value "pid_${pid}_graph" "0" fi pid_count=$((${pid_count}-1)) elif [[ ${pid_count} == "0" ]]; then unset "pid_${pid}_graph" "pid_${pid}_graph_even" "pid_${pid}_graph_odd" "pid_${pid}_graph_last_type" "pid_${pid}_graph_last_val" unset "pid_${pid}_count" fi #* Get info for detailed box if enabled if [[ ${pid} == "${proc[detailed_pid]}" ]]; then local -a det_array if [[ -z ${proc[detailed_name]} ]]; then local get_mem mem_string cmdline="" py_command -a det_array "get_detailed_names_cmd(${pid})" if [[ -z ${det_array[0]} ]]; then continue; fi proc[detailed_name]="${det_array[0]::15}" proc[detailed_parent_name]="${det_array[1]}" proc[detailed_user]="${det_array[2]}" proc[detailed_cmd]="${det_array[3]}" fi proc[detailed_cpu]="${out_arr[-1]}" proc[detailed_cpu_int]="${cpu_int}" proc[detailed_threads]="${out_arr[-4]}" unset 'det_array[@]' py_command -a det_array "get_detailed_mem_time(${pid})" if [[ -z ${det_array[0]} ]]; then continue; fi unset 'proc[detailed_mem_string]' floating_humanizer -v proc[detailed_mem_string] -B ${det_array[0]} if [[ -z ${proc[detailed_mem_string]} ]]; then proc[detailed_mem_string]="? Byte"; fi if ((${#det_array[1]}>8)); then proc[detailed_runtime]="${det_array[1]/ days, /-}" else proc[detailed_runtime]="${det_array[1]}"; fi proc[detailed_mem_count]=0 proc[detailed_mem]="${out_arr[-2]}" proc[detailed_mem_int]="${proc[detailed_mem]/./}" if [[ ${proc[detailed_mem_int]::1} == "0" ]]; then proc[detailed_mem_int]="${proc[detailed_mem_int]:1}0"; fi #* Scale up low mem values to see any changes on mini graph if ((proc[detailed_mem_int]>900)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/10)) elif ((proc[detailed_mem_int]>600)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/8)) elif ((proc[detailed_mem_int]>300)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/5)) elif ((proc[detailed_mem_int]>100)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/2)) elif ((proc[detailed_mem_int]<50)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]*2)); fi #* Copy process cpu usage to history array and trim earlier entries if ((${#detail_history[@]}>box[details_width]*2)); then detail_history=( "${detail_history[@]:${box[details_width]}}" "$((cpu_int+4))") else detail_history+=("$((cpu_int+4))") fi #* Copy process mem usage to history array and trim earlier entries if ((${#detail_mem_history[@]}>box[details_width])); then detail_mem_history=( "${detail_mem_history[@]:$((box[details_width]/2))}" "${proc[detailed_mem_int]}") else detail_mem_history+=("${proc[detailed_mem_int]}") fi fi if ((i==height-2)); then if [[ ${proc[selected]} -gt 0 || -n $filter || ${proc[start]} -gt 1 ]] || [[ ${proc[detailed]} -eq 1 && -z ${proc[detailed_cpu]} && -z ${proc[detailed_killed]} ]]; then : else break; fi fi done if ((proc[detailed]==1)) && [[ -z ${proc[detailed_cpu]} && -z ${proc[detailed_killed]} ]]; then proc[detailed_killed]=1; proc[detailed_change]=1 elif [[ -n ${proc[detailed_cpu]} ]]; then unset 'proc[detailed_killed]'; fi #* Clear up memory ((++proc[general_counter])) if ((proc[general_counter]>100)); then proc[general_counter]=0 for pids in ${!pid_history[@]}; do unset "pid_${pids}_graph" "pid_${pids}_graph_even" "pid_${pids}_graph_odd" "pid_${pids}_graph_last_type" "pid_${pids}_graph_last_val" unset "pid_${pids}_count" unset "pid_history[${pids}]" done fi } collect_net() { #? Collect information from "/proc/net/dev" local operations operation direction index unit_selector speed speed_B total local -a net_dev history_sorted history_last if [[ -n ${net[no_device]} ]]; then return; fi if [[ $1 == "init" ]]; then for direction in "download" "upload"; do net[${direction}_max]=0 net[${direction}_new_low]=0 net[${direction}_new_max]=0 net[${direction}_max_current]=0 net[${direction}_graph_max]=$((50<<10)) done unset 'download_graph[@]' 'upload_graph[@]' 'net_history_download[@]' 'net_history_upload[@]' fi #* Get the line with relevant net device from /proc/net/dev or psutil into array net_dev, index 1 is download, index 9 is upload if [[ $use_psutil == true ]]; then py_command -v net_dev "get_net('${net[device]}')" || return net_dev=(${net_dev}) if ! is_int "${net_dev[0]}"; then net[no_device]=1; return; fi else if ! get_value -map net_dev -sf "/proc/net/dev" -k "${net[device]}" -a; then net[no_device]=1; return; fi fi #* Timestamp the values to accurately calculate values in seconds get_ms net[new_timestamp] for direction in "download" "upload"; do if [[ $direction == "download" ]]; then index=1 else index=9; fi net[new_${direction}]=${net_dev[index]} if [[ -n ${net[old_${direction}]} ]]; then #* Get total, convert to floating point and format string to best fitting unit in Bytes if ((net[nic_change]==1 & net[reset]==1)); then unset "net[total_offset_${direction}]"; net[reset]=0; fi if ((net[reset]==1)) && [[ -z ${net[total_offset_${direction}]} || ${net[total_offset_${direction}]} -gt ${net[new_${direction}]} ]]; then net[total_offset_${direction}]=${net[new_${direction}]} elif ((net[reset]==0)) && [[ -n ${net[total_offset_${direction}]} ]]; then unset "net[total_offset_${direction}]"; fi floating_humanizer -Byte -v net[total_${direction}] $((${net[new_${direction}]}-${net[total_offset_${direction}]:-0})) #* Calculate current speeds: ("New value" - "Old value") * 1000(for ms to seconds) / ("new_timestamp" - "old_timestamp") net[speed_${direction}]=$(( (${net[new_${direction}]}-${net[old_${direction}]})*1000/(net[new_timestamp]-net[old_timestamp]) )) #* Convert to floating point and format string to best fitting unit in Bytes and Bits per second floating_humanizer -Byte -per-second -v net[speed_${direction}_byteps] ${net[speed_${direction}]} floating_humanizer -bit -per-second -v net[speed_${direction}_bitps] ${net[speed_${direction}]} #* Update download and upload max values for graph if ((${net[speed_${direction}]}>${net[${direction}_max]})); then net[${direction}_max]=${net[speed_${direction}]} fi if ((${net[speed_${direction}]}>${net[${direction}_graph_max]})); then ((++net[${direction}_new_max])) if ((net[${direction}_new_low]>0)); then ((net[${direction}_new_low]--)); fi elif ((${net[${direction}_graph_max]}>10<<10 & ${net[speed_${direction}]}<${net[${direction}_graph_max]}/10)); then ((++net[${direction}_new_low])) if ((net[${direction}_new_max]>0)); then ((net[${direction}_new_max]--)); fi fi #* Copy download and upload speed to history arrays and trim earlier entries local -n history="net_history_${direction}" if ((${#history[@]}>box[net_width]*4)); then history=( "${history[@]:$((box[net_width]*2))}" "${net[speed_${direction}]}") else history+=("${net[speed_${direction}]}") fi #* Check for new max value and set flag to adjust resolution of graph if needed if ((${net[${direction}_new_max]}>=5)); then net[${direction}_graph_max]=$((${net[${direction}_max]}+(${net[${direction}_max]}/3) )) net[${direction}_redraw]=1 net[${direction}_new_max]=0 #* If current max value isn't relevant, sort array to get the next largest value to set graph resolution elif ((${net[${direction}_new_low]}>=5 & ${#history[@]}>5)); then history_last=("${history[@]:(-5)}") sort_array_int "history_last" "history_sorted" net[${direction}_max]=${history_sorted[0]} net[${direction}_graph_max]=$(( ${net[${direction}_max]}*3 )) if ((${net[${direction}_graph_max]}<10<<10)); then net[${direction}_graph_max]=$((10<<10)); fi net[${direction}_redraw]=1 net[${direction}_new_low]=0 fi fi floating_humanizer -Byte -short -v net[${direction}_max_string] ${net[${direction}_graph_max]} net[old_${direction}]=${net[new_${direction}]} done net[old_timestamp]=${net[new_timestamp]} } calc_sizes() { #? Calculate width and height of all boxes local pos calc_size calc_total percent threads=${cpu[threads]} #* Calculate heights for pos in ${box[boxes]/processes/}; do if [[ $pos = "cpu" ]]; then percent=32; elif [[ $pos = "mem" ]]; then percent=40; else percent=28; fi #* Multiplying with 10 to convert to floating point calc_size=$(( (tty_height*10)*(percent*10)/100 )) #* Round down if last 2 digits of value is below "50" and round up if above if ((${calc_size:(-2):1}==0)); then calc_size=$((calc_size+10)); fi if ((${calc_size:(-2)}<50)); then calc_size=$((${calc_size::-2})) else calc_size=$((${calc_size::-2}+1)) fi #* Subtract from last value if the total of all rounded numbers is larger then terminal height while ((calc_total+calc_size>tty_height)); do ((--calc_size)); done calc_total=$((calc_total+calc_size)) #* Set calculated values in box array box[${pos}_line]=$((calc_total-calc_size+1)) box[${pos}_col]=1 box[${pos}_height]=$calc_size box[${pos}_width]=$tty_width done #* Calculate widths unset calc_total for pos in net processes; do if [[ $pos = "net" ]]; then percent=45; else percent=55; fi #* Multiplying with 10 to convert to floating point calc_size=$(( (tty_width*10)*(percent*10)/100 )) #* Round down if last 2 digits of value is below "50" and round up if above if ((${calc_size:(-2)}<50)); then calc_size=$((${calc_size::-2})) else calc_size=$((${calc_size::-2}+1)) fi #* Subtract from last value if the total of all rounded numbers is larger then terminal width while ((calc_total+calc_size>tty_width)); do ((--calc_size)); done calc_total=$((calc_total+calc_size)) #* Set calculated values in box array box[${pos}_col]=$((calc_total-calc_size+1)) box[${pos}_width]=$calc_size done #* Copy numbers around to get target layout box[mem_width]=${box[net_width]} box[processes_line]=${box[mem_line]} box[processes_height]=$((box[mem_height]+box[net_height])) # threads=${box[testing]} #! For testing, remove <-------------- #* Recalculate size of process box if currently showing detailed process information if ((proc[detailed]==1)); then box[details_line]=${box[processes_line]} box[details_col]=${box[processes_col]} box[details_width]=${box[processes_width]} box[details_height]=8 box[processes_line]=$((box[processes_line]+box[details_height])) box[processes_height]=$((box[processes_height]-box[details_height])) fi #* Calculate number of columns and placement of cpu meter box local cpu_line=$((box[cpu_line]+1)) cpu_width=$((box[cpu_width]-2)) cpu_height=$((box[cpu_height]-2)) box_cols if ((threads>(cpu_height-3)*3 && tty_width>=200)); then box[p_width]=$((24*4)); box[p_height]=$((threads/4+4)); box_cols=4 elif ((threads>(cpu_height-3)*2 && tty_width>=150)); then box[p_width]=$((24*3)); box[p_height]=$((threads/3+5)); box_cols=3 elif ((threads>cpu_height-3 && tty_width>=100)); then box[p_width]=$((24*2)); box[p_height]=$((threads/2+4)); box_cols=2 else box[p_width]=24; box[p_height]=$((threads+4)); box_cols=1 fi if [[ $check_temp == true ]]; then box[p_width]=$(( box[p_width]+13*box_cols)) fi if ((box[p_height]>cpu_height)); then box[p_height]=$cpu_height; fi box[p_col]="$((cpu_width-box[p_width]+2))" box[p_line]="$((cpu_line+(cpu_height/2)-(box[p_height]/2)+1))" #* Calculate placement of mem divider local mem_line=$((box[mem_line]+1)) mem_width=$((box[mem_width]-2)) mem_height=$((box[mem_height]-2)) mem_col=$((box[mem_col]+1)) box[m_width]=$((mem_width/2)) box[m_width2]=${box[m_width]} if ((box[m_width]+box[m_width2]9)); then box[n_height]=9 else box[n_height]=$net_height; fi box[n_col]="$((net_width-box[n_width]+2))" box[n_line]="$((net_line+(net_height/2)-(box[n_height]/2)+1))" } draw_bg() { #? Draw all box outlines local this_box cpu_p_width i cpu_model_len unset boxes_out for this_box in ${box[boxes]}; do create_box -v boxes_out -col ${box[${this_box}_col]} -line ${box[${this_box}_line]} -width ${box[${this_box}_width]} -height ${box[${this_box}_height]} -fill -lc "${box[${this_box}_color]}" -title ${this_box} done #* Misc cpu box if [[ $check_temp == true ]]; then cpu_model_len=18; else cpu_model_len=9; fi create_box -v boxes_out -col $((box[p_col]-1)) -line $((box[p_line]-1)) -width ${box[p_width]} -height ${box[p_height]} -lc ${theme[div_line]} -t "${cpu[model]:0:${cpu_model_len}}" print -v boxes_out -m ${box[cpu_line]} $((box[cpu_col]+10)) -rs \ -fg ${box[cpu_color]} -t "┤" -b -fg ${theme[hi_fg]} -t "m" -fg ${theme[title]} -t "enu" -rs -fg ${box[cpu_color]} -t "├" #* Misc mem print -v boxes_out -m ${box[mem_line]} $((box[mem_col]+box[m_width]+2)) -rs -fg ${box[mem_color]} -t "┤" -fg ${theme[title]} -b -t "disks" -rs -fg ${box[mem_color]} -t "├" print -v boxes_out -m ${box[mem_line]} $((box[mem_col]+box[m_width])) -rs -fg ${box[mem_color]} -t "┬" print -v boxes_out -m $((box[mem_line]+box[mem_height]-1)) $((box[mem_col]+box[m_width])) -fg ${box[mem_color]} -t "┴" for((i=1;i<=box[mem_height]-2;i++)); do print -v boxes_out -m $((box[mem_line]+i)) $((box[mem_col]+box[m_width])) -fg ${theme[div_line]} -t "│" done #* Misc net box create_box -v boxes_out -col $((box[n_col]-1)) -line $((box[n_line]-1)) -width ${box[n_width]} -height ${box[n_height]} -lc ${theme[div_line]} -t "Download" print -v boxes_out -m $((box[n_line]+box[n_height]-2)) $((box[n_col]+1)) -rs -fg ${theme[div_line]} -t "┤" -fg ${theme[title]} -b -t "Upload" -rs -fg ${theme[div_line]} -t "├" if [[ $1 == "quiet" ]]; then draw_out="${boxes_out}" else echo -en "${boxes_out}"; fi draw_update_string $1 } draw_cpu() { #? Draw cpu and core graphs and print percentages local cpu_out i name cpu_p_color temp_color y pt_line pt_col p_normal_color="${theme[main_fg]}" threads=${cpu[threads]} local meter meter_size meter_width temp_var cpu_out_var core_name temp_name temp_width #* Get variables from previous calculations local col=$((box[cpu_col]+1)) line=$((box[cpu_line]+1)) width=$((box[cpu_width]-2)) height=$((box[cpu_height]-2)) local p_width=${box[p_width]} p_height=${box[p_height]} p_col=${box[p_col]} p_line=${box[p_line]} #* If resized recreate cpu meter/graph box, cpu graph and core graphs if ((resized>0)); then local graph_a_size graph_b_size graph_a_size=$((height/2)); graph_b_size=${graph_a_size} if ((graph_a_size*224+temp_width)); then name="CPU Total "; meter_width=$((p_width-17-temp_width)) fi #* Create cpu usage meter if ((i==0)); then create_meter -v meter -w $meter_width -f -c color_cpu_graph ${cpu_usage[i]} else core_name="cpu_core_${i}_graph" meter="${!core_name}" fi if ((p_width>84+temp_width & i>=(p_height-2)*3-2)); then pt_line=$((p_line+i-y*4)); pt_col=$((p_col+72+temp_width*3)) elif ((p_width>54+temp_width & i>=(p_height-2)*2-1)); then pt_line=$((p_line+i-y*3)); pt_col=$((p_col+48+temp_width*2)) elif ((p_width>24+temp_width & i>=p_height-2)); then pt_line=$((p_line+i-y*2)); pt_col=$((p_col+24+temp_width)) else y=$i; fi print -v cpu_out_var -m $((pt_line+y)) $pt_col -rs -fg $p_normal_color -jl 7 -t "$name" -fg ${theme[inactive_fg]} "⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀" -l 10 -fg $cpu_p_color -t "$meter"\ -jr 4 -fg $cpu_p_color -t "${cpu_usage[i]}" -fg $p_normal_color -t "%" if [[ $check_temp == true && -n ${cpu[temp_${i}]} ]]; then print -v cpu_out_var -fg ${theme[inactive_fg]} " ⡀⡀⡀⡀⡀" -l 7 -fg $temp_color -jl 7 -t " ${!temp_name}" -jr 4 -t ${cpu[temp_${i}]} -fg $p_normal_color -t ${cpu[temp_unit]} fi if (( i>(p_height-2)*( p_width/(24+temp_width) )-( p_width/(24+temp_width) )-1 )); then break; fi done #* Print load average and uptime if ((pt_line+y+30 & resized==0)); then return; fi local i swap_used_meter swap_free_meter mem_available_meter mem_free_meter mem_used_meter mem_cached_meter normal_color="${theme[main_fg]}" value_text local meter_mod_w meter_mod_pos value type m_title meter_options values="used available cached free" local -a types=("mem") unset mem_out if [[ -n ${swap[total]} && ${swap[total]} -gt 0 ]]; then types+=("swap"); fi #* Get variables from previous calculations local col=$((box[mem_col]+1)) line=$((box[mem_line]+1)) width=$((box[mem_width]-2)) height=$((box[mem_height]-2)) local m_width=${box[m_width]} m_height=${box[m_height]} m_col=${box[m_col]} m_line=${box[m_line]} mem_line=$((box[mem_col]+box[m_width])) #* Create text and meters for memory and swap and adapt sizes based on available height local y_pos=$m_line v_height=8 list value meter inv_meter for type in ${types[@]}; do local -n type_name="$type" if [[ $type == "mem" ]]; then m_title="memory" else m_title="$type" if ((height>14)); then ((y_pos++)); fi fi #* Print name of type and total amount in humanized base 2 bytes print -v mem_out -m $y_pos $m_col -rs -fg ${theme[title]} -b -jl 9 -t "${m_title^}:" -m $((y_pos++)) $((mem_line-10)) -jr 9 -t " ${type_name[total_string]::$((m_width-11))}" for value in ${values}; do if [[ $type == "swap" && $value =~ available|cached ]]; then continue; fi if [[ $system == "MacOS" && $value == "cached" ]]; then value_text="active" else value_text="${value::$((m_width-12))}"; fi if ((height<14)); then value_text="${value_text::5}"; fi #* Print name of value and value amount in humanized base 2 bytes print -v mem_out -m $y_pos $m_col -rs -fg $normal_color -jl 9 -t "${value_text^}:" -m $((y_pos++)) $((mem_line-10)) -jr 9 -t " ${type_name[${value}_string]::$((m_width-11))}" #* Create meter for value and calculate size and placement depending on terminal size if ((height>v_height++ | tty_width>100)); then if ((height<=v_height & tty_width<150)); then meter_mod_w=12 meter_mod_pos=7 ((y_pos--)) elif ((height<=v_height)); then print -v mem_out -m $((--y_pos)) $((m_col+5)) -jr 4 -t "${type_name[${value}_percent]}%" meter_mod_w=14 meter_mod_pos=10 fi create_meter -v ${type}_${value}_meter -w $((m_width-7-meter_mod_w)) -f -c color_${value}_graph ${type_name[${value}_percent]} meter="${type}_${value}_meter" print -v mem_out -m $((y_pos++)) $((m_col+meter_mod_pos)) -t "${!meter}" -rs -fg $normal_color if [[ -z $meter_mod_w ]]; then print -v mem_out -jr 4 -t "${type_name[${value}_percent]}%"; fi fi #if [[ $system == "MacOS" && -z $swap_on ]] && ((height>14)); then ((y_pos++)); fi done done #* Create text and meters for disks and adapt sizes based on available height local disk_num disk_name disk_value v_height2 just_val name_len y_pos=$m_line m_col=$((m_col+m_width)) m_width=${box[m_width2]} v_height=$((${#disks_name[@]})) unset meter_mod_w meter_mod_pos for disk_name in "${disks_name[@]}"; do if ((y_pos>m_line+height-2)); then break; fi #* Print folder disk is mounted on, total size in humanized base 2 bytes and io stats if enabled print -v mem_out -m $((y_pos++)) $m_col -rs -fg ${theme[title]} -b -t "${disks_name[disk_num]::10}" name_len=${#disks_name[disk_num]}; if ((name_len>10)); then name_len=10; fi if [[ -n ${disks_io[disk_num]} && ${disks_io[disk_num]} != "0" ]] && ((m_width-11-name_len>6)); then print -v mem_out -jc $((m_width-name_len-10)) -rs -fg ${theme[main_fg]} -t "${disks_io[disk_num]::$((m_width-10-name_len))}" just_val=8 else just_val=$((m_width-name_len-2)) fi print -v mem_out -jr ${just_val} -fg ${theme[title]} -b -t "${disks_total[disk_num]::$((m_width-11))}" for value in "used" "free"; do if ((height=v_height*5 | tty_width>100)); then local -n disk_value_percent="disks_${value}_percent" if ((height<=v_height*5 & tty_width<150)); then meter_mod_w=12 meter_mod_pos=7 ((y_pos--)) elif ((height<=v_height*5)); then print -v mem_out -m $((--y_pos)) $((m_col+5)) -jr 4 -t "${disk_value_percent[disk_num]}%" meter_mod_w=14 meter_mod_pos=10 fi create_meter -v disk_${disk_num}_${value}_meter -w $((m_width-7-meter_mod_w)) -f -c color_${value}_graph ${disk_value_percent[disk_num]} meter="disk_${disk_num}_${value}_meter" print -v mem_out -m $((y_pos++)) $((m_col+meter_mod_pos)) -t "${!meter}" -rs -fg $normal_color if [[ -z $meter_mod_w ]]; then print -v mem_out -jr 4 -t "${disk_value_percent[disk_num]}%"; fi fi if ((y_pos>m_line+height-1)); then break; fi done if ((height>=v_height*4 & height=v_height*6)); then ((y_pos++)); fi ((++disk_num)) done if ((resized>0)); then ((resized++)); fi #* Print created text, graph and meters to output variable draw_out+="${mem_graph[*]}${swap_graph[*]}${mem_out}" } draw_processes() { #? Draw processes and values to screen local argument="$1" if [[ -n $skip_process_draw && $argument != "now" ]]; then return; fi local line=${box[processes_line]} col=${box[processes_col]} width=${box[processes_width]} height=${box[processes_height]} out_line y=1 fg_step_r=0 fg_step_g=0 fg_step_b=0 checker=2 page_string sel_string local reverse_string reverse_pos order_left="───────────┤" filter_string current_num detail_location det_no_add com_fg pg_arrow_up_fg pg_arrow_down_fg p_height=$((height-3)) local pid=0 pid_graph pid_step_r pid_step_g pid_step_b pid_add_r pid_add_g pid_add_b bg_add bg_step proc_start up_fg down_fg page_up_fg page_down_fg this_box=processes local d_width=${box[details_width]} d_height=${box[details_height]} d_line=${box[details_line]} d_col=${box[details_col]} local detail_graph_width=$((d_width/3+2)) detail_graph_height=$((d_height-1)) kill_fg det_mod fg_add_r fg_add_g fg_add_b local right_width=$((d_width-detail_graph_width-2)) local right_col=$((d_col+detail_graph_width+4)) local -a pid_rgb=(${theme[proc_misc]}) fg_rgb=(${theme[main_fg_dec]}) local pid_r=${pid_rgb[0]} pid_g=${pid_rgb[1]} pid_b=${pid_rgb[2]} fg_r=${fg_rgb[0]} fg_g=${fg_rgb[1]} fg_b=${fg_rgb[2]} if [[ $argument == "now" ]]; then skip_process_draw=1; fi if [[ $proc_gradient == true ]]; then if ((fg_r+fg_g+fg_b<(255*3)/2)); then fg_add_r="$(( (fg_r-255-((fg_r-255)/6) )/height))" fg_add_g="$(( (fg_g-255-((fg_g-255)/6) )/height))" fg_add_b="$(( (fg_b-255-((fg_b-255)/6) )/height))" pid_add_r="$(( (pid_r-255-((pid_r-255)/6) )/height))" pid_add_g="$(( (pid_g-255-((pid_g-255)/6) )/height))" pid_add_b="$(( (pid_b-255-((pid_b-255)/6) )/height))" else fg_add_r="$(( (fg_r-(fg_r/6) )/height))" fg_add_g="$(( (fg_g-(fg_g/6) )/height))" fg_add_b="$(( (fg_b-(fg_b/6) )/height))" pid_add_r="$(( (pid_r-(pid_r/6) )/height))" pid_add_g="$(( (pid_g-(pid_g/6) )/height))" pid_add_b="$(( (pid_b-(pid_b/6) )/height))" fi fi unset proc_out #* Details box if ((proc[detailed_change]>0)) || ((proc[detailed]>0 & resized>0)); then proc[detailed_change]=0 proc[order_change]=1 proc[page_change]=1 if ((proc[detailed]==1)); then unset proc_det local enter_fg enter_a_fg misc_fg misc_a_fg i det_y=6 dets cmd_y if [[ ${#detail_history[@]} -eq 1 ]] || ((resized>0)); then unset proc_det2 create_graph -o detail_graph -d $((d_line+1)) $((d_col+1)) ${detail_graph_height} ${detail_graph_width} -c color_cpu_graph -n detail_history if ((tty_width>120)); then create_mini_graph -o detail_mem_graph -w $((right_width/3-3)) -nc detail_mem_history; fi det_no_add=1 for detail_location in "${d_line}" "$((d_line+d_height))"; do print -v proc_det2 -m ${detail_location} $((d_col+1)) -rs -fg ${box[processes_color]} -rp $((d_width-2)) -t "─" done for((i=1;i128)); then print -v proc_det2 -r 1 -t "┤" -fg ${theme[title]} -b -t "${proc[detailed_pid]}" -rs -fg ${box[processes_color]} -t "├"; fi if ((${#proc[detailed_cmd]}>(right_width-6)*2)); then ((det_y--)); dets=2 elif ((${#proc[detailed_cmd]}>right_width-6)); then dets=1; fi print -v proc_det2 -fg ${theme[title]} -b for i in C M D; do print -v proc_det2 -m $((d_line+5+cmd_y++)) $right_col -t "$i" done print -v proc_det2 -m $((d_line+det_y++)) $((right_col+1)) -jc $((right_width-4)) -rs -fg ${theme[main_fg]} -t "${proc[detailed_cmd]::$((right_width-6))}" if ((dets>0)); then print -v proc_det2 -m $((d_line+det_y++)) $((right_col+2)) -jl $((right_width-6)) -t "${proc[detailed_cmd]:$((right_width-6)):$((right_width-6))}"; fi if ((dets>1)); then print -v proc_det2 -m $((d_line+det_y)) $((right_col+2)) -jl $((right_width-6)) -t "${proc[detailed_cmd]:$(( (right_width-6)*2 )):$((right_width-6))}"; fi fi if ((proc[selected]>0)); then enter_fg="${theme[inactive_fg]}"; enter_a_fg="${theme[inactive_fg]}"; else enter_fg="${theme[title]}"; enter_a_fg="${theme[hi_fg]}"; fi if [[ -n ${proc[detailed_killed]} ]]; then misc_fg="${theme[title]}"; misc_a_fg="${theme[hi_fg]}" else misc_fg=$enter_fg; misc_a_fg=$enter_a_fg; fi print -v proc_det -m ${d_line} $((d_col+d_width-11)) -fg ${box[processes_color]} -t "┤" -fg $enter_fg -b -t "close " -fg $enter_a_fg -t "↲" -rs -fg ${box[processes_color]} -t "├" if ((tty_width<129)); then det_mod="-8"; fi print -v proc_det -m ${d_line} $((d_col+detail_graph_width+4+det_mod)) -t "┤" -fg $misc_a_fg -b -t "t" -fg $misc_fg -t "erminate" -rs -fg ${box[processes_color]} -t "├" print -v proc_det -r 1 -t "┤" -fg $misc_a_fg -b -t "k" -fg $misc_fg -t "ill" -rs -fg ${box[processes_color]} -t "├" if ((tty_width>104)); then print -v proc_det -r 1 -t "┤" -fg $misc_a_fg -b -t "i" -fg $misc_fg -t "nterrupt" -rs -fg ${box[processes_color]} -t "├"; fi proc_det="${proc_det2}${proc_det}" proc_out="${proc_det}" elif ((resized==0)); then unset proc_det create_box -v proc_out -col ${box[${this_box}_col]} -line ${box[${this_box}_line]} -width ${box[${this_box}_width]} -height ${box[${this_box}_height]} -fill -lc "${box[${this_box}_color]}" -title ${this_box} fi fi if [[ ${proc[detailed]} -eq 1 ]]; then local det_status status_color det_columns=3 if ((tty_width>140)); then ((det_columns++)); fi if ((tty_width>150)); then ((det_columns++)); fi if [[ -z $det_no_add && $1 != "now" && -z ${proc[detailed_killed]} ]]; then create_graph -add-last detail_graph detail_history if ((tty_width>120)); then create_mini_graph -w $((right_width/3-3)) -nc -add-last detail_mem_graph detail_mem_history; fi fi print -v proc_out -fg ${theme[title]} -b cmd_y=0 for i in C P U; do print -v proc_out -m $((d_line+3+cmd_y++)) $((d_col+1)) -t "$i" done print -v proc_out -m $((d_line+1)) $((d_col+1)) -fg ${theme[title]} -t "${proc[detailed_cpu]}%" if [[ -n ${proc[detailed_killed]} ]]; then det_status="stopped"; status_color="${theme[inactive_fg]}" else det_status="running"; status_color="${theme[proc_misc]}"; fi print -v proc_out -m $((d_line+1)) ${right_col} -fg ${theme[title]} -b -jc $((right_width/det_columns-1)) -t "Status:" -jc $((right_width/det_columns)) -t "Elapsed:" -jc $((right_width/det_columns)) -t "Parent:" if ((det_columns>=4)); then print -v proc_out -jc $((right_width/det_columns-1)) -t "User:"; fi if ((det_columns>=5)); then print -v proc_out -jc $((right_width/det_columns-1)) -t "Threads:"; fi print -v proc_out -m $((d_line+2)) ${right_col} -rs -fg ${status_color} -jc $((right_width/det_columns-1)) -t "${det_status}" -jc $((right_width/det_columns)) -fg ${theme[main_fg]} -t "${proc[detailed_runtime]::$((right_width/det_columns-1))}" -jc $((right_width/det_columns)) -t "${proc[detailed_parent_name]::$((right_width/det_columns-2))}" if ((det_columns>=4)); then print -v proc_out -jc $((right_width/det_columns-1)) -t "${proc[detailed_user]::$((right_width/det_columns-2))}"; fi if ((det_columns>=5)); then print -v proc_out -jc $((right_width/det_columns-1)) -t "${proc[detailed_threads]}"; fi print -v proc_out -m $((d_line+4)) ${right_col} -fg ${theme[title]} -b -jr $((right_width/3+2)) -t "Memory: ${proc[detailed_mem]}%" -t " " if ((tty_width>120)); then print -v proc_out -rs -fg ${theme[inactive_fg]} -rp $((right_width/3-3)) "⡀" -l $((right_width/3-3)) -fg ${theme[proc_misc]} -t "${detail_mem_graph}" -t " "; fi print -v proc_out -fg ${theme[title]} -b -t "${proc[detailed_mem_string]}" fi #* Print processes if ((${#proc_array[@]}<=p_height)); then proc[start]=1 elif (( proc[start]>(${#proc_array[@]}-1)-p_height )); then proc[start]=$(( (${#proc_array[@]}-1)-p_height )) fi if ((proc[selected]>${#proc_array[@]}-1)); then proc[selected]=$((${#proc_array[@]}-1)); fi if [[ $proc_gradient == true ]] && ((proc[selected]>1)); then fg_r="$(( fg_r-( fg_add_r*(proc[selected]-1) ) ))" fg_g="$(( fg_g-( fg_add_g*(proc[selected]-1) ) ))" fg_b="$(( fg_b-( fg_add_b*(proc[selected]-1) ) ))" pid_r="$(( pid_r-( pid_add_r*(proc[selected]-1) ) ))" pid_g="$(( pid_g-( pid_add_g*(proc[selected]-1) ) ))" pid_b="$(( pid_b-( pid_add_b*(proc[selected]-1) ) ))" fi current_num=1 print -v proc_out -rs -m $((line+y++)) $((col+1)) -fg ${theme[title]} -b -t "${proc_array[0]::$((width-3))} " -rs local -a out_arr for out_line in "${proc_array[@]:${proc[start]}}"; do if [[ $use_psutil == true ]]; then out_arr=(${out_line}) pi=0 if [[ $proc_tree == true ]]; then while [[ ! ${out_arr[pi]} =~ ^[0-9]+$ ]]; do ((++pi)); done fi pid="${out_arr[pi]}" else pid="${out_line::$((proc[pid_len]+1))}"; pid="${pid// /}" out_line="${out_line//'\'/'\\'}" out_line="${out_line//'$'/'\$'}" out_line="${out_line//'"'/'\"'}" fi pid_graph="pid_${pid}_graph" if ((current_num==proc[selected])); then print -v proc_out -bg ${theme[selected_bg]} -fg ${theme[selected_fg]} -b; proc[selected_pid]="$pid" else print -v proc_out -rs -fg $((fg_r-fg_step_r)) $((fg_g-fg_step_g)) $((fg_b-fg_step_b)); fi print -v proc_out -m $((line+y)) $((col+1)) -t "${out_line::$((width-3))} " if ((current_num==proc[selected])); then print -v proc_out -rs -bg ${theme[selected_bg]}; fi print -v proc_out -m $((line+y)) $((col+width-12)) -fg ${theme[inactive_fg]} -t "⡀⡀⡀⡀⡀" if [[ -n ${!pid_graph} ]]; then print -v proc_out -m $((line+y)) $((col+width-12)) -fg $((pid_r-pid_step_r)) $((pid_g-pid_step_g)) $((pid_b-pid_step_b)) -t "${!pid_graph}" fi ((y++)) ((current_num++)) if ((y>height-2)); then break; fi if [[ $proc_gradient == false ]]; then : elif ((current_num=proc[selected])); then fg_step_r=$((fg_step_r+fg_add_r)); fg_step_g=$((fg_step_g+fg_add_g)); fg_step_b=$((fg_step_b+fg_add_b)) pid_step_r=$((pid_step_r+pid_add_r)); pid_step_g=$((pid_step_g+pid_add_g)); pid_step_b=$((pid_step_b+pid_add_b)) fi done print -v proc_out -rs while ((y<=height-2)); do print -v proc_out -m $((line+y++)) $((col+1)) -rp $((width-2)) -t " " done if ((proc[selected]>0)); then sel_string=$((proc[start]-1+proc[selected])); else sel_string=0; fi page_string="${sel_string}/$((${#proc_array[@]}-2${filter:++1}))" print -v proc_out -m $((line+height-1)) $((col+width-20)) -fg ${box[processes_color]} -rp 19 -t "─" print -v proc_out -m $((line+height-1)) $((col+width-${#page_string}-4)) -fg ${box[processes_color]} -t "┤" -b -fg ${theme[title]} -t "$page_string" -rs -fg ${box[processes_color]} -t "├" if ((proc[order_change]==1 | proc[filter_change]==1 | resized>0)); then unset proc_misc proc[order_change]=0 proc[filter_change]=0 proc[page_change]=1 print -v proc_misc -m $line $((col+13)) -fg ${box[processes_color]} -rp $((box[processes_width]-14)) -t "─" -rs if ((proc[detailed]==1)); then print -v proc_misc -m $((d_line+d_height)) $((d_col+detail_graph_width+2)) -fg ${box[processes_color]} -t "┴" -rs fi if ((tty_width>100)); then reverse_string="-fg ${box[processes_color]} -t ┤ -fg ${theme[hi_fg]}${proc[reverse]:+ -ul} -b -t r -fg ${theme[title]} -t everse -rs -fg ${box[processes_color]} -t ├" reverse_pos=9 fi print -v proc_misc -m $line $((col+width-${#proc_sorting}-14-reverse_pos)) -rs\ ${reverse_string}\ -fg ${box[processes_color]} -t ┤ -fg ${theme[title]}${proc[tree]:+ -ul} -b -t "tre" -fg ${theme[hi_fg]} -t "e" -rs -fg ${box[processes_color]} -t ├\ -fg ${box[processes_color]} -t "┤" -fg ${theme[hi_fg]} -b -t "‹" -fg ${theme[title]} -t " ${proc_sorting} " -fg ${theme[hi_fg]} -t "›" -rs -fg ${box[processes_color]} -t "├" if [[ -z $filter && -z $input_to_filter ]]; then print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg ${theme[hi_fg]} -b -t "f" -fg ${theme[title]} -t "ilter" -rs -fg ${box[processes_color]} -t "├" elif [[ -n $input_to_filter ]]; then if [[ ${#filter} -le $((width-35-reverse_pos)) ]]; then filter_string="${filter}" elif [[ ${#filter} -gt $((width-35-reverse_pos)) ]]; then filter_string="${filter: (-$((width-35-reverse_pos)))}" fi print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg ${theme[title]} -b -t "${filter_string}" -fg ${theme[proc_misc]} -bl -t "█" -rs -fg ${box[processes_color]} -t "├" elif [[ -n $filter ]]; then if [[ ${#filter} -le $((width-35-reverse_pos-4)) ]]; then filter_string="${filter}" elif [[ ${#filter} -gt $((width-35-reverse_pos-4)) ]]; then filter_string="${filter::$((width-35-reverse_pos-4))}" fi print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg ${theme[hi_fg]} -b -t "f" -fg ${theme[title]} -t " ${filter_string} " -fg ${theme[hi_fg]} -t "c" -rs -fg ${box[processes_color]} -t "├" fi proc_out+="${proc_misc}" fi if ((proc[page_change]==1 | resized>0)); then unset proc_misc2 proc[page_change]=0 if ((proc[selected]>0)); then kill_fg="${theme[hi_fg]}"; com_fg="${theme[title]}"; else kill_fg="${theme[inactive_fg]}"; com_fg="${theme[inactive_fg]}"; fi if ((proc[selected]==(${#proc_array[@]}-1${filter:++1})-proc[start])); then down_fg="${theme[inactive_fg]}"; else down_fg="${theme[hi_fg]}"; fi if ((proc[selected]>0 | proc[start]>1)); then up_fg="${theme[hi_fg]}"; else up_fg="${theme[inactive_fg]}"; fi print -v proc_misc2 -m $((line+height-1)) $((col+2)) -fg ${box[processes_color]} -t "┤" -fg $up_fg -b -t "↑" -fg ${theme[title]} -t " select " -fg $down_fg -t "↓" -rs -fg ${box[processes_color]} -t "├" print -v proc_misc2 -r 1 -fg ${box[processes_color]} -t "┤" -fg $com_fg -b -t "info " -fg $kill_fg "↲" -rs -fg ${box[processes_color]} -t "├" if ((tty_width>100)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "t" -fg $com_fg -t "erminate" -rs -fg ${box[processes_color]} -t "├"; fi if ((tty_width>111)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "k" -fg $com_fg -t "ill" -rs -fg ${box[processes_color]} -t "├"; fi if ((tty_width>126)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "i" -fg $com_fg -t "nterrupt" -rs -fg ${box[processes_color]} -t "├"; fi proc_out+="${proc_misc2}" fi proc_out="${detail_graph[*]}${proc_out}" if ((resized>0)); then ((resized++)); fi if [[ $argument == "now" ]]; then echo -en "${proc_out}" fi } draw_net() { #? Draw net information and graphs to screen local net_out argument=$1 if [[ -n ${net[no_device]} ]]; then return; fi if [[ -n $skip_net_draw && $argument != "now" ]]; then return; fi if [[ $argument == "now" ]]; then skip_net_draw=1; fi #* Get variables from previous calculations local col=$((box[net_col]+1)) line=$((box[net_line]+1)) width=$((box[net_width]-2)) height=$((box[net_height]-2)) local n_width=${box[n_width]} n_height=${box[n_height]} n_col=${box[n_col]} n_line=${box[n_line]} main_fg="${theme[main_fg]}" #* If resized recreate net meter box and net graphs if ((resized>0)); then local graph_a_size graph_b_size graph_a_size=$(( (height)/2 )); graph_b_size=${graph_a_size} if ((graph_a_size*20)); then create_graph -o download_graph -d $line $col ${net[graph_a_size]} $((width-n_width-2)) -c color_download_graph -n -max "${net[download_graph_max]}" net_history_download else create_graph -max "${net[download_graph_max]}" -add-last download_graph net_history_download fi if ((net[upload_redraw]==1 | net[nic_change]==1 | resized>0)); then create_graph -o upload_graph -d $((line+net[graph_a_size])) $col ${net[graph_b_size]} $((width-n_width-2)) -c color_upload_graph -i -n -max "${net[upload_graph_max]}" net_history_upload else create_graph -max "${net[upload_graph_max]}" -i -add-last upload_graph net_history_upload fi if ((net[nic_change]==1 | resized>0)); then local dev_len=${#net[device]} if ((dev_len>15)); then dev_len=15; fi unset net_misc 'net[nic_change]' print -v net_out -m $((line-1)) $((width-23)) -rs -fg ${box[net_color]} -rp 23 -t "─" print -v net_misc -m $((line-1)) $((width-7-dev_len)) -rs -fg ${box[net_color]} -t "┤" -fg ${theme[hi_fg]} -b -t "‹b " -fg ${theme[title]} -t "${net[device]::15}" -fg ${theme[hi_fg]} -t " n›" -rs -fg ${box[net_color]} -t "├" net_out+="${net_misc}" fi #* Create text depening on box height local ypos=$n_line print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▼ Byte:" -jr 12 -t "${net[speed_download_byteps]}" if ((height>4)); then print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▼ Bit:" -jr 12 -t "${net[speed_download_bitps]}"; fi if ((height>6)); then print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▼ Total:" -jr 12 -t "${net[total_download]}"; fi if ((height>8)); then ((ypos++)); fi print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▲ Byte:" -jr 12 -t "${net[speed_upload_byteps]}" if ((height>7)); then print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▲ Bit:" -jr 12 -t "${net[speed_upload_bitps]}"; fi if ((height>5)); then print -v net_out -fg ${main_fg} -m $((ypos++)) $n_col -jl 10 -t "▲ Total:" -jr 12 -t "${net[total_upload]}"; fi print -v net_out -fg ${theme[inactive_fg]} -m $line $col -t "${net[download_max_string]}" print -v net_out -fg ${theme[inactive_fg]} -m $((line+height-1)) $col -t "${net[upload_max_string]}" #* Print graphs and text to output variable draw_out+="${download_graph[*]}${upload_graph[*]}${net_out}" if [[ $argument == "now" ]]; then echo -en "${download_graph[*]}${upload_graph[*]}${net_out}"; fi } draw_clock() { #? Draw a clock at top of screen if [[ -z $draw_clock ]]; then return; fi if [[ $resized -gt 0 && $resized -lt 5 ]]; then unset clock_out; return; fi local width=${box[cpu_width]} color=${box[cpu_color]} old_time_string="${time_string}" #time_string="$(date ${draw_clock})" printf -v time_string "%(${draw_clock})T" if [[ $old_time_string != "$time_string" || -z $clock_out ]]; then unset clock_out print -v clock_out -m 1 $((width/2-${#time_string}/2)) -rs -fg ${color} -t "┤" -fg ${theme[title]} -b -t "${time_string}" -fg ${color} -t "├" fi if [[ $1 == "now" ]]; then echo -en "${clock_out}"; fi } draw_update_string() { unset update_string print -v update_string -m ${box[cpu_line]} $((box[cpu_col]+box[cpu_width]-${#update_ms}-14)) -rs -fg ${box[cpu_color]} -t "────┤" -fg ${theme[hi_fg]} -b -t "+" -fg ${theme[title]} -b -t " ${update_ms}ms " -fg ${theme[hi_fg]} -b -t "-" -rs -fg ${box[cpu_color]} -t "├" if [[ $1 == "quiet" ]]; then draw_out+="${update_string}" else echo -en "${update_string}"; fi } pause_() { #? Pause input and draw a darkened version of main ui local pause_out ext_var if [[ -n $1 && $1 != "off" ]]; then local -n pause_out=${1}; ext_var=1; fi if [[ $1 != "off" ]]; then prev_screen="${boxes_out}${proc_det}${last_screen}${net_misc}${mem_out}${detail_graph[*]}${proc_out}${proc_misc}${proc_misc2}${update_string}${clock_out}" if [[ -n $skip_process_draw ]]; then prev_screen+="${proc_out}" unset skip_process_draw proc_out fi unset pause_screen print -v pause_screen -rs -b -fg ${theme[inactive_fg]} pause_screen+="${theme[main_bg]}m$(${sed} -E 's/\\e\[[0-9;\-]*m//g' <<< "${prev_screen}")\e[0m" #\e[1;38;5;236 if [[ -z $ext_var ]]; then echo -en "${pause_screen}" else pause_out="${pause_screen}"; fi elif [[ $1 == "off" ]]; then echo -en "${prev_screen}" unset pause_screen prev_screen fi } unpause_() { #? Unpause pause_ off } menu_() { #? Shows the main menu overlay local menu i count keypress selected_int=0 selected up local_rez d_banner=1 menu_out bannerd skipped menu_pause out_out wait_string trans local -a menus=("options" "help" "quit") color unset bannerd menu_out until false; do #* Put program to sleep if caught ctrl-z if ((sleepy==1)); then sleep_; fi if [[ $background_update == true || -z $menu_out ]]; then draw_clock pause_ menu_pause else unset menu_pause fi unset draw_out if [[ -z ${bannerd} ]]; then draw_banner "$((tty_height/2-10))" bannerd unset d_banner fi if [[ -n ${keypress} || -z ${menu_out} ]]; then unset menu_out print -v menu_out -t "${bannerd}" print -v menu_out -d 1 -rs selected="${menus[selected_int]}" unset up if [[ -n ${theme[main_bg_dec]} ]] && ((${theme[main_bg_dec]// /*}>255**3/2)); then print -v menu_out -bg "#00"; unset trans; else trans=" -trans"; fi for menu in "${menus[@]}"; do if [[ $menu == "$selected" ]]; then local -n menu_array="menu_${menu}_selected" color=("#c55e5e" "#c23d3d" "#a13030" "#8c2626") else local -n menu_array="menu_${menu}" color=("#bb" "#aa" "#99" "#88") fi up=$((up+${#menu_array[@]})) for((i=0;i<${#menu_array[@]};i++)); do print -v menu_out -d 1 -fg ${color[i]} -c${trans} -t "${menu_array[i]}" done done print -v menu_out -rs -u ${up} fi unset out_out out_out="${menu_pause}${menu_out}" echo -e "${out_out}" get_ms timestamp_end time_left=$((timestamp_start+update_ms-timestamp_end)) if ((time_left>1000)); then wait_string=10; time_left=$((time_left-1000)) elif ((time_left>100)); then wait_string=$((time_left/100)); time_left=0 else wait_string="0"; time_left=0; fi get_key -v keypress -w ${wait_string} if [[ $(${stty} size) != "$tty_height $tty_width" ]]; then resized; fi if ((resized>0)); then calc_sizes; draw_bg quiet; time_left=0; unset menu_out unset bannerd echo -en "${clear_screen}" fi case "$keypress" in up|shift_tab) if ((selected_int>0)); then ((selected_int--)); else selected_int=$((${#menus[@]}-1)); fi ;; down|tab) if ((selected_int<${#menus[@]}-1)); then ((++selected_int)); else selected_int=0; fi ;; enter|space) case "$selected" in options) options_ ;; help) help_ ;; quit) quit_ ;; esac ;; m|M|escape|backspace) break ;; q|Q) quit_ ;; esac if ((time_left==0)) && [[ -z $keypress ]]; then get_ms timestamp_start; collect_and_draw; fi if ((resized>=5)); then resized=0; fi done unpause_ } help_() { #? Shows the help overlay local help_key from_menu col line y i help_out help_pause redraw=1 wait_string pages page=1 height local -a shortcuts descriptions shortcuts=( "(Esc, M, m)" "(F2, O, o)" "(F1, H, h)" "(Ctrl-C, Q, q)" "(+, A, a) (-, S, s)" "(Up) (Down)" "(Enter)" "(Pg Up) (Pg Down)" "(Home) (End)" "(Left) (Right)" "(b, B) (n, N)" "(E, e)" "(R, r)" "(F, f)" "(C, c)" "Selected (T, t)" "Selected (K, k)" "Selected (I, i)" " " " " " " ) descriptions=( "Shows main menu." "Shows options." "Shows this window." "Quits program." "Add/Subtract 100ms to/from update timer." "Select in process list." "Show detailed information for selected process." "Jump 1 page in process list." "Jump to first or last page in process list." "Select previous/next sorting column." "Select previous/next network device." "Toggle processes tree view" "Reverse sorting order in processes box." "Input a string to filter processes with." "Clear any entered filter." "Terminate selected process with SIGTERM - 15." "Kill selected process with SIGKILL - 9." "Interrupt selected process with SIGINT - 2." " " "For bug reporting and project updates, visit:" "\e[1mhttps://github.com/aristocratos/bashtop" ) if [[ -n $pause_screen ]]; then from_menu=1; fi until [[ -n $help_key ]]; do #* Put program to sleep if caught ctrl-z if ((sleepy==1)); then sleep_; redraw=1; fi if [[ $background_update == true || -n $redraw ]]; then draw_clock pause_ help_pause else unset help_pause fi if [[ -n $redraw ]]; then col=$((tty_width/2-36)); line=$((tty_height/2-4)); y=1; height=$((tty_height-2-line)) if ((${#shortcuts[@]}>height)); then pages=$(( (${#shortcuts[@]}/height)+1 )); else height=${#shortcuts[@]}; unset pages; fi unset redraw help_out draw_banner "$((tty_height/2-11))" help_out print -d 1 create_box -v help_out -w 72 -h $((height+3)) -l $((line++)) -c $((col++)) -fill -lc ${theme[div_line]} -title "help" if [[ -n $pages ]]; then print -v help_out -m $((line+height+1)) $((col+72-16)) -rs -fg ${theme[div_line]} -t "┤" -fg ${theme[title]} -b -t "pg" -fg ${theme[hi_fg]} -t "↑"\ -fg ${theme[title]} -t " ${page}/${pages} " -fg ${theme[title]} -t "pg" -fg ${theme[hi_fg]} -t "↓" -rs -fg ${theme[div_line]} -t "├" fi ((++col)) print -v help_out -m $line $col -fg ${theme[title]} -b -jl 20 -t "Key:" -jl 48 -t "Description:" -m $((line+y++)) $col for((i=(page-1)*height;i1000)); then wait_string=10; time_left=$((time_left-1000)) elif ((time_left>100)); then wait_string=$((time_left/100)); time_left=0 else wait_string="0"; time_left=0; fi get_key -v help_key -w "${wait_string}" if [[ -n $pages ]]; then case $help_key in down|page_down) if ((page1)); then ((page--)); else page=${pages}; fi; redraw=1; unset help_key ;; esac fi if [[ $(${stty} size) != "$tty_height $tty_width" ]]; then resized; fi if ((resized>0)); then ${sleep} 0.5 calc_sizes; draw_bg quiet; redraw=1 d_banner=1 unset bannerd menu_out fi if ((time_left==0)); then get_ms timestamp_start; collect_and_draw; fi if ((resized>0)); then resized=0; fi done if [[ -n $from_menu ]]; then pause_ else unpause_; fi } options_() { #? Shows the options overlay local keypress from_menu col line y=1 i=1 options_out selected_int=0 ypos option_string options_misc option_value bg fg skipped start_t end_t left_t changed_cpu_name theme_int=0 page=1 pages height local desc_col right left enter lr inp valid updated_ms local_rez redraw_misc=1 desc_pos desc_height options_pause updated_proc inputting inputting_value inputting_key file theme_check net_totals_reset if ((net[reset]==1)); then net_totals_reset="On"; else net_totals_reset="Off"; fi #* Check theme folder for theme files get_themes desc_color_theme=( "Set bashtop color theme." " " "Choose between theme files located in" "\"\$HOME/.config/bashtop/themes\" &" "\"\$HOME/.config/bashtop/user_themes" " " "User themes are prefixed with \"*\"." "\"Default\" for builtin default." " ") if [[ -z $curled ]]; then desc_color_theme+=("Get more themes at:" "https://github.com/aristocratos/bashtop") else desc_color_theme+=("\e[1mPress ENTER to download the default themes." "Will overwrite changes made to the default" "themes if not copied to user_themes folder."); fi desc_update_ms=( "Update time in milliseconds." "Recommended 2000 ms or above for better sample" "times for graphs." " " "Increases automatically if set below internal" "loops processing time." " " "Max value: 86400000 ms = 24 hours.") desc_use_psutil=( "Enable the use of psutil python3 module for" "data collection. Default on non Linux." "" "Program will automatically restart if changing" "this setting to check for compatibility." " " "True or false." " " "Can only be switched off when on Linux.") desc_proc_sorting=( "Processes sorting." "Valid values are \"pid\", \"program\", \"arguments\"," "\"threads\", \"user\", \"memory\", \"cpu lazy\"" "\"cpu responsive\" and \"tree\"." " " "\"cpu lazy\" shows cpu usage over the lifetime" "of a process." " " "\"cpu responsive\" updates sorting directly at a" "cost of cpu time (unless using psutil)." " " "\"tree\" shows a tree structure of running" "processes. (not available with psutil)") desc_proc_tree=( "Processes tree view." " " "Set true to show processes grouped by parents," "with lines drawn between parent and child" "process." " " "True or false.") desc_check_temp=( "Check cpu temperature." " " "True or false." " " "Only works if sensors, vcgencmd or osx-cpu-temp" "commands is available.") desc_draw_clock=( "Draw a clock at top of screen." " " "Formatting according to strftime, empty" "string to disable." " " "\"%X\" locale HH:MM:SS" "\"%H\" 24h hour, \"%I\" 12h hour" "\"%M\" minute, \"%S\" second" "\"%d\" day, \"%m\" month, \"%y\" year") desc_background_update=( "Update main ui when menus are showing." " " "True or false." " " "Set this to false if the menus is flickering" "too much for a comfortable experience.") desc_custom_cpu_name=( "Custom cpu model name in cpu percentage box." " " "Empty string to disable.") desc_error_logging=("Enable error logging to" "\"\$HOME/.config/bashtop/error.log\"" " " "Program will be automatically restarted if" "changing this option." " " "True or false.") desc_proc_reversed=("Reverse sorting order." " " "True or false.") desc_proc_gradient=("Show color gradient in process list." " " "True or False.") desc_disks_filter=("Optional filter for shown disks." " " "Should be names of mountpoints." "\"root\" replaces \"/\"" " " "Separate multiple values with space." "Example: \"root home external\"") desc_net_totals_reset=("Press ENTER to toggle network upload" "and download totals reset." " " "Shows totals since system start or" "network adapter reset when Off.") desc_proc_per_core=("Process usage per core." " " "If process cpu usage should be of the core" "it's running on or usage of the total" "available cpu power." "" "If true and process is multithreaded" "cpu usage can reach over 100%.") desc_update_check=( "Check for updates." " " "Enable check for new version from" "github.com/aristocratos/bashtop at start." " " "True or False.") desc_hires_graphs=("Enable high resolution graphs." " " "Doubles the horizontal resolution of all" "graphs. At a cpu usage cost." "Needs restart to take effect." " " "True or False.") if [[ -n $pause_screen ]]; then from_menu=1; fi until false; do #* Put program to sleep if caught ctrl-z if ((sleepy==1)); then sleep_; fi if [[ $background_update == true || -n $redraw_misc ]]; then draw_clock if [[ -z $inputting ]]; then pause_ options_pause; fi else unset options_pause fi if [[ -n $redraw_misc ]]; then unset options_misc redraw_misc col=$((tty_width/2-39)) line=$((tty_height/2-4)) height=$(( (tty_height-2-line)/2 )) if ((${#options_array[@]}>height)); then pages=$(( (${#options_array[@]}/height)+1 )); else height=${#options_array[@]}; unset pages; fi desc_col=$((col+30)) draw_banner "$((tty_height/2-11))" options_misc create_box -v options_misc -w 29 -h $((height*2+2)) -l $line -c $((col-1)) -fill -lc ${theme[div_line]} -title "options" if [[ -n $pages ]]; then print -v options_misc -m $((line+height*2+1)) $((col+29-16)) -rs -fg ${theme[div_line]} -t "┤" -fg ${theme[title]} -b -t "pg" -fg ${theme[hi_fg]} -t "↑"\ -fg ${theme[title]} -t " ${page}/${pages} " -fg ${theme[title]} -t "pg" -fg ${theme[hi_fg]} -t "↓" -rs -fg ${theme[div_line]} -t "├" fi fi if [[ -n $keypress || -z $options_out ]]; then unset options_out desc_height lr inp valid selected="${options_array[selected_int]}" local -n selected_desc="desc_${selected}" if [[ $background_update == false ]]; then desc_pos=$line; desc_height=$((height*2+2)) elif (( (selected_int-( (page-1)*height) )*2+${#selected_desc[@]}/dev/null)) if [[ ${theme_index[*]} =~ .theme ]]; then for git_theme in ${theme_index[@]}; do unset new_theme if [[ ! -e "${config_dir}/themes/${git_theme}" ]]; then new_theme=1; fi if curl -m 3 --raw "https://raw.githubusercontent.com/aristocratos/bashtop/master/themes/${git_theme}" >"${config_dir}/themes/${git_theme}" 2>/dev/null; then ((++down_themes)) if [[ -n $new_theme ]]; then ((++new_themes)) themes+=("themes/${git_theme%.theme}") fi fi done desc_color_theme+=("Downloaded ${down_themes} theme(s).") desc_color_theme+=("Found ${new_themes} new theme(s)!") else desc_color_theme+=("ERROR: Couldn't get theme index!") fi fi get_ms timestamp_end if [[ -z $theme_check ]]; then time_left=$((timestamp_start+update_ms-timestamp_end)) else unset theme_check; time_left=0; fi if ((time_left>500)); then wait_string=5; time_left=$((time_left-500)) elif ((time_left>100)); then wait_string=$((time_left/100)); time_left=0 else wait_string="0"; time_left=0; fi get_key -v keypress -w ${wait_string} if [[ -n $inputting ]]; then case "$keypress" in escape) unset inputting inputting_value ;; enter|backspace) valid=1 ;; *) if [[ ${#keypress} -eq 1 ]]; then valid=1; fi ;; esac else case "$keypress" in escape|q|backspace) break 1 ;; down|tab) if ((selected_int<${#options_array[@]}-1)); then ((++selected_int)); else selected_int=0; fi ;; up|shift_tab) if ((selected_int>0)); then ((selected_int--)); else selected_int=$((${#options_array[@]}-1)); fi ;; left|right) if [[ -n $lr && -z $inputting ]]; then valid=1; fi ;; enter) if [[ -n $inp ]]; then valid=1; fi ;; page_down) if ((page1)); then ((page--)); else page=${pages}; fi; redraw_misc=1; selected_int=$(( (page-1)*height )) ;; esac if (( selected_int<(page-1)*height | selected_int>=page*height )); then page=$(( (selected_int/height)+1 )); redraw_misc=1; fi fi if [[ ${selected} == "color_theme" && ${keypress} =~ left|right && ${#themes} -lt 2 ]]; then unset valid; fi if [[ -n $valid ]]; then case "${selected} ${keypress}" in "update_ms right") if ((update_ms<86399900)); then update_ms=$((update_ms+100)) updated_ms=1 fi ;; "update_ms left") if ((update_ms>100)); then update_ms=$((update_ms-100)) updated_ms=1 fi ;; "update_ms enter") if [[ -z $inputting ]]; then inputting=1; inputting_value="${update_ms}" else if ((inputting_value<86400000)); then update_ms="${inputting_value:-0}"; updated_ms=1; fi unset inputting inputting_value fi ;; "update_ms backspace"|"draw_clock backspace"|"custom_cpu_name backspace"|"disks_filter backspace") if [[ ${#inputting_value} -gt 0 ]]; then inputting_value="${inputting_value::-1}" fi ;; "update_ms"*) inputting_value+="${keypress//[^0-9]/}" ;; "draw_clock enter") if [[ -z $inputting ]]; then inputting=1; inputting_value="${draw_clock}" else draw_clock="${inputting_value}"; unset inputting inputting_value clock_out; fi ;; "custom_cpu_name enter") if [[ -z $inputting ]]; then inputting=1; inputting_value="${custom_cpu_name}" else custom_cpu_name="${inputting_value}"; changed_cpu_name=1; unset inputting inputting_value; fi ;; "disks_filter enter") if [[ -z $inputting ]]; then inputting=1; inputting_value="${disks_filter}" else disks_filter="${inputting_value}"; mem[counter]=10; resized=1; unset inputting inputting_value; fi ;; "net_totals_reset enter") if ((net[reset]==1)); then net_totals_reset="Off"; net[reset]=0 else net_totals_reset="On"; net[reset]=1; fi ;; "check_temp"*|"error_logging"*|"background_update"*|"proc_reversed"*|"proc_gradient"*|"proc_per_core"*|"update_check"*|"hires_graphs"*|"use_psutil"*|"proc_tree"*) local -n selected_var=${selected} if [[ ${selected_var} == "true" ]]; then selected_var="false" if [[ $selected == "proc_reversed" ]]; then proc[order_change]=1; unset 'proc[reverse]' elif [[ $selected == "proc_tree" ]]; then proc[order_change]=1; unset 'proc[tree]'; fi else selected_var="true" if [[ $selected == "proc_reversed" ]]; then proc[order_change]=1; proc[reverse]="+" elif [[ $selected == "proc_tree" ]]; then proc[order_change]=1; proc[tree]="+"; fi fi if [[ $selected == "check_temp" && $check_temp == true ]]; then local has_temp sensor_comm="" if [[ $use_psutil == true ]]; then py_command -v has_temp "get_sensors_check()" if [[ $has_temp == true ]]; then sensor_comm="psutil"; fi fi if [[ -z $sensor_comm ]]; then local checker for checker in "vcgencmd" "sensors" "osx-cpu-temp"; do if command -v "${checker}" >/dev/null 2>&1; then sensor_comm="${checker}"; break; fi done fi if [[ -z $sensor_comm ]]; then check_temp="false" else resized=1; fi elif [[ $selected == "check_temp" ]]; then resized=1 fi if [[ $selected == "use_psutil" && $system != "Linux" ]]; then use_psutil="true" elif [[ $selected == "use_psutil" ]]; then quit_ restart psutil; fi if [[ $selected == "error_logging" ]]; then quit_ restart; fi ;; "proc_sorting right") if ((proc[sorting_int]<${#sorting[@]}-1)); then ((++proc[sorting_int])) else proc[sorting_int]=0; fi proc_sorting="${sorting[proc[sorting_int]]}" proc[order_change]=1 ;; "proc_sorting left") if ((proc[sorting_int]>0)); then ((proc[sorting_int]--)) else proc[sorting_int]=$((${#sorting[@]}-1)); fi proc_sorting="${sorting[proc[sorting_int]]}" proc[order_change]=1 ;; "color_theme right") if ((theme_int<${#themes[@]}-1)); then ((++theme_int)) else theme_int=0; fi color_theme="${themes[$theme_int]}" color_init_ resized=1 ;; "color_theme left") if ((theme_int>0)); then ((theme_int--)) else theme_int=$((${#themes[@]}-1)); fi color_theme="${themes[$theme_int]}" color_init_ resized=1 ;; "color_theme enter") theme_check=1 if ((${#desc_color_theme[@]}>8)); then unset 'desc_color_theme[-1]'; fi desc_color_theme+=("Checking for new themes...") ;; "draw_clock"*|"custom_cpu_name"*|"disks_filter"*) inputting_value+="${keypress//[\\\$\"\']/}" ;; esac fi if [[ -n $changed_cpu_name ]]; then changed_cpu_name=0 get_cpu_info calc_sizes draw_bg quiet fi if [[ $(${stty} size) != "$tty_height $tty_width" ]]; then resized; fi if ((resized>0)); then calc_sizes; draw_bg quiet redraw_misc=1 unset options_out bannerd menu_out fi get_ms timestamp_end time_left=$((timestamp_start+update_ms-timestamp_end)) if ((time_left<=0 | resized>0)); then get_ms timestamp_start; if [[ -z $inputting ]]; then collect_and_draw; fi; fi if ((resized>0)); then resized=0; page=1; selected_int=0; fi if [[ -n $updated_ms ]] && ((updated_ms++==2)); then unset updated_ms draw_update_string quiet fi done if [[ -n $from_menu ]]; then pause_ elif [[ -n ${pause_screen} ]]; then unpause_; draw_update_string; fi } killer_() { #? Kill process with selected signal local kill_op="$1" kill_pid="$2" killer_out killer_box col line program keypress selected selected_int=0 sig confirmed=0 option killer_pause status msg local -a options=("yes" "no") if ! program="$(ps -o comm -p ${kill_pid})"; then return else program="$(tail -n1 <<<"$program")"; fi case $kill_op in t|T) kill_op="terminate"; sig="SIGTERM" ;; k|K) kill_op="kill"; sig="SIGKILL" ;; i|I) kill_op="interrupt"; sig="SIGINT" ;; esac until false; do #* Put program to sleep if caught ctrl-z if ((sleepy==1)); then sleep_; fi if [[ $background_update == true || -z $killer_box ]]; then draw_clock pause_ killer_pause else unset killer_pause fi if [[ -z $killer_box ]]; then col=$((tty_width/2-15)); line=$((tty_height/2-4)); y=1 unset redraw killer_box create_box -v killer_box -w 40 -h 9 -l $line -c $((col++)) -fill -lc "${theme[proc_box]}" -title "${kill_op}" fi if ((confirmed==0)); then selected="${options[selected_int]}" print -v killer_out -m $((line+2)) $col -fg ${theme[title]} -b -jc 38 -t "${kill_op^} ${program::20}?" -m $((line+4)) $((col+3)) for option in "${options[@]}"; do if [[ $option == "${selected}" ]]; then print -v killer_out -bg ${theme[selected_bg]} -fg ${theme[selected_fg]}; else print -v killer_out -fg ${theme[title]}; fi print -v killer_out -b -r 5 -t "[ ${option^} ]" -rs done elif ((confirmed==1)); then selected="ok" print -v killer_out -m $((line+2)) $col -fg ${theme[title]} -b -jc 38 -t "Sending signal ${sig} to pid ${kill_pid}!" print -v killer_out -m $((line+4)) $col -fg ${theme[main_fg]} -jc 38 -t "${status^}!" -m $((line+6)) $col if [[ -n $msg ]]; then print -v killer_out -m $((line+5)) $col -fg ${theme[main_fg]} -jc 38 -t "${msg}" -m $((line+7)) $col; fi print -v killer_out -fg ${theme[selected_fg]} -bg ${theme[selected_bg]} -b -r 15 -t "[ Ok ]" -rs fi echo -en "${killer_pause}${killer_box}${killer_out}" unset killer_out draw_out get_ms timestamp_end time_left=$((timestamp_start+update_ms-timestamp_end)) if ((time_left>1000)); then wait_string=10; time_left=$((time_left-1000)) elif ((time_left>100)); then wait_string=$((time_left/100)); time_left=0 else wait_string="0"; time_left=0; fi get_key -v keypress -w ${wait_string} if [[ $(${stty} size) != "$tty_height $tty_width" ]]; then resized; fi if ((resized>0)); then calc_sizes; draw_bg quiet; time_left=0; unset killer_out killer_box fi case "$keypress" in right|shift_tab) if ((selected_int>0)); then ((selected_int--)); else selected_int=$((${#options[@]}-1)); fi ;; left|tab) if ((selected_int<${#options[@]}-1)); then ((++selected_int)); else selected_int=0; fi ;; enter) case "$selected" in yes) confirmed=1 ;; no|ok) confirmed=-1 ;; esac ;; q|Q) quit_ ;; esac if ((confirmed<0)); then unpause_ break elif ((confirmed>0)) && [[ -z $status ]]; then if ${kill} -${sig} ${kill_pid} >/dev/null 2>&1; then status="success" else if ! ps -p ${kill_pid} >/dev/null 2>&1; then msg="Process not running." elif [[ $UID != 0 ]]; then msg="Try restarting with sudo." else msg="Unknown error." fi status="failed"; fi fi if ((time_left==0)); then get_ms timestamp_start; unset draw_out; collect_and_draw; fi if ((resized>=5)); then resized=0; fi done } get_key() { #? Get one key from standard input and translate key code to readable format local key key_out wait_time esc ext_out save if ((quitting==1)); then quit_; fi until (($#==0)); do case "$1" in -v|-variable) local -n key_out=$2; ext_out=1; shift;; #* Output variable -w|-wait) wait_time="$2"; shift;; #* Time to wait for key -s|-save) save=1;; #* Save key for later processing esac shift done if [[ -z $save && -n ${saved_key[0]} ]]; then key="${saved_key[0]}"; unset 'saved_key[0]'; saved_key=("${saved_key[@]}") else unset key key=$(${stty} -cooked min 0 time ${wait_time:-0} 2>/dev/null; ${dd} bs=1 count=1 2>/dev/null) if [[ -z ${key:+s} ]]; then key_out="" ${stty} isig if [[ -z $save ]]; then return 0 else return 1; fi fi #* Read 3 more characters if a leading escape character is detected if [[ $key == "${enter_key}" ]]; then key="enter" elif [[ $key == "${ctrl_c}" ]]; then quitting=1; time_left=0 elif [[ $key == "${ctrl_z}" ]]; then sleepy=1; time_left=0 elif [[ $key == "${backspace}" || $key == "${backspace_real}" ]]; then key="backspace" elif [[ $key == "${tab}" ]]; then key="tab" elif [[ $key == "$esc_character" ]]; then esc=1; key=$(${stty} -cooked min 0 time 0 2>/dev/null; ${dd} bs=1 count=3 2>/dev/null); fi if [[ -z $key && $esc -eq 1 ]]; then key="escape" elif [[ $esc -eq 1 ]]; then case "${key}" in '[A'*|'OA'*) key="up" ;; '[B'*|'OB'*) key="down" ;; '[D'*|'OD'*) key="left" ;; '[C'*|'OC'*) key="right" ;; '[2~') key="insert" ;; '[3~') key="delete" ;; '[H'*) key="home" ;; '[F'*) key="end" ;; '[5~') key="page_up" ;; '[6~') key="page_down" ;; '[Z'*) key="shift_tab" ;; 'OP'*) key="f1";; 'OQ'*) key="f2";; 'OR'*) key="f3";; 'OS'*) key="f4";; '[15') key="f5";; '[17') key="f6";; '[18') key="f7";; '[19') key="f8";; '[20') key="f9";; '[21') key="f10";; '[23') key="f11";; '[24') key="f12";; *) key="" ;; esac fi fi ${stty} -cooked min 0 time 0 >/dev/null 2>&1; ${dd} bs=512 count=1 >/dev/null 2>&1 ${stty} isig if [[ -n $save && -n $key ]]; then saved_key+=("${key}"); return 0; fi if [[ -n $ext_out ]]; then key_out="${key}" else echo -n "${key}"; fi } process_input() { #? Process keypresses for main ui local wait_time="$1" keypress esc prev_screen anykey filter_change p_height=$((box[processes_height]-3)) late_update=0 #* Wait while reading input get_key -v keypress -w "${wait_time}" if [[ -z $keypress ]] || [[ -n $failed_pipe ]]; then return; fi if [[ -n $input_to_filter ]]; then filter_change=1 case "$keypress" in "enter") unset input_to_filter ;; "backspace") if [[ ${#filter} -gt 0 ]]; then filter="${filter:: (-1)}"; else unset filter_change; fi ;; "escape") unset input_to_filter filter ;; *) if [[ ${#keypress} -eq 1 && $keypress =~ ^[A-Za-z0-9\!\@\#\%\&\/\(\)\[\+\-\_\*\,\;\.\:]$ ]]; then filter+="${keypress//[\\\$\"\']/}"; else unset filter_change; fi ;; esac else case "$keypress" in left) #* Move left in processes sorting column if ((proc[sorting_int]>0)); then ((proc[sorting_int]--)) else proc[sorting_int]=$((${#sorting[@]}-1)); fi proc_sorting="${sorting[proc[sorting_int]]}" if [[ $proc_sorting == "tree" && $use_psutil == true ]]; then ((proc[sorting_int]--)) proc_sorting="${sorting[proc[sorting_int]]}" fi filter_change=1 ;; right) #* Move right in processes sorting column if ((proc[sorting_int]<${#sorting[@]}-1)); then ((++proc[sorting_int])) else proc[sorting_int]=0; fi proc_sorting="${sorting[proc[sorting_int]]}" if [[ $proc_sorting == "tree" && $use_psutil == true ]]; then proc[sorting_int]=0 proc_sorting="${sorting[proc[sorting_int]]}" fi filter_change=1 ;; n|N) #* Switch to next network device if ((${#nic_list[@]}>1)); then if ((nic_int<${#nic_list[@]}-1)); then ((++nic_int)) else nic_int=0; fi net[device]="${nic_list[nic_int]}" net[nic_change]=1 collect_net init collect_net draw_net now fi ;; b|B) #* Switch to previous network device if ((${#nic_list[@]}>1)); then if ((nic_int>0)); then ((nic_int--)) else nic_int=$((${#nic_list[@]}-1)); fi net[device]="${nic_list[nic_int]}" net[nic_change]=1 collect_net init collect_net draw_net now fi ;; up|shift_tab) #* Move process selector up one if ((proc[selected]>1)); then ((proc[selected]--)) proc[page_change]=1 elif ((proc[start]>1)); then if ((proc[selected]==0)); then proc[selected]=${p_height}; fi ((proc[start]--)) proc[page_change]=1 elif ((proc[start]==1 & proc[selected]==1)); then proc[selected]=0 proc[page_change]=1 fi ;; down|tab) #* Move process selector down one if ((proc[selected]0 & proc[detailed_pid]!=proc[selected_pid])) && ps -p ${proc[selected_pid]} > /dev/null 2>&1; then proc[detailed]=1 proc[detailed_change]=1 proc[detailed_pid]=${proc[selected_pid]} proc[selected]=0 unset 'proc[detailed_name]' 'detail_history[@]' 'detail_mem_history[@]' 'proc[detailed_killed]' calc_sizes collect_processes now elif ((proc[detailed]==1 & proc[detailed_pid]!=proc[selected_pid])); then proc[detailed]=0 proc[detailed_change]=1 unset 'proc[detailed_pid]' calc_sizes fi ;; page_up) #* Move up one page in process box if ((proc[start]>1)); then proc[start]=$(( proc[start]-p_height )) if ((proc[start]<1)); then proc[start]=1; fi proc[page_change]=1 elif ((proc[selected]>0)); then proc[selected]=0 proc[start]=1 proc[page_change]=1 fi ;; page_down) #* Move down one page in process box if ((proc[start]<(${#proc_array[@]}-1)-p_height)); then if ((proc[start]==1)) && [[ $use_psutil == false ]]; then collect_processes now; fi proc[start]=$(( proc[start]+p_height )) if (( proc[start]>(${#proc_array[@]})-p_height )); then proc[start]=$(( (${#proc_array[@]})-p_height )); fi proc[page_change]=1 elif ((proc[selected]>0)); then proc[selected]=$((p_height)) proc[page_change]=1 fi ;; home) #* Go to first page in process box proc[start]=1 proc[page_change]=1 ;; end) #* Go to last page in process box if ((proc[selected]==0)) && [[ $use_psutil == false ]]; then collect_processes now; fi proc[start]=$(((${#proc_array[@]}-1)-p_height)) proc[page_change]=1 ;; r|R) #* Reverse order of processes sorting column if [[ -z ${proc[reverse]} ]]; then proc[reverse]="+" proc_reversed="true" else proc_reversed="false" unset 'proc[reverse]' fi filter_change=1 ;; e|E) #* Show processes as a tree if [[ -z ${proc[tree]} ]]; then proc[tree]="+" proc_tree="true" else proc_tree="false" unset 'proc[tree]' fi filter_change=1 ;; o|O|f2) #* Options options_ ;; +|A|a) #* Add 100ms to update timer if ((update_ms<86399900)); then update_ms=$((update_ms+100)) draw_update_string fi ;; -|S|s) #* Subtract 100ms from update timer if ((update_ms>100)); then update_ms=$((update_ms-100)) draw_update_string fi ;; h|H|f1) #* Show help help_ ;; q|Q) #* Quit quit_ ;; m|M|escape) #* Show main menu menu_ ;; f|F) #* Start process filtering input input_to_filter=1 filter_change=1 if ((proc[selected]>1)); then proc[selected]=1; fi proc[start]=1 ;; c|C) #* Clear process filter if [[ -n $filter ]]; then unset input_to_filter filter filter_change=1 fi ;; t|T|k|K|i|I) #* Send terminate, kill or interrupt signal if [[ ${proc[selected]} -gt 0 ]]; then killer_ "$keypress" "${proc[selected_pid]}" elif [[ ${proc[detailed]} -eq 1 && -z ${proc[detailed_killed]} ]]; then killer_ "$keypress" "${proc[detailed_pid]}" fi ;; esac fi if [[ -n $filter_change ]]; then unset filter_change collect_processes now proc[filter_change]=1 draw_processes now elif [[ ${proc[page_change]} -eq 1 || ${proc[detailed_change]} == 1 ]]; then if ((proc[selected]==0)); then unset 'proc[selected_pid]'; proc[detailed_change]=1; fi draw_processes now fi #* Subtract time since input start from time left if timer is interrupted get_ms timestamp_input_end time_left=$(( (timestamp_start+update_ms)-timestamp_input_end )) return 0 } collect_and_draw() { #? Run all collect and draw functions local task_int=0 input_runs for task in processes cpu mem net; do ((++task_int)) if [[ -n $pause_screen && -n ${saved_key[0]} ]]; then return elif [[ -z $pause_screen ]]; then input_runs=0 while [[ -n ${saved_key[0]} ]] && ((time_left>0)) && ((++input_runs<=5)); do process_input unset late_update done fi collect_${task} if get_key -save && [[ -z $pause_screen ]]; then process_input; fi draw_${task} if get_key -save && [[ -z $pause_screen ]]; then process_input; fi draw_clock "$1" if ((resized>0 & resized0)); then calc_sizes draw_bg fi #* Run all collect and draw functions collect_and_draw now #* Reset resized variable if resized and all functions have finished redrawing if ((resized>=5)); then resized=0 elif ((resized>0)); then unset draw_out proc_out clock_out; return; fi #* Echo everyting out to screen in one command to get a smooth transition between updates echo -en "${draw_out}${proc_out}${clock_out}" unset draw_out #* Periodically check for new network device if non was found at start or was removed if ((net[device_check]>10)); then net[device_check]=0 get_net_device elif [[ -n ${net[no_device]} ]]; then ((++net[device_check])) fi #* Compare timestamps to get exact time needed to wait until next loop get_ms timestamp_end time_left=$((timestamp_start+update_ms-timestamp_end)) if ((time_left>update_ms)); then time_left=$update_ms; fi if ((time_left>0)); then late_update=0 #* Divide waiting time in chunks of 500ms and below to keep program responsive while reading input while ((time_left>0 & resized==0)); do #* If NOT waiting for input and time left is greater than 500ms, wait 500ms and loop if [[ -z $input_to_filter ]] && ((time_left>=500)); then wait_string="5" time_left=$((time_left-500)) #* If waiting for input and time left is greater than "50 ms", wait 50ms and loop elif [[ -n $input_to_filter ]] && ((time_left>=100)); then wait_string="1" time_left=$((time_left-100)) #* Else format wait string with padded zeroes if needed and break loop else if ((time_left>=100)); then wait_string=$((time_left/100)); else wait_string=0; fi time_left=0 fi #* Wait while reading input process_input "${wait_string}" if [[ -n $failed_pipe || -n $py_error ]]; then return; fi #* Draw clock if set draw_clock now done #* If time left is too low to process any input more than five times in succession, add 100ms to update timer elif ((++late_update==5)); then update_ms=$((update_ms+100)) draw_update_string fi unset skip_process_draw skip_net_draw } #? Pre main loop #* Read config file or create if non existant config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/bashtop" if [[ -d "${config_dir}" && -w "${config_dir}" ]] || mkdir -p "${config_dir}"; then if [[ ! -d "${config_dir}/themes" ]]; then mkdir -p "${config_dir}/themes"; fi if [[ ! -d "${config_dir}/user_themes" ]]; then mkdir -p "${config_dir}/user_themes"; fi config_file="${config_dir}/bashtop.cfg" # shellcheck source=/dev/null if [[ -e "$config_file" ]]; then source "$config_file" #* If current config is from an older version recreate config file and save user changes if [[ $(get_value -sf "${config_file}" -k "bashtop v." -mk 1) != "${version}" ]]; then create_config save_config "${save_array[@]}" fi else create_config; fi else #* If anything goes wrong turn off all writing to filesystem echo "ERROR: Could not set config dir!" config_dir="/dev/null" config_file="/dev/null" error_logging="false" unset 'save_array[@]' fi #* Force the use of python psutil if not on Linux if [[ $system != "Linux" ]]; then use_psutil="true"; fi #* Check for python3 and psutil if "use_psutil" is true if [[ $use_psutil == true ]]; then if ! command -v python3 >/dev/null 2>&1; then echo "Error: Missing python3!" if [[ $system == "Linux" ]]; then use_psutil="false" else exit 1 fi elif [[ $use_psutil == true ]] && ! (cd / && python3 -c "import psutil") >/dev/null 2>&1; then echo "Error: Missing python3 psutil module!" if [[ $system == "Linux" ]]; then use_psutil="false" else exit 1 fi fi fi #* If using bash version 5, set timestamps with EPOCHREALTIME variable if [[ -n $EPOCHREALTIME ]]; then get_ms() { #? Set given variable to current epoch millisecond with EPOCHREALTIME varialble local -n ms_out=$1 ms_out=$((${EPOCHREALTIME/[.,]/}/1000)) } #* If not, but using psutil, set timestamps with python elif [[ $use_psutil == true ]]; then get_ms() { local -n ms_out=$1 py_command -v ms_out "get_ms()" } #* Else use date command else get_ms() { #? Set given variable to current epoch millisecond with date command local -n ms_out=$1 ms_out="" read ms_out < <(${date} +%s%3N) } fi #* Setup psutil script if [[ $use_psutil == true ]]; then py_command() { if [[ -n $failed_pipe ]]; then return; fi local arr var output cmd pyerr=${py_error} ln case $1 in "quit") echo "quit" >&${pycoproc[1]} 2>/dev/null || true return ;; "-v") var=1;; "-vn") var=1; ln=1;; "-a") arr=1;; *) return;; esac local -n pyout=$2 cmd="$3" echo "${cmd}" >&${pycoproc[1]} #2>/dev/null || true if [[ -n $var ]]; then pyout="" else pyout=(); fi while IFS= read -r -u ${pycoproc[0]} -t 1 output; do #2>/dev/null if [[ $output == '/EOL' ]]; then break; fi if [[ -n $failed_pipe ]]; then py_out=""; return 1; fi if [[ $output == '/ERROR' ]]; then ((++py_error)); unset arr var; fi if [[ -n $arr ]]; then pyout+=("${output}") elif [[ -n $var ]]; then pyout+="${output}${ln:+\n}"; fi done if ((py_error>pyerr)); then py_out=""; return 1; fi if [[ -n $ln ]]; then printf -v pyout "%b" "${pyout}"; fi return 0 } if ! pytmpdir=$(mktemp -d "${TMPDIR:-/tmp}"/XXXXXXXXXXXX); then if [[ $system == "Linux" ]]; then use_psutil="false" else echo "ERROR: Failed setting up temp directory for psutil script!" exit 1 fi else pywrapper="${pytmpdir}/bashtop.psutil" cat << 'EOF' > "${pywrapper}" import os, sys, subprocess, re, time, psutil from datetime import timedelta from collections import defaultdict from typing import List, Set, Dict, Tuple, Optional, Union system: str if "linux" in sys.platform: system = "Linux" elif "bsd" in sys.platform: system = "BSD" elif "darwin" in sys.platform: system = "MacOS" else: system = "Other" parent_pid: int = psutil.Process(os.getpid()).ppid() allowed_commands: Tuple[str] = ( 'get_proc', 'get_disks', 'get_cpu_name', 'get_cpu_cores', 'get_nics', 'get_cpu_cores', 'get_cpu_usage', 'get_cpu_freq', 'get_uptime', 'get_load_avg', 'get_mem', 'get_detailed_names_cmd', 'get_detailed_mem_time', 'get_net', 'get_cmd_out', 'get_sensors', 'get_sensors_check', 'get_ms' ) command: str = '' cpu_count: int = psutil.cpu_count() disk_hist: Dict = {} def cleaned(string: str) -> str: '''Escape characters not suitable for "echo -e" in bash''' return string.replace("\\", "\\\\").replace("$", "\\$").replace("\n", "\\n").replace("\t", "\\t").replace("\"", "\\\"").replace("\'", "\\\'") def get_cmd_out(cmd: str): '''Save bash the trouble of creating child processes by running through python instead''' print(subprocess.check_output(cmd, shell=True, universal_newlines=True).rstrip()) def get_ms(): '''Get current epoch millisecond''' t = str(time.time()).split(".") print(f'{t[0]}{t[1][:3]}') def get_sensors(): '''A clone of "sensors" but using psutil''' temps = psutil.sensors_temperatures() if not temps: return try: for name, entries in temps.items(): print(name) for entry in entries: print(f'{entry.label or name}: {entry.current}°C (high = {entry.high}°C, crit = {entry.critical}°C)') print() except: pass def get_sensors_check(): '''Check if get_sensors() output contains accepted CPU temperature values''' if not hasattr(psutil, "sensors_temperatures"): print("false"); return try: temps = psutil.sensors_temperatures() except: pass print("false"); return if not temps: print("false"); return try: for _, entries in temps.items(): for entry in entries: if entry.label.startswith(('Package', 'Core 0', 'Tdie')): print("true") return except: pass print("false") def get_cpu_name(): '''Fetch a suitable CPU identifier from the CPU model name string''' name: str = "" command: str = "" all_info: str = "" rem_line: str = "" if system == "Linux": command = "cat /proc/cpuinfo" rem_line = "model name" elif system == "MacOS": command ="sysctl -n machdep.cpu.brand_string" elif system == "BSD": command ="sysctl hw.model" rem_line = "hw.model" all_info = subprocess.check_output("LANG=C " + command, shell=True, universal_newlines=True) if rem_line: for line in all_info.split("\n"): if rem_line in line: name = re.sub( ".*" + rem_line + ".*:", "", line,1).lstrip() else: name = all_info if "Xeon" in name: name = name.split(" ") name = name[name.index("CPU")+1] elif "Ryzen" in name: name = name.split(" ") name = " ".join(name[name.index("Ryzen"):name.index("Ryzen")+3]) elif "CPU" in name: name = name.split(" ") name = name[name.index("CPU")-1] print(name) def get_cpu_cores(): '''Get number of CPU cores and threads''' cores: int = psutil.cpu_count(logical=False) threads: int = psutil.cpu_count(logical=True) print(f'{cores} {threads if threads else cores}') def get_cpu_usage(): cpu: float = psutil.cpu_percent(percpu=False) threads: List[float] = psutil.cpu_percent(percpu=True) print(f'{cpu:.0f}') for thread in threads: print(f'{thread:.0f}') def get_cpu_freq(): '''Get current CPU frequency''' try: print(f'{psutil.cpu_freq().current:.0f}') except: print(0) def get_uptime(): '''Get current system uptime''' print(str(timedelta(seconds=round(time.time()-psutil.boot_time(),0)))[:-3]) def get_load_avg(): '''Get CPU load average''' for lavg in os.getloadavg(): print(round(lavg, 2), ' ', end='') print() def get_mem(): '''Get current system memory and swap usage''' mem = psutil.virtual_memory() swap = psutil.swap_memory() try: cmem = mem.cached>>10 except: cmem = mem.active>>10 print(mem.total>>10, mem.free>>10, mem.available>>10, cmem, swap.total>>10, swap.free>>10) def get_nics(): '''Get a list of all network devices sorted by highest throughput''' io_all = psutil.net_io_counters(pernic=True) up_stat = psutil.net_if_stats() for nic in sorted(psutil.net_if_addrs(), key=lambda nic: (io_all[nic].bytes_recv + io_all[nic].bytes_sent), reverse=True): if up_stat[nic].isup is False: continue print(nic) def get_net(net_dev: str): '''Emulated /proc/net/dev for selected network device''' net = psutil.net_io_counters(pernic=True)[net_dev] print(0,net.bytes_recv,0,0,0,0,0,0,0,net.bytes_sent) def get_detailed_names_cmd(pid: int): '''Get name, parent name, username and arguments for selected pid''' p = psutil.Process(pid) pa = psutil.Process(p.ppid()) with p.oneshot(): print(p.name()) print(pa.name()) print(p.username()) cmd = ' '.join(p.cmdline()) or '[' + p.name() + ']' print(cleaned(cmd)) def get_detailed_mem_time(pid: int): '''Get memory usage and runtime for selected pid''' p = psutil.Process(pid) with p.oneshot(): print(p.memory_info().rss) print(timedelta(seconds=round(time.time()-p.create_time(),0))) def get_proc(sorting='cpu lazy', tree=False, prog_len=0, arg_len=0, search='', reverse=True, proc_per_cpu=True, max_lines=0): '''List all processess with pid, name, arguments, threads, username, memory percent and cpu percent''' line_count: int = 0 err: float = 0.0 reverse = not reverse if sorting == 'pid': sort_cmd = "p.info['pid']" elif sorting == 'program' or tree and sorting == "arguments": sort_cmd = "p.info['name']" reverse = not reverse elif sorting == 'arguments': sort_cmd = "' '.join(str(p.info['cmdline'])) or p.info['name']" reverse = not reverse elif sorting == 'threads': sort_cmd = "str(p.info['num_threads'])" elif sorting == 'user': sort_cmd = "p.info['username']" reverse = not reverse elif sorting == 'memory': sort_cmd = "str(p.info['memory_percent'])" elif sorting == 'cpu responsive': sort_cmd = "p.info['cpu_percent']" if proc_per_cpu else "(p.info['cpu_percent'] / cpu_count)" else: sort_cmd = "(sum(p.info['cpu_times'][:2] if not p.info['cpu_times'] == 0.0 else [0.0, 0.0]) * 1000 / (time.time() - p.info['create_time']))" if tree: proc_tree(width=prog_len + arg_len, sorting=sort_cmd, reverse=reverse, max_lines=max_lines, proc_per_cpu=proc_per_cpu, search=search) return print(f"{'Pid:':>7} {'Program:':<{prog_len}}", f"{'Arguments:':<{arg_len-4}}" if arg_len else '', f"{'Threads:' if arg_len else ' Tr:'} {'User:':<9}Mem%{'Cpu%':>11}", sep='') for p in sorted(psutil.process_iter(['pid', 'name', 'cmdline', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sort_cmd), reverse=reverse): if p.info['name'] == 'idle' or p.info['name'] == err or p.info['pid'] == err: continue if p.info['cmdline'] == err: p.info['cmdline'] = "" if p.info['username'] == err: p.info['username'] = "?" if p.info['num_threads'] == err: p.info['num_threads'] = 0 if search: found = False for value in [ p.info['name'], ' '.join(p.info['cmdline']), str(p.info['pid']), p.info['username'] ]: if search in value: found = True break if not found: continue cpu = p.info['cpu_percent'] if proc_per_cpu else (p.info['cpu_percent'] / psutil.cpu_count()) mem = p.info['memory_percent'] cmd = ' '.join(p.info['cmdline']) or '[' + p.info['name'] + ']' print(f"{p.info['pid']:>7} ", f"{cleaned(p.info['name']):<{prog_len}.{prog_len-1}}", f"{cleaned(cmd):<{arg_len}.{arg_len-1}}" if arg_len else '', f"{p.info['num_threads']:>4} " if p.info['num_threads'] < 1000 else '999> ', f"{p.info['username']:<9.9}" if len(p.info['username']) < 10 else f"{p.info['username'][:8]:<8}+", f"{mem:>4.1f}" if mem < 100 else f"{mem:>4.0f} ", f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ", sep='') line_count += 1 if max_lines and line_count == max_lines: break def proc_tree(width: int, sorting: str = 'cpu lazy', reverse: bool = True, max_lines: int = 0, proc_per_cpu=True, search=''): '''List all processess in a tree view with pid, name, threads, username, memory percent and cpu percent''' tree_line_count: int = 0 err: float = 0.0 def create_tree(parent: int, tree, indent: str = '', inindent: str = ' ', found: bool = False): nonlocal infolist, tree_line_count, max_lines, tree_width, proc_per_cpu, search cont: bool = True if max_lines and tree_line_count >= max_lines: return try: name: str = psutil.Process(parent).name() if name == "idle": return except psutil.Error: pass name: str = '' try: getinfo: Dict = infolist[parent] except: pass getinfo: bool = False if search and not found: for value in [ name, str(parent), getinfo['username'] if getinfo else '' ]: if search in value: found = True break if not found: cont = False if cont: print(f"{f'{inindent}{parent} {cleaned(name)}':<{tree_width}.{tree_width-1}}", sep='', end='') if getinfo and cont: if getinfo['cpu_times'] == err: getinfo['num_threads'] = 0 if p.info['username'] == err: p.info['username'] = "?" cpu = getinfo['cpu_percent'] if proc_per_cpu else (getinfo['cpu_percent'] / psutil.cpu_count()) print(f"{getinfo['num_threads']:>4} " if getinfo['num_threads'] < 1000 else '999> ', f"{getinfo['username']:<9.9}" if len(getinfo['username']) < 10 else f"{getinfo['username'][:8]:<8}+", f"{getinfo['memory_percent']:>4.1f}" if getinfo['memory_percent'] < 100 else f"{getinfo['memory_percent']:>4.0f} ", f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ", sep='') elif cont: print(f"{'':>14}{'0.0':>4}{'0.0':>11} ", sep='') tree_line_count += 1 if parent not in tree: return children = tree[parent][:-1] for child in children: create_tree(child, tree, indent + " │ ", indent + " ├─ ", found=found) if max_lines and tree_line_count >= max_lines: break child = tree[parent][-1] create_tree(child, tree, indent + " ", indent + " └─ ") infolist: Dict = {} tree: List = defaultdict(list) for p in sorted(psutil.process_iter(['pid', 'name', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sorting), reverse=reverse): try: tree[p.ppid()].append(p.pid) except (psutil.NoSuchProcess, psutil.ZombieProcess): pass else: infolist[p.pid] = p.info if 0 in tree and 0 in tree[0]: tree[0].remove(0) tree_width: int = width + 8 print(f"{' Tree:':<{tree_width-4}}", 'Threads: ', f"{'User:':<9}Mem%{'Cpu%':>11}", sep='') create_tree(min(tree), tree) def get_disks(exclude: str = None, filtering: str = None): '''Get stats, current read and current write for all disks''' global disk_hist disk_read: int = 0 disk_write: int = 0 dev_name: str disk_name: str disk_list: List[str] = [] excludes: List[str] = [] if exclude: excludes = exclude.split(' ') if system == "BSD": excludes += ["devfs", "tmpfs", "procfs", "linprocfs", "gvfs", "fusefs"] if filtering: filtering: Tuple[str] = tuple(filtering.split(' ')) io_counters = psutil.disk_io_counters(perdisk=True if system == "Linux" else False, nowrap=True) print("Ignored line") for disk in psutil.disk_partitions(): disk_io = None disk_name = disk.mountpoint.rsplit('/', 1)[-1] if not disk.mountpoint == "/" else "root" while disk_name in disk_list: disk_name += "_" disk_list += [disk_name] if excludes and disk.fstype in excludes or filtering and not disk_name.endswith(filtering): continue if system == "MacOS" and disk.mountpoint == "/private/var/vm": continue try: disk_u = psutil.disk_usage(disk.mountpoint) except: pass print(f'{disk.device} {disk_u.total >> 10} {disk_u.used >> 10} {disk_u.free >> 10} {disk_u.percent:.0f} ', end='') try: if system == "Linux": dev_name = os.path.realpath(disk.device).rsplit('/', 1)[-1] if dev_name.startswith("md"): try: dev_name = dev_name[:dev_name.index("p")] except: pass disk_io = io_counters[dev_name] elif disk.mountpoint == "/": disk_io = io_counters else: raise Exception disk_read = disk_io.read_bytes disk_write = disk_io.write_bytes disk_read -= disk_hist[disk.device][0] disk_write -= disk_hist[disk.device][1] except: pass disk_read = 0 disk_write = 0 if disk_io: disk_hist[disk.device] = (disk_io.read_bytes, disk_io.write_bytes) print(f'{disk_read >> 10} {disk_write >> 10} {disk_name}') #* The script takes input over coproc pipes and runs command if in the accepted commands list while command != 'quit': if not psutil.pid_exists(parent_pid): quit() try: command = input() except: pass quit() if not command or command == 'test': continue elif command.startswith(allowed_commands): try: exec(command) except Exception as e: pass print() print('/ERROR') print(f'PSUTIL ERROR! Command: {command}\n{e}', file=sys.stderr) else: continue print('/EOL') #print(f'{command}', file=sys.stderr) EOF fi fi #* Set up traps for ctrl-c, soft kill, window resize, ctrl-z and resume from ctrl-z trap 'quitting=1; time_left=0' SIGINT SIGQUIT SIGTERM trap 'resized=1; time_left=0' SIGWINCH trap 'sleepy=1; time_left=0' SIGTSTP trap 'resume_' SIGCONT trap 'failed_pipe=1; time_left=0' PIPE #* Set up error logging to file if enabled if [[ $error_logging == true ]]; then set -o errtrace trap 'traperr' ERR #* Remove everything but the last 500 lines of error log if larger than 500 lines if [[ -e "${config_dir}/error.log" && $(${wc} -l <"${config_dir}/error.log") -gt 500 ]]; then ${tail} -n 500 "${config_dir}/error.log" > "${config_dir}/tmp" ${rm} -f "${config_dir}/error.log" ${mv} -f "${config_dir}/tmp" "${config_dir}/error.log" fi ( echo " " ; echo "New instance of bashtop version: ${version} Pid: $$" ) >> "${config_dir}/error.log" exec 2>>"${config_dir}/error.log" if [[ $1 == "--debug" ]]; then exec 19>"${config_dir}/tracing.log" BASH_XTRACEFD=19 set -x fi else exec 2>/dev/null fi #* If we have been sourced by another shell, quit. Allows sourcing only function definition. [[ "${#BASH_SOURCE[@]}" -gt 1 ]] && { return 0; } #* Call init function init_ if [[ $use_psutil == true ]]; then coproc pycoproc (python3 ${pywrapper}) sleep 0.1 init_ cont fi #* Start infinite loop until false; do if [[ $use_psutil == true ]] && [[ -n $failed_pipe ]]; then if ((++failed_pipes>10)); then if [[ $system == "Linux" ]]; then use_psutil="false" else quit_ 1 fi fi coproc pycoproc (python3 ${pywrapper}) sleep 0.1 unset failed_pipe fi if [[ -n $py_error ]]; then if ((++py_errors>10)); then if [[ $system == "Linux" ]]; then use_psutil="false" else quit_ 1 fi fi unset py_error fi main_loop done #* Quit cleanly even if false starts being true... quit_ bashtop-0.9.25/requirements.txt000066400000000000000000000000161370527161500165370ustar00rootroot00000000000000psutil==5.7.0 bashtop-0.9.25/src/000077500000000000000000000000001370527161500140455ustar00rootroot00000000000000bashtop-0.9.25/src/bashtop.psutil.py000077500000000000000000000321041370527161500174010ustar00rootroot00000000000000#!/usr/bin/env python3 '''This is a copy of the python script that bashtop starts in a coprocess when using psutil for data collection''' import os, sys, subprocess, re, time, psutil from datetime import timedelta from collections import defaultdict from typing import List, Set, Dict, Tuple, Optional, Union system: str if "linux" in sys.platform: system = "Linux" elif "bsd" in sys.platform: system = "BSD" elif "darwin" in sys.platform: system = "MacOS" else: system = "Other" parent_pid: int = psutil.Process(os.getpid()).ppid() allowed_commands: Tuple[str] = ( 'get_proc', 'get_disks', 'get_cpu_name', 'get_cpu_cores', 'get_nics', 'get_cpu_cores', 'get_cpu_usage', 'get_cpu_freq', 'get_uptime', 'get_load_avg', 'get_mem', 'get_detailed_names_cmd', 'get_detailed_mem_time', 'get_net', 'get_cmd_out', 'get_sensors', 'get_sensors_check', 'get_ms' ) command: str = '' cpu_count: int = psutil.cpu_count() disk_hist: Dict = {} def cleaned(string: str) -> str: '''Escape characters not suitable for "echo -e" in bash''' return string.replace("\\", "\\\\").replace("$", "\\$").replace("\n", "\\n").replace("\t", "\\t").replace("\"", "\\\"").replace("\'", "\\\'") def get_cmd_out(cmd: str): '''Save bash the trouble of creating child processes by running through python instead''' print(subprocess.check_output(cmd, shell=True, universal_newlines=True).rstrip()) def get_ms(): '''Get current epoch millisecond''' t = str(time.time()).split(".") print(f'{t[0]}{t[1][:3]}') def get_sensors(): '''A clone of "sensors" but using psutil''' temps = psutil.sensors_temperatures() if not temps: return try: for name, entries in temps.items(): print(name) for entry in entries: print(f'{entry.label or name}: {entry.current}°C (high = {entry.high}°C, crit = {entry.critical}°C)') print() except: pass def get_sensors_check(): '''Check if get_sensors() output contains accepted CPU temperature values''' if not hasattr(psutil, "sensors_temperatures"): print("false"); return try: temps = psutil.sensors_temperatures() except: pass print("false"); return if not temps: print("false"); return try: for _, entries in temps.items(): for entry in entries: if entry.label.startswith(('Package', 'Core 0', 'Tdie')): print("true") return except: pass print("false") def get_cpu_name(): '''Fetch a suitable CPU identifier from the CPU model name string''' name: str = "" command: str = "" all_info: str = "" rem_line: str = "" if system == "Linux": command = "cat /proc/cpuinfo" rem_line = "model name" elif system == "MacOS": command ="sysctl -n machdep.cpu.brand_string" elif system == "BSD": command ="sysctl hw.model" rem_line = "hw.model" all_info = subprocess.check_output("LANG=C " + command, shell=True, universal_newlines=True) if rem_line: for line in all_info.split("\n"): if rem_line in line: name = re.sub( ".*" + rem_line + ".*:", "", line,1).lstrip() else: name = all_info if "Xeon" in name: name = name.split(" ") name = name[name.index("CPU")+1] elif "Ryzen" in name: name = name.split(" ") name = " ".join(name[name.index("Ryzen"):name.index("Ryzen")+3]) elif "CPU" in name: name = name.split(" ") name = name[name.index("CPU")-1] print(name) def get_cpu_cores(): '''Get number of CPU cores and threads''' cores: int = psutil.cpu_count(logical=False) threads: int = psutil.cpu_count(logical=True) print(f'{cores} {threads if threads else cores}') def get_cpu_usage(): cpu: float = psutil.cpu_percent(percpu=False) threads: List[float] = psutil.cpu_percent(percpu=True) print(f'{cpu:.0f}') for thread in threads: print(f'{thread:.0f}') def get_cpu_freq(): '''Get current CPU frequency''' try: print(f'{psutil.cpu_freq().current:.0f}') except: print(0) def get_uptime(): '''Get current system uptime''' print(str(timedelta(seconds=round(time.time()-psutil.boot_time(),0)))[:-3]) def get_load_avg(): '''Get CPU load average''' for lavg in os.getloadavg(): print(round(lavg, 2), ' ', end='') print() def get_mem(): '''Get current system memory and swap usage''' mem = psutil.virtual_memory() swap = psutil.swap_memory() try: cmem = mem.cached>>10 except: cmem = mem.active>>10 print(mem.total>>10, mem.free>>10, mem.available>>10, cmem, swap.total>>10, swap.free>>10) def get_nics(): '''Get a list of all network devices sorted by highest throughput''' io_all = psutil.net_io_counters(pernic=True) up_stat = psutil.net_if_stats() for nic in sorted(psutil.net_if_addrs(), key=lambda nic: (io_all[nic].bytes_recv + io_all[nic].bytes_sent), reverse=True): if up_stat[nic].isup is False: continue print(nic) def get_net(net_dev: str): '''Emulated /proc/net/dev for selected network device''' net = psutil.net_io_counters(pernic=True)[net_dev] print(0,net.bytes_recv,0,0,0,0,0,0,0,net.bytes_sent) def get_detailed_names_cmd(pid: int): '''Get name, parent name, username and arguments for selected pid''' p = psutil.Process(pid) pa = psutil.Process(p.ppid()) with p.oneshot(): print(p.name()) print(pa.name()) print(p.username()) cmd = ' '.join(p.cmdline()) or '[' + p.name() + ']' print(cleaned(cmd)) def get_detailed_mem_time(pid: int): '''Get memory usage and runtime for selected pid''' p = psutil.Process(pid) with p.oneshot(): print(p.memory_info().rss) print(timedelta(seconds=round(time.time()-p.create_time(),0))) def get_proc(sorting='cpu lazy', tree=False, prog_len=0, arg_len=0, search='', reverse=True, proc_per_cpu=True, max_lines=0): '''List all processess with pid, name, arguments, threads, username, memory percent and cpu percent''' line_count: int = 0 err: float = 0.0 reverse = not reverse if sorting == 'pid': sort_cmd = "p.info['pid']" elif sorting == 'program' or tree and sorting == "arguments": sort_cmd = "p.info['name']" reverse = not reverse elif sorting == 'arguments': sort_cmd = "' '.join(str(p.info['cmdline'])) or p.info['name']" reverse = not reverse elif sorting == 'threads': sort_cmd = "str(p.info['num_threads'])" elif sorting == 'user': sort_cmd = "p.info['username']" reverse = not reverse elif sorting == 'memory': sort_cmd = "str(p.info['memory_percent'])" elif sorting == 'cpu responsive': sort_cmd = "p.info['cpu_percent']" if proc_per_cpu else "(p.info['cpu_percent'] / cpu_count)" else: sort_cmd = "(sum(p.info['cpu_times'][:2] if not p.info['cpu_times'] == 0.0 else [0.0, 0.0]) * 1000 / (time.time() - p.info['create_time']))" if tree: proc_tree(width=prog_len + arg_len, sorting=sort_cmd, reverse=reverse, max_lines=max_lines, proc_per_cpu=proc_per_cpu, search=search) return print(f"{'Pid:':>7} {'Program:':<{prog_len}}", f"{'Arguments:':<{arg_len-4}}" if arg_len else '', f"{'Threads:' if arg_len else ' Tr:'} {'User:':<9}Mem%{'Cpu%':>11}", sep='') for p in sorted(psutil.process_iter(['pid', 'name', 'cmdline', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sort_cmd), reverse=reverse): if p.info['name'] == 'idle' or p.info['name'] == err or p.info['pid'] == err: continue if p.info['cmdline'] == err: p.info['cmdline'] = "" if p.info['username'] == err: p.info['username'] = "?" if p.info['num_threads'] == err: p.info['num_threads'] = 0 if search: found = False for value in [ p.info['name'], ' '.join(p.info['cmdline']), str(p.info['pid']), p.info['username'] ]: if search in value: found = True break if not found: continue cpu = p.info['cpu_percent'] if proc_per_cpu else (p.info['cpu_percent'] / psutil.cpu_count()) mem = p.info['memory_percent'] cmd = ' '.join(p.info['cmdline']) or '[' + p.info['name'] + ']' print(f"{p.info['pid']:>7} ", f"{cleaned(p.info['name']):<{prog_len}.{prog_len-1}}", f"{cleaned(cmd):<{arg_len}.{arg_len-1}}" if arg_len else '', f"{p.info['num_threads']:>4} " if p.info['num_threads'] < 1000 else '999> ', f"{p.info['username']:<9.9}" if len(p.info['username']) < 10 else f"{p.info['username'][:8]:<8}+", f"{mem:>4.1f}" if mem < 100 else f"{mem:>4.0f} ", f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ", sep='') line_count += 1 if max_lines and line_count == max_lines: break def proc_tree(width: int, sorting: str = 'cpu lazy', reverse: bool = True, max_lines: int = 0, proc_per_cpu=True, search=''): '''List all processess in a tree view with pid, name, threads, username, memory percent and cpu percent''' tree_line_count: int = 0 err: float = 0.0 def create_tree(parent: int, tree, indent: str = '', inindent: str = ' ', found: bool = False): nonlocal infolist, tree_line_count, max_lines, tree_width, proc_per_cpu, search cont: bool = True if max_lines and tree_line_count >= max_lines: return try: name: str = psutil.Process(parent).name() if name == "idle": return except psutil.Error: pass name: str = '' try: getinfo: Dict = infolist[parent] except: pass getinfo: bool = False if search and not found: for value in [ name, str(parent), getinfo['username'] if getinfo else '' ]: if search in value: found = True break if not found: cont = False if cont: print(f"{f'{inindent}{parent} {cleaned(name)}':<{tree_width}.{tree_width-1}}", sep='', end='') if getinfo and cont: if getinfo['cpu_times'] == err: getinfo['num_threads'] = 0 if p.info['username'] == err: p.info['username'] = "?" cpu = getinfo['cpu_percent'] if proc_per_cpu else (getinfo['cpu_percent'] / psutil.cpu_count()) print(f"{getinfo['num_threads']:>4} " if getinfo['num_threads'] < 1000 else '999> ', f"{getinfo['username']:<9.9}" if len(getinfo['username']) < 10 else f"{getinfo['username'][:8]:<8}+", f"{getinfo['memory_percent']:>4.1f}" if getinfo['memory_percent'] < 100 else f"{getinfo['memory_percent']:>4.0f} ", f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ", sep='') elif cont: print(f"{'':>14}{'0.0':>4}{'0.0':>11} ", sep='') tree_line_count += 1 if parent not in tree: return children = tree[parent][:-1] for child in children: create_tree(child, tree, indent + " │ ", indent + " ├─ ", found=found) if max_lines and tree_line_count >= max_lines: break child = tree[parent][-1] create_tree(child, tree, indent + " ", indent + " └─ ") infolist: Dict = {} tree: List = defaultdict(list) for p in sorted(psutil.process_iter(['pid', 'name', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sorting), reverse=reverse): try: tree[p.ppid()].append(p.pid) except (psutil.NoSuchProcess, psutil.ZombieProcess): pass else: infolist[p.pid] = p.info if 0 in tree and 0 in tree[0]: tree[0].remove(0) tree_width: int = width + 8 print(f"{' Tree:':<{tree_width-4}}", 'Threads: ', f"{'User:':<9}Mem%{'Cpu%':>11}", sep='') create_tree(min(tree), tree) def get_disks(exclude: str = None, filtering: str = None): '''Get stats, current read and current write for all disks''' global disk_hist disk_read: int = 0 disk_write: int = 0 dev_name: str disk_name: str disk_list: List[str] = [] excludes: List[str] = [] if exclude: excludes = exclude.split(' ') if system == "BSD": excludes += ["devfs", "tmpfs", "procfs", "linprocfs", "gvfs", "fusefs"] if filtering: filtering: Tuple[str] = tuple(filtering.split(' ')) io_counters = psutil.disk_io_counters(perdisk=True if system == "Linux" else False, nowrap=True) print("Ignored line") for disk in psutil.disk_partitions(): disk_io = None disk_name = disk.mountpoint.rsplit('/', 1)[-1] if not disk.mountpoint == "/" else "root" while disk_name in disk_list: disk_name += "_" disk_list += [disk_name] if excludes and disk.fstype in excludes or filtering and not disk_name.endswith(filtering): continue if system == "MacOS" and disk.mountpoint == "/private/var/vm": continue try: disk_u = psutil.disk_usage(disk.mountpoint) except: pass print(f'{disk.device} {disk_u.total >> 10} {disk_u.used >> 10} {disk_u.free >> 10} {disk_u.percent:.0f} ', end='') try: if system == "Linux": dev_name = os.path.realpath(disk.device).rsplit('/', 1)[-1] if dev_name.startswith("md"): try: dev_name = dev_name[:dev_name.index("p")] except: pass disk_io = io_counters[dev_name] elif disk.mountpoint == "/": disk_io = io_counters else: raise Exception disk_read = disk_io.read_bytes disk_write = disk_io.write_bytes disk_read -= disk_hist[disk.device][0] disk_write -= disk_hist[disk.device][1] except: pass disk_read = 0 disk_write = 0 if disk_io: disk_hist[disk.device] = (disk_io.read_bytes, disk_io.write_bytes) print(f'{disk_read >> 10} {disk_write >> 10} {disk_name}') #* The script takes input over coproc pipes and runs command if in the accepted commands list while command != 'quit': if not psutil.pid_exists(parent_pid): quit() try: command = input() except: pass quit() if not command or command == 'test': continue elif command.startswith(allowed_commands): try: exec(command) except Exception as e: pass print() print('/ERROR') print(f'PSUTIL ERROR! Command: {command}\n{e}', file=sys.stderr) else: continue print('/EOL') #print(f'{command}', file=sys.stderr)bashtop-0.9.25/test.sh000077500000000000000000000000731370527161500145740ustar00rootroot00000000000000#!/usr/bin/env bash ./test/libs/bats/bin/bats test/*.bats bashtop-0.9.25/test/000077500000000000000000000000001370527161500142355ustar00rootroot00000000000000bashtop-0.9.25/test/basic_test.bats000066400000000000000000000004611370527161500172310ustar00rootroot00000000000000#!/usr/bin/env bats load 'libs/bats-support/load' load 'libs/bats-assert/load' load test_helper @test "Sourcing works, by checking if \$system is set" { run echo $system refute_output "" } @test "#get_themes populates themes" { get_themes assert_success assert [ ${#themes[@]} -gt 0 ] } bashtop-0.9.25/test/libs/000077500000000000000000000000001370527161500151665ustar00rootroot00000000000000bashtop-0.9.25/test/libs/bats/000077500000000000000000000000001370527161500161175ustar00rootroot00000000000000bashtop-0.9.25/test/libs/bats-assert/000077500000000000000000000000001370527161500174165ustar00rootroot00000000000000bashtop-0.9.25/test/libs/bats-support/000077500000000000000000000000001370527161500176315ustar00rootroot00000000000000bashtop-0.9.25/test/test_helper.bash000066400000000000000000000000171370527161500174100ustar00rootroot00000000000000source bashtop bashtop-0.9.25/themes/000077500000000000000000000000001370527161500145435ustar00rootroot00000000000000bashtop-0.9.25/themes/default_black.theme000066400000000000000000000043151370527161500203520ustar00rootroot00000000000000#Bashtop theme with default colors and black background #by aristocratos # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#00" # Main text color theme[main_fg]="#cc" # Title color for boxes theme[title]="#ee" # Higlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box theme[selected_bg]="#7e2626" # Foreground color of selected item in processes box theme[selected_fg]="#ee" # Color of inactive/disabled text theme[inactive_fg]="#40" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#0de756" # Cpu box outline color theme[cpu_box]="#3d7b46" # Memory/disks box outline color theme[mem_box]="#8a882e" # Net up/down box outline color theme[net_box]="#423ba5" # Processes box outline color theme[proc_box]="#923535" # Box divider line and small boxes line color theme[div_line]="#30" # Temperature graph colors theme[temp_start]="#4897d4" theme[temp_mid]="#5474e8" theme[temp_end]="#ff40b6" # CPU graph colors theme[cpu_start]="#50f095" theme[cpu_mid]="#f2e266" theme[cpu_end]="#fa1e1e" # Mem/Disk free meter theme[free_start]="#223014" theme[free_mid]="#b5e685" theme[free_end]="#dcff85" # Mem/Disk cached meter theme[cached_start]="#0b1a29" theme[cached_mid]="#74e6fc" theme[cached_end]="#26c5ff" # Mem/Disk available meter theme[available_start]="#292107" theme[available_mid]="#ffd77a" theme[available_end]="#ffb814" # Mem/Disk used meter theme[used_start]="#3b1f1c" theme[used_mid]="#d9626d" theme[used_end]="#ff4769" # Download graph colors theme[download_start]="#231a63" theme[download_mid]="#4f43a3" theme[download_end]="#b0a9de" # Upload graph colors theme[upload_start]="#510554" theme[upload_mid]="#7d4180" theme[upload_end]="#dcafde"bashtop-0.9.25/themes/flat-remix-light.theme000066400000000000000000000043471370527161500207540ustar00rootroot00000000000000#Bashtop theme with flat-remix colors #by Daniel Ruiz de Alegría # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#ffffff", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#e4e4e7" # Main text color theme[main_fg]="#737680" # Title color for boxes theme[title]="#272a34" # Higlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box theme[selected_bg]="#b8174c" # Foreground color of selected item in processes box theme[selected_fg]="#ff" # Color of inactive/disabled text theme[inactive_fg]="#40" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#367bf0" # Cpu box outline color theme[cpu_box]="#367bf0" # Memory/disks box outline color theme[mem_box]="#19a187" # Net up/down box outline color theme[net_box]="#fd3535" # Processes box outline color theme[proc_box]="#4aaee6" # Box divider line and small boxes line color theme[div_line]="#50" # Temperature graph colors theme[temp_start]="#367bf0" theme[temp_mid]="#b8174c" theme[temp_end]="#d41919" # CPU graph colors theme[cpu_start]="#367bf0" theme[cpu_mid]="#4aaee6" theme[cpu_end]="#54bd8e" # Mem/Disk free meter theme[free_start]="#811035" theme[free_mid]="#b8174c" theme[free_end]="#d41919" # Mem/Disk cached meter theme[cached_start]="#2656a8" theme[cached_mid]="#4aaee6" theme[cached_end]="#23bac2" # Mem/Disk available meter theme[available_start]="#fea44c" theme[available_mid]="#fd7d00" theme[available_end]="#fe7171" # Mem/Disk used meter theme[used_start]="#12715f" theme[used_mid]="#19a187" theme[used_end]="#23bac2" # Download graph colors theme[download_start]="#367bf0" theme[download_mid]="#19a187" theme[download_end]="#4aaee6" # Upload graph colors theme[upload_start]="#8c42ab" theme[upload_mid]="#b8174c" theme[upload_end]="#d41919" bashtop-0.9.25/themes/flat-remix.theme000066400000000000000000000043341370527161500176430ustar00rootroot00000000000000#Bashtop theme with flat-remix colors #by Daniel Ruiz de Alegría # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#ffffff", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="" # Main text color theme[main_fg]="#E6E6E6" # Title color for boxes theme[title]="#ff" # Higlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box theme[selected_bg]="#b8174c" # Foreground color of selected item in processes box theme[selected_fg]="#ff" # Color of inactive/disabled text theme[inactive_fg]="#40" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#367bf0" # Cpu box outline color theme[cpu_box]="#367bf0" # Memory/disks box outline color theme[mem_box]="#19a187" # Net up/down box outline color theme[net_box]="#fd3535" # Processes box outline color theme[proc_box]="#4aaee6" # Box divider line and small boxes line color theme[div_line]="#50" # Temperature graph colors theme[temp_start]="#367bf0" theme[temp_mid]="#b8174c" theme[temp_end]="#d41919" # CPU graph colors theme[cpu_start]="#367bf0" theme[cpu_mid]="#4aaee6" theme[cpu_end]="#54bd8e" # Mem/Disk free meter theme[free_start]="#811035" theme[free_mid]="#b8174c" theme[free_end]="#d41919" # Mem/Disk cached meter theme[cached_start]="#2656a8" theme[cached_mid]="#4aaee6" theme[cached_end]="#23bac2" # Mem/Disk available meter theme[available_start]="#fea44c" theme[available_mid]="#fd7d00" theme[available_end]="#fe7171" # Mem/Disk used meter theme[used_start]="#12715f" theme[used_mid]="#19a187" theme[used_end]="#23bac2" # Download graph colors theme[download_start]="#367bf0" theme[download_mid]="#19a187" theme[download_end]="#4aaee6" # Upload graph colors theme[upload_start]="#8c42ab" theme[upload_mid]="#b8174c" theme[upload_end]="#d41919" bashtop-0.9.25/themes/greyscale.theme000066400000000000000000000040361370527161500175500ustar00rootroot00000000000000#Bashtop grayscale theme #by aristocratos # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#00" # Main text color theme[main_fg]="#bb" # Title color for boxes theme[title]="#cc" # Higlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box theme[selected_bg]="#ff" # Foreground color of selected item in processes box theme[selected_fg]="#00" # Color of inactive/disabled text theme[inactive_fg]="#30" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#90" # Cpu box outline color theme[cpu_box]="#90" # Memory/disks box outline color theme[mem_box]="#90" # Net up/down box outline color theme[net_box]="#90" # Processes box outline color theme[proc_box]="#90" # Box divider line and small boxes line color theme[div_line]="#30" # Temperature graph colors theme[temp_start]="#50" theme[temp_mid]="" theme[temp_end]="#ff" # CPU graph colors theme[cpu_start]="#50" theme[cpu_mid]="" theme[cpu_end]="#ff" # Mem/Disk free meter theme[free_start]="#50" theme[free_mid]="" theme[free_end]="#ff" # Mem/Disk cached meter theme[cached_start]="#50" theme[cached_mid]="" theme[cached_end]="#ff" # Mem/Disk available meter theme[available_start]="#50" theme[available_mid]="" theme[available_end]="#ff" # Mem/Disk used meter theme[used_start]="#50" theme[used_mid]="" theme[used_end]="#ff" # Download graph colors theme[download_start]="#30" theme[download_mid]="" theme[download_end]="#ff" # Upload graph colors theme[upload_start]="#30" theme[upload_mid]="" theme[upload_end]="#ff"bashtop-0.9.25/themes/gruvbox_dark.theme000066400000000000000000000042571370527161500202740ustar00rootroot00000000000000#Bashtop gruvbox (https://github.com/morhetz/gruvbox) theme #by BachoSeven # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#1d2021" # Main text color theme[main_fg]="#a89984" # Title color for boxes theme[title]="#ebdbb2" # Higlight color for keyboard shortcuts theme[hi_fg]="#d79921" # Background color of selected items theme[selected_bg]="#282828" # Foreground color of selected items theme[selected_fg]="#fabd2f" # Color of inactive/disabled text theme[inactive_fg]="#282828" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#98971a" # Cpu box outline color theme[cpu_box]="#a89984" # Memory/disks box outline color theme[mem_box]="#a89984" # Net up/down box outline color theme[net_box]="#a89984" # Processes box outline color theme[proc_box]="#a89984" # Box divider line and small boxes line color theme[div_line]="#a89984" # Temperature graph colors theme[temp_start]="#458588" theme[temp_mid]="#d3869b" theme[temp_end]="#fb4394" # CPU graph colors theme[cpu_start]="#b8bb26" theme[cpu_mid]="#d79921" theme[cpu_end]="#fb4934" # Mem/Disk free meter theme[free_start]="#4e5900" theme[free_mid]="" theme[free_end]="#98971a" # Mem/Disk cached meter theme[cached_start]="#458588" theme[cached_mid]="" theme[cached_end]="#83a598" # Mem/Disk available meter theme[available_start]="#d79921" theme[available_mid]="" theme[available_end]="#fabd2f" # Mem/Disk used meter theme[used_start]="#cc241d" theme[used_mid]="" theme[used_end]="#fb4934" # Download graph colors theme[download_start]="#3d4070" theme[download_mid]="#6c71c4" theme[download_end]="#a3a8f7" # Upload graph colors theme[upload_start]="#701c45" theme[upload_mid]="#b16286" theme[upload_end]="#d3869b" bashtop-0.9.25/themes/index.txt000066400000000000000000000002341370527161500164120ustar00rootroot00000000000000default_black.theme flat-remix-light.theme flat-remix.theme greyscale.theme gruvbox_dark.theme monokai.theme nord.theme solarized_dark.theme whiteout.theme bashtop-0.9.25/themes/monokai.theme000066400000000000000000000043121370527161500172240ustar00rootroot00000000000000#Bashtop monokai theme #by aristocratos # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="" # Main text color theme[main_fg]="#F8F8F2" # Title color for boxes theme[title]="#F8F8F2" # Higlight color for keyboard shortcuts theme[hi_fg]="#F92672" # Background color of selected item in processes box theme[selected_bg]="#7a1137" # Foreground color of selected item in processes box theme[selected_fg]="#F8F8F2" # Color of inactive/disabled text theme[inactive_fg]="#595647" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#A6E22E" # Cpu box outline color theme[cpu_box]="#75715E" # Memory/disks box outline color theme[mem_box]="#75715E" # Net up/down box outline color theme[net_box]="#75715E" # Processes box outline color theme[proc_box]="#75715E" # Box divider line and small boxes line color theme[div_line]="#595647" # Temperature graph colors theme[temp_start]="#7976B7" theme[temp_mid]="#D8B8B2" theme[temp_end]="#F92672" # CPU graph colors theme[cpu_start]="#A6E22E" theme[cpu_mid]="#F8F8F2" #b05475" theme[cpu_end]="#F92672" # Mem/Disk free meter theme[free_start]="#75715E" theme[free_mid]="#a9c474" theme[free_end]="#e2f5bc" # Mem/Disk cached meter theme[cached_start]="#75715E" theme[cached_mid]="#66D9EF" theme[cached_end]="#aae7f2" # Mem/Disk available meter theme[available_start]="#75715E" theme[available_mid]="#E6DB74" theme[available_end]="#f2ecb6" # Mem/Disk used meter theme[used_start]="#75715E" theme[used_mid]="#F92672" theme[used_end]="#ff87b2" # Download graph colors theme[download_start]="#2d2042" theme[download_mid]="#7352a8" theme[download_end]="#ccaefc" # Upload graph colors theme[upload_start]="#570d33" theme[upload_mid]="#cf277d" theme[upload_end]="#fa91c7" bashtop-0.9.25/themes/nord.theme000066400000000000000000000044071370527161500165360ustar00rootroot00000000000000#Bashtop theme with nord palette (https://www.nordtheme.com) #by Justin Zobel # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#ffffff", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#2E3440" # Main text color theme[main_fg]="#D8DEE9" # Title color for boxes theme[title]="#8FBCBB" # Higlight color for keyboard shortcuts theme[hi_fg]="#5E81AC" # Background color of selected item in processes box theme[selected_bg]="#4C566A" # Foreground color of selected item in processes box theme[selected_fg]="#ECEFF4" # Color of inactive/disabled text theme[inactive_fg]="#4C566A" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#5E81AC" # Cpu box outline color theme[cpu_box]="#4C566A" # Memory/disks box outline color theme[mem_box]="#4C566A" # Net up/down box outline color theme[net_box]="#4C566A" # Processes box outline color theme[proc_box]="#4C566A" # Box divider line and small boxes line color theme[div_line]="#4C566A" # Temperature graph colors theme[temp_start]="#81A1C1" theme[temp_mid]="#88C0D0" theme[temp_end]="#ECEFF4" # CPU graph colors theme[cpu_start]="#81A1C1" theme[cpu_mid]="#88C0D0" theme[cpu_end]="#ECEFF4" # Mem/Disk free meter theme[free_start]="#81A1C1" theme[free_mid]="#88C0D0" theme[free_end]="#ECEFF4" # Mem/Disk cached meter theme[cached_start]="#81A1C1" theme[cached_mid]="#88C0D0" theme[cached_end]="#ECEFF4" # Mem/Disk available meter theme[available_start]="#81A1C1" theme[available_mid]="#88C0D0" theme[available_end]="#ECEFF4" # Mem/Disk used meter theme[used_start]="#81A1C1" theme[used_mid]="#88C0D0" theme[used_end]="#ECEFF4" # Download graph colors theme[download_start]="#81A1C1" theme[download_mid]="#88C0D0" theme[download_end]="#ECEFF4" # Upload graph colors theme[upload_start]="#81A1C1" theme[upload_mid]="#88C0D0" theme[upload_end]="#ECEFF4" bashtop-0.9.25/themes/solarized_dark.theme000066400000000000000000000042161370527161500205670ustar00rootroot00000000000000#Bashtop solarized theme #by aristocratos # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#002b36" # Main text color theme[main_fg]="#eee8d5" # Title color for boxes theme[title]="#fdf6e3" # Higlight color for keyboard shortcuts theme[hi_fg]="#b58900" # Background color of selected items theme[selected_bg]="#073642" # Foreground color of selected items theme[selected_fg]="#d6a200" # Color of inactive/disabled text theme[inactive_fg]="#073642" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#bad600" # Cpu box outline color theme[cpu_box]="#586e75" # Memory/disks box outline color theme[mem_box]="#586e75" # Net up/down box outline color theme[net_box]="#586e75" # Processes box outline color theme[proc_box]="#586e75" # Box divider line and small boxes line color theme[div_line]="#586e75" # Temperature graph colors theme[temp_start]="#268bd2" theme[temp_mid]="#ccb5f7" theme[temp_end]="#fc5378" # CPU graph colors theme[cpu_start]="#adc700" theme[cpu_mid]="#d6a200" theme[cpu_end]="#e65317" # Mem/Disk free meter theme[free_start]="#4e5900" theme[free_mid]="" theme[free_end]="#bad600" # Mem/Disk cached meter theme[cached_start]="#114061" theme[cached_mid]="" theme[cached_end]="#268bd2" # Mem/Disk available meter theme[available_start]="#705500" theme[available_mid]="" theme[available_end]="#edb400" # Mem/Disk used meter theme[used_start]="#6e1718" theme[used_mid]="" theme[used_end]="#e02f30" # Download graph colors theme[download_start]="#3d4070" theme[download_mid]="#6c71c4" theme[download_end]="#a3a8f7" # Upload graph colors theme[upload_start]="#701c45" theme[upload_mid]="#d33682" theme[upload_end]="#f56caf"bashtop-0.9.25/themes/whiteout.theme000066400000000000000000000042631370527161500174440ustar00rootroot00000000000000#Bashtop "whiteout" theme #by aristocratos # Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" # example for white: "#FFFFFF", "#ff" or "255 255 255". # All graphs and meters can be gradients # For single color graphs leave "mid" and "end" variable empty. # Use "start" and "end" variables for two color gradient # Use "start", "mid" and "end" for three color gradient # Main background, empty for terminal default, need to be empty if you want transparent background theme[main_bg]="#ff" # Main text color theme[main_fg]="#30" # Title color for boxes theme[title]="#10" # Higlight color for keyboard shortcuts theme[hi_fg]="#284d75" # Background color of selected item in processes box theme[selected_bg]="#15283d" # Foreground color of selected item in processes box theme[selected_fg]="#ff" # Color of inactive/disabled text theme[inactive_fg]="#dd" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text theme[proc_misc]="#03521d" # Cpu box outline color theme[cpu_box]="#1a361e" # Memory/disks box outline color theme[mem_box]="#3d3c14" # Net up/down box outline color theme[net_box]="#1a1742" # Processes box outline color theme[proc_box]="#3b1515" # Box divider line and small boxes line color theme[div_line]="#80" # Temperature graph colors theme[temp_start]="#184567" theme[temp_mid]="#122c87" theme[temp_end]="#9e0061" # CPU graph colors theme[cpu_start]="#0b8e44" theme[cpu_mid]="#a49104" theme[cpu_end]="#8d0202" # Mem/Disk free meter theme[free_start]="#b0d090" theme[free_mid]="#70ba26" theme[free_end]="#496600" # Mem/Disk cached meter theme[cached_start]="#26c5ff" theme[cached_mid]="#74e6fc" theme[cached_end]="#0b1a29" # Mem/Disk available meter theme[available_start]="#ffb814" theme[available_mid]="#ffd77a" theme[available_end]="#292107" # Mem/Disk used meter theme[used_start]="#ff4769" theme[used_mid]="#d9626d" theme[used_end]="#3b1f1c" # Download graph colors theme[download_start]="#8d82de" theme[download_mid]="#413786" theme[download_end]="#130f29" # Upload graph colors theme[upload_start]="#f590f9" theme[upload_mid]="#722e76" theme[upload_end]="#2b062d"