@@ -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 ;
0 commit comments