-
Notifications
You must be signed in to change notification settings - Fork 41
/
gui-installer
executable file
·397 lines (318 loc) · 12.1 KB
/
gui-installer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pip install pip>=24.0
# pip install PyQt6
# sudo apt install libxcb-cursor0
# GTK or QT based installer
# Copy files to some directory, symlink the binary.
import os
import shutil
import stat
import sys
from PyQt6.QtCore import Qt, pyqtSlot, pyqtSignal, QObject, QThread
from PyQt6.QtGui import QGuiApplication, QPixmap, QTextCursor
from PyQt6.QtWidgets import QGraphicsScene, QFileDialog, QMessageBox, QMainWindow, QStyleFactory, QApplication
from PyQt6 import QtGui
from PyQt6.uic import loadUi
# Globals
paintownVersion = ["3.6.1"]
binLocation = ['/usr/share/games/paintown']
dataLocation = ['/usr/games']
allowExit = [True]
currentPage = [0]
# Higher numbers of verbose will print more stuff (and slow the program down)
verbose = 0
widget: QMainWindow
def debug(s):
if verbose > 0:
print(s)
# Create Directory
def createDirectory(path):
debug("Creating directory '%s'" % path)
if not os.path.exists(path):
os.makedirs(path)
# Update page
def updatePageNumber():
widget.positionLabel.setText(
'Page ' + str(widget.stackedWidget.currentIndex() + 1) + ' of ' + str(widget.stackedWidget.count()))
# Set Info on Data that will be copied
def setDataFileInfo(location, widget, emitter):
# widget.clear()
for root, dirs, files in os.walk("data"):
for file in files:
tmpFile = os.path.join(root, file)
if '.svn' in tmpFile:
continue
# widget.emit(SIGNAL(signal), lambda: widget.append(tmpFile + ' >> ' + dataLocation[0] + '/' + root))
# scoping is broken in python so we have to create a new function that
# will ensure `path' stores its value correctly
def doit(path):
emitter(lambda: widget.append(path))
# This string is ugly, use something that looks nicer
doit(tmpFile + ' >> ' + location + '/' + root)
# emitter(lambda: widget.append(tmpFile + ' >> ' + dataLocation[0] + '/' + root))
# widget.append(tmpFile + ' >> ' + dataLocation[0] + '/' + root)
# widget.append('paintown' + ' >> ' + dataLocation[0] + '/paintown-bin')
emitter(lambda: widget.append('paintown' + ' >> ' + location + '/paintown-bin'))
# Move the cursor to the start of the document
emitter(lambda: widget.moveCursor(QTextCursor.MoveOperation.Start))
# Copy Data
def copyData(currentRoot, binDirectory, widget, emitter):
def updateGui(str):
emitter(lambda: widget.append(str))
def getFiles(datadir):
for root, dirs, files in os.walk(datadir):
for file in files:
tmpFile = os.path.join(root, file)
yield (root, tmpFile)
def copyAllFiles():
for paths in getFiles('data'):
root, tmpFile = paths
try:
updateGui('Copying %s...' % tmpFile)
# Create directory and copy
# Use str to convert from QString to plain python string
createDirectory(str(currentRoot + '/' + root))
shutil.copy(str(tmpFile), str(currentRoot + '/' + tmpFile))
except Exception as e:
updateGui("Could not copy '%s' because %s" % (tmpFile, e))
print("Could not copy '%s' because %s!" % (tmpFile, e))
def createRunScript(script, binary):
out = open(script, 'w')
data = """#!/bin/sh
%s -d %s/data
""" % (binary, currentRoot)
out.write(data)
os.chmod(script, stat.S_IRWXU)
# Copy paintown to paintown-bin
updateGui('Copying the paintown binary...')
try:
createDirectory(str(currentRoot))
createDirectory(str(binDirectory))
shutil.copy('paintown', str(currentRoot + '/' + 'paintown-bin'))
os.chmod(str(currentRoot + '/' + 'paintown-bin'), stat.S_IRWXU)
except Exception as e:
updateGui('Could not copy the paintown binary because %s!' % e)
print('Could not copy the paintown binary because %s!' % e)
updateGui('Creating run script in %s/%s' % (str(binDirectory), 'paintown'))
createRunScript('%s/paintown' % str(binDirectory), '%s/paintown-bin' % str(currentRoot))
copyAllFiles()
updateGui('Done!')
threads = []
class LambdaEvent(QObject):
# Define a custom signal
signal: pyqtSignal = pyqtSignal(object)
# Define a slot function
@pyqtSlot(object)
def process(self, arg):
arg()
def doBackground(func):
class Runner(QThread):
def __init__(self, event):
QThread.__init__(self)
self.event = event
def run(self):
print("Start thread..")
func(lambda arg: self.event.signal.emit(arg))
print("Ok done")
event = LambdaEvent()
event.signal.connect(lambda arg: event.process(arg))
n = Runner(event)
# Have to save threads from being GC'd, so just put it in a variable that no one will use
global threads
threads.append(n)
n.start()
return n
def updateFileList():
# Set data info
widget.textBrowser.clear()
def doit(location):
doBackground(lambda emitter: setDataFileInfo(location, widget.textBrowser, emitter))
doit(dataLocation[0])
# setDataFileInfo(widget.textBrowser)
# About
def about():
QMessageBox.about(widget, 'Paintown ', 'A simple installer built with PyQt')
# About Qt
def aboutQt():
QMessageBox.aboutQt(widget, 'About Qt')
# Check permissions on directories
def checkDirs():
binOk = False
dataOk = False
if os.access(binLocation[0], os.W_OK):
widget.binAccess.setText('<font color=\'green\'>Directory is writable.</font>')
binOk = True
else:
widget.binAccess.setText('<font color=\'red\'>Directory is <strong>NOT</strong> writable.</font>')
binOk = False
if os.access(dataLocation[0], os.W_OK):
widget.dataAccess.setText('<font color=\'green\'>Directory is writable.</font>')
dataOk = True
else:
widget.dataAccess.setText('<font color=\'red\'>Directory is <strong>NOT</strong> writable.</font>')
dataOk = False
if binOk and dataOk:
widget.next.setEnabled(True)
else:
widget.next.setEnabled(False)
return
# Back function
def backButton():
currentPage[0] -= 1
if currentPage[0] <= 0:
currentPage[0] = 0
widget.back.setEnabled(False)
widget.next.setEnabled(True)
elif currentPage[0] == 1:
widget.back.setEnabled(True)
elif currentPage[0] == 2:
widget.back.setEnabled(True)
elif currentPage[0] == 3:
widget.back.setEnabled(False)
elif currentPage[0] == 4:
widget.back.setEnabled(False)
widget.stackedWidget.setCurrentIndex(currentPage[0])
updatePageNumber()
# Forward function
def forwardButton():
currentPage[0] += 1
if currentPage[0] == 1:
widget.back.setEnabled(True)
checkDirs()
elif currentPage[0] == 3:
widget.back.setEnabled(False)
# disable forward until copy is complete
widget.next.setEnabled(False)
allowExit[0] = False
widget.stackedWidget.setCurrentIndex(currentPage[0])
doBackground(lambda emitter: copyData(dataLocation[0], binLocation[0], widget.installOutput, emitter))
widget.next.setEnabled(True)
widget.installLabel.setText('Complete.')
allowExit[0] = True
elif currentPage[0] == 4:
allowExit[0] = True
widget.next.clicked.disconnect(forwardButton)
widget.next.clicked.connect(widget.close)
widget.next.setText('&Exit')
widget.next.setShortcut('Alt+E')
widget.stackedWidget.setCurrentIndex(currentPage[0])
updatePageNumber()
# Select Binary Directory
def selectBinDirectory():
tempstr = QFileDialog.getExistingDirectory(widget, 'Please Select Binary Directory', binLocation[0],
options=QFileDialog.Option.ShowDirsOnly)
if not tempstr == '':
binLocation[0] = tempstr
widget.binDir.setText(tempstr)
checkDirs()
# Select Data Directory
def selectDataDirectory():
tempstr = QFileDialog.getExistingDirectory(widget, 'Please Select Data Directory', dataLocation[0],
options=QFileDialog.Option.ShowDirsOnly)
if not tempstr == '':
dataLocation[0] = tempstr
widget.dataDir.setText(tempstr)
# FIXME!!! Redo this thread
updateFileList()
# setDataFileInfo(widget.textBrowser)
checkDirs()
class InstallerWindow(QMainWindow):
def __init__(self, ui_file):
super().__init__()
self.load_interface(ui_file)
def closeEvent(self, event):
# Intercept the close event
reply = QMessageBox.question(self, 'Exit Confirmation',
"Are you sure you want to exit?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
event.accept() # Allow the close event to proceed
else:
event.ignore() # Ignore the close event
def load_interface(self, ui_file):
loadUi(ui_file, self)
self.back.setEnabled(False)
def setUpLookAndFeel(self):
self.setStyle(QStyleFactory.create("Cleanlooks"))
def show(self):
self.setUpLookAndFeel()
self.updateWidgetsWithNeededInfo()
self.pageIntroAndGraphic()
self.pageInstallLocations()
self.pageInstallInformation()
self.pageConsoleOutput()
self.pageFinished()
self.setupActions()
self.centerOnScreen()
super().show()
def updateWidgetsWithNeededInfo(self):
self.back.clicked.connect(backButton)
self.changeBinDir.clicked.connect(selectBinDirectory)
self.changeDataDir.clicked.connect(selectDataDirectory)
self.next.clicked.connect(forwardButton)
self.back.setShortcut('Alt+B')
self.next.setShortcut('Alt+N')
updateFileList()
updatePageNumber()
def pageIntroAndGraphic(self):
"""
Page 1 - Intro and graphic
"""
scene = QGraphicsScene()
scene.addPixmap(QPixmap('data/menu/paintown.png'))
self.WelcomePage.setScene(scene)
# widget.WelcomePage.scale(1,1)
self.WelcomePage.fitInView(0, 0, 94, 29)
self.WelcomePage.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.WelcomePage.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
def pageInstallLocations(self):
"""
Page 2 - Install Locations
"""
self.binDir.setText(binLocation[0])
self.dataDir.setText(dataLocation[0])
def pageInstallInformation(self):
"""
Page 3 - Install information
"""
pass
def pageConsoleOutput(self):
"""
Page 4 - Console Output
****FIXME**** Add file copy info and renable next button after complete
"""
pass
def pageFinished(self):
""""
Page 5 - Finished - Nothing to do
"""
pass
def confirm_exit(self):
self.close()
def setupActions(self):
# Set About info
menuAbout = QtGui.QAction('About Paintown', widget)
menuAbout.triggered.connect(about)
self.about.addAction(menuAbout)
menuAboutQt = QtGui.QAction('About Qt', widget)
menuAboutQt.triggered.connect(aboutQt)
self.about.addAction(menuAboutQt)
# Set Exit installer
exitAction = QtGui.QAction('Exit', widget)
exitAction.setShortcut('Alt+Q')
exitAction.setStatusTip('Quit Installer')
exitAction.triggered.connect(self.confirm_exit)
self.file.addAction(exitAction)
def centerOnScreen(self):
screen = QGuiApplication.primaryScreen().geometry()
size = widget.geometry()
self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)
def main():
app = QApplication(sys.argv)
global widget
widget = InstallerWindow("installer/installer.ui")
widget.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()