tor-commits
Threads by month
- ----- 2026 -----
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 216095 discussions
commit c90d3a11b73f5c418ed5b5630602d06d9d5eec89
Author: Roger Dingledine <arma(a)torproject.org>
Date: Sun Jun 5 01:45:42 2011 -0400
put the changelog stanzas in time order
---
ChangeLog | 288 ++++++++++++++++++++++++++++++------------------------------
1 files changed, 144 insertions(+), 144 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index a735900..a72747f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,147 +1,3 @@
-Changes in version 0.2.3.1-alpha - 2011-05-05
- Tor 0.2.3.1-alpha adds some new experimental features, including support
- for an improved network IO backend, IOCP networking on Windows,
- microdescriptor caching, "fast-start" support for streams, and automatic
- home router configuration. There are also numerous internal improvements
- to try to make the code easier for developers to work with.
-
- This is the first alpha release in a new series, so expect there to be
- bugs. Users who would rather test out a more stable branch should
- stay with 0.2.2.x for now.
-
- o Major features:
- - Tor can now optionally build with the "bufferevents" buffered IO
- backend provided by Libevent 2. To use this feature, make sure you
- have the latest possible version of Libevent, and pass the
- --enable-bufferevents flag to configure when building Tor from
- source. This feature will make our networking code more flexible,
- let us stack layers on each other, and let us use more efficient
- zero-copy transports where available.
- - As an experimental feature, Tor can use IOCP for networking on Windows.
- Once this code is tuned and optimized, it promises much better
- performance than the select-based backend we've used in the past. To
- try this feature, you must build Tor with Libevent 2, configure Tor
- with the "bufferevents" buffered IO backend, and add "DisableIOCP 0" to
- your torrc. There are known bugs here: only try this if you can help
- debug it as it breaks.
- - The EntryNodes option can now include country codes like {de} or IP
- addresses or network masks. Previously we had disallowed these options
- because we didn't have an efficient way to keep the list up to
- date. Fixes bug 1982, but see bug 2798 for an unresolved issue here.
- - Exit nodes now accept and queue data on not-yet-connected streams.
- Previously, the client wasn't allowed to send data until the stream was
- connected, which slowed down all connections. This change will enable
- clients to perform a "fast-start" on streams and send data without
- having to wait for a confirmation that the stream has opened. (Patch
- from Ian Goldberg; implements the server side of Proposal 174.)
- - Tor now has initial support for automatic port mapping on the many
- home routers that support NAT-PMP or UPnP. (Not yet supported on
- Windows). To build the support code, you'll need to have libnatpnp
- library and/or the libminiupnpc library, and you'll need to enable the
- feature specifically by passing "--enable-upnp" and/or
- "--enable-natpnp" to configure. To turn it on, use the new
- PortForwarding option.
- - Caches now download, cache, and serve multiple "flavors" of the
- consensus, including a flavor that describes microdescriptors.
- - Caches now download, cache, and serve microdescriptors -- small
- summaries of router descriptors that are authenticated by all of the
- directory authorities. Once enough caches are running this code,
- clients will be able to save significant amounts of directory bandwidth
- by downloading microdescriptors instead of router descriptors.
-
- o Minor features:
- - Make logging resolution configurable with a new LogGranularity
- option, and change the default from 1 millisecond to 1
- second. Implements enhancement 1668.
- - We log which torrc file we're using on startup. Implements ticket
- 2444.
- - Ordinarily, Tor does not count traffic from private addresses (like
- 127.0.0.1 or 10.0.0.1) when calculating rate limits or accounting.
- There is now a new option, CountPrivateBandwidth, to disable this
- behavior. Patch from Daniel Cagara.
- - New --enable-static-tor configure option for building Tor as
- statically as possible. Idea, general hackery and thoughts from
- Alexei Czeskis, John Gilmore, Jacob Appelbaum. Implements ticket
- 2702.
- - If you set the NumCPUs option to 0, Tor will now try to detect how
- many CPUs you have. This is the new default behavior.
- - Turn on directory request statistics by default and include them in
- extra-info descriptors. Don't break if we have no GeoIP database.
- - Relays that set "ConnDirectionStatistics 1" write statistics on the
- bidirectional use of connections to disk every 24 hours.
- - Add a GeoIP file digest to the extra-info descriptor. Implements
- enhancement 1883.
- - Add a new 'Heartbeat' log message type to periodically log a message
- describing Tor's status at level Notice. This feature is meant for
- operators who log at notice, and want to make sure that their Tor
- server is still working. Implementation by George Kadianakis.
-
- o Minor bugfixes (on 0.2.2.25-alpha):
- - When loading the microdesc journal, remember its current size.
- In 0.2.2, this helps prevent the microdesc journal from growing
- without limit on authorities (who are the only ones to use it in
- 0.2.2). Fixes a part of bug 2230; bugfix on 0.2.2.6-alpha.
- Fix posted by "cypherpunks."
- - The microdesc journal is supposed to get rebuilt only if it is
- at least _half_ the length of the store, not _twice_ the length
- of the store. Bugfix on 0.2.2.6-alpha; fixes part of bug 2230.
- - If as an authority we fail to compute the identity digest of a v3
- legacy keypair, warn, and don't use a buffer-full of junk instead.
- Bugfix on 0.2.1.1-alpha; fixes bug 3106.
- - Authorities now clean their microdesc cache periodically and when
- reading from disk initially, not only when adding new descriptors.
- This prevents a bug where we could lose microdescriptors. Bugfix
- on 0.2.2.6-alpha.
-
- o Minor features (controller):
- - Add a new SIGNAL event to the controller interface so that
- controllers can be notified when Tor handles a signal. Resolves
- issue 1955. Patch by John Brooks.
- - Add a new GETINFO option to get total bytes read and written. Patch
- from pipe, revised by atagar. Resolves ticket 2345.
- - Implement some GETINFO controller fields to provide information about
- the Tor process's pid, euid, username, and resource limits.
-
- o Build changes:
- - Our build system requires automake 1.6 or later to create the
- Makefile.in files. Previously, you could have used 1.4.
- This only affects developers and people building Tor from git;
- people who build Tor from the source distribution without changing
- the Makefile.am files should be fine.
- - Our autogen.sh script uses autoreconf to launch autoconf, automake, and
- so on. This is more robust against some of the failure modes
- associated with running the autotools pieces on their own.
-
- o Minor packaging issues:
- - On OpenSUSE, create the /var/run/tor directory on startup if it is not
- already created. Patch from Andreas Stieger. Fixes bug 2573.
-
- o Code simplifications and refactoring:
- - A major revision to our internal node-selecting and listing logic.
- Tor already had at least two major ways to look at the question of
- "which Tor servers do we know about": a list of router descriptors,
- and a list of entries in the current consensus. With
- microdescriptors, we're adding a third. Having so many systems
- without an abstraction layer over them was hurting the codebase.
- Now, we have a new "node_t" abstraction that presents a consistent
- interface to a client's view of a Tor node, and holds (nearly) all
- of the mutable state formerly in routerinfo_t and routerstatus_t.
- - The helper programs tor-gencert, tor-resolve, and tor-checkkey
- no longer link against Libevent: they never used it, but
- our library structure used to force them to link it.
-
- o Removed features:
- - Remove some old code to work around even older versions of Tor that
- used forked processes to handle DNS requests. Such versions of Tor
- are no longer in use as servers.
-
- o Documentation fixes:
- - Correct a broken faq link in the INSTALL file. Fixes bug 2307.
- - Add missing documentation for the authority-related torrc options
- RephistTrackTime, BridgePassword, and V3AuthUseLegacyKey. Resolves
- issue 2379.
-
-
Changes in version 0.2.2.27-beta - 2011-05-18
Tor 0.2.2.27-beta fixes a bridge-related stability bug in the previous
release, and also adds a few more general bugfixes.
@@ -300,6 +156,150 @@ Changes in version 0.2.2.26-beta - 2011-05-17
bug 3022.
+Changes in version 0.2.3.1-alpha - 2011-05-05
+ Tor 0.2.3.1-alpha adds some new experimental features, including support
+ for an improved network IO backend, IOCP networking on Windows,
+ microdescriptor caching, "fast-start" support for streams, and automatic
+ home router configuration. There are also numerous internal improvements
+ to try to make the code easier for developers to work with.
+
+ This is the first alpha release in a new series, so expect there to be
+ bugs. Users who would rather test out a more stable branch should
+ stay with 0.2.2.x for now.
+
+ o Major features:
+ - Tor can now optionally build with the "bufferevents" buffered IO
+ backend provided by Libevent 2. To use this feature, make sure you
+ have the latest possible version of Libevent, and pass the
+ --enable-bufferevents flag to configure when building Tor from
+ source. This feature will make our networking code more flexible,
+ let us stack layers on each other, and let us use more efficient
+ zero-copy transports where available.
+ - As an experimental feature, Tor can use IOCP for networking on Windows.
+ Once this code is tuned and optimized, it promises much better
+ performance than the select-based backend we've used in the past. To
+ try this feature, you must build Tor with Libevent 2, configure Tor
+ with the "bufferevents" buffered IO backend, and add "DisableIOCP 0" to
+ your torrc. There are known bugs here: only try this if you can help
+ debug it as it breaks.
+ - The EntryNodes option can now include country codes like {de} or IP
+ addresses or network masks. Previously we had disallowed these options
+ because we didn't have an efficient way to keep the list up to
+ date. Fixes bug 1982, but see bug 2798 for an unresolved issue here.
+ - Exit nodes now accept and queue data on not-yet-connected streams.
+ Previously, the client wasn't allowed to send data until the stream was
+ connected, which slowed down all connections. This change will enable
+ clients to perform a "fast-start" on streams and send data without
+ having to wait for a confirmation that the stream has opened. (Patch
+ from Ian Goldberg; implements the server side of Proposal 174.)
+ - Tor now has initial support for automatic port mapping on the many
+ home routers that support NAT-PMP or UPnP. (Not yet supported on
+ Windows). To build the support code, you'll need to have libnatpnp
+ library and/or the libminiupnpc library, and you'll need to enable the
+ feature specifically by passing "--enable-upnp" and/or
+ "--enable-natpnp" to configure. To turn it on, use the new
+ PortForwarding option.
+ - Caches now download, cache, and serve multiple "flavors" of the
+ consensus, including a flavor that describes microdescriptors.
+ - Caches now download, cache, and serve microdescriptors -- small
+ summaries of router descriptors that are authenticated by all of the
+ directory authorities. Once enough caches are running this code,
+ clients will be able to save significant amounts of directory bandwidth
+ by downloading microdescriptors instead of router descriptors.
+
+ o Minor features:
+ - Make logging resolution configurable with a new LogGranularity
+ option, and change the default from 1 millisecond to 1
+ second. Implements enhancement 1668.
+ - We log which torrc file we're using on startup. Implements ticket
+ 2444.
+ - Ordinarily, Tor does not count traffic from private addresses (like
+ 127.0.0.1 or 10.0.0.1) when calculating rate limits or accounting.
+ There is now a new option, CountPrivateBandwidth, to disable this
+ behavior. Patch from Daniel Cagara.
+ - New --enable-static-tor configure option for building Tor as
+ statically as possible. Idea, general hackery and thoughts from
+ Alexei Czeskis, John Gilmore, Jacob Appelbaum. Implements ticket
+ 2702.
+ - If you set the NumCPUs option to 0, Tor will now try to detect how
+ many CPUs you have. This is the new default behavior.
+ - Turn on directory request statistics by default and include them in
+ extra-info descriptors. Don't break if we have no GeoIP database.
+ - Relays that set "ConnDirectionStatistics 1" write statistics on the
+ bidirectional use of connections to disk every 24 hours.
+ - Add a GeoIP file digest to the extra-info descriptor. Implements
+ enhancement 1883.
+ - Add a new 'Heartbeat' log message type to periodically log a message
+ describing Tor's status at level Notice. This feature is meant for
+ operators who log at notice, and want to make sure that their Tor
+ server is still working. Implementation by George Kadianakis.
+
+ o Minor bugfixes (on 0.2.2.25-alpha):
+ - When loading the microdesc journal, remember its current size.
+ In 0.2.2, this helps prevent the microdesc journal from growing
+ without limit on authorities (who are the only ones to use it in
+ 0.2.2). Fixes a part of bug 2230; bugfix on 0.2.2.6-alpha.
+ Fix posted by "cypherpunks."
+ - The microdesc journal is supposed to get rebuilt only if it is
+ at least _half_ the length of the store, not _twice_ the length
+ of the store. Bugfix on 0.2.2.6-alpha; fixes part of bug 2230.
+ - If as an authority we fail to compute the identity digest of a v3
+ legacy keypair, warn, and don't use a buffer-full of junk instead.
+ Bugfix on 0.2.1.1-alpha; fixes bug 3106.
+ - Authorities now clean their microdesc cache periodically and when
+ reading from disk initially, not only when adding new descriptors.
+ This prevents a bug where we could lose microdescriptors. Bugfix
+ on 0.2.2.6-alpha.
+
+ o Minor features (controller):
+ - Add a new SIGNAL event to the controller interface so that
+ controllers can be notified when Tor handles a signal. Resolves
+ issue 1955. Patch by John Brooks.
+ - Add a new GETINFO option to get total bytes read and written. Patch
+ from pipe, revised by atagar. Resolves ticket 2345.
+ - Implement some GETINFO controller fields to provide information about
+ the Tor process's pid, euid, username, and resource limits.
+
+ o Build changes:
+ - Our build system requires automake 1.6 or later to create the
+ Makefile.in files. Previously, you could have used 1.4.
+ This only affects developers and people building Tor from git;
+ people who build Tor from the source distribution without changing
+ the Makefile.am files should be fine.
+ - Our autogen.sh script uses autoreconf to launch autoconf, automake, and
+ so on. This is more robust against some of the failure modes
+ associated with running the autotools pieces on their own.
+
+ o Minor packaging issues:
+ - On OpenSUSE, create the /var/run/tor directory on startup if it is not
+ already created. Patch from Andreas Stieger. Fixes bug 2573.
+
+ o Code simplifications and refactoring:
+ - A major revision to our internal node-selecting and listing logic.
+ Tor already had at least two major ways to look at the question of
+ "which Tor servers do we know about": a list of router descriptors,
+ and a list of entries in the current consensus. With
+ microdescriptors, we're adding a third. Having so many systems
+ without an abstraction layer over them was hurting the codebase.
+ Now, we have a new "node_t" abstraction that presents a consistent
+ interface to a client's view of a Tor node, and holds (nearly) all
+ of the mutable state formerly in routerinfo_t and routerstatus_t.
+ - The helper programs tor-gencert, tor-resolve, and tor-checkkey
+ no longer link against Libevent: they never used it, but
+ our library structure used to force them to link it.
+
+ o Removed features:
+ - Remove some old code to work around even older versions of Tor that
+ used forked processes to handle DNS requests. Such versions of Tor
+ are no longer in use as servers.
+
+ o Documentation fixes:
+ - Correct a broken faq link in the INSTALL file. Fixes bug 2307.
+ - Add missing documentation for the authority-related torrc options
+ RephistTrackTime, BridgePassword, and V3AuthUseLegacyKey. Resolves
+ issue 2379.
+
+
Changes in version 0.2.2.25-alpha - 2011-04-29
Tor 0.2.2.25-alpha fixes many bugs: hidden service clients are more
robust, routers no longer overreport their bandwidth, Win7 should crash
1
0
commit 66f847857c5b3a8df8f47b77ff9159d8f3e2b471
Author: Roger Dingledine <arma(a)torproject.org>
Date: Sun Jun 5 01:43:28 2011 -0400
put the release blurb in
---
ChangeLog | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 5c9b96c..4d229d0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,11 @@
Changes in version 0.2.2.28-beta - 2011-06-04
+ Tor 0.2.2.28-beta makes great progress towards a new stable release: we
+ fixed a big bug in whether relays stay in the consensus consistently,
+ we moved closer to handling bridges and hidden services correctly,
+ and we started the process of better handling the dreaded "my Vidalia
+ died, and now my Tor demands a password when I try to reconnect to it"
+ usability issue.
+
o Major bugfixes:
- Don't decide to make a new descriptor when receiving a HUP signal.
This bug has caused a lot of 0.2.2.x relays to disappear from the
1
0
commit f08c8d406a08ef8b842db467237dfac6daacb4df
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 21:47:57 2011 -0700
Message for how to close the menu
When presenting the menu showing the hotkeys for closeing in the label panel.
---
src/cli/menu/menu.py | 7 ++++++-
1 files changed, 6 insertions(+), 1 deletions(-)
diff --git a/src/cli/menu/menu.py b/src/cli/menu/menu.py
index 02f87ee..f83b76d 100644
--- a/src/cli/menu/menu.py
+++ b/src/cli/menu/menu.py
@@ -148,6 +148,9 @@ def showMenu():
popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red"))
selectionHierarchy = cursor.getSelection().getHierarchy()
+ # provide a message saying how to close the menu
+ control.setMsg("Press m or esc to close the menu.", curses.A_BOLD, True)
+
# renders the menu bar, noting where the open submenu is positioned
drawLeft, selectionLeft = 0, 0
@@ -175,7 +178,9 @@ def showMenu():
if not cursor.isDone():
for panelImpl in control.getDisplayPanels():
panelImpl.redraw(True)
- finally: cli.popups.finalize()
+ finally:
+ control.setMsg()
+ cli.popups.finalize()
def _drawSubmenu(cursor, level, top, left):
selectionHierarchy = cursor.getSelection().getHierarchy()
1
0
commit 3ec4ad0a4be48b6674b7107fbc30ca8894b45d13
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 21:37:57 2011 -0700
fix: Reverting one of the map changes
Mistakenly thought map could call object methods (oops). This caused a crash
with most popups.
---
src/cli/popups.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/src/cli/popups.py b/src/cli/popups.py
index 4bb519d..8061c8f 100644
--- a/src/cli/popups.py
+++ b/src/cli/popups.py
@@ -26,7 +26,7 @@ def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
control = cli.controller.getController()
if belowStatic:
- stickyHeight = sum(map(getHeight, control.getStickyPanels()))
+ stickyHeight = sum([stickyPanel.getHeight() for stickyPanel in control.getStickyPanels()])
else: stickyHeight = 0
popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
1
0
commit f56e2b85d5dd133c40955e9438f4109c31cd9f30
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 20:20:16 2011 -0700
Replacing list comprehension with map
Couple minor refactoring changes from a code review comment of Robert's. I'm
actually not sure why the first was working since it was missing the
brackets...
---
src/cli/popups.py | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/cli/popups.py b/src/cli/popups.py
index b90f95d..52e5376 100644
--- a/src/cli/popups.py
+++ b/src/cli/popups.py
@@ -26,7 +26,7 @@ def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
control = cli.controller.getController()
if belowStatic:
- stickyHeight = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
+ stickyHeight = sum(map(getHeight, control.getStickyPanels()))
else: stickyHeight = 0
popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
@@ -272,7 +272,7 @@ def showMenu(title, options, oldSelection):
selection without a carrot if -1)
"""
- maxWidth = max([len(label) for label in options]) + 9
+ maxWidth = max(map(len, options)) + 9
popup, _, _ = init(len(options) + 2, maxWidth)
if not popup: return
key, selection = 0, oldSelection if oldSelection != -1 else 0
1
0
05 Jun '11
commit d7f1af69b70732ef9f046411b7cf12814a8a62f9
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 21:22:35 2011 -0700
fix: Submenus were crashing when outside window
The menus (and any window with a non-zero left position) would crash when it
attempted to be created outside the screen. Caught by krkhan
---
src/cli/popups.py | 2 +-
src/util/panel.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/cli/popups.py b/src/cli/popups.py
index 52e5376..4bb519d 100644
--- a/src/cli/popups.py
+++ b/src/cli/popups.py
@@ -38,7 +38,7 @@ def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
if popup.win != None:
panel.CURSES_LOCK.acquire()
return (popup, popup.maxX - 1, popup.maxY)
- else: return None
+ else: return (None, 0, 0)
def finalize():
"""
diff --git a/src/util/panel.py b/src/util/panel.py
index 6fae341..70f72a3 100644
--- a/src/util/panel.py
+++ b/src/util/panel.py
@@ -381,7 +381,7 @@ class Panel():
# if the panel's completely outside its parent then this is a no-op
newHeight, newWidth = self.getPreferredSize()
- if newHeight == 0:
+ if newHeight == 0 or newWidth == 0:
self.win = None
return
1
0
[torspec/master] Add proposal 181 (Optimistic data, client side) from Ian Goldberg
by nickm@torproject.org 05 Jun '11
by nickm@torproject.org 05 Jun '11
05 Jun '11
commit 2ab6738fa3695773b537d09e59575bc3d9be27cf
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Sat Jun 4 23:08:40 2011 -0400
Add proposal 181 (Optimistic data, client side) from Ian Goldberg
---
proposals/000-index.txt | 2 +
proposals/181-optimistic-data-client.txt | 127 ++++++++++++++++++++++++++++++
2 files changed, 129 insertions(+), 0 deletions(-)
diff --git a/proposals/000-index.txt b/proposals/000-index.txt
index dca67dc..06e97b5 100644
--- a/proposals/000-index.txt
+++ b/proposals/000-index.txt
@@ -101,6 +101,7 @@ Proposals by number:
178 Require majority of authorities to vote for consensus parameters [OPEN]
179 TLS certificate and parameter normalization [DRAFT]
180 Pluggable transports for circumvention [OPEN]
+181 Optimistic Data for Tor: Client Side [OPEN]
Proposals by status:
@@ -130,6 +131,7 @@ Proposals by status:
177 Abstaining from votes on individual flags [for 0.2.3.x]
178 Require majority of authorities to vote for consensus parameters [for 0.2.3.x]
180 Pluggable transports for circumvention [for 0.2.3.x]
+ 181 Optimistic Data for Tor: Client Side
ACCEPTED:
110 Avoiding infinite length circuits [for 0.2.3.x] [in 0.2.1.3-alpha]
117 IPv6 exits [for 0.2.3.x]
diff --git a/proposals/181-optimistic-data-client.txt b/proposals/181-optimistic-data-client.txt
new file mode 100644
index 0000000..733b8a1
--- /dev/null
+++ b/proposals/181-optimistic-data-client.txt
@@ -0,0 +1,127 @@
+Filename: 181-optimistic-data-client.txt
+Title: Optimistic Data for Tor: Client Side
+Author: Ian Goldberg
+Created: 2-Jun-2011
+Status: Open
+
+Overview:
+
+This proposal (as well as its already-implemented sibling concerning the
+server side) aims to reduce the latency of HTTP requests in particular
+by allowing:
+1. SOCKS clients to optimistically send data before they are notified
+ that the SOCKS connection has completed successfully
+2. OPs to optimistically send DATA cells on streams in the CONNECT_WAIT
+ state
+3. Exit nodes to accept and queue DATA cells while in the
+ EXIT_CONN_STATE_CONNECTING state
+
+This particular proposal deals with #1 and #2.
+
+For more details (in general and for #3), see the sibling proposal 174
+(Optimistic Data for Tor: Server Side), which has been implemented in
+0.2.3.1-alpha.
+
+Motivation:
+
+This change will save one OP<->Exit round trip (down to one from two).
+There are still two SOCKS Client<->OP round trips (negligible time) and
+two Exit<->Server round trips. Depending on the ratio of the
+Exit<->Server (Internet) RTT to the OP<->Exit (Tor) RTT, this will
+decrease the latency by 25 to 50 percent. Experiments validate these
+predictions. [Goldberg, PETS 2010 rump session; see
+https://thunk.cs.uwaterloo.ca/optimistic-data-pets2010-rump.pdf ]
+
+Design:
+
+Currently, data arriving on the SOCKS connection to the OP on a stream
+in AP_CONN_STATE_CONNECT_WAIT is queued, and transmitted when the state
+transitions to AP_CONN_STATE_OPEN. Instead, when data arrives on the
+SOCKS connection to the OP on a stream in AP_CONN_STATE_CONNECT_WAIT
+(connection_edge_process_inbuf):
+
+- Check to see whether optimistic data is allowed at all (see below).
+- Check to see whether the exit node for this stream supports optimistic
+ data (according to tor-spec.txt section 6.2, this means that the
+ exit node's version number is at least 0.2.3.1-alpha). If you don't
+ know the exit node's version number (because it's not in your
+ hashtable of fingerprints, for example), assume it does *not* support
+ optimistic data.
+- If both are true, transmit the data on the stream.
+
+Also, when a stream transitions *to* AP_CONN_STATE_CONNECT_WAIT
+(connection_ap_handshake_send_begin), do the above checks, and
+immediately send any already-queued data if they pass.
+
+SOCKS clients (e.g. polipo) will also need to be patched to take
+advantage of optimistic data. The simplest solution would seem to be to
+just start sending data immediately after sending the SOCKS CONNECT
+command, without waiting for the SOCKS server reply. When the SOCKS
+client starts reading data back from the SOCKS server, it will first
+receive the SOCKS server reply, which may indicate success or failure.
+If success, it just continues reading the stream as normal. If failure,
+it does whatever it used to do when a SOCKS connection failed.
+
+Security implications:
+
+ORs (for sure the Exit, and possibly others, by watching the
+pattern of packets), as well as possibly end servers, will be able to
+tell that a particular client is using optimistic data. This of course
+has the potential to fingerprint clients, dividing the anonymity set.
+The usual kind of solution is suggested:
+
+- There is a boolean consensus parameter UseOptimisticData.
+- There is a 3-state (-1, 0, 1) configuration parameter
+ UseOptimisticData (or give it a distinct name if you like)
+ defaulting to -1.
+- If the configuration parameter is -1, the OP obeys the consensus
+ value; otherwise, it obeys the configuration parameter.
+
+It may be wise to set the consensus parameter to 1 at the same time as
+similar other client protocol changes are made (for example, a new
+circuit construction protocol) in order to not further subdivide the
+anonymity set.
+
+Specification:
+
+The current tor-spec has already been updated by proposal 174 to handle
+optimistic data. It says, in part:
+
+ If the exit node does not support optimistic data (i.e. its version
+ number is before 0.2.3.1-alpha), then the OP MUST wait for a
+ RELAY_CONNECTED cell before sending any data. If the exit node
+ supports optimistic data (i.e. its version number is 0.2.3.1-alpha
+ or later), then the OP MAY send RELAY_DATA cells immediately after
+ sending the RELAY_BEGIN cell (and before receiving either a
+ RELAY_CONNECTED or RELAY_END cell).
+
+Should the "MAY" be more specific, referring to the consensus
+parameters? Or does the existence of the configuration parameter
+override mean it's really "MAY", regardless?
+
+Compatibility:
+
+There are compatibility issues, as mentioned above. OPs MUST NOT send
+optimistic data to Exit nodes whose version numbers predate
+0.2.3.1-alpha. OPs MAY send optimistic data to Exit nodes whose version
+numbers match or follow that value.
+
+Implementation:
+
+My git diff is 42 lines long (+17 lines, -1 line), changing only the two
+functions mentioned above (connection_edge_process_inbuf and
+connection_ap_handshake_send_begin). This diff does not, however,
+handle the configuration options, or check the version number of the
+exit node.
+
+I have patched a command-line SOCKS client (webfetch) to use optimistic
+data. I have not attempted to patch polipo, but I have looked at it a
+bit, and it seems pretty straightforward. (Of course, if and when
+polipo is deprecated, whatever else speaks SOCKS to the OP should take
+advantage of optimistic data.)
+
+Performance and scalability notes:
+
+OPs may queue a little more data, if the SOCKS client pushes it faster
+than the OP can write it out. But that's also true today after the
+SOCKS CONNECT returns success, right?
1
0
04 Jun '11
commit 856226d684220b746b44c2f44a1c3d3d03f8384b
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 14:05:23 2011 -0700
Moving Kamran's menu to avoid namespace conflicts
My 'src/cli/menu' module was conflicting with his 'src/cli/menu.py' class so
I've moved his implementation to 'src/cli/menu_alt'.
---
src/cli/__init__.py | 2 +-
src/cli/controller.py | 6 +-
src/cli/menu.py | 438 -----------------------------------------
src/cli/menu_alt/__init__.py | 6 +
src/cli/menu_alt/menu.py | 439 ++++++++++++++++++++++++++++++++++++++++++
src/cli/menu_alt/menuItem.py | 41 ++++
src/util/menuItem.py | 41 ----
7 files changed, 490 insertions(+), 483 deletions(-)
diff --git a/src/cli/__init__.py b/src/cli/__init__.py
index 81fabeb..1564f68 100644
--- a/src/cli/__init__.py
+++ b/src/cli/__init__.py
@@ -2,5 +2,5 @@
Panels, popups, and handlers comprising the arm user interface.
"""
-__all__ = ["configPanel", "controller", "descriptorPopup", "headerPanel", "logPanel", "menu", "popups", "torrcPanel"]
+__all__ = ["configPanel", "controller", "descriptorPopup", "headerPanel", "logPanel", "popups", "torrcPanel"]
diff --git a/src/cli/controller.py b/src/cli/controller.py
index fac43a4..997d9d3 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -7,8 +7,8 @@ import time
import curses
import threading
-import cli.menu
import cli.menu.menu
+import cli.menu_alt.menu
import cli.popups
import cli.headerPanel
import cli.logPanel
@@ -512,10 +512,10 @@ def drawTorMonitor(stdscr, startTime):
elif key == ord('p') or key == ord('P'):
control.setPaused(not control.isPaused())
elif key == ord('n') or key == ord('N'):
- menu = cli.menu.Menu()
+ menu = cli.menu_alt.menu.Menu()
menuKeys = menu.showMenu(keys=menuKeys)
if menuKeys != []:
- for key in (ord('m'), ord('q'), ord('x')):
+ for key in (ord('n'), ord('q'), ord('x')):
if key in menuKeys:
menuKeys.remove(key)
overrideKey = key
diff --git a/src/cli/menu.py b/src/cli/menu.py
deleted file mode 100644
index 47e8d09..0000000
--- a/src/cli/menu.py
+++ /dev/null
@@ -1,438 +0,0 @@
-"""
-A drop-down menu for sending actions to panels.
-"""
-
-import curses
-
-import cli.controller
-import popups
-
-from cli.graphing.graphPanel import Bounds as GraphBounds
-from util import log, panel, uiTools, menuItem
-
-PARENTLEVEL, TOPLEVEL = (-1, 0)
-
-class Menu():
- """Displays a popup menu and sends keys to appropriate panels"""
-
- def __init__(self, item=None):
- DEFAULT_ROOT = menuItem.MenuItem(label="Root", children=(
- menuItem.MenuItem(label="File", children=(
- menuItem.MenuItem(label="Exit",
- callback=lambda item: self._callbackReturnKey(ord('q'))),)),
- menuItem.MenuItem(label="Logs", children=(
- menuItem.MenuItem(label="Events",
- callback=lambda item: self._callbackPressKey('log', ord('e'))),
- menuItem.MenuItem(label="Clear",
- callback=lambda item: self._callbackPressKey('log', ord('c'))),
- menuItem.MenuItem(label="Save",
- callback=lambda item: self._callbackPressKey('log', ord('a'))),
- menuItem.MenuItem(label="Filter",
- callback=lambda item: self._callbackPressKey('log', ord('f'))),
- menuItem.MenuItem(label="Duplicates", children=(
- menuItem.MenuItem(label="Hidden",
- callback=lambda item: self._callbackSet('log', 'showDuplicates', False, ord('u')),
- enabled=lambda: self._getItemEnabled('log', 'showDuplicates', False)),
- menuItem.MenuItem(label="Visible",
- callback=lambda item: self._callbackSet('log', 'showDuplicates', True, ord('u')),
- enabled=lambda: self._getItemEnabled('log', 'showDuplicates', True)),
- )))),
- menuItem.MenuItem(label="View", children=(
- menuItem.MenuItem(label="Graph",
- callback=lambda item: self._callbackView('graph')),
- menuItem.MenuItem(label="Connections",
- callback=lambda item: self._callbackView('conn')),
- menuItem.MenuItem(label="Configuration",
- callback=lambda item: self._callbackView('configState')),
- menuItem.MenuItem(label="Configuration File",
- callback=lambda item: self._callbackView('configFile')),)),
- menuItem.MenuItem(label="Graph", children=(
- menuItem.MenuItem(label="Stats",
- callback=lambda item: self._callbackPressKey('graph', ord('s'))),
- menuItem.MenuItem(label="Size", children=(
- menuItem.MenuItem(label="Increase",
- callback=lambda item: self._callbackPressKey('graph', ord('m'))),
- menuItem.MenuItem(label="Decrease",
- callback=lambda item: self._callbackPressKey('graph', ord('n'))),
- )),
- menuItem.MenuItem(label="Update Interval",
- callback=lambda item: self._callbackPressKey('graph', ord('i'))),
- menuItem.MenuItem(label="Bounds", children=(
- menuItem.MenuItem(label="Local Max",
- callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.LOCAL_MAX, ord('b')),
- enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.LOCAL_MAX)),
- menuItem.MenuItem(label="Global Max",
- callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.GLOBAL_MAX, ord('b')),
- enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.GLOBAL_MAX)),
- menuItem.MenuItem(label="Tight",
- callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.TIGHT, ord('b')),
- enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.TIGHT)),
- )),)),
- menuItem.MenuItem(label="Connections", children=(
- menuItem.MenuItem(label="Identity",
- callback=lambda item: self._callbackPressKey('conn', ord('l'))),
- menuItem.MenuItem(label="Resolver",
- callback=lambda item: self._callbackPressKey('conn', ord('u'))),
- menuItem.MenuItem(label="Sort Order",
- callback=lambda item: self._callbackPressKey('conn', ord('s'))),
- )),
- menuItem.MenuItem(label="Configuration" , children=(
- menuItem.MenuItem(label="Comments", children=(
- menuItem.MenuItem(label="Hidden",
- callback=lambda item: self._callbackSet('configFile', 'stripComments', True, ord('s')),
- enabled=lambda: self._getItemEnabled('configFile', 'stripComments', True)),
- menuItem.MenuItem(label="Visible",
- callback=lambda item: self._callbackSet('configFile', 'stripComments', False, ord('s')),
- enabled=lambda: self._getItemEnabled('configFile', 'stripComments', False)),
- )),
- menuItem.MenuItem(label="Reload",
- callback=lambda item: self._callbackPressKey('configFile', ord('r'))),
- menuItem.MenuItem(label="Reset Tor",
- callback=lambda item: self._callbackReturnKey(ord('x'))),))
- ))
-
- self._first = [0]
- self._selection = [0]
-
- if item and item.isParent():
- self._rootItem = item
- else:
- self._rootItem = DEFAULT_ROOT
-
- def showMenu(self, keys=[]):
- keys.reverse()
- returnkeys = []
-
- popup, width, height = popups.init(height=3)
- if popup:
- try:
- while True:
- popup.win.erase()
- popup.win.box()
-
- self._drawTopLevel(popup, width, height)
-
- popup.win.refresh()
-
- control = cli.controller.getController()
-
- if keys == []:
- key = control.getScreen().getch()
- else:
- key = keys.pop()
-
- if key == curses.KEY_RIGHT:
- self._moveTopLevelRight(width)
- elif key == curses.KEY_LEFT:
- self._moveTopLevelLeft(width)
- elif key == curses.KEY_DOWN:
- cascaded, returnkeys = self._cascadeNLevel()
- break
- elif key == 27:
- break
- elif uiTools.isSelectionKey(key):
- self._handleEvent()
- break
-
- finally:
- popups.finalize()
-
- return returnkeys
-
- def _appendLevel(self):
- self._first.append(0)
- self._selection.append(0)
-
- def _removeLevel(self):
- self._first.pop()
- self._selection.pop()
-
- def _getCurrentTopLevelItem(self):
- index = self._first[TOPLEVEL] + self._selection[TOPLEVEL]
- return self._rootItem.getChildren()[index]
-
- def _getCurrentItem(self, level=0):
- item = self._rootItem
- if level == 0:
- sums = [sum(values) for values in zip(self._first, self._selection)]
- else:
- sums = [sum(values) for values in zip(self._first[:level], self._selection[:level])]
-
- for index in sums:
- if item.isParent():
- item = item.getChildren()[index]
- else:
- break
-
- return item
-
- def _calculateTopLevelWidths(self, width=0):
- labels = [menuItem.getLabel() for menuItem in self._rootItem.getChildren()]
-
- # width per label is set according to the longest label
- labelwidth = max(map(len, labels)) + 2
-
- # total number of labels that can be printed in supplied width
- printable = min(width / labelwidth - 1, self._rootItem.getChildrenCount())
-
- return (labelwidth, printable)
-
- def _calculateNLevelWidths(self, level=0):
- parent = self._getCurrentItem(level)
-
- if parent.isLeaf():
- return 0
-
- labels = [menuItem.getLabel() for menuItem in parent.getChildren()]
-
- labelwidth = max(map(len, labels))
-
- return labelwidth
-
- def _calculateNLevelHeights(self, height=0, level=0):
- control = cli.controller.getController()
- height, _ = control.getScreen().getmaxyx()
- topSize = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
- height = height - topSize
-
- parent = self._getCurrentItem(level)
-
- if parent.isLeaf():
- return 0
-
- printable = min(height - 4, parent.getChildrenCount())
-
- return printable if printable else parent.getChildrenCount()
-
- def _moveTopLevelRight(self, width):
- _, printable = self._calculateTopLevelWidths(width)
-
- if self._selection[TOPLEVEL] < printable - 1:
- self._selection[TOPLEVEL] = self._selection[TOPLEVEL] + 1
- else:
- self._selection[TOPLEVEL] = 0
- if printable < self._rootItem.getChildrenCount():
- self._first[TOPLEVEL] = (self._first[TOPLEVEL] + printable) % self._rootItem.getChildrenCount()
-
- if self._first[TOPLEVEL] + self._selection[TOPLEVEL] == self._rootItem.getChildrenCount():
- self._first[TOPLEVEL] = 0
- self._selection[TOPLEVEL] = 0
-
- def _moveTopLevelLeft(self, width):
- _, printable = self._calculateTopLevelWidths(width)
-
- if self._selection[TOPLEVEL] > 0:
- self._selection[TOPLEVEL] = self._selection[TOPLEVEL] - 1
- else:
- if self._first[TOPLEVEL] == 0:
- self._first[TOPLEVEL] = ((self._rootItem.getChildrenCount() / printable) * printable) % self._rootItem.getChildrenCount()
- else:
- self._first[TOPLEVEL] = abs(self._first[TOPLEVEL] - printable) % self._rootItem.getChildrenCount()
- self._selection[TOPLEVEL] = self._rootItem.getChildrenCount() - self._first[TOPLEVEL] - 1
-
- if self._selection[TOPLEVEL] > printable:
- self._selection[TOPLEVEL] = printable - 1
-
- def _drawTopLevel(self, popup, width, height):
- labelwidth, printable = self._calculateTopLevelWidths(width)
- children = self._rootItem.getChildren()[self._first[TOPLEVEL]:self._first[TOPLEVEL] + printable]
-
- top = 1
- left = 1
- for (index, item) in enumerate(children):
- labelformat = curses.A_STANDOUT if index == self._selection[TOPLEVEL] else curses.A_NORMAL
-
- popup.addch(top, left, curses.ACS_VLINE)
- left = left + 1
- popup.addstr(top, left, item.getLabel().center(labelwidth), labelformat)
- left = left + labelwidth
-
- popup.addch(top, left, curses.ACS_VLINE)
- left = left + 1
-
- def _cascadeNLevel(self):
- parent = self._getCurrentItem()
-
- if parent.isLeaf():
- return (False, [])
-
- self._appendLevel()
-
- labelwidth = self._calculateNLevelWidths(level=PARENTLEVEL)
- printable = self._calculateNLevelHeights(level=PARENTLEVEL)
-
- toplabelwidth, _ = self._calculateTopLevelWidths()
- left = (toplabelwidth + 2) * self._selection[TOPLEVEL]
-
- popup, width, height = popups.init(height=printable+2, width=labelwidth+2, top=2, left=left)
-
- while self._getCurrentItem().isEnabled() == False:
- self._moveNLevelDown(height)
-
- if popup.win:
- returnkeys = []
- try:
- while True:
- popup.win.erase()
- popup.win.box()
-
- self._drawNLevel(popup, width, height)
-
- popup.win.refresh()
-
- control = cli.controller.getController()
- key = control.getScreen().getch()
-
- if key == curses.KEY_DOWN:
- self._moveNLevelDown(height)
- elif key == curses.KEY_UP:
- self._moveNLevelUp(height)
- elif key == curses.KEY_RIGHT:
- cascaded, returnkeys = self._cascadeNLevel()
- if cascaded == False:
- index = self._first[TOPLEVEL] + self._selection[TOPLEVEL] + 1
- returnkeys.append(ord('m'))
- for i in range(index):
- returnkeys.append(curses.KEY_RIGHT)
- returnkeys.append(curses.KEY_DOWN)
- break
- elif key == curses.KEY_LEFT:
- index = self._first[TOPLEVEL] + self._selection[TOPLEVEL] - 1
- index = index % self._rootItem.getChildrenCount()
- returnkeys.append(ord('m'))
- for i in range(index):
- returnkeys.append(curses.KEY_RIGHT)
- returnkeys.append(curses.KEY_DOWN)
- break
- elif key == 27:
- self._removeLevel()
- break
- elif uiTools.isSelectionKey(key):
- returnkey = self._handleEvent()
- if returnkey:
- returnkeys.append(returnkey)
- self._removeLevel()
- break
-
- finally:
- popups.finalize()
-
- return (True, returnkeys)
-
- return (False, [])
-
- def _drawNLevel(self, popup, width, height):
- printable = self._calculateNLevelHeights(level=PARENTLEVEL)
- parent = self._getCurrentItem(level=PARENTLEVEL)
- children = parent.getChildren()[self._first[PARENTLEVEL]:self._first[PARENTLEVEL] + printable]
-
- top = 1
- left = 1
- for (index, item) in enumerate(children):
- labelformat = curses.A_STANDOUT if index == self._selection[PARENTLEVEL] else curses.A_NORMAL
-
- if not item.isEnabled():
- labelformat = labelformat | uiTools.getColor('yellow')
-
- popup.addstr(top, left, item.getLabel(), labelformat)
- top = top + 1
-
- def _moveNLevelDown(self, height):
- printable = self._calculateNLevelHeights(level=PARENTLEVEL)
- parent = self._getCurrentItem(level=PARENTLEVEL)
-
- if self._selection[PARENTLEVEL] < printable - 1:
- self._selection[PARENTLEVEL] = self._selection[PARENTLEVEL] + 1
- else:
- self._selection[PARENTLEVEL] = 0
- if printable < parent.getChildrenCount():
- self._first[PARENTLEVEL] = (self._first[PARENTLEVEL] + printable) % parent.getChildrenCount()
-
- if self._first[PARENTLEVEL] + self._selection[PARENTLEVEL] == parent.getChildrenCount():
- self._first[PARENTLEVEL] = 0
- self._selection[PARENTLEVEL] = 0
-
- while self._getCurrentItem().isEnabled() == False:
- self._moveNLevelDown(height)
-
- def _moveNLevelUp(self, height):
- printable = self._calculateNLevelHeights(level=PARENTLEVEL)
- parent = self._getCurrentItem(level=PARENTLEVEL)
-
- if self._selection[PARENTLEVEL] > 0:
- self._selection[PARENTLEVEL] = self._selection[PARENTLEVEL] - 1
- else:
- if self._first[PARENTLEVEL] == 0:
- self._first[PARENTLEVEL] = ((parent.getChildrenCount() / printable) * printable) % parent.getChildrenCount()
- else:
- self._first[PARENTLEVEL] = abs(self._first[PARENTLEVEL] - printable) % parent.getChildrenCount()
- self._selection[PARENTLEVEL] = parent.getChildrenCount() - self._first[PARENTLEVEL] - 1
-
- if self._selection[PARENTLEVEL] > printable:
- self._selection[PARENTLEVEL] = printable - 1
-
- while self._getCurrentItem().isEnabled() == False:
- self._moveNLevelUp(height)
-
- def _handleEvent(self):
- item = self._getCurrentItem()
-
- if item.isLeaf():
- return item.select()
- else:
- self._cascadeNLevel()
-
- def _callbackDefault(self, item):
- log.log(log.NOTICE, "%s selected" % item.getLabel())
-
- def _callbackView(self, panelname):
- control = cli.controller.getController()
-
- start = control.getPage()
- panels = control.getDisplayPanels(includeSticky=False)
- panelnames = [panel.getName() for panel in panels]
- while not panelname in panelnames:
- control.nextPage()
- panels = control.getDisplayPanels(includeSticky=False)
- panelnames = [panel.getName() for panel in panels]
-
- if control.getPage() == start:
- log.log(log.ERR, "Panel %s not found" % panelname)
- break
-
- def _getItemEnabled(self, panel, attr, value):
- control = cli.controller.getController()
- if control:
- panel = control.getPanel(panel)
-
- if panel:
- return getattr(panel, attr, None) != value
-
- return False
-
- def _callbackSet(self, panel, attr, value, key=None):
- control = cli.controller.getController()
- panel = control.getPanel(panel)
-
- panelattr = getattr(panel, attr, None)
-
- if panelattr != None:
- if hasattr(panelattr, '__call__'):
- panelattr(value)
- elif panelattr != value and key != None:
- start = panelattr
- while panelattr != value:
- panel.handleKey(key)
- panelattr = getattr(panel, attr, None)
- if panelattr == start:
- log.log(log.ERR, "Could not set %s.%s" % (panel, attr))
- break
-
- def _callbackPressKey(self, panel, key):
- control = cli.controller.getController()
- panel = control.getPanel(panel)
- panel.handleKey(key)
-
- def _callbackReturnKey(self, key):
- return key
-
diff --git a/src/cli/menu_alt/__init__.py b/src/cli/menu_alt/__init__.py
new file mode 100644
index 0000000..6d7b6b7
--- /dev/null
+++ b/src/cli/menu_alt/__init__.py
@@ -0,0 +1,6 @@
+"""
+Resources for displaying the menu.
+"""
+
+__all__ = ["menuItem", "menu"]
+
diff --git a/src/cli/menu_alt/menu.py b/src/cli/menu_alt/menu.py
new file mode 100644
index 0000000..7e10366
--- /dev/null
+++ b/src/cli/menu_alt/menu.py
@@ -0,0 +1,439 @@
+"""
+A drop-down menu for sending actions to panels.
+"""
+
+import curses
+
+import cli.controller
+import cli.menu_alt.menuItem as menuItem
+import cli.popups
+
+from cli.graphing.graphPanel import Bounds as GraphBounds
+from util import log, panel, uiTools
+
+PARENTLEVEL, TOPLEVEL = (-1, 0)
+
+class Menu():
+ """Displays a popup menu and sends keys to appropriate panels"""
+
+ def __init__(self, item=None):
+ DEFAULT_ROOT = menuItem.MenuItem(label="Root", children=(
+ menuItem.MenuItem(label="File", children=(
+ menuItem.MenuItem(label="Exit",
+ callback=lambda item: self._callbackReturnKey(ord('q'))),)),
+ menuItem.MenuItem(label="Logs", children=(
+ menuItem.MenuItem(label="Events",
+ callback=lambda item: self._callbackPressKey('log', ord('e'))),
+ menuItem.MenuItem(label="Clear",
+ callback=lambda item: self._callbackPressKey('log', ord('c'))),
+ menuItem.MenuItem(label="Save",
+ callback=lambda item: self._callbackPressKey('log', ord('a'))),
+ menuItem.MenuItem(label="Filter",
+ callback=lambda item: self._callbackPressKey('log', ord('f'))),
+ menuItem.MenuItem(label="Duplicates", children=(
+ menuItem.MenuItem(label="Hidden",
+ callback=lambda item: self._callbackSet('log', 'showDuplicates', False, ord('u')),
+ enabled=lambda: self._getItemEnabled('log', 'showDuplicates', False)),
+ menuItem.MenuItem(label="Visible",
+ callback=lambda item: self._callbackSet('log', 'showDuplicates', True, ord('u')),
+ enabled=lambda: self._getItemEnabled('log', 'showDuplicates', True)),
+ )))),
+ menuItem.MenuItem(label="View", children=(
+ menuItem.MenuItem(label="Graph",
+ callback=lambda item: self._callbackView('graph')),
+ menuItem.MenuItem(label="Connections",
+ callback=lambda item: self._callbackView('conn')),
+ menuItem.MenuItem(label="Configuration",
+ callback=lambda item: self._callbackView('configState')),
+ menuItem.MenuItem(label="Configuration File",
+ callback=lambda item: self._callbackView('configFile')),)),
+ menuItem.MenuItem(label="Graph", children=(
+ menuItem.MenuItem(label="Stats",
+ callback=lambda item: self._callbackPressKey('graph', ord('s'))),
+ menuItem.MenuItem(label="Size", children=(
+ menuItem.MenuItem(label="Increase",
+ callback=lambda item: self._callbackPressKey('graph', ord('m'))),
+ menuItem.MenuItem(label="Decrease",
+ callback=lambda item: self._callbackPressKey('graph', ord('n'))),
+ )),
+ menuItem.MenuItem(label="Update Interval",
+ callback=lambda item: self._callbackPressKey('graph', ord('i'))),
+ menuItem.MenuItem(label="Bounds", children=(
+ menuItem.MenuItem(label="Local Max",
+ callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.LOCAL_MAX, ord('b')),
+ enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.LOCAL_MAX)),
+ menuItem.MenuItem(label="Global Max",
+ callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.GLOBAL_MAX, ord('b')),
+ enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.GLOBAL_MAX)),
+ menuItem.MenuItem(label="Tight",
+ callback=lambda item: self._callbackSet('graph', 'bounds', GraphBounds.TIGHT, ord('b')),
+ enabled=lambda: self._getItemEnabled('graph', 'bounds', GraphBounds.TIGHT)),
+ )),)),
+ menuItem.MenuItem(label="Connections", children=(
+ menuItem.MenuItem(label="Identity",
+ callback=lambda item: self._callbackPressKey('conn', ord('l'))),
+ menuItem.MenuItem(label="Resolver",
+ callback=lambda item: self._callbackPressKey('conn', ord('u'))),
+ menuItem.MenuItem(label="Sort Order",
+ callback=lambda item: self._callbackPressKey('conn', ord('s'))),
+ )),
+ menuItem.MenuItem(label="Configuration" , children=(
+ menuItem.MenuItem(label="Comments", children=(
+ menuItem.MenuItem(label="Hidden",
+ callback=lambda item: self._callbackSet('configFile', 'stripComments', True, ord('s')),
+ enabled=lambda: self._getItemEnabled('configFile', 'stripComments', True)),
+ menuItem.MenuItem(label="Visible",
+ callback=lambda item: self._callbackSet('configFile', 'stripComments', False, ord('s')),
+ enabled=lambda: self._getItemEnabled('configFile', 'stripComments', False)),
+ )),
+ menuItem.MenuItem(label="Reload",
+ callback=lambda item: self._callbackPressKey('configFile', ord('r'))),
+ menuItem.MenuItem(label="Reset Tor",
+ callback=lambda item: self._callbackReturnKey(ord('x'))),))
+ ))
+
+ self._first = [0]
+ self._selection = [0]
+
+ if item and item.isParent():
+ self._rootItem = item
+ else:
+ self._rootItem = DEFAULT_ROOT
+
+ def showMenu(self, keys=[]):
+ keys.reverse()
+ returnkeys = []
+
+ popup, width, height = cli.popups.init(height=3)
+ if popup:
+ try:
+ while True:
+ popup.win.erase()
+ popup.win.box()
+
+ self._drawTopLevel(popup, width, height)
+
+ popup.win.refresh()
+
+ control = cli.controller.getController()
+
+ if keys == []:
+ key = control.getScreen().getch()
+ else:
+ key = keys.pop()
+
+ if key == curses.KEY_RIGHT:
+ self._moveTopLevelRight(width)
+ elif key == curses.KEY_LEFT:
+ self._moveTopLevelLeft(width)
+ elif key == curses.KEY_DOWN:
+ cascaded, returnkeys = self._cascadeNLevel()
+ break
+ elif key == 27:
+ break
+ elif uiTools.isSelectionKey(key):
+ self._handleEvent()
+ break
+
+ finally:
+ cli.popups.finalize()
+
+ return returnkeys
+
+ def _appendLevel(self):
+ self._first.append(0)
+ self._selection.append(0)
+
+ def _removeLevel(self):
+ self._first.pop()
+ self._selection.pop()
+
+ def _getCurrentTopLevelItem(self):
+ index = self._first[TOPLEVEL] + self._selection[TOPLEVEL]
+ return self._rootItem.getChildren()[index]
+
+ def _getCurrentItem(self, level=0):
+ item = self._rootItem
+ if level == 0:
+ sums = [sum(values) for values in zip(self._first, self._selection)]
+ else:
+ sums = [sum(values) for values in zip(self._first[:level], self._selection[:level])]
+
+ for index in sums:
+ if item.isParent():
+ item = item.getChildren()[index]
+ else:
+ break
+
+ return item
+
+ def _calculateTopLevelWidths(self, width=0):
+ labels = [menuItem.getLabel() for menuItem in self._rootItem.getChildren()]
+
+ # width per label is set according to the longest label
+ labelwidth = max(map(len, labels)) + 2
+
+ # total number of labels that can be printed in supplied width
+ printable = min(width / labelwidth - 1, self._rootItem.getChildrenCount())
+
+ return (labelwidth, printable)
+
+ def _calculateNLevelWidths(self, level=0):
+ parent = self._getCurrentItem(level)
+
+ if parent.isLeaf():
+ return 0
+
+ labels = [menuItem.getLabel() for menuItem in parent.getChildren()]
+
+ labelwidth = max(map(len, labels))
+
+ return labelwidth
+
+ def _calculateNLevelHeights(self, height=0, level=0):
+ control = cli.controller.getController()
+ height, _ = control.getScreen().getmaxyx()
+ topSize = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
+ height = height - topSize
+
+ parent = self._getCurrentItem(level)
+
+ if parent.isLeaf():
+ return 0
+
+ printable = min(height - 4, parent.getChildrenCount())
+
+ return printable if printable else parent.getChildrenCount()
+
+ def _moveTopLevelRight(self, width):
+ _, printable = self._calculateTopLevelWidths(width)
+
+ if self._selection[TOPLEVEL] < printable - 1:
+ self._selection[TOPLEVEL] = self._selection[TOPLEVEL] + 1
+ else:
+ self._selection[TOPLEVEL] = 0
+ if printable < self._rootItem.getChildrenCount():
+ self._first[TOPLEVEL] = (self._first[TOPLEVEL] + printable) % self._rootItem.getChildrenCount()
+
+ if self._first[TOPLEVEL] + self._selection[TOPLEVEL] == self._rootItem.getChildrenCount():
+ self._first[TOPLEVEL] = 0
+ self._selection[TOPLEVEL] = 0
+
+ def _moveTopLevelLeft(self, width):
+ _, printable = self._calculateTopLevelWidths(width)
+
+ if self._selection[TOPLEVEL] > 0:
+ self._selection[TOPLEVEL] = self._selection[TOPLEVEL] - 1
+ else:
+ if self._first[TOPLEVEL] == 0:
+ self._first[TOPLEVEL] = ((self._rootItem.getChildrenCount() / printable) * printable) % self._rootItem.getChildrenCount()
+ else:
+ self._first[TOPLEVEL] = abs(self._first[TOPLEVEL] - printable) % self._rootItem.getChildrenCount()
+ self._selection[TOPLEVEL] = self._rootItem.getChildrenCount() - self._first[TOPLEVEL] - 1
+
+ if self._selection[TOPLEVEL] > printable:
+ self._selection[TOPLEVEL] = printable - 1
+
+ def _drawTopLevel(self, popup, width, height):
+ labelwidth, printable = self._calculateTopLevelWidths(width)
+ children = self._rootItem.getChildren()[self._first[TOPLEVEL]:self._first[TOPLEVEL] + printable]
+
+ top = 1
+ left = 1
+ for (index, item) in enumerate(children):
+ labelformat = curses.A_STANDOUT if index == self._selection[TOPLEVEL] else curses.A_NORMAL
+
+ popup.addch(top, left, curses.ACS_VLINE)
+ left = left + 1
+ popup.addstr(top, left, item.getLabel().center(labelwidth), labelformat)
+ left = left + labelwidth
+
+ popup.addch(top, left, curses.ACS_VLINE)
+ left = left + 1
+
+ def _cascadeNLevel(self):
+ parent = self._getCurrentItem()
+
+ if parent.isLeaf():
+ return (False, [])
+
+ self._appendLevel()
+
+ labelwidth = self._calculateNLevelWidths(level=PARENTLEVEL)
+ printable = self._calculateNLevelHeights(level=PARENTLEVEL)
+
+ toplabelwidth, _ = self._calculateTopLevelWidths()
+ left = (toplabelwidth + 2) * self._selection[TOPLEVEL]
+
+ popup, width, height = cli.popups.init(height=printable+2, width=labelwidth+2, top=2, left=left)
+
+ while self._getCurrentItem().isEnabled() == False:
+ self._moveNLevelDown(height)
+
+ if popup.win:
+ returnkeys = []
+ try:
+ while True:
+ popup.win.erase()
+ popup.win.box()
+
+ self._drawNLevel(popup, width, height)
+
+ popup.win.refresh()
+
+ control = cli.controller.getController()
+ key = control.getScreen().getch()
+
+ if key == curses.KEY_DOWN:
+ self._moveNLevelDown(height)
+ elif key == curses.KEY_UP:
+ self._moveNLevelUp(height)
+ elif key == curses.KEY_RIGHT:
+ cascaded, returnkeys = self._cascadeNLevel()
+ if cascaded == False:
+ index = self._first[TOPLEVEL] + self._selection[TOPLEVEL] + 1
+ returnkeys.append(ord('n'))
+ for i in range(index):
+ returnkeys.append(curses.KEY_RIGHT)
+ returnkeys.append(curses.KEY_DOWN)
+ break
+ elif key == curses.KEY_LEFT:
+ index = self._first[TOPLEVEL] + self._selection[TOPLEVEL] - 1
+ index = index % self._rootItem.getChildrenCount()
+ returnkeys.append(ord('n'))
+ for i in range(index):
+ returnkeys.append(curses.KEY_RIGHT)
+ returnkeys.append(curses.KEY_DOWN)
+ break
+ elif key == 27:
+ self._removeLevel()
+ break
+ elif uiTools.isSelectionKey(key):
+ returnkey = self._handleEvent()
+ if returnkey:
+ returnkeys.append(returnkey)
+ self._removeLevel()
+ break
+
+ finally:
+ cli.popups.finalize()
+
+ return (True, returnkeys)
+
+ return (False, [])
+
+ def _drawNLevel(self, popup, width, height):
+ printable = self._calculateNLevelHeights(level=PARENTLEVEL)
+ parent = self._getCurrentItem(level=PARENTLEVEL)
+ children = parent.getChildren()[self._first[PARENTLEVEL]:self._first[PARENTLEVEL] + printable]
+
+ top = 1
+ left = 1
+ for (index, item) in enumerate(children):
+ labelformat = curses.A_STANDOUT if index == self._selection[PARENTLEVEL] else curses.A_NORMAL
+
+ if not item.isEnabled():
+ labelformat = labelformat | uiTools.getColor('yellow')
+
+ popup.addstr(top, left, item.getLabel(), labelformat)
+ top = top + 1
+
+ def _moveNLevelDown(self, height):
+ printable = self._calculateNLevelHeights(level=PARENTLEVEL)
+ parent = self._getCurrentItem(level=PARENTLEVEL)
+
+ if self._selection[PARENTLEVEL] < printable - 1:
+ self._selection[PARENTLEVEL] = self._selection[PARENTLEVEL] + 1
+ else:
+ self._selection[PARENTLEVEL] = 0
+ if printable < parent.getChildrenCount():
+ self._first[PARENTLEVEL] = (self._first[PARENTLEVEL] + printable) % parent.getChildrenCount()
+
+ if self._first[PARENTLEVEL] + self._selection[PARENTLEVEL] == parent.getChildrenCount():
+ self._first[PARENTLEVEL] = 0
+ self._selection[PARENTLEVEL] = 0
+
+ while self._getCurrentItem().isEnabled() == False:
+ self._moveNLevelDown(height)
+
+ def _moveNLevelUp(self, height):
+ printable = self._calculateNLevelHeights(level=PARENTLEVEL)
+ parent = self._getCurrentItem(level=PARENTLEVEL)
+
+ if self._selection[PARENTLEVEL] > 0:
+ self._selection[PARENTLEVEL] = self._selection[PARENTLEVEL] - 1
+ else:
+ if self._first[PARENTLEVEL] == 0:
+ self._first[PARENTLEVEL] = ((parent.getChildrenCount() / printable) * printable) % parent.getChildrenCount()
+ else:
+ self._first[PARENTLEVEL] = abs(self._first[PARENTLEVEL] - printable) % parent.getChildrenCount()
+ self._selection[PARENTLEVEL] = parent.getChildrenCount() - self._first[PARENTLEVEL] - 1
+
+ if self._selection[PARENTLEVEL] > printable:
+ self._selection[PARENTLEVEL] = printable - 1
+
+ while self._getCurrentItem().isEnabled() == False:
+ self._moveNLevelUp(height)
+
+ def _handleEvent(self):
+ item = self._getCurrentItem()
+
+ if item.isLeaf():
+ return item.select()
+ else:
+ self._cascadeNLevel()
+
+ def _callbackDefault(self, item):
+ log.log(log.NOTICE, "%s selected" % item.getLabel())
+
+ def _callbackView(self, panelname):
+ control = cli.controller.getController()
+
+ start = control.getPage()
+ panels = control.getDisplayPanels(includeSticky=False)
+ panelnames = [panel.getName() for panel in panels]
+ while not panelname in panelnames:
+ control.nextPage()
+ panels = control.getDisplayPanels(includeSticky=False)
+ panelnames = [panel.getName() for panel in panels]
+
+ if control.getPage() == start:
+ log.log(log.ERR, "Panel %s not found" % panelname)
+ break
+
+ def _getItemEnabled(self, panel, attr, value):
+ control = cli.controller.getController()
+ if control:
+ panel = control.getPanel(panel)
+
+ if panel:
+ return getattr(panel, attr, None) != value
+
+ return False
+
+ def _callbackSet(self, panel, attr, value, key=None):
+ control = cli.controller.getController()
+ panel = control.getPanel(panel)
+
+ panelattr = getattr(panel, attr, None)
+
+ if panelattr != None:
+ if hasattr(panelattr, '__call__'):
+ panelattr(value)
+ elif panelattr != value and key != None:
+ start = panelattr
+ while panelattr != value:
+ panel.handleKey(key)
+ panelattr = getattr(panel, attr, None)
+ if panelattr == start:
+ log.log(log.ERR, "Could not set %s.%s" % (panel, attr))
+ break
+
+ def _callbackPressKey(self, panel, key):
+ control = cli.controller.getController()
+ panel = control.getPanel(panel)
+ panel.handleKey(key)
+
+ def _callbackReturnKey(self, key):
+ return key
+
diff --git a/src/cli/menu_alt/menuItem.py b/src/cli/menu_alt/menuItem.py
new file mode 100644
index 0000000..277c78a
--- /dev/null
+++ b/src/cli/menu_alt/menuItem.py
@@ -0,0 +1,41 @@
+"""
+Menu Item class, used by the drop-down menus.
+"""
+
+class MenuItem():
+ """
+ Contains title, callback handler and possible children.
+ """
+
+ def __init__(self, label=None, callback=None, children=[], enabled=None):
+ self._label = label
+ self._callback = callback
+ self._children = children
+ self._enabled = enabled
+
+ def getLabel(self):
+ return self._label
+
+ def isLeaf(self):
+ return self._children == []
+
+ def isParent(self):
+ return self._children != []
+
+ def isEnabled(self):
+ if self._enabled == None:
+ return True
+ elif hasattr(self._enabled, '__call__'):
+ return self._enabled()
+ else:
+ return self._enabled
+
+ def getChildren(self):
+ return self._children
+
+ def getChildrenCount(self):
+ return len(self._children)
+
+ def select(self):
+ return self._callback(self)
+
diff --git a/src/util/menuItem.py b/src/util/menuItem.py
deleted file mode 100644
index 277c78a..0000000
--- a/src/util/menuItem.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
-Menu Item class, used by the drop-down menus.
-"""
-
-class MenuItem():
- """
- Contains title, callback handler and possible children.
- """
-
- def __init__(self, label=None, callback=None, children=[], enabled=None):
- self._label = label
- self._callback = callback
- self._children = children
- self._enabled = enabled
-
- def getLabel(self):
- return self._label
-
- def isLeaf(self):
- return self._children == []
-
- def isParent(self):
- return self._children != []
-
- def isEnabled(self):
- if self._enabled == None:
- return True
- elif hasattr(self._enabled, '__call__'):
- return self._enabled()
- else:
- return self._enabled
-
- def getChildren(self):
- return self._children
-
- def getChildrenCount(self):
- return len(self._children)
-
- def select(self):
- return self._callback(self)
-
1
0
commit c1aecf68b13bf2eb12a104b98dab9dde6a2a46f0
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 4 13:52:09 2011 -0700
Alternative menu implementation
This is a smaller and (I think) simpler implementation of a menu. Its model
and presentation logic are done so the menu can be opened and used, but
handlers haven't been attached yet so nothing happens when a selection is
made.
---
README | 5 +
src/cli/controller.py | 5 +-
src/cli/menu/__init__.py | 6 ++
src/cli/menu/item.py | 146 ++++++++++++++++++++++++++++++
src/cli/menu/menu.py | 222 ++++++++++++++++++++++++++++++++++++++++++++++
src/cli/popups.py | 15 ++--
src/util/panel.py | 2 +-
7 files changed, 393 insertions(+), 8 deletions(-)
diff --git a/README b/README
index b20ec1c..0f26163 100644
--- a/README
+++ b/README
@@ -176,6 +176,11 @@ Layout:
connEntry.py - individual connections to or from the system
entries.py - common parent for connPanel display entries
+ menu/
+ __init__.py
+ menu.py - provides an interactive menu
+ item.py - individual items within the menu
+
__init__.py
controller.py - main display loop, handling input and layout
headerPanel.py - top of all pages, providing general information
diff --git a/src/cli/controller.py b/src/cli/controller.py
index fcbaed5..fac43a4 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -8,6 +8,7 @@ import curses
import threading
import cli.menu
+import cli.menu.menu
import cli.popups
import cli.headerPanel
import cli.logPanel
@@ -510,7 +511,7 @@ def drawTorMonitor(stdscr, startTime):
control.prevPage()
elif key == ord('p') or key == ord('P'):
control.setPaused(not control.isPaused())
- elif key == ord('m') or key == ord('M'):
+ elif key == ord('n') or key == ord('N'):
menu = cli.menu.Menu()
menuKeys = menu.showMenu(keys=menuKeys)
if menuKeys != []:
@@ -518,6 +519,8 @@ def drawTorMonitor(stdscr, startTime):
if key in menuKeys:
menuKeys.remove(key)
overrideKey = key
+ elif key == ord('m') or key == ord('M'):
+ cli.menu.menu.showMenu()
elif key == ord('q') or key == ord('Q'):
# provides prompt to confirm that arm should exit
if CONFIG["features.confirmQuit"]:
diff --git a/src/cli/menu/__init__.py b/src/cli/menu/__init__.py
new file mode 100644
index 0000000..e6b5f10
--- /dev/null
+++ b/src/cli/menu/__init__.py
@@ -0,0 +1,6 @@
+"""
+Resources for displaying the menu.
+"""
+
+__all__ = ["item", "menu"]
+
diff --git a/src/cli/menu/item.py b/src/cli/menu/item.py
new file mode 100644
index 0000000..bfc7ffd
--- /dev/null
+++ b/src/cli/menu/item.py
@@ -0,0 +1,146 @@
+"""
+Menu item, representing an option in the drop-down menu.
+"""
+
+class MenuItem():
+ """
+ Option in a drop-down menu.
+ """
+
+ def __init__(self, label, callback):
+ self._label = label
+ self._callback = callback
+ self._parent = None
+
+ def getLabel(self):
+ """
+ Provides a tuple of three strings representing the prefix, label, and
+ suffix for this item.
+ """
+
+ return ("", self._label, "")
+
+ def getParent(self):
+ """
+ Provides the Submenu we're contained within.
+ """
+
+ return self._parent
+
+ def getHierarchy(self):
+ """
+ Provides a list with all of our parents, up to the root.
+ """
+
+ myHierarchy = [self]
+ while myHierarchy[-1].getParent():
+ myHierarchy.append(myHierarchy[-1].getParent())
+
+ myHierarchy.reverse()
+ return myHierarchy
+
+ def getRoot(self):
+ """
+ Provides the base submenu we belong to.
+ """
+
+ if self._parent: return self._parent.getRoot()
+ else: return self
+
+ def select(self):
+ """
+ Performs the callback for the menu item, returning true if we should close
+ the menu and false otherwise.
+ """
+
+ if self._callback: return self._callback()
+ else: return False
+
+ def next(self):
+ """
+ Provides the next option for the submenu we're in, raising a ValueError
+ if we don't have a parent.
+ """
+
+ return self._getSibling(1)
+
+ def prev(self):
+ """
+ Provides the previous option for the submenu we're in, raising a ValueError
+ if we don't have a parent.
+ """
+
+ return self._getSibling(-1)
+
+ def _getSibling(self, offset):
+ """
+ Provides our sibling with a given index offset from us, raising a
+ ValueError if we don't have a parent.
+
+ Arguments:
+ offset - index offset for the sibling to be returned
+ """
+
+ if self._parent:
+ mySiblings = self._parent.getChildren()
+
+ try:
+ myIndex = mySiblings.index(self)
+ return mySiblings[(myIndex + offset) % len(mySiblings)]
+ except ValueError:
+ # We expect a bidirectional references between submenus and their
+ # children. If we don't have this then our menu's screwed up.
+
+ msg = "The '%s' submenu doesn't contain '%s' (children: '%s')" % (self, self._parent, "', '".join(mySiblings))
+ raise ValueError(msg)
+ else: raise ValueError("Menu option '%s' doesn't have a parent" % self)
+
+ def __str__(self):
+ return self._label
+
+class Submenu(MenuItem):
+ """
+ Menu item that lists other menu options.
+ """
+
+ def __init__(self, label):
+ MenuItem.__init__(self, label, None)
+ self._children = []
+
+ def getLabel(self):
+ """
+ Provides our label with a ">" suffix to indicate that we have suboptions.
+ """
+
+ myLabel = MenuItem.getLabel(self)[1]
+ return ("", myLabel, " >")
+
+ def add(self, menuItem):
+ """
+ Adds the given menu item to our listing. This raises a ValueError if the
+ item already has a parent.
+
+ Arguments:
+ menuItem - menu option to be added
+ """
+
+ if menuItem.getParent():
+ raise ValueError("Menu option '%s' already has a parent" % menuItem)
+ else:
+ menuItem._parent = self
+ self._children.append(menuItem)
+
+ def getChildren(self):
+ """
+ Provides the menu and submenus we contain.
+ """
+
+ return list(self._children)
+
+ def isEmpty(self):
+ """
+ True if we have no children, false otherwise.
+ """
+
+ return not bool(self._children)
+
diff --git a/src/cli/menu/menu.py b/src/cli/menu/menu.py
new file mode 100644
index 0000000..02f87ee
--- /dev/null
+++ b/src/cli/menu/menu.py
@@ -0,0 +1,222 @@
+
+import curses
+
+import cli.popups
+import cli.controller
+import cli.menu.item
+
+from util import uiTools
+
+def makeMenu():
+ """
+ Constructs the base menu and all of its contents.
+ """
+
+ baseMenu = cli.menu.item.Submenu("")
+
+ fileMenu = cli.menu.item.Submenu("File")
+ fileMenu.add(cli.menu.item.MenuItem("Exit", None))
+ baseMenu.add(fileMenu)
+
+ logsMenu = cli.menu.item.Submenu("Logs")
+ logsMenu.add(cli.menu.item.MenuItem("Events", None))
+ logsMenu.add(cli.menu.item.MenuItem("Clear", None))
+ logsMenu.add(cli.menu.item.MenuItem("Save", None))
+ logsMenu.add(cli.menu.item.MenuItem("Filter", None))
+
+ duplicatesSubmenu = cli.menu.item.Submenu("Duplicates")
+ duplicatesSubmenu.add(cli.menu.item.MenuItem("Hidden", None))
+ duplicatesSubmenu.add(cli.menu.item.MenuItem("Visible", None))
+ logsMenu.add(duplicatesSubmenu)
+ baseMenu.add(logsMenu)
+
+ viewMenu = cli.menu.item.Submenu("View")
+ viewMenu.add(cli.menu.item.MenuItem("Graph", None))
+ viewMenu.add(cli.menu.item.MenuItem("Connections", None))
+ viewMenu.add(cli.menu.item.MenuItem("Configuration", None))
+ viewMenu.add(cli.menu.item.MenuItem("Configuration File", None))
+ baseMenu.add(viewMenu)
+
+ graphMenu = cli.menu.item.Submenu("Graph")
+ graphMenu.add(cli.menu.item.MenuItem("Stats", None))
+
+ sizeSubmenu = cli.menu.item.Submenu("Size")
+ sizeSubmenu.add(cli.menu.item.MenuItem("Increase", None))
+ sizeSubmenu.add(cli.menu.item.MenuItem("Decrease", None))
+ graphMenu.add(sizeSubmenu)
+
+ graphMenu.add(cli.menu.item.MenuItem("Update Interval", None))
+
+ boundsSubmenu = cli.menu.item.Submenu("Bounds")
+ boundsSubmenu.add(cli.menu.item.MenuItem("Local Max", None))
+ boundsSubmenu.add(cli.menu.item.MenuItem("Global Max", None))
+ boundsSubmenu.add(cli.menu.item.MenuItem("Tight", None))
+ graphMenu.add(boundsSubmenu)
+ baseMenu.add(graphMenu)
+
+ connectionsMenu = cli.menu.item.Submenu("Connections")
+ connectionsMenu.add(cli.menu.item.MenuItem("Identity", None))
+ connectionsMenu.add(cli.menu.item.MenuItem("Resolver", None))
+ connectionsMenu.add(cli.menu.item.MenuItem("Sort Order", None))
+ baseMenu.add(connectionsMenu)
+
+ configurationMenu = cli.menu.item.Submenu("Configuration")
+
+ commentsSubmenu = cli.menu.item.Submenu("Comments")
+ commentsSubmenu.add(cli.menu.item.MenuItem("Hidden", None))
+ commentsSubmenu.add(cli.menu.item.MenuItem("Visible", None))
+ configurationMenu.add(commentsSubmenu)
+
+ configurationMenu.add(cli.menu.item.MenuItem("Reload", None))
+ configurationMenu.add(cli.menu.item.MenuItem("Reset Tor", None))
+ baseMenu.add(configurationMenu)
+
+ return baseMenu
+
+class MenuCursor:
+ """
+ Tracks selection and key handling in the menu.
+ """
+
+ def __init__(self, initialSelection):
+ self._selection = initialSelection
+ self._isDone = False
+
+ def isDone(self):
+ """
+ Provides true if a selection has indicated that we should close the menu.
+ False otherwise.
+ """
+
+ return self._isDone
+
+ def getSelection(self):
+ """
+ Provides the currently selected menu item.
+ """
+
+ return self._selection
+
+ def handleKey(self, key):
+ isSelectionSubmenu = isinstance(self._selection, cli.menu.item.Submenu)
+ selectionHierarchy = self._selection.getHierarchy()
+
+ if uiTools.isSelectionKey(key):
+ if isSelectionSubmenu:
+ if not self._selection.isEmpty():
+ self._selection = self._selection.getChildren()[0]
+ else: self._isDone = self._selection.select()
+ elif key == curses.KEY_UP:
+ self._selection = self._selection.prev()
+ elif key == curses.KEY_DOWN:
+ self._selection = self._selection.next()
+ elif key == curses.KEY_LEFT:
+ if len(selectionHierarchy) <= 3:
+ # shift to the previous main submenu
+ prevSubmenu = selectionHierarchy[1].prev()
+ self._selection = prevSubmenu.getChildren()[0]
+ else:
+ # go up a submenu level
+ self._selection = self._selection.getParent()
+ elif key == curses.KEY_RIGHT:
+ if isSelectionSubmenu:
+ # open submenu (same as making a selection)
+ if not self._selection.isEmpty():
+ self._selection = self._selection.getChildren()[0]
+ else:
+ # shift to the next main submenu
+ nextSubmenu = selectionHierarchy[1].next()
+ self._selection = nextSubmenu.getChildren()[0]
+ elif key in (27, ord('m'), ord('M')):
+ # close menu
+ self._isDone = True
+
+def showMenu():
+ popup, _, _ = cli.popups.init(1, belowStatic = False)
+ if not popup: return
+ control = cli.controller.getController()
+
+ try:
+ # generates the menu and uses the initial selection of the first item in
+ # the file menu
+ menu = makeMenu()
+ cursor = MenuCursor(menu.getChildren()[0].getChildren()[0])
+
+ while not cursor.isDone():
+ # sets the background color
+ popup.win.clear()
+ popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red"))
+ selectionHierarchy = cursor.getSelection().getHierarchy()
+
+ # renders the menu bar, noting where the open submenu is positioned
+ drawLeft, selectionLeft = 0, 0
+
+ for topLevelItem in menu.getChildren():
+ drawFormat = curses.A_BOLD
+ if topLevelItem == selectionHierarchy[1]:
+ drawFormat |= curses.A_UNDERLINE
+ selectionLeft = drawLeft
+
+ drawLabel = " %s " % topLevelItem.getLabel()[1]
+ popup.addstr(0, drawLeft, drawLabel, drawFormat)
+ popup.addch(0, drawLeft + len(drawLabel), curses.ACS_VLINE)
+
+ drawLeft += len(drawLabel) + 1
+
+ # recursively shows opened submenus
+ _drawSubmenu(cursor, 1, 1, selectionLeft)
+
+ popup.win.refresh()
+
+ key = control.getScreen().getch()
+ cursor.handleKey(key)
+
+ # redraws the rest of the interface if we're rendering on it again
+ if not cursor.isDone():
+ for panelImpl in control.getDisplayPanels():
+ panelImpl.redraw(True)
+ finally: cli.popups.finalize()
+
+def _drawSubmenu(cursor, level, top, left):
+ selectionHierarchy = cursor.getSelection().getHierarchy()
+
+ # checks if there's nothing to display
+ if len(selectionHierarchy) < level + 2: return
+
+ # fetches the submenu and selection we're displaying
+ submenu = selectionHierarchy[level]
+ selection = selectionHierarchy[level + 1]
+
+ # gets the size of the prefix, middle, and suffix columns
+ allLabelSets = [entry.getLabel() for entry in submenu.getChildren()]
+ prefixColSize = max([len(entry[0]) for entry in allLabelSets])
+ middleColSize = max([len(entry[1]) for entry in allLabelSets])
+ suffixColSize = max([len(entry[2]) for entry in allLabelSets])
+
+ # formatted string so we can display aligned menu entries
+ labelFormat = " %%-%is%%-%is%%-%is " % (prefixColSize, middleColSize, suffixColSize)
+ menuWidth = len(labelFormat % ("", "", ""))
+
+ popup, _, _ = cli.popups.init(len(submenu.getChildren()), menuWidth, top, left, belowStatic = False)
+ if not popup: return
+
+ try:
+ # sets the background color
+ popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red"))
+
+ drawTop, selectionTop = 0, 0
+ for menuItem in submenu.getChildren():
+ if menuItem == selection:
+ drawFormat = curses.A_BOLD | uiTools.getColor("white")
+ selectionTop = drawTop
+ else: drawFormat = curses.A_NORMAL
+
+ popup.addstr(drawTop, 0, labelFormat % menuItem.getLabel(), drawFormat)
+ drawTop += 1
+
+ popup.win.refresh()
+
+ # shows the next submenu
+ _drawSubmenu(cursor, level + 1, top + selectionTop, left + menuWidth)
+ finally: cli.popups.finalize()
+
diff --git a/src/cli/popups.py b/src/cli/popups.py
index 0e50aa1..b90f95d 100644
--- a/src/cli/popups.py
+++ b/src/cli/popups.py
@@ -8,7 +8,7 @@ import cli.controller
from util import panel, uiTools
-def init(height = -1, width = -1, top = 0, left = 0):
+def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
"""
Preparation for displaying a popup. This creates a popup with a valid
subwindow instance. If that's successful then the curses lock is acquired
@@ -17,14 +17,17 @@ def init(height = -1, width = -1, top = 0, left = 0):
Otherwise this leaves curses unlocked and returns None.
Arguments:
- height - maximum height of the popup
- width - maximum width of the popup
- top - top position, relative to the sticky content
- left - left position from the screen
+ height - maximum height of the popup
+ width - maximum width of the popup
+ top - top position, relative to the sticky content
+ left - left position from the screen
+ belowStatic - positions popup below static content if true
"""
control = cli.controller.getController()
- stickyHeight = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
+ if belowStatic:
+ stickyHeight = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
+ else: stickyHeight = 0
popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
popup.setVisible(True)
diff --git a/src/util/panel.py b/src/util/panel.py
index 93b44c2..6fae341 100644
--- a/src/util/panel.py
+++ b/src/util/panel.py
@@ -484,7 +484,7 @@ class Panel():
# direction) from actual content to prevent crash when shrank
if self.win and self.maxX > x and self.maxY > y:
try:
- self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
+ self.win.addstr(y, x, msg[:self.maxX - x], attr)
except:
# this might produce a _curses.error during edge cases, for instance
# when resizing with visible popups
1
0
04 Jun '11
commit b5c7dca851bb7f1e13c90bd667136398fa89ce01
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue May 31 20:50:03 2011 -0700
Minor api, formatting, and documentation changes
Revising some of the merged changes for the menus.
---
src/cli/controller.py | 10 +++-------
src/cli/popups.py | 15 +++++----------
src/util/menuItem.py | 22 ++++++++++++----------
src/util/panel.py | 25 +++++++++++++++++--------
4 files changed, 37 insertions(+), 35 deletions(-)
diff --git a/src/cli/controller.py b/src/cli/controller.py
index bb258c3..fcbaed5 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -7,6 +7,7 @@ import time
import curses
import threading
+import cli.menu
import cli.popups
import cli.headerPanel
import cli.logPanel
@@ -17,7 +18,6 @@ import cli.graphing.bandwidthStats
import cli.graphing.connStats
import cli.graphing.resourceStats
import cli.connections.connPanel
-import cli.menu
from util import connections, conf, enum, log, panel, sysTools, torConfig, torTools
@@ -77,9 +77,6 @@ def initController(stdscr, startTime):
# fourth page: torrc
pagePanels.append([cli.torrcPanel.TorrcPanel(stdscr, cli.torrcPanel.Config.TORRC, config)])
- # top menu
- menu = cli.menu.Menu()
-
# initializes the controller
ARM_CONTROLLER = Controller(stdscr, stickyPanels, pagePanels)
@@ -110,7 +107,7 @@ class LabelPanel(panel.Panel):
"""
def __init__(self, stdscr):
- panel.Panel.__init__(self, stdscr, "msg", 0, 1)
+ panel.Panel.__init__(self, stdscr, "msg", 0, height=1)
self.msgText = ""
self.msgAttr = curses.A_NORMAL
@@ -144,7 +141,6 @@ class Controller:
stdscr - curses window
stickyPanels - panels shown at the top of each page
pagePanels - list of pages, each being a list of the panels on it
- menu - popup drop-down menu
"""
self._screen = stdscr
@@ -304,7 +300,7 @@ class Controller:
if attr == None:
if not self._isPaused:
- msg = "page %i / %i - m: menu, q: quit, p: pause, h: page help" % (self._page + 1, len(self._pagePanels))
+ msg = "page %i / %i - m: menu, p: pause, h: page help, q: quit" % (self._page + 1, len(self._pagePanels))
attr = curses.A_NORMAL
else:
msg = "Paused"
diff --git a/src/cli/popups.py b/src/cli/popups.py
index b3eb105..0e50aa1 100644
--- a/src/cli/popups.py
+++ b/src/cli/popups.py
@@ -8,7 +8,7 @@ import cli.controller
from util import panel, uiTools
-def init(height = -1, width = -1, top = -1, left = -1):
+def init(height = -1, width = -1, top = 0, left = 0):
"""
Preparation for displaying a popup. This creates a popup with a valid
subwindow instance. If that's successful then the curses lock is acquired
@@ -19,19 +19,14 @@ def init(height = -1, width = -1, top = -1, left = -1):
Arguments:
height - maximum height of the popup
width - maximum width of the popup
+ top - top position, relative to the sticky content
+ left - left position from the screen
"""
control = cli.controller.getController()
- topSize = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
- leftSize = 0
-
- if top != -1:
- topSize = topSize + top
- if left != -1:
- leftSize = left
+ stickyHeight = sum(stickyPanel.getHeight() for stickyPanel in control.getStickyPanels())
- popup = panel.Panel(control.getScreen(), "popup", topSize, height, width)
- popup.setLeft(leftSize)
+ popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
popup.setVisible(True)
# Redraws the popup to prepare a subwindow instance. If none is spawned then
diff --git a/src/util/menuItem.py b/src/util/menuItem.py
index b48c433..277c78a 100644
--- a/src/util/menuItem.py
+++ b/src/util/menuItem.py
@@ -1,25 +1,27 @@
"""
-Menu Item class, used by the drop-down menus
+Menu Item class, used by the drop-down menus.
"""
class MenuItem():
- """Contains title, callback handler and possible children"""
-
+ """
+ Contains title, callback handler and possible children.
+ """
+
def __init__(self, label=None, callback=None, children=[], enabled=None):
self._label = label
self._callback = callback
self._children = children
self._enabled = enabled
-
+
def getLabel(self):
return self._label
-
+
def isLeaf(self):
return self._children == []
-
+
def isParent(self):
return self._children != []
-
+
def isEnabled(self):
if self._enabled == None:
return True
@@ -27,13 +29,13 @@ class MenuItem():
return self._enabled()
else:
return self._enabled
-
+
def getChildren(self):
return self._children
-
+
def getChildrenCount(self):
return len(self._children)
-
+
def select(self):
return self._callback(self)
diff --git a/src/util/panel.py b/src/util/panel.py
index 4fc2158..93b44c2 100644
--- a/src/util/panel.py
+++ b/src/util/panel.py
@@ -43,7 +43,7 @@ class Panel():
redraw().
"""
- def __init__(self, parent, name, top, height=-1, width=-1):
+ def __init__(self, parent, name, top, left=0, height=-1, width=-1):
"""
Creates a durable wrapper for a curses subwindow in the given parent.
@@ -51,6 +51,7 @@ class Panel():
parent - parent curses window
name - identifier for the panel
top - positioning of top within parent
+ left - positioning of the left edge within the parent
height - maximum height of panel (uses all available space if -1)
width - maximum width of panel (uses all available space if -1)
"""
@@ -74,7 +75,7 @@ class Panel():
self.pauseTime = -1
self.top = top
- self.left = 0
+ self.left = left
self.height = height
self.width = width
@@ -256,25 +257,33 @@ class Panel():
self.top = top
self.win = None
- def getHeight(self):
+ def getLeft(self):
"""
- Provides the height used for subwindows (-1 if it isn't limited).
+ Provides the left position where this subwindow is placed within its
+ parent.
"""
- return self.height
-
+ return self.left
+
def setLeft(self, left):
"""
- Changes the position where subwindows are placed within its parent.
+ Changes the left position where this subwindow is placed within its parent.
Arguments:
- top - positioning of top within parent
+ left - positioning of top within parent
"""
if self.left != left:
self.left = left
self.win = None
+ def getHeight(self):
+ """
+ Provides the height used for subwindows (-1 if it isn't limited).
+ """
+
+ return self.height
+
def setHeight(self, height):
"""
Changes the height used for subwindows. This uses all available space if -1.
1
0