Skip to content

Commit 7fa199d

Browse files
authored
Add files via upload
0 parents  commit 7fa199d

File tree

3 files changed

+360
-0
lines changed

3 files changed

+360
-0
lines changed

build.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import PyInstaller.__main__
2+
import sys
3+
import os
4+
from pathlib import Path
5+
6+
desktop_path = Path.home() / "Desktop"
7+
spec_path = desktop_path / "ModularInstaller.spec"
8+
9+
if not spec_path.exists():
10+
print("Error: ModularInstaller.spec not found!")
11+
sys.exit(1)
12+
13+
print("Starting build process...")
14+
PyInstaller.__main__.run([str(spec_path)])
15+
print("Build complete! Check the Desktop/dist folder for your executable.")

main.py

+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
import sys
2+
import subprocess
3+
import ctypes
4+
import os
5+
import time
6+
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
7+
QHBoxLayout, QCheckBox, QPushButton, QLabel,
8+
QGridLayout, QFrame, QProgressBar)
9+
from PyQt6.QtCore import Qt, QThread, pyqtSignal
10+
from PyQt6.QtGui import QFont, QColor
11+
12+
def is_admin():
13+
try:
14+
return ctypes.windll.shell32.IsUserAnAdmin()
15+
except:
16+
return False
17+
18+
def run_as_admin(command):
19+
if not is_admin():
20+
ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd.exe", f"/c {command}", None, 1)
21+
return True
22+
return False
23+
24+
def run_without_admin(command):
25+
batch_content = f"""
26+
@echo off
27+
set command={command}
28+
start cmd /c "%command%"
29+
"""
30+
with open('temp_command.bat', 'w', encoding='utf-8') as f:
31+
f.write(batch_content)
32+
33+
subprocess.run(['explorer.exe', 'temp_command.bat'], shell=True)
34+
35+
try:
36+
time.sleep(1)
37+
os.remove('temp_command.bat')
38+
except:
39+
pass
40+
41+
class PackageManagerThread(QThread):
42+
progress_update = pyqtSignal(str)
43+
progress_value = pyqtSignal(int)
44+
45+
def __init__(self, apps_to_process, action="install"):
46+
super().__init__()
47+
self.apps_to_process = apps_to_process
48+
self.action = action
49+
self.total_apps = len(apps_to_process)
50+
self.current_app = 0
51+
52+
def update_progress(self):
53+
self.current_app += 1
54+
progress = int((self.current_app / self.total_apps) * 100)
55+
self.progress_value.emit(progress)
56+
57+
def run(self):
58+
self.current_app = 0
59+
self.progress_value.emit(0)
60+
61+
spotify_app = next((app for app in self.apps_to_process if app["name"] == "Spotify"), None)
62+
other_apps = [app for app in self.apps_to_process if app["name"] != "Spotify"]
63+
64+
if spotify_app and self.action == "install":
65+
self.progress_update.emit(f"Installing {spotify_app['name']}...")
66+
try:
67+
command = f'winget install -e -h --id {spotify_app["id"]} --accept-source-agreements --accept-package-agreements'
68+
run_without_admin(command)
69+
self.progress_update.emit(f"Started {spotify_app['name']} installation")
70+
except Exception as e:
71+
self.progress_update.emit(f"Failed to install {spotify_app['name']}: {str(e)}")
72+
self.update_progress()
73+
74+
for app in other_apps:
75+
action_text = "Installing" if self.action == "install" else "Uninstalling"
76+
self.progress_update.emit(f"{action_text} {app['name']}...")
77+
78+
try:
79+
if self.action == "install":
80+
command = f'winget install -e -h --id {app["id"]} --silent --accept-source-agreements --accept-package-agreements'
81+
else:
82+
command = f'winget uninstall -e -h --id {app["id"]} --silent'
83+
84+
if not is_admin():
85+
run_as_admin(command)
86+
else:
87+
subprocess.run(command, shell=True, check=True)
88+
89+
action_text = "installed" if self.action == "install" else "uninstalled"
90+
self.progress_update.emit(f"Successfully {action_text} {app['name']}")
91+
except subprocess.CalledProcessError:
92+
action_text = "install" if self.action == "install" else "uninstall"
93+
self.progress_update.emit(f"Failed to {action_text} {app['name']}")
94+
95+
self.update_progress()
96+
97+
class AppCard(QFrame):
98+
def __init__(self, app_name, parent=None):
99+
super().__init__(parent)
100+
self.setObjectName("AppCard")
101+
102+
layout = QHBoxLayout(self)
103+
layout.setContentsMargins(16, 12, 16, 12)
104+
layout.setSpacing(12)
105+
106+
self.checkbox = QCheckBox(app_name)
107+
self.checkbox.setFont(QFont("Segoe UI", 11))
108+
layout.addWidget(self.checkbox)
109+
110+
self.checkbox.toggled.connect(self.update_selection)
111+
112+
def update_selection(self, checked):
113+
self.setProperty("selected", checked)
114+
self.style().unpolish(self)
115+
self.style().polish(self)
116+
self.update()
117+
118+
class ModernInstaller(QMainWindow):
119+
def __init__(self):
120+
super().__init__()
121+
self.setWindowTitle("Modular")
122+
self.setMinimumSize(900, 600)
123+
self.setup_ui()
124+
125+
def setup_ui(self):
126+
self.apps = [
127+
{"name": "Spotify", "id": "Spotify.Spotify"},
128+
{"name": "Discord", "id": "Discord.Discord"},
129+
{"name": "Brave", "id": "Brave.Brave"},
130+
{"name": "Steam", "id": "Valve.Steam"},
131+
{"name": "7-Zip", "id": "7zip.7zip"},
132+
{"name": "Stremio", "id": "Stremio.Stremio"},
133+
{"name": "JPEGView", "id": "JPEGView.JPEGView"},
134+
{"name": "Visual C++ Redistributable", "id": "Microsoft.VCRedist.2015+.x64"},
135+
{"name": "Git", "id": "Git.Git"},
136+
{"name": "Python 3.10", "id": "Python.Python.3.10"},
137+
{"name": "Bitwarden", "id": "Bitwarden.Bitwarden"},
138+
{"name": "Mullvad VPN", "id": "MullvadVPN.MullvadVPN"},
139+
{"name": "Process Explorer", "id": "Microsoft.Sysinternals.ProcessExplorer"},
140+
{"name": "Visual Studio Code", "id": "Microsoft.VisualStudioCode"},
141+
{"name": "Trading View", "id": "TradingView.TradingViewDesktop"}
142+
]
143+
144+
central_widget = QWidget()
145+
self.setCentralWidget(central_widget)
146+
main_layout = QVBoxLayout(central_widget)
147+
main_layout.setContentsMargins(30, 30, 30, 30)
148+
main_layout.setSpacing(25)
149+
150+
title = QLabel("Select applications to install")
151+
title.setFont(QFont("Segoe UI", 32, QFont.Weight.Light))
152+
main_layout.addWidget(title)
153+
154+
grid_widget = QWidget()
155+
grid_layout = QGridLayout(grid_widget)
156+
grid_layout.setSpacing(10)
157+
grid_layout.setContentsMargins(0, 0, 0, 0)
158+
159+
self.app_cards = {}
160+
row = 0
161+
col = 0
162+
max_cols = 3
163+
164+
for app in self.apps:
165+
card = AppCard(app["name"])
166+
self.app_cards[app["name"]] = card
167+
grid_layout.addWidget(card, row, col)
168+
169+
col += 1
170+
if col >= max_cols:
171+
col = 0
172+
row += 1
173+
174+
main_layout.addWidget(grid_widget)
175+
176+
progress_container = QWidget()
177+
progress_layout = QVBoxLayout(progress_container)
178+
progress_layout.setSpacing(8)
179+
progress_layout.setContentsMargins(0, 0, 0, 0)
180+
181+
self.progress_label = QLabel("Ready to install")
182+
self.progress_label.setFont(QFont("Segoe UI", 11))
183+
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
184+
progress_layout.addWidget(self.progress_label)
185+
186+
self.progress_bar = QProgressBar()
187+
self.progress_bar.setTextVisible(False)
188+
self.progress_bar.setFixedHeight(4)
189+
self.progress_bar.setMaximum(100)
190+
self.progress_bar.setValue(0)
191+
progress_layout.addWidget(self.progress_bar)
192+
193+
main_layout.addWidget(progress_container)
194+
195+
button_layout = QHBoxLayout()
196+
button_layout.setSpacing(8)
197+
198+
select_all_btn = QPushButton("Select All")
199+
select_all_btn.clicked.connect(self.select_all)
200+
button_layout.addWidget(select_all_btn)
201+
202+
clear_btn = QPushButton("Clear All")
203+
clear_btn.clicked.connect(self.clear_all)
204+
button_layout.addWidget(clear_btn)
205+
206+
button_layout.addStretch()
207+
208+
uninstall_btn = QPushButton("Uninstall Selected")
209+
uninstall_btn.clicked.connect(lambda: self.process_selected("uninstall"))
210+
button_layout.addWidget(uninstall_btn)
211+
212+
install_btn = QPushButton("Install Selected")
213+
install_btn.clicked.connect(lambda: self.process_selected("install"))
214+
install_btn.setProperty("primary", True)
215+
button_layout.addWidget(install_btn)
216+
217+
main_layout.addLayout(button_layout)
218+
219+
self.setStyleSheet("""
220+
QMainWindow {
221+
background-color: #1f1f1f;
222+
}
223+
QWidget {
224+
background-color: transparent;
225+
color: #ffffff;
226+
}
227+
#AppCard {
228+
background-color: rgba(255, 255, 255, 0.04);
229+
border: 1px solid rgba(255, 255, 255, 0.1);
230+
border-radius: 6px;
231+
}
232+
#AppCard:hover {
233+
background-color: rgba(255, 255, 255, 0.08);
234+
border: 1px solid rgba(255, 255, 255, 0.15);
235+
}
236+
#AppCard[selected="true"] {
237+
background-color: rgba(0, 120, 212, 0.2);
238+
border: 1px solid #0078d4;
239+
}
240+
QCheckBox {
241+
spacing: 12px;
242+
}
243+
QCheckBox::indicator {
244+
width: 20px;
245+
height: 20px;
246+
border-radius: 4px;
247+
border: 1px solid rgba(255, 255, 255, 0.3);
248+
background-color: transparent;
249+
}
250+
QCheckBox::indicator:hover {
251+
border: 1px solid rgba(255, 255, 255, 0.5);
252+
background-color: rgba(255, 255, 255, 0.1);
253+
}
254+
QCheckBox::indicator:checked {
255+
background-color: #0078d4;
256+
border: 1px solid #0078d4;
257+
}
258+
QCheckBox::indicator:checked:hover {
259+
background-color: #1884d7;
260+
border: 1px solid #1884d7;
261+
}
262+
QPushButton {
263+
background-color: rgba(255, 255, 255, 0.06);
264+
border: none;
265+
padding: 8px 20px;
266+
border-radius: 4px;
267+
font-family: 'Segoe UI';
268+
font-size: 13px;
269+
min-width: 100px;
270+
min-height: 32px;
271+
}
272+
QPushButton:hover {
273+
background-color: rgba(255, 255, 255, 0.08);
274+
}
275+
QPushButton[primary="true"] {
276+
background-color: #0078d4;
277+
}
278+
QPushButton[primary="true"]:hover {
279+
background-color: #1884d7;
280+
}
281+
QLabel {
282+
color: white;
283+
}
284+
QProgressBar {
285+
background-color: rgba(255, 255, 255, 0.1);
286+
border: none;
287+
border-radius: 2px;
288+
}
289+
QProgressBar::chunk {
290+
background-color: #0078d4;
291+
border-radius: 2px;
292+
}
293+
""")
294+
295+
def select_all(self):
296+
for card in self.app_cards.values():
297+
card.checkbox.setChecked(True)
298+
299+
def clear_all(self):
300+
for card in self.app_cards.values():
301+
card.checkbox.setChecked(False)
302+
303+
def process_selected(self, action):
304+
selected_apps = [
305+
app for app in self.apps
306+
if self.app_cards[app["name"]].checkbox.isChecked()
307+
]
308+
309+
if not selected_apps:
310+
self.progress_label.setText("Please select at least one application")
311+
return
312+
313+
for card in self.app_cards.values():
314+
card.checkbox.setEnabled(False)
315+
316+
self.progress_bar.setValue(0)
317+
318+
self.manager_thread = PackageManagerThread(selected_apps, action)
319+
self.manager_thread.progress_update.connect(self.update_progress)
320+
self.manager_thread.progress_value.connect(self.progress_bar.setValue)
321+
self.manager_thread.finished.connect(lambda: self.process_finished(selected_apps))
322+
self.manager_thread.start()
323+
324+
def update_progress(self, message):
325+
self.progress_label.setText(message)
326+
327+
def process_finished(self, processed_apps):
328+
for card in self.app_cards.values():
329+
card.checkbox.setEnabled(True)
330+
if any(app["name"] == card.checkbox.text() for app in processed_apps):
331+
card.checkbox.setChecked(False)
332+
self.progress_label.setText("Operation completed!")
333+
334+
def main():
335+
if not is_admin():
336+
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
337+
sys.exit()
338+
339+
app = QApplication(sys.argv)
340+
window = ModernInstaller()
341+
window.show()
342+
sys.exit(app.exec())
343+
344+
if __name__ == "__main__":
345+
main()

modular.ico

3.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)