|
3 | 3 | import subprocess |
4 | 4 | import ctypes |
5 | 5 | import json |
6 | | -from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QLineEdit, QPushButton, QSystemTrayIcon, QMenu, QAction, QStyleFactory, QMessageBox, QTextEdit, QVBoxLayout, QHBoxLayout, QFileDialog, QSlider, QFormLayout, QGroupBox, QCheckBox |
| 6 | +from PyQt5.QtWidgets import ( |
| 7 | + QApplication, |
| 8 | + QMainWindow, |
| 9 | + QWidget, |
| 10 | + QLabel, |
| 11 | + QLineEdit, |
| 12 | + QPushButton, |
| 13 | + QSystemTrayIcon, |
| 14 | + QMenu, |
| 15 | + QAction, |
| 16 | + QStyleFactory, |
| 17 | + QMessageBox, |
| 18 | + QTextEdit, |
| 19 | + QVBoxLayout, |
| 20 | + QHBoxLayout, |
| 21 | + QFileDialog, |
| 22 | + QSlider, |
| 23 | + QFormLayout, |
| 24 | + QGroupBox, |
| 25 | + QCheckBox, |
| 26 | +) |
7 | 27 | from PyQt5.QtCore import QTimer, Qt |
8 | 28 | from PyQt5.QtGui import QIcon, QColor, QPalette, QPixmap |
9 | 29 | import ping3 |
|
12 | 32 | import requests |
13 | 33 | from fontawesome import icons |
14 | 34 |
|
| 35 | +if sys.platform == "win32": |
| 36 | + import ctypes |
| 37 | + |
| 38 | + |
15 | 39 | class NetworkCheckApp(QMainWindow): |
16 | 40 | def __init__(self): |
17 | 41 | super().__init__() |
18 | 42 |
|
19 | 43 | self.setWindowTitle("Network Check Application") |
20 | 44 | self.setGeometry(100, 100, 600, 300) |
21 | | - |
| 45 | + |
22 | 46 | self.init_ui() |
23 | | - |
| 47 | + |
24 | 48 | self.tray_icon = QSystemTrayIcon(self) |
25 | 49 | self.tray_icon.setIcon(QIcon("icon.png")) |
26 | 50 | self.tray_icon.setVisible(True) |
27 | | - |
| 51 | + |
28 | 52 | self.tray_menu = QMenu() |
29 | 53 | self.show_action = QAction("Show", self) |
30 | 54 | self.exit_action = QAction("Exit", self) |
31 | 55 | self.tray_menu.addAction(self.show_action) |
32 | 56 | self.tray_menu.addAction(self.exit_action) |
33 | | - |
| 57 | + |
34 | 58 | self.show_action.triggered.connect(self.show_window) |
35 | 59 | self.exit_action.triggered.connect(self.exit_app) |
36 | | - |
| 60 | + |
37 | 61 | self.tray_icon.setContextMenu(self.tray_menu) |
38 | | - |
| 62 | + |
39 | 63 | self.output_text = "" |
40 | | - |
| 64 | + |
41 | 65 | def init_ui(self): |
42 | 66 | self.central_widget = QWidget(self) |
43 | 67 | self.setCentralWidget(self.central_widget) |
44 | | - |
| 68 | + |
45 | 69 | self.create_layout() |
46 | | - |
| 70 | + |
47 | 71 | def create_layout(self): |
48 | 72 | layout = QVBoxLayout() |
49 | | - |
| 73 | + |
50 | 74 | self.output_window = QTextEdit(self.central_widget) |
51 | 75 | self.output_window.setReadOnly(True) |
52 | 76 | layout.addWidget(self.output_window) |
53 | | - |
| 77 | + |
54 | 78 | settings_group = QGroupBox("Settings", self.central_widget) |
55 | 79 | settings_layout = QFormLayout(settings_group) |
56 | | - |
| 80 | + |
57 | 81 | self.host_input = QLineEdit(self.central_widget) |
58 | 82 | self.host_input.setPlaceholderText("Host (IP or Domain)") |
59 | | - |
| 83 | + |
60 | 84 | self.interval_input = QLineEdit(self.central_widget) |
61 | 85 | self.interval_input.setPlaceholderText("Ping Interval (seconds)") |
62 | | - |
| 86 | + |
63 | 87 | self.timeout_slider = QSlider(Qt.Horizontal, self.central_widget) |
64 | 88 | self.timeout_slider.setRange(1, 5000) |
65 | 89 | self.timeout_slider.setValue(1000) |
66 | 90 | self.timeout_slider.valueChanged.connect(self.update_timeout_label) |
67 | 91 | self.timeout_value_label = QLabel("1000", self.central_widget) |
68 | | - |
69 | | - self.continuous_ping_checkbox = QCheckBox("Continuous Ping", self.central_widget) |
70 | | - |
| 92 | + |
| 93 | + self.continuous_ping_checkbox = QCheckBox( |
| 94 | + "Continuous Ping", self.central_widget |
| 95 | + ) |
| 96 | + |
71 | 97 | self.traceroute_button = QPushButton("Traceroute", self.central_widget) |
72 | 98 | self.traceroute_button.clicked.connect(self.start_traceroute) |
73 | | - |
| 99 | + |
74 | 100 | self.geolocation_button = QPushButton("Geolocation Lookup", self.central_widget) |
75 | 101 | self.geolocation_button.clicked.connect(self.start_geolocation_lookup) |
76 | | - |
| 102 | + |
77 | 103 | settings_layout.addRow("Host:", self.host_input) |
78 | 104 | settings_layout.addRow("Interval:", self.interval_input) |
79 | 105 | settings_layout.addRow("Timeout (ms):", self.timeout_slider) |
80 | 106 | settings_layout.addRow("", self.timeout_value_label) |
81 | 107 | settings_layout.addRow("", self.continuous_ping_checkbox) |
82 | 108 | settings_layout.addRow("", self.traceroute_button) |
83 | 109 | settings_layout.addRow("", self.geolocation_button) |
84 | | - |
| 110 | + |
85 | 111 | settings_group.setLayout(settings_layout) |
86 | | - |
| 112 | + |
87 | 113 | control_layout = QHBoxLayout() |
88 | | - |
| 114 | + |
89 | 115 | self.ping_button = QPushButton("Ping Test", self.central_widget) |
90 | | - ping_icon = QPixmap(icons['network-wired']).scaledToHeight(20) |
| 116 | + ping_icon = QPixmap(icons["network-wired"]).scaledToHeight(20) |
91 | 117 | self.ping_button.setIcon(QIcon(ping_icon)) |
92 | 118 | self.ping_button.clicked.connect(self.start_ping) |
93 | 119 |
|
94 | 120 | self.clear_button = QPushButton("Clear Output", self.central_widget) |
95 | 121 | self.clear_button.clicked.connect(self.clear_output) |
96 | | - |
| 122 | + |
97 | 123 | self.save_button = QPushButton("Save Log", self.central_widget) |
98 | 124 | self.save_button.clicked.connect(self.save_log) |
99 | | - |
| 125 | + |
100 | 126 | self.minimize_button = QPushButton("Minimize to Tray", self.central_widget) |
101 | 127 | self.minimize_button.clicked.connect(self.minimize_to_tray) |
102 | | - |
| 128 | + |
103 | 129 | control_layout.addWidget(self.ping_button) |
104 | 130 | control_layout.addWidget(self.clear_button) |
105 | 131 | control_layout.addWidget(self.save_button) |
106 | 132 | control_layout.addWidget(self.minimize_button) |
107 | | - |
| 133 | + |
108 | 134 | layout.addWidget(settings_group) |
109 | 135 | layout.addLayout(control_layout) |
110 | 136 | self.central_widget.setLayout(layout) |
111 | | - |
| 137 | + |
112 | 138 | def update_timeout_label(self): |
113 | 139 | self.timeout_value_label.setText(str(self.timeout_slider.value())) |
114 | | - |
| 140 | + |
115 | 141 | def start_ping(self): |
116 | | - if hasattr(self, 'timer') and self.timer.isActive(): |
| 142 | + if hasattr(self, "timer") and self.timer.isActive(): |
117 | 143 | self.stop_ping() |
118 | 144 | else: |
119 | 145 | host = self.host_input.text() |
120 | 146 | interval = self.interval_input.text() |
121 | 147 | timeout = self.timeout_slider.value() / 1000.0 |
122 | 148 |
|
123 | 149 | if not host or not interval.isdigit(): |
124 | | - QMessageBox.critical(self, "Error", "Invalid input. Please enter a valid host and interval.") |
| 150 | + QMessageBox.critical( |
| 151 | + self, |
| 152 | + "Error", |
| 153 | + "Invalid input. Please enter a valid host and interval.", |
| 154 | + ) |
125 | 155 | return |
126 | 156 |
|
127 | 157 | self.timer = QTimer(self) |
@@ -130,86 +160,99 @@ def start_ping(self): |
130 | 160 | self.ping_button.setText("Stop Ping") |
131 | 161 | self.continuous_ping_checkbox.setEnabled(False) |
132 | 162 | self.timeout_slider.setEnabled(False) |
133 | | - |
| 163 | + |
134 | 164 | def stop_ping(self): |
135 | | - if hasattr(self, 'timer') and self.timer.isActive(): |
| 165 | + if hasattr(self, "timer") and self.timer.isActive(): |
136 | 166 | self.timer.stop() |
137 | 167 | self.ping_button.setText("Ping Test") |
138 | 168 | self.continuous_ping_checkbox.setEnabled(True) |
139 | 169 | self.timeout_slider.setEnabled(True) |
140 | | - |
| 170 | + |
141 | 171 | def ping(self): |
142 | 172 | host = self.host_input.text() |
143 | 173 | timeout = self.timeout_slider.value() / 1000.0 |
144 | | - |
| 174 | + |
145 | 175 | response_time = ping3.ping(host, timeout=timeout, unit="ms") |
146 | | - |
| 176 | + |
147 | 177 | if response_time is not None: |
148 | | - self.append_to_output(f"Ping response from {host}: {response_time:.2f} ms", "green") |
| 178 | + self.append_to_output( |
| 179 | + f"Ping response from {host}: {response_time:.2f} ms", "green" |
| 180 | + ) |
149 | 181 | else: |
150 | 182 | self.append_to_output(f"Request timed out for {host}", "red") |
151 | | - |
| 183 | + |
152 | 184 | def start_traceroute(self): |
153 | 185 | host = self.host_input.text() |
154 | | - |
| 186 | + |
155 | 187 | if not host: |
156 | | - QMessageBox.critical(self, "Error", "Invalid input. Please enter a valid host.") |
| 188 | + QMessageBox.critical( |
| 189 | + self, "Error", "Invalid input. Please enter a valid host." |
| 190 | + ) |
157 | 191 | return |
158 | | - |
| 192 | + |
159 | 193 | try: |
160 | 194 | command = ["tracert", host] |
161 | 195 | result = subprocess.run(command, stdout=subprocess.PIPE, text=True) |
162 | 196 | self.append_to_output(result.stdout, "white") |
163 | 197 | except subprocess.CalledProcessError: |
164 | 198 | self.append_to_output("Traceroute failed.", "red") |
165 | | - |
| 199 | + |
166 | 200 | def start_geolocation_lookup(self): |
167 | 201 | host = self.host_input.text() |
168 | | - |
| 202 | + |
169 | 203 | if not host: |
170 | | - QMessageBox.critical(self, "Error", "Invalid input. Please enter a valid host.") |
| 204 | + QMessageBox.critical( |
| 205 | + self, "Error", "Invalid input. Please enter a valid host." |
| 206 | + ) |
171 | 207 | return |
172 | | - |
| 208 | + |
173 | 209 | try: |
174 | 210 | ip = socket.gethostbyname(host) |
175 | 211 | ipwhois = IPWhois(ip) |
176 | 212 | results = ipwhois.lookup_rdap() |
177 | | - country = results.get('asn_country_code', 'Unknown') |
178 | | - self.append_to_output(f"Geolocation for {host}: Country - {country}", "green") |
| 213 | + country = results.get("asn_country_code", "Unknown") |
| 214 | + self.append_to_output( |
| 215 | + f"Geolocation for {host}: Country - {country}", "green" |
| 216 | + ) |
179 | 217 | except: |
180 | 218 | self.append_to_output("Geolocation lookup failed.", "red") |
181 | | - |
| 219 | + |
182 | 220 | def append_to_output(self, text, color): |
183 | 221 | self.output_text += f'<span style="color: {color}">{text}</span><br>' |
184 | 222 | self.output_window.setHtml(self.output_text) |
185 | | - self.output_window.verticalScrollBar().setValue(self.output_window.verticalScrollBar().maximum()) |
186 | | - |
| 223 | + self.output_window.verticalScrollBar().setValue( |
| 224 | + self.output_window.verticalScrollBar().maximum() |
| 225 | + ) |
| 226 | + |
187 | 227 | def show_window(self): |
188 | 228 | self.show() |
189 | | - |
| 229 | + |
190 | 230 | def exit_app(self): |
191 | 231 | self.tray_icon.hide() |
192 | 232 | self.close() |
193 | | - |
| 233 | + |
194 | 234 | def clear_output(self): |
195 | 235 | self.output_text = "" |
196 | 236 | self.output_window.clear() |
197 | | - |
| 237 | + |
198 | 238 | def save_log(self): |
199 | 239 | options = QFileDialog.Options() |
200 | 240 | options |= QFileDialog.ReadOnly |
201 | | - file_name, _ = QFileDialog.getSaveFileName(self, "Save Log", "", "Text Files (*.txt);;All Files (*)", options=options) |
202 | | - |
| 241 | + file_name, _ = QFileDialog.getSaveFileName( |
| 242 | + self, "Save Log", "", "Text Files (*.txt);;All Files (*)", options=options |
| 243 | + ) |
| 244 | + |
203 | 245 | if file_name: |
204 | 246 | with open(file_name, "w") as file: |
205 | 247 | file.write(self.output_text) |
206 | | - |
| 248 | + |
207 | 249 | def minimize_to_tray(self): |
208 | 250 | self.hide() |
209 | 251 |
|
| 252 | + |
210 | 253 | if __name__ == "__main__": |
211 | 254 | app = QApplication(sys.argv) |
212 | | - |
| 255 | + |
213 | 256 | app.setStyle(QStyleFactory.create("Fusion")) |
214 | 257 | palette = QPalette() |
215 | 258 | palette.setColor(QPalette.Window, QColor(53, 53, 53)) |
@@ -227,9 +270,10 @@ def minimize_to_tray(self): |
227 | 270 | palette.setColor(QPalette.HighlightedText, Qt.black) |
228 | 271 | app.setPalette(palette) |
229 | 272 |
|
230 | | - fw_app_id = u'fw.network.checkapp.1' |
231 | | - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(fw_app_id) |
232 | | - |
| 273 | + if sys.platform == "win32": |
| 274 | + fw_app_id = "fw.network.checkapp.1" |
| 275 | + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(fw_app_id) |
| 276 | + |
233 | 277 | window = NetworkCheckApp() |
234 | 278 | window.show() |
235 | 279 | sys.exit(app.exec_()) |