tor-commits
Threads by month
- ----- 2025 -----
- 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
April 2016
- 20 participants
- 1645 discussions

[translation/bridgedb_completed] Update translations for bridgedb_completed
by translation@torproject.org 05 Apr '16
by translation@torproject.org 05 Apr '16
05 Apr '16
commit 27c9e05413252beeb00916c9ac095616fdd0b854
Author: Translation commit bot <translation(a)torproject.org>
Date: Tue Apr 5 06:45:09 2016 +0000
Update translations for bridgedb_completed
---
zh_HK/LC_MESSAGES/bridgedb.po | 383 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 383 insertions(+)
diff --git a/zh_HK/LC_MESSAGES/bridgedb.po b/zh_HK/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..7b4139b
--- /dev/null
+++ b/zh_HK/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,383 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# CasperLi_HK <casper.hk(a)hotmail.com>, 2013
+# CasperLi_HK <casper.hk(a)hotmail.com>, 2013
+# Wen-Gan Li <wgli(a)wgli.net>, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywo…'\n"
+"POT-Creation-Date: 2015-07-25 03:40+0000\n"
+"PO-Revision-Date: 2016-04-05 06:41+0000\n"
+"Last-Translator: Kin Kwok\n"
+"Language-Team: Chinese (Hong Kong) (http://www.transifex.com/otf/torproject/language/zh_HK/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+"Language: zh_HK\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: bridgedb/https/server.py:167
+msgid "Sorry! Something went wrong with your request."
+msgstr "對唔住!你的請求有錯誤。"
+
+#: bridgedb/https/templates/base.html:79
+msgid "Report a Bug"
+msgstr "回報錯誤"
+
+#: bridgedb/https/templates/base.html:82
+msgid "Source Code"
+msgstr "原始程式碼"
+
+#: bridgedb/https/templates/base.html:85
+msgid "Changelog"
+msgstr "更新日誌"
+
+#: bridgedb/https/templates/base.html:88
+msgid "Contact"
+msgstr "聯絡資訊"
+
+#: bridgedb/https/templates/bridges.html:35
+msgid "Select All"
+msgstr "選擇全部"
+
+#: bridgedb/https/templates/bridges.html:40
+msgid "Show QRCode"
+msgstr "顯示QRCode"
+
+#: bridgedb/https/templates/bridges.html:52
+msgid "QRCode for your bridge lines"
+msgstr "你的橋接資訊QRCode"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: bridgedb/https/templates/bridges.html:67
+#: bridgedb/https/templates/bridges.html:125
+msgid "Uh oh, spaghettios!"
+msgstr "有問題!"
+
+#: bridgedb/https/templates/bridges.html:68
+msgid "It seems there was an error getting your QRCode."
+msgstr "掃瞄QRCode時似乎發生錯誤。"
+
+#: bridgedb/https/templates/bridges.html:73
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "此QRCode含你的橋接資訊。以流動裝置掃瞄可取得你的橋接資訊。"
+
+#: bridgedb/https/templates/bridges.html:131
+msgid "There currently aren't any bridges available..."
+msgstr "目前無可用橋接器…"
+
+#: bridgedb/https/templates/bridges.html:132
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "或許可嘗試%s回到%s,然後選取不同的橋接類型!"
+
+#: bridgedb/https/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "步驟%s1%s"
+
+#: bridgedb/https/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "下載%s Tor洋蔥路由瀏覽器%s"
+
+#: bridgedb/https/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "步驟%s2%s"
+
+#: bridgedb/https/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "取得%s橋接器%s"
+
+#: bridgedb/https/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "步驟%s3%s"
+
+#: bridgedb/https/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "現在%s將橋接加入到Tor洋蔥路由瀏覽器%s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: bridgedb/https/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust予我橋接!"
+
+#: bridgedb/https/templates/options.html:51
+msgid "Advanced Options"
+msgstr "進階選項"
+
+#: bridgedb/https/templates/options.html:86
+msgid "No"
+msgstr "否"
+
+#: bridgedb/https/templates/options.html:87
+msgid "none"
+msgstr "無"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: bridgedb/https/templates/options.html:124
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: bridgedb/https/templates/options.html:147
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%s設定橋接器"
+
+#: bridgedb/strings.py:43
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[這是自動回覆訊息。請不要回覆。]"
+
+#: bridgedb/strings.py:45
+msgid "Here are your bridges:"
+msgstr "這是你的橋接器:"
+
+#: bridgedb/strings.py:47
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "你已超過速度限制。請減慢速度!電郵之間的最短時間為%s個小時。這段時間內所有其他郵件將被忽略。"
+
+#: bridgedb/strings.py:50
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "指令:(可將多個指令組合一起以便同時指定多個選項)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: bridgedb/strings.py:53
+msgid "Welcome to BridgeDB!"
+msgstr "歡迎使用BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: bridgedb/strings.py:55
+msgid "Currently supported transport TYPEs:"
+msgstr "目前支援的傳輸類型:"
+
+#: bridgedb/strings.py:56
+#, python-format
+msgid "Hey, %s!"
+msgstr "嘿,%s!"
+
+#: bridgedb/strings.py:57
+msgid "Hello, friend!"
+msgstr "朋友,你好!"
+
+#: bridgedb/strings.py:58 bridgedb/https/templates/base.html:90
+msgid "Public Keys"
+msgstr "公開鎖匙"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice(a)example.com on Friday, 09 May, 2014 at 18:59:39."
+#: bridgedb/strings.py:62
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "這%s的電郵使用rainbows、unicorns和sparkles產生\n時間是%s的%s。"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: bridgedb/strings.py:72
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB可提供幾種Pluggable Transports%s的%stypes來橋接,\n助你使人混淆地連接到Tor洋蔥路由網絡,透過監看你的\n網絡流量來確定你正使用Tor洋蔥路由會更加困難。\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: bridgedb/strings.py:79
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "有些具有IPv6位址的橋接器也可使用,雖然有些Pluggable\nTransports與IPv6不相容。\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: bridgedb/strings.py:88
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "此外,BridgeDB有普通的橋接器%s,沒有任何\nPluggable Transports %s,或許聽起來悶蛋,但\n仍有助於規避很多情況下的網絡審查。\n"
+
+#: bridgedb/strings.py:101
+msgid "What are bridges?"
+msgstr "何為橋接?"
+
+#: bridgedb/strings.py:102
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s橋接器%s是Tor洋蔥路由轉向點,可助你規避審查。"
+
+#: bridgedb/strings.py:107
+msgid "I need an alternative way of getting bridges!"
+msgstr "我需要另一方式做橋接!"
+
+#: bridgedb/strings.py:108
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "另一取得橋接的方式是發送電郵到%s。請注意,你須\n使用下列電郵供應商的位址來發送電郵:\n%s、%s或%s。"
+
+#: bridgedb/strings.py:115
+msgid "My bridges don't work! I need help!"
+msgstr "橋接器無法正常運作,我需要幫助!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:117
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "如果Tor洋蔥路由無法運作,可發送電郵到%s。"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:121
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "盡可能包含最多有關你情況的資訊,包括你嘗試使用過的\n橋接和Pluggable Transports清單、Tor洋蔥路由瀏覽器版本,\n以及Tor洋蔥路由顯示的任何訊息。"
+
+#: bridgedb/strings.py:128
+msgid "Here are your bridge lines:"
+msgstr "這是你的橋接線路:"
+
+#: bridgedb/strings.py:129
+msgid "Get Bridges!"
+msgstr "取得橋接器!"
+
+#: bridgedb/strings.py:133
+msgid "Please select options for bridge type:"
+msgstr "請選取橋接器類型:"
+
+#: bridgedb/strings.py:134
+msgid "Do you need IPv6 addresses?"
+msgstr "你需要IPv6位址嗎?"
+
+#: bridgedb/strings.py:135
+#, python-format
+msgid "Do you need a %s?"
+msgstr "你需要%s嗎?"
+
+#: bridgedb/strings.py:139
+msgid "Your browser is not displaying images properly."
+msgstr "你的瀏覽器不能正確顯示圖像。"
+
+#: bridgedb/strings.py:140
+msgid "Enter the characters from the image above..."
+msgstr "輸入上面圖像中的字元…"
+
+#: bridgedb/strings.py:144
+msgid "How to start using your bridges"
+msgstr "如何開始使用橋接器"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: bridgedb/strings.py:146
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+msgstr "將橋接器輸入到Tor洋蔥路由瀏覽器,請首先前往%s Tor洋蔥路由\n瀏覽器下載頁面%s,並按照頁面上的指示下載和啟動Tor洋蔥\n路由瀏覽器。"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:151
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "當「Tor洋蔥路由網絡設定」話匣彈出時,按下〔設定〕,然後按照\n指示,直到其要求:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:155
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "你的網絡服務供應商(ISP)會否阻擋或過濾Tor洋蔥路由網絡的連線?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:159
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "選取〔是〕,然後按下〔下一步〕。要設定新橋接器,請複製和\n將橋接線路貼到文字輸入匣中。最後,按一下〔連接〕。\n如果遇到麻煩,請嘗試按下「Tor洋蔥路由網絡設定」的〔説明〕\n尋求進一步協助。"
+
+#: bridgedb/strings.py:167
+msgid "Displays this message."
+msgstr "顯示此訊息。"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: bridgedb/strings.py:171
+msgid "Request vanilla bridges."
+msgstr "請求Vanilla橋接器。"
+
+#: bridgedb/strings.py:172
+msgid "Request IPv6 bridges."
+msgstr "請求IPv6橋接器。"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: bridgedb/strings.py:174
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "按照TYPE請求Pluggable Transport。"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: bridgedb/strings.py:177
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "取得BridgeDB公共GnuPG鎖匙。"
1
0

05 Apr '16
commit 77ea2a56c76adc7d2e9c9eb73ddd4535a2492937
Author: Translation commit bot <translation(a)torproject.org>
Date: Tue Apr 5 06:45:04 2016 +0000
Update translations for bridgedb
---
zh_HK/LC_MESSAGES/bridgedb.po | 127 +++++++++++++++++++++---------------------
1 file changed, 64 insertions(+), 63 deletions(-)
diff --git a/zh_HK/LC_MESSAGES/bridgedb.po b/zh_HK/LC_MESSAGES/bridgedb.po
index 2c7360c..7b4139b 100644
--- a/zh_HK/LC_MESSAGES/bridgedb.po
+++ b/zh_HK/LC_MESSAGES/bridgedb.po
@@ -4,14 +4,15 @@
#
# Translators:
# CasperLi_HK <casper.hk(a)hotmail.com>, 2013
+# CasperLi_HK <casper.hk(a)hotmail.com>, 2013
# Wen-Gan Li <wgli(a)wgli.net>, 2015
msgid ""
msgstr ""
"Project-Id-Version: The Tor Project\n"
"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywo…'\n"
"POT-Creation-Date: 2015-07-25 03:40+0000\n"
-"PO-Revision-Date: 2016-03-21 16:27+0000\n"
-"Last-Translator: Wen-Gan Li <wgli(a)wgli.net>\n"
+"PO-Revision-Date: 2016-04-05 06:41+0000\n"
+"Last-Translator: Kin Kwok\n"
"Language-Team: Chinese (Hong Kong) (http://www.transifex.com/otf/torproject/language/zh_HK/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -33,35 +34,35 @@ msgstr ""
#. "Tor Browser"
#: bridgedb/https/server.py:167
msgid "Sorry! Something went wrong with your request."
-msgstr ""
+msgstr "對唔住!你的請求有錯誤。"
#: bridgedb/https/templates/base.html:79
msgid "Report a Bug"
-msgstr ""
+msgstr "回報錯誤"
#: bridgedb/https/templates/base.html:82
msgid "Source Code"
-msgstr ""
+msgstr "原始程式碼"
#: bridgedb/https/templates/base.html:85
msgid "Changelog"
-msgstr ""
+msgstr "更新日誌"
#: bridgedb/https/templates/base.html:88
msgid "Contact"
-msgstr ""
+msgstr "聯絡資訊"
#: bridgedb/https/templates/bridges.html:35
msgid "Select All"
-msgstr ""
+msgstr "選擇全部"
#: bridgedb/https/templates/bridges.html:40
msgid "Show QRCode"
-msgstr ""
+msgstr "顯示QRCode"
#: bridgedb/https/templates/bridges.html:52
msgid "QRCode for your bridge lines"
-msgstr ""
+msgstr "你的橋接資訊QRCode"
#. TRANSLATORS: Please translate this into some silly way to say
#. "There was a problem!" in your language. For example,
@@ -70,58 +71,58 @@ msgstr ""
#: bridgedb/https/templates/bridges.html:67
#: bridgedb/https/templates/bridges.html:125
msgid "Uh oh, spaghettios!"
-msgstr ""
+msgstr "有問題!"
#: bridgedb/https/templates/bridges.html:68
msgid "It seems there was an error getting your QRCode."
-msgstr ""
+msgstr "掃瞄QRCode時似乎發生錯誤。"
#: bridgedb/https/templates/bridges.html:73
msgid ""
"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
" your bridge lines onto mobile and other devices."
-msgstr ""
+msgstr "此QRCode含你的橋接資訊。以流動裝置掃瞄可取得你的橋接資訊。"
#: bridgedb/https/templates/bridges.html:131
msgid "There currently aren't any bridges available..."
-msgstr ""
+msgstr "目前無可用橋接器…"
#: bridgedb/https/templates/bridges.html:132
#, python-format
msgid ""
" Perhaps you should try %s going back %s and choosing a different bridge "
"type!"
-msgstr ""
+msgstr "或許可嘗試%s回到%s,然後選取不同的橋接類型!"
#: bridgedb/https/templates/index.html:11
#, python-format
msgid "Step %s1%s"
-msgstr ""
+msgstr "步驟%s1%s"
#: bridgedb/https/templates/index.html:13
#, python-format
msgid "Download %s Tor Browser %s"
-msgstr ""
+msgstr "下載%s Tor洋蔥路由瀏覽器%s"
#: bridgedb/https/templates/index.html:25
#, python-format
msgid "Step %s2%s"
-msgstr ""
+msgstr "步驟%s2%s"
#: bridgedb/https/templates/index.html:27
#, python-format
msgid "Get %s bridges %s"
-msgstr ""
+msgstr "取得%s橋接器%s"
#: bridgedb/https/templates/index.html:36
#, python-format
msgid "Step %s3%s"
-msgstr ""
+msgstr "步驟%s3%s"
#: bridgedb/https/templates/index.html:38
#, python-format
msgid "Now %s add the bridges to Tor Browser %s"
-msgstr ""
+msgstr "現在%s將橋接加入到Tor洋蔥路由瀏覽器%s"
#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
#. beginning of words are present in your final translation. Thanks!
@@ -130,11 +131,11 @@ msgstr ""
#: bridgedb/https/templates/options.html:38
#, python-format
msgid "%sJ%sust give me bridges!"
-msgstr ""
+msgstr "%sJ%sust予我橋接!"
#: bridgedb/https/templates/options.html:51
msgid "Advanced Options"
-msgstr ""
+msgstr "進階選項"
#: bridgedb/https/templates/options.html:86
msgid "No"
@@ -142,7 +143,7 @@ msgstr "否"
#: bridgedb/https/templates/options.html:87
msgid "none"
-msgstr ""
+msgstr "無"
#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
#. beginning of words are present in your final translation. Thanks!
@@ -150,7 +151,7 @@ msgstr ""
#: bridgedb/https/templates/options.html:124
#, python-format
msgid "%sY%ses!"
-msgstr ""
+msgstr "%sY%ses!"
#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
#. beginning of words are present in your final translation. Thanks!
@@ -158,50 +159,50 @@ msgstr ""
#: bridgedb/https/templates/options.html:147
#, python-format
msgid "%sG%set Bridges"
-msgstr ""
+msgstr "%sG%s設定橋接器"
#: bridgedb/strings.py:43
msgid "[This is an automated message; please do not reply.]"
-msgstr ""
+msgstr "[這是自動回覆訊息。請不要回覆。]"
#: bridgedb/strings.py:45
msgid "Here are your bridges:"
-msgstr ""
+msgstr "這是你的橋接器:"
#: bridgedb/strings.py:47
#, python-format
msgid ""
"You have exceeded the rate limit. Please slow down! The minimum time between\n"
"emails is %s hours. All further emails during this time period will be ignored."
-msgstr ""
+msgstr "你已超過速度限制。請減慢速度!電郵之間的最短時間為%s個小時。這段時間內所有其他郵件將被忽略。"
#: bridgedb/strings.py:50
msgid ""
"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr ""
+msgstr "指令:(可將多個指令組合一起以便同時指定多個選項)"
#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
#: bridgedb/strings.py:53
msgid "Welcome to BridgeDB!"
-msgstr ""
+msgstr "歡迎使用BridgeDB!"
#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
#: bridgedb/strings.py:55
msgid "Currently supported transport TYPEs:"
-msgstr ""
+msgstr "目前支援的傳輸類型:"
#: bridgedb/strings.py:56
#, python-format
msgid "Hey, %s!"
-msgstr ""
+msgstr "嘿,%s!"
#: bridgedb/strings.py:57
msgid "Hello, friend!"
-msgstr ""
+msgstr "朋友,你好!"
#: bridgedb/strings.py:58 bridgedb/https/templates/base.html:90
msgid "Public Keys"
-msgstr ""
+msgstr "公開鎖匙"
#. TRANSLATORS: This string will end up saying something like:
#. "This email was generated with rainbows, unicorns, and sparkles
@@ -211,7 +212,7 @@ msgstr ""
msgid ""
"This email was generated with rainbows, unicorns, and sparkles\n"
"for %s on %s at %s."
-msgstr ""
+msgstr "這%s的電郵使用rainbows、unicorns和sparkles產生\n時間是%s的%s。"
#. TRANSLATORS: Please DO NOT translate "BridgeDB".
#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
@@ -225,7 +226,7 @@ msgid ""
"difficult for anyone watching your internet traffic to determine that you are\n"
"using Tor.\n"
"\n"
-msgstr ""
+msgstr "BridgeDB可提供幾種Pluggable Transports%s的%stypes來橋接,\n助你使人混淆地連接到Tor洋蔥路由網絡,透過監看你的\n網絡流量來確定你正使用Tor洋蔥路由會更加困難。\n\n"
#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
#: bridgedb/strings.py:79
@@ -233,7 +234,7 @@ msgid ""
"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
"Transports aren't IPv6 compatible.\n"
"\n"
-msgstr ""
+msgstr "有些具有IPv6位址的橋接器也可使用,雖然有些Pluggable\nTransports與IPv6不相容。\n\n"
#. TRANSLATORS: Please DO NOT translate "BridgeDB".
#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
@@ -247,20 +248,20 @@ msgid ""
"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
"help to circumvent internet censorship in many cases.\n"
"\n"
-msgstr ""
+msgstr "此外,BridgeDB有普通的橋接器%s,沒有任何\nPluggable Transports %s,或許聽起來悶蛋,但\n仍有助於規避很多情況下的網絡審查。\n"
#: bridgedb/strings.py:101
msgid "What are bridges?"
-msgstr "咩叫橋接?"
+msgstr "何為橋接?"
#: bridgedb/strings.py:102
#, python-format
msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr ""
+msgstr "%s橋接器%s是Tor洋蔥路由轉向點,可助你規避審查。"
#: bridgedb/strings.py:107
msgid "I need an alternative way of getting bridges!"
-msgstr "我需要另一種黎方式做橋接!"
+msgstr "我需要另一方式做橋接!"
#: bridgedb/strings.py:108
#, python-format
@@ -268,17 +269,17 @@ msgid ""
"Another way to get bridges is to send an email to %s. Please note that you must\n"
"send the email using an address from one of the following email providers:\n"
"%s, %s or %s."
-msgstr ""
+msgstr "另一取得橋接的方式是發送電郵到%s。請注意,你須\n使用下列電郵供應商的位址來發送電郵:\n%s、%s或%s。"
#: bridgedb/strings.py:115
msgid "My bridges don't work! I need help!"
-msgstr ""
+msgstr "橋接器無法正常運作,我需要幫助!"
#. TRANSLATORS: Please DO NOT translate "Tor".
#: bridgedb/strings.py:117
#, python-format
msgid "If your Tor doesn't work, you should email %s."
-msgstr ""
+msgstr "如果Tor洋蔥路由無法運作,可發送電郵到%s。"
#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
#. TRANSLATORS: Please DO NOT translate "Tor Browser".
@@ -288,40 +289,40 @@ msgid ""
"Try including as much info about your case as you can, including the list of\n"
"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
"and any messages which Tor gave out, etc."
-msgstr ""
+msgstr "盡可能包含最多有關你情況的資訊,包括你嘗試使用過的\n橋接和Pluggable Transports清單、Tor洋蔥路由瀏覽器版本,\n以及Tor洋蔥路由顯示的任何訊息。"
#: bridgedb/strings.py:128
msgid "Here are your bridge lines:"
-msgstr ""
+msgstr "這是你的橋接線路:"
#: bridgedb/strings.py:129
msgid "Get Bridges!"
-msgstr ""
+msgstr "取得橋接器!"
#: bridgedb/strings.py:133
msgid "Please select options for bridge type:"
-msgstr ""
+msgstr "請選取橋接器類型:"
#: bridgedb/strings.py:134
msgid "Do you need IPv6 addresses?"
-msgstr ""
+msgstr "你需要IPv6位址嗎?"
#: bridgedb/strings.py:135
#, python-format
msgid "Do you need a %s?"
-msgstr ""
+msgstr "你需要%s嗎?"
#: bridgedb/strings.py:139
msgid "Your browser is not displaying images properly."
-msgstr ""
+msgstr "你的瀏覽器不能正確顯示圖像。"
#: bridgedb/strings.py:140
msgid "Enter the characters from the image above..."
-msgstr ""
+msgstr "輸入上面圖像中的字元…"
#: bridgedb/strings.py:144
msgid "How to start using your bridges"
-msgstr ""
+msgstr "如何開始使用橋接器"
#. TRANSLATORS: Please DO NOT translate "Tor Browser".
#: bridgedb/strings.py:146
@@ -330,21 +331,21 @@ msgid ""
"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
"page %s and then follow the instructions there for downloading and starting\n"
"Tor Browser."
-msgstr ""
+msgstr "將橋接器輸入到Tor洋蔥路由瀏覽器,請首先前往%s Tor洋蔥路由\n瀏覽器下載頁面%s,並按照頁面上的指示下載和啟動Tor洋蔥\n路由瀏覽器。"
#. TRANSLATORS: Please DO NOT translate "Tor".
#: bridgedb/strings.py:151
msgid ""
"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
"the wizard until it asks:"
-msgstr ""
+msgstr "當「Tor洋蔥路由網絡設定」話匣彈出時,按下〔設定〕,然後按照\n指示,直到其要求:"
#. TRANSLATORS: Please DO NOT translate "Tor".
#: bridgedb/strings.py:155
msgid ""
"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
"to the Tor network?"
-msgstr ""
+msgstr "你的網絡服務供應商(ISP)會否阻擋或過濾Tor洋蔥路由網絡的連線?"
#. TRANSLATORS: Please DO NOT translate "Tor".
#: bridgedb/strings.py:159
@@ -353,30 +354,30 @@ msgid ""
"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr ""
+msgstr "選取〔是〕,然後按下〔下一步〕。要設定新橋接器,請複製和\n將橋接線路貼到文字輸入匣中。最後,按一下〔連接〕。\n如果遇到麻煩,請嘗試按下「Tor洋蔥路由網絡設定」的〔説明〕\n尋求進一步協助。"
#: bridgedb/strings.py:167
msgid "Displays this message."
-msgstr ""
+msgstr "顯示此訊息。"
#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
#. same non-Pluggable Transport bridges described above as being
#. "plain-ol'-vanilla" bridges.
#: bridgedb/strings.py:171
msgid "Request vanilla bridges."
-msgstr ""
+msgstr "請求Vanilla橋接器。"
#: bridgedb/strings.py:172
msgid "Request IPv6 bridges."
-msgstr ""
+msgstr "請求IPv6橋接器。"
#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
#: bridgedb/strings.py:174
msgid "Request a Pluggable Transport by TYPE."
-msgstr ""
+msgstr "按照TYPE請求Pluggable Transport。"
#. TRANSLATORS: Please DO NOT translate "BridgeDB".
#. TRANSLATORS: Please DO NOT translate "GnuPG".
#: bridgedb/strings.py:177
msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr ""
+msgstr "取得BridgeDB公共GnuPG鎖匙。"
1
0

[translation/https_everywhere_completed] Update translations for https_everywhere_completed
by translation@torproject.org 05 Apr '16
by translation@torproject.org 05 Apr '16
05 Apr '16
commit 443c2f2b705fae0b7ea6e86ff75b98e5a816758f
Author: Translation commit bot <translation(a)torproject.org>
Date: Tue Apr 5 05:15:29 2016 +0000
Update translations for https_everywhere_completed
---
zh-CN/https-everywhere.dtd | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zh-CN/https-everywhere.dtd b/zh-CN/https-everywhere.dtd
index cb9d724..56fef4e 100644
--- a/zh-CN/https-everywhere.dtd
+++ b/zh-CN/https-everywhere.dtd
@@ -6,7 +6,7 @@
<!ENTITY https-everywhere.about.and ",及">
<!ENTITY https-everywhere.about.librarians "规则管理员">
<!ENTITY https-everywhere.about.thanks "致谢">
-<!ENTITY https-everywhere.about.many_contributors "许多许多贡献者,包括">
+<!ENTITY https-everywhere.about.many_contributors "许许多多的贡献者,包括">
<!ENTITY https-everywhere.about.noscript "另外,HTTPS Everywhere 的部分源代码基于 NoScript,作者是 Giorgio Maone 等人。我们非常感谢他们出色的成果!">
<!ENTITY https-everywhere.about.contribute "如果喜欢 HTTPS Everywhere,您可以考虑">
<!ENTITY https-everywhere.about.donate_tor "捐助 Tor">
1
0

[translation/https_everywhere] Update translations for https_everywhere
by translation@torproject.org 05 Apr '16
by translation@torproject.org 05 Apr '16
05 Apr '16
commit 2cebfc2cb8c22ab33114c5f376d826072edcda5f
Author: Translation commit bot <translation(a)torproject.org>
Date: Tue Apr 5 05:15:22 2016 +0000
Update translations for https_everywhere
---
zh-CN/https-everywhere.dtd | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zh-CN/https-everywhere.dtd b/zh-CN/https-everywhere.dtd
index cb9d724..56fef4e 100644
--- a/zh-CN/https-everywhere.dtd
+++ b/zh-CN/https-everywhere.dtd
@@ -6,7 +6,7 @@
<!ENTITY https-everywhere.about.and ",及">
<!ENTITY https-everywhere.about.librarians "规则管理员">
<!ENTITY https-everywhere.about.thanks "致谢">
-<!ENTITY https-everywhere.about.many_contributors "许多许多贡献者,包括">
+<!ENTITY https-everywhere.about.many_contributors "许许多多的贡献者,包括">
<!ENTITY https-everywhere.about.noscript "另外,HTTPS Everywhere 的部分源代码基于 NoScript,作者是 Giorgio Maone 等人。我们非常感谢他们出色的成果!">
<!ENTITY https-everywhere.about.contribute "如果喜欢 HTTPS Everywhere,您可以考虑">
<!ENTITY https-everywhere.about.donate_tor "捐助 Tor">
1
0
commit cfaf6953c5a2df4521fb48e464b8415b594d4a11
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Apr 4 15:36:14 2016 -0700
Revise descriptor dialog
Definitely the largest, thorniest in this file. Rejiggered code, migrating to
the new draw() function and adding tests.
---
nyx/curses.py | 69 ++++++++++++++++++--
nyx/panel/connection.py | 8 +--
nyx/panel/graph.py | 4 +-
nyx/panel/log.py | 2 +-
nyx/popups.py | 170 ++++++++++++++++++++----------------------------
test/popups.py | 119 +++++++++++++++++++++++++--------
6 files changed, 232 insertions(+), 140 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py
index 6d3b170..3718742 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -15,6 +15,7 @@ if we want Windows support in the future too.
raw_screen - provides direct access to the curses screen
key_input - get keypress by user
curses_attr - curses encoded text attribute
+ screen_size - provides the dimensions of our screen
screenshot - dump of the present on-screen content
is_color_supported - checks if terminal supports color output
@@ -76,11 +77,13 @@ if we want Windows support in the future too.
from __future__ import absolute_import
+import collections
import curses
import threading
import stem.util.conf
import stem.util.enum
+import stem.util.str_tools
import stem.util.system
from nyx import msg, log
@@ -135,16 +138,21 @@ SPECIAL_KEYS = {
'esc': 27,
}
+Dimensions = collections.namedtuple('Dimensions', ['width', 'height'])
+
def conf_handler(key, value):
if key == 'features.colorOverride':
if value not in Color and value != 'None':
raise ValueError(msg('usage.unable_to_set_color_override', color = value))
+ elif key == 'features.torrc.maxLineWrap':
+ return max(1, value)
CONFIG = stem.util.conf.config_dict('nyx', {
'features.colorOverride': 'None',
'features.colorInterface': True,
+ 'features.maxLineWrap': 8,
}, conf_handler)
@@ -251,6 +259,17 @@ def curses_attr(*attributes):
return encoded
+def screen_size():
+ """
+ Provides the current dimensions of our screen.
+
+ :returns: :data:`~nyx.curses.Dimensions` with our screen size
+ """
+
+ height, width = CURSES_SCREEN.getmaxyx()
+ return Dimensions(width, height)
+
+
def screenshot():
"""
Provides a dump of the present content of the screen.
@@ -260,7 +279,7 @@ def screenshot():
lines = []
- for y in range(CURSES_SCREEN.getmaxyx()[0]):
+ for y in range(screen_size().height):
lines.append(CURSES_SCREEN.instr(y, 0).rstrip())
return '\n'.join(lines).rstrip()
@@ -408,10 +427,9 @@ def draw(func, left = 0, top = 0, width = None, height = None):
"""
with CURSES_LOCK:
- max_height, max_width = CURSES_SCREEN.getmaxyx()
-
- subwindow_width = max(0, max_width - left)
- subwindow_height = max(0, max_height - top)
+ dimensions = screen_size()
+ subwindow_width = max(0, dimensions.width - left)
+ subwindow_height = max(0, dimensions.height - top)
if width:
subwindow_width = min(width, subwindow_width)
@@ -443,7 +461,7 @@ class _Subwindow(object):
Draws a string in the subwindow.
:param int x: horizontal location
- :param int y, vertical location
+ :param int y: vertical location
:param str msg: string to be written
:param list attr: text attributes to apply
"""
@@ -458,6 +476,36 @@ class _Subwindow(object):
return x
+ def addstr_wrap(self, x, y, msg, width, min_x = 0, *attr):
+ """
+ Draws a string in the subwindow, with text wrapped if it exceeds a width.
+
+ :param int x: horizontal location
+ :param int y: vertical location
+ :param str msg: string to be written
+ :param int width: width avaialble to render the string
+ :param int min_x: horizontal position to wrap to on new lines
+ :param list attr: text attributes to apply
+ """
+
+ orig_y = y
+
+ while msg:
+ draw_msg, msg = stem.util.str_tools.crop(msg, width - x, None, ending = None, get_remainder = True)
+
+ if not draw_msg:
+ draw_msg, msg = stem.util.str_tools.crop(msg, width - x), '' # first word is longer than the line
+
+ x = self.addstr(x, y, draw_msg, *attr)
+
+ if (y - orig_y + 1) >= CONFIG['features.maxLineWrap']:
+ break # maximum number we'll wrap
+
+ if msg:
+ x, y = min_x, y + 1
+
+ return x, y
+
def box(self, left = 0, top = 0, width = None, height = None, *attr):
"""
Draws a box with the given bounds.
@@ -551,6 +599,15 @@ class KeyInput(object):
return self._key in (curses.KEY_ENTER, 10, ord(' '))
+ def __eq__(self, other):
+ if isinstance(other, KeyInput):
+ return self._key == other._key
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not self == other
+
class Scroller(object):
"""
diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py
index e8eb0b5..afd1da1 100644
--- a/nyx/panel/connection.py
+++ b/nyx/panel/connection.py
@@ -376,14 +376,14 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
return key.is_selection() or key.match('d') or key.match('left') or key.match('right')
color = CONFIG['attr.connection.category_color'].get(selected.entry.get_type(), WHITE)
- key = nyx.popups.show_descriptor_popup(selected.fingerprint, color, self.max_x, is_close_key)
+ key = nyx.popups.show_descriptor(selected.fingerprint, color, is_close_key)
if not key or key.is_selection() or key.match('d'):
break # closes popup
elif key.match('left'):
- self.handle_key(nyx.curses.KeyInput(curses.KEY_UP))
+ _scroll(nyx.curses.KeyInput(curses.KEY_UP))
elif key.match('right'):
- self.handle_key(nyx.curses.KeyInput(curses.KEY_DOWN))
+ _scroll(nyx.curses.KeyInput(curses.KEY_DOWN))
self.redraw(True)
@@ -392,7 +392,7 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
resolver = connection_tracker.get_custom_resolver()
options = ['auto'] + list(connection.Resolver) + list(nyx.tracker.CustomResolver)
- selected = nyx.popups.show_selector('Connection Resolver:', options, resolver if resolver else 'auto')
+ selected = nyx.popups.show_list_selector('Connection Resolver:', options, resolver if resolver else 'auto')
connection_tracker.set_custom_resolver(None if selected == 'auto' else selected)
self.redraw(True)
diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py
index 2b40390..cad754b 100644
--- a/nyx/panel/graph.py
+++ b/nyx/panel/graph.py
@@ -510,7 +510,7 @@ class GraphPanel(nyx.panel.Panel):
options = ['None'] + [stat.capitalize() for stat in available_stats]
previous_selection = options[available_stats.index(self.displayed_stat) + 1] if self.displayed_stat else 'None'
- selection = nyx.popups.show_selector('Graphed Stats:', options, previous_selection)
+ selection = nyx.popups.show_list_selector('Graphed Stats:', options, previous_selection)
self.displayed_stat = None if selection == 'None' else available_stats[options.index(selection) - 1]
def _next_bounds():
@@ -518,7 +518,7 @@ class GraphPanel(nyx.panel.Panel):
self.redraw(True)
def _pick_interval():
- self.update_interval = nyx.popups.show_selector('Update Interval:', list(Interval), self.update_interval)
+ self.update_interval = nyx.popups.show_list_selector('Update Interval:', list(Interval), self.update_interval)
self.redraw(True)
return (
diff --git a/nyx/panel/log.py b/nyx/panel/log.py
index 765e251..4582885 100644
--- a/nyx/panel/log.py
+++ b/nyx/panel/log.py
@@ -237,7 +237,7 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
with nyx.curses.CURSES_LOCK:
options = ['None'] + self._filter.latest_selections() + ['New...']
initial_selection = self._filter.selection() if self._filter.selection() else 'None'
- selection = nyx.popups.show_selector('Log Filter:', options, initial_selection)
+ selection = nyx.popups.show_list_selector('Log Filter:', options, initial_selection)
if selection == 'None':
self._filter.select(None)
diff --git a/nyx/popups.py b/nyx/popups.py
index 261cdce..4bf14fe 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -122,7 +122,7 @@ def show_help():
subwindow.addstr(2, 7, 'Press any key...')
with nyx.curses.CURSES_LOCK:
- nyx.curses.draw(_render, top = control.header_panel().get_height(), width = 80, height = 9)
+ nyx.curses.draw(_render, top = _top(), width = 80, height = 9)
keypress = nyx.curses.key_input()
if keypress.is_selection() or keypress.is_scroll() or keypress.match('left', 'right'):
@@ -146,7 +146,7 @@ def show_about():
subwindow.addstr(2, 7, 'Press any key...')
with nyx.curses.CURSES_LOCK:
- nyx.curses.draw(_render, top = nyx.controller.get_controller().header_panel().get_height(), width = 80, height = 9)
+ nyx.curses.draw(_render, top = _top(), width = 80, height = 9)
nyx.curses.key_input()
@@ -188,18 +188,16 @@ def show_counts(title, counts, fill_char = ' '):
subwindow.addstr(2, subwindow.height - 2, 'Press any key...')
- top = nyx.controller.get_controller().header_panel().get_height()
-
with nyx.curses.CURSES_LOCK:
if not counts:
- nyx.curses.draw(_render_no_stats, top = top, width = len(NO_STATS_MSG) + 4, height = 3)
+ nyx.curses.draw(_render_no_stats, top = _top(), width = len(NO_STATS_MSG) + 4, height = 3)
else:
- nyx.curses.draw(_render_stats, top = top, width = 80, height = 4 + max(1, len(counts)))
+ nyx.curses.draw(_render_stats, top = _top(), width = 80, height = 4 + max(1, len(counts)))
nyx.curses.key_input()
-def show_selector(title, options, previous_selection):
+def show_list_selector(title, options, previous_selection):
"""
Provides list of items the user can choose from.
@@ -211,7 +209,6 @@ def show_selector(title, options, previous_selection):
"""
selected_index = options.index(previous_selection) if previous_selection in options else 0
- top = nyx.controller.get_controller().header_panel().get_height()
def _render(subwindow):
subwindow.box()
@@ -226,8 +223,8 @@ def show_selector(title, options, previous_selection):
with nyx.curses.CURSES_LOCK:
while True:
- nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = top, height = 1) # hides title below us
- nyx.curses.draw(_render, top = top, width = max(map(len, options)) + 9, height = len(options) + 2)
+ nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us
+ nyx.curses.draw(_render, top = _top(), width = max(map(len, options)) + 9, height = len(options) + 2)
key = nyx.curses.key_input()
if key.match('up'):
@@ -285,7 +282,7 @@ def show_sort_dialog(title, options, previous_order, option_colors):
with nyx.curses.CURSES_LOCK:
while len(new_order) < len(previous_order):
- nyx.curses.draw(_render, top = nyx.controller.get_controller().header_panel().get_height(), width = 80, height = 9)
+ nyx.curses.draw(_render, top = _top(), width = 80, height = 9)
key = nyx.curses.key_input()
if key.match('left'):
@@ -311,13 +308,12 @@ def show_sort_dialog(title, options, previous_order, option_colors):
return new_order
-def show_descriptor_popup(fingerprint, color, max_width, is_close_key):
+def show_descriptor(fingerprint, color, is_close_key):
"""
- Provides a dialog showing the descriptors for a given relay.
+ Provides a dialog showing descriptors for a relay.
:param str fingerprint: fingerprint of the relay to be shown
:param str color: text color of the dialog
- :param int max_width: maximum width of the dialog
:param function is_close_key: method to indicate if a key should close the
dialog or not
@@ -326,39 +322,78 @@ def show_descriptor_popup(fingerprint, color, max_width, is_close_key):
"""
if fingerprint:
- title = 'Consensus Descriptor:'
- lines = _display_text(fingerprint)
+ title = 'Consensus Descriptor (%s):' % fingerprint
+ lines = _descriptor_text(fingerprint)
show_line_numbers = True
else:
- title = 'Consensus Descriptor (%s):' % fingerprint
+ title = 'Consensus Descriptor:'
lines = [UNRESOLVED_MSG]
show_line_numbers = False
- popup_height, popup_width = _preferred_size(lines, max_width, show_line_numbers)
+ scroller = nyx.curses.Scroller()
+ line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
- with popup_window(popup_height, popup_width) as (popup, _, height):
- if not popup:
- return None
+ def _render(subwindow):
+ in_block = False # flag indicating if we're currently in crypto content
+ y, offset = 1, line_number_width + 3 if show_line_numbers else 2
+
+ for i, line in enumerate(lines):
+ keyword, value = line, ''
+ line_color = color
+
+ if line in HEADERS:
+ line_color = HEADER_COLOR
+ elif line.startswith(BLOCK_START):
+ in_block = True
+ elif line.startswith(BLOCK_END):
+ in_block = False
+ elif in_block:
+ keyword, value = '', line
+ elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG:
+ keyword, value = line.split(' ', 1)
+ keyword = keyword + ' '
+
+ if i < scroller.location():
+ continue
+
+ if show_line_numbers:
+ subwindow.addstr(2, y, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
+
+ x, y = subwindow.addstr_wrap(3 + line_number_width, y, keyword, subwindow.width - 2, offset, line_color, BOLD)
+ x, y = subwindow.addstr_wrap(x, y, value, subwindow.width - 2, offset, line_color)
+ y += 1
+
+ if y > subwindow.height - 2:
+ break
- with popup_window(1, -1) as (title_erase, _, _):
- title_erase.addstr(0, 0, ' ' * 500) # hide title of the panel below us
+ subwindow.box()
+ subwindow.addstr(0, 0, title, HIGHLIGHT)
- scroller, redraw = nyx.curses.Scroller(), True
+ width, height = 0, len(lines) + 2
+ screen_size = nyx.curses.screen_size()
- while True:
- if redraw:
- _draw(popup, title, lines, color, scroller.location(), show_line_numbers)
- redraw = False
+ for line in lines:
+ width = min(screen_size.width, max(width, len(line) + line_number_width + 5))
+ height += len(line) / (screen_size.width - line_number_width - 5) # extra lines due to text wrap
- key = nyx.curses.key_input()
+ with nyx.curses.CURSES_LOCK:
+ nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us
+ nyx.curses.draw(_render, top = _top(), width = width, height = height)
+ popup_height = min(screen_size.height - _top(), height)
+
+ while True:
+ key = nyx.curses.key_input()
- if key.is_scroll():
- redraw = scroller.handle_key(key, len(lines), height - 2)
- elif is_close_key(key):
- return key
+ if key.is_scroll():
+ is_changed = scroller.handle_key(key, len(lines), popup_height - 2)
+ if is_changed:
+ nyx.curses.draw(_render, top = _top(), width = width, height = height)
+ elif is_close_key(key):
+ return key
-def _display_text(fingerprint):
+
+def _descriptor_text(fingerprint):
"""
Provides the descriptors for a relay.
@@ -383,68 +418,5 @@ def _display_text(fingerprint):
return description.split('\n')
-def _preferred_size(text, max_width, show_line_numbers):
- """
- Provides the preferred dimensions of our dialog.
-
- :param list text: lines of text to be shown
- :param int max_width: maximum width the dialog can be
- :param bool show_line_numbers: if we should leave room for line numbers
-
- :returns: **tuple** of the preferred (height, width)
- """
-
- width, height = 0, len(text) + 2
- line_number_width = int(math.log10(len(text))) + 2 if show_line_numbers else 0
- max_content_width = max_width - line_number_width - 4
-
- for line in text:
- width = min(max_width, max(width, len(line) + line_number_width + 4))
- height += len(line) / max_content_width # extra lines due to text wrap
-
- return (height, width)
-
-
-def _draw(popup, title, lines, entry_color, scroll, show_line_numbers):
- popup.win.erase()
-
- line_number_width = int(math.log10(len(lines))) + 1
- in_block = False # flag indicating if we're currently in crypto content
- width = popup.max_x - 2 # leave space on the right for the border and an empty line
- height = popup.max_y - 2 # height of the dialog without the top and bottom border
- offset = line_number_width + 3 if show_line_numbers else 2
-
- y = 1
-
- for i, line in enumerate(lines):
- keyword, value = line, ''
- color = entry_color
-
- if line in HEADERS:
- color = HEADER_COLOR
- elif line.startswith(BLOCK_START):
- in_block = True
- elif line.startswith(BLOCK_END):
- in_block = False
- elif in_block:
- keyword, value = '', line
- elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG:
- keyword, value = line.split(' ', 1)
-
- if i < scroll:
- continue
-
- if show_line_numbers:
- popup.addstr(y, 2, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
-
- x, y = popup.addstr_wrap(y, 3 + line_number_width, keyword, width, offset, color, BOLD)
- x, y = popup.addstr_wrap(y, x + 1, value, width, offset, color)
-
- y += 1
-
- if y > height:
- break
-
- popup.draw_box()
- popup.addstr(0, 0, title, HIGHLIGHT)
- popup.win.refresh()
+def _top():
+ return nyx.controller.get_controller().header_panel().get_height()
diff --git a/test/popups.py b/test/popups.py
index f3d4963..2a308f0 100644
--- a/test/popups.py
+++ b/test/popups.py
@@ -53,7 +53,7 @@ Client Locales-----------------------------------------------------------------+
+------------------------------------------------------------------------------+
""".strip()
-EXPECTED_SELECTOR = """
+EXPECTED_LIST_SELECTOR = """
Update Interval:---+
| > each second |
| 5 seconds |
@@ -90,9 +90,72 @@ Config Option Ordering:--------------------------------------------------------+
+------------------------------------------------------------------------------+
""".strip()
+EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
+Consensus Descriptor:----------+
+| No consensus data available |
++------------------------------+
+""".strip()
+
+DESCRIPTOR_TEXT = """
+Consensus:
+
+r cyberphunk KXh3YBRc0aRzVSovxkxyqaEwgg4 VjdJThHuYj0jDY2tkkDJkCa8s1s 2016-04-04 19:03:16 94.23.150.191 8080 0
+s Fast Guard Running Stable Valid
+w Bandwidth=8410
+p reject 1-65535
+
+Server Descriptor:
+
+router cyberphunk 94.23.150.191 8080 0 0
+platform Tor 0.2.4.27 on Linux
+protocols Link 1 2 Circuit 1
+published 2016-04-04 19:03:16
+fingerprint 2978 7760 145C D1A4 7355 2A2F C64C 72A9 A130 820E
+uptime 3899791
+bandwidth 10240000 10444800 6482376
+extra-info-digest 9DC532664DDFD238A4119D623D30F136A3B851BF
+reject *:*
+router-signature
+-----BEGIN SIGNATURE-----
+EUFm38gONCoDuY7ZWHyJtBKuvk6Xi1MPuKuecS5frP3fX0wiZSrOVcpX0X8J+4Hr
+Fb5i+yuMIAXeEn6UhtjqhhZBbY9PW9GdZOMTH8hJpG+evURyr+10PZq6UElg86rA
+NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0=
+-----END SIGNATURE-----
+""".strip().split('\n')
+
+EXPECTED_DESCRIPTOR = """
+Consensus Descriptor (29787760145CD1A473552A2FC64C72A9A130820E):---------------------------------------------------+
+| 1 Consensus: |
+| 2 |
+| 3 r cyberphunk KXh3YBRc0aRzVSovxkxyqaEwgg4 VjdJThHuYj0jDY2tkkDJkCa8s1s 2016-04-04 19:03:16 94.23.150.191 8080 0 |
+| 4 s Fast Guard Running Stable Valid |
+| 5 w Bandwidth=8410 |
+| 6 p reject 1-65535 |
+| 7 |
+| 8 Server Descriptor: |
+| 9 |
+| 10 router cyberphunk 94.23.150.191 8080 0 0 |
+| 11 platform Tor 0.2.4.27 on Linux |
+| 12 protocols Link 1 2 Circuit 1 |
+| 13 published 2016-04-04 19:03:16 |
+| 14 fingerprint 2978 7760 145C D1A4 7355 2A2F C64C 72A9 A130 820E |
+| 15 uptime 3899791 |
+| 16 bandwidth 10240000 10444800 6482376 |
+| 17 extra-info-digest 9DC532664DDFD238A4119D623D30F136A3B851BF |
+| 18 reject *:* |
+| 19 router-signature |
+| 20 -----BEGIN SIGNATURE----- |
+| 21 EUFm38gONCoDuY7ZWHyJtBKuvk6Xi1MPuKuecS5frP3fX0wiZSrOVcpX0X8J+4Hr |
+| 22 Fb5i+yuMIAXeEn6UhtjqhhZBbY9PW9GdZOMTH8hJpG+evURyr+10PZq6UElg86rA |
+| 23 NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0= |
+| 24 -----END SIGNATURE----- |
++------------------------------------------------------------------------------------------------------------------+
+""".strip()
+
class TestPopups(unittest.TestCase):
@patch('nyx.controller.get_controller')
+ @patch('nyx.popups._top', Mock(return_value = 0))
def test_help(self, get_controller_mock):
header_panel = Mock()
@@ -121,30 +184,23 @@ class TestPopups(unittest.TestCase):
nyx.panel.KeyHandler('c', 'clear event log'),
)
- get_controller_mock().header_panel().get_height.return_value = 0
get_controller_mock().get_display_panels.return_value = [header_panel, graph_panel, log_panel]
rendered = test.render(nyx.popups.show_help)
self.assertEqual(EXPECTED_HELP_POPUP, rendered.content)
- @patch('nyx.controller.get_controller')
- def test_about(self, get_controller_mock):
- get_controller_mock().header_panel().get_height.return_value = 0
-
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_about(self):
rendered = test.render(nyx.popups.show_about)
self.assertEqual(EXPECTED_ABOUT_POPUP, rendered.content)
- @patch('nyx.controller.get_controller')
- def test_counts_when_empty(self, get_controller_mock):
- get_controller_mock().header_panel().get_height.return_value = 0
-
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_counts_when_empty(self):
rendered = test.render(nyx.popups.show_counts, 'Client Locales', {})
self.assertEqual(EXPECTED_EMPTY_COUNTS, rendered.content)
- @patch('nyx.controller.get_controller')
- def test_counts(self, get_controller_mock):
- get_controller_mock().header_panel().get_height.return_value = 0
-
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_counts(self):
clients = {
'fr': 5,
'us': 6,
@@ -156,19 +212,15 @@ class TestPopups(unittest.TestCase):
rendered = test.render(nyx.popups.show_counts, 'Client Locales', clients, fill_char = '*')
self.assertEqual(EXPECTED_COUNTS, rendered.content)
- @patch('nyx.controller.get_controller')
- def test_selector(self, get_controller_mock):
- get_controller_mock().header_panel().get_height.return_value = 0
-
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_selector(self):
options = ['each second', '5 seconds', '30 seconds', 'minutely', '15 minute', '30 minute', 'hourly', 'daily']
- rendered = test.render(nyx.popups.show_selector, 'Update Interval:', options, 'each second')
- self.assertEqual(EXPECTED_SELECTOR, rendered.content)
+ rendered = test.render(nyx.popups.show_list_selector, 'Update Interval:', options, 'each second')
+ self.assertEqual(EXPECTED_LIST_SELECTOR, rendered.content)
self.assertEqual('each second', rendered.return_value)
- @patch('nyx.controller.get_controller')
- def test_sort_dialog(self, get_controller_mock):
- get_controller_mock().header_panel().get_height.return_value = 0
-
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_sort_dialog(self):
previous_order = ['Man Page Entry', 'Name', 'Is Set']
options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
@@ -176,8 +228,8 @@ class TestPopups(unittest.TestCase):
self.assertEqual(EXPECTED_SORT_DIALOG_START, rendered.content)
self.assertEqual(None, rendered.return_value)
- @patch('nyx.controller.get_controller')
- def test_sort_dialog_selecting(self, get_controller_mock):
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_sort_dialog_selecting(self):
# Use the dialog to make a selection. At the end we render two options as
# being selected (rather than three) because the act of selecing the third
# closed the popup.
@@ -193,11 +245,22 @@ class TestPopups(unittest.TestCase):
with patch('nyx.curses.key_input', side_effect = keypresses):
return nyx.popups.show_sort_dialog('Config Option Ordering:', options, previous_order, {})
- get_controller_mock().header_panel().get_height.return_value = 0
-
previous_order = ['Man Page Entry', 'Name', 'Is Set']
options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
rendered = test.render(draw_func)
self.assertEqual(EXPECTED_SORT_DIALOG_END, rendered.content)
self.assertEqual(['Name', 'Summary', 'Description'], rendered.return_value)
+
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_descriptor_without_fingerprint(self):
+ rendered = test.render(nyx.popups.show_descriptor, None, nyx.curses.Color.RED, lambda key: key.match('esc'))
+ self.assertEqual(EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT, rendered.content)
+ self.assertEqual(nyx.curses.KeyInput(27), rendered.return_value)
+
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ @patch('nyx.popups._descriptor_text', Mock(return_value = DESCRIPTOR_TEXT))
+ def test_descriptor(self):
+ rendered = test.render(nyx.popups.show_descriptor, '29787760145CD1A473552A2FC64C72A9A130820E', nyx.curses.Color.RED, lambda key: key.match('esc'))
+ self.assertEqual(EXPECTED_DESCRIPTOR, rendered.content)
+ self.assertEqual(nyx.curses.KeyInput(27), rendered.return_value)
1
0

05 Apr '16
commit da49b908fb1ee3dc6a7cbd0e90a5ed76ec890e2c
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Apr 4 18:14:36 2016 -0700
Revise confirmation dialog for saving the torrc
Overhauling our last popup. Now that the popup module has everything picking a
uniform naming scheme.
---
nyx/panel/config.py | 60 +++---------
nyx/panel/connection.py | 4 +-
nyx/panel/graph.py | 4 +-
nyx/panel/log.py | 4 +-
nyx/popups.py | 248 +++++++++++++++++++++++++++++-------------------
test/popups.py | 63 +++++++++---
6 files changed, 219 insertions(+), 164 deletions(-)
diff --git a/nyx/panel/config.py b/nyx/panel/config.py
index 6de9e61..1b991b6 100644
--- a/nyx/panel/config.py
+++ b/nyx/panel/config.py
@@ -17,7 +17,7 @@ import nyx.popups
import stem.control
import stem.manual
-from nyx.curses import GREEN, CYAN, WHITE, NORMAL, BOLD, HIGHLIGHT
+from nyx.curses import WHITE, NORMAL, BOLD, HIGHLIGHT
from nyx import DATA_DIR, tor_controller
from stem.util import conf, enum, log, str_tools
@@ -175,7 +175,7 @@ class ConfigPanel(nyx.panel.Panel):
"""
sort_colors = dict([(attr, CONFIG['attr.config.sort_color'].get(attr, WHITE)) for attr in SortAttr])
- results = nyx.popups.show_sort_dialog('Config Option Ordering:', SortAttr, self._sort_order, sort_colors)
+ results = nyx.popups.select_sort_order('Config Option Ordering:', SortAttr, self._sort_order, sort_colors)
if results:
self._sort_order = results
@@ -186,53 +186,15 @@ class ConfigPanel(nyx.panel.Panel):
Confirmation dialog for saving tor's configuration.
"""
- selection, controller = 1, tor_controller()
- config_text = controller.get_info('config-text', None)
- config_lines = config_text.splitlines() if config_text else []
-
- with nyx.popups.popup_window(len(config_lines) + 2) as (popup, width, height):
- if not popup or height <= 2:
- return
-
- while True:
- height, width = popup.get_preferred_size() # allow us to be resized
- popup.win.erase()
-
- for i, full_line in enumerate(config_lines):
- line = str_tools.crop(full_line, width - 2)
- option, arg = line.split(' ', 1) if ' ' in line else (line, '')
-
- popup.addstr(i + 1, 1, option, GREEN, BOLD)
- popup.addstr(i + 1, len(option) + 2, arg, CYAN, BOLD)
-
- x = width - 16
-
- for i, option in enumerate(['Save', 'Cancel']):
- x = popup.addstr(height - 2, x, '[')
- x = popup.addstr(height - 2, x, option, BOLD, HIGHLIGHT if i == selection else NORMAL)
- x = popup.addstr(height - 2, x, '] ')
-
- popup.draw_box()
- popup.addstr(0, 0, 'Torrc to save:', HIGHLIGHT)
- popup.win.refresh()
-
- key = nyx.curses.key_input()
-
- if key.match('left'):
- selection = max(0, selection - 1)
- elif key.match('right'):
- selection = min(1, selection + 1)
- elif key.is_selection():
- if selection == 0:
- try:
- controller.save_conf()
- nyx.controller.show_message('Saved configuration to %s' % controller.get_info('config-file', '<unknown>'), HIGHLIGHT, max_wait = 2)
- except IOError as exc:
- nyx.controller.show_message('Unable to save configuration (%s)' % exc.strerror, HIGHLIGHT, max_wait = 2)
-
- break
- elif key.match('esc'):
- break # esc - cancel
+ controller = tor_controller()
+ torrc = controller.get_info('config-text', None)
+
+ if nyx.popups.confirm_save_torrc(torrc):
+ try:
+ controller.save_conf()
+ nyx.controller.show_message('Saved configuration to %s' % controller.get_info('config-file', '<unknown>'), HIGHLIGHT, max_wait = 2)
+ except IOError as exc:
+ nyx.controller.show_message('Unable to save configuration (%s)' % exc.strerror, HIGHLIGHT, max_wait = 2)
self.redraw(True)
diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py
index afd1da1..709cf52 100644
--- a/nyx/panel/connection.py
+++ b/nyx/panel/connection.py
@@ -311,7 +311,7 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
"""
sort_colors = dict([(attr, CONFIG['attr.connection.sort_color'].get(attr, WHITE)) for attr in SortAttr])
- results = nyx.popups.show_sort_dialog('Connection Ordering:', SortAttr, self._sort_order, sort_colors)
+ results = nyx.popups.select_sort_order('Connection Ordering:', SortAttr, self._sort_order, sort_colors)
if results:
self._sort_order = results
@@ -392,7 +392,7 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
resolver = connection_tracker.get_custom_resolver()
options = ['auto'] + list(connection.Resolver) + list(nyx.tracker.CustomResolver)
- selected = nyx.popups.show_list_selector('Connection Resolver:', options, resolver if resolver else 'auto')
+ selected = nyx.popups.select_from_list('Connection Resolver:', options, resolver if resolver else 'auto')
connection_tracker.set_custom_resolver(None if selected == 'auto' else selected)
self.redraw(True)
diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py
index cad754b..7c53675 100644
--- a/nyx/panel/graph.py
+++ b/nyx/panel/graph.py
@@ -510,7 +510,7 @@ class GraphPanel(nyx.panel.Panel):
options = ['None'] + [stat.capitalize() for stat in available_stats]
previous_selection = options[available_stats.index(self.displayed_stat) + 1] if self.displayed_stat else 'None'
- selection = nyx.popups.show_list_selector('Graphed Stats:', options, previous_selection)
+ selection = nyx.popups.select_from_list('Graphed Stats:', options, previous_selection)
self.displayed_stat = None if selection == 'None' else available_stats[options.index(selection) - 1]
def _next_bounds():
@@ -518,7 +518,7 @@ class GraphPanel(nyx.panel.Panel):
self.redraw(True)
def _pick_interval():
- self.update_interval = nyx.popups.show_list_selector('Update Interval:', list(Interval), self.update_interval)
+ self.update_interval = nyx.popups.select_from_list('Update Interval:', list(Interval), self.update_interval)
self.redraw(True)
return (
diff --git a/nyx/panel/log.py b/nyx/panel/log.py
index 364878f..dc26dc3 100644
--- a/nyx/panel/log.py
+++ b/nyx/panel/log.py
@@ -141,7 +141,7 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
Prompts the user to select the events being listened for.
"""
- event_types = nyx.popups.show_event_selector()
+ event_types = nyx.popups.select_event_types()
if event_types != self._event_types:
self._event_types = nyx.log.listen_for_events(self._register_tor_event, event_types)
@@ -214,7 +214,7 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
with nyx.curses.CURSES_LOCK:
options = ['None'] + self._filter.latest_selections() + ['New...']
initial_selection = self._filter.selection() if self._filter.selection() else 'None'
- selection = nyx.popups.show_list_selector('Log Filter:', options, initial_selection)
+ selection = nyx.popups.select_from_list('Log Filter:', options, initial_selection)
if selection == 'None':
self._filter.select(None)
diff --git a/nyx/popups.py b/nyx/popups.py
index 3160949..b7ad7b1 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -2,7 +2,20 @@
# See LICENSE for licensing information
"""
-Functions for displaying popups in the interface.
+Popup dialogs provided by our interface.
+
+::
+
+ show_help - keybindings provided by the current page
+ show_about - basic information about our application
+ show_counts - listing of counts with bar graphs
+ show_descriptor - presents descriptors for a relay
+
+ select_from_list - selects from a list of options
+ select_sort_order - selects attributes by which to sort by
+ select_event_types - select from a list of event types
+
+ confirm_save_torrc - confirmation dialog for saving the torrc
"""
import math
@@ -204,7 +217,117 @@ def show_counts(title, counts, fill_char = ' '):
nyx.curses.key_input()
-def show_list_selector(title, options, previous_selection):
+def show_descriptor(fingerprint, color, is_close_key):
+ """
+ Provides a dialog showing descriptors for a relay.
+
+ :param str fingerprint: fingerprint of the relay to be shown
+ :param str color: text color of the dialog
+ :param function is_close_key: method to indicate if a key should close the
+ dialog or not
+
+ :returns: :class:`~nyx.curses.KeyInput` for the keyboard input that
+ closed the dialog
+ """
+
+ if fingerprint:
+ title = 'Consensus Descriptor (%s):' % fingerprint
+ lines = _descriptor_text(fingerprint)
+ show_line_numbers = True
+ else:
+ title = 'Consensus Descriptor:'
+ lines = [UNRESOLVED_MSG]
+ show_line_numbers = False
+
+ scroller = nyx.curses.Scroller()
+ line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
+
+ def _render(subwindow):
+ in_block = False # flag indicating if we're currently in crypto content
+ y, offset = 1, line_number_width + 3 if show_line_numbers else 2
+
+ for i, line in enumerate(lines):
+ keyword, value = line, ''
+ line_color = color
+
+ if line in HEADERS:
+ line_color = HEADER_COLOR
+ elif line.startswith(BLOCK_START):
+ in_block = True
+ elif line.startswith(BLOCK_END):
+ in_block = False
+ elif in_block:
+ keyword, value = '', line
+ elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG:
+ keyword, value = line.split(' ', 1)
+ keyword = keyword + ' '
+
+ if i < scroller.location():
+ continue
+
+ if show_line_numbers:
+ subwindow.addstr(2, y, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
+
+ x, y = subwindow.addstr_wrap(3 + line_number_width, y, keyword, subwindow.width - 2, offset, line_color, BOLD)
+ x, y = subwindow.addstr_wrap(x, y, value, subwindow.width - 2, offset, line_color)
+ y += 1
+
+ if y > subwindow.height - 2:
+ break
+
+ subwindow.box()
+ subwindow.addstr(0, 0, title, HIGHLIGHT)
+
+ width, height = 0, len(lines) + 2
+ screen_size = nyx.curses.screen_size()
+
+ for line in lines:
+ width = min(screen_size.width, max(width, len(line) + line_number_width + 5))
+ height += len(line) / (screen_size.width - line_number_width - 5) # extra lines due to text wrap
+
+ with nyx.curses.CURSES_LOCK:
+ nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us
+ nyx.curses.draw(_render, top = _top(), width = width, height = height)
+ popup_height = min(screen_size.height - _top(), height)
+
+ while True:
+ key = nyx.curses.key_input()
+
+ if key.is_scroll():
+ is_changed = scroller.handle_key(key, len(lines), popup_height - 2)
+
+ if is_changed:
+ nyx.curses.draw(_render, top = _top(), width = width, height = height)
+ elif is_close_key(key):
+ return key
+
+
+def _descriptor_text(fingerprint):
+ """
+ Provides the descriptors for a relay.
+
+ :param str fingerprint: relay fingerprint to be looked up
+
+ :returns: **list** with the lines that should be displayed in the dialog
+ """
+
+ controller = nyx.tor_controller()
+ router_status_entry = controller.get_network_status(fingerprint, None)
+ microdescriptor = controller.get_microdescriptor(fingerprint, None)
+ server_descriptor = controller.get_server_descriptor(fingerprint, None)
+
+ description = 'Consensus:\n\n%s' % (router_status_entry if router_status_entry else ERROR_MSG)
+
+ if server_descriptor:
+ description += '\n\nServer Descriptor:\n\n%s' % server_descriptor
+
+ if microdescriptor:
+ description += '\n\nMicrodescriptor:\n\n%s' % microdescriptor
+
+ return description.split('\n')
+
+
+def select_from_list(title, options, previous_selection):
"""
Provides list of items the user can choose from.
@@ -244,7 +367,7 @@ def show_list_selector(title, options, previous_selection):
return previous_selection
-def show_sort_dialog(title, options, previous_order, option_colors):
+def select_sort_order(title, options, previous_order, option_colors):
"""
Provides sorting dialog of the form...
@@ -315,7 +438,7 @@ def show_sort_dialog(title, options, previous_order, option_colors):
return new_order
-def show_event_selector():
+def select_event_types():
"""
Presents a chart of event types we support, with a prompt for the user to
select a set.
@@ -345,114 +468,49 @@ def show_event_selector():
return None
-def show_descriptor(fingerprint, color, is_close_key):
+def confirm_save_torrc(torrc):
"""
- Provides a dialog showing descriptors for a relay.
+ Provides a confirmation dialog for saving tor's current configuration.
- :param str fingerprint: fingerprint of the relay to be shown
- :param str color: text color of the dialog
- :param function is_close_key: method to indicate if a key should close the
- dialog or not
+ :param str torrc: torrc that would be saved
- :returns: :class:`~nyx.curses.KeyInput` for the keyboard input that
- closed the dialog
+ :returns: **True** if the torrc should be saved and **False** otherwise
"""
- if fingerprint:
- title = 'Consensus Descriptor (%s):' % fingerprint
- lines = _descriptor_text(fingerprint)
- show_line_numbers = True
- else:
- title = 'Consensus Descriptor:'
- lines = [UNRESOLVED_MSG]
- show_line_numbers = False
-
- scroller = nyx.curses.Scroller()
- line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
+ torrc_lines = torrc.splitlines() if torrc else []
+ selection = 1
def _render(subwindow):
- in_block = False # flag indicating if we're currently in crypto content
- y, offset = 1, line_number_width + 3 if show_line_numbers else 2
-
- for i, line in enumerate(lines):
- keyword, value = line, ''
- line_color = color
-
- if line in HEADERS:
- line_color = HEADER_COLOR
- elif line.startswith(BLOCK_START):
- in_block = True
- elif line.startswith(BLOCK_END):
- in_block = False
- elif in_block:
- keyword, value = '', line
- elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG:
- keyword, value = line.split(' ', 1)
- keyword = keyword + ' '
+ for i, full_line in enumerate(torrc_lines):
+ line = stem.util.str_tools.crop(full_line, subwindow.width - 2)
+ option, arg = line.split(' ', 1) if ' ' in line else (line, '')
- if i < scroller.location():
- continue
-
- if show_line_numbers:
- subwindow.addstr(2, y, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
+ subwindow.addstr(1, i + 1, option, GREEN, BOLD)
+ subwindow.addstr(len(option) + 2, i + 1, arg, CYAN, BOLD)
- x, y = subwindow.addstr_wrap(3 + line_number_width, y, keyword, subwindow.width - 2, offset, line_color, BOLD)
- x, y = subwindow.addstr_wrap(x, y, value, subwindow.width - 2, offset, line_color)
- y += 1
+ x = subwindow.width - 16
- if y > subwindow.height - 2:
- break
+ for i, option in enumerate(['Save', 'Cancel']):
+ x = subwindow.addstr(x, subwindow.height - 2, '[')
+ x = subwindow.addstr(x, subwindow.height - 2, option, BOLD, HIGHLIGHT if i == selection else NORMAL)
+ x = subwindow.addstr(x, subwindow.height - 2, '] ')
subwindow.box()
- subwindow.addstr(0, 0, title, HIGHLIGHT)
-
- width, height = 0, len(lines) + 2
- screen_size = nyx.curses.screen_size()
-
- for line in lines:
- width = min(screen_size.width, max(width, len(line) + line_number_width + 5))
- height += len(line) / (screen_size.width - line_number_width - 5) # extra lines due to text wrap
+ subwindow.addstr(0, 0, 'Torrc to save:', HIGHLIGHT)
with nyx.curses.CURSES_LOCK:
- nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us
- nyx.curses.draw(_render, top = _top(), width = width, height = height)
- popup_height = min(screen_size.height - _top(), height)
-
while True:
+ nyx.curses.draw(_render, top = _top(), height = len(torrc_lines) + 2)
key = nyx.curses.key_input()
- if key.is_scroll():
- is_changed = scroller.handle_key(key, len(lines), popup_height - 2)
-
- if is_changed:
- nyx.curses.draw(_render, top = _top(), width = width, height = height)
- elif is_close_key(key):
- return key
-
-
-def _descriptor_text(fingerprint):
- """
- Provides the descriptors for a relay.
-
- :param str fingerprint: relay fingerprint to be looked up
-
- :returns: **list** with the lines that should be displayed in the dialog
- """
-
- controller = nyx.tor_controller()
- router_status_entry = controller.get_network_status(fingerprint, None)
- microdescriptor = controller.get_microdescriptor(fingerprint, None)
- server_descriptor = controller.get_server_descriptor(fingerprint, None)
-
- description = 'Consensus:\n\n%s' % (router_status_entry if router_status_entry else ERROR_MSG)
-
- if server_descriptor:
- description += '\n\nServer Descriptor:\n\n%s' % server_descriptor
-
- if microdescriptor:
- description += '\n\nMicrodescriptor:\n\n%s' % microdescriptor
-
- return description.split('\n')
+ if key.match('left'):
+ selection = max(0, selection - 1)
+ elif key.match('right'):
+ selection = min(1, selection + 1)
+ elif key.is_selection():
+ return selection == 0
+ elif key.match('esc'):
+ return False # esc - cancel
def _top():
diff --git a/test/popups.py b/test/popups.py
index e34398b..34ff587 100644
--- a/test/popups.py
+++ b/test/popups.py
@@ -109,10 +109,24 @@ Event Types:-------------------------------------------------------------------+
+------------------------------------------------------------------------------+
""".strip()
-EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
-Consensus Descriptor:----------+
-| No consensus data available |
-+------------------------------+
+TORRC = """
+ControlPort 9051
+CookieAuthentication 1
+ExitPolicy reject *:*
+DataDirectory /home/atagar/.tor
+Log notice file /home/atagar/.tor/log
+ORPort 7000
+""".strip()
+
+EXPECTED_SAVE_TORRC_CONFIRMATION = """
+Torrc to save:-----------------------------------------------------------------+
+|ControlPort 9051 |
+|CookieAuthentication 1 |
+|ExitPolicy reject *:* |
+|DataDirectory /home/atagar/.tor |
+|Log notice file /home/atagar/.tor/log |
+|ORPort 7000 [Save] [Cancel]|
++------------------------------------------------------------------------------+
""".strip()
DESCRIPTOR_TEXT = """
@@ -142,6 +156,12 @@ NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0=
-----END SIGNATURE-----
""".strip().split('\n')
+EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
+Consensus Descriptor:----------+
+| No consensus data available |
++------------------------------+
+""".strip()
+
EXPECTED_DESCRIPTOR = """
Consensus Descriptor (29787760145CD1A473552A2FC64C72A9A130820E):---------------------------------------------------+
| 1 Consensus: |
@@ -232,23 +252,23 @@ class TestPopups(unittest.TestCase):
self.assertEqual(EXPECTED_COUNTS, rendered.content)
@patch('nyx.popups._top', Mock(return_value = 0))
- def test_selector(self):
+ def test_select_from_list(self):
options = ['each second', '5 seconds', '30 seconds', 'minutely', '15 minute', '30 minute', 'hourly', 'daily']
- rendered = test.render(nyx.popups.show_list_selector, 'Update Interval:', options, 'each second')
+ rendered = test.render(nyx.popups.select_from_list, 'Update Interval:', options, 'each second')
self.assertEqual(EXPECTED_LIST_SELECTOR, rendered.content)
self.assertEqual('each second', rendered.return_value)
@patch('nyx.popups._top', Mock(return_value = 0))
- def test_sort_dialog(self):
+ def test_select_sort_order(self):
previous_order = ['Man Page Entry', 'Name', 'Is Set']
options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
- rendered = test.render(nyx.popups.show_sort_dialog, 'Config Option Ordering:', options, previous_order, {})
+ rendered = test.render(nyx.popups.select_sort_order, 'Config Option Ordering:', options, previous_order, {})
self.assertEqual(EXPECTED_SORT_DIALOG_START, rendered.content)
self.assertEqual(None, rendered.return_value)
@patch('nyx.popups._top', Mock(return_value = 0))
- def test_sort_dialog_selecting(self):
+ def test_select_sort_order_usage(self):
# Use the dialog to make a selection. At the end we render two options as
# being selected (rather than three) because the act of selecing the third
# closed the popup.
@@ -262,7 +282,7 @@ class TestPopups(unittest.TestCase):
def draw_func():
with patch('nyx.curses.key_input', side_effect = keypresses):
- return nyx.popups.show_sort_dialog('Config Option Ordering:', options, previous_order, {})
+ return nyx.popups.select_sort_order('Config Option Ordering:', options, previous_order, {})
previous_order = ['Man Page Entry', 'Name', 'Is Set']
options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
@@ -273,18 +293,33 @@ class TestPopups(unittest.TestCase):
@patch('nyx.popups._top', Mock(return_value = 0))
@patch('nyx.controller.input_prompt', Mock(return_value = None))
- def test_event_selector_when_canceled(self):
- rendered = test.render(nyx.popups.show_event_selector)
+ def test_select_event_types_when_canceled(self):
+ rendered = test.render(nyx.popups.select_event_types)
self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
self.assertEqual(None, rendered.return_value)
@patch('nyx.popups._top', Mock(return_value = 0))
@patch('nyx.controller.input_prompt', Mock(return_value = '2bwe'))
- def test_event_selector_with_input(self):
- rendered = test.render(nyx.popups.show_event_selector)
+ def test_select_event_types_with_input(self):
+ rendered = test.render(nyx.popups.select_event_types)
self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
self.assertEqual(set(['NYX_INFO', 'ERR', 'WARN', 'BW', 'NYX_ERR', 'NYX_WARN', 'NYX_NOTICE']), rendered.return_value)
+ @patch('nyx.curses.screen_size', Mock(return_value = nyx.curses.Dimensions(80, 60)))
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ def test_confirm_save_torrc(self):
+ rendered = test.render(nyx.popups.confirm_save_torrc, TORRC)
+ self.assertEqual(EXPECTED_SAVE_TORRC_CONFIRMATION, rendered.content)
+ self.assertEqual(False, rendered.return_value)
+
+ def draw_func():
+ with patch('nyx.curses.key_input', side_effect = [nyx.curses.KeyInput(curses.KEY_LEFT), nyx.curses.KeyInput(curses.KEY_ENTER)]):
+ return nyx.popups.confirm_save_torrc(TORRC)
+
+ rendered = test.render(draw_func)
+ self.assertEqual(EXPECTED_SAVE_TORRC_CONFIRMATION, rendered.content)
+ self.assertEqual(True, rendered.return_value)
+
@patch('nyx.popups._top', Mock(return_value = 0))
def test_descriptor_without_fingerprint(self):
rendered = test.render(nyx.popups.show_descriptor, None, nyx.curses.Color.RED, lambda key: key.match('esc'))
1
0
commit ad18634b75e6b08e9c5ba7d63e9a7f89ed018051
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Apr 4 18:47:20 2016 -0700
Drop popup_window() usage in menu
The menu is very much due for an overhaul. In fact, is the last file in our
hole damn codebase that hasn't been touched yet! But for now simply replacing
its popup_window() so we can finally drop that function.
---
nyx/curses.py | 28 ++++++++++++-----
nyx/menu/menu.py | 95 +++++++++++++++++++++-----------------------------------
nyx/popups.py | 51 ------------------------------
3 files changed, 55 insertions(+), 119 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py
index 3718742..95d9dda 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -29,7 +29,10 @@ if we want Windows support in the future too.
Subwindow - subwindow that can be drawn within
|- addstr - draws a string
- +- box - draws box with the given dimensions
+ |- addstr_wrap - draws a string with line wrapping
+ |- box - draws box with the given dimensions
+ |- hline - draws a horizontal line
+ +- vline - draws a vertical line
KeyInput - user keyboard input
|- match - checks if this matches the given inputs
@@ -414,7 +417,7 @@ def is_wide_characters_supported():
return False
-def draw(func, left = 0, top = 0, width = None, height = None):
+def draw(func, left = 0, top = 0, width = None, height = None, background = None):
"""
Renders a subwindow. This calls the given draw function with a
:class:`~nyx.curses._Subwindow`.
@@ -424,6 +427,7 @@ def draw(func, left = 0, top = 0, width = None, height = None):
:param int top: top position of the panel
:param int width: panel width, uses all available space if **None**
:param int height: panel height, uses all available space if **None**
+ :param nyx.curses.Color background: background color, unset if **None**
"""
with CURSES_LOCK:
@@ -439,6 +443,10 @@ def draw(func, left = 0, top = 0, width = None, height = None):
curses_subwindow = CURSES_SCREEN.subwin(subwindow_height, subwindow_width, top, left)
curses_subwindow.erase()
+
+ if background:
+ curses_subwindow.bkgd(' ', curses_attr(background, HIGHLIGHT))
+
func(_Subwindow(subwindow_width, subwindow_height, curses_subwindow))
curses_subwindow.refresh()
@@ -464,6 +472,8 @@ class _Subwindow(object):
:param int y: vertical location
:param str msg: string to be written
:param list attr: text attributes to apply
+
+ :returns: **int** with the horizontal position we drew to
"""
if self.width > x and self.height > y:
@@ -486,6 +496,8 @@ class _Subwindow(object):
:param int width: width avaialble to render the string
:param int min_x: horizontal position to wrap to on new lines
:param list attr: text attributes to apply
+
+ :returns: **tuple** of the (x, y) position we drew to
"""
orig_y = y
@@ -523,10 +535,10 @@ class _Subwindow(object):
width = max_width if width is None else min(width, max_width)
height = max_height if height is None else min(height, max_height)
- self._hline(left + 1, top, width - 2, *attr) # top
- self._hline(left + 1, top + height - 1, width - 2, *attr) # bottom
- self._vline(left, top + 1, height - 2, *attr) # left
- self._vline(left + width - 1, top + 1, height - 2, *attr) # right
+ self.hline(left + 1, top, width - 2, *attr) # top
+ self.hline(left + 1, top + height - 1, width - 2, *attr) # bottom
+ self.vline(left, top + 1, height - 2, *attr) # left
+ self.vline(left + width - 1, top + 1, height - 2, *attr) # right
self._addch(left, top, curses.ACS_ULCORNER, *attr) # upper left corner
self._addch(left, top + height - 1, curses.ACS_LLCORNER, *attr) # lower left corner
@@ -543,14 +555,14 @@ class _Subwindow(object):
return x
- def _hline(self, x, y, length, *attr):
+ def hline(self, x, y, length, *attr):
if self.width > x and self.height > y:
try:
self._curses_subwindow.hline(y, x, curses.ACS_HLINE | curses_attr(*attr), min(length, self.width - x))
except:
pass
- def _vline(self, x, y, length, *attr):
+ def vline(self, x, y, length, *attr):
if self.width > x and self.height > y:
try:
self._curses_subwindow.vline(y, x, curses.ACS_VLINE | curses_attr(*attr), min(length, self.height - y))
diff --git a/nyx/menu/menu.py b/nyx/menu/menu.py
index 5b719bb..01aa25e 100644
--- a/nyx/menu/menu.py
+++ b/nyx/menu/menu.py
@@ -11,7 +11,7 @@ import nyx.controller
import nyx.menu.item
import nyx.menu.actions
-from nyx.curses import RED, WHITE, NORMAL, BOLD, UNDERLINE, HIGHLIGHT
+from nyx.curses import RED, WHITE, NORMAL, BOLD, UNDERLINE
class MenuCursor:
@@ -78,54 +78,43 @@ class MenuCursor:
def show_menu():
- with nyx.popups.popup_window(1, below_static = False) as (popup, width, height):
- if popup:
- # generates the menu and uses the initial selection of the first item in
- # the file menu
+ selection_left = [0]
- menu = nyx.menu.actions.make_menu()
- cursor = MenuCursor(menu.get_children()[0].get_children()[0])
+ def _render(subwindow):
+ x = 0
- while not cursor.is_done():
- # sets the background color
-
- popup.win.clear()
- popup.win.bkgd(' ', nyx.curses.curses_attr(RED, HIGHLIGHT))
- selection_hierarchy = cursor.get_selection().get_hierarchy()
-
- # provide a message saying how to close the menu
-
- nyx.controller.show_message('Press m or esc to close the menu.', BOLD)
-
- # renders the menu bar, noting where the open submenu is positioned
-
- draw_left, selection_left = 0, 0
-
- for top_level_item in menu.get_children():
- if top_level_item == selection_hierarchy[1]:
- attr = UNDERLINE
- selection_left = draw_left
- else:
- attr = NORMAL
+ for top_level_item in menu.get_children():
+ if top_level_item == selection_hierarchy[1]:
+ selection_left[0] = x
+ attr = UNDERLINE
+ else:
+ attr = NORMAL
- draw_label = ' %s ' % top_level_item.get_label()[1]
- popup.addstr(0, draw_left, draw_label, BOLD, attr)
- popup.vline(0, draw_left + len(draw_label), 1)
+ x = subwindow.addstr(x, 0, ' %s ' % top_level_item.get_label()[1], BOLD, attr)
+ subwindow.vline(x, 0, 1)
+ x += 1
- draw_left += len(draw_label) + 1
+ with nyx.curses.CURSES_LOCK:
+ # generates the menu and uses the initial selection of the first item in
+ # the file menu
- # recursively shows opened submenus
+ menu = nyx.menu.actions.make_menu()
+ cursor = MenuCursor(menu.get_children()[0].get_children()[0])
- _draw_submenu(cursor, 1, 1, selection_left)
+ while not cursor.is_done():
+ selection_hierarchy = cursor.get_selection().get_hierarchy()
- popup.win.refresh()
+ # provide a message saying how to close the menu
- cursor.handle_key(nyx.curses.key_input())
+ nyx.controller.show_message('Press m or esc to close the menu.', BOLD)
+ nyx.curses.draw(_render, height = 1, background = RED)
+ _draw_submenu(cursor, 1, 1, selection_left[0])
+ cursor.handle_key(nyx.curses.key_input())
- # redraws the rest of the interface if we're rendering on it again
+ # redraws the rest of the interface if we're rendering on it again
- if not cursor.is_done():
- nyx.controller.get_controller().redraw()
+ if not cursor.is_done():
+ nyx.controller.get_controller().redraw()
nyx.controller.show_message()
@@ -154,29 +143,15 @@ def _draw_submenu(cursor, level, top, left):
label_format = ' %%-%is%%-%is%%-%is ' % (prefix_col_size, middle_col_size, suffix_col_size)
menu_width = len(label_format % ('', '', ''))
+ selection_top = submenu.get_children().index(selection) if selection in submenu.get_children() else 0
- with nyx.popups.popup_window(len(submenu.get_children()), menu_width, top, left, below_static = False) as (popup, _, _):
- if not popup:
- return
-
- # sets the background color
-
- popup.win.bkgd(' ', nyx.curses.curses_attr(RED, HIGHLIGHT))
-
- draw_top, selection_top = 0, 0
-
- for menu_item in submenu.get_children():
+ def _render(subwindow):
+ for y, menu_item in enumerate(submenu.get_children()):
if menu_item == selection:
- draw_format = (WHITE, BOLD)
- selection_top = draw_top
+ subwindow.addstr(0, y, label_format % menu_item.get_label(), WHITE, BOLD)
else:
- draw_format = (NORMAL,)
-
- popup.addstr(draw_top, 0, label_format % menu_item.get_label(), *draw_format)
- draw_top += 1
-
- popup.win.refresh()
-
- # shows the next submenu
+ subwindow.addstr(0, y, label_format % menu_item.get_label())
+ with nyx.curses.CURSES_LOCK:
+ nyx.curses.draw(_render, top = top, left = left, width = menu_width, height = len(submenu.get_children()), background = RED)
_draw_submenu(cursor, level + 1, top + selection_top, left + menu_width)
diff --git a/nyx/popups.py b/nyx/popups.py
index b7ad7b1..70d996a 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -47,57 +47,6 @@ CONFIG = stem.util.conf.config_dict('nyx', {
})
-def popup_window(height = -1, width = -1, top = 0, left = 0, below_static = True):
- """
- Provides a popup dialog you can use in a 'with' block...
-
- with popup_window(5, 10) as (popup, width, height):
- if popup:
- ... do stuff...
-
- This popup has a lock on the curses interface for the duration of the block,
- preventing other draw operations from taking place. If the popup isn't
- visible then the popup it returns will be **None**.
-
- :param int height: maximum height of the popup
- :param int width: maximum width of the popup
- :param int top: top position, relative to the sticky content
- :param int left: left position from the screen
- :param bool below_static: positions popup below static content if True
-
- :returns: tuple of the form (subwindow, width, height) when used in a with block
- """
-
- class _Popup(object):
- def __enter__(self):
- control = nyx.controller.get_controller()
-
- if below_static:
- sticky_height = control.header_panel().get_height()
- else:
- sticky_height = 0
-
- popup = nyx.panel.Panel('popup', top + sticky_height, left, height, width)
- popup.set_visible(True)
-
- # Redraws the popup to prepare a subwindow instance. If none is spawned then
- # the panel can't be drawn (for instance, due to not being visible).
-
- popup.redraw(True)
-
- if popup.win is not None:
- nyx.curses.CURSES_LOCK.acquire()
- return (popup, popup.max_x - 1, popup.max_y)
- else:
- return (None, 0, 0)
-
- def __exit__(self, exit_type, value, traceback):
- nyx.curses.CURSES_LOCK.release()
- nyx.controller.get_controller().redraw(False)
-
- return _Popup()
-
-
def show_help():
"""
Presents a popup with the current page's hotkeys.
1
0
commit 2ab1b5f2f2f90ea11383c091b0b86202b7c3ca5d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Apr 4 17:11:33 2016 -0700
Revise event selector dialog
There's a couple popups left within the panels. Moving, cleaning up, and
testing.
---
nyx/panel/log.py | 31 ++++---------------------------
nyx/popups.py | 37 +++++++++++++++++++++++++++++++++++++
test/popups.py | 33 +++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+), 27 deletions(-)
diff --git a/nyx/panel/log.py b/nyx/panel/log.py
index 4582885..364878f 100644
--- a/nyx/panel/log.py
+++ b/nyx/panel/log.py
@@ -43,7 +43,6 @@ CONFIG = conf.config_dict('nyx', {
'features.log.prepopulateReadLimit': 5000,
'features.log.maxRefreshRate': 300,
'features.log.regex': [],
- 'msg.misc.event_types': '',
'startup.events': 'N3',
}, conf_handler)
@@ -142,33 +141,11 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
Prompts the user to select the events being listened for.
"""
- # allow user to enter new types of events to log - unchanged if left blank
+ event_types = nyx.popups.show_event_selector()
- with nyx.popups.popup_window(16, 80) as (popup, width, height):
- if popup:
- # displays the available flags
-
- popup.draw_box()
- popup.addstr(0, 0, 'Event Types:', HIGHLIGHT)
- event_lines = CONFIG['msg.misc.event_types'].split('\n')
-
- for i in range(len(event_lines)):
- popup.addstr(i + 1, 1, event_lines[i][6:])
-
- popup.win.refresh()
-
- user_input = nyx.controller.input_prompt('Events to log: ')
-
- if user_input:
- try:
- user_input = user_input.replace(' ', '') # strip spaces
- event_types = nyx.arguments.expand_events(user_input)
-
- if event_types != self._event_types:
- self._event_types = nyx.log.listen_for_events(self._register_tor_event, event_types)
- self.redraw(True)
- except ValueError as exc:
- nyx.controller.show_message('Invalid flags: %s' % exc, HIGHLIGHT, max_wait = 2)
+ if event_types != self._event_types:
+ self._event_types = nyx.log.listen_for_events(self._register_tor_event, event_types)
+ self.redraw(True)
def show_snapshot_prompt(self):
"""
diff --git a/nyx/popups.py b/nyx/popups.py
index 4bf14fe..3160949 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -9,12 +9,15 @@ import math
import operator
import nyx
+import nyx.arguments
import nyx.controller
import nyx.curses
import nyx.panel
from nyx.curses import RED, GREEN, YELLOW, CYAN, WHITE, NORMAL, BOLD, HIGHLIGHT
+import stem.util.conf
+
NO_STATS_MSG = "Usage stats aren't available yet, press any key..."
HEADERS = ['Consensus:', 'Microdescriptor:', 'Server Descriptor:']
@@ -26,6 +29,10 @@ BLOCK_START, BLOCK_END = '-----BEGIN ', '-----END '
UNRESOLVED_MSG = 'No consensus data available'
ERROR_MSG = 'Unable to retrieve data'
+CONFIG = stem.util.conf.config_dict('nyx', {
+ 'msg.misc.event_types': '',
+})
+
def popup_window(height = -1, width = -1, top = 0, left = 0, below_static = True):
"""
@@ -308,6 +315,36 @@ def show_sort_dialog(title, options, previous_order, option_colors):
return new_order
+def show_event_selector():
+ """
+ Presents a chart of event types we support, with a prompt for the user to
+ select a set.
+
+ :returns: **list** of event types the user has selected or **None** if dialog
+ is canceled
+ """
+
+ def _render(subwindow):
+ subwindow.box()
+ subwindow.addstr(0, 0, 'Event Types:', HIGHLIGHT)
+
+ for i, line in enumerate(CONFIG['msg.misc.event_types'].split('\n')):
+ subwindow.addstr(1, i + 1, line[6:])
+
+ with nyx.curses.CURSES_LOCK:
+ nyx.curses.draw(_render, top = _top(), width = 80, height = 16)
+ user_input = nyx.controller.input_prompt('Events to log: ')
+
+ if user_input:
+ try:
+ user_input = user_input.replace(' ', '') # strip spaces
+ return nyx.arguments.expand_events(user_input)
+ except ValueError as exc:
+ nyx.controller.show_message('Invalid flags: %s' % exc, HIGHLIGHT, max_wait = 2)
+
+ return None
+
+
def show_descriptor(fingerprint, color, is_close_key):
"""
Provides a dialog showing descriptors for a relay.
diff --git a/test/popups.py b/test/popups.py
index 2a308f0..e34398b 100644
--- a/test/popups.py
+++ b/test/popups.py
@@ -90,6 +90,25 @@ Config Option Ordering:--------------------------------------------------------+
+------------------------------------------------------------------------------+
""".strip()
+EXPECTED_EVENT_SELECTOR = """
+Event Types:-------------------------------------------------------------------+
+| d DEBUG a ADDRMAP r CLIENTS_SEEN C SIGNAL |
+| i INFO f AUTHDIR_NEWDESCS u DESCCHANGED F STREAM_BW |
+| n NOTICE j BUILDTIMEOUT_SET g GUARD G STATUS_CLIENT |
+| w WARN b BW h HS_DESC H STATUS_GENERAL |
+| e ERR k CELL_STATS v HS_DESC_CONTENT I STATUS_SERVER |
+| c CIRC x NETWORK_LIVENESS s STREAM |
+| l CIRC_BW y NEWCONSENSUS J TB_EMPTY |
+| m CIRC_MINOR z NEWDESC t TRANSPORT_LAUNCHED |
+| p CONF_CHANGED B NS |
+| q CONN_BW o ORCONN |
+| |
+| DINWE tor runlevel+ A All Events |
+| 12345 nyx runlevel+ X No Events |
+| U Unknown Events |
++------------------------------------------------------------------------------+
+""".strip()
+
EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
Consensus Descriptor:----------+
| No consensus data available |
@@ -253,6 +272,20 @@ class TestPopups(unittest.TestCase):
self.assertEqual(['Name', 'Summary', 'Description'], rendered.return_value)
@patch('nyx.popups._top', Mock(return_value = 0))
+ @patch('nyx.controller.input_prompt', Mock(return_value = None))
+ def test_event_selector_when_canceled(self):
+ rendered = test.render(nyx.popups.show_event_selector)
+ self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
+ self.assertEqual(None, rendered.return_value)
+
+ @patch('nyx.popups._top', Mock(return_value = 0))
+ @patch('nyx.controller.input_prompt', Mock(return_value = '2bwe'))
+ def test_event_selector_with_input(self):
+ rendered = test.render(nyx.popups.show_event_selector)
+ self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
+ self.assertEqual(set(['NYX_INFO', 'ERR', 'WARN', 'BW', 'NYX_ERR', 'NYX_WARN', 'NYX_NOTICE']), rendered.return_value)
+
+ @patch('nyx.popups._top', Mock(return_value = 0))
def test_descriptor_without_fingerprint(self):
rendered = test.render(nyx.popups.show_descriptor, None, nyx.curses.Color.RED, lambda key: key.match('esc'))
self.assertEqual(EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT, rendered.content)
1
0

[translation/liveusb-creator] Update translations for liveusb-creator
by translation@torproject.org 04 Apr '16
by translation@torproject.org 04 Apr '16
04 Apr '16
commit 942f84cb39d72231dfb910a802f4c8266c904ccd
Author: Translation commit bot <translation(a)torproject.org>
Date: Mon Apr 4 22:15:19 2016 +0000
Update translations for liveusb-creator
---
ca/ca.po | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/ca/ca.po b/ca/ca.po
index 4a4ace2..8b782a3 100644
--- a/ca/ca.po
+++ b/ca/ca.po
@@ -8,7 +8,7 @@
# dartmalak <alvar.sevilla(a)gmail.com>, 2014
# David Anglada <codiobert(a)codiobert.es>, 2014
# F Xavier Castane <electromigracion(a)gmail.com>, 2013
-# Guillem Arias Fauste <oficialredstonehelper(a)gmail.com>, 2015
+# Guillem Arias Fauste <oficialredstonehelper(a)gmail.com>, 2015-2016
# josep constanti <iceberg.jcm(a)gmail.com>, 2015
# laia_, 2015
# Pau Sellés i Garcia <pau.selles(a)josoc.cat>, 2013
@@ -18,8 +18,8 @@ msgstr ""
"Project-Id-Version: The Tor Project\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-11-02 21:23+0100\n"
-"PO-Revision-Date: 2016-03-21 16:31+0000\n"
-"Last-Translator: laia_\n"
+"PO-Revision-Date: 2016-04-04 22:03+0000\n"
+"Last-Translator: Guillem Arias Fauste <oficialredstonehelper(a)gmail.com>\n"
"Language-Team: Catalan (http://www.transifex.com/otf/torproject/language/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -216,7 +216,7 @@ msgstr "Si no seleccioneu una imatge ISO autoarrencable existent, es baixarà la
msgid ""
"Install\n"
"by cloning"
-msgstr ""
+msgstr "Instal·lar\nclonant"
#: ../liveusb/dialog.py:172
msgid "Install Tails"
@@ -538,13 +538,13 @@ msgstr "Actualitzant les propietats de la partició de sistema %(system_partitio
msgid ""
"Upgrade\n"
"by cloning"
-msgstr ""
+msgstr "Actualitzar \nclonant"
#: ../liveusb/launcher_ui.py:158
msgid ""
"Upgrade\n"
"from ISO"
-msgstr ""
+msgstr "Millorar\ndes de una ISO"
#: ../liveusb/dialog.py:159
msgid "Use existing Live system ISO"
1
0

04 Apr '16
commit 87de7243b91c9a9d452ad02f202b53402d7a3a31
Author: Georg Koppen <gk(a)torproject.org>
Date: Mon Apr 4 14:10:26 2016 +0000
Bug 15538: Document authenticode signing
Thanks to a cypherpunk who suggested looking for missing intermediate
certificates. And thanks to a second cypherpunk (who might be the same
as the first one) for preparing a patch.
---
processes/AuthenticodeSigning | 110 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/processes/AuthenticodeSigning b/processes/AuthenticodeSigning
new file mode 100644
index 0000000..453fb55
--- /dev/null
+++ b/processes/AuthenticodeSigning
@@ -0,0 +1,110 @@
+Signing Tor Browser bundles for Windows on a Linux machine
+==========================================================
+
+These instructions are for our current token donated to us by Digicert. It is an
+Aladdin eToken pro 72k.
+
+Software needed:
+----------------
+
+1) osslsigncode
+
+ - any stable version > 1.7.1 will do; using the Git mirror at
+ http://git.code.sf.net/p/osslsigncode/osslsigncode/ commit
+ e72a1937d1a13e87074e4584f012f13e03fc1d64 is working fine
+
+2) SafeNetAuthenticationClient (eToken middleware)
+
+ - version 9.0.43 (for Ubuntu 12.04+ and Debian Wheezy+); the SHA-256 sum of
+ SafeNetAuthenticationClient-SAC_9_0_43_Linux.zip is
+ 50545F3FF8BC561634363E683500DCF817CB9A8B379177886F043D14C774A36E or the one
+ of the ISO file included in the archive:
+ 31577e4590ea8c5dc0787fe1501d052dd7b4c0d7a4c4cfa25f6885f50415ee6c
+ - the core part is enough to get the signing going
+
+Installation
+------------
+
+1) Requirements
+
+- for the middleware: sudo apt-get install libccid pcscd openssl libpcsclite1
+- for the cert extraction/signing: sudo apt-get install opensc \
+ libengine-pkcs11-openssl
+
+2) SafeNetAuthenticationClient
+
+- sudo dpkg -i SafenetAuthenticationClient-core-9.0.43-0_amd64.deb
+
+Signing and timestamping
+------------------------
+
+1) Getting the certificate
+
+- pkcs11-tool --module /usr/lib/libeTPkcs11.so --type cert --read-object \
+ --id c27eac1b263465cdd73630d94b0b92e674f01501 > tpo_cert.der
+- convert it to PEM: openssl x509 -in tpo_cert.der -inform der -outform pem \
+ -out tpo_cert.crt
+
+2) Getting the intermediate certificate
+
+- torsocks wget https://www.digicert.com/CACerts/DigiCertEVCodeSigningCA-SHA2.crt
+- convert it to PEM: openssl x509 -in DigiCertEVCodeSigningCA-SHA2.crt \
+ -inform der -outform pem -out middle_cert.crt
+- append it to tpo_cert.crt: cat middle_cert.crt >> tpo_cert.crt
+
+3) Signing the exectuable(s):
+
+- path/to/osslsigncode -pkcs11engine /usr/lib/engines/engine_pkcs11.so \
+ -pkcs11module /usr/lib/libeTPkcs11.so \
+ -certs tpo_cert.crt \
+ -key c27eac1b263465cdd73630d94b0b92e674f01501 \
+ torbrowser-install-XXX.exe tb-XXX-signed.exe
+
+- pass the passphrase to osslsigncode in case you want to script the whole
+process by using `-pass $pass` as an additional commandline parameter
+
+4) Timestamping the executable(s):
+
+- path/to/osslsigncode add -t http://timestamp.digicert.com \
+ -p socks://127.0.0.1:9050 \
+ torbrowser-install-XXX.exe tb-XXX-timestamped.exe
+
+Note: the current tip of osslsigncode's master branch does not allow the
+decoupling of signing and timestamping. In order to do so one needs to apply
+the following patch:
+
+From 28b384e77fa0d4dd38751a0c72ab5976d2e38f75 Mon Sep 17 00:00:00 2001
+From: Georg Koppen <gk(a)torproject.org>
+Date: Fri, 5 Feb 2016 09:23:10 +0000
+Subject: [PATCH] Allow timestamping with the 'add' command
+
+
+diff --git a/osslsigncode.c b/osslsigncode.c
+index 32e37c8..2978c02 100644
+--- a/osslsigncode.c
++++ b/osslsigncode.c
+@@ -2556,16 +2556,16 @@ int main(int argc, char **argv)
+ if (--argc < 1) usage(argv0);
+ url = *(++argv);
+ #ifdef ENABLE_CURL
+- } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-t")) {
++ } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-t")) {
+ if (--argc < 1) usage(argv0);
+ turl[nturl++] = *(++argv);
+- } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-ts")) {
++ } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-ts")) {
+ if (--argc < 1) usage(argv0);
+ tsurl[ntsurl++] = *(++argv);
+- } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-p")) {
++ } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-p")) {
+ if (--argc < 1) usage(argv0);
+ proxy = *(++argv);
+- } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-noverifypeer")) {
++ } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-noverifypeer")) {
+ noverifypeer = 1;
+ #endif
+ } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-addUnauthenticatedBlob")) {
+--
+2.7.0
+
+
1
0