[or-cvs] r21646: {arm} Refactoring goodness and bug fixes. change: revised curses u (in arm/trunk: . init interface util)

Damian Johnson atagar1 at gmail.com
Mon Feb 15 01:01:35 UTC 2010


Author: atagar
Date: 2010-02-15 01:01:34 +0000 (Mon, 15 Feb 2010)
New Revision: 21646

Added:
   arm/trunk/LICENSE
   arm/trunk/init/
   arm/trunk/init/__init__.py
   arm/trunk/init/starter.py
   arm/trunk/init/versionCheck.py
   arm/trunk/util/
   arm/trunk/util/__init__.py
   arm/trunk/util/panel.py
   arm/trunk/util/uiTools.py
Removed:
   arm/trunk/arm.py
   arm/trunk/interface/util.py
   arm/trunk/versionCheck.py
Modified:
   arm/trunk/ChangeLog
   arm/trunk/arm
   arm/trunk/interface/bandwidthMonitor.py
   arm/trunk/interface/confPanel.py
   arm/trunk/interface/connPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/cpuMemMonitor.py
   arm/trunk/interface/descriptorPopup.py
   arm/trunk/interface/fileDescriptorPopup.py
   arm/trunk/interface/graphPanel.py
   arm/trunk/interface/headerPanel.py
   arm/trunk/interface/logPanel.py
Log:
Refactoring goodness and bug fixes.
change: revised curses utilities to further simplify interface implementations
change: substantial layout changes (adding util and init packages) and including a copy of the gpl
fix: bug with handing of DST for accounting's 'Time to reset' (patch provided by waltman)
fix: header and connection panels weren't accounting for having ORListenAddress set (caught by waltman)
fix: crashing bug when shrank too much for scrollbars to be drawn
fix: couple system commands weren't redirecting their stderr to /dev/null



Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/ChangeLog	2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,6 +1,16 @@
 CHANGE LOG
 
-2/7/10 - version 1.3.1
+2/14/10 - version 1.3.2
+Refactoring goodness and bug fixes.
+
+    * change: revised curses utilities to further simplify interfaces
+    * change: substantial layout changes (separating into util and init packages) and including a copy of the gpl
+    * fix: bug with handing of DST for accounting's 'Time to reset' (patch provided by waltman)
+    * fix: header and connection panels weren't accounting for having ORListenAddress set (caught by waltman)
+    * fix: crashing bug when shrank too much for scrollbars to be drawn
+    * fix: couple system commands weren't redirecting their stderr to /dev/null
+
+2/7/10 - version 1.3.1 (r21580)
 Had enough of a siesta - getting back into development beginning with a rewrite if the starter.
 
     * added: made authentication a little smarter, using PROTOCOLINFO to autodetect authentication type and cookie location

Added: arm/trunk/LICENSE
===================================================================
--- arm/trunk/LICENSE	                        (rev 0)
+++ arm/trunk/LICENSE	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

Modified: arm/trunk/arm
===================================================================
--- arm/trunk/arm	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/arm	2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,8 +1,8 @@
 #!/bin/sh
-python versionCheck.py
+python init/versionCheck.py
 
 if [ $? = 0 ]
 then
-  python -W ignore::DeprecationWarning arm.py $*
+  python -W ignore::DeprecationWarning init/starter.py $*
 fi
 

Deleted: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/arm.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,194 +0,0 @@
-#!/usr/bin/env python
-# arm.py -- Terminal status monitor for Tor relays.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-"""
-Command line application for monitoring Tor relays, providing real time status
-information. This is the starter for the application, handling and validating
-command line parameters.
-"""
-
-import sys
-import socket
-import getopt
-import getpass
-
-from TorCtl import TorCtl
-from TorCtl import TorUtil
-
-from interface import controller
-from interface import logPanel
-
-VERSION = "1.3.1"
-LAST_MODIFIED = "Feb 7, 2010"
-
-DEFAULT_CONTROL_ADDR = "127.0.0.1"
-DEFAULT_CONTROL_PORT = 9051
-DEFAULT_LOGGED_EVENTS = "N3" # tor and arm NOTICE, WARN, and ERR events
-
-OPT = "i:p:be:vh"
-OPT_EXPANDED = ["interface=", "password=", "blind", "event=", "version", "help"]
-HELP_TEXT = """Usage arm [OPTION]
-Terminal status monitor for Tor relays.
-
-  -i, --interface [ADDRESS:]PORT  change control interface from %s:%i
-  -p, --password PASSWORD         authenticate using password (skip prompt)
-  -b, --blind                     disable connection lookups
-  -e, --event EVENT_FLAGS         event types in message log  (default: %s)
-%s
-  -v, --version                   provides version information
-  -h, --help                      presents this help
-
-Example:
-arm -b -i 1643          hide connection data, attaching to control port 1643
-arm -e=we -p=nemesis    use password 'nemesis' with 'WARN'/'ERR' events
-""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
-
-def isValidIpAddr(ipStr):
-  """
-  Returns true if input is a valid IPv4 address, false otherwise.
-  """
-  
-  for i in range(4):
-    if i < 3:
-      divIndex = ipStr.find(".")
-      if divIndex == -1: return False # expected a period to be valid
-      octetStr = ipStr[:divIndex]
-      ipStr = ipStr[divIndex + 1:]
-    else:
-      octetStr = ipStr
-    
-    try:
-      octet = int(octetStr)
-      if not octet >= 0 or not octet <= 255: return False
-    except ValueError:
-      # address value isn't an integer
-      return False
-  
-  return True
-
-if __name__ == '__main__':
-  controlAddr = DEFAULT_CONTROL_ADDR     # controller interface IP address
-  controlPort = DEFAULT_CONTROL_PORT     # controller interface port
-  authPassword = ""                      # authentication password (prompts if unset and needed)
-  isBlindMode = False                    # allows connection lookups to be disabled
-  loggedEvents = DEFAULT_LOGGED_EVENTS   # flags for event types in message log
-  
-  # parses user input, noting any issues
-  try:
-    opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
-  except getopt.GetoptError, exc:
-    print str(exc) + " (for usage provide --help)"
-    sys.exit()
-  
-  for opt, arg in opts:
-    if opt in ("-i", "--interface"):
-      # defines control interface address/port
-      try:
-        divIndex = arg.find(":")
-        
-        if divIndex == -1:
-          controlPort = int(arg)
-        else:
-          controlAddr = arg[0:divIndex]
-          controlPort = int(arg[divIndex + 1:])
-        
-        # validates that input is a valid ip address and port
-        if divIndex != -1 and not isValidIpAddr(controlAddr):
-          raise AssertionError("'%s' isn't a valid IP address" % controlAddr)
-        elif controlPort < 0 or controlPort > 65535:
-          raise AssertionError("'%s' isn't a valid port number (ports range 0-65535)" % controlPort)
-      except ValueError:
-        print "'%s' isn't a valid port number" % arg
-        sys.exit()
-      except AssertionError, exc:
-        print exc
-        sys.exit()
-    elif opt in ("-p", "--password"): authPassword = arg    # sets authentication password
-    elif opt in ("-b", "--blind"): isBlindMode = True       # prevents connection lookups
-    elif opt in ("-e", "--event"): loggedEvents = arg       # set event flags
-    elif opt in ("-v", "--version"):
-      print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
-      sys.exit()
-    elif opt in ("-h", "--help"):
-      print HELP_TEXT
-      sys.exit()
-  
-  # validates and expands log event flags
-  try:
-    expandedEvents = logPanel.expandEvents(loggedEvents)
-  except ValueError, exc:
-    for flag in str(exc):
-      print "Unrecognized event flag: %s" % flag
-    sys.exit()
-  
-  # temporarily disables TorCtl logging to prevent issues from going to stdout while starting
-  TorUtil.loglevel = "NONE"
-  
-  # attempts to open a socket to the tor server
-  try:
-    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    s.connect((controlAddr, controlPort))
-    conn = TorCtl.Connection(s)
-  except socket.error, exc:
-    if str(exc) == "[Errno 111] Connection refused":
-      # most common case - tor control port isn't available
-      print "Connection refused. Is the ControlPort enabled?"
-    else:
-      # less common issue - provide exc message
-      print "Failed to establish socket: %s" % exc
-    
-    sys.exit()
-  
-  # check PROTOCOLINFO for authentication type
-  try:
-    authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
-  except TorCtl.ErrorReply, exc:
-    print "Unable to query PROTOCOLINFO for authentication type: %s" % exc
-    sys.exit()
-  
-  try:
-    if authInfo.startswith("AUTH METHODS=NULL"):
-      # no authentication required
-      conn.authenticate("")
-    elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
-      # password authentication, promts for password if it wasn't provided
-      if not authPassword: authPassword = getpass.getpass()
-      conn.authenticate(authPassword)
-    elif authInfo.startswith("AUTH METHODS=COOKIE"):
-      # cookie authtication, parses path to authentication cookie
-      start = authInfo.find("COOKIEFILE=\"") + 12
-      end = authInfo[start:].find("\"")
-      authCookiePath = authInfo[start:start + end]
-      
-      try:
-        authCookie = open(authCookiePath, "r")
-        conn.authenticate_cookie(authCookie)
-        authCookie.close()
-      except IOError, exc:
-        # cleaner message for common errors
-        issue = None
-        if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
-        elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
-        
-        # if problem's recognized give concise message, otherwise print exception string
-        if issue: print "Failed to read authentication cookie (%s): %s" % (issue, authCookiePath)
-        else: print "Failed to read authentication cookie: %s" % exc
-        
-        sys.exit()
-    else:
-      # authentication type unrecognized (probably a new addition to the controlSpec)
-      print "Unrecognized authentication type: %s" % authInfo
-      sys.exit()
-  except TorCtl.ErrorReply, exc:
-    # authentication failed
-    issue = str(exc)
-    if str(exc).startswith("515 Authentication failed: Password did not match"): issue = "password incorrect"
-    if str(exc) == "515 Authentication failed: Wrong length on authentication cookie.": issue = "cookie value incorrect"
-    
-    print "Unable to authenticate: %s" % issue
-    sys.exit()
-  
-  controller.startTorMonitor(conn, expandedEvents, isBlindMode)
-  conn.close()
-

Added: arm/trunk/init/__init__.py
===================================================================
--- arm/trunk/init/__init__.py	                        (rev 0)
+++ arm/trunk/init/__init__.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,6 @@
+"""
+Scripts involved in validating user input, system state, and initializing arm.
+"""
+
+__all__ = ["starter", "versionCheck"]
+

Copied: arm/trunk/init/starter.py (from rev 21580, arm/trunk/arm.py)
===================================================================
--- arm/trunk/init/starter.py	                        (rev 0)
+++ arm/trunk/init/starter.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+
+"""
+Command line application for monitoring Tor relays, providing real time status
+information. This is the starter for the application, handling and validating
+command line parameters.
+"""
+
+import sys
+import socket
+import getopt
+import getpass
+
+# includes parent directory rather than init in path (so sibling modules are included)
+sys.path[0] = sys.path[0][:-5]
+
+from TorCtl import TorCtl, TorUtil
+from interface import controller, logPanel
+
+VERSION = "1.3.2"
+LAST_MODIFIED = "Feb 14, 2010"
+
+DEFAULT_CONTROL_ADDR = "127.0.0.1"
+DEFAULT_CONTROL_PORT = 9051
+DEFAULT_LOGGED_EVENTS = "N3" # tor and arm NOTICE, WARN, and ERR events
+
+OPT = "i:p:be:vh"
+OPT_EXPANDED = ["interface=", "password=", "blind", "event=", "version", "help"]
+HELP_MSG = """Usage arm [OPTION]
+Terminal status monitor for Tor relays.
+
+  -i, --interface [ADDRESS:]PORT  change control interface from %s:%i
+  -p, --password PASSWORD         authenticate using password (skip prompt)
+  -b, --blind                     disable connection lookups
+  -e, --event EVENT_FLAGS         event types in message log  (default: %s)
+%s
+  -v, --version                   provides version information
+  -h, --help                      presents this help
+
+Example:
+arm -b -i 1643          hide connection data, attaching to control port 1643
+arm -e=we -p=nemesis    use password 'nemesis' with 'WARN'/'ERR' events
+""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
+
+def isValidIpAddr(ipStr):
+  """
+  Returns true if input is a valid IPv4 address, false otherwise.
+  """
+  
+  for i in range(4):
+    if i < 3:
+      divIndex = ipStr.find(".")
+      if divIndex == -1: return False # expected a period to be valid
+      octetStr = ipStr[:divIndex]
+      ipStr = ipStr[divIndex + 1:]
+    else:
+      octetStr = ipStr
+    
+    try:
+      octet = int(octetStr)
+      if not octet >= 0 or not octet <= 255: return False
+    except ValueError:
+      # address value isn't an integer
+      return False
+  
+  return True
+
+if __name__ == '__main__':
+  controlAddr = DEFAULT_CONTROL_ADDR     # controller interface IP address
+  controlPort = DEFAULT_CONTROL_PORT     # controller interface port
+  authPassword = ""                      # authentication password (prompts if unset and needed)
+  isBlindMode = False                    # allows connection lookups to be disabled
+  loggedEvents = DEFAULT_LOGGED_EVENTS   # flags for event types in message log
+  
+  # parses user input, noting any issues
+  try:
+    opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
+  except getopt.GetoptError, exc:
+    print str(exc) + " (for usage provide --help)"
+    sys.exit()
+  
+  for opt, arg in opts:
+    if opt in ("-i", "--interface"):
+      # defines control interface address/port
+      try:
+        divIndex = arg.find(":")
+        
+        if divIndex == -1:
+          controlPort = int(arg)
+        else:
+          controlAddr = arg[0:divIndex]
+          controlPort = int(arg[divIndex + 1:])
+        
+        # validates that input is a valid ip address and port
+        if divIndex != -1 and not isValidIpAddr(controlAddr):
+          raise AssertionError("'%s' isn't a valid IP address" % controlAddr)
+        elif controlPort < 0 or controlPort > 65535:
+          raise AssertionError("'%s' isn't a valid port number (ports range 0-65535)" % controlPort)
+      except ValueError:
+        print "'%s' isn't a valid port number" % arg
+        sys.exit()
+      except AssertionError, exc:
+        print exc
+        sys.exit()
+    elif opt in ("-p", "--password"): authPassword = arg    # sets authentication password
+    elif opt in ("-b", "--blind"): isBlindMode = True       # prevents connection lookups
+    elif opt in ("-e", "--event"): loggedEvents = arg       # set event flags
+    elif opt in ("-v", "--version"):
+      print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
+      sys.exit()
+    elif opt in ("-h", "--help"):
+      print HELP_MSG
+      sys.exit()
+  
+  # validates and expands log event flags
+  try:
+    expandedEvents = logPanel.expandEvents(loggedEvents)
+  except ValueError, exc:
+    for flag in str(exc):
+      print "Unrecognized event flag: %s" % flag
+    sys.exit()
+  
+  # temporarily disables TorCtl logging to prevent issues from going to stdout while starting
+  TorUtil.loglevel = "NONE"
+  
+  # attempts to open a socket to the tor server
+  try:
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect((controlAddr, controlPort))
+    conn = TorCtl.Connection(s)
+  except socket.error, exc:
+    if str(exc) == "[Errno 111] Connection refused":
+      # most common case - tor control port isn't available
+      print "Connection refused. Is the ControlPort enabled?"
+    else:
+      # less common issue - provide exc message
+      print "Failed to establish socket: %s" % exc
+    
+    sys.exit()
+  
+  # check PROTOCOLINFO for authentication type
+  try:
+    authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
+  except TorCtl.ErrorReply, exc:
+    print "Unable to query PROTOCOLINFO for authentication type: %s" % exc
+    sys.exit()
+  
+  try:
+    if authInfo.startswith("AUTH METHODS=NULL"):
+      # no authentication required
+      conn.authenticate("")
+    elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
+      # password authentication, promts for password if it wasn't provided
+      if not authPassword: authPassword = getpass.getpass()
+      conn.authenticate(authPassword)
+    elif authInfo.startswith("AUTH METHODS=COOKIE"):
+      # cookie authtication, parses path to authentication cookie
+      start = authInfo.find("COOKIEFILE=\"") + 12
+      end = authInfo[start:].find("\"")
+      authCookiePath = authInfo[start:start + end]
+      
+      try:
+        authCookie = open(authCookiePath, "r")
+        conn.authenticate_cookie(authCookie)
+        authCookie.close()
+      except IOError, exc:
+        # cleaner message for common errors
+        issue = None
+        if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
+        elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
+        
+        # if problem's recognized give concise message, otherwise print exception string
+        if issue: print "Failed to read authentication cookie (%s): %s" % (issue, authCookiePath)
+        else: print "Failed to read authentication cookie: %s" % exc
+        
+        sys.exit()
+    else:
+      # authentication type unrecognized (probably a new addition to the controlSpec)
+      print "Unrecognized authentication type: %s" % authInfo
+      sys.exit()
+  except TorCtl.ErrorReply, exc:
+    # authentication failed
+    issue = str(exc)
+    if str(exc).startswith("515 Authentication failed: Password did not match"): issue = "password incorrect"
+    if str(exc) == "515 Authentication failed: Wrong length on authentication cookie.": issue = "cookie value incorrect"
+    
+    print "Unable to authenticate: %s" % issue
+    sys.exit()
+  
+  controller.startTorMonitor(conn, expandedEvents, isBlindMode)
+  conn.close()
+

Copied: arm/trunk/init/versionCheck.py (from rev 21469, arm/trunk/versionCheck.py)
===================================================================
--- arm/trunk/init/versionCheck.py	                        (rev 0)
+++ arm/trunk/init/versionCheck.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,17 @@
+"""
+Provides a warning and error code if python version isn't compatible.
+"""
+
+import sys
+
+if __name__ == '__main__':
+  majorVersion = sys.version_info[0]
+  minorVersion = sys.version_info[1]
+  
+  if majorVersion > 2:
+    print("arm isn't compatible beyond the python 2.x series\n")
+    sys.exit(1)
+  elif majorVersion < 2 or minorVersion < 5:
+    print("arm requires python version 2.5 or greater\n")
+    sys.exit(1)
+

Modified: arm/trunk/interface/bandwidthMonitor.py
===================================================================
--- arm/trunk/interface/bandwidthMonitor.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/bandwidthMonitor.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -7,7 +7,7 @@
 from TorCtl import TorCtl
 
 import graphPanel
-import util
+from util import uiTools
 
 DL_COLOR = "green"  # download section color
 UL_COLOR = "cyan"   # upload section color
@@ -47,8 +47,8 @@
       bwStats = self.conn.get_option(['BandwidthRate', 'BandwidthBurst'])
       relayStats = self.conn.get_option(['RelayBandwidthRate', 'RelayBandwidthBurst'])
       
-      self.bwRate = util.getSizeLabel(int(bwStats[0][1] if relayStats[0][1] == "0" else relayStats[0][1]))
-      self.bwBurst = util.getSizeLabel(int(bwStats[1][1] if relayStats[1][1] == "0" else relayStats[1][1]))
+      self.bwRate = uiTools.getSizeLabel(int(bwStats[0][1] if relayStats[0][1] == "0" else relayStats[0][1]))
+      self.bwBurst = uiTools.getSizeLabel(int(bwStats[1][1] if relayStats[1][1] == "0" else relayStats[1][1]))
     except (ValueError, socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
       pass # keep old values
     
@@ -59,7 +59,7 @@
   def bandwidth_event(self, event):
     self._processEvent(event.read / 1024.0, event.written / 1024.0)
   
-  def redraw(self, panel):
+  def draw(self, panel):
     # if display is narrow, overwrites x-axis labels with avg / total stats
     if panel.maxX <= COLLAPSE_WIDTH:
       # clears line
@@ -69,8 +69,8 @@
       primaryFooter = "%s, %s" % (self._getAvgLabel(True), self._getTotalLabel(True))
       secondaryFooter = "%s, %s" % (self._getAvgLabel(False), self._getTotalLabel(False))
       
-      panel.addstr(8, 1, primaryFooter, util.getColor(self.primaryColor))
-      panel.addstr(8, graphCol + 6, secondaryFooter, util.getColor(self.secondaryColor))
+      panel.addstr(8, 1, primaryFooter, uiTools.getColor(self.primaryColor))
+      panel.addstr(8, graphCol + 6, secondaryFooter, uiTools.getColor(self.secondaryColor))
     
     # provides accounting stats if enabled
     if self.isAccounting:
@@ -84,8 +84,8 @@
         
         panel.addfstr(10, 0, "<b>Accounting (<%s>%s</%s>)" % (hibernateColor, status, hibernateColor))
         panel.addstr(10, 35, "Time to reset: %s" % self.accountingInfo["resetTime"])
-        panel.addstr(11, 2, "%s / %s" % (self.accountingInfo["read"], self.accountingInfo["readLimit"]), util.getColor(self.primaryColor))
-        panel.addstr(11, 37, "%s / %s" % (self.accountingInfo["written"], self.accountingInfo["writtenLimit"]), util.getColor(self.secondaryColor))
+        panel.addstr(11, 2, "%s / %s" % (self.accountingInfo["read"], self.accountingInfo["readLimit"]), uiTools.getColor(self.primaryColor))
+        panel.addstr(11, 37, "%s / %s" % (self.accountingInfo["written"], self.accountingInfo["writtenLimit"]), uiTools.getColor(self.secondaryColor))
       else:
         panel.addfstr(10, 0, "<b>Accounting:</b> Shutting Down...")
   
@@ -108,7 +108,7 @@
       stats[1] = "- %s" % self._getAvgLabel(isPrimary)
       stats[2] = ", %s" % self._getTotalLabel(isPrimary)
     
-    stats[0] = "%-14s" % ("%s/sec" % util.getSizeLabel((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1))
+    stats[0] = "%-14s" % ("%s/sec" % uiTools.getSizeLabel((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1))
     
     labeling = graphType + " (" + "".join(stats).strip() + "):"
     while (len(labeling) >= width):
@@ -123,11 +123,11 @@
   
   def _getAvgLabel(self, isPrimary):
     total = self.primaryTotal if isPrimary else self.secondaryTotal
-    return "avg: %s/sec" % util.getSizeLabel((total / max(1, self.tick)) * 1024, 1)
+    return "avg: %s/sec" % uiTools.getSizeLabel((total / max(1, self.tick)) * 1024, 1)
   
   def _getTotalLabel(self, isPrimary):
     total = self.primaryTotal if isPrimary else self.secondaryTotal
-    return "total: %s" % util.getSizeLabel(total * 1024, 1)
+    return "total: %s" % uiTools.getSizeLabel(total * 1024, 1)
   
   def _updateAccountingInfo(self):
     """
@@ -143,8 +143,11 @@
       accountingParams = self.conn.get_info(["accounting/hibernating", "accounting/bytes", "accounting/bytes-left", "accounting/interval-end"])
       self.accountingInfo["status"] = accountingParams["accounting/hibernating"]
       
-      # altzone subtraction converts from gmt to local with respect to DST
-      sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - time.altzone
+      # converts from gmt to local with respect to DST
+      if time.localtime()[8]: tz_offset = time.altzone
+      else: tz_offset = time.timezone
+      
+      sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - tz_offset
       resetHours = sec / 3600
       sec %= 3600
       resetMin = sec / 60
@@ -156,10 +159,10 @@
       readLeft = int(accountingParams["accounting/bytes-left"].split(" ")[0])
       writtenLeft = int(accountingParams["accounting/bytes-left"].split(" ")[1])
       
-      self.accountingInfo["read"] = util.getSizeLabel(read)
-      self.accountingInfo["written"] = util.getSizeLabel(written)
-      self.accountingInfo["readLimit"] = util.getSizeLabel(read + readLeft)
-      self.accountingInfo["writtenLimit"] = util.getSizeLabel(written + writtenLeft)
+      self.accountingInfo["read"] = uiTools.getSizeLabel(read)
+      self.accountingInfo["written"] = uiTools.getSizeLabel(written)
+      self.accountingInfo["readLimit"] = uiTools.getSizeLabel(read + readLeft)
+      self.accountingInfo["writtenLimit"] = uiTools.getSizeLabel(written + writtenLeft)
     except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
       self.accountingInfo = None
 

Modified: arm/trunk/interface/confPanel.py
===================================================================
--- arm/trunk/interface/confPanel.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/confPanel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
 import math
 import curses
 
-import util
+from util import panel, uiTools
 
 # torrc parameters that can be defined multiple times without overwriting
 # from src/or/config.c (entries with LINELIST or LINELIST_S)
@@ -24,13 +24,13 @@
 LABEL_DAY = ["day", "days"]
 LABEL_WEEK = ["week", "weeks"]
 
-class ConfPanel(util.Panel):
+class ConfPanel(panel.Panel):
   """
   Presents torrc with syntax highlighting in a scroll-able area.
   """
   
-  def __init__(self, lock, confLocation, conn, logPanel):
-    util.Panel.__init__(self, lock, -1)
+  def __init__(self, confLocation, conn, logPanel):
+    panel.Panel.__init__(self, -1)
     self.confLocation = confLocation
     self.showLineNum = True
     self.stripComments = False
@@ -133,64 +133,56 @@
       self.scroll = 0
     self.redraw()
   
-  def redraw(self):
-    if self.win:
-      if not self.lock.acquire(False): return
-      try:
-        self.clear()
-        self.addstr(0, 0, "Tor Config (%s):" % self.confLocation, util.LABEL_ATTR)
+  def draw(self):
+    self.addstr(0, 0, "Tor Config (%s):" % self.confLocation, uiTools.LABEL_ATTR)
+    
+    pageHeight = self.maxY - 1
+    numFieldWidth = int(math.log10(len(self.confContents))) + 1
+    lineNum, displayLineNum = self.scroll + 1, 1 # lineNum corresponds to torrc, displayLineNum concerns what's presented
+    
+    for i in range(self.scroll, min(len(self.confContents), self.scroll + pageHeight)):
+      lineText = self.confContents[i].strip()
+      skipLine = False # true if we're not presenting line due to stripping
+      
+      command, argument, correction, comment = "", "", "", ""
+      commandColor, argumentColor, correctionColor, commentColor = "green", "cyan", "cyan", "white"
+      
+      if not lineText:
+        # no text
+        if self.stripComments: skipLine = True
+      elif lineText[0] == "#":
+        # whole line is commented out
+        comment = lineText
+        if self.stripComments: skipLine = True
+      else:
+        # parse out command, argument, and possible comment
+        ctlEnd = lineText.find(" ")   # end of command
+        argEnd = lineText.find("#")   # end of argument (start of comment or end of line)
+        if argEnd == -1: argEnd = len(lineText)
         
-        pageHeight = self.maxY - 1
-        numFieldWidth = int(math.log10(len(self.confContents))) + 1
-        lineNum, displayLineNum = self.scroll + 1, 1 # lineNum corresponds to torrc, displayLineNum concerns what's presented
+        command, argument, comment = lineText[:ctlEnd], lineText[ctlEnd:argEnd], lineText[argEnd:]
         
-        for i in range(self.scroll, min(len(self.confContents), self.scroll + pageHeight)):
-          lineText = self.confContents[i].strip()
-          skipLine = False # true if we're not presenting line due to stripping
-          
-          command, argument, correction, comment = "", "", "", ""
-          commandColor, argumentColor, correctionColor, commentColor = "green", "cyan", "cyan", "white"
-          
-          if not lineText:
-            # no text
-            if self.stripComments: skipLine = True
-          elif lineText[0] == "#":
-            # whole line is commented out
-            comment = lineText
-            if self.stripComments: skipLine = True
-          else:
-            # parse out command, argument, and possible comment
-            ctlEnd = lineText.find(" ")   # end of command
-            argEnd = lineText.find("#")   # end of argument (start of comment or end of line)
-            if argEnd == -1: argEnd = len(lineText)
-            
-            command, argument, comment = lineText[:ctlEnd], lineText[ctlEnd:argEnd], lineText[argEnd:]
-            
-            # changes presentation if value's incorrect or irrelevant
-            if lineNum in self.corrections.keys():
-              argumentColor = "red"
-              correction = " (%s)" % self.corrections[lineNum]
-            elif lineNum in self.irrelevantLines:
-              commandColor = "blue"
-              argumentColor = "blue"
-          
-          if not skipLine:
-            numOffset = 0     # offset for line numbering
-            if self.showLineNum:
-              self.addstr(displayLineNum, 0, ("%%%ii" % numFieldWidth) % lineNum, curses.A_BOLD | util.getColor("yellow"))
-              numOffset = numFieldWidth + 1
-            
-            xLoc = 0
-            displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, command, curses.A_BOLD | util.getColor(commandColor), numOffset)
-            displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, argument, curses.A_BOLD | util.getColor(argumentColor), numOffset)
-            displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, correction, curses.A_BOLD | util.getColor(correctionColor), numOffset)
-            displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, comment, util.getColor(commentColor), numOffset)
-            
-            displayLineNum += 1
-          
-          lineNum += 1
-          
-        self.refresh()
-      finally:
-        self.lock.release()
+        # changes presentation if value's incorrect or irrelevant
+        if lineNum in self.corrections.keys():
+          argumentColor = "red"
+          correction = " (%s)" % self.corrections[lineNum]
+        elif lineNum in self.irrelevantLines:
+          commandColor = "blue"
+          argumentColor = "blue"
+      
+      if not skipLine:
+        numOffset = 0     # offset for line numbering
+        if self.showLineNum:
+          self.addstr(displayLineNum, 0, ("%%%ii" % numFieldWidth) % lineNum, curses.A_BOLD | uiTools.getColor("yellow"))
+          numOffset = numFieldWidth + 1
+        
+        xLoc = 0
+        displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, command, curses.A_BOLD | uiTools.getColor(commandColor), numOffset)
+        displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, argument, curses.A_BOLD | uiTools.getColor(argumentColor), numOffset)
+        displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, correction, curses.A_BOLD | uiTools.getColor(correctionColor), numOffset)
+        displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, comment, uiTools.getColor(commentColor), numOffset)
+        
+        displayLineNum += 1
+      
+      lineNum += 1
 

Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/connPanel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -9,7 +9,7 @@
 from TorCtl import TorCtl
 
 import hostnameResolver
-import util
+from util import panel, uiTools
 
 # directory servers (IP, port) for tor version 0.2.2.1-alpha-dev
 DIR_SERVERS = [("86.59.21.38", "80"),         # tor26
@@ -92,14 +92,14 @@
     if sortLabel == label: return type
   raise ValueError(sortLabel)
 
-class ConnPanel(TorCtl.PostEventListener, util.Panel):
+class ConnPanel(TorCtl.PostEventListener, panel.Panel):
   """
   Lists netstat provided network data of tor.
   """
   
-  def __init__(self, lock, conn, connResolver, logger):
+  def __init__(self, conn, connResolver, logger):
     TorCtl.PostEventListener.__init__(self)
-    util.Panel.__init__(self, lock, -1)
+    panel.Panel.__init__(self, -1)
     self.scroll = 0
     self.conn = conn                  # tor connection for querrying country codes
     self.connResolver = connResolver  # thread performing netstat queries
@@ -138,6 +138,7 @@
     self.familyResolutions = {}
     
     self.nickname = ""
+    self.listenPort = "0"           # port used to identify inbound/outbound connections (from ORListenAddress if defined, otherwise ORPort)
     self.orPort = "0"
     self.dirPort = "0"
     self.controlPort = "0"
@@ -161,17 +162,23 @@
     try:
       self.nickname = self.conn.get_option("Nickname")[0][1]
       
-      # uses ports to identify type of connections
       self.orPort = self.conn.get_option("ORPort")[0][1]
       self.dirPort = self.conn.get_option("DirPort")[0][1]
       self.controlPort = self.conn.get_option("ControlPort")[0][1]
       
+      # uses ports to identify type of connections (ORListenAddress port overwrites ORPort if set)
+      listenAddr = self.conn.get_option("ORListenAddress")[0][1]
+      if listenAddr and ":" in listenAddr:
+        self.listenPort = listenAddr[listenAddr.find(":") + 1:]
+      else: self.listenPort = self.orPort
+      
       # entry is None if not set, otherwise of the format "$<fingerprint>,$<fingerprint>"
       familyEntry = self.conn.get_option("MyFamily")[0][1]
       if familyEntry: self.family = [entry[1:] for entry in familyEntry.split(",")]
       else: self.family = []
     except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
       self.nickname = ""
+      self.listenPort = None
       self.orPort = "0"
       self.dirPort = "0"
       self.controlPort = "0"
@@ -262,7 +269,7 @@
         localIP, foreignIP = local[:local.find(":")], foreign[:foreign.find(":")]
         localPort, foreignPort = local[len(localIP) + 1:], foreign[len(foreignIP) + 1:]
         
-        if localPort in (self.orPort, self.dirPort):
+        if localPort in (self.listenPort, self.dirPort):
           type = "inbound"
           connectionCountTmp[0] += 1
         elif localPort == self.controlPort:
@@ -341,7 +348,7 @@
           familyResolutionsTmp[(familyAddress, familyPort)] = fingerprint
           connectionsTmp.append(("family", familyAddress, familyPort, familyAddress, familyPort, familyCountryCode, connTime))
         except (socket.error, TorCtl.ErrorReply):
-          # use dummy entry for sorting - the redraw function notes that entries are unknown
+          # use dummy entry for sorting - the draw function notes that entries are unknown
           portIdentifier = str(65536 + tmpCounter)
           familyResolutionsTmp[("256.255.255.255", portIdentifier)] = fingerprint
           connectionsTmp.append(("family", "256.255.255.255", portIdentifier, "256.255.255.255", portIdentifier, "??", time.time()))
@@ -361,7 +368,7 @@
         self.connectionCount = connectionCountTmp
         self.familyResolutions = familyResolutionsTmp
         
-        # hostnames are sorted at redraw - otherwise now's a good time
+        # hostnames are sorted at draw - otherwise now's a good time
         if self.listingType != LIST_HOSTNAME: self.sortConnections()
       self.lastNetstatResults = results
     finally:
@@ -407,188 +414,182 @@
     else: return # skip following redraw
     self.redraw()
   
-  def redraw(self):
-    if self.win:
-      if not self.lock.acquire(False): return
-      self.connectionsLock.acquire()
-      try:
-        # hostnames frequently get updated so frequent sorting needed
-        if self.listingType == LIST_HOSTNAME: self.sortConnections()
+  def draw(self):
+    self.connectionsLock.acquire()
+    try:
+      # hostnames frequently get updated so frequent sorting needed
+      if self.listingType == LIST_HOSTNAME: self.sortConnections()
+      
+      if self.showLabel:
+        # notes the number of connections for each type if above zero
+        countLabel = ""
+        for i in range(len(self.connectionCount)):
+          if self.connectionCount[i] > 0: countLabel += "%i %s, " % (self.connectionCount[i], CONN_COUNT_LABELS[i])
+        if countLabel: countLabel = " (%s)" % countLabel[:-2] # strips ending ", " and encases in parentheses
+        self.addstr(0, 0, "Connections%s:" % countLabel, uiTools.LABEL_ATTR)
+      
+      if self.connections:
+        listingHeight = self.maxY - 1
+        currentTime = time.time() if not self.isPaused else self.pauseTime
         
-        self.clear()
-        if self.showLabel:
-          # notes the number of connections for each type if above zero
-          countLabel = ""
-          for i in range(len(self.connectionCount)):
-            if self.connectionCount[i] > 0: countLabel += "%i %s, " % (self.connectionCount[i], CONN_COUNT_LABELS[i])
-          if countLabel: countLabel = " (%s)" % countLabel[:-2] # strips ending ", " and encases in parentheses
-          self.addstr(0, 0, "Connections%s:" % countLabel, util.LABEL_ATTR)
+        if self.showingDetails:
+          listingHeight -= 8
+          isScrollBarVisible = len(self.connections) > self.maxY - 9
+          if self.maxX > 80: self.win.hline(8, 80, curses.ACS_HLINE, self.maxX - 81)
+        else:
+          isScrollBarVisible = len(self.connections) > self.maxY - 1
+        xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
         
-        if self.connections:
-          listingHeight = self.maxY - 1
-          currentTime = time.time() if not self.isPaused else self.pauseTime
+        # ensure cursor location and scroll top are within bounds
+        self.cursorLoc = max(min(self.cursorLoc, len(self.connections) - 1), 0)
+        self.scroll = max(min(self.scroll, len(self.connections) - listingHeight), 0)
+        
+        if self.isCursorEnabled:
+          # update cursorLoc with selection (or vice versa if selection not found)
+          if self.cursorSelection not in self.connections:
+            self.cursorSelection = self.connections[self.cursorLoc]
+          else: self.cursorLoc = self.connections.index(self.cursorSelection)
           
-          if self.showingDetails:
-            listingHeight -= 8
-            isScrollBarVisible = len(self.connections) > self.maxY - 9
-            if self.maxX > 80: self.win.hline(8, 80, curses.ACS_HLINE, self.maxX - 81)
-          else:
-            isScrollBarVisible = len(self.connections) > self.maxY - 1
-          xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
-          
-          # ensure cursor location and scroll top are within bounds
-          self.cursorLoc = max(min(self.cursorLoc, len(self.connections) - 1), 0)
-          self.scroll = max(min(self.scroll, len(self.connections) - listingHeight), 0)
-          
-          if self.isCursorEnabled:
-            # update cursorLoc with selection (or vice versa if selection not found)
-            if self.cursorSelection not in self.connections:
-              self.cursorSelection = self.connections[self.cursorLoc]
-            else: self.cursorLoc = self.connections.index(self.cursorSelection)
+          # shift scroll if necessary for cursor to be visible
+          if self.cursorLoc < self.scroll: self.scroll = self.cursorLoc
+          elif self.cursorLoc - listingHeight + 1 > self.scroll: self.scroll = self.cursorLoc - listingHeight + 1
+        
+        lineNum = (-1 * self.scroll) + 1
+        for entry in self.connections:
+          if lineNum >= 1:
+            type = entry[CONN_TYPE]
+            color = TYPE_COLORS[type]
             
-            # shift scroll if necessary for cursor to be visible
-            if self.cursorLoc < self.scroll: self.scroll = self.cursorLoc
-            elif self.cursorLoc - listingHeight + 1 > self.scroll: self.scroll = self.cursorLoc - listingHeight + 1
-          
-          lineNum = (-1 * self.scroll) + 1
-          for entry in self.connections:
-            if lineNum >= 1:
-              type = entry[CONN_TYPE]
-              color = TYPE_COLORS[type]
+            # adjustments to measurements for 'xOffset' are to account for scroll bar
+            if self.listingType == LIST_IP:
+              # base data requires 73 characters
+              src = "%s:%s" % (entry[CONN_L_IP], entry[CONN_L_PORT])
+              dst = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
+              src, dst = "%-21s" % src, "%-26s" % dst
               
-              # adjustments to measurements for 'xOffset' are to account for scroll bar
-              if self.listingType == LIST_IP:
-                # base data requires 73 characters
-                src = "%s:%s" % (entry[CONN_L_IP], entry[CONN_L_PORT])
-                dst = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
-                src, dst = "%-21s" % src, "%-26s" % dst
+              etc = ""
+              if self.maxX > 115 + xOffset:
+                # show fingerprint (column width: 42 characters)
+                etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
                 
-                etc = ""
-                if self.maxX > 115 + xOffset:
-                  # show fingerprint (column width: 42 characters)
-                  etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-                  
-                if self.maxX > 127 + xOffset:
-                  # show nickname (column width: remainder)
-                  nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-                  nicknameSpace = self.maxX - 118 - xOffset
-                  
-                  # truncates if too long
-                  if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
-                  
-                  etc += ("%%-%is  " % nicknameSpace) % nickname
-              elif self.listingType == LIST_HOSTNAME:
-                # base data requires 80 characters
-                src = "localhost:%-5s" % entry[CONN_L_PORT]
+              if self.maxX > 127 + xOffset:
+                # show nickname (column width: remainder)
+                nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+                nicknameSpace = self.maxX - 118 - xOffset
                 
-                # space available for foreign hostname (stretched to claim any free space)
-                foreignHostnameSpace = self.maxX - 42 - xOffset
+                # truncates if too long
+                if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
                 
-                etc = ""
-                if self.maxX > 102 + xOffset:
-                  # shows ip/locale (column width: 22 characters)
-                  foreignHostnameSpace -= 22
-                  etc += "%-20s  " % ("%s %s" % (entry[CONN_F_IP], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
-                
-                if self.maxX > 134 + xOffset:
-                  # show fingerprint (column width: 42 characters)
-                  foreignHostnameSpace -= 42
-                  etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-                
-                if self.maxX > 151 + xOffset:
-                  # show nickname (column width: min 17 characters, uses half of the remainder)
-                  nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-                  nicknameSpace = 15 + (self.maxX - xOffset - 151) / 2
-                  foreignHostnameSpace -= (nicknameSpace + 2)
-                  
-                  if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
-                  etc += ("%%-%is  " % nicknameSpace) % nickname
-                
-                hostname = self.resolver.resolve(entry[CONN_F_IP])
-                
-                # truncates long hostnames
-                portDigits = len(str(entry[CONN_F_PORT]))
-                if hostname and (len(hostname) + portDigits) > foreignHostnameSpace - 1:
-                  hostname = hostname[:(foreignHostnameSpace - portDigits - 4)] + "..."
-                
-                dst = "%s:%s" % (hostname if hostname else entry[CONN_F_IP], entry[CONN_F_PORT])
-                dst = ("%%-%is" % foreignHostnameSpace) % dst
-              elif self.listingType == LIST_FINGERPRINT:
-                # base data requires 75 characters
-                src = "localhost"
-                if entry[CONN_TYPE] == "control": dst = "localhost"
-                else: dst = self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-                dst = "%-40s" % dst
-                
-                etc = ""
-                if self.maxX > 92 + xOffset:
-                  # show nickname (column width: min 17 characters, uses remainder if extra room's available)
-                  nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-                  nicknameSpace = self.maxX - 78 - xOffset if self.maxX < 126 else self.maxX - 106 - xOffset
-                  if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
-                  etc += ("%%-%is  " % nicknameSpace) % nickname
-                
-                if self.maxX > 125 + xOffset:
-                  # shows ip/port/locale (column width: 28 characters)
-                  etc += "%-26s  " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
-              else:
-                # base data uses whatever extra room's available (using minimun of 50 characters)
-                src = self.nickname
-                if entry[CONN_TYPE] == "control": dst = self.nickname
-                else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-                
-                # space available for foreign nickname
-                foreignNicknameSpace = self.maxX - len(self.nickname) - 27 - xOffset
-                
-                etc = ""
-                if self.maxX > 92 + xOffset:
-                  # show fingerprint (column width: 42 characters)
-                  foreignNicknameSpace -= 42
-                  etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-                
-                if self.maxX > 120 + xOffset:
-                  # shows ip/port/locale (column width: 28 characters)
-                  foreignNicknameSpace -= 28
-                  etc += "%-26s  " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
-                
-                dst = ("%%-%is" % foreignNicknameSpace) % dst
+                etc += ("%%-%is  " % nicknameSpace) % nickname
+            elif self.listingType == LIST_HOSTNAME:
+              # base data requires 80 characters
+              src = "localhost:%-5s" % entry[CONN_L_PORT]
               
-              timeLabel = util.getTimeLabel(currentTime - entry[CONN_TIME], 1)
-              if type == "inbound": src, dst = dst, src
-              elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
-                # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
-                timeLabel = "---"
+              # space available for foreign hostname (stretched to claim any free space)
+              foreignHostnameSpace = self.maxX - 42 - xOffset
+              
+              etc = ""
+              if self.maxX > 102 + xOffset:
+                # shows ip/locale (column width: 22 characters)
+                foreignHostnameSpace -= 22
+                etc += "%-20s  " % ("%s %s" % (entry[CONN_F_IP], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+              
+              if self.maxX > 134 + xOffset:
+                # show fingerprint (column width: 42 characters)
+                foreignHostnameSpace -= 42
+                etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+              
+              if self.maxX > 151 + xOffset:
+                # show nickname (column width: min 17 characters, uses half of the remainder)
+                nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+                nicknameSpace = 15 + (self.maxX - xOffset - 151) / 2
+                foreignHostnameSpace -= (nicknameSpace + 2)
                 
-                if self.listingType == LIST_IP:
-                  src = "%-21s" % "UNKNOWN"
-                  dst = "%-26s" % "UNKNOWN"
-                elif self.listingType == LIST_HOSTNAME:
-                  src = "%-15s" % "UNKNOWN"
-                  dst = ("%%-%is" % len(dst)) % "UNKNOWN"
-                  if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
-                else:
-                  ipStart = etc.find("256")
-                  if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
+                if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
+                etc += ("%%-%is  " % nicknameSpace) % nickname
               
-              padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
-              lineEntry = "<%s>%s  -->  %s  %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
+              hostname = self.resolver.resolve(entry[CONN_F_IP])
               
-              if self.isCursorEnabled and entry == self.cursorSelection:
-                lineEntry = "<h>%s</h>" % lineEntry
+              # truncates long hostnames
+              portDigits = len(str(entry[CONN_F_PORT]))
+              if hostname and (len(hostname) + portDigits) > foreignHostnameSpace - 1:
+                hostname = hostname[:(foreignHostnameSpace - portDigits - 4)] + "..."
               
-              yOffset = 0 if not self.showingDetails else 8
-              self.addfstr(lineNum + yOffset, xOffset, lineEntry)
-            lineNum += 1
-          
-          if isScrollBarVisible:
-            topY = 9 if self.showingDetails else 1
-            bottomEntry = self.scroll + self.maxY - 9 if self.showingDetails else self.scroll + self.maxY - 1
-            util.drawScrollBar(self, topY, self.maxY - 1, self.scroll, bottomEntry, len(self.connections))
+              dst = "%s:%s" % (hostname if hostname else entry[CONN_F_IP], entry[CONN_F_PORT])
+              dst = ("%%-%is" % foreignHostnameSpace) % dst
+            elif self.listingType == LIST_FINGERPRINT:
+              # base data requires 75 characters
+              src = "localhost"
+              if entry[CONN_TYPE] == "control": dst = "localhost"
+              else: dst = self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+              dst = "%-40s" % dst
+              
+              etc = ""
+              if self.maxX > 92 + xOffset:
+                # show nickname (column width: min 17 characters, uses remainder if extra room's available)
+                nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+                nicknameSpace = self.maxX - 78 - xOffset if self.maxX < 126 else self.maxX - 106 - xOffset
+                if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
+                etc += ("%%-%is  " % nicknameSpace) % nickname
+              
+              if self.maxX > 125 + xOffset:
+                # shows ip/port/locale (column width: 28 characters)
+                etc += "%-26s  " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+            else:
+              # base data uses whatever extra room's available (using minimun of 50 characters)
+              src = self.nickname
+              if entry[CONN_TYPE] == "control": dst = self.nickname
+              else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+              
+              # space available for foreign nickname
+              foreignNicknameSpace = self.maxX - len(self.nickname) - 27 - xOffset
+              
+              etc = ""
+              if self.maxX > 92 + xOffset:
+                # show fingerprint (column width: 42 characters)
+                foreignNicknameSpace -= 42
+                etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+              
+              if self.maxX > 120 + xOffset:
+                # shows ip/port/locale (column width: 28 characters)
+                foreignNicknameSpace -= 28
+                etc += "%-26s  " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+              
+              dst = ("%%-%is" % foreignNicknameSpace) % dst
+            
+            timeLabel = uiTools.getTimeLabel(currentTime - entry[CONN_TIME], 1)
+            if type == "inbound": src, dst = dst, src
+            elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
+              # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
+              timeLabel = "---"
+              
+              if self.listingType == LIST_IP:
+                src = "%-21s" % "UNKNOWN"
+                dst = "%-26s" % "UNKNOWN"
+              elif self.listingType == LIST_HOSTNAME:
+                src = "%-15s" % "UNKNOWN"
+                dst = ("%%-%is" % len(dst)) % "UNKNOWN"
+                if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
+              else:
+                ipStart = etc.find("256")
+                if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
+            
+            padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
+            lineEntry = "<%s>%s  -->  %s  %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
+            
+            if self.isCursorEnabled and entry == self.cursorSelection:
+              lineEntry = "<h>%s</h>" % lineEntry
+            
+            yOffset = 0 if not self.showingDetails else 8
+            self.addfstr(lineNum + yOffset, xOffset, lineEntry)
+          lineNum += 1
         
-        self.refresh()
-      finally:
-        self.lock.release()
-        self.connectionsLock.release()
+        if isScrollBarVisible:
+          topY = 9 if self.showingDetails else 1
+          bottomEntry = self.scroll + self.maxY - 9 if self.showingDetails else self.scroll + self.maxY - 1
+          uiTools.drawScrollBar(self, topY, self.maxY - 1, self.scroll, bottomEntry, len(self.connections))
+    finally:
+      self.connectionsLock.release()
   
   def getFingerprint(self, ipAddr, port):
     """

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/controller.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -12,7 +12,6 @@
 import time
 import curses
 import socket
-from threading import RLock
 from TorCtl import TorCtl
 from TorCtl import TorUtil
 
@@ -24,7 +23,7 @@
 import descriptorPopup
 import fileDescriptorPopup
 
-import util
+from util import panel, uiTools
 import connResolver
 import bandwidthMonitor
 import cpuMemMonitor
@@ -32,8 +31,6 @@
 
 CONFIRM_QUIT = True
 REFRESH_RATE = 5        # seconds between redrawing screen
-cursesLock = RLock()    # global curses lock (curses isn't thread safe and
-                        # concurrency bugs produce especially sinister glitches)
 MAX_REGEX_FILTERS = 5   # maximum number of previous regex filters that'll be remembered
 
 # enums for message in control label
@@ -50,11 +47,11 @@
 # events needed for panels other than the event log
 REQ_EVENTS = ["BW", "NEWDESC", "NEWCONSENSUS", "CIRC"]
 
-class ControlPanel(util.Panel):
+class ControlPanel(panel.Panel):
   """ Draws single line label for interface controls. """
   
-  def __init__(self, lock, resolver, isBlindMode):
-    util.Panel.__init__(self, lock, 1)
+  def __init__(self, resolver, isBlindMode):
+    panel.Panel.__init__(self, 1)
     self.msgText = CTL_HELP           # message text to be displyed
     self.msgAttr = curses.A_NORMAL    # formatting attributes
     self.page = 1                     # page number currently being displayed
@@ -71,61 +68,56 @@
     self.msgText = msgText
     self.msgAttr = msgAttr
   
-  def redraw(self):
-    if self.win:
-      self.clear()
+  def draw(self):
+    msgText = self.msgText
+    msgAttr = self.msgAttr
+    barTab = 2                # space between msgText and progress bar
+    barWidthMax = 40          # max width to progress bar
+    barWidth = -1             # space between "[ ]" in progress bar (not visible if -1)
+    barProgress = 0           # cells to fill
+    
+    if msgText == CTL_HELP:
+      msgAttr = curses.A_NORMAL
       
-      msgText = self.msgText
-      msgAttr = self.msgAttr
-      barTab = 2                # space between msgText and progress bar
-      barWidthMax = 40          # max width to progress bar
-      barWidth = -1             # space between "[ ]" in progress bar (not visible if -1)
-      barProgress = 0           # cells to fill
+      if self.resolvingCounter != -1:
+        if self.resolver.unresolvedQueue.empty() or self.resolver.isPaused:
+          # done resolving dns batch
+          self.resolvingCounter = -1
+          curses.halfdelay(REFRESH_RATE * 10) # revert to normal refresh rate
+        else:
+          batchSize = self.resolver.totalResolves - self.resolvingCounter
+          entryCount = batchSize - self.resolver.unresolvedQueue.qsize()
+          if batchSize > 0: progress = 100 * entryCount / batchSize
+          else: progress = 0
+          
+          additive = "or l " if self.page == 2 else ""
+          batchSizeDigits = int(math.log10(batchSize)) + 1
+          entryCountLabel = ("%%%ii" % batchSizeDigits) % entryCount
+          #msgText = "Resolving hostnames (%i / %i, %i%%) - press esc %sto cancel" % (entryCount, batchSize, progress, additive)
+          msgText = "Resolving hostnames (press esc %sto cancel) - %s / %i, %2i%%" % (additive, entryCountLabel, batchSize, progress)
+          
+          barWidth = min(barWidthMax, self.maxX - len(msgText) - 3 - barTab)
+          barProgress = barWidth * entryCount / batchSize
       
-      if msgText == CTL_HELP:
-        msgAttr = curses.A_NORMAL
+      if self.resolvingCounter == -1:
+        currentPage = self.page
+        pageCount = len(PAGES)
         
-        if self.resolvingCounter != -1:
-          if self.resolver.unresolvedQueue.empty() or self.resolver.isPaused:
-            # done resolving dns batch
-            self.resolvingCounter = -1
-            curses.halfdelay(REFRESH_RATE * 10) # revert to normal refresh rate
-          else:
-            batchSize = self.resolver.totalResolves - self.resolvingCounter
-            entryCount = batchSize - self.resolver.unresolvedQueue.qsize()
-            if batchSize > 0: progress = 100 * entryCount / batchSize
-            else: progress = 0
-            
-            additive = "or l " if self.page == 2 else ""
-            batchSizeDigits = int(math.log10(batchSize)) + 1
-            entryCountLabel = ("%%%ii" % batchSizeDigits) % entryCount
-            #msgText = "Resolving hostnames (%i / %i, %i%%) - press esc %sto cancel" % (entryCount, batchSize, progress, additive)
-            msgText = "Resolving hostnames (press esc %sto cancel) - %s / %i, %2i%%" % (additive, entryCountLabel, batchSize, progress)
-            
-            barWidth = min(barWidthMax, self.maxX - len(msgText) - 3 - barTab)
-            barProgress = barWidth * entryCount / batchSize
+        if self.isBlindMode:
+          if currentPage >= 2: currentPage -= 1
+          pageCount -= 1
         
-        if self.resolvingCounter == -1:
-          currentPage = self.page
-          pageCount = len(PAGES)
-          
-          if self.isBlindMode:
-            if currentPage >= 2: currentPage -= 1
-            pageCount -= 1
-          
-          msgText = "page %i / %i - q: quit, p: pause, h: page help" % (currentPage, pageCount)
-      elif msgText == CTL_PAUSED:
-        msgText = "Paused"
-        msgAttr = curses.A_STANDOUT
-      
-      self.addstr(0, 0, msgText, msgAttr)
-      if barWidth > -1:
-        xLoc = len(msgText) + barTab
-        self.addstr(0, xLoc, "[", curses.A_BOLD)
-        self.addstr(0, xLoc + 1, " " * barProgress, curses.A_STANDOUT | util.getColor("red"))
-        self.addstr(0, xLoc + barWidth + 1, "]", curses.A_BOLD)
-      
-      self.refresh()
+        msgText = "page %i / %i - q: quit, p: pause, h: page help" % (currentPage, pageCount)
+    elif msgText == CTL_PAUSED:
+      msgText = "Paused"
+      msgAttr = curses.A_STANDOUT
+    
+    self.addstr(0, 0, msgText, msgAttr)
+    if barWidth > -1:
+      xLoc = len(msgText) + barTab
+      self.addstr(0, xLoc, "[", curses.A_BOLD)
+      self.addstr(0, xLoc + 1, " " * barProgress, curses.A_STANDOUT | uiTools.getColor("red"))
+      self.addstr(0, xLoc + barWidth + 1, "]", curses.A_BOLD)
 
 class sighupListener(TorCtl.PostEventListener):
   """
@@ -160,7 +152,7 @@
   selection = initialSelection if initialSelection != -1 else 0
   
   if popup.win:
-    if not popup.lock.acquire(False): return -1
+    if not panel.CURSES_LOCK.acquire(False): return -1
     try:
       curses.cbreak() # wait indefinitely for key presses (no timeout)
       
@@ -168,13 +160,13 @@
       popup.height = len(options) + 2
       
       newWidth = max([len(label) for label in options]) + 9
-      popup.recreate(stdscr, popup.startY, newWidth)
+      popup.recreate(stdscr, newWidth)
       
       key = 0
       while key not in (curses.KEY_ENTER, 10, ord(' ')):
         popup.clear()
         popup.win.box()
-        popup.addstr(0, 0, title, util.LABEL_ATTR)
+        popup.addstr(0, 0, title, uiTools.LABEL_ATTR)
         
         for i in range(len(options)):
           label = options[i]
@@ -191,11 +183,11 @@
       
       # reverts popup dimensions and conn panel label
       popup.height = 9
-      popup.recreate(stdscr, popup.startY, 80)
+      popup.recreate(stdscr, 80)
       
       curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
     finally:
-      cursesLock.release()
+      panel.CURSES_LOCK.release()
   
   return selection
 
@@ -257,7 +249,7 @@
   """
   
   curses.use_default_colors()           # allows things like semi-transparent backgrounds
-  util.initColors()                     # initalizes color pairs for colored text
+  uiTools.initColors()                  # initalizes color pairs for colored text
   curses.halfdelay(REFRESH_RATE * 10)   # uses getch call as timer for REFRESH_RATE seconds
   
   # attempts to make the cursor invisible (not supported in all terminals)
@@ -279,7 +271,7 @@
     try:
       # uses netstat to identify process with open control port (might not
       # work if tor's being run as a different user due to permissions)
-      netstatCall = os.popen("netstat -npl 2> /dev/null | grep 127.0.0.1:%s" % conn.get_option("ControlPort")[0][1])
+      netstatCall = os.popen("netstat -npl 2> /dev/null | grep 127.0.0.1:%s 2> /dev/null" % conn.get_option("ControlPort")[0][1])
       results = netstatCall.readlines()
       
       if len(results) == 1:
@@ -311,18 +303,18 @@
     confLocation = ""
   
   panels = {
-    "header": headerPanel.HeaderPanel(cursesLock, conn, torPid),
-    "popup": util.Panel(cursesLock, 9),
-    "graph": graphPanel.GraphPanel(cursesLock),
-    "log": logPanel.LogMonitor(cursesLock, conn, loggedEvents)}
+    "header": headerPanel.HeaderPanel(conn, torPid),
+    "popup": panel.Panel(9),
+    "graph": graphPanel.GraphPanel(),
+    "log": logPanel.LogMonitor(conn, loggedEvents)}
   
   # starts thread for processing netstat queries
   connResolutionThread = connResolver.ConnResolver(conn, torPid, panels["log"])
   if not isBlindMode: connResolutionThread.start()
   
-  panels["conn"] = connPanel.ConnPanel(cursesLock, conn, connResolutionThread, panels["log"])
-  panels["control"] = ControlPanel(cursesLock, panels["conn"].resolver, isBlindMode)
-  panels["torrc"] = confPanel.ConfPanel(cursesLock, confLocation, conn, panels["log"])
+  panels["conn"] = connPanel.ConnPanel(conn, connResolutionThread, panels["log"])
+  panels["control"] = ControlPanel(panels["conn"].resolver, isBlindMode)
+  panels["torrc"] = confPanel.ConfPanel(confLocation, conn, panels["log"])
   
   # prevents netstat calls by connPanel if not being used
   if isBlindMode: panels["conn"].isDisabled = True
@@ -374,7 +366,7 @@
     # noticeable lag when resizing and didn't have an appreciable effect
     # on system usage
     
-    cursesLock.acquire()
+    panel.CURSES_LOCK.acquire()
     try:
       # if sighup received then reload related information
       if sighupTracker.isReset:
@@ -398,16 +390,16 @@
       # resilient in case of funky changes (such as resizing during popups)
       startY = 0
       for panelKey in PAGE_S[:2]:
-        panels[panelKey].recreate(stdscr, startY)
+        panels[panelKey].recreate(stdscr, -1, startY)
         startY += panels[panelKey].height
       
-      panels["popup"].recreate(stdscr, startY, 80)
+      panels["popup"].recreate(stdscr, 80, startY)
       
       for panelSet in PAGES:
         tmpStartY = startY
         
         for panelKey in panelSet:
-          panels[panelKey].recreate(stdscr, tmpStartY)
+          panels[panelKey].recreate(stdscr, -1, tmpStartY)
           tmpStartY += panels[panelKey].height
       
       # if it's been at least ten seconds since the last BW event Tor's probably done
@@ -422,10 +414,18 @@
       panels["conn"].reset()
       
       # I haven't the foggiest why, but doesn't work if redrawn out of order...
+      
+      # TODO: temporary hack to prevent popup redraw until a valid replacement is implemented (ick!)
+      tmpSubwin = panels["popup"].win
+      panels["popup"].win = None
+      
       for panelKey in (PAGE_S + PAGES[page]): panels[panelKey].redraw()
+      
+      panels["popup"].win = tmpSubwin
+      
       stdscr.refresh()
     finally:
-      cursesLock.release()
+      panel.CURSES_LOCK.release()
     
     # wait for user keyboard input until timeout (unless an override was set)
     if overrideKey:
@@ -439,7 +439,7 @@
       
       # provides prompt to confirm that arm should exit
       if CONFIRM_QUIT:
-        cursesLock.acquire()
+        panel.CURSES_LOCK.acquire()
         try:
           setPauseState(panels, isPaused, page, True)
           
@@ -455,7 +455,7 @@
           panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
           setPauseState(panels, isPaused, page)
         finally:
-          cursesLock.release()
+          panel.CURSES_LOCK.release()
       
       if quitConfirmed:
         # quits arm
@@ -490,16 +490,16 @@
       panels["control"].refresh()
     elif key == ord('p') or key == ord('P'):
       # toggles update freezing
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         isPaused = not isPaused
         setPauseState(panels, isPaused, page)
         panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif key == ord('h') or key == ord('H'):
       # displays popup for current page's controls
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         
@@ -507,7 +507,7 @@
         popup = panels["popup"]
         popup.clear()
         popup.win.box()
-        popup.addstr(0, 0, "Page %i Commands:" % (page + 1), util.LABEL_ATTR)
+        popup.addstr(0, 0, "Page %i Commands:" % (page + 1), uiTools.LABEL_ATTR)
         
         pageOverrideKeys = ()
         
@@ -566,7 +566,7 @@
         
         setPauseState(panels, isPaused, page)
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 0 and (key == ord('s') or key == ord('S')):
       # provides menu to pick stats to be graphed
       #options = ["None"] + [label for label in panels["graph"].stats.keys()]
@@ -626,7 +626,7 @@
       panels["graph"].bounds = (panels["graph"].bounds + 1) % 2
     elif page == 0 and key in (ord('d'), ord('D')):
       # provides popup with file descriptors
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -636,10 +636,10 @@
         setPauseState(panels, isPaused, page)
         curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 0 and (key == ord('e') or key == ord('E')):
       # allow user to enter new types of events to log - unchanged if left blank
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         
@@ -655,11 +655,11 @@
         # lists event types
         popup = panels["popup"]
         popup.height = 10
-        popup.recreate(stdscr, popup.startY, 80)
+        popup.recreate(stdscr, 80)
         
         popup.clear()
         popup.win.box()
-        popup.addstr(0, 0, "Event Types:", util.LABEL_ATTR)
+        popup.addstr(0, 0, "Event Types:", uiTools.LABEL_ATTR)
         lineNum = 1
         for line in logPanel.EVENT_LISTING.split("\n"):
           line = line[6:]
@@ -690,12 +690,12 @@
         
         # reverts popup dimensions
         popup.height = 9
-        popup.recreate(stdscr, popup.startY, 80)
+        popup.recreate(stdscr, 80)
         
         panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
         setPauseState(panels, isPaused, page)
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 0 and (key == ord('f') or key == ord('F')):
       # provides menu to pick previous regular expression filters or to add a new one
       # for syntax see: http://docs.python.org/library/re.html#regular-expression-syntax
@@ -715,7 +715,7 @@
         panels["log"].regexFilter = None
       elif selection == len(options) - 1:
         # selected 'New...' option - prompt user to input regular expression
-        cursesLock.acquire()
+        panel.CURSES_LOCK.acquire()
         try:
           # provides prompt
           panels["control"].setMsg("Regular expression: ")
@@ -746,7 +746,7 @@
               time.sleep(2)
           panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
         finally:
-          cursesLock.release()
+          panel.CURSES_LOCK.release()
       elif selection != -1:
         try:
           panels["log"].regexFilter = re.compile(regexFilters[selection - 1])
@@ -772,7 +772,7 @@
       panels["conn"].sortConnections()
     elif page == 1 and panels["conn"].isCursorEnabled and key in (curses.KEY_ENTER, 10, ord(' ')):
       # provides details on selected connection
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         popup = panels["popup"]
@@ -792,12 +792,12 @@
         while key not in (curses.KEY_ENTER, 10, ord(' ')):
           popup.clear()
           popup.win.box()
-          popup.addstr(0, 0, "Connection Details:", util.LABEL_ATTR)
+          popup.addstr(0, 0, "Connection Details:", uiTools.LABEL_ATTR)
           
           selection = panels["conn"].cursorSelection
           if not selection or not panels["conn"].connections: break
           selectionColor = connPanel.TYPE_COLORS[selection[connPanel.CONN_TYPE]]
-          format = util.getColor(selectionColor) | curses.A_BOLD
+          format = uiTools.getColor(selectionColor) | curses.A_BOLD
           
           selectedIp = selection[connPanel.CONN_F_IP]
           selectedPort = selection[connPanel.CONN_F_PORT]
@@ -911,10 +911,10 @@
         setPauseState(panels, isPaused, page)
         curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 1 and panels["conn"].isCursorEnabled and key in (ord('d'), ord('D')):
       # presents popup for raw consensus data
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -927,7 +927,7 @@
         curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
         panels["conn"].showLabel = True
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 1 and (key == ord('l') or key == ord('L')):
       # provides menu to pick identification info listed for connections
       optionTypes = [connPanel.LIST_IP, connPanel.LIST_HOSTNAME, connPanel.LIST_FINGERPRINT, connPanel.LIST_NICKNAME]
@@ -963,7 +963,7 @@
         panels["conn"].sortConnections()
     elif page == 1 and (key == ord('s') or key == ord('S')):
       # set ordering for connection listing
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -986,7 +986,7 @@
         while len(selections) < 3:
           popup.clear()
           popup.win.box()
-          popup.addstr(0, 0, "Connection Ordering:", util.LABEL_ATTR)
+          popup.addstr(0, 0, "Connection Ordering:", uiTools.LABEL_ATTR)
           popup.addfstr(1, 2, prevOrdering)
           
           # provides new ordering
@@ -1027,7 +1027,7 @@
         setPauseState(panels, isPaused, page)
         curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 1 and (key == ord('c') or key == ord('C')):
       # displays popup with client circuits
       clientCircuits = None
@@ -1039,7 +1039,7 @@
       if clientCircuits:
         for clientEntry in clientCircuits: maxEntryLength = max(len(clientEntry), maxEntryLength)
       
-      cursesLock.acquire()
+      panel.CURSES_LOCK.acquire()
       try:
         setPauseState(panels, isPaused, page, True)
         
@@ -1048,12 +1048,12 @@
         popup._resetBounds()
         if clientCircuits and maxEntryLength + 4 > popup.maxX:
           popup.height = max(popup.height, len(clientCircuits) + 3)
-          popup.recreate(stdscr, popup.startY, maxEntryLength + 4)
+          popup.recreate(stdscr, maxEntryLength + 4)
         
         # lists commands
         popup.clear()
         popup.win.box()
-        popup.addstr(0, 0, "Client Circuits:", util.LABEL_ATTR)
+        popup.addstr(0, 0, "Client Circuits:", uiTools.LABEL_ATTR)
         
         if clientCircuits == None:
           popup.addstr(1, 2, "Unable to retireve current circuits")
@@ -1074,11 +1074,11 @@
         
         # reverts popup dimensions
         popup.height = 9
-        popup.recreate(stdscr, popup.startY, 80)
+        popup.recreate(stdscr, 80)
         
         setPauseState(panels, isPaused, page)
       finally:
-        cursesLock.release()
+        panel.CURSES_LOCK.release()
     elif page == 0:
       panels["log"].handleKey(key)
     elif page == 1:

Modified: arm/trunk/interface/cpuMemMonitor.py
===================================================================
--- arm/trunk/interface/cpuMemMonitor.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/cpuMemMonitor.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -6,7 +6,7 @@
 import time
 from TorCtl import TorCtl
 
-import util
+from util import uiTools
 import graphPanel
 
 class CpuMemMonitor(graphPanel.GraphStats, TorCtl.PostEventListener):
@@ -29,7 +29,7 @@
       self._processEvent(float(self.headerPanel.vals["%cpu"]), float(self.headerPanel.vals["rss"]) / 1024.0)
     else:
       # cached results stale - requery ps
-      psCall = os.popen('ps -p %s -o %s  2> /dev/null' % (self.headerPanel.vals["pid"], "%cpu,rss"))
+      psCall = os.popen('ps -p %s -o %s 2> /dev/null' % (self.headerPanel.vals["pid"], "%cpu,rss"))
       try:
         sampling = psCall.read().strip().split()[2:]
         psCall.close()
@@ -50,5 +50,5 @@
   def getHeaderLabel(self, width, isPrimary):
     avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max(1, self.tick)
     if isPrimary: return "CPU (%s%%, avg: %0.1f%%):" % (self.lastPrimary, avg)
-    else: return "Memory (%s, avg: %s):" % (util.getSizeLabel(self.lastSecondary * 1048576, 1), util.getSizeLabel(avg * 1048576, 1))
+    else: return "Memory (%s, avg: %s):" % (uiTools.getSizeLabel(self.lastSecondary * 1048576, 1), uiTools.getSizeLabel(avg * 1048576, 1))
 

Modified: arm/trunk/interface/descriptorPopup.py
===================================================================
--- arm/trunk/interface/descriptorPopup.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/descriptorPopup.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -8,7 +8,7 @@
 from TorCtl import TorCtl
 
 import connPanel
-import util
+from util import panel, uiTools
 
 # field keywords used to identify areas for coloring
 LINE_NUM_COLOR = "yellow"
@@ -78,7 +78,7 @@
   properties = PopupProperties(conn)
   isVisible = True
   
-  if not popup.lock.acquire(False): return
+  if not panel.CURSES_LOCK.acquire(False): return
   try:
     while isVisible:
       selection = connectionPanel.cursorSelection
@@ -100,7 +100,7 @@
       
       popup._resetBounds()
       popup.height = min(len(properties.text) + height + 2, connectionPanel.maxY)
-      popup.recreate(stdscr, popup.startY, width)
+      popup.recreate(stdscr, width)
       
       while isVisible:
         draw(popup, properties)
@@ -116,9 +116,9 @@
         else: properties.handleKey(key, popup.height - 2)
     
     popup.height = 9
-    popup.recreate(stdscr, popup.startY, 80)
+    popup.recreate(stdscr, 80)
   finally:
-    popup.lock.release()
+    panel.CURSES_LOCK.release()
 
 def draw(popup, properties):
   popup.clear()
@@ -126,8 +126,8 @@
   xOffset = 2
   
   if properties.text:
-    if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, util.LABEL_ATTR)
-    else: popup.addstr(0, 0, "Consensus Descriptor:", util.LABEL_ATTR)
+    if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, uiTools.LABEL_ATTR)
+    else: popup.addstr(0, 0, "Consensus Descriptor:", uiTools.LABEL_ATTR)
     
     isEncryption = False          # true if line is part of an encryption block
     
@@ -145,31 +145,31 @@
       
       numOffset = 0     # offset for line numbering
       if properties.showLineNum:
-        popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | util.getColor(LINE_NUM_COLOR))
+        popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR))
         numOffset = numFieldWidth + 1
       
       if lineText:
         keyword = lineText.split()[0]   # first word of line
         remainder = lineText[len(keyword):]
-        keywordFormat = curses.A_BOLD | util.getColor(properties.entryColor)
-        remainderFormat = util.getColor(properties.entryColor)
+        keywordFormat = curses.A_BOLD | uiTools.getColor(properties.entryColor)
+        remainderFormat = uiTools.getColor(properties.entryColor)
         
         if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]):
           keyword, remainder = lineText, ""
-          keywordFormat = curses.A_BOLD | util.getColor(HEADER_COLOR)
+          keywordFormat = curses.A_BOLD | uiTools.getColor(HEADER_COLOR)
         if lineText == UNRESOLVED_MSG or lineText == ERROR_MSG:
           keyword, remainder = lineText, ""
         if lineText in SIG_START_KEYS:
           keyword, remainder = lineText, ""
           isEncryption = True
-          keywordFormat = curses.A_BOLD | util.getColor(SIG_COLOR)
+          keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR)
         elif lineText in SIG_END_KEYS:
           keyword, remainder = lineText, ""
           isEncryption = False
-          keywordFormat = curses.A_BOLD | util.getColor(SIG_COLOR)
+          keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR)
         elif isEncryption:
           keyword, remainder = lineText, ""
-          keywordFormat = util.getColor(SIG_COLOR)
+          keywordFormat = uiTools.getColor(SIG_COLOR)
         
         lineNum, xLoc = popup.addstr_wrap(lineNum, 0, keyword, keywordFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1)
         lineNum, xLoc = popup.addstr_wrap(lineNum, xLoc, remainder, remainderFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1)

Modified: arm/trunk/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/interface/fileDescriptorPopup.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/fileDescriptorPopup.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
 import os
 import curses
 
-import util
+from util import panel, uiTools
 
 class PopupProperties:
   """
@@ -27,7 +27,7 @@
       
       # retrieves list of open files, options are:
       # n = no dns lookups, p = by pid, -F = show fields (L = login name, n = opened files)
-      lsofCall = os.popen3("lsof -np %s -F Ln" % torPid)
+      lsofCall = os.popen3("lsof -np %s -F Ln 2> /dev/null" % torPid)
       results = lsofCall[1].readlines()
       errResults = lsofCall[2].readlines()
       
@@ -103,7 +103,7 @@
   
   properties = PopupProperties(torPid)
   
-  if not popup.lock.acquire(False): return
+  if not panel.CURSES_LOCK.acquire(False): return
   try:
     if properties.errorMsg:
       popupWidth = len(properties.errorMsg) + 4
@@ -118,7 +118,7 @@
     
     popup._resetBounds()
     popup.height = popupHeight
-    popup.recreate(stdscr, popup.startY, popupWidth)
+    popup.recreate(stdscr, popupWidth)
     
     while True:
       draw(popup, properties)
@@ -132,9 +132,9 @@
         break
     
     popup.height = 9
-    popup.recreate(stdscr, popup.startY, 80)
+    popup.recreate(stdscr, 80)
   finally:
-    popup.lock.release()
+    panel.CURSES_LOCK.release()
 
 def draw(popup, properties):
   popup.clear()
@@ -144,7 +144,7 @@
   popup.addstr(0, 0, "Open File Descriptors:", curses.A_STANDOUT)
   
   if properties.errorMsg:
-    popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | util.getColor("red"))
+    popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | uiTools.getColor("red"))
   else:
     # text with file descriptor count and limit
     fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
@@ -155,14 +155,14 @@
     elif fdCountPer >= 50: statsColor = "yellow"
     
     countMsg = "%i / %i (%i%%)" % (fdCount, properties.fdLimit, fdCountPer)
-    popup.addstr(1, 2, countMsg, curses.A_BOLD | util.getColor(statsColor))
+    popup.addstr(1, 2, countMsg, curses.A_BOLD | uiTools.getColor(statsColor))
     
     # provides a progress bar reflecting the stats
     barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar
     barProgress = barWidth * fdCountPer / 100 # filled cells
     if fdCount > 0: barProgress = max(1, barProgress) # ensures one cell is filled unless really zero
     popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD)
-    popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | util.getColor(statsColor))
+    popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | uiTools.getColor(statsColor))
     popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD)
     
     popup.win.hline(2, 1, curses.ACS_HLINE, popup.maxX - 2)
@@ -181,7 +181,7 @@
         line = properties.fdConn[entryNum - len(properties.fdFile) - len(properties.fdMisc)]
         color = "blue"
       
-      popup.addstr(lineNum, 2, line, curses.A_BOLD | util.getColor(color))
+      popup.addstr(lineNum, 2, line, curses.A_BOLD | uiTools.getColor(color))
       lineNum += 1
       entryNum += 1
   

Modified: arm/trunk/interface/graphPanel.py
===================================================================
--- arm/trunk/interface/graphPanel.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/graphPanel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
 import copy
 import curses
 
-import util
+from util import panel, uiTools
 
 MAX_GRAPH_COL = 150  # max columns of data in graph
 WIDE_LABELING_GRAPH_COL = 50  # minimum graph columns to use wide spacing for x-axis labels
@@ -90,9 +90,9 @@
     
     return ""
   
-  def redraw(self, panel):
+  def draw(self, panel):
     """
-    Allows for any custom redrawing monitor wishes to append.
+    Allows for any custom drawing monitor wishes to append.
     """
     
     pass
@@ -157,14 +157,14 @@
       
       if self.graphPanel: self.graphPanel.redraw()
 
-class GraphPanel(util.Panel):
+class GraphPanel(panel.Panel):
   """
   Panel displaying a graph, drawing statistics from custom GraphStats
   implementations.
   """
   
-  def __init__(self, lock):
-    util.Panel.__init__(self, lock, 0) # height is overwritten with current module
+  def __init__(self):
+    panel.Panel.__init__(self, 0) # height is overwritten with current module
     self.updateInterval = DEFAULT_UPDATE_INTERVAL
     self.isPaused = False
     self.showLabel = True         # shows top label if true, hides otherwise
@@ -172,81 +172,74 @@
     self.currentDisplay = None    # label of the stats currently being displayed
     self.stats = {}               # available stats (mappings of label -> instance)
   
-  def redraw(self):
+  def draw(self):
     """ Redraws graph panel """
-    if self.win:
-      if not self.lock.acquire(False): return
-      try:
-        self.clear()
-        graphCol = min((self.maxX - 10) / 2, MAX_GRAPH_COL)
+    
+    graphCol = min((self.maxX - 10) / 2, MAX_GRAPH_COL)
+    
+    if self.currentDisplay:
+      param = self.stats[self.currentDisplay]
+      primaryColor = uiTools.getColor(param.primaryColor)
+      secondaryColor = uiTools.getColor(param.secondaryColor)
+      
+      if self.showLabel: self.addstr(0, 0, param.getTitle(self.maxX), uiTools.LABEL_ATTR)
+      
+      # top labels
+      left, right = param.getHeaderLabel(self.maxX / 2, True), param.getHeaderLabel(self.maxX / 2, False)
+      if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
+      if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
+      
+      # determines max value on the graph
+      primaryBound, secondaryBound = -1, -1
+      
+      if self.bounds == BOUNDS_MAX:
+        primaryBound = param.maxPrimary[self.updateInterval]
+        secondaryBound = param.maxSecondary[self.updateInterval]
+      elif self.bounds == BOUNDS_TIGHT:
+        for value in param.primaryCounts[self.updateInterval][1:graphCol + 1]: primaryBound = max(value, primaryBound)
+        for value in param.secondaryCounts[self.updateInterval][1:graphCol + 1]: secondaryBound = max(value, secondaryBound)
+      
+      # displays bound
+      self.addstr(2, 0, "%4s" % str(int(primaryBound)), primaryColor)
+      self.addstr(7, 0, "   0", primaryColor)
+      
+      self.addstr(2, graphCol + 5, "%4s" % str(int(secondaryBound)), secondaryColor)
+      self.addstr(7, graphCol + 5, "   0", secondaryColor)
+      
+      # creates bar graph of bandwidth usage over time
+      for col in range(graphCol):
+        colHeight = min(5, 5 * param.primaryCounts[self.updateInterval][col + 1] / max(1, primaryBound))
+        for row in range(colHeight): self.addstr(7 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
+      
+      for col in range(graphCol):
+        colHeight = min(5, 5 * param.secondaryCounts[self.updateInterval][col + 1] / max(1, secondaryBound))
+        for row in range(colHeight): self.addstr(7 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
+      
+      # bottom labeling of x-axis
+      intervalSec = 1
+      for (label, timescale) in UPDATE_INTERVALS:
+        if label == self.updateInterval: intervalSec = timescale
+      
+      intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
+      unitsLabel, decimalPrecision = None, 0
+      for i in range(1, (graphCol + intervalSpacing - 4) / intervalSpacing):
+        loc = i * intervalSpacing
+        timeLabel = uiTools.getTimeLabel(loc * intervalSec, decimalPrecision)
         
-        if self.currentDisplay:
-          param = self.stats[self.currentDisplay]
-          primaryColor = util.getColor(param.primaryColor)
-          secondaryColor = util.getColor(param.secondaryColor)
-          
-          if self.showLabel: self.addstr(0, 0, param.getTitle(self.maxX), util.LABEL_ATTR)
-          
-          # top labels
-          left, right = param.getHeaderLabel(self.maxX / 2, True), param.getHeaderLabel(self.maxX / 2, False)
-          if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
-          if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
-          
-          # determines max value on the graph
-          primaryBound, secondaryBound = -1, -1
-          
-          if self.bounds == BOUNDS_MAX:
-            primaryBound = param.maxPrimary[self.updateInterval]
-            secondaryBound = param.maxSecondary[self.updateInterval]
-          elif self.bounds == BOUNDS_TIGHT:
-            for value in param.primaryCounts[self.updateInterval][1:graphCol + 1]: primaryBound = max(value, primaryBound)
-            for value in param.secondaryCounts[self.updateInterval][1:graphCol + 1]: secondaryBound = max(value, secondaryBound)
-          
-          # displays bound
-          self.addstr(2, 0, "%4s" % str(int(primaryBound)), primaryColor)
-          self.addstr(7, 0, "   0", primaryColor)
-          
-          self.addstr(2, graphCol + 5, "%4s" % str(int(secondaryBound)), secondaryColor)
-          self.addstr(7, graphCol + 5, "   0", secondaryColor)
-          
-          # creates bar graph of bandwidth usage over time
-          for col in range(graphCol):
-            colHeight = min(5, 5 * param.primaryCounts[self.updateInterval][col + 1] / max(1, primaryBound))
-            for row in range(colHeight): self.addstr(7 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
-          
-          for col in range(graphCol):
-            colHeight = min(5, 5 * param.secondaryCounts[self.updateInterval][col + 1] / max(1, secondaryBound))
-            for row in range(colHeight): self.addstr(7 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
-          
-          # bottom labeling of x-axis
-          intervalSec = 1
-          for (label, timescale) in UPDATE_INTERVALS:
-            if label == self.updateInterval: intervalSec = timescale
-          
-          intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
-          unitsLabel, decimalPrecision = None, 0
-          for i in range(1, (graphCol + intervalSpacing - 4) / intervalSpacing):
-            loc = i * intervalSpacing
-            timeLabel = util.getTimeLabel(loc * intervalSec, decimalPrecision)
-            
-            if not unitsLabel: unitsLabel = timeLabel[-1]
-            elif unitsLabel != timeLabel[-1]:
-              # upped scale so also up precision of future measurements
-              unitsLabel = timeLabel[-1]
-              decimalPrecision += 1
-            else:
-              # if constrained on space then strips labeling since already provided
-              timeLabel = timeLabel[:-1]
-            
-            self.addstr(8, 4 + loc, timeLabel, primaryColor)
-            self.addstr(8, graphCol + 10 + loc, timeLabel, secondaryColor)
-            
-          # allows for finishing touches by monitor
-          param.redraw(self)
-          
-        self.refresh()
-      finally:
-        self.lock.release()
+        if not unitsLabel: unitsLabel = timeLabel[-1]
+        elif unitsLabel != timeLabel[-1]:
+          # upped scale so also up precision of future measurements
+          unitsLabel = timeLabel[-1]
+          decimalPrecision += 1
+        else:
+          # if constrained on space then strips labeling since already provided
+          timeLabel = timeLabel[:-1]
+        
+        self.addstr(8, 4 + loc, timeLabel, primaryColor)
+        self.addstr(8, graphCol + 10 + loc, timeLabel, secondaryColor)
+        
+      # allows for finishing touches by monitor
+      param.draw(self)
   
   def addStats(self, label, stats):
     """

Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/headerPanel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -7,7 +7,7 @@
 import socket
 from TorCtl import TorCtl
 
-import util
+from util import panel, uiTools
 
 # minimum width for which panel attempts to double up contents (two columns to
 # better use screen real estate)
@@ -21,7 +21,7 @@
 VERSION_STATUS_COLORS = {"new": "blue",     "new in series": "blue",    "recommended": "green",
                          "old": "red",      "obsolete": "red",          "unrecommended": "red"}
 
-class HeaderPanel(util.Panel):
+class HeaderPanel(panel.Panel):
   """
   Draws top area containing static information.
   
@@ -37,8 +37,8 @@
   fingerprint: BDAD31F6F318E0413833E8EBDA956F76E4D66788
   """
   
-  def __init__(self, lock, conn, torPid):
-    util.Panel.__init__(self, lock, 6)
+  def __init__(self, conn, torPid):
+    panel.Panel.__init__(self, 6)
     self.vals = {"pid": torPid}     # mapping of information to be presented
     self.conn = conn                # Tor control port connection
     self.isPaused = False
@@ -47,119 +47,112 @@
     self.lastUpdate = -1            # time last stats was retrived
     self._updateParams()
   
-  def recreate(self, stdscr, startY, maxX=-1):
+  def recreate(self, stdscr, maxX=-1, newTop=None):
     # might need to recreate twice so we have a window to get width
-    if not self.win: util.Panel.recreate(self, stdscr, startY, maxX)
+    if not self.win: panel.Panel.recreate(self, stdscr, maxX, newTop)
     
     self._resetBounds()
     self.isWide = self.maxX >= MIN_DUAL_ROW_WIDTH
     self.rightParamX = max(self.maxX / 2, 75) if self.isWide else 0
     self.height = 4 if self.isWide else 6
     
-    util.Panel.recreate(self, stdscr, startY, maxX)
+    panel.Panel.recreate(self, stdscr, maxX, newTop)
   
-  def redraw(self):
-    if self.win:
-      if not self.lock.acquire(False): return
-      try:
-        if not self.isPaused: self._updateParams()
-        
-        # extra erase/refresh is needed to avoid internal caching screwing up and
-        # refusing to redisplay content in the case of graphical glitches - probably
-        # an obscure curses bug...
-        self.win.erase()
-        self.win.refresh()
-        
-        self.clear()
-        
-        # Line 1 (system and tor version information)
-        systemNameLabel = "arm - %s " % self.vals["sys-name"]
-        systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
-        
-        # wraps systemVersionLabel in parentheses and truncates if too long
-        versionLabelMaxWidth = 40 - len(systemNameLabel)
-        if len(systemNameLabel) > 40:
-          # we only have room for the system name label
-          systemNameLabel = systemNameLabel[:39] + "..."
-          systemVersionLabel = ""
-        elif len(systemVersionLabel) > versionLabelMaxWidth:
-          # not enough room to show full version
-          systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
-        else:
-          # enough room for everything
-          systemVersionLabel = "(%s)" % systemVersionLabel
-        
-        self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
-        
-        versionStatus = self.vals["status/version/current"]
-        versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
-        
-        # truncates torVersionLabel if too long
-        torVersionLabel = self.vals["version"]
-        versionLabelMaxWidth =  (self.rightParamX if self.isWide else self.maxX) - 51 - len(versionStatus)
-        if len(torVersionLabel) > versionLabelMaxWidth:
-          torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
-        
-        self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
-        
-        # Line 2 (authentication label red if open, green if credentials required)
-        dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
-        
-        if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
-        elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
-        else: controlPortAuthLabel = "open"
-        controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
-        
-        labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
-        self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
-        
-        # Line 3 (system usage info) - line 1 right if wide
-        y, x = (0, self.rightParamX) if self.isWide else (2, 0)
-        self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
-        self.addstr(y, x + 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
-        self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
-        self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
-        
-        # Line 4 (fingerprint) - line 2 right if wide
-        y, x = (1, self.rightParamX) if self.isWide else (3, 0)
-        self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
-        
-        # Line 5 (flags) - line 3 left if wide
-        flagLine = "flags: "
-        for flag in self.vals["flags"]:
-          flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
-          flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
-        
-        if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
-        self.addfstr(2 if self.isWide else 4, 0, flagLine)
-        
-        # Line 3 right (exit policy) - only present if wide
-        if self.isWide:
-          exitPolicy = self.vals["ExitPolicy"]
-          
-          # adds note when default exit policy is appended
-          if exitPolicy == None: exitPolicy = "<default>"
-          elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
-            exitPolicy += ", <default>"
-          
-          policies = exitPolicy.split(", ")
-          
-          # color codes accepts to be green, rejects to be red, and default marker to be cyan
-          isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
-          for i in range(len(policies)):
-            policy = policies[i].strip()
-            displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
-            if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
-            elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
-            elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
-            policies[i] = policy
-          exitPolicy = ", ".join(policies)
-          
-          self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
-        
-        self.refresh()
-      finally:
-        self.lock.release()
+  def draw(self):
+    if not self.isPaused: self._updateParams()
+    
+    # extra erase/refresh is needed to avoid internal caching screwing up and
+    # refusing to redisplay content in the case of graphical glitches - probably
+    # an obscure curses bug...
+    self.win.erase()
+    self.win.refresh()
+    
+    self.clear()
+    
+    # Line 1 (system and tor version information)
+    systemNameLabel = "arm - %s " % self.vals["sys-name"]
+    systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
+    
+    # wraps systemVersionLabel in parentheses and truncates if too long
+    versionLabelMaxWidth = 40 - len(systemNameLabel)
+    if len(systemNameLabel) > 40:
+      # we only have room for the system name label
+      systemNameLabel = systemNameLabel[:39] + "..."
+      systemVersionLabel = ""
+    elif len(systemVersionLabel) > versionLabelMaxWidth:
+      # not enough room to show full version
+      systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
+    else:
+      # enough room for everything
+      systemVersionLabel = "(%s)" % systemVersionLabel
+    
+    self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
+    
+    versionStatus = self.vals["status/version/current"]
+    versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
+    
+    # truncates torVersionLabel if too long
+    torVersionLabel = self.vals["version"]
+    versionLabelMaxWidth =  (self.rightParamX if self.isWide else self.maxX) - 51 - len(versionStatus)
+    if len(torVersionLabel) > versionLabelMaxWidth:
+      torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
+    
+    self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
+    
+    # Line 2 (authentication label red if open, green if credentials required)
+    dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
+    
+    if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
+    elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
+    else: controlPortAuthLabel = "open"
+    controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+    
+    labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
+    self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
+    
+    # Line 3 (system usage info) - line 1 right if wide
+    y, x = (0, self.rightParamX) if self.isWide else (2, 0)
+    self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
+    self.addstr(y, x + 13, "mem: %s (%s%%)" % (uiTools.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
+    self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
+    self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
+    
+    # Line 4 (fingerprint) - line 2 right if wide
+    y, x = (1, self.rightParamX) if self.isWide else (3, 0)
+    self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
+    
+    # Line 5 (flags) - line 3 left if wide
+    flagLine = "flags: "
+    for flag in self.vals["flags"]:
+      flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
+      flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
+    
+    if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
+    self.addfstr(2 if self.isWide else 4, 0, flagLine)
+    
+    # Line 3 right (exit policy) - only present if wide
+    if self.isWide:
+      exitPolicy = self.vals["ExitPolicy"]
+      
+      # adds note when default exit policy is appended
+      if exitPolicy == None: exitPolicy = "<default>"
+      elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
+        exitPolicy += ", <default>"
+      
+      policies = exitPolicy.split(", ")
+      
+      # color codes accepts to be green, rejects to be red, and default marker to be cyan
+      isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
+      for i in range(len(policies)):
+        policy = policies[i].strip()
+        displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
+        if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
+        elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
+        elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
+        policies[i] = policy
+      exitPolicy = ", ".join(policies)
+      
+      self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
   
   def setPaused(self, isPause):
     """
@@ -222,6 +215,18 @@
         # Tor shut down or crashed - keep last known values
         if not self.vals[param]: self.vals[param] = "Unknown"
     
+    # if ORListenAddress is set overwrites 'address' (and possibly ORPort)
+    try:
+      listenAddr = self.conn.get_option("ORListenAddress")[0][1]
+      if listenAddr:
+        if ":" in listenAddr:
+          # both ip and port overwritten
+          self.vals["address"] = listenAddr[:listenAddr.find(":")]
+          self.vals["ORPort"] = listenAddr[listenAddr.find(":") + 1:]
+        else:
+          self.vals["address"] = listenAddr
+    except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
+    
     # flags held by relay
     self.vals["flags"] = []
     if self.vals["fingerprint"] != "Unknown":

Modified: arm/trunk/interface/logPanel.py
===================================================================
--- arm/trunk/interface/logPanel.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/logPanel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -8,7 +8,7 @@
 from curses.ascii import isprint
 from TorCtl import TorCtl
 
-import util
+from util import panel, uiTools
 
 PRE_POPULATE_LOG = True               # attempts to retrieve events from log file if available
 
@@ -84,14 +84,14 @@
   if invalidFlags: raise ValueError(invalidFlags)
   else: return expandedEvents
 
-class LogMonitor(TorCtl.PostEventListener, util.Panel):
+class LogMonitor(TorCtl.PostEventListener, panel.Panel):
   """
   Tor event listener, noting messages, the time, and their type in a panel.
   """
   
-  def __init__(self, lock, conn, loggedEvents):
+  def __init__(self, conn, loggedEvents):
     TorCtl.PostEventListener.__init__(self)
-    util.Panel.__init__(self, lock, -1)
+    panel.Panel.__init__(self, -1)
     self.scroll = 0
     self.msgLog = []                      # tuples of (logText, color)
     self.isPaused = False
@@ -287,78 +287,70 @@
       if len(self.msgLog) > MAX_LOG_ENTRIES: del self.msgLog[MAX_LOG_ENTRIES:]
       self.redraw()
   
-  def redraw(self):
+  def draw(self):
     """
     Redraws message log. Entries stretch to use available space and may
     contain up to two lines. Starts with newest entries.
     """
     
-    if self.win:
-      if not self.lock.acquire(False): return
-      try:
-        self.clear()
-        
-        isScrollBarVisible = self.getLogDisplayLength() > self.maxY - 1
-        xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
-        
-        # draws label - uses ellipsis if too long, for instance:
-        # Events (DEBUG, INFO, NOTICE, WARN...):
-        eventsLabel = "Events"
-        
-        # separates tor and arm runlevels (might be able to show as range)
-        eventsList = list(self.loggedEvents)
-        torRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, ""))
-        armRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, "ARM_"))
-        
-        if armRunlevelLabel: eventsList = ["ARM " + armRunlevelLabel] + eventsList
-        if torRunlevelLabel: eventsList = [torRunlevelLabel] + eventsList
-        
-        eventsListing = ", ".join(eventsList)
-        filterLabel = "" if not self.regexFilter else " - filter: %s" % self.regexFilter.pattern
-        
-        firstLabelLen = eventsListing.find(", ")
-        if firstLabelLen == -1: firstLabelLen = len(eventsListing)
-        else: firstLabelLen += 3
-        
-        if self.maxX > 10 + firstLabelLen:
-          eventsLabel += " ("
-          
-          if len(eventsListing) > self.maxX - 11:
-            labelBreak = eventsListing[:self.maxX - 12].rfind(", ")
-            eventsLabel += "%s..." % eventsListing[:labelBreak]
-          elif len(eventsListing) + len(filterLabel) > self.maxX - 11:
-            eventsLabel += eventsListing
-          else: eventsLabel += eventsListing + filterLabel
-          eventsLabel += ")"
-        eventsLabel += ":"
-        
-        self.addstr(0, 0, eventsLabel, util.LABEL_ATTR)
-        
-        # log entries
-        maxLoc = self.getLogDisplayLength() - self.maxY + 1
-        self.scroll = max(0, min(self.scroll, maxLoc))
-        lineCount = 1 - self.scroll
-        
-        for (line, color) in self.msgLog:
-          if self.regexFilter and not self.regexFilter.search(line):
-            continue  # filter doesn't match log message - skip
-          
-          # splits over too lines if too long
-          if len(line) < self.maxX:
-            if lineCount >= 1: self.addstr(lineCount, xOffset, line, util.getColor(color))
-            lineCount += 1
-          else:
-            (line1, line2) = splitLine(line, self.maxX - xOffset)
-            if lineCount >= 1: self.addstr(lineCount, xOffset, line1, util.getColor(color))
-            if lineCount >= 0: self.addstr(lineCount + 1, xOffset, line2, util.getColor(color))
-            lineCount += 2
-          
-          if lineCount >= self.maxY: break # further log messages wouldn't fit
-        
-        if isScrollBarVisible: util.drawScrollBar(self, 1, self.maxY - 1, self.scroll, self.scroll + self.maxY - 1, self.getLogDisplayLength())
-        self.refresh()
-      finally:
-        self.lock.release()
+    isScrollBarVisible = self.getLogDisplayLength() > self.maxY - 1
+    xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
+    
+    # draws label - uses ellipsis if too long, for instance:
+    # Events (DEBUG, INFO, NOTICE, WARN...):
+    eventsLabel = "Events"
+    
+    # separates tor and arm runlevels (might be able to show as range)
+    eventsList = list(self.loggedEvents)
+    torRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, ""))
+    armRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, "ARM_"))
+    
+    if armRunlevelLabel: eventsList = ["ARM " + armRunlevelLabel] + eventsList
+    if torRunlevelLabel: eventsList = [torRunlevelLabel] + eventsList
+    
+    eventsListing = ", ".join(eventsList)
+    filterLabel = "" if not self.regexFilter else " - filter: %s" % self.regexFilter.pattern
+    
+    firstLabelLen = eventsListing.find(", ")
+    if firstLabelLen == -1: firstLabelLen = len(eventsListing)
+    else: firstLabelLen += 3
+    
+    if self.maxX > 10 + firstLabelLen:
+      eventsLabel += " ("
+      
+      if len(eventsListing) > self.maxX - 11:
+        labelBreak = eventsListing[:self.maxX - 12].rfind(", ")
+        eventsLabel += "%s..." % eventsListing[:labelBreak]
+      elif len(eventsListing) + len(filterLabel) > self.maxX - 11:
+        eventsLabel += eventsListing
+      else: eventsLabel += eventsListing + filterLabel
+      eventsLabel += ")"
+    eventsLabel += ":"
+    
+    self.addstr(0, 0, eventsLabel, uiTools.LABEL_ATTR)
+    
+    # log entries
+    maxLoc = self.getLogDisplayLength() - self.maxY + 1
+    self.scroll = max(0, min(self.scroll, maxLoc))
+    lineCount = 1 - self.scroll
+    
+    for (line, color) in self.msgLog:
+      if self.regexFilter and not self.regexFilter.search(line):
+        continue  # filter doesn't match log message - skip
+      
+      # splits over too lines if too long
+      if len(line) < self.maxX:
+        if lineCount >= 1: self.addstr(lineCount, xOffset, line, uiTools.getColor(color))
+        lineCount += 1
+      else:
+        (line1, line2) = splitLine(line, self.maxX - xOffset)
+        if lineCount >= 1: self.addstr(lineCount, xOffset, line1, uiTools.getColor(color))
+        if lineCount >= 0: self.addstr(lineCount + 1, xOffset, line2, uiTools.getColor(color))
+        lineCount += 2
+      
+      if lineCount >= self.maxY: break # further log messages wouldn't fit
+    
+    if isScrollBarVisible: uiTools.drawScrollBar(self, 1, self.maxY - 1, self.scroll, self.scroll + self.maxY - 1, self.getLogDisplayLength())
   
   def getLogDisplayLength(self):
     """

Deleted: arm/trunk/interface/util.py
===================================================================
--- arm/trunk/interface/util.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/util.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,282 +0,0 @@
-#!/usr/bin/env python
-# util.py -- support functions common for arm user interface.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-import curses
-from sys import maxint
-
-LABEL_ATTR = curses.A_STANDOUT          # default formatting constant
-
-# colors curses can handle
-COLOR_LIST = (("red", curses.COLOR_RED),
-             ("green", curses.COLOR_GREEN),
-             ("yellow", curses.COLOR_YELLOW),
-             ("blue", curses.COLOR_BLUE),
-             ("cyan", curses.COLOR_CYAN),
-             ("magenta", curses.COLOR_MAGENTA),
-             ("black", curses.COLOR_BLACK),
-             ("white", curses.COLOR_WHITE))
-
-FORMAT_TAGS = {"<b>": curses.A_BOLD,
-               "<u>": curses.A_UNDERLINE,
-               "<h>": curses.A_STANDOUT}
-for (colorLabel, cursesAttr) in COLOR_LIST: FORMAT_TAGS["<%s>" % colorLabel] = curses.A_NORMAL
-
-# foreground color mappings (starts uninitialized - all colors associated with default white fg / black bg)
-COLOR_ATTR_INITIALIZED = False
-COLOR_ATTR = dict([(color[0], 0) for color in COLOR_LIST])
-
-def initColors():
-  """
-  Initializes color mappings for the current curses. This needs to be called
-  after curses.initscr().
-  """
-  
-  global COLOR_ATTR_INITIALIZED
-  if not COLOR_ATTR_INITIALIZED:
-    COLOR_ATTR_INITIALIZED = True
-    
-    # if color support is available initializes color mappings
-    if curses.has_colors():
-      colorpair = 0
-      
-      for name, fgColor in COLOR_LIST:
-        colorpair += 1
-        curses.init_pair(colorpair, fgColor, -1) # -1 allows for default (possibly transparent) background
-        COLOR_ATTR[name] = curses.color_pair(colorpair)
-      
-      # maps color tags to initialized attributes
-      for colorLabel in COLOR_ATTR.keys(): FORMAT_TAGS["<%s>" % colorLabel] = COLOR_ATTR[colorLabel]
-
-def getColor(color):
-  """
-  Provides attribute corresponding to a given text color. Supported colors
-  include:
-  red, green, yellow, blue, cyan, magenta, black, and white
-  
-  If color support isn't available then this uses the default terminal coloring
-  scheme.
-  """
-  
-  return COLOR_ATTR[color]
-
-def getSizeLabel(bytes, decimal = 0):
-  """
-  Converts byte count into label in its most significant units, for instance
-  7500 bytes would return "7 KB".
-  """
-  
-  format = "%%.%if" % decimal
-  if bytes >= 1073741824: return (format + " GB") % (bytes / 1073741824.0)
-  elif bytes >= 1048576: return (format + " MB") % (bytes / 1048576.0)
-  elif bytes >= 1024: return (format + " KB") % (bytes / 1024.0)
-  else: return "%i bytes" % bytes
-
-def getTimeLabel(seconds, decimal = 0):
-  """
-  Concerts seconds into a time label truncated to its most significant units,
-  for instance 7500 seconds would return "2h". Units go up through days.
-  """
-  
-  format = "%%.%if" % decimal
-  if seconds >= 86400: return (format + "d") % (seconds / 86400.0)
-  elif seconds >= 3600: return (format + "h") % (seconds / 3600.0)
-  elif seconds >= 60: return (format + "m") % (seconds / 60.0)
-  else: return "%is" % seconds
-
-def drawScrollBar(panel, drawTop, drawBottom, top, bottom, size):
-  """
-  Draws scroll bar reflecting position within a vertical listing. This is
-  squared off at the bottom, having a layout like:
-   | 
-  *|
-  *|
-  *|
-   |
-  -+
-  """
-  
-  barTop = (drawBottom - drawTop) * top / size
-  barSize = (drawBottom - drawTop) * (bottom - top) / size
-  
-  # makes sure bar isn't at top or bottom unless really at those extreme bounds
-  if top > 0: barTop = max(barTop, 1)
-  if bottom != size: barTop = min(barTop, drawBottom - drawTop - barSize - 2)
-  
-  for i in range(drawBottom - drawTop):
-    if i >= barTop and i <= barTop + barSize:
-      panel.addstr(i + drawTop, 0, " ", curses.A_STANDOUT)
-  
-  # draws box around scroll bar
-  panel.win.vline(drawTop, 1, curses.ACS_VLINE, panel.maxY - 2)
-  panel.win.vline(drawBottom, 1, curses.ACS_LRCORNER, 1)
-  panel.win.hline(drawBottom, 0, curses.ACS_HLINE, 1)
-
-class Panel():
-  """
-  Wrapper for curses subwindows. This provides safe proxies to common methods
-  and is extended by panels.
-  """
-  
-  def __init__(self, lock, height):
-    self.win = None           # associated curses subwindow
-    self.lock = lock          # global curses lock
-    self.startY = -1          # top in parent window when created
-    self.height = height      # preferred (max) height of panel, -1 if infinite
-    self.isDisplaced = False  # window isn't in the right location - don't redraw
-    self.maxY, self.maxX = -1, -1
-    self._resetBounds()       # sets last known dimensions of win (maxX and maxY)
-  
-  def redraw(self):
-    pass # overwritten by implementations
-  
-  def recreate(self, stdscr, startY, maxX=-1):
-    """
-    Creates a new subwindow for the panel if:
-    - panel currently doesn't have a subwindow
-    - the panel is being moved (startY is different)
-    - there's room for the panel to grow
-    
-    Returns True if subwindow's created, False otherwise.
-    """
-    
-    # I'm not sure if recreating subwindows is some sort of memory leak but the
-    # Python curses bindings seem to lack all of the following:
-    # - subwindow deletion (to tell curses to free the memory)
-    # - subwindow moving/resizing (to restore the displaced windows)
-    # so this is the only option (besides removing subwindows entirly which 
-    # would mean more complicated code and no more selective refreshing)
-    
-    y, x = stdscr.getmaxyx()
-    self._resetBounds()
-    
-    if self.win and startY > y:
-      return False # trying to make panel out of bounds
-    
-    newHeight = max(0, y - startY)
-    if self.height != -1: newHeight = min(newHeight, self.height)
-    
-    if self.startY != startY or newHeight != self.maxY or self.isDisplaced or (self.maxX != maxX and maxX != -1):
-      # window resized or moving - recreate
-      self.startY = startY
-      startY = min(startY, y - 1) # better create a displaced window than leave it as None
-      if maxX != -1: x = min(x, maxX)
-      
-      self.win = stdscr.subwin(newHeight, x, startY, 0)
-      return True
-    else: return False
-  
-  def clear(self):
-    """
-    Erases window and resets bounds used in writting to it.
-    """
-    
-    if self.win:
-      self.isDisplaced = self.startY > self.win.getparyx()[0]
-      if not self.isDisplaced: self.win.erase()
-      self._resetBounds()
-  
-  def refresh(self):
-    """
-    Proxy for window refresh.
-    """
-    
-    if self.win and not self.isDisplaced: self.win.refresh()
-  
-  def addstr(self, y, x, msg, attr=curses.A_NORMAL):
-    """
-    Writes string to subwindow if able. This takes into account screen bounds
-    to avoid making curses upset.
-    """
-    
-    # subwindows need a character buffer (either in the x or y direction) from
-    # actual content to prevent crash when shrank
-    if self.win and self.maxX > x and self.maxY > y and not self.isDisplaced:
-      self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
-  
-  def addfstr(self, y, x, msg):
-    """
-    Writes string to subwindow. The message can contain xhtml-style tags for
-    formatting, including:
-    <b>text</b>               bold
-    <u>text</u>               underline
-    <h>text</h>               highlight
-    <[color]>text</[color]>   use color (see COLOR_LIST for constants)
-    
-    Tag nexting is supported and tag closing is not strictly enforced. This 
-    does not valididate input and unrecognized tags are treated as normal text.
-    Currently this funtion has the following restrictions:
-    - Duplicate tags nested (such as "<b><b>foo</b></b>") is invalid and may
-    throw an error.
-    - Color tags shouldn't be nested in each other (results are undefined).
-    """
-    
-    if self.win and self.maxY > y and not self.isDisplaced:
-      formatting = [curses.A_NORMAL]
-      expectedCloseTags = []
-      
-      while self.maxX > x and len(msg) > 0:
-        # finds next consumeable tag
-        nextTag, nextTagIndex = None, maxint
-        
-        for tag in FORMAT_TAGS.keys() + expectedCloseTags:
-          tagLoc = msg.find(tag)
-          if tagLoc != -1 and tagLoc < nextTagIndex:
-            nextTag, nextTagIndex = tag, tagLoc
-        
-        # splits into text before and after tag
-        if nextTag:
-          msgSegment = msg[:nextTagIndex]
-          msg = msg[nextTagIndex + len(nextTag):]
-        else:
-          msgSegment = msg
-          msg = ""
-        
-        # adds text before tag with current formatting
-        attr = 0
-        for format in formatting: attr |= format
-        self.win.addstr(y, x, msgSegment[:self.maxX - x - 1], attr)
-        
-        # applies tag attributes for future text
-        if nextTag:
-          if not nextTag.startswith("</"):
-            # open tag - add formatting
-            expectedCloseTags.append("</" + nextTag[1:])
-            formatting.append(FORMAT_TAGS[nextTag])
-          else:
-            # close tag - remove formatting
-            expectedCloseTags.remove(nextTag)
-            formatting.remove(FORMAT_TAGS["<" + nextTag[2:]])
-        
-        x += len(msgSegment)
-  
-  def addstr_wrap(self, y, x, text, formatting, startX = 0, endX = -1, maxY = -1):
-    """
-    Writes text with word wrapping, returning the ending y/x coordinate.
-    y: starting write line
-    x: column offset from startX
-    text / formatting: content to be written
-    startX / endX: column bounds in which text may be written
-    """
-    
-    if not text: return (y, x)          # nothing to write
-    if endX == -1: endX = self.maxX     # defaults to writing to end of panel
-    if maxY == -1: maxY = self.maxY + 1 # defaults to writing to bottom of panel
-    lineWidth = endX - startX           # room for text
-    while True:
-      if len(text) > lineWidth - x - 1:
-        chunkSize = text.rfind(" ", 0, lineWidth - x)
-        writeText = text[:chunkSize]
-        text = text[chunkSize:].strip()
-        
-        self.addstr(y, x + startX, writeText, formatting)
-        y, x = y + 1, 0
-        if y >= maxY: return (y, x)
-      else:
-        self.addstr(y, x + startX, text, formatting)
-        return (y, x + len(text))
-  
-  def _resetBounds(self):
-    if self.win: self.maxY, self.maxX = self.win.getmaxyx()
-    else: self.maxY, self.maxX = -1, -1
-

Added: arm/trunk/util/__init__.py
===================================================================
--- arm/trunk/util/__init__.py	                        (rev 0)
+++ arm/trunk/util/__init__.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,8 @@
+"""
+General purpose utilities for a variety of tasks including logging the 
+application's status, making cross platform system calls, parsing tor data, 
+and safely working with curses (hiding some of the gory details).
+"""
+
+__all__ = ["panel", "uiTools"]
+

Added: arm/trunk/util/panel.py
===================================================================
--- arm/trunk/util/panel.py	                        (rev 0)
+++ arm/trunk/util/panel.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,230 @@
+"""
+Wrapper for safely working with curses subwindows.
+"""
+
+import curses
+from sys import maxint
+from threading import RLock
+
+import uiTools
+
+# TODO: external usage of clear and refresh are only used by popup (remove when alternative is used)
+
+# global ui lock governing all panel instances (curses isn't thread save and 
+# concurrency bugs produce especially sinister glitches)
+CURSES_LOCK = RLock()
+
+class Panel():
+  """
+  Wrapper for curses subwindows. This hides most of the ugliness in common
+  curses operations including:
+    - locking when concurrently drawing to multiple windows
+    - gracefully handle terminal resizing
+    - clip text that falls outside the panel
+    - convenience methods for word wrap, inline formatting, etc
+  
+  This can't be used until it has a subwindow instance, which is done via the 
+  recreate() function. Until this is done the top, maxX, and maxY parameters 
+  are defaulted to -1.
+  
+  Parameters:
+  win - current curses subwindow
+  height - preferred (max) height of panel, -1 if infinite
+  top - upper Y-coordinate within parent window
+  maxX, maxY - cached bounds of subwindow
+  """
+  
+  def __init__(self, height):
+    self.win = None
+    self.height = height
+    self.top = -1
+    self.maxY, self.maxX = -1, -1
+    
+    # when the terminal is shrank then expanded curses attempts to draw 
+    # displaced windows in the wrong location - this results in graphical 
+    # glitches if we let the panel be redrawn
+    self.isDisplaced = True
+  
+  def draw(self):
+    """
+    Draws display's content. This is meant to be overwriten by 
+    impelementations.
+    """
+    
+    pass
+  
+  def redraw(self, block=False):
+    """
+    Clears display and redraws.
+    """
+    
+    if self.win:
+      if not CURSES_LOCK.acquire(block): return
+      try:
+        self.clear()
+        self.draw()
+        self.refresh()
+      finally:
+        CURSES_LOCK.release()
+  
+  def recreate(self, stdscr, newWidth=-1, newTop=None):
+    """
+    Creates a new subwindow for the panel if:
+    - panel currently doesn't have a subwindow
+    - the panel is being moved (top is different from newTop)
+    - there's room for the panel to grow
+    
+    Returns True if subwindow's created, False otherwise.
+    """
+    
+    if newTop == None: newTop = self.top
+    
+    # I'm not sure if recreating subwindows is some sort of memory leak but the
+    # Python curses bindings seem to lack all of the following:
+    # - subwindow deletion (to tell curses to free the memory)
+    # - subwindow moving/resizing (to restore the displaced windows)
+    # so this is the only option (besides removing subwindows entirly which 
+    # would mean more complicated code and no more selective refreshing)
+    
+    y, x = stdscr.getmaxyx()
+    self._resetBounds()
+    
+    if self.win and newTop > y:
+      return False # trying to make panel out of bounds
+    
+    newHeight = max(0, y - newTop)
+    if self.height != -1: newHeight = min(newHeight, self.height)
+    
+    recreate = False
+    recreate |= self.top != newTop      # position has shifted
+    recreate |= newHeight != self.maxY  # subwindow can grow (vertically)
+    recreate |= self.isDisplaced        # resizing has bumped subwindow out of position
+    recreate |= self.maxX != newWidth and newWidth != -1    # set to use a new width
+    
+    if recreate:
+      if newWidth == -1: newWidth = x
+      else: newWidth = min(newWidth, x)
+      
+      self.top = newTop
+      newTop = min(newTop, y - 1) # better create a displaced window than leave it as None
+      
+      self.win = stdscr.subwin(newHeight, newWidth, newTop, 0)
+      return True
+    else: return False
+  
+  # TODO: merge into repaint when no longer needed
+  def clear(self):
+    """
+    Erases window and resets bounds used in writting to it.
+    """
+    
+    if self.win:
+      self.isDisplaced = self.top > self.win.getparyx()[0]
+      if not self.isDisplaced: self.win.erase()
+      self._resetBounds()
+  
+  # TODO: merge into repaint when no longer needed
+  def refresh(self):
+    """
+    Proxy for window refresh.
+    """
+    
+    if self.win and not self.isDisplaced: self.win.refresh()
+  
+  def addstr(self, y, x, msg, attr=curses.A_NORMAL):
+    """
+    Writes string to subwindow if able. This takes into account screen bounds
+    to avoid making curses upset.
+    """
+    
+    # subwindows need a single character buffer (either in the x or y 
+    # direction) from actual content to prevent crash when shrank
+    if self.win and self.maxX > x and self.maxY > y and not self.isDisplaced:
+      self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
+  
+  def addfstr(self, y, x, msg):
+    """
+    Writes string to subwindow. The message can contain xhtml-style tags for
+    formatting, including:
+    <b>text</b>               bold
+    <u>text</u>               underline
+    <h>text</h>               highlight
+    <[color]>text</[color]>   use color (see COLOR_LIST for constants)
+    
+    Tag nexting is supported and tag closing is not strictly enforced. This 
+    does not valididate input and unrecognized tags are treated as normal text.
+    Currently this funtion has the following restrictions:
+    - Duplicate tags nested (such as "<b><b>foo</b></b>") is invalid and may
+    throw an error.
+    - Color tags shouldn't be nested in each other (results are undefined).
+    """
+    
+    if self.win and self.maxY > y and not self.isDisplaced:
+      formatting = [curses.A_NORMAL]
+      expectedCloseTags = []
+      
+      while self.maxX > x and len(msg) > 0:
+        # finds next consumeable tag
+        nextTag, nextTagIndex = None, maxint
+        
+        for tag in uiTools.FORMAT_TAGS.keys() + expectedCloseTags:
+          tagLoc = msg.find(tag)
+          if tagLoc != -1 and tagLoc < nextTagIndex:
+            nextTag, nextTagIndex = tag, tagLoc
+        
+        # splits into text before and after tag
+        if nextTag:
+          msgSegment = msg[:nextTagIndex]
+          msg = msg[nextTagIndex + len(nextTag):]
+        else:
+          msgSegment = msg
+          msg = ""
+        
+        # adds text before tag with current formatting
+        attr = 0
+        for format in formatting: attr |= format
+        self.win.addstr(y, x, msgSegment[:self.maxX - x - 1], attr)
+        
+        # applies tag attributes for future text
+        if nextTag:
+          if not nextTag.startswith("</"):
+            # open tag - add formatting
+            expectedCloseTags.append("</" + nextTag[1:])
+            formatting.append(uiTools.FORMAT_TAGS[nextTag])
+          else:
+            # close tag - remove formatting
+            expectedCloseTags.remove(nextTag)
+            formatting.remove(uiTools.FORMAT_TAGS["<" + nextTag[2:]])
+        
+        x += len(msgSegment)
+  
+  def addstr_wrap(self, y, x, text, formatting, startX = 0, endX = -1, maxY = -1):
+    """
+    Writes text with word wrapping, returning the ending y/x coordinate.
+    y: starting write line
+    x: column offset from startX
+    text / formatting: content to be written
+    startX / endX: column bounds in which text may be written
+    """
+    
+    if not text: return (y, x)          # nothing to write
+    if endX == -1: endX = self.maxX     # defaults to writing to end of panel
+    if maxY == -1: maxY = self.maxY + 1 # defaults to writing to bottom of panel
+    lineWidth = endX - startX           # room for text
+    while True:
+      if len(text) > lineWidth - x - 1:
+        chunkSize = text.rfind(" ", 0, lineWidth - x)
+        writeText = text[:chunkSize]
+        text = text[chunkSize:].strip()
+        
+        self.addstr(y, x + startX, writeText, formatting)
+        y, x = y + 1, 0
+        if y >= maxY: return (y, x)
+      else:
+        self.addstr(y, x + startX, text, formatting)
+        return (y, x + len(text))
+  
+  def _resetBounds(self):
+    if self.win: self.maxY, self.maxX = self.win.getmaxyx()
+    else: self.maxY, self.maxX = -1, -1
+

Added: arm/trunk/util/uiTools.py
===================================================================
--- arm/trunk/util/uiTools.py	                        (rev 0)
+++ arm/trunk/util/uiTools.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+# util.py -- support functions common for arm user interface.
+# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+
+import curses
+
+LABEL_ATTR = curses.A_STANDOUT          # default formatting constant
+
+# colors curses can handle
+COLOR_LIST = (("red", curses.COLOR_RED),
+             ("green", curses.COLOR_GREEN),
+             ("yellow", curses.COLOR_YELLOW),
+             ("blue", curses.COLOR_BLUE),
+             ("cyan", curses.COLOR_CYAN),
+             ("magenta", curses.COLOR_MAGENTA),
+             ("black", curses.COLOR_BLACK),
+             ("white", curses.COLOR_WHITE))
+
+FORMAT_TAGS = {"<b>": curses.A_BOLD,
+               "<u>": curses.A_UNDERLINE,
+               "<h>": curses.A_STANDOUT}
+for (colorLabel, cursesAttr) in COLOR_LIST: FORMAT_TAGS["<%s>" % colorLabel] = curses.A_NORMAL
+
+# foreground color mappings (starts uninitialized - all colors associated with default white fg / black bg)
+COLOR_ATTR_INITIALIZED = False
+COLOR_ATTR = dict([(color[0], 0) for color in COLOR_LIST])
+
+def initColors():
+  """
+  Initializes color mappings for the current curses. This needs to be called
+  after curses.initscr().
+  """
+  
+  global COLOR_ATTR_INITIALIZED
+  if not COLOR_ATTR_INITIALIZED:
+    COLOR_ATTR_INITIALIZED = True
+    
+    # if color support is available initializes color mappings
+    if curses.has_colors():
+      colorpair = 0
+      
+      for name, fgColor in COLOR_LIST:
+        colorpair += 1
+        curses.init_pair(colorpair, fgColor, -1) # -1 allows for default (possibly transparent) background
+        COLOR_ATTR[name] = curses.color_pair(colorpair)
+      
+      # maps color tags to initialized attributes
+      for colorLabel in COLOR_ATTR.keys(): FORMAT_TAGS["<%s>" % colorLabel] = COLOR_ATTR[colorLabel]
+
+def getColor(color):
+  """
+  Provides attribute corresponding to a given text color. Supported colors
+  include:
+  red, green, yellow, blue, cyan, magenta, black, and white
+  
+  If color support isn't available then this uses the default terminal coloring
+  scheme.
+  """
+  
+  return COLOR_ATTR[color]
+
+def getSizeLabel(bytes, decimal = 0):
+  """
+  Converts byte count into label in its most significant units, for instance
+  7500 bytes would return "7 KB".
+  """
+  
+  format = "%%.%if" % decimal
+  if bytes >= 1073741824: return (format + " GB") % (bytes / 1073741824.0)
+  elif bytes >= 1048576: return (format + " MB") % (bytes / 1048576.0)
+  elif bytes >= 1024: return (format + " KB") % (bytes / 1024.0)
+  else: return "%i bytes" % bytes
+
+def getTimeLabel(seconds, decimal = 0):
+  """
+  Concerts seconds into a time label truncated to its most significant units,
+  for instance 7500 seconds would return "2h". Units go up through days.
+  """
+  
+  format = "%%.%if" % decimal
+  if seconds >= 86400: return (format + "d") % (seconds / 86400.0)
+  elif seconds >= 3600: return (format + "h") % (seconds / 3600.0)
+  elif seconds >= 60: return (format + "m") % (seconds / 60.0)
+  else: return "%is" % seconds
+
+def drawScrollBar(panel, drawTop, drawBottom, top, bottom, size):
+  """
+  Draws scroll bar reflecting position within a vertical listing. This is
+  squared off at the bottom, having a layout like:
+   | 
+  *|
+  *|
+  *|
+   |
+  -+
+  """
+  
+  if panel.maxY < 2: return # not enough room
+  
+  barTop = (drawBottom - drawTop) * top / size
+  barSize = (drawBottom - drawTop) * (bottom - top) / size
+  
+  # makes sure bar isn't at top or bottom unless really at those extreme bounds
+  if top > 0: barTop = max(barTop, 1)
+  if bottom != size: barTop = min(barTop, drawBottom - drawTop - barSize - 2)
+  
+  for i in range(drawBottom - drawTop):
+    if i >= barTop and i <= barTop + barSize:
+      panel.addstr(i + drawTop, 0, " ", curses.A_STANDOUT)
+  
+  # draws box around scroll bar
+  panel.win.vline(drawTop, 1, curses.ACS_VLINE, panel.maxY - 2)
+  panel.win.vline(drawBottom, 1, curses.ACS_LRCORNER, 1)
+  panel.win.hline(drawBottom, 0, curses.ACS_HLINE, 1)
+

Deleted: arm/trunk/versionCheck.py
===================================================================
--- arm/trunk/versionCheck.py	2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/versionCheck.py	2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,17 +0,0 @@
-#!/usr/bin/env python
-# versionCheck.py -- Provides a warning and error code if python version isn't compatible.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-import sys
-
-if __name__ == '__main__':
-  majorVersion = sys.version_info[0]
-  minorVersion = sys.version_info[1]
-  
-  if majorVersion > 2:
-    print("arm isn't compatible beyond the python 2.x series\n")
-    sys.exit(1)
-  elif majorVersion < 2 or minorVersion < 5:
-    print("arm requires python version 2.5 or greater\n")
-    sys.exit(1)
-



More information about the tor-commits mailing list