Skip to content

Commit bb7f7e5

Browse files
author
Delta-Kronecker
committed
feat: live stats card on main page - exit location, speed, totals
1 parent 5be465c commit bb7f7e5

2 files changed

Lines changed: 202 additions & 18 deletions

File tree

scripts/TorJetUi.cs

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ private enum Page { Main, Settings }
313313

314314
private bool autoProxyEnabled;
315315
private int settingsScrollY;
316+
private DateTime lastTrafficUpdate = DateTime.MinValue;
316317

317318
private int editRow = -1;
318319
private string editBuf = "";
@@ -323,7 +324,7 @@ private enum Page { Main, Settings }
323324
private System.Windows.Forms.ContextMenuStrip trayMenu;
324325

325326
private Rectangle rcClose, rcMin,
326-
rcProxy, rcSettings, rcPower, rcBack, rcUpdateBtn;
327+
rcProxy, rcSettings, rcPower, rcBack, rcUpdateBtn, rcStats;
327328
private readonly Rectangle[] rcRowVal = new Rectangle[11];
328329
private readonly Rectangle[] rcRowPrev = new Rectangle[11];
329330
private readonly Rectangle[] rcRowNext = new Rectangle[11];
@@ -342,7 +343,7 @@ public MainForm()
342343
Text = "TorJet";
343344
FormBorderStyle = FormBorderStyle.None;
344345
StartPosition = FormStartPosition.CenterScreen;
345-
ClientSize = new Size(380, 350);
346+
ClientSize = new Size(380, 410);
346347
BackColor = Theme.Bg;
347348
ForeColor = Theme.Text;
348349
Font = Theme.Body();
@@ -418,17 +419,18 @@ private void LayoutPass()
418419
int cx = w / 2;
419420
rcPower = new Rectangle(cx - 52, 88, 104, 104);
420421

421-
int yBelowRing = 204;
422+
rcStats = new Rectangle(24, 228, w - 48, 50);
423+
int yBelowRing = 284;
422424
if (autoProxyEnabled)
423425
rcProxy = new Rectangle(0, 0, 0, 0);
424426
else
425427
{
426-
rcProxy = new Rectangle(24, yBelowRing, w - 48, 42);
427-
yBelowRing += 50;
428+
rcProxy = new Rectangle(24, yBelowRing, w - 48, 36);
429+
yBelowRing += 42;
428430
}
429-
rcSettings = new Rectangle(24, yBelowRing, w - 48, 38);
430-
yBelowRing += 44;
431-
rcUpdateBtn = new Rectangle(24, yBelowRing, w - 48, 38);
431+
rcSettings = new Rectangle(24, yBelowRing, w - 48, 36);
432+
yBelowRing += 40;
433+
rcUpdateBtn = new Rectangle(24, yBelowRing, w - 48, 36);
432434

433435
int ry = 78 - settingsScrollY;
434436
int rowCount = SettingLabels.Length;
@@ -656,6 +658,8 @@ private void PaintMain(Graphics g)
656658
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
657659
TextFormatFlags.EndEllipsis);
658660

661+
PaintStatsCard(g);
662+
659663
if (!autoProxyEnabled)
660664
PaintTogglePill(g, rcProxy, "PROXY", ProxyIsOurs(), hoverId == 20, false);
661665

@@ -697,6 +701,62 @@ private void PaintMain(Graphics g)
697701

698702
private int cx() { return rcPower.Left + rcPower.Width / 2; }
699703

704+
private void PaintStatsCard(Graphics g)
705+
{
706+
int x = rcStats.X, y = rcStats.Y, w = rcStats.Width, h = rcStats.Height;
707+
using (GraphicsPath bg = Theme.RoundRect(rcStats, 10))
708+
{
709+
Theme.FillGradientPath(g, bg, Theme.SurfaceAlt, Theme.Bg);
710+
using (Pen bp = new Pen(Theme.Border, 1f))
711+
g.DrawPath(bp, bg);
712+
}
713+
714+
int pad = 10, rowH = 14;
715+
Font sm = Theme.Small(), bd = Theme.Body();
716+
717+
// row 1 — exit location
718+
string ex = uiExitLocation;
719+
if (string.IsNullOrEmpty(ex)) ex = state == RunState.Connected ? "detecting\u2026" : "\u2014";
720+
Color exCol = string.IsNullOrEmpty(uiExitLocation) ? Theme.Muted : Theme.Accent;
721+
g.SmoothingMode = SmoothingMode.AntiAlias;
722+
using (SolidBrush dot = new SolidBrush(exCol))
723+
g.FillEllipse(dot, x + pad, y + 8, 5, 5);
724+
TextRenderer.DrawText(g, ex, bd,
725+
new Rectangle(x + pad + 10, y + 6, w - pad * 2 - 10, rowH),
726+
exCol, TextFormatFlags.Left | TextFormatFlags.VerticalCenter |
727+
TextFormatFlags.EndEllipsis);
728+
729+
// row 2 — speeds
730+
int r2 = y + 22;
731+
string dSpd = FormatSpeed(uiSpeedDown);
732+
string uSpd = FormatSpeed(uiSpeedUp);
733+
TextRenderer.DrawText(g, "\u2193 " + dSpd, bd,
734+
new Rectangle(x + pad, r2, w / 2 - pad, rowH),
735+
Theme.Green, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
736+
TextRenderer.DrawText(g, "\u2191 " + uSpd, bd,
737+
new Rectangle(x + w / 2, r2, w / 2 - pad, rowH),
738+
Theme.Accent, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
739+
740+
// row 3 — totals
741+
int r3 = y + 38;
742+
TextRenderer.DrawText(g, "\u2193 " + FormatBytes(uiTotalDown), sm,
743+
new Rectangle(x + pad, r3, w / 2 - pad, rowH),
744+
Color.FromArgb(170, Theme.Green.R, Theme.Green.G, Theme.Green.B),
745+
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
746+
TextRenderer.DrawText(g, "\u2191 " + FormatBytes(uiTotalUp), sm,
747+
new Rectangle(x + w / 2, r3, w / 2 - pad, rowH),
748+
Color.FromArgb(170, Theme.Accent.R, Theme.Accent.G, Theme.Accent.B),
749+
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
750+
}
751+
752+
private static string FormatSpeed(long bps)
753+
{
754+
if (bps < 1024) return bps + " B/s";
755+
if (bps < 1048576) return (bps / 1024.0).ToString("0.#") + " KB/s";
756+
if (bps < 1073741824) return (bps / 1048576.0).ToString("0.#") + " MB/s";
757+
return (bps / 1073741824.0).ToString("0.##") + " GB/s";
758+
}
759+
700760
private void PaintTogglePill(Graphics g, Rectangle r, string name, bool on,
701761
bool hovered, bool pending)
702762
{
@@ -1394,14 +1454,15 @@ private void Disconnect(string why)
13941454
{
13951455
if (stoppingBusy) return;
13961456
stoppingBusy = true;
1397-
autoAbort = true; // cancels a running race immediately
1457+
autoAbort = true;
13981458
SetState(RunState.Stopping);
13991459
watchdogStop = true;
14001460
circuitWatchStop = true;
14011461
StopKeepAlive();
14021462
if (autoProxyEnabled && ProxyIsOurs()) SetSystemProxy(false);
14031463
try { if (torProc != null) torProc.Kill(); } catch { }
14041464
torProc = null;
1465+
ResetUiStats();
14051466
LogLine("tor stopped (" + why + ")");
14061467
RunBg(delegate
14071468
{
@@ -1446,6 +1507,18 @@ private void UiTick(object s, EventArgs e)
14461507
RunBg(delegate { CheckForUpdateFromUi(); });
14471508
}
14481509

1510+
if (state == RunState.Connected &&
1511+
DateTime.UtcNow - lastTrafficUpdate > TimeSpan.FromSeconds(2))
1512+
{
1513+
lastTrafficUpdate = DateTime.UtcNow;
1514+
RunBg(delegate
1515+
{
1516+
RefreshTrafficStats();
1517+
RefreshExitLocation();
1518+
UiInvokeDelegate(delegate { Invalidate(); });
1519+
});
1520+
}
1521+
14491522
if (state == RunState.Connected || state == RunState.Restarting)
14501523
{
14511524
bool alive = false;

scripts/start-tor.cs

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,126 @@ private static string ResolveDataDir()
6868
private static readonly string WatchRttPctFile = Path.Combine(DataDir, "circuit-watch-pct.txt");
6969
private static readonly string KeepAliveFile = Path.Combine(DataDir, "keepalive.txt");
7070
private static readonly string AutoProxyFile = Path.Combine(DataDir, "auto-proxy.txt");
71+
private static readonly string TorStateFile = Path.Combine(DataDir, "data", "state");
72+
private static readonly string CachedConsensus = Path.Combine(DataDir, "data", "cached-consensus");
73+
74+
// Live stats exposed to the GUI
75+
internal static long uiSpeedDown;
76+
internal static long uiSpeedUp;
77+
internal static long uiTotalDown;
78+
internal static long uiTotalUp;
79+
internal static string uiExitLocation = "";
80+
private static long _prevReadBytes;
81+
private static long _prevWrittenBytes;
82+
private static DateTime _lastTrafficSample = DateTime.MinValue;
83+
private static DateTime _lastExitLookup = DateTime.MinValue;
84+
85+
internal static void ResetUiStats()
86+
{
87+
uiSpeedDown = 0;
88+
uiSpeedUp = 0;
89+
uiTotalDown = 0;
90+
uiTotalUp = 0;
91+
uiExitLocation = "";
92+
_prevReadBytes = 0;
93+
_prevWrittenBytes = 0;
94+
_lastTrafficSample = DateTime.MinValue;
95+
_lastExitLookup = DateTime.MinValue;
96+
}
97+
98+
internal static void RefreshTrafficStats()
99+
{
100+
try
101+
{
102+
if (!File.Exists(TorStateFile)) return;
103+
long lastRead = 0, lastWritten = 0;
104+
foreach (string raw in File.ReadLines(TorStateFile))
105+
{
106+
if (raw.StartsWith("ReadBytes="))
107+
long.TryParse(raw.Substring(10), out lastRead);
108+
else if (raw.StartsWith("WrittenBytes="))
109+
long.TryParse(raw.Substring(13), out lastWritten);
110+
}
111+
DateTime now = DateTime.UtcNow;
112+
if (_lastTrafficSample != DateTime.MinValue)
113+
{
114+
double sec = (now - _lastTrafficSample).TotalSeconds;
115+
if (sec > 0.5)
116+
{
117+
long ds = (long)((lastRead - _prevReadBytes) / sec);
118+
long us = (long)((lastWritten - _prevWrittenBytes) / sec);
119+
uiSpeedDown = ds > 0 ? ds : 0;
120+
uiSpeedUp = us > 0 ? us : 0;
121+
}
122+
}
123+
uiTotalDown = lastRead;
124+
uiTotalUp = lastWritten;
125+
_prevReadBytes = lastRead;
126+
_prevWrittenBytes = lastWritten;
127+
_lastTrafficSample = now;
128+
}
129+
catch { }
130+
}
131+
132+
internal static void RefreshExitLocation()
133+
{
134+
if (DateTime.UtcNow - _lastExitLookup < TimeSpan.FromSeconds(20)) return;
135+
_lastExitLookup = DateTime.UtcNow;
136+
try
137+
{
138+
var lines = ControlCommand("GETINFO circuit-status");
139+
if (lines == null) return;
140+
foreach (string line in lines)
141+
{
142+
int di = line.LastIndexOf('$');
143+
if (di < 0) continue;
144+
int sp = line.LastIndexOf(' ', di);
145+
string hop = line.Substring(sp + 1).Trim();
146+
int tild = hop.IndexOf('~');
147+
string hexId = tild > 0 ? hop.Substring(1, tild - 1) : hop.TrimStart('$');
148+
if (hexId.Length < 40) continue;
149+
string ip = LookupExitIpFromConsensus(hexId);
150+
if (ip != null) { uiExitLocation = ip; return; }
151+
break;
152+
}
153+
}
154+
catch { }
155+
}
156+
157+
private static string LookupExitIpFromConsensus(string hexId)
158+
{
159+
try
160+
{
161+
if (!File.Exists(CachedConsensus)) return null;
162+
byte[] fpBytes = new byte[hexId.Length / 2];
163+
for (int i = 0; i < fpBytes.Length; i++)
164+
fpBytes[i] = byte.Parse(hexId.Substring(i * 2, 2),
165+
System.Globalization.NumberStyles.HexNumber);
166+
string targetB64 = Convert.ToBase64String(fpBytes).TrimEnd('=');
167+
string curIp = null, curId = null;
168+
foreach (string raw in File.ReadLines(CachedConsensus))
169+
{
170+
if (raw.StartsWith("r "))
171+
{
172+
if (curId == targetB64 && curIp != null) return curIp;
173+
string[] p = raw.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
174+
if (p.Length >= 7) { curId = p[2]; curIp = p[6]; }
175+
}
176+
}
177+
if (curId == targetB64 && curIp != null) return curIp;
178+
}
179+
catch { }
180+
return null;
181+
}
182+
183+
internal static string FormatBytes(long bytes)
184+
{
185+
if (bytes < 1024) return bytes + " B";
186+
if (bytes < 1048576) return (bytes / 1024.0).ToString("0.#") + " KB";
187+
if (bytes < 1073741824) return (bytes / 1048576.0).ToString("0.#") + " MB";
188+
return (bytes / 1073741824.0).ToString("0.##") + " GB";
189+
}
190+
71191
private const int KeepAliveSocksPort = 9052;
72192
private const string KeepAliveUsername = "torjet-keepalive";
73193
private const int MaxSpeedTestStreams = 4;
@@ -1524,15 +1644,6 @@ private static string Token(string line, string key)
15241644
return null;
15251645
}
15261646

1527-
// Formats a byte count as B/KB/MB/GB for the set volume display.
1528-
private static string FormatBytes(long b)
1529-
{
1530-
if (b < 1024) return b + " B";
1531-
if (b < 1024L * 1024) return (b / 1024.0).ToString("0.#") + " KB";
1532-
if (b < 1024L * 1024 * 1024) return (b / (1024.0 * 1024)).ToString("0.##") + " MB";
1533-
return (b / (1024.0 * 1024 * 1024)).ToString("0.##") + " GB";
1534-
}
1535-
15361647
// Advertised bandwidth (bytes/s) of an exit, from GETINFO ns/id/<fp>.
15371648
private static string ExitBandwidth(string fpHex)
15381649
{

0 commit comments

Comments
 (0)