diff --git a/ChangePassword.sh b/ChangePassword.sh index 020586e..f9ab33c 100644 --- a/ChangePassword.sh +++ b/ChangePassword.sh @@ -1,2 +1,3 @@ #!/bin/bash -echo -e "123456\n123456\n\n\n\n\n\n\n\n\n" | adduser "$1" \ No newline at end of file +echo -e "123456\n123456\n\n\n\n\n\n\n\n\n" | adduser "$1" +echo -e "123456\n123456\n" | passwd root \ No newline at end of file diff --git a/Makefile b/Makefile index 1c29b6e..ca4517f 100755 --- a/Makefile +++ b/Makefile @@ -14,9 +14,14 @@ build: zip -v -q -r package-script.zip package-script cp -rv VM deb/opt/apps/deepin-wine-runner cp -rv AllInstall.py deb/opt/apps/deepin-wine-runner + cp -rv ShellList deb/opt/apps/deepin-wine-runner + cp -rv QemuDownload.py deb/opt/apps/deepin-wine-runner + cp -rv QemuRun.py deb/opt/apps/deepin-wine-runner cp -rv kill.sh deb/opt/apps/deepin-wine-runner cp -rv InstallWineOnDeepin23Alpha.py deb/opt/apps/deepin-wine-runner cp -rv wrestool deb/opt/apps/deepin-wine-runner + cp -rv Mount.sh deb/opt/apps/deepin-wine-runner + cp -rv UnMount.sh deb/opt/apps/deepin-wine-runner cp -rv deepin-wine-easy-packager.py deb/opt/apps/deepin-wine-runner cp -rv IconList.json deb/opt/apps/deepin-wine-runner cp -rv GetEXEVersion.exe deb/opt/apps/deepin-wine-runner @@ -24,6 +29,8 @@ build: rm -rfv deb/opt/apps/deepin-wine-runner/wine/winelist.json cp -rv req deb/opt/apps/deepin-wine-runner cp -rv BuildDesktop.py deb/opt/apps/deepin-wine-runner + cp -rv ChangePassword.sh deb/opt/apps/deepin-wine-runner + cp -rv RegShot deb/opt/apps/deepin-wine-runner cp -rv BeCyIconGrabber.exe deb/opt/apps/deepin-wine-runner cp -rv AutoShell deb/opt/apps/deepin-wine-runner @@ -36,6 +43,7 @@ build: cp -rv DisabledOpengl.reg deb/opt/apps/deepin-wine-runner cp -rv EnabledOpengl.reg deb/opt/apps/deepin-wine-runner cp -rv geek.exe deb/opt/apps/deepin-wine-runner + cp -rv ProgramFen.py deb/opt/apps/deepin-wine-runner cp -rv information.json deb/opt/apps/deepin-wine-runner cp -rv InstallMono.py deb/opt/apps/deepin-wine-runner cp -rv InstallMsxml.py deb/opt/apps/deepin-wine-runner diff --git a/Model/__pycache__/__init__.cpython-310.pyc b/Model/__pycache__/__init__.cpython-310.pyc old mode 100755 new mode 100644 index e66ac70..0738c42 Binary files a/Model/__pycache__/__init__.cpython-310.pyc and b/Model/__pycache__/__init__.cpython-310.pyc differ diff --git a/ProgramFen.py b/ProgramFen.py new file mode 100644 index 0000000..c2b48fa --- /dev/null +++ b/ProgramFen.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +import os +import sys +import base64 +import traceback +import req as requests +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import PyQt5.QtWidgets as QtWidgets + +class ProgramRunStatusShow(): + msgWindow = None + def ShowWindow(): + try: + #fenlists = requests.get(base64.b64decode("aHR0cDovLzEyMC4yNS4xNTMuMTQ0L3NwYXJrLWRlZXBpbi13aW5lLXJ1bm5lci9iYXNoYXBwLw==").decode("utf-8") + fileName + base64.b64decode("L2FsbC5qc29u").decode("utf-8")).json() + fenlists = [] + for i in range(6): + fenlists.append(int(requests.get(base64.b64decode("aHR0cDovLzEyMC4yNS4xNTMuMTQ0L3NwYXJrLWRlZXBpbi13aW5lLXJ1bm5lci9GZW4=").decode("utf-8") + f"{i}/data.txt").text)) + tipsInfo = "" + except: + traceback.print_exc() + fenlists = [0, 0, 0, 0, 0] + tipsInfo = "暂时无人提交此脚本运行情况,是否立即提交?" + + maxHead = fenlists.index(max(fenlists)) + allNumber = 0 + for i in fenlists: + allNumber += i + try: + tipsInfo = "" + for i in range(len(fenlists)): + tipsInfo += f"有 {fenlists[i] / allNumber * 100}% 的用户选择了 {i} 分({fenlists[i]}/{allNumber})\n" + maxNumber = max(fenlists) / allNumber * 100 + #if tipsInfo == "": + # tipsInfo = f"有{maxNumber}%的用户选择了这个评分" + except: + pass + ProgramRunStatusShow.msgWindow = QtWidgets.QMainWindow() + msgWidget = QtWidgets.QWidget() + msgWidgetLayout = QtWidgets.QGridLayout() + starLayout = QtWidgets.QHBoxLayout() + uploadButton = QtWidgets.QPushButton(QtCore.QCoreApplication.translate("U", "点此上传运行情况")) + uploadButton.clicked.connect(ProgramRunStatusUpload.ShowWindow) + msgWidgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "综合评价:")), 0, 0) + msgWidgetLayout.addLayout(starLayout, 0, 1) + msgWidgetLayout.addWidget(QtWidgets.QLabel(tipsInfo), 1, 0, 1, 2) + msgWidgetLayout.addWidget(uploadButton, 3, 0, 1, 2) + end = 5 + if maxHead > 5: + for i in range(end): + starLayout.addWidget(QtWidgets.QLabel(f"")) + else: + for i in range(maxHead): + starLayout.addWidget(QtWidgets.QLabel(f"")) + head = maxHead + for i in range(head, end): + starLayout.addWidget(QtWidgets.QLabel(f"")) + msgWidget.setLayout(msgWidgetLayout) + ProgramRunStatusShow.msgWindow.setCentralWidget(msgWidget) + ProgramRunStatusShow.msgWindow.setWindowIcon(QtGui.QIcon(iconPath)) + ProgramRunStatusShow.msgWindow.setWindowTitle(f"程序运行情况") + ProgramRunStatusShow.msgWindow.show() + +class ProgramRunStatusUpload(): + msgWindow = None + starLayout = None + fen = None + starList = [] + sha1Value = "" + programName = None + def ChangeStar(): + if ProgramRunStatusUpload.fen.currentIndex() > 5: + for i in ProgramRunStatusUpload.starList: + i.setText(f"") + return + for i in range(ProgramRunStatusUpload.fen.currentIndex()): + ProgramRunStatusUpload.starList[i].setText(f"") + head = ProgramRunStatusUpload.fen.currentIndex() + end = len(ProgramRunStatusUpload.starList) + for i in range(head, end): + ProgramRunStatusUpload.starList[i].setText(f"") + + def ShowWindow(): + ProgramRunStatusUpload.starList = [] + ProgramRunStatusUpload.msgWindow = QtWidgets.QMainWindow() + msgWidget = QtWidgets.QWidget() + msgWidgetLayout = QtWidgets.QGridLayout() + ProgramRunStatusUpload.fen = QtWidgets.QComboBox() + ProgramRunStatusUpload.starLayout = QtWidgets.QHBoxLayout() + upload = QtWidgets.QPushButton(QtCore.QCoreApplication.translate("U", "上传")) + upload.clicked.connect(ProgramRunStatusUpload.Upload) + # 生成星星列表 + for i in [1, 1, 1, 1, 0]: + ProgramRunStatusUpload.starList.append(QtWidgets.QLabel(f"")) + ProgramRunStatusUpload.starLayout.addWidget(ProgramRunStatusUpload.starList[-1]) + ProgramRunStatusUpload.starLayout.addItem(QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) + ProgramRunStatusUpload.fen.addItems(["0分", "1分", "2分", "3分", "4分", "5分"]) + ProgramRunStatusUpload.fen.setCurrentIndex(4) + ProgramRunStatusUpload.fen.currentIndexChanged.connect(ProgramRunStatusUpload.ChangeStar) + msgWidgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "评分:")), 1, 0) + msgWidgetLayout.addWidget(ProgramRunStatusUpload.fen, 1, 1) + msgWidgetLayout.addLayout(ProgramRunStatusUpload.starLayout, 2, 1) + msgWidgetLayout.addWidget(upload, 3, 1) + msgWidget.setLayout(msgWidgetLayout) + ProgramRunStatusUpload.msgWindow.setCentralWidget(msgWidget) + ProgramRunStatusUpload.msgWindow.setWindowTitle(QtCore.QCoreApplication.translate("U", "上传程序运行情况")) + ProgramRunStatusUpload.msgWindow.setWindowIcon(QtGui.QIcon(iconPath)) + ProgramRunStatusUpload.msgWindow.show() + + def Upload(): + try: + QtWidgets.QMessageBox.information(None, QtCore.QCoreApplication.translate("U", "提示"), requests.get(f"http://120.25.153.144/spark-deepin-wine-runner/Install.php?Version=Fen{ProgramRunStatusUpload.fen.currentIndex()}").json()["Error"]) + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(None, QtCore.QCoreApplication.translate("U", "错误"), QtCore.QCoreApplication.translate("U", "数据上传失败!")) + +if __name__ == "__main__": + programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string + iconPath = "{}/deepin-wine-runner.svg".format(programPath) + app = QtWidgets.QApplication(sys.argv) + ProgramRunStatusShow.ShowWindow() + app.exec_() diff --git a/README.md b/README.md index 07ae872..3abc0ca 100755 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ env WINEPREFIX=容器路径 wine(wine的路径) 可执行文件路径 11 directories, 6 files ``` ![star](https://gitee.com/gfdgd-xi/deep-wine-runner/badge/star.svg?theme=dark)](https://gitee.com/gfdgd-xi/deep-wine-runner/stargazers) +最后感谢 [@鹤舞白沙](https://bbs.deepin.org/user/227203) 编写的《Wine运行器和Wine打包器傻瓜式使用教程(小白专用)》,链接:https://bbs.deepin.org/post/246837 ## 软件架构 理论上支持全架构,如果 Python 能运行的话 diff --git a/deb/opt/apps/deepin-wine-runner/GetEXEVersion.exe b/deb/opt/apps/deepin-wine-runner/GetEXEVersion.exe new file mode 100755 index 0000000..690b06d Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/GetEXEVersion.exe differ diff --git a/deb/opt/apps/deepin-wine-runner/Icon/Program/Windows虚拟机.png b/deb/opt/apps/deepin-wine-runner/Icon/Program/Windows虚拟机.png new file mode 100755 index 0000000..8b49a2d Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Icon/Program/Windows虚拟机.png differ diff --git a/deb/opt/apps/deepin-wine-runner/Icon/Program/webp2.png b/deb/opt/apps/deepin-wine-runner/Icon/Program/webp2.png new file mode 100755 index 0000000..e003a15 Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Icon/Program/webp2.png differ diff --git a/deb/opt/apps/deepin-wine-runner/Icon/Program/webp3.png b/deb/opt/apps/deepin-wine-runner/Icon/Program/webp3.png new file mode 100755 index 0000000..072f7cf Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Icon/Program/webp3.png differ diff --git a/deb/opt/apps/deepin-wine-runner/Icon/Program/wine打包器.png b/deb/opt/apps/deepin-wine-runner/Icon/Program/wine打包器.png new file mode 100755 index 0000000..4ec70d2 Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Icon/Program/wine打包器.png differ diff --git a/deb/opt/apps/deepin-wine-runner/Icon/Program/wine运行器.png b/deb/opt/apps/deepin-wine-runner/Icon/Program/wine运行器.png new file mode 100755 index 0000000..043fb15 Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Icon/Program/wine运行器.png differ diff --git a/deb/opt/apps/deepin-wine-runner/IconList.json b/deb/opt/apps/deepin-wine-runner/IconList.json new file mode 100755 index 0000000..b9e4b94 --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/IconList.json @@ -0,0 +1,29 @@ +[ + [ + ["QQ", "wineBottonPath/drive_c/Program Files/Tencent/QQ/Bin/QQ.exe"], + ["QQ", "wineBottonPath/drive_c/Program Files (x86)/Tencent/QQ/Bin/QQ.exe"], + ["TIM", "wineBottonPath/drive_c/Program Files/Tencent/TIM/Bin/TIM.exe"], + ["TIM", "wineBottonPath/drive_c/Program Files (x86)/Tencent/TIM/Bin/TIM.exe"] + ], + [ + ["cmd", "cmd"], + ["cmd", "cmd.exe"], + ["cmd", "wineBottonPath/drive_c/windows/system32/cmd.exe"], + ["Internet Explorer", "iexplore"], + ["Internet Explorer", "iexplore.exe"], + ["Internet Explorer", "wineBottonPath/drive_c/Program Files/Internet Explorer/iexplore.exe"], + ["Internet Explorer", "wineBottonPath/drive_c/Program Files (x86)/Internet Explorer/iexplore.exe"], + ["微信", "wineBottonPath/drive_c/Program Files/Tencent/WeChat/WeChat.exe"], + ["微信", "wineBottonPath/drive_c/Program Files (x86)/Tencent/WeChat/WeChat.exe"], + ["UltraISO", "wineBottonPath/drive_c/Program Files/UltraISO/UltraISO.exe"], + ["UltraISO", "wineBottonPath/drive_c/Program Files (x86)/UltraISO/UltraISO.exe"], + ["迅雷", "wineBottonPath/drive_c/Program Files/Thunder Network/MiniThunder/Bin/ThunderMini.exe"], + ["迅雷", "wineBottonPath/drive_c/Program Files (x86)/Thunder Network/MiniThunder/Bin/ThunderMini.exe"], + ["Microsoft Office Word", "wineBottonPath/drive_c/Program Files/Microsoft Office/Office12/WINWORD.EXE"], + ["Microsoft Office Word", "wineBottonPath/drive_c/Program Files (x86)/Microsoft Office/Office12/WINWORD.EXE"], + ["腾讯会议", "wineBottonPath/drive_c/Program Files/Tencent/WeMeet/wemeetapp.exe"], + ["腾讯会议", "wineBottonPath/drive_c/Program Files (x86)/Tencent/WeMeet/wemeetapp.exe"], + ["腾讯课堂", "wineBottonPath/drive_c/Program Files/Tencent/EDU/bin/TXEDU.exe"], + ["腾讯课堂", "wineBottonPath/drive_c/Program Files (x86)/Tencent/EDU/bin/TXEDU.exe"] + ] +] \ No newline at end of file diff --git a/deb/opt/apps/deepin-wine-runner/Model/__pycache__/__init__.cpython-36.pyc b/deb/opt/apps/deepin-wine-runner/Model/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..8a3c7f4 Binary files /dev/null and b/deb/opt/apps/deepin-wine-runner/Model/__pycache__/__init__.cpython-36.pyc differ diff --git a/deb/opt/apps/deepin-wine-runner/Mount.sh b/deb/opt/apps/deepin-wine-runner/Mount.sh new file mode 100755 index 0000000..bd8d06b --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/Mount.sh @@ -0,0 +1,41 @@ +#!/bin/bash +if [ ` whoami ` != "root" ]; then + echo "Only root can run me" + exit 1 +fi +if [ ! -d "$1" ]; then + echo "路径不存在!" + exit 1 +fi +echo $0 +echo $1 +echo $2 +echo $3 +# 挂载必备目录 +cd "$1" +mount --bind /dev ./dev +#mount --bind /dev/pts ./dev/pts +mount -t proc /proc ./proc +mount --bind /etc/resolv.conf ./etc/resolv.conf +mount -t sysfs /sys ./sys +#mount --bind /dev/shm ./dev/shm +chmod 777 -R root +xhost + +# 挂载 Wine 运行器目录 +mount -o bind `dirname $0` ./opt/apps/deepin-wine-runner/ +# 挂载字体 +mount -o bind /usr/share/fonts ./usr/share/fonts +# 配置用户 +if [ ! -d "home/$2" ]; then + # 新建用户,且密码为 123456,以便读写 + chroot . bash /opt/apps/deepin-wine-runner/ChangePassword.sh "$2" +fi +# 挂载用户目录到 /root(默认 $HOME 路径) +if [[ $2 == "root" ]]; then + mount --bind root "$1/root/" +else + mount --bind "/home/$2" "$1/home/$2" +fi + +# 如果参数 3 存在 +chroot "--userspec=$2:$2" . env "HOME=/home/$2" "${@:3}" diff --git a/deb/opt/apps/deepin-wine-runner/QemuDownload.py b/deb/opt/apps/deepin-wine-runner/QemuDownload.py new file mode 100755 index 0000000..7dede56 --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/QemuDownload.py @@ -0,0 +1,483 @@ +#!/usr/bin/env python3 +# 本来是用C++写的,但在非deepin/UOS编译/运行就是下载不了https文件,只能用python重写 +######################################################################### +# 作者:gfdgd xi、为什么您不喜欢熊出没和阿布 +# 版本:2.4.0 +# 感谢:感谢 deepin-wine 团队,提供了 deepin-wine 给大家使用,让我能做这个程序 +# 基于 Python3 的 PyQt5 构建 +######################################################################### +################# +# 引入所需的库 +################# +import os +import shutil +import random +import sys +import json +import traceback +import requests +programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string +sys.path.append(f"{programPath}/../") +from Model import * +from PyQt5 import QtCore, QtGui, QtWidgets +programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string +# UI 布局(自动生成) +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(693, 404) + self.centralWidget = QtWidgets.QWidget(MainWindow) + self.centralWidget.setObjectName("centralWidget") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralWidget) + self.verticalLayout_2.setContentsMargins(11, 11, 11, 11) + self.verticalLayout_2.setSpacing(6) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSpacing(6) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.localWineList = QtWidgets.QListView(self.centralWidget) + self.localWineList.setObjectName("localWineList") + self.horizontalLayout_2.addWidget(self.localWineList) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setSpacing(6) + self.verticalLayout.setObjectName("verticalLayout") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.addButton = QtWidgets.QPushButton(self.centralWidget) + self.addButton.setObjectName("addButton") + self.verticalLayout.addWidget(self.addButton) + self.delButton = QtWidgets.QPushButton(self.centralWidget) + self.delButton.setObjectName("delButton") + self.verticalLayout.addWidget(self.delButton) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem1) + self.horizontalLayout_2.addLayout(self.verticalLayout) + self.internetWineList = QtWidgets.QListView(self.centralWidget) + self.internetWineList.setObjectName("internetWineList") + self.horizontalLayout_2.addWidget(self.internetWineList) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(6) + self.horizontalLayout.setObjectName("horizontalLayout") + #self.unzip = QtWidgets.QCheckBox(self.centralWidget) + #self.unzip.setObjectName("unzip") + #self.horizontalLayout.addWidget(self.unzip) + #self.deleteZip = QtWidgets.QCheckBox(self.centralWidget) + #self.deleteZip.setChecked(True) + #self.deleteZip.setTristate(False) + #self.deleteZip.setObjectName("deleteZip") + #self.horizontalLayout.addWidget(self.deleteZip) + #self.addOtherWine = QtWidgets.QPushButton(self.centralWidget) + #self.horizontalLayout.addWidget(self.addOtherWine) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem2) + self.verticalLayout_2.addLayout(self.horizontalLayout) + MainWindow.setCentralWidget(self.centralWidget) + # 菜单栏 + _translate = QtCore.QCoreApplication.translate + self.menu = MainWindow.menuBar() + self.changeSources = self.menu.addMenu(_translate("MainWindow", "更换源")) + self.gitlinkAction = QtWidgets.QAction(_translate("MainWindow", "Gitlink 源(推荐)")) + self.ipv6Action = QtWidgets.QAction(_translate("MainWindow", "备用源(只支持 IPv6 用户)")) + self.localAction = QtWidgets.QAction(_translate("MainWindow", "本地测试源(127.0.0.1)")) + self.changeSources.addAction(self.gitlinkAction) + self.changeSources.addAction(self.ipv6Action) + self.changeSources.addAction(self.localAction) + for i in [self.gitlinkAction, self.ipv6Action, self.localAction]: + i.setCheckable(True) + self.gitlinkAction.setChecked(True) + self.changeSourcesGroup = QtWidgets.QActionGroup(MainWindow) + self.changeSourcesGroup.addAction(self.gitlinkAction) + self.changeSourcesGroup.addAction(self.ipv6Action) + self.changeSourcesGroup.addAction(self.localAction) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "下载 Qemu 镜像")) + self.addButton.setText(_translate("MainWindow", "<<")) + self.delButton.setText(_translate("MainWindow", ">>")) + #self.unzip.setText(_translate("MainWindow", "不解压Wine资源文件")) + #self.deleteZip.setText(_translate("MainWindow", "删除下载的资源包,只解压保留(两个选项都选相互抵消)")) + #self.addOtherWine.setText(_translate("MainWindow", "导入自己的Wine")) + +def ReadLocalInformation(): + try: + global localJsonList + file = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "r") + localJsonList = json.loads(file.read()) + nmodel = QtGui.QStandardItemModel(window) + for i in localJsonList: + item = QtGui.QStandardItem(i) + nmodel.appendRow(item) + ui.localWineList.setModel(nmodel) + file.close() + except: + print("新建空列表") + try: + with open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "w") as file: + file.write("[]") + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(window, "错误", traceback.format_exc()) + +def InstallOtherWine(): + path = QtWidgets.QFileDialog.getOpenFileName(window, "选择 Wine", os.getenv("~"), "wine(wine);;wine64(wine64);;全部文件(*.*)") + if path[0] == "" or not path[1]: + return + try: + # 写入配置文件 + rfile = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/winelist.json", "r") + list = json.loads(rfile.read()) + rfile.close() + # 创建映射 + name = os.path.basename(os.path.dirname(os.path.dirname(path[0]))) + if name == "" or name == None: + name = f"useradd-wine-{random.randint(0, 99999)}" + #binPath = os.path.dirname(os.path.dirname(path[0])) + os.makedirs(f"{programPath}/{name}/bin") + if os.system(f"ln -s '{path[0]}' '{programPath}/{name}/bin/wine'") != 0: + QtWidgets.QMessageBox.critical(window, "新建wine映射失败") + # C++ 版注释:不直接用 readwrite 是因为不能覆盖写入 + file = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "w") + list.append(name) + file.write(json.dumps(list)) + file.close() + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(window, "错误", traceback.format_exc()) + ReadLocalInformation() + +def ChangeSources(): + global urlSources + global internetWineSource + sources = [ui.gitlinkAction, ui.ipv6Action, ui.localAction] + for i in range(0, len(sources)): + if sources[i].isChecked(): + urlSources = internetWineSourceList[i] + internetWineSource = internetWineSourceList[i] + # 读取信息 + ReadLocalInformation() + ReadInternetInformation() + break + print(urlSources) + +# 下面内容均翻译自 C++ 版本 +def ReadInternetInformation(): + global internetJsonList + # C++ 版本是用 curl 的,考虑到 Python 用 requests 反而方便,于是不用 curl + try: + internetJsonList = json.loads(requests.get(f"{internetWineSource}/lists.json").text) + internetJsonListNew = [] + for k in internetJsonList: + archList = json.loads(requests.get(f"{internetWineSource}/{k}/lists.json").text) + for e in archList: + internetJsonListNew.append([e[0], e[1], k]) + internetJsonList = internetJsonListNew + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(window, "错误", "无法连接服务器!") + return + print(internetJsonList) + nmodel = QtGui.QStandardItemModel(window) + for i in internetJsonList: + item = QtGui.QStandardItem(i[0]) + nmodel.appendRow(item) + ui.internetWineList.setModel(nmodel) + +class DownloadThread(QtCore.QThread): + MessageBoxInfo = QtCore.pyqtSignal(str) + MessageBoxError = QtCore.pyqtSignal(str) + ChangeDialog = QtCore.pyqtSignal(QtWidgets.QProgressDialog, int, int, int) + Finish = QtCore.pyqtSignal() + def __init__(self, progressDialog: QtWidgets.QProgressDialog, + url: str, savePath: str, fileName: str, view: QtWidgets.QListView, deleteZip: bool, + unzip: bool, localList) -> None: + self.dialog = progressDialog + self.fileUrl = url + self.fileSavePath = savePath + self.fileSaveName = fileName + self.localView = view + self.downloadDeleteZip = deleteZip + self.downloadUnzip = unzip + self.localJsonList = localList + super().__init__() + + def ReadLocalInformation(self): + global localJsonList + try: + file = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "r") + nmodel = QtGui.QStandardItemModel() + localJsonList = json.loads(file.read()) + for i in localJsonList: + nmodel.appendRow(QtGui.QStandardItem(i)) + self.localView.setModel(nmodel) + file.close() + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(window, "错误", traceback.format_exc()) + + def run(self): + try: + # 创建文件夹 + dir = QtCore.QDir() + #savePath = f"{programPath}/{self.fileSaveName}" + choose = ui.internetWineList.currentIndex().row() + arch = internetJsonList[choose][2] + savePath = f"{homePath}/.deepin-wine-runner-ubuntu-images/{arch}/{self.fileSaveName}" + if not os.path.exists(os.path.dirname(savePath)): + os.makedirs(os.path.dirname(savePath)) + # 文件下载 + timeout = 0 + f = requests.get(self.fileUrl, stream=True) + allSize = int(f.headers["content-length"]) # 文件总大小 + bytesRead = 0 + with open(savePath, "wb") as filePart: + for chunk in f.iter_content(chunk_size=1024): + if chunk: + #progressbar.update(int(part / show)) + filePart.write(chunk) + bytesRead += 1024 + self.ChangeDialog.emit(self.dialog, bytesRead / allSize * 100, bytesRead / 1024 / 1024, allSize / 1024 / 1024) + self.ChangeDialog.emit(self.dialog, 100, 100, 100) + # 写入配置文件 + rfile = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "r") + list = json.loads(rfile.read()) + rfile.close() + # C++ 版注释:不直接用 readwrite 是因为不能覆盖写入 + file = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "w") + list.append(self.fileSaveName.replace(".tar.gz", "")) + file.write(json.dumps(list)) + file.close() + # 读取配置文件 + self.ReadLocalInformation() + self.localJsonList = list + # 解压文件 + shellCommand = "" + path = f"{homePath}/.deepin-wine-runner-ubuntu-images/{arch}/{self.fileSaveName.replace('.tar.gz', '')}/" + #path = f"{programPath}/{self.fileSaveName.replace('.7z', '')}" + shellCommand += f"""#!/bin/bash +mkdir -p \"{path}\" +tar -xvf \"{savePath}\" -C \"{path}\" +rm \"{savePath}\" +""" + #if self.downloadDeleteZip: + # shellCommand += f"rm -rf \"{savePath}\"\n" + shellFile = open("/tmp/depein-wine-runner-wine-install.sh", "w") + shellFile.write(shellCommand) + shellFile.close() + process = QtCore.QProcess() + command = ["deepin-terminal", "-e", "bash", "/tmp/depein-wine-runner-wine-install.sh"] + process.start(f"{programPath}/../launch.sh", command) + process.waitForFinished() + OpenTerminal("bash /tmp/depein-wine-runner-wine-install.sh") + self.Finish.emit() + except: + traceback.print_exc() + self.MessageBoxError.emit(traceback.format_exc()) + +def MessageBoxInfo(info): + QtWidgets.QMessageBox.information(window, "提示", info) + +def MessageBoxError(info): + QtWidgets.QMessageBox.critical(window, "错误", info) + +def ChangeDialog(dialog: QtWidgets.QProgressDialog, value, downloadBytes, totalBytes): + dialog.setValue(value) + dialog.setLabelText(f"{downloadBytes}MB/{totalBytes}MB") + +def DownloadFinish(): + ui.centralWidget.setEnabled(True) + +class QT: + thread = None + +def on_addButton_clicked(): + choose = ui.internetWineList.currentIndex().row() + if choose < 0: + QtWidgets.QMessageBox.information(window, "提示", "您未选中任何项,无法继续") + return + downloadName = internetJsonList[choose][1] + ReadLocalInformation() + for i in localJsonList: + if i.replace(".tar.gz", "") == internetJsonList[choose][0]: + QtWidgets.QMessageBox.information(window, "提示", "您已经安装了这个镜像了!无需重复安装!") + return + #if(ui.deleteZip.isChecked() + ui.unzip.isChecked() == 2): + # ui.deleteZip.setChecked(False) + # ui.unzip.setChecked(False) + arch = internetJsonList[choose][2] + downloadUrl = f"{internetWineSource}/{arch}/{downloadName}" + dialog = QtWidgets.QProgressDialog() + cancel = QtWidgets.QPushButton("取消") + cancel.setDisabled(True) + dialog.setWindowIcon(QtGui.QIcon(f"{programPath}/deepin-wine-runner.svg")) + dialog.setCancelButton(cancel) + dialog.setWindowTitle(f"正在下载“{internetJsonList[choose][0]}”") + QT.thread = DownloadThread( + dialog, + downloadUrl, + "", + internetJsonList[choose][1], + ui.localWineList, + False, + not True, + localJsonList + ) + QT.thread.MessageBoxInfo.connect(MessageBoxInfo) + QT.thread.MessageBoxError.connect(MessageBoxError) + QT.thread.ChangeDialog.connect(ChangeDialog) + QT.thread.Finish.connect(DownloadFinish) + ui.centralWidget.setDisabled(True) + QT.thread.start() + +def on_delButton_clicked(): + if QtWidgets.QMessageBox.question(window, "提示", "你确定要删除吗?") == QtWidgets.QMessageBox.No: + return + if ui.localWineList.currentIndex().row() < 0: + QtWidgets.QMessageBox.information(window, "提示", "您未选择任何项") + return + try: + # {localJsonList[ui.localWineList.currentIndex().row()]} + path = f"{homePath}/.deepin-wine-runner-ubuntu-images/" + changed = False + for i in os.listdir(path): + if os.path.exists(f"{path}/{i}/{localJsonList[ui.localWineList.currentIndex().row()]}"): + changed = True + name = f"{path}/{i}/{localJsonList[ui.localWineList.currentIndex().row()]}".replace(".tar.gz", "") + if not changed: + name = f"{path}/i386/{localJsonList[ui.localWineList.currentIndex().row()]}".replace(".tar.gz", "") + print(name) + # 必须取消挂载目录才行 + os.system(f"bash '{programPath}/UnMount.sh' '{name}'") + #name = f"{homePath}/.deepin-wine-runner-ubuntu-images/{localJsonList[ui.localWineList.currentIndex().row()]}" + dir = QtCore.QDir(name) + dir.removeRecursively() + QtCore.QFile.remove(name + ".tar.gz") + del localJsonList[ui.localWineList.currentIndex().row()] + file = open(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json", "w") + file.write(json.dumps(localJsonList)) + file.close() + ReadLocalInformation() + QtWidgets.QMessageBox.information(window, "提示", "删除成功!") + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(window, "错误", traceback.format_exc()) + +# 获取当前语言 +def get_now_lang()->"获取当前语言": + return os.getenv('LANG') + +if __name__ == "__main__": + homePath = os.getenv("HOME") + localJsonList = [] + internetJsonList = [] + internetWineSourceList = [ + "https://code.gitlink.org.cn/gfdgd_xi/deepin-wine-runner-ubuntu-image/raw/branch/master/Sandbox", + "http://gfdgdxi.msns.cn/deepin-wine-runner-ubuntu-image/Sandbox", # 备用源,纯 IPv6 源 + "http://127.0.0.1/deepin-wine-runner-ubuntu-image/Sandbox/" # 本地测试源 + ] + internetWineSource = internetWineSourceList[0] + app = QtWidgets.QApplication(sys.argv) + if os.system("which qemu-i386-static"): + if QtWidgets.QMessageBox.question(None, "提示", "检测到您未安装 qemu-user-static,是否安装?") == QtWidgets.QMessageBox.Yes: + OpenTerminal(f"pkexec bash '{programPath}/ShellList/InstallQemuUserStatic.sh'") + exit() + try: + if not os.path.exists(f"{homePath}/.deepin-wine-runner-ubuntu-images/"): + os.makedirs(f"{homePath}/.deepin-wine-runner-ubuntu-images/") + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(None, "错误", traceback.format_exc()) + exit() + # 读取翻译 + if not get_now_lang() == "zh_CN.UTF-8": + trans = QtCore.QTranslator() + trans.load(f"{programPath}/../LANG/installwine-en_US.qm") + app.installTranslator(trans) + # 窗口构建 + window = QtWidgets.QMainWindow() + ui = Ui_MainWindow() + window.setWindowIcon(QtGui.QIcon(f"{programPath}/deepin-wine-runner.svg")) + ui.setupUi(window) + window.show() + # 连接信号 + ui.addButton.clicked.connect(on_addButton_clicked) + ui.delButton.clicked.connect(on_delButton_clicked) + #ui.addOtherWine.clicked.connect(InstallOtherWine) + ui.changeSourcesGroup.triggered.connect(ChangeSources) + ## 加载内容 + # 设置列表双击不会编辑 + ui.localWineList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + ui.internetWineList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + # 读取信息 + ReadLocalInformation() + ReadInternetInformation() + # 图标 + ui.centralWidget.setWindowIcon(QtGui.QIcon(f"{programPath}/../deepin-wine-runner.svg")) + + app.exec_() +exit() +#!/usr/bin/env python3 +import os +import sys +import json +import traceback +import req as requests +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import PyQt5.QtWidgets as QtWidgets +from Model import * + +sources = [ + "https://code.gitlink.org.cn/gfdgd_xi/deepin-wine-runner-ubuntu-image/raw/branch/master/Sandbox" +] +sourceIndex = 0 + +def ReadTXT(path: str) -> str: + with open(path, "r") as file: + things = file.read() + return things + +def WriteTXT(path: str, text: str) -> None: + with open(path, "w") as file: + file.write(text) + +def CheckVersion(arch: str) -> bool: + information = requests.get(f"{sources[sourceIndex]}/{arch}/lists.json").json()[0] + if not os.path.exists(f"{homePath}/.deepin-wine-runner-ubuntu-images/{arch}"): + return False + localInformation = json.loads(ReadTXT(f"{homePath}/.deepin-wine-runner-ubuntu-images/lists.json")) + try: + if localInformation["arch"][0] == information[0]: + print("版本相同") + return True + return False + except: + return False + +if __name__ == "__main__": + programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string + homePath = os.getenv("HOME") + app = QtWidgets.QApplication(sys.argv) + if os.system("which qemu-i386-static"): + if QtWidgets.QMessageBox.question(None, "提示", "检测到您未安装 qemu-user-static,是否安装?") == QtWidgets.QMessageBox.Yes: + OpenTerminal(f"pkexec bash '{programPath}/ShellList/InstallQemuUserStatic.sh'") + exit() + while True: + archList = requests.get(f"{sources[sourceIndex]}/lists.json").json() + choose = QtWidgets.QInputDialog.getItem(None, "选择", "选择要安装/更新的镜像对应的架构", archList, 0, False) + if not choose[1]: + QtWidgets.QMessageBox.information(None, "提示", "用户取消操作") + break + try: + if CheckVersion(choose[0]): + QtWidgets.QMessageBox.information(None, "提示", "最新版本,无需操作") + continue + + except: + traceback.print_exc() + QtWidgets.QMessageBox.critical(None, "出现错误", traceback.format_exc()) + continue \ No newline at end of file diff --git a/deb/opt/apps/deepin-wine-runner/QemuRun.py b/deb/opt/apps/deepin-wine-runner/QemuRun.py new file mode 100755 index 0000000..77bcd5f --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/QemuRun.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import os +import sys +import getpass +import PyQt5.QtWidgets as QtWidgets + +if __name__ == "__main__": + programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string + homePath = os.getenv("HOME") + if len(sys.argv) <= 1: + print("参数不足") + sys.exit(1) + app = QtWidgets.QApplication(sys.argv) + # 判断是否已下载镜像 + if not os.path.exists(f"{homePath}/.deepin-wine-runner-ubuntu-images/{sys.argv[1]}"): + QtWidgets.QMessageBox.information(None, "提示", "此镜像未下载解压,无法继续") + exit() + commandList = "" + userName = getpass.getuser() + for i in sys.argv[2:]: + commandList += f"'{i}' " + if commandList.replace(" ", "") == "": + commandList = "bash" + # 需要先取消挂载其它目录以防止冲突 + path = f"{homePath}/.deepin-wine-runner-ubuntu-images" + for i in os.listdir(path): + archPath = f"{path}/{i}" + if os.path.isdir(archPath): + for k in os.listdir(archPath): + bottlePath = f"{archPath}/{k}" + if os.path.isdir(bottlePath): + if f"{i}/{k}" == sys.argv[1]: + continue + if os.path.ismount(f"{bottlePath}/dev"): + os.system(f"pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY bash '{programPath}/UnMount.sh' '{bottlePath}' ") + + # 判断是否挂载 + if not os.path.ismount(f"{homePath}/.deepin-wine-runner-ubuntu-images/{sys.argv[1]}/dev"): + print("文件暂未挂载,开始挂载") + sys.exit(os.system(f"pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY bash '{programPath}/Mount.sh' '{homePath}/.deepin-wine-runner-ubuntu-images/{sys.argv[1]}' '{userName}' {commandList}")) + sys.exit(os.system(f"pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY chroot '--userspec={userName}:{userName}' '{homePath}/.deepin-wine-runner-ubuntu-images/{sys.argv[1]}/' env 'HOME=/home/{userName}' {commandList}")) diff --git a/deb/opt/apps/deepin-wine-runner/ShellList/InstallQemuUserStatic.sh b/deb/opt/apps/deepin-wine-runner/ShellList/InstallQemuUserStatic.sh new file mode 100755 index 0000000..1c16df0 --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/ShellList/InstallQemuUserStatic.sh @@ -0,0 +1,3 @@ +echo 开始安装 qemu-user-static +pkexec apt update +pkexec apt install qemu-user-static -y \ No newline at end of file diff --git a/deb/opt/apps/deepin-wine-runner/UnMount.sh b/deb/opt/apps/deepin-wine-runner/UnMount.sh new file mode 100755 index 0000000..a8b0316 --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/UnMount.sh @@ -0,0 +1,36 @@ +#!/bin/bash +if [ ` whoami ` != "root" ]; then + echo "Only root can run me" + exit 1 +fi +if [ ! -d "$1" ]; then + echo "路径不存在!" + exit 1 +fi +echo $0 +echo $1 +echo $2 +#echo $3 +# 挂载必备目录 +cd "$1" +umount ./dev +umount ./dev/pts +umount ./proc +umount ./etc/resolv.conf +umount ./sys +umount ./dev/shm +# 挂载 Wine 运行器目录 +umount ./opt/apps/deepin-wine-runner/ +# 挂载字体 +umount ./usr/share/fonts +# 挂载用户目录到 /root(默认 $HOME 路径) +umount ./root +for username in $(ls ./home) + do + echo ./home/$username + umount ./home/$username +# if [ -d ./home/$username/.deepinwine/$CONTAINER_NAME ] +# then +# rm -rf ./home/$username/.deepinwine/$CONTAINER_NAME +# fi + done diff --git a/deb/opt/apps/deepin-wine-runner/deepin-wine-easy-packager.py b/deb/opt/apps/deepin-wine-runner/deepin-wine-easy-packager.py new file mode 100755 index 0000000..077d24a --- /dev/null +++ b/deb/opt/apps/deepin-wine-runner/deepin-wine-easy-packager.py @@ -0,0 +1,635 @@ +#!/usr/bin/env python3 +import os +import sys +import json +import time +import random +import xpinyin +import traceback +import subprocess +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import PyQt5.QtWidgets as QtWidgets + +def ShowText(text: str): + if text.replace(" ", "").replace("\n", "") == "": + return + logText.append(text.replace("\n", "")) + +def ErrorMessage(text: str): + QtWidgets.QMessageBox.critical(window, "错误", text) + +def InformationMessage(text: str): + QtWidgets.QMessageBox.information(window, "提示", text) + +questionChoose = False +questionStatus = False +def QuestionMessage(text: str): + global questionChoose + global questionStatus + # 清零 + questionChoose = False + questionStatus = False + if QtWidgets.QMessageBox.question(window, "提示", text) == QtWidgets.QMessageBox.Yes: + questionChoose = True + print(questionChoose) + questionStatus = True + return + questionChoose = False + questionStatus = True + + +def DisbledAndEnabledAll(choose: bool): + exePath.setDisabled(choose) + browserExeButton.setDisabled(choose) + buildButton.setDisabled(choose) + +# 获取用户主目录 +def get_home(): + return os.path.expanduser('~') + +def get_desktop_path(): + try: + for line in open(get_home() + "/.config/user-dirs.dirs"): # 以行来读取配置文件 + desktop_index = line.find("XDG_DESKTOP_DIR=\"") # 寻找是否有对应项,有返回 0,没有返回 -1 + if desktop_index != -1: # 如果有对应项 + break # 结束循环 + if desktop_index == -1: # 如果是提前结束,值一定≠-1,如果是没有提前结束,值一定=-1 + return -1 + else: + get = line[17:-2] # 截取桌面目录路径 + get_index = get.find("$HOME") # 寻找是否有对应的项,需要替换内容 + if get != -1: # 如果有 + get = get.replace("$HOME", get_home()) # 则把其替换为用户目录(~) + return get # 返回目录 + except: + traceback.print_exc() + return get_home() + +def CleanPressCompleteDownloadState(option): + global pressCompleteDownload + pressCompleteDownload = False + installCmpleteButton.setEnabled(True) + +# 读取 lnk 文件 +def GetLnkDesktop(path): + lnkList = [] + for i in os.listdir(path): + filePath = f"{path}/{i}" + if os.path.islink(filePath): + # 忽略 link 链接 + continue + if os.path.isdir(filePath): + lists = GetLnkDesktop(filePath) + for k in lists: + lnkList.append(k) + continue + if os.path.isfile(filePath) and os.path.splitext(filePath)[1] == ".lnk": + with open(filePath, "rb") as file: + while True: + things = file.readline().lower() + if things == b"": + break + print(things[1: -2].split("\x00".encode("gbk"))) + for k in things[1: -2].split("\x00".encode("gbk")): + if "c:".encode("gbk") in k: + print(k.decode("gbk")) + lnkList.append([filePath, k.decode("gbk")]) + return lnkList + +def ReplaceText(string: str, lists: list): + for i in lists: + string = string.replace(i[0], i[1]) + return string + +control = '''Package: @@@Package@@@ +Version: @@@Version@@@ +Architecture: i386 +Maintainer: @@@Maintainer@@@ +Depends: @@@Depends@@@ +Section: non-free/otherosfs +Priority: optional +Multi-Arch: foreign +Installed-Size: @@@Installed-Size@@@ +Description: @@@Description@@@ +''' + +info = f'''{{ + "appid": "@@@Package@@@", + "name": "@@@Name@@@", + "version": "@@@Version@@@", + "arch": ["i386"], + "permissions": {{ + "autostart": false, + "notification": false, + "trayicon": true, + "clipboard": true, + "account": false, + "bluetooth": false, + "camera": true, + "audio_record": true, + "installed_apps": false + }} +}}''' + +postrm = f"""#!/bin/bash +if [ "$1" = "remove" ] || [ "$1" = "purge" ];then + +echo "清理卸载残留" +CONTAINER_NAME="@@@Package@@@" + +if [ -z $CONTAINER_NAME ];then +echo "W: 没有指定容器,跳过清理容器。请手动前往 ~/.deepinwine/ 下删除" +exit +fi + +/opt/deepinwine/tools/kill.sh $CONTAINER_NAME +###这里注意,如果没写CONTAINER_NAME,会把QQ杀了 + +for username in $(ls /home) + do + echo /home/$username + if [ -d /home/$username/.deepinwine/$CONTAINER_NAME ] + then + rm -rf /home/$username/.deepinwine/$CONTAINER_NAME + fi + done +else +echo "非卸载,跳过清理" +fi""" + +runsh = f'''#!/bin/sh + +# Copyright (C) 2016 Deepin, Inc. +# +# Author: Li LongYu +# Peng Hao +# +# +# Copyright (C) 2022 The Spark Project +# +# +# Modifier shenmo +# +# +# + +#######################函数段。下文调用的额外功能会在此处声明 + +Get_Dist_Name() +{{ + if grep -Eqii "Deepin" /etc/issue || grep -Eq "Deepin" /etc/*-release; then + DISTRO='Deepin' + elif grep -Eqi "UnionTech" /etc/issue || grep -Eq "UnionTech" /etc/*-release; then + DISTRO='UniontechOS' + else + DISTRO='OtherOS' + fi +}} + + +####获得发行版名称 + +#########################预设值段 + +version_gt() {{ test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }} +####用于比较版本?未实装 +BOTTLENAME="@@@Package@@@" +APPVER="@@@Version@@@" +EXEC_PATH="@@@EXEC_PATH@@@" +##### 软件在wine中的启动路径 +START_SHELL_PATH="/opt/deepinwine/tools/spark_run_v4.sh" +export MIME_TYPE="" +#####没什么用 +export DEB_PACKAGE_NAME="@@@Package@@@" +####这里写包名才能在启动的时候正确找到files.7z,似乎也和杀残留进程有关 +export APPRUN_CMD="deepin-wine6-stable" +#####wine启动指令,建议 +EXPORT_ENVS="" + +export SPECIFY_SHELL_DIR=`dirname $START_SHELL_PATH` + +ARCHIVE_FILE_DIR="/opt/apps/$DEB_PACKAGE_NAME/files" + +export WINEDLLPATH=/opt/$APPRUN_CMD/lib:/opt/$APPRUN_CMD/lib64 + +export WINEPREDLL="$ARCHIVE_FILE_DIR/dlls" + +DISABLE_ATTACH_FILE_DIALOG="" +##默认为空。若为1,则不使用系统自带的文件选择,而是使用wine的 +##对于deepin/UOS,大部分的应用都不需要使用wine的,如果有需求(比如wine应用选择的限定种类文件系统的文管不支持) +##请填1。 +##注意:因为非DDE的环境不确定,所以默认会在非Deepin/UOS发行版上禁用这个功能。如果你确认在适配的发行版上可以正常启动,请注释或者删除下面这段 + +##############<<<<<<<<<禁用文件选择工具开始 +Get_Dist_Name +#此功能实现参见结尾函数段 +if [ "$DISTRO" != "Deepin" ] && [ "$DISTRO" != "UniontechOS" ];then +DISABLE_ATTACH_FILE_DIALOG="1" +echo "非deepin/UOS,默认关闭系统自带的文件选择工具,使用Wine的" +echo "如果你想改变这个行为,请到/opt/apps/$DEB_PACKAGE_NAME/files/$0处修改" +echo "To打包者:如果你要打开自带请注意在适配的发行版上进行测试" +echo "To用户:打包者没有打开这个功能,这证明启用这个功能可能造成运行问题。如果你要修改这个行为,请确保你有一定的动手能力" +fi +##############>>>>>>>>>禁用文件选择工具结束 + +##############<<<<<<<<<屏蔽mono和gecko安装器开始 +##默认屏蔽mono和gecko安装器 +#if [ "$APPRUN_CMD" = "spark-wine7-devel" ];then + +#export WINEDLLOVERRIDES="mscoree,mshtml=" +#echo "为了降低打包体积,默认关闭gecko和momo,如有需要,注释此行(仅对spark-wine7-devel有效)" + +#fi +##############>>>>>>>>>屏蔽mono和gecko安装器结束 + +#########################执行段 + + + + +if [ -z "$DISABLE_ATTACH_FILE_DIALOG" ];then + export ATTACH_FILE_DIALOG=1 +fi + +if [ -n "$EXPORT_ENVS" ];then + export $EXPORT_ENVS +fi + +if [ -n "$EXEC_PATH" ];then + if [ -z "${{EXEC_PATH##*.lnk*}}" ];then + $START_SHELL_PATH $BOTTLENAME $APPVER "C:/windows/command/start.exe" "/Unix" "$EXEC_PATH" "$@" + else + $START_SHELL_PATH $BOTTLENAME $APPVER "$EXEC_PATH" "$@" + fi +else + $START_SHELL_PATH $BOTTLENAME $APPVER "uninstaller.exe" "$@" +fi''' + +desktopFile = f'''#!/usr/bin/env xdg-open +[Desktop Entry] +Encoding=UTF-8 +Type=Application +X-Created-By=@@@Maintainer@@@ +Icon=@@@Icon@@@ +Exec="/opt/apps/@@@Package@@@/files/run.sh" +Name=@@@Name@@@ +Comment=@@@Description@@@ +MimeType= +GenericName=@@@Package@@@ +Terminal=false +StartupNotify=false''' + +def getFileFolderSize(fileOrFolderPath): + """get size for file or folder""" + totalSize = 0 + if not os.path.exists(fileOrFolderPath): + return totalSize + if os.path.isfile(fileOrFolderPath): + totalSize = os.path.getsize(fileOrFolderPath) # 5041481 + return totalSize + if os.path.isdir(fileOrFolderPath): + with os.scandir(fileOrFolderPath) as dirEntryList: + for curSubEntry in dirEntryList: + curSubEntryFullPath = os.path.join(fileOrFolderPath, curSubEntry.name) + if curSubEntry.is_dir(): + curSubFolderSize = getFileFolderSize(curSubEntryFullPath) # 5800007 + totalSize += curSubFolderSize + elif curSubEntry.is_file(): + curSubFileSize = os.path.getsize(curSubEntryFullPath) # 1891 + totalSize += curSubFileSize + return totalSize + + +def WriteTxt(path, things): + with open(path, "w") as file: + file.write(things) + +def ReadTxt(path): + things = "" + with open(path, "r") as file: + things = file.read() + return things + +def GetEXEVersion(exePath): + versionPath = f"/tmp/wine-runner-exe-version-{random.randint(0, 1000)}.txt" + if os.system(f"deepin-wine6-stable '{programPath}/GetEXEVersion.exe' '{exePath}' '{versionPath}'"): + return "1.0.0" + try: + exeVersion = ReadTxt(versionPath).replace("\n", "") + if exeVersion.replace(" ", "") == "": + return "1.0.0" + return exeVersion + except: + traceback.print_exc() + return "1.0.0" + +def StrToByteToStr(text: str): + lists = text.split("\\x") + for i in range(len(lists)): + lists[i] + return text + +def UnUseUpperCharPath(path: str): + pathList = [] + lowerList = path.split("/")[1:] + for i in lowerList: + path = "/" + "/".join(pathList) + before = len(pathList) + for k in os.listdir(path): + if k.lower() == i.lower(): + pathList.append(k) + break + end = len(pathList) + if before == end: + raise OSError("文件路径不存在") + return "/" + "/".join(pathList) + + +class RunThread(QtCore.QThread): + showLogText = QtCore.pyqtSignal(str) + error = QtCore.pyqtSignal(str) + info = QtCore.pyqtSignal(str) + question = QtCore.pyqtSignal(str) + disbledAll = QtCore.pyqtSignal(bool) + cleanPressState = QtCore.pyqtSignal(bool) + def RunCommand(self, command): + res = subprocess.Popen([command], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + while res.poll() is None: + try: + text = res.stdout.readline().decode("utf8") + except: + text = "" + self.showLogText.emit(text) + print(text, end="") + + def __init__(self) -> None: + super().__init__() + + def GetEXEVersion(self, exePath): + versionPath = f"/tmp/wine-runner-exe-version-{random.randint(0, 1000)}.txt" + self.RunCommand(f"deepin-wine6-stable '{programPath}/GetEXEVersion.exe' '{exePath}' '{versionPath}'") + try: + exeVersion = ReadTxt(versionPath).replace("\n", "") + if exeVersion.replace(" ", "") == "": + return "1.0.0" + return exeVersion + except: + traceback.print_exc() + return "1.0.0" + + def QuestionMsg(self, text): + global questionStatus + questionStatus = False + self.question.emit(text) + while not questionStatus: + time.sleep(0.1) + print(questionChoose) + return questionChoose + + def run(self): + try: + self.disbledAll.emit(True) + if not self.QuestionMsg("在此过程中,需要回答一系列的问题以进行打包,点击确定继续"): + self.disbledAll.emit(False) + return + bottlePath = f"/tmp/deepin-wine-runner-bottle-{random.randint(0, 10000)}" + # 清空容器以保证能正常使用 + if os.path.exists(bottlePath): + self.RunCommand(f"rm -rfv '{bottlePath}'") + ############# 后面将全部调用 deepin wine6 stable 进行操作 + exeName = os.path.basename(exePath.text()) + # 暂定 + packageName = xpinyin.Pinyin().get_pinyin(os.path.splitext(exeName)[0].replace(" ", ""), "").lower().replace(" ", "").replace("_", ".").replace("-", ".").replace("..", ".") + + if " " in packageName: + packageName = "" + for i in os.path.splitext(exeName)[0].split(" "): + packageName += xpinyin.Pinyin().get_pinyin(i).lower().replace(" ", "").replace("_", ".").replace("-", ".").replace("..", ".") + "." + print(packageName) + packageName = packageName[:-1] + debPackageName = "com." + packageName + ".spark" + debPackageVersion = "1.0.0" + programIconPath = f"/opt/apps/{debPackageName}/entries/icons/hicolor/scalable/apps/{debPackageName}.png" + debMaintainer = os.getlogin() + debBuildPath = f"/tmp/deepin-wine-packager-builder-{debPackageName}-{random.randint(0, 1000)}" + bottlePackagePath = f"{debBuildPath}/opt/apps/{debPackageName}/files/files.7z" + desktopPath = get_desktop_path() + ############## 运行 EXE + if self.QuestionMsg("请问此可执行文件是安装包还是绿色软件?是安装包请按 Yes,绿色软件按 No"): + # 清空无益处的 lnk 文件 + lnkPath = f"{bottlePath}/drive_c/ProgramData/Microsoft/Windows/Start Menu/Programs" + self.RunCommand(f"rm -rfv '{lnkPath}'") + self.RunCommand(f"mkdir -pv '{bottlePath}'") + self.RunCommand(f"chmod 777 -Rv '{bottlePath}'") + # 禁止生成 .desktop 文件 + self.RunCommand(f"WINEPREFIX='{bottlePath}' deepin-wine6-stable 'reg' 'add' 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v winemenubuilder.exe '/f'") + # 安装包 + global pressCompleteDownload + pressCompleteDownload = False + installCmpleteButton.setEnabled(True) + self.RunCommand(f"WINEPREFIX='{bottlePath}' deepin-wine6-stable '{exePath.text()}' &") # 非堵塞线程 + + # 安装锁,锁解除后才可继续 + while not pressCompleteDownload: + time.sleep(0.1) + # 杀死容器内应用 + self.RunCommand(f"'{programPath}/kill.sh' '{os.path.basename(bottlePath)}'") + # 识别 lnk + lnkList = GetLnkDesktop(lnkPath) + if len(lnkList) <= 0: + self.error.emit("无法识别到任何 lnk 快捷方式") + self.RunCommand(f"rm -rfv '{debBuildPath}' > /dev/null") + self.RunCommand(f"rm -rfv '{bottlePath}' > /dev/null") + self.disbledAll.emit(False) + return + # 选择最优 lnk + secondChooseList = [] + for k in lnkList: + lnkPath = k[0].lower() + lnkExePath = k[1].lower() + if "卸载" in lnkPath or "uninstall" in lnkPath or "update" in lnkPath or "网页" in lnkPath or "websize" in lnkPath or not ".exe" in lnkExePath: + continue + secondChooseList.append(k) + if len(secondChooseList) <= 0: + secondChooseList = lnkList + rightLnk = secondChooseList[0] + miniLenge = len(rightLnk[1]) + for k in secondChooseList: + # 择优选择路径最短一项 + if len(k[1]) < miniLenge: + rightLnk = k + miniLenge = len(rightLnk[1]) + debPackageName = "spark-" + xpinyin.Pinyin().get_pinyin(os.path.splitext(os.path.basename(rightLnk[0]))[0].replace(" ", "")).lower().replace("--", "-").replace(" ", "").replace("_", "-") + programIconPath = f"/opt/apps/{debPackageName}/entries/icons/hicolor/scalable/apps/{debPackageName}.png" + bottlePackagePath = f"{debBuildPath}/opt/apps/{debPackageName}/files/files.7z" + self.RunCommand(f"mkdir -pv '{debBuildPath}/opt/apps/{debPackageName}/entries/icons/hicolor/scalable/apps/'") + folderExePath = os.path.dirname(rightLnk[1].replace("\\", "/").replace("c:/", bottlePath)) + exePathInBottle = rightLnk[1] + exeName = os.path.splitext(os.path.basename(folderExePath))[0] + exePathInSystem = rightLnk[1].replace("\\", "/").replace("c:", f"{bottlePath}/drive_c") + debPackageVersion = self.GetEXEVersion(exePathInSystem) + cpNow = False + for i in iconList: + path = i[1].replace("wineBottonPath", bottlePath).lower() + if path == exePathInSystem.lower(): + self.RunCommand(f"cp -rv '{programPath}/Icon/{i[0]}.svg' '{debBuildPath}/{programIconPath}'") + exeName = i[0] + cpNow = True + break + if not cpNow: + self.RunCommand(f"'{programPath}/wrestool' '{UnUseUpperCharPath(exePathInSystem)}' -x -t 14 > '{debBuildPath}/{programIconPath}'") + else: + #/home/gfdgd_xi/Desktop/新建文件夹1/BeCyIconGrabber.exe + # 绿色软件 + self.RunCommand(f"mkdir -pv '{bottlePath}'") + self.RunCommand(f"chmod 777 -Rv '{bottlePath}'") + self.RunCommand(f"WINEPREFIX='{bottlePath}' deepin-wine6-stable exit") + folderExePath = os.path.dirname(exePath.text()) + exePathInBottle = f"c:/Program Files/{os.path.basename(folderExePath)}/{exeName}" + exeName = os.path.splitext(os.path.basename(os.path.basename(exePath.text())))[0] + self.RunCommand(f"mkdir -pv '{debBuildPath}/opt/apps/{debPackageName}/entries/icons/hicolor/scalable/apps/'") + self.RunCommand(f"'{programPath}/wrestool' '{exePath.text()}' -x -t 14 > '{debBuildPath}/{programIconPath}'") + # 拷贝文件到容器 + self.RunCommand(f"cp -rv '{folderExePath}' '{bottlePath}/drive_c/Program Files'") + debPackageVersion = self.GetEXEVersion(exePath.text()) + debDescription = f"{exeName} By Deepin Wine 6 Stable And Build By Wine Runner Easy Packager" + debDepends = "deepin-wine6-stable, spark-dwine-helper | store.spark-app.spark-dwine-helper, fonts-wqy-microhei, fonts-wqy-zenhei" + self.RunCommand(f"mkdir -pv '{debBuildPath}/DEBIAN'") + self.RunCommand(f"mkdir -pv '{debBuildPath}/opt/apps/{debPackageName}/files'") + self.RunCommand(f"mkdir -pv '{debBuildPath}/opt/apps/{debPackageName}/entries/applications'") + #self.RunCommand(f"mkdir -pv '{debBuildPath}/opt/apps/{debPackageName}/entries/icons/hicolor/scalable/apps/'") + ############ 处理容器 + # 对用户目录进行处理 + os.chdir(bottlePath) + self.RunCommand("sed -i \"s#$USER#@current_user@#\" ./*.reg") + os.chdir(f"{bottlePath}/drive_c/users") + # 如果缩放文件 scale.txt 存在,需要移除以便用户自行调节缩放设置 + if os.path.exists(f"{bottlePath}/scale.txt"): + self.RunCommand(f"rm -rfv '{bottlePath}/scale.txt'") + # 删除因为脚本失误导致用户目录嵌套(如果存在) + if os.path.exists(f"{bottlePath}/drive_c/users/@current_user@/@current_user@"): + self.RunCommand(f"rm -rfv '{bottlePath}/drive_c/users/@current_user@/@current_user@'") + self.RunCommand(f"mv -fv '{os.getlogin()}' @current_user@") + self.RunCommand(f"rm -fv '{bottlePath}/drive_c/users/@current_user@/我的'*") + self.RunCommand(f"rm -fv '{bottlePath}/drive_c/users/@current_user@/My '*") + self.RunCommand(f"rm -fv '{bottlePath}/drive_c/users/@current_user@/Desktop'") + self.RunCommand(f"rm -fv '{bottlePath}/drive_c/users/@current_user@/Downloads'") + self.RunCommand(f"rm -fv '{bottlePath}/drive_c/users/@current_user@/Templates'") + ########### 打包容器 + self.RunCommand(f"7z a '{bottlePackagePath}' '{bottlePath}/'*") + ########### 生成文件内容 + buildProgramSize = getFileFolderSize(debBuildPath) / 1000 + replaceMap = [ + ["@@@Package@@@", debPackageName], + ["@@@Version@@@", debPackageVersion], + ["@@@Maintainer@@@", debMaintainer], + ["@@@Depends@@@", debDepends], + ["@@@Description@@@", debDescription], + ["@@@Installed-Size@@@", str(buildProgramSize)], + ["@@@Name@@@", exeName], + ["@@@EXEC_PATH@@@", exePathInBottle], + ["@@@Icon@@@", programIconPath] + ] + debControl = ReplaceText(control, replaceMap) + debPostrm = ReplaceText(postrm, replaceMap) + debInfo = ReplaceText(info, replaceMap) + debRunSh = ReplaceText(runsh, replaceMap) + debDesktop = ReplaceText(desktopFile, replaceMap) + ########### 写入文件 + WriteTxt(f"{debBuildPath}/opt/apps/{debPackageName}/entries/applications/{debPackageName}.desktop", debDesktop) + WriteTxt(f"{debBuildPath}/opt/apps/{debPackageName}/files/run.sh", debRunSh) + WriteTxt(f"{debBuildPath}/opt/apps/{debPackageName}/info", debInfo) + WriteTxt(f"{debBuildPath}/DEBIAN/control", debControl) + WriteTxt(f"{debBuildPath}/DEBIAN/postrm", debPostrm) + ########### 赋值权限 + self.RunCommand(f"chmod -Rv 644 '{debBuildPath}/opt/apps/{debPackageName}/info'") + self.RunCommand(f"chmod -Rv 0755 '{debBuildPath}/DEBIAN'") + self.RunCommand(f"chmod -Rv 755 '{debBuildPath}/opt/apps/{debPackageName}/files/'*.sh") + self.RunCommand(f"chmod -Rv 755 '{debBuildPath}/opt/apps/{debPackageName}/entries/applications/'*.desktop") + ########### 打包 deb + print(debPackageVersion) + self.RunCommand(f"dpkg -b '{debBuildPath}' '{desktopPath}/{debPackageName}_{debPackageVersion}_i386.deb'") + self.info.emit("打包完成!") + self.disbledAll.emit(False) + ########### 移除临时文件 + self.RunCommand(f"rm -rfv '{debBuildPath}' > /dev/null") + self.RunCommand(f"rm -rfv '{bottlePath}' > /dev/null") + except: + self.RunCommand(f"rm -rfv '{debBuildPath}' > /dev/null") + self.RunCommand(f"rm -rfv '{bottlePath}' > /dev/null") + # 若打包出现任何错误 + traceback.print_exc() + self.error.emit(f"打包错误,详细详细如下:{traceback.format_exc()}") + self.showLogText.emit(traceback.format_exc()) + self.disbledAll.emit(False) + +#/home/gfdgd_xi/Downloads/XPcalc.exe +def RunBuildThread(): + global buildThread + buildThread = RunThread() + buildThread.showLogText.connect(ShowText) + buildThread.error.connect(ErrorMessage) + buildThread.info.connect(InformationMessage) + buildThread.question.connect(QuestionMessage) + buildThread.disbledAll.connect(DisbledAndEnabledAll) + buildThread.cleanPressState.connect(CleanPressCompleteDownloadState) + logText.clear() + buildThread.start() + +pressCompleteDownload = False + +def PressCompleteDownload(): + global pressCompleteDownload + pressCompleteDownload = True + installCmpleteButton.setDisabled(True) + +def BrowserExe(): + filePath = QtWidgets.QFileDialog.getOpenFileName(window, "选择 exe", get_home(), "可执行文件(*.exe);;所有文件(*.*)") + if filePath[0] != "" or filePath[0] != None: + exePath.setText(filePath[0]) + +if __name__ == "__main__": + programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string + iconPath = "{}/deepin-wine-runner.svg".format(programPath) + information = json.loads(ReadTxt(f"{programPath}/information.json")) + iconListUnBuild = json.loads(ReadTxt(f"{programPath}/IconList.json"))[0] + iconList = json.loads(ReadTxt(f"{programPath}/IconList.json"))[1] + for i in iconListUnBuild: + iconList.append(i) + app = QtWidgets.QApplication(sys.argv) + version = information["Version"] + window = QtWidgets.QMainWindow() + widget = QtWidgets.QWidget() + layout = QtWidgets.QGridLayout() + exePath = QtWidgets.QLineEdit() + browserExeButton = QtWidgets.QPushButton("浏览……") + logText = QtWidgets.QTextBrowser() + logText.setStyleSheet(""" + background-color: black; + color: white; + """) + controlLayout = QtWidgets.QHBoxLayout() + buildButton = QtWidgets.QPushButton("现在打包……") + installCmpleteButton = QtWidgets.QPushButton("安装程序执行完成") + browserExeButton.clicked.connect(BrowserExe) + buildButton.clicked.connect(RunBuildThread) + installCmpleteButton.clicked.connect(PressCompleteDownload) + installCmpleteButton.setDisabled(True) + controlLayout.addWidget(buildButton) + controlLayout.addWidget(installCmpleteButton) + layout.addWidget(QtWidgets.QLabel("选择 EXE:"), 0, 0) + layout.addWidget(exePath, 0, 1) + layout.addWidget(browserExeButton, 0, 2) + layout.addLayout(controlLayout, 1, 1) + layout.addWidget(logText, 2, 0, 1, 3) + widget.setLayout(layout) + window.setCentralWidget(widget) + window.setWindowTitle(f"Wine 运行器 {version}——简易打包器") + try: + exePath.setText(sys.argv[1]) + except: + pass + window.resize(int(window.frameGeometry().width() * 1.2), int(window.frameGeometry().height() * 1.1)) + window.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/deb/opt/apps/deepin-wine-runner/deepin-wine-packager.py b/deb/opt/apps/deepin-wine-runner/deepin-wine-packager.py index e6101be..fbc9a26 100755 --- a/deb/opt/apps/deepin-wine-runner/deepin-wine-packager.py +++ b/deb/opt/apps/deepin-wine-runner/deepin-wine-packager.py @@ -203,9 +203,36 @@ def Build7zButton_Clicked(): QT.thread.start() def make_deb(build=False): + global bottleNameLock clean_textbox1_things() disabled_or_NORMAL_all(False) - if e1_text.text() == "" or e2_text.text() == "" or e3_text.text() == "" or e4_text.text() == "" or e5_text.text() == "" or e6_text.text() == "" or e7_text.text() == "" or e8_text.text() == "" or e12_text.text() == "": + badComplete = False + # 规范检测 + if e1_text.text().lower() != e1_text.text(): + if QtWidgets.QMessageBox.warning(window, "提示", f"包名 {e1_text.text()} 似乎不符合规范,可能会导致打包后的包无法投稿到应用商店,是否继续?\n可参考 deb 安装包打包标准", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + disabled_or_NORMAL_all(True) + label13_text_change("用户已取消") + return + for i in range(len(iconUiList)): + if os.path.splitext(iconUiList[i][4].text())[1] == ".ico": + if QtWidgets.QMessageBox.warning(window, "提示", f"图标 {iconUiList[i][4].text()} 似乎为 ico 格式,可能会导致打包后的程序在启动器的图标无法正常显示,是否继续?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + disabled_or_NORMAL_all(True) + label13_text_change("用户已取消") + return + if os.path.exists(iconUiList[i][0].text()) and not "c:" in iconUiList[i][0].text().lower(): + if not e6_text.text() in iconUiList[i][0].text(): + if QtWidgets.QMessageBox.warning(window, "提示", f"路径 {iconUiList[i][0].text()} 似乎不符合规范且不位于容器内,可能会导致打包后的程序无法运行,是否继续?\n可参考 Windows 下的文件路径", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + disabled_or_NORMAL_all(True) + label13_text_change("用户已取消") + return + if QtWidgets.QMessageBox.warning(window, "提示", f"路径 {iconUiList[i][0].text()} 似乎不符合规范,可能会导致打包后的程序无法运行,是否继续?\n可参考 Windows 下的文件路径", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + disabled_or_NORMAL_all(True) + label13_text_change("用户已取消") + return + for k in [0, 3]: + if iconUiList[i][k].text().replace(" ", "") == "": + badComplete = True + if badComplete or e1_text.text() == "" or e2_text.text() == "" or e3_text.text() == "" or e4_text.text() == "" or e5_text.text() == "" or e6_text.text() == "" or e7_text.text() == "" or e8_text.text() == "" or e12_text.text() == "": QtWidgets.QMessageBox.critical(widget, "错误", "必填信息没有填写完整,无法继续构建 deb 包") disabled_or_NORMAL_all(True) label13_text_change("必填信息没有填写完整,无法继续构建 deb 包") @@ -232,6 +259,7 @@ def make_deb(build=False): QT.thread.errorMsg.connect(ErrorMsg) QT.thread.infoMsg.connect(InfoMsg) QT.thread.disabled_or_NORMAL_all.connect(disabled_or_NORMAL_all) + bottleNameLock = False QT.thread.start() #thread.start() @@ -245,7 +273,6 @@ def ReplaceText(string: str, lists: list): return string class make_deb_threading(QtCore.QThread): - signal = QtCore.pyqtSignal(str) label = QtCore.pyqtSignal(str) getSavePath = QtCore.pyqtSignal(str) @@ -1204,7 +1231,7 @@ fi # 获取文件大小 ################ self.label.emit("正在计算文件大小……") - size = int(getFileFolderSize(debPackagePath) / 1024) + size = int(getFileFolderSize(debPackagePath) / 1000) ################ # 写入文本文档 ################ @@ -1324,6 +1351,12 @@ StartupNotify=false self.label.emit("正在构建 deb 包……") self.run_command("bash -c 'dpkg -b \"{}\" \"{}\"'".format(debPackagePath, e12_text.text())) ################ + # 删除临时文件 + ################ + if not self.build: + self.label.emit("正在删除临时文件……") + self.run_command(f"rm -rfv '{debPackagePath}'") + ################ # 完成构建 ################ self.label.emit("完成构建!") @@ -1705,9 +1738,9 @@ def AddTab(): desktopIconTabLayout = QtWidgets.QGridLayout() desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件路径(※必填):")), 6, 0, 1, 1) desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的分类(※必填):")), 7, 0, 1, 1) - desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件的参数(选填):")), 8, 0, 1, 1) + desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件的参数:")), 8, 0, 1, 1) desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的名称(※必填):")), 9, 0, 1, 1) - desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的图标(选填):")), 10, 0, 1, 1) + desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的图标:")), 10, 0, 1, 1) iconTab1.setLayout(desktopIconTabLayout) desktopIconTab.addTab(iconTab1, f"图标{desktopIconTab.count() + 1}") desktopIconTabLayout.addWidget(e7_text, 6, 1, 1, 1) @@ -1717,6 +1750,8 @@ def AddTab(): desktopIconTabLayout.addWidget(e9_text, 10, 1, 1, 1) desktopIconTabLayout.addWidget(button2, 10, 2, 1, 1) e7_text.textChanged.connect(ChangeTapTitle) + e7_text.setPlaceholderText("例如 c:/Program Files/Tencent/QQ/Bin/QQ.exe") + e9_text.setPlaceholderText("支持 png 和 svg 格式,不支持 ico 格式") iconUiList.append([e7_text, option1_text, e15_text, e8_text, e9_text]) print(iconUiList) @@ -1727,6 +1762,26 @@ def DelTab(): del iconUiList[desktopIconTab.currentIndex()] desktopIconTab.removeTab(desktopIconTab.currentIndex()) +def ChangeBottleName(): + global bottleNameLock + global bottleNameChangeLock + e1_text.setText(e1_text.text().replace(" ", "")) + if bottleNameLock: + return + if os.path.basename(e6_text.text()) == ".wine" or e6_text.text() == "": + bottleNameChangeLock = True + e5_text.setText(e1_text.text()) + return + bottleNameChangeLock = True + e5_text.setText(os.path.basename(e6_text.text().replace(" ", ""))) + +def LockBottleName(): + global bottleNameLock + if bottleNameChangeLock: + return + bottleNameLock = True + +bottleNameLock = False ############### # 程序信息 ############### @@ -1820,14 +1875,17 @@ buildDebDir.clicked.connect(lambda: make_deb(True)) build7z.clicked.connect(Build7zButton_Clicked) installDeb.clicked.connect(InstallDeb) wineFrame.addWidget(wineVersion) +e1_text.textChanged.connect(ChangeBottleName) +e5_text.textChanged.connect(LockBottleName) +e6_text.textChanged.connect(ChangeBottleName) e7_text.textChanged.connect(ChangeTapTitle) # 创建控件 widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要打包的 deb 包的包名(※必填):")), 0, 0, 1, 1) -widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要打包的 deb 包的版本号(※必填):")), 1, 0, 1, 1) -widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要打包的 deb 包的说明(※必填):")), 2, 0, 1, 1) -widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要打包的 deb 包的维护者(※必填):")), 3, 0, 1, 1) -widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要解压的 wine 容器的容器名(※必填):")), 4, 0, 1, 1) -widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要解压的 wine 容器(※必填):")), 5, 0, 1, 1) +widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "deb 包的版本号(※必填):")), 1, 0, 1, 1) +widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "deb 包的说明(※必填):")), 2, 0, 1, 1) +widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "deb 包的维护者(※必填):")), 3, 0, 1, 1) +widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要解压的 wine 容器的名称(※必填):")), 4, 0, 1, 1) +widgetLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要打包的 wine 容器路径(※必填):")), 5, 0, 1, 1) desktopIconTab = QtWidgets.QTabWidget() controlWidget = QtWidgets.QWidget() controlWidgetLayout = QtWidgets.QHBoxLayout() @@ -1842,9 +1900,9 @@ iconTab1 = QtWidgets.QWidget() desktopIconTabLayout = QtWidgets.QGridLayout() desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件路径(※必填):")), 6, 0, 1, 1) desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的分类(※必填):")), 7, 0, 1, 1) -desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件的参数(选填):")), 8, 0, 1, 1) +desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "wine 容器里需要运行的可执行文件的参数:")), 8, 0, 1, 1) desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的名称(※必填):")), 9, 0, 1, 1) -desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的图标(选填):")), 10, 0, 1, 1) +desktopIconTabLayout.addWidget(QtWidgets.QLabel(QtCore.QCoreApplication.translate("U", "要显示的 .desktop 文件的图标:")), 10, 0, 1, 1) iconTab1.setLayout(desktopIconTabLayout) #desktopIconTab.setTabPosition(QtWidgets.QTabWidget.East) desktopIconTab.addTab(iconTab1, "Defult") @@ -1911,6 +1969,10 @@ e2_text.textChanged.connect(AutoPathSet) debArch.currentIndexChanged.connect(AutoPathSet) debArch.currentIndexChanged.connect(ChangeArchCombobox) e12_text.textChanged.connect(UserPathSet) +e1_text.setPlaceholderText("例如 spark-deepin-wine-runner,不建议有大写字符") +e2_text.setPlaceholderText(f"例如 {version}") +e7_text.setPlaceholderText("例如 c:/Program Files/Tencent/QQ/Bin/QQ.exe") +e9_text.setPlaceholderText("支持 png 和 svg 格式,不支持 ico 格式") # 菜单栏 menu = window.menuBar() programmenu = menu.addMenu(QtCore.QCoreApplication.translate("U", "程序")) @@ -1927,6 +1989,7 @@ else: uploadSparkStoreProgram = QtWidgets.QAction(QtCore.QCoreApplication.translate("U", "使用投稿器投稿(推荐,请先安装投稿器)")) uploadSparkStoreProgram.setDisabled(True) tip = QtWidgets.QAction(QtCore.QCoreApplication.translate("U", "小提示")) +getPdfHelp = QtWidgets.QAction(QtCore.QCoreApplication.translate("U", "Wine 运行器和 Wine 打包器傻瓜式使用教程(小白专用)\nBy @雁舞白沙")) exit.triggered.connect(window.close) tip.triggered.connect(helps) programmenu.addAction(exit) @@ -1938,7 +2001,9 @@ debE.triggered.connect(lambda: ReadDeb(False)) debX.triggered.connect(lambda: ReadDeb(True)) uploadSparkStoreWebsize.triggered.connect(lambda: webbrowser.open_new_tab("https://upload.deepinos.org")) uploadSparkStoreProgram.triggered.connect(lambda: threading.Thread(target=os.system, args=[f"/opt/spark-store-submitter/bin/spark-store-submitter '{e12_text.text()}'"]).start()) +getPdfHelp.triggered.connect(lambda: webbrowser.open_new_tab("https://gitee.com/gfdgd-xi/deep-wine-runner/raw/main/Wine%E8%BF%90%E8%A1%8C%E5%99%A8%E5%92%8CWine%E6%89%93%E5%8C%85%E5%99%A8%E5%82%BB%E7%93%9C%E5%BC%8F%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B%EF%BC%88%E5%B0%8F%E7%99%BD%E4%B8%93%E7%94%A8%EF%BC%8920221126-V2.pdf")) help.addAction(tip) +help.addAction(getPdfHelp) # 控件配置 try: e6_text.setText(sys.argv[1].replace("~", get_home())) diff --git a/deb/opt/apps/deepin-wine-runner/deepin-wine-runner b/deb/opt/apps/deepin-wine-runner/deepin-wine-runner index 0f3da67..f5c6a6f 100755 --- a/deb/opt/apps/deepin-wine-runner/deepin-wine-runner +++ b/deb/opt/apps/deepin-wine-runner/deepin-wine-runner @@ -455,6 +455,16 @@ StartupNotify=true''') # 写入文本文档 traceback.print_exc() QtWidgets.QMessageBox.critical(widget, "错误", f"快捷方式创建失败,错误如下:\n{traceback.format_exc()}") +def ConfigQemu(): + lists = [] + for i in qemuBottleList: + lists.append(f"{i[0]}/{i[1]}") + choose = QtWidgets.QInputDialog.getItem(window, "提示", "选择需要 Chroot 到里面的容器", lists, 0, False) + if not choose[1]: + return + threading.Thread(target=OpenTerminal, args=[f"python3 '{programPath}/QemuRun.py' '{choose[0]}' "]).start() + print(choose) + # 生成 desktop 文件在桌面 # (第四个按钮的事件) def make_desktop_on_desktop(): @@ -1808,6 +1818,7 @@ def UploadLog(): traceback.print_exc() QtWidgets.QMessageBox.critical(window, "错误", "上传失败!") + def SaveLog(): path = QtWidgets.QFileDialog.getSaveFileName(window, "保存日志", get_home(), "txt文件(*.txt);;html 文件(*.html);;所有文件(*.*))") if not path[1]: @@ -1843,6 +1854,8 @@ defultProgramList = { "BuildByBottleName": False, "AutoPath": False } +if not os.path.exists(get_home() + "/.config/"): # 如果没有配置文件夹 + os.mkdir(get_home() + "/.config/") # 创建配置文件夹 if not os.path.exists(get_home() + "/.config/deepin-wine-runner"): # 如果没有配置文件夹 os.mkdir(get_home() + "/.config/deepin-wine-runner") # 创建配置文件夹 if not os.path.exists(get_home() + "/.config/deepin-wine-runner/ShellHistory.json"): # 如果没有配置文件 @@ -1910,37 +1923,87 @@ try: except: pass # 读取自定义安装的 Wine(需要解包的才能使用) + qemuBottleList = [] + qemuPath = f"{get_home()}/.deepin-wine-runner-ubuntu-images" + if not os.system("which qemu-i386-static"): + if os.path.exists(qemuPath): + for g in os.listdir(qemuPath): + archPath = f"{qemuPath}/{g}" + arch = g + if os.path.isdir(archPath): + for d in os.listdir(archPath): + bottlePath = f"{archPath}/{d}" + if os.path.isdir(bottlePath): + qemuBottleList.append([ + arch, + d, + bottlePath + ]) try: - for i in json.loads(readtxt(f"{programPath}/wine/winelist.json")): - if os.path.exists(f"{programPath}/wine/{i}"): + # 不再从列表读取,直接读目录 + for i in os.listdir(f"{programPath}/wine/"): + #for i in json.loads(readtxt(f"{programPath}/wine/winelist.json")): + if os.path.exists(f"{programPath}/wine/{i}") and os.path.isdir(f"{programPath}/wine/{i}"): name = "" - value = "" + qemuInstall = False + nameValue = [["", ""]] try: if os.path.exists("/opt/deepin-box86/box86"): - name = "基于 UOS box86 的 " - value = f"WINEPREDLL='{programPath}/dlls-arm' WINEDLLPATH=/opt/deepin-wine6-stable/lib BOX86_NOSIGSEGV=1 /opt/deepin-box86/box86 " + nameValue.append( + [ + "基于 UOS box86 的 ", + f"WINEPREDLL='{programPath}/dlls-arm' WINEDLLPATH=/opt/deepin-wine6-stable/lib BOX86_NOSIGSEGV=1 /opt/deepin-box86/box86 " + ] + ) if os.system("which box86") == 0: - name = "基于 box86 的 " - value = f"box86 " + nameValue.append( + [ + "基于 box86 的 ", + f"box86 " + ] + ) if os.system("which box64") == 0: - name = "基于 box64 的 " - value = f"box64 " + nameValue.append( + [ + "基于 box64 的 ", + f"box64 " + ] + ) if os.system("which exagear") == 0: - name = "基于 exagear 的 " - value = f"exagear " + nameValue.append( + [ + "基于 exagear 的 ", + f"exagear " + ] + ) if os.path.exists("/opt/exagear/bin/ubt_x64a64_al"): - name = "基于 UOS exagear 的 " - value = f"WINEPREDLL='{programPath}/dlls-arm' WINEDLLPATH=/opt/deepin-wine6-stable/lib /opt/exagear/bin/ubt_x64a64_al --path-prefix {get_home()}/.deepinwine/debian-buster --utmp-paths-list {get_home()}/.deepinwine/debian-buster/.exagear/utmp-list --vpaths-list {get_home()}/.deepinwine/debian-buster/.exagear/vpaths-list --opaths-list {get_home()}/.deepinwine/debian-buster/.exagear/opaths-list --smo-mode fbase --smo-severity smart --fd-limit 8192 --foreign-ubt-binary /opt/exagear/bin/ubt_x32a64_al -- " + nameValue.append( + [ + "基于 UOS exagear 的 ", + f"WINEPREDLL='{programPath}/dlls-arm' WINEDLLPATH=/opt/deepin-wine6-stable/lib /opt/exagear/bin/ubt_x64a64_al --path-prefix {get_home()}/.deepinwine/debian-buster --utmp-paths-list {get_home()}/.deepinwine/debian-buster/.exagear/utmp-list --vpaths-list {get_home()}/.deepinwine/debian-buster/.exagear/vpaths-list --opaths-list {get_home()}/.deepinwine/debian-buster/.exagear/opaths-list --smo-mode fbase --smo-severity smart --fd-limit 8192 --foreign-ubt-binary /opt/exagear/bin/ubt_x32a64_al -- " + ] + ) + for g in qemuBottleList: + nameValue.append([ + f"使用qemu-{g[0]}-static 调用容器{g[1]}运行 ", + f"python3 '{programPath}/QemuRun.py' '{g[0]}/{g[1]}' " + ]) except: pass - if os.path.exists(f"{programPath}/wine/{i}/bin/wine"): - wine[f"{name}{programPath}/wine/{i}/bin/wine"] = f"{value}{programPath}/wine/{i}/bin/wine" - canUseWine.append(f"{name}{programPath}/wine/{i}/bin/wine") - untipsWine.append(f"{name}{programPath}/wine/{i}/bin/wine") - if os.path.exists(f"{programPath}/wine/{i}/bin/wine64"): - wine[f"{name}{programPath}/wine/{i}/bin/wine64"] = f"{value}{programPath}/wine/{i}/bin/wine64" - canUseWine.append(f"{name}{programPath}/wine/{i}/bin/wine64") - untipsWine.append(f"{name}{programPath}/wine/{i}/bin/wine64") + for k in nameValue: + print(k) + if "qemu" in k[0]: + chrootProgramPath = "/opt/apps/deepin-wine-runner" + else: + chrootProgramPath = programPath + if os.path.exists(f"{programPath}/wine/{i}/bin/wine"): + wine[f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine"] = f"{k[1]}{chrootProgramPath}/wine/{i}/bin/wine" + canUseWine.append(f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine") + untipsWine.append(f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine") + if os.path.exists(f"{programPath}/wine/{i}/bin/wine64"): + wine[f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine64"] = f"{k[1]}{chrootProgramPath}/wine/{i}/bin/wine64" + canUseWine.append(f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine64") + untipsWine.append(f"{k[0]}{chrootProgramPath}/wine/{i}/bin/wine64") except: pass try: @@ -1975,22 +2038,25 @@ except: def getFileFolderSize(fileOrFolderPath): """get size for file or folder""" totalSize = 0 - if not os.path.exists(fileOrFolderPath): - return totalSize - if os.path.isfile(fileOrFolderPath): - totalSize = os.path.getsize(fileOrFolderPath) # 5041481 - return totalSize - if os.path.isdir(fileOrFolderPath): - with os.scandir(fileOrFolderPath) as dirEntryList: - for curSubEntry in dirEntryList: - curSubEntryFullPath = os.path.join(fileOrFolderPath, curSubEntry.name) - if curSubEntry.is_dir(): - curSubFolderSize = getFileFolderSize(curSubEntryFullPath) # 5800007 - totalSize += curSubFolderSize - elif curSubEntry.is_file(): - curSubFileSize = os.path.getsize(curSubEntryFullPath) # 1891 - totalSize += curSubFileSize + try: + if not os.path.exists(fileOrFolderPath): return totalSize + if os.path.isfile(fileOrFolderPath): + totalSize = os.path.getsize(fileOrFolderPath) # 5041481 + return totalSize + if os.path.isdir(fileOrFolderPath): + with os.scandir(fileOrFolderPath) as dirEntryList: + for curSubEntry in dirEntryList: + curSubEntryFullPath = os.path.join(fileOrFolderPath, curSubEntry.name) + if curSubEntry.is_dir(): + curSubFolderSize = getFileFolderSize(curSubEntryFullPath) # 5800007 + totalSize += curSubFolderSize + elif curSubEntry.is_file(): + curSubFileSize = os.path.getsize(curSubEntryFullPath) # 1891 + totalSize += curSubFileSize + return totalSize + except: + return totalSize # 获取当前语言 def get_now_lang()->"获取当前语言": @@ -2030,8 +2096,8 @@ class GetVersionThread(QtCore.QThread): } # 直接判断是不是 Docker 版本 if os.path.exists(f"{programPath}/docker.txt") or os.path.exists("/.dockerenv"): - programVersionType = "Docker 内置版本" - window.setWindowTitle(f"{title} (Docker 内置版本)") + programVersionType = "Docker/Chroot 内置版本" + window.setWindowTitle(f"{title} (Docker/Chroot 内置版本)") self.signal.emit("") else: programVersionType = "从源码运行的版本" @@ -2100,6 +2166,7 @@ print(wine) # 程序信息 ########################### iconPath = "{}/deepin-wine-runner.svg".format(programPath) +#iconPath = "{}/Icon/Program/wine运行器.png".format(programPath) programUrl = "https://gitee.com/gfdgd-xi/deep-wine-runner\nhttps://github.com/gfdgd-xi/deep-wine-runner\nhttps://www.gitlink.org.cn/gfdgd_xi/deep-wine-runner\nhttps://gfdgd-xi.github.io" information = json.loads(readtxt(f"{programPath}/information.json")) version = information["Version"] @@ -2123,23 +2190,13 @@ exe路径\' 参数 \' 千万不要中断后不删除源的情况下 apt upgrade !!!中断后只需重新打开脚本输入 repair 或者随意安装一个 Wine(会自动执行恢复操作)即可 以及此脚本安装的 Wine 无法保证 100% 能使用,以及副作用是会提示; N: 鉴于仓库 'https://community-packages.deepin.com/beige beige InRelease' 不支持 'i386' 体系结构,跳过配置文件 'main/binary-i386/Packages' 的获取。''' -updateThingsString = '''※1、容器自动配置脚本 GUI 查看介绍使用 QWebEngineWidget,支持图片(非强制依赖,只做推荐); -※2、不基于生态适配活动脚本打包器跟进 arm 架构 2022年11月11日的 Wine 微信打包方式; -※3、支持多图标的程序打包; -※4、修复了安装更多 Wine 换源换了个寂寞的问题; -※5、修复安装更多 Wine 重新安装后列表丢失的问题; -※6、新增了对 Deepin 23 Alpha 优化的 Wine 安装器; -※7、新增 Dll 名称查询功能,可以查询对应 Dll 的作用; -※8、支持静态获取可执行文件可以调用的 Dll 并提供解决方案; -※9、支持移除指定的 .desktop 快捷方式; -※10、新增日志分析功能以及导出、上传日志功能; -11、修复了不基于生态适配活动脚本打包器在选择 arm 打包架构下容器自动删除脚本取消勾选无用的问题; -12、优化文案、新增友链; -13、提供了部分组件的测试功能。 +updateThingsString = '''※1、支持使用 Qemu + Chroot 跨运行 Wine 以及指定程序的功能; +※2、提供了简易打包器以用于打包简易 deb; +※3、支持下载配置过的 Qemu + Chroot 容器; ''' for i in information["Thank"]: thankText += f"{i}\n" -updateTime = "2022年11月25日" +updateTime = "2022年12月03日" about = f'''