1+ import psutil
2+ import time
3+ import tkinter as tk
4+ import threading
5+ import win32gui
6+ import win32con
7+ import win32api
8+
9+ class SpeedMeterApp :
10+ def __init__ (self ):
11+ self .last_upload = 0
12+ self .last_download = 0
13+ self .last_time = 0
14+ self .running = True
15+ self .show_upload = True
16+ self .show_download = True
17+ self .update_interval = 1 # seconds
18+ # New variable to track position
19+ self .last_valid_position = None
20+
21+ # Create main window
22+ self .root = tk .Tk ()
23+ self .root .title ("Network Speed Meter" )
24+
25+ # Window styling to match taskbar
26+ self .root .overrideredirect (True )
27+ self .root .attributes ('-topmost' , True )
28+ self .root .attributes ('-alpha' , 0.95 )
29+ self .root .attributes ('-transparentcolor' , '#2D2D2D' ) # Match your bg color
30+ self .root .configure (bg = '#2D2D2D' ) # Dark gray similar to taskbar
31+
32+ # Create speed display label
33+ self .speed_label = tk .Label (
34+ self .root ,
35+ text = "↓ 0.0 KB/s\n ↑ 0.0 KB/s" ,
36+ fg = 'white' ,
37+ bg = self .root .cget ('bg' ),
38+ font = ('Segoe UI' , 9 ),
39+ padx = 0 ,
40+ pady = 0
41+ )
42+ self .speed_label .pack ()
43+
44+ # Right-click menu
45+ self .create_context_menu ()
46+
47+ # Initial positioning
48+ self .position_window ()
49+
50+ # Start monitoring
51+ self .start_monitoring ()
52+
53+ # Start periodic position refresh
54+ self .root .after (1000 , self .refresh_position )
55+
56+ # Main loop
57+ self .root .mainloop ()
58+
59+ def create_context_menu (self ):
60+ """Create right-click context menu"""
61+ self .menu = tk .Menu (self .root , tearoff = 0 , bg = '#2D2D2D' , fg = 'white' )
62+ self .menu .add_command (label = "Exit" , command = self .exit_app )
63+ self .root .bind ("<Button-3>" , self .show_menu )
64+
65+ def show_menu (self , event ):
66+ """Show context menu on right-click"""
67+ try :
68+ self .menu .tk_popup (event .x_root , event .y_root )
69+ finally :
70+ self .menu .grab_release ()
71+
72+ def find_taskbar_apps (self ):
73+ """Find existing taskbar apps and return their leftmost position"""
74+ try :
75+ # Get taskbar handle
76+ h_taskbar = win32gui .FindWindow ("Shell_TrayWnd" , None )
77+ if not h_taskbar :
78+ return None
79+
80+ # Find the tray toolbar containing apps
81+ h_tray_toolbar = None
82+ h_child = win32gui .GetWindow (h_taskbar , win32con .GW_CHILD )
83+ while h_child :
84+ class_name = win32gui .GetClassName (h_child )
85+ if class_name == "TrayToolbarWindow32" :
86+ h_tray_toolbar = h_child
87+ break
88+ h_child = win32gui .GetWindow (h_child , win32con .GW_HWNDNEXT )
89+
90+ if not h_tray_toolbar :
91+ return None
92+
93+ # Get position of the first taskbar app
94+ rect = win32gui .GetWindowRect (h_tray_toolbar )
95+ return rect [0 ] # Left edge of apps area
96+
97+ except Exception as e :
98+ print (f"Error finding taskbar apps: { e } " )
99+ return None
100+
101+ def position_window (self ):
102+ """Position window to the left of other taskbar widgets"""
103+ try :
104+ self .root .update_idletasks ()
105+ window_width = self .root .winfo_width ()
106+ window_height = self .root .winfo_height ()
107+
108+ # Get taskbar info
109+ h_taskbar = win32gui .FindWindow ("Shell_TrayWnd" , None )
110+ if not h_taskbar :
111+ self .position_fallback ()
112+ return
113+
114+ taskbar_rect = win32gui .GetWindowRect (h_taskbar )
115+ taskbar_height = taskbar_rect [3 ] - taskbar_rect [1 ]
116+
117+ # Find where widgets start
118+ widgets_start = self .find_taskbar_widgets_area ()
119+
120+ if widgets_start :
121+ # Position to the left of widgets with 0px gap
122+ margin = 0
123+ x = widgets_start - window_width - margin
124+ self .last_valid_position = (x , taskbar_rect [1 ] + (taskbar_height - window_height ) // 2 )
125+ else :
126+ # Fallback to right of system tray
127+ h_systray = win32gui .FindWindowEx (h_taskbar , 0 , "TrayNotifyWnd" , None )
128+ if h_systray :
129+ systray_rect = win32gui .GetWindowRect (h_systray )
130+ x = systray_rect [0 ] - window_width - margin
131+ self .last_valid_position = (x , taskbar_rect [1 ] + (taskbar_height - window_height ) // 2 )
132+ else :
133+ self .position_fallback ()
134+ return
135+
136+ # Adjust for different taskbar positions
137+ screen_width = win32api .GetSystemMetrics (0 )
138+ if taskbar_rect [1 ] > 0 : # Taskbar at top
139+ self .last_valid_position = (self .last_valid_position [0 ], taskbar_rect [3 ] - window_height - 2 )
140+ elif taskbar_rect [0 ] > 0 and taskbar_rect [2 ] < screen_width // 2 : # Left
141+ self .last_valid_position = (taskbar_rect [2 ] + 2 , taskbar_rect [3 ] - window_height - 2 )
142+ elif taskbar_rect [2 ] == screen_width : # Right
143+ self .last_valid_position = (taskbar_rect [0 ] - window_width - 2 , taskbar_rect [3 ] - window_height - 2 )
144+
145+ self .root .geometry (f"+{ self .last_valid_position [0 ]} +{ self .last_valid_position [1 ]} " )
146+
147+ # Ensure proper z-order
148+ self .root .after (100 , lambda : [
149+ self .root .lift (),
150+ self .root .attributes ('-topmost' , True )
151+ ])
152+
153+ except Exception as e :
154+ print (f"Positioning error: { e } " )
155+ self .position_fallback ()
156+
157+ def position_fallback (self ):
158+ """Fallback positioning using last known good position or default"""
159+ self .root .update_idletasks ()
160+ if self .last_valid_position :
161+ self .root .geometry (f"+{ self .last_valid_position [0 ]} +{ self .last_valid_position [1 ]} " )
162+ else :
163+ screen_width = self .root .winfo_screenwidth ()
164+ screen_height = self .root .winfo_screenheight ()
165+ window_width = self .root .winfo_width ()
166+ window_height = self .root .winfo_height ()
167+ x = screen_width - window_width - 100
168+ y = screen_height - window_height - 40
169+ self .root .geometry (f"+{ x } +{ y } " )
170+
171+ def refresh_position (self ):
172+ """Periodically refresh window position"""
173+ if self .running :
174+ self .position_window ()
175+ self .root .after (5000 , self .refresh_position )
176+
177+ def start_monitoring (self ):
178+ """Start network monitoring thread"""
179+ self .monitor_thread = threading .Thread (target = self .monitor_network )
180+ self .monitor_thread .daemon = True
181+ self .monitor_thread .start ()
182+
183+ def monitor_network (self ):
184+ """Monitor network speeds"""
185+ while self .running :
186+ net_io = psutil .net_io_counters ()
187+ current_time = time .time ()
188+
189+ if self .last_time > 0 :
190+ time_elapsed = current_time - self .last_time
191+ if time_elapsed > 0 :
192+ upload_speed = (net_io .bytes_sent - self .last_upload ) / time_elapsed
193+ download_speed = (net_io .bytes_recv - self .last_download ) / time_elapsed
194+ self .update_display (download_speed , upload_speed )
195+
196+ self .last_upload = net_io .bytes_sent
197+ self .last_download = net_io .bytes_recv
198+ self .last_time = current_time
199+ time .sleep (self .update_interval )
200+
201+ def update_display (self , download_speed , upload_speed ):
202+ """Update the speed display"""
203+ def format_speed (speed ):
204+ if speed < 1024 :
205+ return "0.0 B/s"
206+ elif speed < 1024 * 1024 :
207+ return f"{ speed / 1024 :.1f} KB/s"
208+ else :
209+ return f"{ speed / (1024 * 1024 ):.1f} MB/s"
210+
211+ download_text = f"↓ { format_speed (download_speed )} " if self .show_download else ""
212+ upload_text = f"↑ { format_speed (upload_speed )} " if self .show_upload else ""
213+ separator = "\n " if self .show_download and self .show_upload else ""
214+ text = f"{ download_text } { separator } { upload_text } "
215+
216+ self .speed_label .config (text = text )
217+ self .position_window ()
218+
219+ def exit_app (self ):
220+ """Clean exit"""
221+ self .running = False
222+ self .root .quit ()
223+ self .root .destroy ()
224+
225+ def find_taskbar_widgets_area (self ):
226+ """Find the area where taskbar widgets are located"""
227+ try :
228+ h_taskbar = win32gui .FindWindow ("Shell_TrayWnd" , None )
229+ if not h_taskbar :
230+ return None
231+
232+ # Find the Widgets window (Windows 11)
233+ h_widgets = win32gui .FindWindow ("Windows.UI.Core.CoreWindow" , "Widgets" )
234+ if h_widgets :
235+ widgets_rect = win32gui .GetWindowRect (h_widgets )
236+ return widgets_rect [0 ] # Return left edge of widgets area
237+
238+ # For Windows 10 or when Widgets window isn't found
239+ h_rebar = win32gui .FindWindowEx (h_taskbar , 0 , "ReBarWindow32" , None )
240+ if not h_rebar :
241+ return None
242+
243+ # Find the MSTaskSwWClass (task items)
244+ h_tasklist = win32gui .FindWindowEx (h_rebar , 0 , "MSTaskSwWClass" , None )
245+ if not h_tasklist :
246+ return None
247+
248+ # Find the right edge of the task items
249+ tasklist_rect = win32gui .GetWindowRect (h_tasklist )
250+ return tasklist_rect [2 ] # Right edge of task items
251+
252+ except Exception as e :
253+ print (f"Error finding widgets area: { e } " )
254+ return None
255+
256+ if __name__ == "__main__" :
257+ app = SpeedMeterApp ()
0 commit comments