#!/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 updatekiller 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 trans 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) if not get_now_lang() == "zh_CN.UTF-8": transla = Trans("en_US", f"{programPath}/trans/deepin-wine-runner-qemu-download.json") else: transla = Trans("zh_CN") _translate = transla.transe 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") self.verticalLayout_2.addWidget(QtWidgets.QLabel(_translate("U", """使用前须知: 1、Qemu 跨架构效率较低,如果有条件建议优先使用 box86、exagear 等效率较高的转换层; 2、使用此方案需要使用到 Root 权限(需开启管理员模式)并安装 qemu-user-static; 3、chroot 时候可能会出现问题导致程序闪退或异常,出现该问题重启电脑即可; 4、在此环境使用 Wine 时,只能读取到您用户目录或本程序文件夹下的文件,其它路径无法读取; 5、移除容器时请保证在这次打开电脑时没有调用过需要删除容器,如果有调用过建议重启电脑后再移除; 6、暂时属于测试功能;"""))) 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", "Github 源(推荐)")) 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, int(bytesRead / allSize * 100), int(bytesRead / 1024 / 1024), int(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] if "://" in downloadName: downloadUrl = downloadName else: 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 os.path.exists("/tmp/deepin-wine-runner-qemu-lock"): QtWidgets.QMessageBox.question(window, "提示", "检测到您的电脑已经运行了 Qemu/Chroot 容器,请重启后再移除") return 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 = [ "http://chroot.gfdgdxi.top", "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_()