macutility 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
app/__init__.py ADDED
File without changes
app/main.py ADDED
@@ -0,0 +1,20 @@
1
+ import sys
2
+
3
+ from PySide6.QtWidgets import QApplication
4
+
5
+ from app.ui.main_window import MainWindow
6
+
7
+
8
+ def main():
9
+ app = QApplication(sys.argv)
10
+
11
+ window = MainWindow()
12
+ window.show()
13
+
14
+ sys.exit(
15
+ app.exec()
16
+ )
17
+
18
+
19
+ if __name__ == "__main__":
20
+ main()
File without changes
@@ -0,0 +1,169 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ import psutil
5
+
6
+ from app.utils.protected_processes import (
7
+ PROTECTED_APP_KEYWORDS,
8
+ PROTECTED_PROCESS_NAMES,
9
+ )
10
+
11
+
12
+ class ProcessService:
13
+ """
14
+ Service untuk membaca dan mengontrol proses macOS.
15
+ """
16
+
17
+ def __init__(self):
18
+ self.process_cache: dict[int, psutil.Process] = {}
19
+
20
+ # ---------------------------------------------------------
21
+ # Process List
22
+ # ---------------------------------------------------------
23
+
24
+ def get_processes(self) -> list[dict[str, Any]]:
25
+ processes = []
26
+
27
+ current_pids = set()
28
+
29
+ for process in psutil.process_iter(
30
+ [
31
+ "pid",
32
+ "ppid",
33
+ "name",
34
+ "username",
35
+ "memory_percent",
36
+ "status",
37
+ ]
38
+ ):
39
+ try:
40
+ pid = process.pid
41
+ current_pids.add(pid)
42
+
43
+ # Simpan object Process supaya sampling CPU
44
+ # tetap konsisten antar-refresh.
45
+ self.process_cache[pid] = process
46
+
47
+ info = process.info
48
+
49
+ name = info.get("name") or "Unknown"
50
+
51
+ # CPU sampling.
52
+ cpu = process.cpu_percent(interval=None)
53
+
54
+ memory = info.get("memory_percent") or 0.0
55
+
56
+ protected = self.is_protected(
57
+ name,
58
+ process,
59
+ )
60
+
61
+ processes.append(
62
+ {
63
+ "pid": pid,
64
+ "ppid": info.get("ppid") or 0,
65
+ "name": name,
66
+ "username": info.get("username") or "",
67
+ "cpu": cpu,
68
+ "memory": memory,
69
+ "status": info.get("status") or "",
70
+ "protected": protected,
71
+ }
72
+ )
73
+
74
+ except (
75
+ psutil.NoSuchProcess,
76
+ psutil.AccessDenied,
77
+ psutil.ZombieProcess,
78
+ ):
79
+ continue
80
+
81
+ # Hapus process yang sudah mati dari cache.
82
+ stale_pids = set(self.process_cache.keys()) - current_pids
83
+
84
+ for pid in stale_pids:
85
+ self.process_cache.pop(pid, None)
86
+
87
+ return processes
88
+
89
+ # ---------------------------------------------------------
90
+ # Protected Process
91
+ # ---------------------------------------------------------
92
+
93
+ @staticmethod
94
+ def is_protected(
95
+ name: str,
96
+ process: psutil.Process,
97
+ ) -> bool:
98
+
99
+ if name in PROTECTED_PROCESS_NAMES:
100
+ return True
101
+
102
+ for keyword in PROTECTED_APP_KEYWORDS:
103
+ if keyword.lower() in name.lower():
104
+ return True
105
+
106
+ # Jangan izinkan aplikasi membunuh dirinya sendiri.
107
+ try:
108
+ if process.pid == os.getpid():
109
+ return True
110
+
111
+ except Exception:
112
+ pass
113
+
114
+ return False
115
+
116
+ # ---------------------------------------------------------
117
+ # Kill Process
118
+ # ---------------------------------------------------------
119
+
120
+ def kill_process(
121
+ self,
122
+ pid: int,
123
+ force: bool = False,
124
+ ) -> tuple[bool, str]:
125
+
126
+ try:
127
+ process = psutil.Process(pid)
128
+
129
+ name = process.name()
130
+
131
+ # Protection check.
132
+ if self.is_protected(name, process):
133
+ return (
134
+ False,
135
+ f"Process '{name}' (PID {pid}) dilindungi.",
136
+ )
137
+
138
+ if force:
139
+ process.kill()
140
+ else:
141
+ process.terminate()
142
+
143
+ # Hapus dari cache.
144
+ self.process_cache.pop(pid, None)
145
+
146
+ return (
147
+ True,
148
+ f"Process '{name}' (PID {pid}) berhasil dihentikan.",
149
+ )
150
+
151
+ except psutil.NoSuchProcess:
152
+ self.process_cache.pop(pid, None)
153
+
154
+ return (
155
+ False,
156
+ f"Process PID {pid} sudah tidak ada.",
157
+ )
158
+
159
+ except psutil.AccessDenied:
160
+ return (
161
+ False,
162
+ f"Tidak punya permission untuk menghentikan PID {pid}.",
163
+ )
164
+
165
+ except Exception as error:
166
+ return (
167
+ False,
168
+ str(error),
169
+ )
app/ui/__init__.py ADDED
File without changes
app/ui/main_window.py ADDED
@@ -0,0 +1,517 @@
1
+ from PySide6.QtCore import QTimer, Qt
2
+ from PySide6.QtGui import QAction
3
+ from PySide6.QtWidgets import (
4
+ QApplication,
5
+ QHBoxLayout,
6
+ QHeaderView,
7
+ QLabel,
8
+ QLineEdit,
9
+ QMainWindow,
10
+ QMessageBox,
11
+ QPushButton,
12
+ QProgressBar,
13
+ QTableWidget,
14
+ QTableWidgetItem,
15
+ QVBoxLayout,
16
+ QWidget,
17
+ )
18
+
19
+ import psutil
20
+
21
+ from app.services.process_service import ProcessService
22
+
23
+
24
+ class MainWindow(QMainWindow):
25
+
26
+ def __init__(self):
27
+ super().__init__()
28
+
29
+ self.setWindowTitle("Mac Utility")
30
+ self.resize(1100, 700)
31
+
32
+ self.process_service = ProcessService()
33
+
34
+ self.all_processes = []
35
+
36
+ self.setup_ui()
37
+ self.setup_timer()
38
+
39
+ self.refresh_system()
40
+ self.refresh_processes()
41
+
42
+ # ---------------------------------------------------------
43
+ # UI
44
+ # ---------------------------------------------------------
45
+
46
+ def setup_ui(self):
47
+
48
+ central_widget = QWidget()
49
+ self.setCentralWidget(central_widget)
50
+
51
+ main_layout = QVBoxLayout(central_widget)
52
+ main_layout.setContentsMargins(20, 20, 20, 20)
53
+ main_layout.setSpacing(15)
54
+
55
+ # -----------------------------------------------------
56
+ # Header
57
+ # -----------------------------------------------------
58
+
59
+ header_layout = QHBoxLayout()
60
+
61
+ title = QLabel("Mac Utility")
62
+ title.setObjectName("title")
63
+
64
+ subtitle = QLabel("System & Process Manager")
65
+
66
+ header_layout.addWidget(title)
67
+ header_layout.addWidget(subtitle)
68
+ header_layout.addStretch()
69
+
70
+ main_layout.addLayout(header_layout)
71
+
72
+ # -----------------------------------------------------
73
+ # System monitor
74
+ # -----------------------------------------------------
75
+
76
+ system_layout = QHBoxLayout()
77
+
78
+ # CPU
79
+
80
+ cpu_container = QVBoxLayout()
81
+
82
+ self.cpu_label = QLabel("CPU 0%")
83
+
84
+ self.cpu_progress = QProgressBar()
85
+ self.cpu_progress.setRange(0, 100)
86
+
87
+ cpu_container.addWidget(self.cpu_label)
88
+ cpu_container.addWidget(self.cpu_progress)
89
+
90
+ # RAM
91
+
92
+ ram_container = QVBoxLayout()
93
+
94
+ self.ram_label = QLabel("RAM 0%")
95
+
96
+ self.ram_progress = QProgressBar()
97
+ self.ram_progress.setRange(0, 100)
98
+
99
+ ram_container.addWidget(self.ram_label)
100
+ ram_container.addWidget(self.ram_progress)
101
+
102
+ system_layout.addLayout(cpu_container)
103
+ system_layout.addLayout(ram_container)
104
+
105
+ main_layout.addLayout(system_layout)
106
+
107
+ # -----------------------------------------------------
108
+ # Search
109
+ # -----------------------------------------------------
110
+
111
+ search_layout = QHBoxLayout()
112
+
113
+ self.search_input = QLineEdit()
114
+ self.search_input.setPlaceholderText(
115
+ "Search process..."
116
+ )
117
+
118
+ self.refresh_button = QPushButton("Refresh")
119
+
120
+ self.refresh_button.clicked.connect(
121
+ self.refresh_processes
122
+ )
123
+
124
+ search_layout.addWidget(self.search_input)
125
+ search_layout.addWidget(self.refresh_button)
126
+
127
+ main_layout.addLayout(search_layout)
128
+
129
+ self.search_input.textChanged.connect(
130
+ self.filter_processes
131
+ )
132
+
133
+ # -----------------------------------------------------
134
+ # Process table
135
+ # -----------------------------------------------------
136
+
137
+ self.process_table = QTableWidget()
138
+
139
+ self.process_table.setColumnCount(7)
140
+
141
+ self.process_table.setHorizontalHeaderLabels(
142
+ [
143
+ "PID",
144
+ "Process",
145
+ "CPU",
146
+ "RAM",
147
+ "Status",
148
+ "Protection",
149
+ "Action",
150
+ ]
151
+ )
152
+
153
+ self.process_table.setSortingEnabled(True)
154
+
155
+ self.process_table.setSelectionBehavior(
156
+ QTableWidget.SelectRows
157
+ )
158
+
159
+ self.process_table.setEditTriggers(
160
+ QTableWidget.NoEditTriggers
161
+ )
162
+
163
+ header = self.process_table.horizontalHeader()
164
+
165
+ header.setSectionResizeMode(
166
+ 1,
167
+ QHeaderView.Stretch,
168
+ )
169
+
170
+ for column in [0, 2, 3, 4, 5, 6]:
171
+ header.setSectionResizeMode(
172
+ column,
173
+ QHeaderView.ResizeToContents,
174
+ )
175
+
176
+ main_layout.addWidget(self.process_table)
177
+
178
+ # -----------------------------------------------------
179
+ # Style
180
+ # -----------------------------------------------------
181
+
182
+ self.setStyleSheet(
183
+ """
184
+ QMainWindow {
185
+ background: #111827;
186
+ }
187
+
188
+ QWidget {
189
+ color: #e5e7eb;
190
+ font-size: 13px;
191
+ }
192
+
193
+ QLabel#title {
194
+ font-size: 24px;
195
+ font-weight: bold;
196
+ }
197
+
198
+ QLineEdit {
199
+ background: #1f2937;
200
+ border: 1px solid #374151;
201
+ border-radius: 6px;
202
+ padding: 8px;
203
+ }
204
+
205
+ QPushButton {
206
+ background: #374151;
207
+ border: none;
208
+ border-radius: 6px;
209
+ padding: 8px 14px;
210
+ }
211
+
212
+ QPushButton:hover {
213
+ background: #4b5563;
214
+ }
215
+
216
+ QTableWidget {
217
+ background: #111827;
218
+ alternate-background-color: #1f2937;
219
+ border: 1px solid #374151;
220
+ gridline-color: #374151;
221
+ }
222
+
223
+ QHeaderView::section {
224
+ background: #1f2937;
225
+ padding: 8px;
226
+ border: none;
227
+ font-weight: bold;
228
+ }
229
+
230
+ QProgressBar {
231
+ background: #1f2937;
232
+ border: none;
233
+ border-radius: 5px;
234
+ text-align: center;
235
+ }
236
+
237
+ QProgressBar::chunk {
238
+ background: #60a5fa;
239
+ border-radius: 5px;
240
+ }
241
+ """
242
+ )
243
+
244
+ # ---------------------------------------------------------
245
+ # Timer
246
+ # ---------------------------------------------------------
247
+
248
+ def setup_timer(self):
249
+
250
+ self.timer = QTimer(self)
251
+
252
+ self.timer.timeout.connect(
253
+ self.refresh_system
254
+ )
255
+
256
+ self.timer.timeout.connect(
257
+ self.refresh_processes
258
+ )
259
+
260
+ # Refresh setiap 2 detik.
261
+ self.timer.start(2000)
262
+
263
+ # ---------------------------------------------------------
264
+ # System
265
+ # ---------------------------------------------------------
266
+
267
+ def refresh_system(self):
268
+
269
+ cpu = psutil.cpu_percent(interval=None)
270
+
271
+ memory = psutil.virtual_memory()
272
+
273
+ ram = memory.percent
274
+
275
+ self.cpu_label.setText(
276
+ f"CPU {cpu:.1f}%"
277
+ )
278
+
279
+ self.cpu_progress.setValue(
280
+ int(cpu)
281
+ )
282
+
283
+ self.ram_label.setText(
284
+ f"RAM {ram:.1f}%"
285
+ )
286
+
287
+ self.ram_progress.setValue(
288
+ int(ram)
289
+ )
290
+
291
+ # ---------------------------------------------------------
292
+ # Processes
293
+ # ---------------------------------------------------------
294
+
295
+ def refresh_processes(self):
296
+
297
+ self.all_processes = (
298
+ self.process_service.get_processes()
299
+ )
300
+
301
+ self.filter_processes(
302
+ self.search_input.text()
303
+ )
304
+
305
+ def filter_processes(self, text: str):
306
+
307
+ search = text.lower().strip()
308
+
309
+ processes = self.all_processes
310
+
311
+ if search:
312
+ processes = [
313
+ process
314
+ for process in processes
315
+ if (
316
+ search in process["name"].lower()
317
+ or search
318
+ in str(process["pid"])
319
+ )
320
+ ]
321
+
322
+ # CPU terbesar di atas.
323
+ processes = sorted(
324
+ processes,
325
+ key=lambda item: item["cpu"],
326
+ reverse=True,
327
+ )
328
+
329
+ self.process_table.setSortingEnabled(False)
330
+
331
+ self.process_table.setRowCount(
332
+ len(processes)
333
+ )
334
+
335
+ for row, process in enumerate(processes):
336
+
337
+ self.process_table.setItem(
338
+ row,
339
+ 0,
340
+ QTableWidgetItem(
341
+ str(process["pid"])
342
+ ),
343
+ )
344
+
345
+ self.process_table.setItem(
346
+ row,
347
+ 1,
348
+ QTableWidgetItem(
349
+ process["name"]
350
+ ),
351
+ )
352
+
353
+ cpu_item = QTableWidgetItem(
354
+ f'{process["cpu"]:.1f}%'
355
+ )
356
+
357
+ cpu_item.setTextAlignment(
358
+ Qt.AlignCenter
359
+ )
360
+
361
+ self.process_table.setItem(
362
+ row,
363
+ 2,
364
+ cpu_item,
365
+ )
366
+
367
+ memory_item = QTableWidgetItem(
368
+ f'{process["memory"]:.1f}%'
369
+ )
370
+
371
+ memory_item.setTextAlignment(
372
+ Qt.AlignCenter
373
+ )
374
+
375
+ self.process_table.setItem(
376
+ row,
377
+ 3,
378
+ memory_item,
379
+ )
380
+
381
+ self.process_table.setItem(
382
+ row,
383
+ 4,
384
+ QTableWidgetItem(
385
+ process["status"]
386
+ ),
387
+ )
388
+
389
+ protection = (
390
+ "🔒 Protected"
391
+ if process["protected"]
392
+ else "—"
393
+ )
394
+
395
+ self.process_table.setItem(
396
+ row,
397
+ 5,
398
+ QTableWidgetItem(
399
+ protection
400
+ ),
401
+ )
402
+
403
+ button = QPushButton(
404
+ "Protected"
405
+ if process["protected"]
406
+ else "Kill"
407
+ )
408
+
409
+ button.setEnabled(
410
+ not process["protected"]
411
+ )
412
+
413
+ button.clicked.connect(
414
+ lambda checked=False,
415
+ pid=process["pid"],
416
+ name=process["name"]:
417
+ self.confirm_kill(pid, name)
418
+ )
419
+
420
+ self.process_table.setCellWidget(
421
+ row,
422
+ 6,
423
+ button,
424
+ )
425
+
426
+ self.process_table.setSortingEnabled(True)
427
+
428
+ # ---------------------------------------------------------
429
+ # Kill
430
+ # ---------------------------------------------------------
431
+
432
+ def confirm_kill(
433
+ self,
434
+ pid: int,
435
+ name: str,
436
+ ):
437
+
438
+ dialog = QMessageBox(self)
439
+
440
+ dialog.setWindowTitle(
441
+ "Kill Process"
442
+ )
443
+
444
+ dialog.setText(
445
+ f"Apakah kamu yakin ingin menghentikan:\n\n"
446
+ f"{name}\n"
447
+ f"PID: {pid}"
448
+ )
449
+
450
+ dialog.setInformativeText(
451
+ "Gunakan Force Kill hanya jika process "
452
+ "tidak merespons."
453
+ )
454
+
455
+ normal_button = dialog.addButton(
456
+ "Kill",
457
+ QMessageBox.AcceptRole,
458
+ )
459
+
460
+ force_button = dialog.addButton(
461
+ "Force Kill",
462
+ QMessageBox.DestructiveRole,
463
+ )
464
+
465
+ dialog.addButton(
466
+ "Cancel",
467
+ QMessageBox.RejectRole,
468
+ )
469
+
470
+ dialog.exec()
471
+
472
+ clicked = dialog.clickedButton()
473
+
474
+ if clicked == normal_button:
475
+
476
+ self.execute_kill(
477
+ pid,
478
+ force=False,
479
+ )
480
+
481
+ elif clicked == force_button:
482
+
483
+ self.execute_kill(
484
+ pid,
485
+ force=True,
486
+ )
487
+
488
+ def execute_kill(
489
+ self,
490
+ pid: int,
491
+ force: bool,
492
+ ):
493
+
494
+ success, message = (
495
+ self.process_service.kill_process(
496
+ pid,
497
+ force,
498
+ )
499
+ )
500
+
501
+ if success:
502
+
503
+ QMessageBox.information(
504
+ self,
505
+ "Process Stopped",
506
+ message,
507
+ )
508
+
509
+ else:
510
+
511
+ QMessageBox.warning(
512
+ self,
513
+ "Unable to Stop Process",
514
+ message,
515
+ )
516
+
517
+ self.refresh_processes()
app/utils/__init__.py ADDED
File without changes
@@ -0,0 +1,35 @@
1
+ """
2
+ Daftar proses yang dilindungi.
3
+
4
+ Proses di sini tidak akan bisa di-kill melalui aplikasi
5
+ untuk mengurangi risiko membuat macOS tidak stabil.
6
+ """
7
+
8
+ PROTECTED_PROCESS_NAMES = {
9
+ "kernel_task",
10
+ "launchd",
11
+ "WindowServer",
12
+ "loginwindow",
13
+ "systemuiserver",
14
+ "Finder",
15
+ "Dock",
16
+ "SystemUIServer",
17
+ "coreaudiod",
18
+ "coreduetd",
19
+ "mds",
20
+ "mds_stores",
21
+ "mdworker",
22
+ "mdworker_shared",
23
+ "cfprefsd",
24
+ "opendirectoryd",
25
+ "distnoted",
26
+ "notifyd",
27
+ }
28
+
29
+ # Aplikasi yang secara eksplisit tidak boleh disentuh.
30
+ # Sesuai kebutuhan kita: Termius dan VS Code.
31
+ PROTECTED_APP_KEYWORDS = {
32
+ "Termius",
33
+ "Visual Studio Code",
34
+ "Code Helper",
35
+ }
@@ -0,0 +1,432 @@
1
+ Metadata-Version: 2.5
2
+ Name: macutility
3
+ Version: 0.1.0
4
+ Summary: A lightweight macOS desktop utility for system monitoring and process management.
5
+ Project-URL: Homepage, https://github.com/codesyariah122/MacUtility
6
+ Project-URL: Repository, https://github.com/codesyariah122/MacUtility
7
+ Project-URL: Issues, https://github.com/codesyariah122/MacUtility/issues
8
+ Author: Puji Ermanto
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: desktop,developer-tools,mac,macos,process-manager,system-monitor,utility
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: System :: Monitoring
25
+ Classifier: Topic :: System :: Systems Administration
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: psutil>=7.2.0
28
+ Requires-Dist: pyside6>=6.11.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # MacUtility
32
+
33
+ A lightweight macOS desktop utility built with Python and PySide6 for monitoring system resources and managing running processes.
34
+
35
+ MacUtility is designed as a practical developer-oriented utility for macOS, with a focus on quickly identifying high CPU or memory usage and safely managing processes from a graphical interface.
36
+
37
+ > 🚧 **Project Status:** Early Development / Pre-release
38
+
39
+ ---
40
+
41
+ ## Features
42
+
43
+ ### Process Manager
44
+
45
+ Monitor running processes directly from a desktop interface.
46
+
47
+ - View running processes
48
+ - Display PID
49
+ - Display process name
50
+ - Monitor CPU usage
51
+ - Monitor memory usage
52
+ - View process status
53
+ - Search processes by name or PID
54
+ - Sort processes by CPU usage
55
+ - Terminate processes
56
+ - Force kill processes
57
+ - Protected system processes
58
+
59
+ ### System Monitor
60
+
61
+ Monitor basic system resources in real time.
62
+
63
+ - CPU usage
64
+ - Memory usage
65
+ - Automatic refresh
66
+ - Lightweight monitoring
67
+
68
+ ### Protected Processes
69
+
70
+ MacUtility includes a protection layer to prevent accidental termination of important macOS processes.
71
+
72
+ Protected processes include system components such as:
73
+
74
+ - `kernel_task`
75
+ - `launchd`
76
+ - `WindowServer`
77
+ - `loginwindow`
78
+ - `systemuiserver`
79
+ - `Finder`
80
+ - `Dock`
81
+ - `SystemUIServer`
82
+ - `coreaudiod`
83
+ - `coreduetd`
84
+ - `mds`
85
+ - `mds_stores`
86
+ - `mdworker`
87
+ - `mdworker_shared`
88
+ - `cfprefsd`
89
+ - `opendirectoryd`
90
+ - `distnoted`
91
+ - `notifyd`
92
+
93
+ The application also protects selected applications that should remain available during development, including:
94
+
95
+ - Termius
96
+ - Visual Studio Code
97
+ - Code Helper
98
+
99
+ The protection system is intentionally conservative and may be expanded as the project evolves.
100
+
101
+ ---
102
+
103
+ ## Screenshots
104
+
105
+ ### Process Manager
106
+
107
+ > Screenshot will be added as the UI evolves.
108
+
109
+ ---
110
+
111
+ ## Tech Stack
112
+
113
+ MacUtility is built with:
114
+
115
+ - **Python 3**
116
+ - **PySide6** — desktop GUI framework
117
+ - **psutil** — system and process monitoring
118
+
119
+ ### Architecture
120
+
121
+ The project separates UI, process management, and utility logic:
122
+
123
+ ```text
124
+ MacUtility/
125
+ ├── app/
126
+ │ ├── __init__.py
127
+ │ ├── main.py
128
+ │ │
129
+ │ ├── services/
130
+ │ │ ├── __init__.py
131
+ │ │ └── process_service.py
132
+ │ │
133
+ │ ├── ui/
134
+ │ │ ├── __init__.py
135
+ │ │ └── main_window.py
136
+ │ │
137
+ │ └── utils/
138
+ │ ├── __init__.py
139
+ │ └── protected_processes.py
140
+ │
141
+ ├── .gitignore
142
+ ├── requirements.txt
143
+ └── README.md
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Requirements
149
+
150
+ - macOS
151
+ - Python 3.10+
152
+ - pip
153
+ - Virtual environment recommended
154
+
155
+ PySide6 provides the desktop GUI, while psutil is responsible for accessing system and process information.
156
+
157
+ ---
158
+
159
+ ## Installation
160
+
161
+ ### 1. Clone the repository
162
+
163
+ ```bash
164
+ git clone https://github.com/codesyariah122/MacUtility.git
165
+ cd MacUtility
166
+ ```
167
+
168
+ ### 2. Create a virtual environment
169
+
170
+ ```bash
171
+ python3 -m venv .venv
172
+ ```
173
+
174
+ ### 3. Activate the virtual environment
175
+
176
+ ```bash
177
+ source .venv/bin/activate
178
+ ```
179
+
180
+ ### 4. Install dependencies
181
+
182
+ ```bash
183
+ pip install -r requirements.txt
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Running the Application
189
+
190
+ From the project root:
191
+
192
+ ```bash
193
+ python -m app.main
194
+ ```
195
+
196
+ MacUtility should open as a desktop application.
197
+
198
+ ---
199
+
200
+ ## Development
201
+
202
+ MacUtility is currently being developed incrementally.
203
+
204
+ The current development milestone is:
205
+
206
+ ### V0.1.0
207
+
208
+ - Process monitoring
209
+ - CPU monitoring
210
+ - Memory monitoring
211
+ - Process search
212
+ - Process sorting
213
+ - Process termination
214
+ - Force kill
215
+ - Protected processes
216
+
217
+ Future releases will expand the application into a broader macOS developer utility.
218
+
219
+ ---
220
+
221
+ ## Roadmap
222
+
223
+ ### V0.1.x — Process Manager
224
+
225
+ - [x] CPU monitoring
226
+ - [x] Memory monitoring
227
+ - [x] Process list
228
+ - [x] Process search
229
+ - [x] Process sorting
230
+ - [x] Process termination
231
+ - [x] Force kill
232
+ - [x] Protected processes
233
+ - [x] Protected Termius
234
+ - [x] Protected Visual Studio Code
235
+
236
+ ### V0.2.x — Dashboard
237
+
238
+ - [ ] Improved system dashboard
239
+ - [ ] Top CPU processes
240
+ - [ ] Top memory processes
241
+ - [ ] CPU usage indicators
242
+ - [ ] Memory usage indicators
243
+ - [ ] Process details
244
+ - [ ] Background monitoring worker
245
+
246
+ ### V0.3.x — Disk Utilities
247
+
248
+ - [ ] Disk usage monitoring
249
+ - [ ] Large file finder
250
+ - [ ] Cache inspection
251
+ - [ ] Developer cache utilities
252
+ - [ ] Storage overview
253
+
254
+ ### V0.4.x — Network Utilities
255
+
256
+ - [ ] Network status
257
+ - [ ] Network interface information
258
+ - [ ] IP information
259
+ - [ ] DNS utilities
260
+ - [ ] Ping utility
261
+ - [ ] Network diagnostics
262
+
263
+ ### V0.5.x — Port Manager
264
+
265
+ - [ ] List listening ports
266
+ - [ ] Identify processes using ports
267
+ - [ ] Search ports
268
+ - [ ] Terminate process by port
269
+ - [ ] Port diagnostics
270
+
271
+ Example:
272
+
273
+ ```text
274
+ Port 8000
275
+
276
+ PID Process
277
+ 1234 php
278
+
279
+ [ Kill Process ]
280
+ ```
281
+
282
+ ### V0.6.x — Developer Utilities
283
+
284
+ Planned utilities for common development environments:
285
+
286
+ - [ ] PHP process management
287
+ - [ ] Node.js process management
288
+ - [ ] Laravel utilities
289
+ - [ ] Composer utilities
290
+ - [ ] npm utilities
291
+ - [ ] Yarn utilities
292
+ - [ ] Flutter utilities
293
+ - [ ] Android SDK utilities
294
+ - [ ] Git utilities
295
+ - [ ] SSH utilities
296
+
297
+ ### V0.7.x — Docker Utilities
298
+
299
+ - [ ] Docker container overview
300
+ - [ ] Container CPU usage
301
+ - [ ] Container memory usage
302
+ - [ ] Start/stop containers
303
+ - [ ] Docker cleanup utilities
304
+ - [ ] Docker resource overview
305
+
306
+ ### V0.8.x — macOS Integration
307
+
308
+ - [ ] Menu bar application
309
+ - [ ] macOS notifications
310
+ - [ ] Launch at login
311
+ - [ ] Native macOS application icon
312
+ - [ ] Improved macOS permissions handling
313
+
314
+ ### V1.0.0 — Stable Release
315
+
316
+ The goal for `1.0.0` is a stable, polished macOS developer utility with reliable system monitoring and a collection of useful developer-oriented tools.
317
+
318
+ ---
319
+
320
+ ## Safety
321
+
322
+ MacUtility interacts directly with running processes.
323
+
324
+ Process termination can cause:
325
+
326
+ - Unsaved data loss
327
+ - Application crashes
328
+ - Interrupted development processes
329
+ - Unexpected system behavior
330
+
331
+ For this reason, MacUtility includes protected processes and confirmation dialogs before terminating processes.
332
+
333
+ Use **Force Kill** only when a process is unresponsive and normal termination does not work.
334
+
335
+ ---
336
+
337
+ ## Why MacUtility?
338
+
339
+ macOS already provides tools such as Activity Monitor and Terminal for process management.
340
+
341
+ MacUtility aims to provide a simpler workflow for developers who frequently need to:
342
+
343
+ 1. Identify a process consuming excessive CPU or memory.
344
+ 2. Find its PID.
345
+ 3. Determine whether the process is safe to terminate.
346
+ 4. Stop it without opening Terminal.
347
+ 5. Access additional developer-oriented utilities from one application.
348
+
349
+ For example:
350
+
351
+ ```text
352
+ 🔥 Firefox plugin-container
353
+
354
+ CPU: 88%
355
+
356
+ [ Kill ]
357
+ ```
358
+
359
+ instead of manually running:
360
+
361
+ ```bash
362
+ ps -Ao pid,pcpu,pmem,comm | sort -k2 -nr
363
+ ```
364
+
365
+ and then:
366
+
367
+ ```bash
368
+ kill -9 <PID>
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Project Goals
374
+
375
+ MacUtility aims to become a practical toolbox for macOS developers.
376
+
377
+ The long-term goal is to bring frequently used commands and diagnostics into one lightweight desktop application.
378
+
379
+ Instead of remembering multiple Terminal commands, developers should be able to perform common tasks through a single interface.
380
+
381
+ ---
382
+
383
+ ## Contributing
384
+
385
+ Contributions, suggestions, and issue reports are welcome.
386
+
387
+ Before submitting a pull request:
388
+
389
+ 1. Fork the repository.
390
+ 2. Create a feature branch.
391
+ 3. Make your changes.
392
+ 4. Test the application on macOS.
393
+ 5. Commit your changes.
394
+ 6. Open a pull request.
395
+
396
+ Example:
397
+
398
+ ```bash
399
+ git checkout -b feature/my-feature
400
+ ```
401
+
402
+ ---
403
+
404
+ ## License
405
+
406
+ License information will be added before the first public release.
407
+
408
+ ---
409
+
410
+ ## Author
411
+
412
+ **Puji Ermanto**
413
+
414
+ GitHub:
415
+
416
+ https://github.com/codesyariah122
417
+
418
+ ---
419
+
420
+ ## Repository
421
+
422
+ GitHub:
423
+
424
+ https://github.com/codesyariah122/MacUtility
425
+
426
+ ---
427
+
428
+ ## Status
429
+
430
+ MacUtility is currently an active development project.
431
+
432
+ The current version focuses on the foundation of the process manager and system monitoring functionality. APIs, architecture, UI, and features may change before the `1.0.0` stable release.
@@ -0,0 +1,13 @@
1
+ app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ app/main.py,sha256=85pWCTUur8r9fbG5GnICyc5G34S4b2kplKqsqnYjz98,269
3
+ app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ app/services/process_service.py,sha256=cHYBHLTgClBffdS40bwQtbtYFX1rmBjoN1c_dtib34o,4349
5
+ app/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ app/ui/main_window.py,sha256=hOgHQ-RLitEle4uvZliwmNgqj7tUh8nCCEC_-MSG0no,12062
7
+ app/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ app/utils/protected_processes.py,sha256=6HBIL670i09dCpJjup7RCNgh1G7234bbye5nvPPcIJI,685
9
+ macutility-0.1.0.dist-info/METADATA,sha256=C4IG3L6fyRZehZPcZtywsOTOu_AUF2i9mNtEuj7_ahU,9021
10
+ macutility-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
11
+ macutility-0.1.0.dist-info/entry_points.txt,sha256=BfuLN8elGHoy_CkzVrmOz4NXRSzr5N46SzHeeaYeny8,45
12
+ macutility-0.1.0.dist-info/licenses/LICENSE,sha256=SeRn2Z_Buqe-t5rOVIYna_nrKcs7KHTtVcQOrxhio2k,1069
13
+ macutility-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ macutility = app.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Puji Ermanto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.c