Skip to content

Commit 9ca9279

Browse files
committed
initial
0 parents  commit 9ca9279

25 files changed

+18657
-0
lines changed

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Speed Meter :signal_strength:
2+
3+
Lightweight Windows taskbar network monitor (upload/download speeds)
4+
5+
## Requirements
6+
- Python 3.7+
7+
- Windows 10/11
8+
- `requirements.txt`:
9+
```
10+
psutil==5.9.5
11+
pywin32==306
12+
pyinstaller==6.2.0 # (optional for building)
13+
```
14+
15+
## Installation
16+
17+
### Option 1: Run from Source
18+
1. Clone repo:
19+
```bash
20+
git clone https://github.com/Benojir/Networx-Alternative-Python.git
21+
cd Networx-Alternative-Python
22+
```
23+
2. Install dependencies:
24+
```bash
25+
pip install -r requirements.txt
26+
```
27+
3. Run:
28+
```bash
29+
python SpeedMeterApp.py
30+
```
31+
32+
### Option 2: Build Executable
33+
1. Install PyInstaller:
34+
```bash
35+
pip install pyinstaller
36+
```
37+
2. Build (include icon):
38+
```bash
39+
pyinstaller --onefile --windowed --noconsole --clean --upx-exclude=vcruntime140.dll --add-data "speedmeter.ico;." SpeedMeterApp.py
40+
```
41+
3. Find executable in `dist/` folder
42+
43+
### Option 3: Download Speed Meter Setup file
44+
1. Download the installer setup file from release page.
45+
2. Now double click and install the application.
46+
47+
## Uninstall
48+
1. Run `uninstall.bat` (for installed versions)
49+
2. Or manually delete:
50+
- `%LOCALAPPDATA%\SpeedMeter` (config data)
51+
- Shortcuts (if created)
52+
53+
> **Note**: The released EXE may consider as false malware by antiviruses and the EXE was generated by Pyinstaller.
54+
```
55+
56+
Key improvements:
57+
1. Explicit `requirements.txt` specification
58+
2. Clear build instructions with icon inclusion
59+
3. Notes on file locations and permissions
60+
4. Separation of source vs. executable methods
61+
5. Uninstall clarification
62+
63+
Want me to add any of these?
64+
- Screenshot positioning
65+
- Config file details
66+
- Troubleshooting common issues

SpeedMeterApp.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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()

SpeedMeterApp.spec

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
3+
4+
a = Analysis(
5+
['SpeedMeterApp.py'],
6+
pathex=[],
7+
binaries=[],
8+
datas=[('speedmeter.ico', '.')],
9+
hiddenimports=[],
10+
hookspath=[],
11+
hooksconfig={},
12+
runtime_hooks=[],
13+
excludes=[],
14+
noarchive=False,
15+
optimize=0,
16+
)
17+
pyz = PYZ(a.pure)
18+
19+
exe = EXE(
20+
pyz,
21+
a.scripts,
22+
a.binaries,
23+
a.datas,
24+
[],
25+
name='SpeedMeterApp',
26+
debug=False,
27+
bootloader_ignore_signals=False,
28+
strip=False,
29+
upx=True,
30+
upx_exclude=['vcruntime140.dll'],
31+
runtime_tmpdir=None,
32+
console=False,
33+
disable_windowed_traceback=False,
34+
argv_emulation=False,
35+
target_arch=None,
36+
codesign_identity=None,
37+
entitlements_file=None,
38+
)

0 commit comments

Comments
 (0)