Compare commits

...

31 Commits

Author SHA1 Message Date
9fed1005dd 小调makefile
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-30 22:38:18 +08:00
24131b0050 初步的虚拟机vnc连接 2024-07-30 16:36:45 +08:00
83fd8fffd4 修复下载量统计脚本因没设置timeout导致dpkg卡住的问题(https://gitee.com/gfdgd-xi/deep-wine-runner/issues/IAGG16)
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-30 12:39:02 +08:00
b6f8145fa5 快捷方式工具、wine安装器支持import 2024-07-30 08:26:37 +08:00
1d59f6b4f1 紧急修复arm helper 指定错误bug
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-29 18:22:00 +08:00
ab31fc6d65 补一点注释 2024-07-29 17:23:51 +08:00
c88521a3e7 修复debian10判断glibc版本的问题导致推荐wine的问题 2024-07-29 17:15:49 +08:00
385348a7b9 打包器支持import
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-29 09:09:15 +08:00
3bcff8dd87 支持将窗口作为组件引入 2024-07-29 08:54:38 +08:00
0623b95fca 尝试termux构建
Some checks failed
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Has been cancelled
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Has been cancelled
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Has been cancelled
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Has been cancelled
2024-07-26 20:59:23 +08:00
fbd14710a5 忘记刷新源 2024-07-26 20:44:03 +08:00
9a212ca212 arch自动构建尝试 2024-07-26 20:42:18 +08:00
ee6b166dd7 更新winetricks版本 2024-07-26 20:36:30 +08:00
fcd4e5e610 修改文本注释
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-25 17:13:46 +08:00
dd4785d8e2 加入box64
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-25 09:40:29 +08:00
e6df6addd7 调整自动构建 2024-07-25 09:39:20 +08:00
9e1e898da4 移除部分内容以精简体积
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-24 23:04:11 +08:00
ff60f69565 debug
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
2024-07-24 16:39:29 +08:00
c5577568c9 减小包体积 2024-07-24 14:17:32 +08:00
eb6fe87485 参数错误
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
2024-07-24 12:49:37 +08:00
e9f6847e57 调整 2024-07-24 09:04:55 +08:00
47f1b2a843 调整 2024-07-24 08:47:12 +08:00
d95a4cd9f4 调整
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
2024-07-23 22:55:42 +08:00
e5ffd06b26 调整 2024-07-23 22:48:33 +08:00
f327216855 新增arm 2024-07-23 22:42:23 +08:00
d335a72aee 调整 2024-07-23 22:19:52 +08:00
ee8be07185 调整有误
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
2024-07-23 11:30:16 +08:00
4f08e44ca3 有个地方有个小问题 2024-07-23 10:51:30 +08:00
caa08cfa8d 修复AOSC、Termux无法打开Wine安装器的问题 2024-07-23 10:37:09 +08:00
a4ab3b27d1 调整README
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
2024-07-22 11:38:33 +08:00
4caf1a3be5 小调Makefile 2024-07-22 10:46:14 +08:00
32 changed files with 3551 additions and 4761 deletions

33
.github/workflows/auto-building-pkg.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Auto Building Wine Runnerpkg
run-name: ${{ github.actor }} Auto Building Wine Runnerpkg 🚀
on:
workflow_dispatch:
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
container: archlinux
steps:
- name: Building PKG
env:
GUSER: ${{ secrets.GUSER }}
PASSWORD: ${{ secrets.PASSWORD }}
UPLOADURL: ${{ secrets.UPLOADURL }}
run: |
# 配置环境
pacman -Sy
pacman -S yay git sudo
pacman -S dpkg qt5-base -y
yay install
git clone https://github.com/gfdgd-xi/deep-wine-runner
cd deep-wine-runner
make package-deb -j4
make package-pkg -j4
cd ..
mv spark-deepin-wine-runner*.pkg.tar.zst ../spark-deepin-wine-runner.pkg.tar.zst
- name: upload result
uses: actions/upload-artifact@v3
with:
name: spark-deepin-wine-runner.pkg.tar.zst
path: spark-deepin-wine-runner.pkg.tar.zst

View File

@@ -8,10 +8,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Building DEB
env:
GUSER: ${{ secrets.GUSER }}
PASSWORD: ${{ secrets.PASSWORD }}
UPLOADURL: ${{ secrets.UPLOADURL }}
run: |
# 配置环境
sudo apt update
@@ -22,14 +18,24 @@ jobs:
mv spark-deepin-wine-runner.deb ~
mv spark-deepin-wine-runner-ace.deb ~
- name: upload result
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: spark-deepin-wine-runner.deb
path: /home/runner/spark-deepin-wine-runner.deb
- name: upload result
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: spark-deepin-wine-runner-ace.deb
path: /home/runner/spark-deepin-wine-runner-ace.deb
- name: Building DEB (termux)
run: |
cd deep-wine-runner
make package-deb-termux -j4
mv spark-deepin-wine-runner-termux.deb ~
- name: upload result
uses: actions/upload-artifact@v3
with:
name: spark-deepin-wine-runner-termux.deb
path: /home/runner/spark-deepin-wine-runner-termux.deb

View File

@@ -0,0 +1,76 @@
name: Building Wine Runner Off-line Pages(arm64)
run-name: ${{ github.actor }} Building Wine Runner Off-line Pages(arm64) 🚀
on:
push:
workflow_dispatch:
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- name: Building DEB
run: |
# 获取所需数据
cpu=$(cat /proc/cpuinfo | grep processor | wc -l)
# 配置环境
sudo apt update
sudo apt install python3-requests debootstrap xz-utils qemu-user-static -y
sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools psmisc -y
cd ~
mkdir package
# 部署 chroot 环境
sudo debootstrap --arch=arm64 bookworm debian
wget https://github.com/gfdgd-xi/deep-wine-runner/raw/main/pardus-chroot
sudo cp pardus-chroot /usr/bin
sudo chmod 777 /usr/bin/pardus-chroot
sudo pardus-chroot debian
### 配置容器
## 加入 wine 源
sudo chroot debian apt update
sudo chroot debian apt install sudo gpg wget -y
sudo chroot debian wget https://ryanfortner.github.io/box64-debs/box64.list -O /etc/apt/sources.list.d/box64.list
sudo chroot debian bash -c "wget -qO- https://ryanfortner.github.io/box64-debs/KEY.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/box64-debs-archive-keyring.gpg"
sudo chroot debian apt update
sudo chroot debian apt install box64 wine -y
## 获取 Wine 运行器安装包
git clone https://github.com/gfdgd-xi/deep-wine-runner --depth=1
cd deep-wine-runner
make package-deb
cd ..
url=`python3 deep-wine-runner/off-line-shell/GetNewestDebUrl.py`
#wget $url
mv deep-wine-runner/spark-deepin-wine-runner.deb debian/tmp/wine-runner.deb
## 安装
sudo chroot debian apt install locales /tmp/wine-runner.deb fcitx xfce4-terminal -y
sudo chroot debian apt install libxenmisc4.17 libxenstore4 libxenforeignmemory1 -y
# 构建软件包
mkdir package/runner -p
sudo cp debian/usr/local/bin package -rv
sudo cp debian/usr/bin package -rv
sudo cp debian/usr/lib package -rv
sudo cp debian/usr/share package -rv
#sudo cp debian/usr/lib64 package -rv
sudo cp debian/opt/apps/deepin-wine-runner/* package/runner -rv
# 精简运行器体积
sudo rm -rf package/runner/2048
sudo rm -rf package/runner/geek.exe
sudo rm -rf package/runner/BeCyIconGrabber.exe
sudo rm -rf package/runner/Icon
sudo rm -rf package/runner/RegShot.exe
sudo rm -rf package/runner/novnc
sudo rm -rf package/bin/wine*
cp deep-wine-runner/off-line-shell/run.sh package -rv
cp deep-wine-runner/off-line-shell/bwrap_arm64 package/bwrap -rv
sudo chmod 777 -Rv package ; true
cd package
# 添加 Wine 运行器离线模式标识
touch runner/off-line.lock
tar -cvf ../spark-deepin-wine-runner-off-line.tar *
cd ..
xz -T $cpu spark-deepin-wine-runner-off-line.tar
- name: upload result
uses: actions/upload-artifact@v1
with:
name: spark-deepin-wine-runner-off-line.tar.xz
path: /home/runner/spark-deepin-wine-runner-off-line.tar.xz

View File

@@ -1,16 +1,13 @@
name: Building Wine Runner Off-line Pages(amd64)
run-name: ${{ github.actor }} Building Wine Runner Off-line Pages(amd64) 🚀
on:
push:
workflow_dispatch:
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- name: Building DEB
env:
GUSER: ${{ secrets.GUSER }}
PASSWORD: ${{ secrets.PASSWORD }}
UPLOADURL: ${{ secrets.UPLOADURL }}
run: |
# 获取所需数据
cpu=$(cat /proc/cpuinfo | grep processor | wc -l)
@@ -30,7 +27,7 @@ jobs:
## 加入 wine 源
sudo chroot debian dpkg --add-architecture i386
sudo chroot debian apt update
sudo chroot debian apt install wget -y
sudo chroot debian apt install sudo gpg wget -y
sudo chroot debian wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo chroot debian wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/debian/dists/bookworm/winehq-bookworm.sources
sudo chroot debian apt update
@@ -44,15 +41,22 @@ jobs:
mv deep-wine-runner/spark-deepin-wine-runner.deb debian/tmp/wine-runner.deb
## 安装
sudo chroot debian apt install locales /tmp/wine-runner.deb winehq-devel fcitx xfce4-terminal -y
sudo chroot debian apt install libxenmisc4.17 libxenstore4 libxenforeignmemory1 -y
# 构建软件包
mkdir package/opt -p
mkdir package/runner -p
sudo cp debian/opt/wine-devel package/opt -rv
sudo cp debian/usr/bin package -rv
sudo cp debian/usr/lib package -rv
sudo cp debian/usr/lib32 package -rv
sudo cp debian/usr/share package -rv
sudo cp debian/usr/lib64 package -rv
sudo cp debian/opt/apps/deepin-wine-runner/* package/runner -rv
# 精简运行器体积
sudo rm -rf package/runner/2048
sudo rm -rf package/runner/geek.exe
sudo rm -rf package/runner/BeCyIconGrabber.exe
sudo rm -rf package/runner/Icon
sudo rm -rf package/runner/RegShot.exe
sudo rm -rf package/runner/novnc
sudo rm -rf package/bin/wine*
cp deep-wine-runner/off-line-shell/run.sh package -rv
cp deep-wine-runner/off-line-shell/bwrap_amd64 package/bwrap -rv
sudo chmod 777 -Rv package ; true
@@ -62,17 +66,10 @@ jobs:
tar -cvf ../spark-deepin-wine-runner-off-line.tar *
cd ..
xz -T $cpu spark-deepin-wine-runner-off-line.tar
cp deep-wine-runner/off-line-shell/compression-packager.sh spark-deepin-wine-runner-off-line.sh
cat spark-deepin-wine-runner-off-line.tar.xz >> spark-deepin-wine-runner-off-line.sh
- name: upload result
uses: actions/upload-artifact@v1
with:
name: spark-deepin-wine-runner-off-line.tar.xz
path: /home/runner/spark-deepin-wine-runner-off-line.tar.xz
- name: upload result
uses: actions/upload-artifact@v1
with:
name: spark-deepin-wine-runner-off-line.sh
path: /home/runner/spark-deepin-wine-runner-off-line.sh

View File

@@ -12,6 +12,7 @@
#################
import os
import sys
import globalenv
import traceback
import updatekiller
import PyQt5.QtGui as QtGui
@@ -101,7 +102,10 @@ homePath = os.getenv("HOME")
iconPath = "{}/deepin-wine-runner.svg".format(programPath)
#GetDesktopList(f"{homePath}/.local/share/applications")
#print(desktopList)
app = QtWidgets.QApplication(sys.argv)
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
else:
app = globalenv.get_value("app")
window = QtWidgets.QMainWindow()
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout()
@@ -118,6 +122,7 @@ window.setCentralWidget(widget)
window.setWindowTitle("图标管理")
window.resize(int(window.frameGeometry().width() * 1.5), int(window.frameGeometry().height() * 1.2))
window.setWindowIcon(QtGui.QIcon(f"{programPath}/deepin-wine-runner.svg"))
window.show()
GetDesktopThread()
app.exec_()
if (__name__ == "__main__"):
window.show()
app.exec_()

View File

@@ -8,4 +8,8 @@ import requests
programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string
with open(f"{programPath}/information.json") as file:
version = json.loads(file.read())["Version"]
print(requests.get(base64.b64decode("aHR0cDovLzEyMC4yNS4xNTMuMTQ0L3NwYXJrLWRlZXBpbi13aW5lLXJ1bm5lci9JbnN0YWxsLnBocD9WZXJzaW9uPQ==").decode("utf-8") + version).text)
print(requests.get(base64.b64decode("aHR0cHM6Ly9zb3VyY2Vmb3JnZS5uZXQvcHJvamVjdHMvZGVlcC13aW5lLXJ1bm5lci13aW5lLWRvd25sb2FkL2ZpbGVzL2Rvd25sb2FkLXRpbWUv").decode("utf-8")
+ version
+ base64.b64decode("L2Rvd25sb2Fk").decode("utf-8"),
timeout=5 # timeout 设置为 5S
).text)

View File

@@ -146,7 +146,9 @@ copy-files:
cp -rv RemoveQemuUser.sh deb/opt/apps/deepin-wine-runner
cp -rv InstallBox86.sh deb/opt/apps/deepin-wine-runner
cp -rv InstallRuntime deb/opt/apps/deepin-wine-runner
cp -rv globalenv.py deb/opt/apps/deepin-wine-runner
if [[ ! -d novnc/utils/websockify ]]; then git submodule update --init --recursive ; fi
if [[ ! -d novnc/utils/websockify ]]; then cd novnc/utils; git clone https://github.com/novnc/websockify ; fi
cp -rv novnc deb/opt/apps/deepin-wine-runner
mkdir -pv deb/opt/apps/deepin-wine-runner/entries/
cp -rv deb/usr/share/applications deb/opt/apps/deepin-wine-runner/entries/applications

View File

@@ -30,6 +30,7 @@ Wine 运行器 QQ 交流群762985460
Wine 运行器离线包下载地址https://www.123pan.com/s/pDSKVv-pAJWv.html
### Wine 运行器 For Termux 安装方法以及注意事项
Wine 运行器 For Termux 使用的是独立的安装包,详细见:
见 https://gitee.com/gfdgd-xi/deep-wine-runner-termux
https://github.com/gfdgd-xi/deep-wine-runner-termux
@@ -57,14 +58,6 @@ https://gfdgdxi.lanzouw.com/b0plly5cj
spark-deepin-wine-runner 是普通包spark-deepin-wine-runner-ace 是使用 ace 兼容环境运行的运行器
## Wine 运行器离线包
### X86
123panhttps://www.123pan.com/s/pDSKVv-pAJWv.html
百度网盘https://pan.baidu.com/s/1klBw63tw2_ZQLzmi11dDBw?pwd=7bu5 提取码: 7bu5
诚通网盘http://ctfile.gfdgdxi.top/d/31540479-59254792-909739?p=2061 (访问密码: 2061)
Githubhttps://github.com/gfdgd-xi/deep-wine-runner/releases/
Sourceforgehttps://sourceforge.net/projects/deep-wine-runner/files/
更多需求需要私聊作者定制(有偿服务)
### ARM
需要私聊作者定制(有偿服务)
## 软件架构

View File

@@ -62,7 +62,7 @@ if [[ $? == 0 ]] && [[ -f "$HOME/Qemu/Windows/Windows.qcow2" ]]; then
fi
fi
else
qemuUEFI="-vga virtio -device nec-usb-xhci,id=xhci,addr=0x1b -device usb-tablet,id=tablet,bus=xhci.0,port=1 "
qemuUEFI="-vga virtio -machine usb=on -device usb-tablet "
fi
echo $qemuUEFI
./VM/kvm-ok
@@ -70,9 +70,10 @@ if [[ $? == 0 ]] && [[ -f "$HOME/Qemu/Windows/Windows.qcow2" ]]; then
echo X86 架构,使用 kvm 加速
$qemuMore qemu-system-x86_64 --enable-kvm -cpu host --hda "$HOME/Qemu/Windows/Windows.qcow2" \
-smp $CpuCount,sockets=$CpuSocketNum,cores=$(($CpuCoreNum / $CpuSocketNum)),threads=$(($CpuCount / $CpuCoreNum / $CpuSocketNum)) \
-m ${use}G -display vnc=:5 -display gtk -usb -nic model=rtl8139 $qemuUEFI \
-m ${use}G -display vnc=:5 -display gtk -nic model=rtl8139 $qemuUEFI \
-device AC97 -device ES1370 -device intel-hda -device hda-duplex \
--boot 'splash=VM/boot.jpg,menu=on,splash-time=2000' \
-usb \
> $TMPDIR/tmp/windows-virtual-machine-installer-for-wine-runner-run.log 2>&1 # 最新的 qemu 已经移除参数 -soundhw all
exit
fi
@@ -99,7 +100,7 @@ if [[ $? == 0 ]] && [[ -f "$HOME/Qemu/Windows/Windows.qcow2" ]]; then
echo 不使用 kvm 加速
$qemuPath --hda "$HOME/Qemu/Windows/Windows.qcow2" \
-smp $CpuCount,sockets=$CpuSocketNum,cores=$(($CpuCoreNum / $CpuSocketNum)),threads=$(($CpuCount / $CpuCoreNum / $CpuSocketNum)) \
-m ${use}G -display vnc=:5 -display gtk -usb -nic model=rtl8139 $qemuUEFI \
-m ${use}G -display vnc=:5 -display gtk -nic model=rtl8139 $qemuUEFI \
-device AC97 -device ES1370 -device intel-hda -device hda-duplex \
--boot 'splash=VM/boot.jpg,menu=on,splash-time=2000' \
> $TMPDIR/tmp/windows-virtual-machine-installer-for-wine-runner-run.log 2>&1 # 最新的 qemu 已经移除参数 -soundhw all

View File

@@ -24,11 +24,11 @@ Depends: python3,
fakeroot,
which,
git,
xfwm4,
tigervnc,
proot,
bash
Recommends: winbind,
tigervnc | termux-x11-nightly | termux-x11,
xfwm4,
wimtools | wimlib,
python3-pyquery,
python3-pyqt5.qtwebengine | pyqtwebengine,
@@ -53,7 +53,14 @@ Conflicts: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, s
Replaces: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, spark-deepin-wine-runner-52
Provides: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, spark-deepin-wine-runner-52
Installed-Size: @@SIZE@@
Description: Wine运行器是一个能让Linux用户更加方便地运行Windows应用的程序。原版的 Wine 只能使用命令操作且安装过程较为繁琐对小白不友好。于是该运行器为了解决该痛点内置了对Wine图形化的支持、Wine 安装器、微型应用商店、各种Wine工具、自制的Wine程序打包器、运行库安装工具等。
它同时还内置了基于Qemu/VirtualBox制作的、专供小白使用的Windows虚拟机安装工具可以做到只需下载系统镜像并点击安装即可无需考虑虚拟机的安装、创建、分区等操作也能在非 X86 架构安装 X86 架构的 Windows 操作系统(但是效率较低,可以运行些老系统)。
而且对于部分 Wine 应用适配者来说,提供了图形化的打包工具,以及提供了一些常用工具以及运行库的安装方式,以及能安装多种不同的 Wine 以测试效果,能极大提升适配效率。
且对于 Deepin23 用户做了特别优化,以便能在缺少 i386 运行库的情况下运行 Wine32。同时也为非 X86 架构用户提供了 Box86/64、Qemu User 的安装方式
Description: Wine运行器是一个能让Linux用户更加方便地运行Windows应用的
程序。原版的 Wine 只能使用命令操作,且安装过程较为繁琐,对小白不友好。于是该运行器为了解决
该痛点内置了对Wine图形化的支持、Wine 安装器、微型应用商店、各种Wine工具、自制的
Wine程序打包器、运行库安装工具等。
它同时还内置了基于Qemu/VirtualBox制作的、专供小白使用的Windows虚拟机安装工具
可以做到只需下载系统镜像并点击安装即可,无需考虑虚拟机的安装、创建、分区等操作,也能在非 X86 架
构安装 X86 架构的 Windows 操作系统(但是效率较低,可以运行些老系统)。
而且对于部分 Wine 应用适配者来说,提供了图形化的打包工具,以及提供了一些常用工具以及运行库的安
装方式,以及能安装多种不同的 Wine 以测试效果,能极大提升适配效率。
且对于 Deepin23 用户做了特别优化,以便能在缺少 i386 运行库的情况下运行 Wine32。
同时也为非 X86 架构用户提供了 Box86/64、Qemu User 的安装方式

View File

@@ -108,7 +108,10 @@ fi
# 设置目录权限,让用户可读可写,方便后续删除组件
chmod 777 -R /opt/apps/deepin-wine-runner > /dev/null 2>&1 | true
if [[ ! -f /data/data/com.termux/files/home/.vnc/passwd ]]; then
# 向服务器返回安装数加1不显示内容且忽略错误
python3 /opt/apps/deepin-wine-runner/Download.py $version > /dev/null 2>&1 | true
if [[ ! -f /data/data/com.termux/files/home/.vnc/passwd ]] && [[ -f /data/data/com.termux/files/usr/bin/vncpasswd ]]; then
echo 开始配置 VNCServer
echo 接下来需要设置 VNCServer 的密码
set +e
@@ -121,11 +124,11 @@ if [[ ! -f /data/data/com.termux/files/home/.vnc/passwd ]]; then
echo 设置有误,需重新设置
sleep 1
done
echo 'Wine 运行器安装完成,如果没有设置 $DISPLAY 变量的情况下打开 Wine 运行器'
echo '可以在浏览器输入网址 http://localhost:6080/vnc.html 远程访问'
echo '或者用 VNC 远程工具输入 localhost:5 远程访问'
fi
# 向服务器返回安装数加1不显示内容且忽略错误
python3 /opt/apps/deepin-wine-runner/Download.py $version > /dev/null 2>&1 | true
echo 'Wine 运行器安装完成,如果没有设置 $DISPLAY 变量的情况下打开 Wine 运行器'
echo '可以在浏览器输入网址 http://localhost:6080/vnc.html 远程访问'
echo '或者用 VNC 远程工具输入 localhost:5 远程访问'

View File

@@ -49,7 +49,14 @@ Conflicts: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, s
Replaces: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, spark-deepin-wine-runner-52, spark-deepin-wine-runner-termux
Provides: spark.deepin-venturi-setter, spark-deepin-wine5-application-packer, spark-deepin-wine-runner-52, spark-deepin-wine-runner-termux
Installed-Size: @@SIZE@@
Description: Wine运行器是一个能让Linux用户更加方便地运行Windows应用的程序。原版的 Wine 只能使用命令操作且安装过程较为繁琐对小白不友好。于是该运行器为了解决该痛点内置了对Wine图形化的支持、Wine 安装器、微型应用商店、各种Wine工具、自制的Wine程序打包器、运行库安装工具等。
它同时还内置了基于Qemu/VirtualBox制作的、专供小白使用的Windows虚拟机安装工具可以做到只需下载系统镜像并点击安装即可无需考虑虚拟机的安装、创建、分区等操作也能在非 X86 架构安装 X86 架构的 Windows 操作系统(但是效率较低,可以运行些老系统)。
而且对于部分 Wine 应用适配者来说,提供了图形化的打包工具,以及提供了一些常用工具以及运行库的安装方式,以及能安装多种不同的 Wine 以测试效果,能极大提升适配效率。
且对于 Deepin23 用户做了特别优化,以便能在缺少 i386 运行库的情况下运行 Wine32。同时也为非 X86 架构用户提供了 Box86/64、Qemu User 的安装方式
Description: Wine运行器是一个能让Linux用户更加方便地运行Windows应用的
程序。原版的 Wine 只能使用命令操作,且安装过程较为繁琐,对小白不友好。于是该运行器为了解决
该痛点内置了对Wine图形化的支持、Wine 安装器、微型应用商店、各种Wine工具、自制的
Wine程序打包器、运行库安装工具等。
它同时还内置了基于Qemu/VirtualBox制作的、专供小白使用的Windows虚拟机安装工具
可以做到只需下载系统镜像并点击安装即可,无需考虑虚拟机的安装、创建、分区等操作,也能在非 X86 架
构安装 X86 架构的 Windows 操作系统(但是效率较低,可以运行些老系统)。
而且对于部分 Wine 应用适配者来说,提供了图形化的打包工具,以及提供了一些常用工具以及运行库的安
装方式,以及能安装多种不同的 Wine 以测试效果,能极大提升适配效率。
且对于 Deepin23 用户做了特别优化,以便能在缺少 i386 运行库的情况下运行 Wine32。
同时也为非 X86 架构用户提供了 Box86/64、Qemu User 的安装方式

View File

@@ -19,6 +19,8 @@ import PyQt5.QtCore as QtCore
import PyQt5.QtWidgets as QtWidgets
from DefaultSetting import *
import globalenv
def ShowText(text: str):
if text.replace(" ", "").replace("\n", "") == "":
return
@@ -669,82 +671,94 @@ def BrowserExe():
exePath.setText(filePath[0])
chooseWine = ""
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)
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)
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
version = information["Version"]
window = QtWidgets.QMainWindow()
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout()
exePath = QtWidgets.QLineEdit()
wineChooser = QtWidgets.QComboBox()
browserExeButton = QtWidgets.QPushButton("浏览……")
logText = QtWidgets.QTextBrowser()
logText.setStyleSheet("""
background-color: black;
color: white;
""")
wineChooserList = [
"使用 Deepin Wine8 Stable 打包应用",
"使用 Spark Wine9 wow 打包应用",
"使用 Spark Wine9 打包应用",
"使用 Spark Wine8 打包应用",
"使用 Spark Wine7 Devel 打包应用",
"使用 Deepin Wine6 Stable 打包应用",
"使用 Deepin Wine5 Stable 打包应用",
"使用 Deepin Wine5 打包应用",
"使用 Deepin Wine2 打包应用",
"使用 Spark Wine 打包应用"
]
wineChooserIndex = 2
wineList = ["deepin-wine8-stable", "spark-wine9-wow", "spark-wine9", "spark-wine8", "spark-wine7-devel", "deepin-wine6-stable", "deepin-wine6-vannila", "spark-wine8-wow", "deepin-wine5-stable", "deepin-wine5", "deepin-wine", "spark-wine"]
for i in range(len(wineList)):
if not os.system(f"which '{wineList[i]}'"):
wineChooserIndex = i
break
chooseWine = wineList[wineChooserIndex]
wineChooserList[wineChooserIndex] = f"{wineChooserList[wineChooserIndex]}(推荐,如无特殊需求不建议更换)"
wineChooser.addItems(wineChooserList)
wineChooser.setCurrentIndex(wineChooserIndex)
controlLayout = QtWidgets.QHBoxLayout()
buildButton = QtWidgets.QPushButton("现在打包……")
installCmpleteButton = QtWidgets.QPushButton("安装程序执行完成")
helpButton = QtWidgets.QPushButton("帮助")
installUosPackingTool = QtWidgets.QPushButton("安装维护工具箱(可以安装测试 deb")
browserExeButton.clicked.connect(BrowserExe)
buildButton.clicked.connect(RunBuildThread)
installCmpleteButton.clicked.connect(PressCompleteDownload)
helpButton.clicked.connect(ReadMe)
def InstallUosPackingTool():
if os.system("which spark-store"):
QtWidgets.QMessageBox.critical(window, "提示", "未安装星火应用商店,无法继续\n星火应用商店官网https://spark-app.store/")
return 0
threading.Thread(target=os.system, args=["spark-store spk://store/tools/uos-packaging-tools"]).start()
installUosPackingTool.clicked.connect(InstallUosPackingTool)
installCmpleteButton.setDisabled(True)
controlLayout.addWidget(buildButton)
controlLayout.addWidget(installCmpleteButton)
controlLayout.addWidget(helpButton)
controlLayout.addWidget(installUosPackingTool)
layout.addWidget(QtWidgets.QLabel("选择 EXE"), 0, 0)
layout.addWidget(exePath, 0, 1)
layout.addWidget(browserExeButton, 0, 2)
layout.addWidget(wineChooser, 1, 1)
layout.addLayout(controlLayout, 2, 1)
layout.addWidget(logText, 3, 0, 1, 3)
widget.setLayout(layout)
window.setCentralWidget(widget)
window.setWindowTitle(f"Wine 运行器 {version}——简易打包器")
try:
exePath.setText(sys.argv[1])
except:
pass
else:
app = globalenv.get_value("app")
version = information["Version"]
window = QtWidgets.QMainWindow()
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout()
exePath = QtWidgets.QLineEdit()
wineChooser = QtWidgets.QComboBox()
browserExeButton = QtWidgets.QPushButton("浏览……")
logText = QtWidgets.QTextBrowser()
logText.setStyleSheet("""
background-color: black;
color: white;
""")
wineChooserList = [
"使用 Deepin Wine8 Stable 打包应用",
"使用 Spark Wine9 wow 打包应用",
"使用 Spark Wine9 打包应用",
"使用 Spark Wine8 打包应用",
"使用 Spark Wine7 Devel 打包应用",
"使用 Deepin Wine6 Stable 打包应用",
"使用 Deepin Wine5 Stable 打包应用",
"使用 Deepin Wine5 打包应用",
"使用 Deepin Wine2 打包应用",
"使用 Spark Wine 打包应用"
]
wineChooserIndex = 2
wineList = ["deepin-wine8-stable", "spark-wine9-wow", "spark-wine9", "spark-wine8", "spark-wine7-devel", "deepin-wine6-stable", "deepin-wine6-vannila", "spark-wine8-wow", "deepin-wine5-stable", "deepin-wine5", "deepin-wine", "spark-wine"]
for i in range(len(wineList)):
if not os.system(f"which '{wineList[i]}'"):
wineChooserIndex = i
break
chooseWine = wineList[wineChooserIndex]
wineChooserList[wineChooserIndex] = f"{wineChooserList[wineChooserIndex]}(推荐,如无特殊需求不建议更换)"
wineChooser.addItems(wineChooserList)
wineChooser.setCurrentIndex(wineChooserIndex)
controlLayout = QtWidgets.QHBoxLayout()
buildButton = QtWidgets.QPushButton("现在打包……")
installCmpleteButton = QtWidgets.QPushButton("安装程序执行完成")
helpButton = QtWidgets.QPushButton("帮助")
installUosPackingTool = QtWidgets.QPushButton("安装维护工具箱(可以安装测试 deb")
browserExeButton.clicked.connect(BrowserExe)
buildButton.clicked.connect(RunBuildThread)
installCmpleteButton.clicked.connect(PressCompleteDownload)
helpButton.clicked.connect(ReadMe)
def InstallUosPackingTool():
if os.system("which spark-store"):
QtWidgets.QMessageBox.critical(window, "提示", "未安装星火应用商店,无法继续\n星火应用商店官网https://spark-app.store/")
return 0
threading.Thread(target=os.system, args=["spark-store spk://store/tools/uos-packaging-tools"]).start()
installUosPackingTool.clicked.connect(InstallUosPackingTool)
installCmpleteButton.setDisabled(True)
controlLayout.addWidget(buildButton)
controlLayout.addWidget(installCmpleteButton)
controlLayout.addWidget(helpButton)
controlLayout.addWidget(installUosPackingTool)
layout.addWidget(QtWidgets.QLabel("选择 EXE"), 0, 0)
layout.addWidget(exePath, 0, 1)
layout.addWidget(browserExeButton, 0, 2)
layout.addWidget(wineChooser, 1, 1)
layout.addLayout(controlLayout, 2, 1)
layout.addWidget(logText, 3, 0, 1, 3)
widget.setLayout(layout)
window.setCentralWidget(widget)
window.setWindowTitle(f"Wine 运行器 {version}——简易打包器")
try:
exePath.setText(sys.argv[1])
except:
pass
if (__name__ != "__main__"):
# 设置滚动条
areaScroll = QtWidgets.QScrollArea(window)
areaScroll.setWidgetResizable(True)
areaScroll.setWidget(widget)
areaScroll.setFrameShape(QtWidgets.QFrame.NoFrame)
window.setCentralWidget(areaScroll)
if (__name__ == "__main__"):
window.resize(int(window.frameGeometry().width() * 1.2), int(window.frameGeometry().height() * 1.1))
window.show()
# 设置字体

View File

@@ -27,6 +27,7 @@ import PyQt5.QtWidgets as QtWidgets
from trans import *
from DefaultSetting import *
from Model import *
import globalenv
TMPDIR = os.getenv("TMPDIR")
if (TMPDIR == None):
@@ -414,7 +415,7 @@ class make_deb_threading(QtCore.QThread):
"Architecture": debFirstArch.currentText(),
"Depends": [
f"{wine[wineVersion.currentText()]}, deepin-wine-helper | com.wine-helper.deepin, fonts-wqy-microhei, fonts-wqy-zenhei",
f"{wine[wineVersion.currentText()]}, spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepinwinerunner, fonts-wqy-microhei, fonts-wqy-zenhei"
f"{wine[wineVersion.currentText()]}, spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepin, fonts-wqy-microhei, fonts-wqy-zenhei"
][int(chooseWineHelperValue.isChecked())],
"postinst": ['', f'''#!/bin/bash
PACKAGE_NAME="{e1_text.text()}"
@@ -1360,7 +1361,7 @@ true
print("c")
if os.path.exists(wine[wineVersion.currentText()]):
debInformation[0]["Depends"] = ["deepin-wine-helper | com.wine-helper.deepin",
"spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepinwinerunner"
"spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepin"
][int(chooseWineHelperValue.isChecked())] #+ ["", "libasound2 (>= 1.0.16), libc6 (>= 2.28), libglib2.0-0 (>= 2.12.0), libgphoto2-6 (>= 2.5.10), libgphoto2-port12 (>= 2.5.10), libgstreamer-plugins-base1.0-0 (>= 1.0.0), libgstreamer1.0-0 (>= 1.4.0), liblcms2-2 (>= 2.2+git20110628), libldap-2.4-2 (>= 2.4.7), libmpg123-0 (>= 1.13.7), libopenal1 (>= 1.14), libpcap0.8 (>= 0.9.8), libpulse0 (>= 0.99.1), libudev1 (>= 183), libvkd3d1 (>= 1.0), libx11-6, libxext6, libxml2 (>= 2.9.0), ocl-icd-libopencl1 | libopencl1, udis86, zlib1g (>= 1:1.1.4), libasound2-plugins, libncurses6 | libncurses5 | libncurses, deepin-wine-plugin-virtual\nRecommends: libcapi20-3, libcups2, libdbus-1-3, libfontconfig1, libfreetype6, libglu1-mesa | libglu1, libgnutls30 | libgnutls28 | libgnutls26, libgsm1, libgssapi-krb5-2, libjpeg62-turbo | libjpeg8, libkrb5-3, libodbc1, libosmesa6, libpng16-16 | libpng12-0, libsane | libsane1, libsdl2-2.0-0, libtiff5, libv4l-0, libxcomposite1, libxcursor1, libxfixes3, libxi6, libxinerama1, libxrandr2, libxrender1, libxslt1.1, libxxf86vm1"][]
print("d")
debInformation[0]["run.sh"] = f'''#!/bin/sh
@@ -1918,14 +1919,14 @@ def BrowserHelperConfigPathText():
def ChangeWine():
useInstallWineArch.setEnabled(os.path.exists(wine[wineVersion.currentText()]))
debDepends.setText([f"{wine[wineVersion.currentText()]} | {wine[wineVersion.currentText()]}-bcm | {wine[wineVersion.currentText()]}-dcm | com.{wine[wineVersion.currentText()]}.deepin, deepin-wine-helper | com.wine-helper.deepin, fonts-wqy-microhei, fonts-wqy-zenhei",
f"{wine[wineVersion.currentText()]} | {wine[wineVersion.currentText()]}-bcm | {wine[wineVersion.currentText()]}-dcm | com.{wine[wineVersion.currentText()]}.deepin, spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepinwinerunner, fonts-wqy-microhei, fonts-wqy-zenhei"
f"{wine[wineVersion.currentText()]} | {wine[wineVersion.currentText()]}-bcm | {wine[wineVersion.currentText()]}-dcm | com.{wine[wineVersion.currentText()]}.deepin, spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepin, fonts-wqy-microhei, fonts-wqy-zenhei"
][int(chooseWineHelperValue.isChecked())])
debRecommend.setText("")
helperConfigPathText.setEnabled(chooseWineHelperValue.isChecked())
helperConfigPathButton.setEnabled(chooseWineHelperValue.isChecked())
if os.path.exists(wine[wineVersion.currentText()]):
debDepends.setText(["deepin-wine-helper | com.wine-helper.deepin",
"spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepinwinerunner"
"spark-dwine-helper | store.spark-app.spark-dwine-helper | deepin-wine-helper | com.wine-helper.deepin"
][int(chooseWineHelperValue.isChecked())])
#if "deepin-wine5-stable" in wine[wineVersion.currentText()]:
# debDepends.setText("libasound2 (>= 1.0.16), libc6 (>= 2.28), libglib2.0-0 (>= 2.12.0), libgphoto2-6 (>= 2.5.10), libgphoto2-port12 (>= 2.5.10), libgstreamer-plugins-base1.0-0 (>= 1.0.0), libgstreamer1.0-0 (>= 1.4.0), liblcms2-2 (>= 2.2+git20110628), libldap-2.4-2 (>= 2.4.7), libmpg123-0 (>= 1.13.7), libopenal1 (>= 1.14), libpcap0.8 (>= 0.9.8), libpulse0 (>= 0.99.1), libudev1 (>= 183), libvkd3d1 (>= 1.0), libx11-6, libxext6, libxml2 (>= 2.9.0), ocl-icd-libopencl1 | libopencl1, udis86, zlib1g (>= 1:1.1.4), libasound2-plugins, libncurses6 | libncurses5 | libncurses, deepin-wine-plugin-virtual")
@@ -2366,7 +2367,10 @@ tips = transla.transe("U", """提示:
# 窗口创建
###############
app = QtWidgets.QApplication(sys.argv)
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
else:
app = globalenv.get_value("app")
window = QtWidgets.QMainWindow()
widget = QtWidgets.QWidget()
@@ -2703,7 +2707,8 @@ SetFont(app)
window.setCentralWidget(widget)
# 判断是否为小屏幕,是则设置滚动条并全屏
if (window.frameGeometry().width() > app.primaryScreen().availableGeometry().size().width() * 0.8 or
window.frameGeometry().height() > app.primaryScreen().availableGeometry().size().height() * 0.9):
window.frameGeometry().height() > app.primaryScreen().availableGeometry().size().height() * 0.9 or
__name__ != "__main__"):
# 设置滚动条
areaScroll = QtWidgets.QScrollArea(window)
areaScroll.setWidgetResizable(True)
@@ -2711,8 +2716,8 @@ if (window.frameGeometry().width() > app.primaryScreen().availableGeometry().siz
areaScroll.setFrameShape(QtWidgets.QFrame.NoFrame)
window.setCentralWidget(areaScroll)
window.showMaximized() # 设置全屏
window.show()
window.show()
sys.exit(app.exec_())
# Flag解包只读control和解包全部读取
if (__name__ == "__main__"):
window.show()
sys.exit(app.exec_())
# Flag解包只读control和解包全部读取

20
globalenv.py Normal file
View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# 用于实现主模块的变量可被子模块调用和读取
# 要在主模块和需要的子模块分别 import globalenv
# 然后需要在主模块进行初始化_init子模块不要重复 init
# 接着即可调用 set_value 和 get_value 存放/读取变量
def _init(): #初始化(在主模块初始化,不要在子模块重复 init
global _global_dict
_global_dict = {}
def set_value(key :str, value):
""" 定义一个全局变量 """
_global_dict[key] = value
""" 获得一个全局变量,不存在则返回默认值 """
def get_value(key, defValue=None):
try:
return _global_dict[key]
except KeyError:
return defValue

View File

@@ -1,5 +1,5 @@
{
"Version": "4.0.0",
"Version": "4.0.0.2",
"Time": "未知",
"Thank": [
"感谢 @り哥拽的冇气质° 和 @杨 提供了 3a5000新世界的测试机器",

View File

@@ -3,13 +3,13 @@ CURRENT_DIR=$(dirname $(readlink -f "$0"))
if [[ ! -d $TMPDIR/tmp ]]; then
mkdir -p $TMPDIR/tmp
fi
noVNCOption="--vnc localhost:5901"
noVNCOption="--listen localhost:6080"
VNCServerOption="-localhost yes"
if [[ -f $HOME/.config/deepin-wine-runner/vnc-public ]]; then
unset noVNCOption
unset VNCServerOption
fi
if [[ $DISPLAY == "" ]] && [[ $WAYLAND_DISPLAY == "" ]]; then
if [[ $DISPLAY == "" ]] && [[ $WAYLAND_DISPLAY == "" ]] && [[ -f /data/data/com.termux/files/usr/bin/vncpasswd ]]; then
# 自动配置 NoVNC
export DISPLAY=:5
vncserver -kill :5

View File

@@ -50,8 +50,13 @@ from trans import *
from Model import *
from DefaultSetting import *
import globalenv
def PythonLower():
app = QtWidgets.QApplication(sys.argv)
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
else:
app = globalenv.get_value("app")
QtWidgets.QMessageBox.critical(None, "错误", "Python 至少需要 3.6 及以上版本,目前版本:" + platform.python_version() + "")
sys.exit(1)
@@ -448,7 +453,7 @@ def about_this_program()->"显示“关于这个程序”窗口":
if i[-4:] == ".svg" or i[-4:] == ".png":
iconPathList.append(f"{programPath}/Icon/{k}/{i}")
except:
traceback.print_exec()
traceback.print_exc()
randomNumber = random.randint(0, len(iconPathList) - 1)
iconShow.setText(f"<a href='https://www.gfdgdxi.top'><img width=256 src='{iconPathList[randomNumber]}'></a><p align='center'>{randomNumber + 1}/{len(iconPathList)}</p>")
iconShow.linkActivated.connect(ChangeIcon)
@@ -2456,7 +2461,6 @@ def CheckWine():
traceback.print_exc()
except:
traceback.print_exc()
app = QtWidgets.QApplication(sys.argv)
QtWidgets.QMessageBox.critical(None, "错误", f"无法读取配置,无法继续\n{traceback.format_exc()}")
sys.exit(1)
CheckWine()
@@ -2468,7 +2472,10 @@ print(wine)
###########################
# 程序信息
###########################
app = QtWidgets.QApplication(sys.argv)
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
else:
app = globalenv.get_value("app")
trans = QtCore.QTranslator()
transeObject = QtCore.QObject()
transla = QtCore.QCoreApplication.translate
@@ -2615,7 +2622,7 @@ else:
#<h1>©2020~{time.strftime("%Y")} <a href="https://gitee.com/gfdgd-xi">By gfdgd xi</h1>'''
updateThings = "{} 更新内容:\n{}\n更新时间:{}".format(version, updateThingsString, updateTime, time.strftime("%Y"))
try:
threading.Thread(target=requests.get, args=[parse.unquote(base64.b64decode("aHR0cDovLzEyMC4yNS4xNTMuMTQ0L3NwYXJrLWRlZXBpbi13aW5lLXJ1bm5lci9vcGVuL0luc3RhbGwucGhw").decode("utf-8")) + "?Version=" + version]).start()
threading.Thread(target=requests.get, args=[parse.unquote(base64.b64decode("aHR0cHM6Ly9zb3VyY2Vmb3JnZS5uZXQvcHJvamVjdHMvZGVlcC13aW5lLXJ1bm5lci13aW5lLWRvd25sb2FkL2ZpbGVzL29wZW4tdGltZS8=").decode("utf-8")) + version + base64.b64decode("L2Rvd25sb2Fk").decode("utf-8")]).start()
except:
pass
iconListUnBuild = json.loads(readtxt(f"{programPath}/IconList.json"))[0]
@@ -3381,8 +3388,10 @@ SetFont(setting["FontSize"])
window.setCentralWidget(widget)
# 判断是否为小屏幕,是则设置滚动条并全屏
# 获取为 import 为控件,也默认开启滚动条
if (window.frameGeometry().width() > app.primaryScreen().availableGeometry().size().width() * 0.8 or
window.frameGeometry().height() > app.primaryScreen().availableGeometry().size().height() * 0.9):
window.frameGeometry().height() > app.primaryScreen().availableGeometry().size().height() * 0.9 or
__name__ != "__main__"):
# 设置滚动条
areaScroll = QtWidgets.QScrollArea(window)
areaScroll.setWidgetResizable(True)
@@ -3390,9 +3399,13 @@ if (window.frameGeometry().width() > app.primaryScreen().availableGeometry().siz
areaScroll.setFrameShape(QtWidgets.QFrame.NoFrame)
window.setCentralWidget(areaScroll)
window.showMaximized() # 设置全屏
window.show()
# Mini 模式
# MiniMode(True)
sys.exit(app.exec_())
if (__name__ == "__main__"):
window.show()
# Mini 模式
# MiniMode(True)
sys.exit(app.exec_())

44
novnc-client/main.py Normal file
View File

@@ -0,0 +1,44 @@
import sys
import logging
from PyQt5.QtWidgets import QApplication, QMainWindow
from qvncwidget import QVNCWidget
#logging.basicConfig(level=logging.DEBUG) # DEBUG及以上的日志信息都会显示
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("QVNCWidget")
self.vnc = QVNCWidget(
parent=self,
host="127.0.0.1", port=5905,
readOnly=False
)
self.setCentralWidget(self.vnc)
# you can disable mouse tracking if desired
self.vnc.setMouseTracking(True)
self.setAutoFillBackground(True)
self.vnc.start()
def keyPressEvent(self, ev):
self.vnc.keyPressEvent(ev)
return super().keyPressEvent(ev) # in case you need the signal somewhere else in the window
def keyReleaseEvent(self, ev):
self.vnc.keyReleaseEvent(ev)
return super().keyReleaseEvent(ev) # in case you need the signal somewhere else in the window
def closeEvent(self, ev):
self.vnc.stop()
return super().closeEvent(ev)
app = QApplication(sys.argv)
window = Window()
window.resize(800, 600)
window.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
from .qvncwidget import QVNCWidget

View File

@@ -0,0 +1,237 @@
#! /usr/bin/env python3
import struct
##
# reading
##
def read_float_buff(buffer, big_endian=False) -> float:
if big_endian:
return struct.unpack(">f", buffer.read(4))[0]
else:
return struct.unpack("<f", buffer.read(4))[0]
def read_double_buff(buffer, big_endian=False) -> float:
if big_endian:
return struct.unpack(">d", buffer.read(8))[0]
else:
return struct.unpack("<d", buffer.read(8))[0]
def read_uint8_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">B", buffer.read(1))[0]
else:
return struct.unpack("<B", buffer.read(1))[0]
def read_uint16_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">H", buffer.read(2))[0]
else:
return struct.unpack("<H", buffer.read(2))[0]
def read_uint32_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">I", buffer.read(4))[0]
else:
return struct.unpack("<I", buffer.read(4))[0]
def read_uint64_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">Q", buffer.read(8))[0]
else:
return struct.unpack("<Q", buffer.read(8))[0]
def read_sint8_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">b", buffer.read(1))[0]
else:
return struct.unpack("<b", buffer.read(1))[0]
def read_sint16_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">h", buffer.read(2))[0]
else:
return struct.unpack("<h", buffer.read(2))[0]
def read_sint32_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">i", buffer.read(4))[0]
else:
return struct.unpack("<i", buffer.read(4))[0]
def read_sint64_buff(buffer, big_endian=False) -> int:
if big_endian:
return struct.unpack(">q", buffer.read(8))[0]
else:
return struct.unpack("<q", buffer.read(8))[0]
##
# writing
##
def write_float_buff(buffer, value: float, big_endian=False) -> None:
buffer.write(return_float_bytes(value, big_endian))
def write_double_buff(buffer, value: float, big_endian=False) -> None:
buffer.write(return_double_bytes(value, big_endian))
def write_uint8_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_uint8_bytes(value, big_endian))
def write_uint16_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_uint16_bytes(value, big_endian))
def write_uint32_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_uint32_bytes(value, big_endian))
def write_uint64_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_uint64_bytes(value, big_endian))
def write_sint8_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_sint8_bytes(value, big_endian))
def write_sint16_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_sint16_bytes(value, big_endian))
def write_sint32_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_sint32_bytes(value, big_endian))
def write_sint64_buff(buffer, value: int, big_endian=False) -> None:
buffer.write(return_sint64_bytes(value, big_endian))
##
# return bytes
##
def return_float_bytes(value: float, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">f", value)
else:
return struct.pack("<f", value)
def return_double_bytes(value: float, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">d", value)
else:
return struct.pack("<d", value)
def return_uint8_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">B", value)
else:
return struct.pack("<B", value)
def return_uint16_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">H", value)
else:
return struct.pack("<H", value)
def return_uint32_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">I", value)
else:
return struct.pack("<I", value)
def return_uint64_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">Q", value)
else:
return struct.pack("<Q", value)
def return_sint8_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">b", value)
else:
return struct.pack("<b", value)
def return_sint16_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">h", value)
else:
return struct.pack("<h", value)
def return_sint32_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">i", value)
else:
return struct.pack("<i", value)
def return_sint64_bytes(value: int, big_endian=False) -> bytes:
if big_endian:
return struct.pack(">q", value)
else:
return struct.pack("<q", value)
##
# return val
##
def return_float_val(data: bytes, big_endian=False) -> float:
if big_endian:
return struct.unpack(">f", data)[0]
else:
return struct.unpack("<f", data)[0]
def return_double_val(data: bytes, big_endian=False) -> float:
if big_endian:
return struct.unpack(">d", data)[0]
else:
return struct.unpack("<d", data)[0]
def return_uint8_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">B", data)[0]
else:
return struct.unpack("<B", data)[0]
def return_uint16_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">H", data)[0]
else:
return struct.unpack("<H", data)[0]
def return_uint32_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">I", data)[0]
else:
return struct.unpack("<I", data)[0]
def return_uint64_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">Q", data)[0]
else:
return struct.unpack("<Q", data)[0]
def return_sint8_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">b", data)[0]
else:
return struct.unpack("<b", data)[0]
def return_sint16_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">h", data)[0]
else:
return struct.unpack("<h", data)[0]
def return_sint32_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">i", data)[0]
else:
return struct.unpack("<i", data)[0]
def return_sint64_val(data: bytes, big_endian=False) -> int:
if big_endian:
return struct.unpack(">q", data)[0]
else:
return struct.unpack("<q", data)[0]

View File

@@ -0,0 +1,690 @@
"""
Qt Widget for displaying VNC framebuffer using RFB protocol
(c) zocker-160 2024
licensed under GPLv3
"""
import logging
import time
from PyQt5.QtCore import (
QSize,
Qt,
pyqtSignal,
QSemaphore
)
from PyQt5.QtGui import (
QImage,
QPaintEvent,
QPainter,
QColor,
QBrush,
QPixmap,
QResizeEvent,
QKeyEvent,
QMouseEvent
)
from PyQt5.QtWidgets import (
QWidget,
QLabel,
QWidget,
QOpenGLWidget
)
from qvncwidget.rfb import RFBClient
from qvncwidget.rfbhelpers import RFBPixelformat, RFBInput
log = logging.getLogger("QVNCWidget")
class QVNCWidget(QWidget, RFBClient):
onInitialResize = pyqtSignal(QSize)
def __init__(self, parent: QWidget,
host: str, port = 5900, password: str = None,
readOnly = False):
super().__init__(
parent=parent,
host=host, port=port, password=password
)
self.readOnly = readOnly
self.backbuffer: QImage = None
self.frontbuffer: QImage = None
self.setMouseTracking(not self.readOnly)
self.setMinimumSize(1, 1) # make window scalable
self.mouseButtonMask = 0
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
def onConnectionMade(self):
log.info("VNC handshake done")
self.setPixelFormat(RFBPixelformat.getRGB32())
self.PIX_FORMAT = QImage.Format.Format_RGB32
self.backbuffer = QImage(self.vncWidth, self.vncHeight, self.PIX_FORMAT)
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
if self.backbuffer is None:
log.warning("backbuffer is None")
return
else:
log.debug("drawing backbuffer")
#with open(f"{width}x{height}.data", "wb") as f:
# f.write(data)
t1 = time.time()
painter = QPainter(self.backbuffer)
painter.drawImage(x, y, QImage(data, width, height, self.PIX_FORMAT))
painter.end()
log.debug(f"painting took: {(time.time() - t1)*1e3} ms")
del painter
del data
def onFramebufferUpdateFinished(self):
log.debug("FB Update finished")
self.update()
def paintEvent(self, a0: QPaintEvent):
#log.debug("Paint event")
painter = QPainter(self)
if self.backbuffer is None:
log.debug("backbuffer is None")
painter.fillRect(0, 0, self.width(), self.height(), Qt.GlobalColor.black)
else:
self.frontbuffer = self.backbuffer.scaled(
self.width(), self.height(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
painter.drawImage(0, 0, self.frontbuffer)
painter.end()
# Mouse events
def mousePressEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
self.mouseButtonMask = RFBInput.fromQMouseEvent(ev, True, self.mouseButtonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
self.mouseButtonMask = RFBInput.fromQMouseEvent(ev, False, self.mouseButtonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
try:
# 忽略拖动导致的问题
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
except:
pass
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
xPos = (ev.localPos().x() / self.frontbuffer.width()) * self.vncWidth
yPos = (ev.localPos().y() / self.frontbuffer.height()) * self.vncHeight
return int(xPos), int(yPos)
# Key events
def keyPressEvent(self, ev: QKeyEvent):
if self.readOnly: return
self.keyEvent(RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def keyReleaseEvent(self, ev: QKeyEvent):
if self.readOnly: return
self.keyEvent(RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# other experimental implementations
class QVNCWidgetGL(QOpenGLWidget, RFBClient):
IMG_FORMAT = QImage.Format_RGB32
onInitialResize = pyqtSignal(QSize)
#onUpdatePixmap = pyqtSignal(int, int, int, int, bytes)
onUpdatePixmap = pyqtSignal()
onSetPixmap = pyqtSignal()
onKeyPress = pyqtSignal(QKeyEvent)
onKeyRelease = pyqtSignal(QKeyEvent)
def __init__(self, parent,
host, port=5900, password: str=None,
mouseTracking=False):
#super(QOpenGLWidget, self).__init__()
#super(RFBClient, self).__init__(
super().__init__(
parent=parent,
host=host,
port=port,
password=password,
daemonThread=True
)
#self.setAlignment(Qt.AlignCenter)
#self.onUpdatePixmap.connect(self._updateImage)
#self.onSetPixmap.connect(self._setImage)
self.onSetPixmap.connect(self._updateImage)
self.acceptMouseEvents = False # mouse events are not accepted at first
self.setMouseTracking(mouseTracking)
# Allow Resizing
self.setMinimumSize(1, 1)
self.data = list(tuple())
self.dataMonitor = QSemaphore(0)
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
def onConnectionMade(self):
log.info("VNC handshake done")
self.setPixelFormat(RFBPixelformat.getRGB32())
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
self._initKeypress()
self._initMouse()
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
#img = QImage(data, width, height, self.IMG_FORMAT)
#self.onUpdatePixmap.emit(x, y, width, height, data)
#self.dataMonitor.acquire(1)
self.data.append((x, y, width, height, data))
#self.data = (x, y, width, height, data)
#self.dataMonitor.release(1)
#self.onUpdatePixmap.emit()
#else:
# print("AAAAAAAAAAAAAA", "MONITOR AQUIRE FAILED")
def onFramebufferUpdateFinished(self):
self.onSetPixmap.emit()
return
if self.pixmap:
#self.setPixmap(QPixmap.fromImage(self.image))
self.resizeEvent(None)
def onFatalError(self, error: Exception):
log.error(str(error))
#logging.exception(str(error))
#self.reconnect()
#def _updateImage(self, x: int, y: int, width: int, height: int, data: bytes):
def _updateImage(self):
print("update image")
self.update()
#if not self.screen:
# self.screen = QImage(width, height, self.IMG_FORMAT)
# self.screen.fill(Qt.red)
# self.screenPainter = QPainter(self.screen)
#self.painter.beginNativePainting()
#self.painter.drawPixmapFragments()
#with open("/tmp/images/test.raw", "wb") as f:
# f.write(data)
#p = QPainter(self.screen)
#self.screenPainter.drawImage(
# x, y, QImage(data, width, height, self.IMG_FORMAT))
#p.end()
#self.repaint()
#self.update()
def _setPixmap(self):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
)
def _setImage(self):
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
))
self.acceptMouseEvents = True # mouse events are getting accepted
# Passed events
def _keyPress(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def _keyRelease(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# Window events
def paintEvent(self, e: QPaintEvent):
print("paint event")
#self.dataMonitor.acquire(1)
#while self.dataMonitor.tryAcquire(1):
while len(self.data) > 0:
x, y, w, h, data = self.data.pop(0)
p = QPainter(self)
#p.setPen(QColor(255, 0, 0))
#p.drawText(e.rect(), Qt.AlignCenter, str(self.dataMonitor.available()))
p.drawImage(x, y, QImage(data, w, h, self.IMG_FORMAT))
p.end()
#self.dataMonitor.release(1)
return
p = QPainter(self)
p.fillRect(e.rect(), QBrush(QColor(255, 255, 255)))
p.end()
return
if self.dataMonitor.tryAcquire(1):
x, y, w, h, data = self.data
p = QPainter(self)
p.drawImage(x, y, QImage(data, w, h, self.IMG_FORMAT))
p.end()
self.dataMonitor.release(1)
print("CCCCC", "Image painted diggah")
else:
print("BBBBBBBBB", "aquire monitor failed")
#return super().paintEvent(a0)
return
if not self.screen:
self.screen = QImage(self.size(), self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
p = QPainter()
p.begin(self)
p.drawImage(0, 0,
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
p.end()
def resizeEvent(self, e: QResizeEvent):
return super().resizeEvent(e)
def resizeGL(self, w: int, h: int):
print("RESIZE THAT BITCH!!!", w, h)
#return super().resizeGL(w, h)
def resizeEvent_(self, a0: QResizeEvent):
#print("RESIZE!", self.width(), self.height())
#return super().resizeEvent(a0)
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
)
return super().resizeEvent(a0)
def mousePressEvent(self, ev: QMouseEvent):
#print(ev.localPos(), ev.button())
#print(self.height() - self.pixmap().height())
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, True, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, False, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
# FIXME: The pixmap is assumed to be aligned center.
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
# FIXME: this code is ugly as fk
# y coord is kinda fucked up
yDiff = (self.height() - self.pixmap().height()) / 2
yPos = ev.localPos().y() - yDiff
if yPos < 0: yPos = 0
if yPos > self.pixmap().height(): yPos = self.pixmap().height()
yPos = self._calcRemoteRel(
yPos, self.pixmap().height(), self.vncHeight)
# x coord is kinda fucked up, too
xDiff = (self.width() - self.pixmap().width()) / 2
xPos = ev.localPos().x() - xDiff
if xPos < 0: xPos = 0
if xPos > self.pixmap().width(): xPos = self.pixmap().width()
xPos = self._calcRemoteRel(
xPos, self.pixmap().width(), self.vncWidth)
return xPos, yPos
def _calcRemoteRel(self, locRel, locMax, remoteMax) -> int:
return int( (locRel / locMax) * remoteMax )
def _initMouse(self):
self.buttonMask = 0 # pressed buttons (bit fields)
def _initKeypress(self):
self.onKeyPress.connect(self._keyPress)
self.onKeyRelease.connect(self._keyRelease)
def __del__(self):
self.stop()
def __exit__(self, *args):
self.stop()
self.deleteLater()
class QVNCWidget_old(QLabel, RFBClient):
IMG_FORMAT = QImage.Format_RGB32
onInitialResize = pyqtSignal(QSize)
onUpdatePixmap = pyqtSignal(int, int, int, int, bytes)
onSetPixmap = pyqtSignal()
onKeyPress = pyqtSignal(QKeyEvent)
onKeyRelease = pyqtSignal(QKeyEvent)
def __init__(self, parent,
host, port=5900, password: str=None,
mouseTracking=False):
super().__init__(
parent=parent,
host=host,
port=port,
password=password,
daemonThread=True
)
#import faulthandler
#faulthandler.enable()
self.screen: QImage = None
# FIXME: The pixmap is assumed to be aligned center.
self.setAlignment(Qt.AlignCenter)
self.onUpdatePixmap.connect(self._updateImage)
self.onSetPixmap.connect(self._setImage)
self.acceptMouseEvents = False # mouse events are not accepted at first
self.setMouseTracking(mouseTracking)
# Allow Resizing
self.setMinimumSize(1,1)
def _initMouse(self):
self.buttonMask = 0 # pressed buttons (bit fields)
def _initKeypress(self):
self.onKeyPress.connect(self._keyPress)
self.onKeyRelease.connect(self._keyRelease)
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
if self.screenPainter: self.screenPainter.end()
def onConnectionMade(self):
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
self.setPixelFormat(RFBPixelformat.getRGB32())
self._initKeypress()
self._initMouse()
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
#img = QImage(data, width, height, self.IMG_FORMAT)
self.onUpdatePixmap.emit(x, y, width, height, data)
def onFramebufferUpdateFinished(self):
self.onSetPixmap.emit()
return
if self.pixmap:
#self.setPixmap(QPixmap.fromImage(self.image))
self.resizeEvent(None)
def onFatalError(self, error: Exception):
log.error(str(error))
#logging.exception(str(error))
#self.reconnect()
def _updateImage(self, x: int, y: int, width: int, height: int, data: bytes):
if not self.screen:
self.screen = QImage(width, height, self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
#self.painter.beginNativePainting()
#self.painter.drawPixmapFragments()
#with open("/tmp/images/test.raw", "wb") as f:
# f.write(data)
#p = QPainter(self.screen)
self.screenPainter.drawImage(
x, y, QImage(data, width, height, self.IMG_FORMAT))
#p.end()
#self.repaint()
#self.update()
def _drawPixmap(self, x: int, y: int, pix: QPixmap):
#self.paintLock.acquire()
self.pixmap = pix
if not self.painter:
self.painter = QPainter(self.pixmap)
else:
print("DRAW PIXMAP:", x, y, self.pixmap, self.painter, pix, pix.isNull())
self.painter.drawPixmap(x, y, self.pixmap)
#self.paintLock.release()
def _drawPixmap2(self, x: int, y: int, pix: QPixmap, data: bytes):
if not self.pixmap or (
x == 0 and y == 0 and
pix.width() == self.pixmap.width() and pix.height() == self.pixmap.height()):
self.pixmap = pix.copy()
self._setPixmap()
return
import time
print("DRAW PIXMAP:", x, y, self.pixmap.width(), self.pixmap.height(), pix.width(), pix.height())
_t = time.time()
#self.pixmap.save(f"/tmp/images/imgP_{_t}", "jpg")
#with open(f"/tmp/images/img_{_t}.raw", "wb") as f:
# f.write(data)
#pix.save(f"/tmp/images/img_{_t}", "jpg")
painter = QPainter(self.pixmap)
painter.drawPixmap(x, y, pix)
painter.end()
#self._setPixmap()
def _setPixmap(self):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
)
def _setImage(self):
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
))
self.acceptMouseEvents = True # mouse events are getting accepted
# Passed events
def _keyPress(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def _keyRelease(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# Window events
def paintEvent(self, a0: QPaintEvent):
return super().paintEvent(a0)
if not self.screen:
self.screen = QImage(self.size(), self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
p = QPainter()
p.begin(self)
p.drawImage(0, 0,
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
p.end()
def resizeEvent(self, a0: QResizeEvent):
#print("RESIZE!", self.width(), self.height())
#return super().resizeEvent(a0)
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
)
return super().resizeEvent(a0)
def mousePressEvent(self, ev: QMouseEvent):
#print(ev.localPos(), ev.button())
#print(self.height() - self.pixmap().height())
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, True, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, False, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
# FIXME: The pixmap is assumed to be aligned center.
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
# FIXME: this code is ugly as fk
# y coord is kinda fucked up
yDiff = (self.height() - self.pixmap().height()) / 2
yPos = ev.localPos().y() - yDiff
if yPos < 0: yPos = 0
if yPos > self.pixmap().height(): yPos = self.pixmap().height()
yPos = self._calcRemoteRel(
yPos, self.pixmap().height(), self.vncHeight)
# x coord is kinda fucked up, too
xDiff = (self.width() - self.pixmap().width()) / 2
xPos = ev.localPos().x() - xDiff
if xPos < 0: xPos = 0
if xPos > self.pixmap().width(): xPos = self.pixmap().width()
xPos = self._calcRemoteRel(
xPos, self.pixmap().width(), self.vncWidth)
return xPos, yPos
def _calcRemoteRel(self, locRel, locMax, remoteMax) -> int:
return int( (locRel / locMax) * remoteMax )
def __del__(self):
self.stop()
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
self.deleteLater()

View File

@@ -0,0 +1,485 @@
"""
RFB protocol implementation, client side
(c) zocker-160 2024
licensed under GPLv3
References:
- http://www.realvnc.com/docs/rfbproto.pdf
- https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst
"""
from qvncwidget.rfbhelpers import RFBPixelformat, RFBRectangle
from qvncwidget.rfbdes import RFBDes
import qvncwidget.rfbconstants as c
import qvncwidget.easystruct as es
from threading import Thread
import logging
import socket
from socket import SHUT_RDWR
import struct as s
import time
import threading
class RFBUnexpectedResponse(Exception):
pass
class RFBNoResponse(Exception):
pass
class RFBUnknownVersion(Exception):
pass
class RFBHandshakeFailed(Exception):
pass
class VNCAuthentificationFailed(Exception):
pass
SUPPORTED_VERSIONS = [
(3,3)
]
KNOWN_VERSIONS = [
(3,3), (3,6), (3,7), (3,8),
(4,0), (4,1),
(5,0)
]
"""
3.3: official minimum version
3.6: UltraVNC
3.7: official
3.8: official
4.0: Intel AMT KVM
4.1: RealVNC 4.6
5.0: RealVNC 5.3
"""
SUPPORTED_ENCODINGS = [
c.ENC_RAW
]
MAX_BUFF_SIZE: int = 10*1024*1024 # 10MB
class RFBClient:
log = logging.getLogger("RFB Client")
logc = logging.getLogger("RFB -> Server")
logs = logging.getLogger("RFB Client <-")
pixformat: RFBPixelformat
numRectangles = 0
#rectanglePositions = list() # list[RFBRectangle]
_stop = False
_connected = False
_requestFrameBufferUpdate = False
_incrementalFrameBufferUpdate = True
def __init__(self, host, port = 5900,
password: str = None,
sharedConnection = True,
keepRequesting = True,
requestIncremental = True):
self.host = host
self.port = port
self.password = password
self.sharedConn = sharedConnection
self._requestFrameBufferUpdate = keepRequesting
self._incrementalFrameBufferUpdate = requestIncremental
self._mainLoop: Thread = None
def __recv(self, expectedSize: int = None, maxSize=MAX_BUFF_SIZE) -> bytes:
if not expectedSize:
buffer = self.connection.recv(4096)
else:
buffer = self.connection.recv(expectedSize, socket.MSG_WAITALL)
if len(buffer) <= 50:
self.logs.debug(f"len: {len(buffer)} | {buffer}")
else:
self.logs.debug(f"{len(buffer)} Bytes | {len(buffer)//1024} KB")
return buffer
def __send(self, data: bytes):
self.connection.send(data)
self.logc.debug(data)
def __start(self):
self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connection.connect( (self.host, self.port) )
self._handleInitial()
def __close(self):
self.log.debug("Closing connection")
if self.connection:
try:
self.connection.shutdown(SHUT_RDWR)
self.connection.close()
except OSError:
self.log.debug("TCP Connection already closed")
def _handleInitial(self):
buffer = self.__recv(12)
if b'\n' in buffer and buffer.startswith(b'RFB'):
maj, min = [int(x) for x in buffer[3:-1].split(b'.')]
self.log.info(f"RFB from server: {maj}.{min}")
if (maj, min) not in KNOWN_VERSIONS:
raise RFBUnknownVersion(f"Unknown RFB version by server: {maj}.{min}")
if (maj, min) not in SUPPORTED_VERSIONS:
# request highest supported version
# TODO: requested version must not be higher than
# the one offered by the server
maj, min = SUPPORTED_VERSIONS[-1]
else:
self.__close()
raise RFBUnknownVersion(buffer)
self.version_maj, self.version_min = maj, min
# request supported RFB version
self.__send(f"RFB 00{maj}.00{min}\n".encode())
self.log.info("VNC connected")
if (maj, min) == (3,3):
self._handleAuth33(self.__recv(4))
else:
self.log.error(f"Missing AUTH implementation for {maj}.{min}")
def _handleAuth33(self, data: bytes):
"""
Handle security handshake for protocol version 3.3.
In this version, the server decides the protocol (failed, none or VNCAuth)
"""
auth = es.return_uint32_val(data, True)
if auth == c.AUTH_FAIL:
self._handleConnFailed(self.__recv(4))
elif auth == c.AUTH_NONE:
self._doClientInit()
elif auth == c.AUTH_VNCAUTH:
self._handleVNCAuth(self.__recv(16))
else:
self.__close()
raise RFBUnexpectedResponse(f"Unknown auth response {auth}")
def _doClientInit(self):
shared = 1 if self.sharedConn else 0
self.__send(es.return_uint8_bytes(shared, True))
self._handleServerInit(self.__recv(24))
def _handleServerInit(self, data: bytes):
try:
self.vncWidth, self.vncHeight, pixformat, namelen = s.unpack("!HH16sI", data)
except s.error as e:
self.log.error("Handshake failed")
self.__close()
raise RFBHandshakeFailed(e)
threading.Thread(target=a).start()
self.desktopname = self.__recv(namelen).decode()
self.log.debug(f"Connecting to \"{self.desktopname}\"")
pixformatData = s.unpack("!BBBBHHHBBBxxx", pixformat)
self.pixformat = RFBPixelformat(*pixformatData)
self.log.debug(f"Server Pixelformat: {self.pixformat}")
self.log.debug(f"Resolution: {self.vncWidth}x{self.vncHeight}")
# this should not be required, but some VNC servers (like QT QPA VNC)
# require this to send FramebufferUpdate
self.setEncodings(SUPPORTED_ENCODINGS)
self.onConnectionMade()
self._connected = True
# enter main request loop
self._mainRequestLoop()
def _handleVNCAuth(self, data: bytes):
self._VNCAuthChallenge = data
self.log.info("Requesting password")
self.vncRequestPassword()
self._handleVNCAuthResult(self.__recv(4))
def _handleVNCAuthResult(self, data: bytes):
try:
result = es.return_uint32_val(data)
except s.error as e:
raise VNCAuthentificationFailed(f"Authentication failed ({str(e)})")
self.log.debug(f"Auth result {result}")
if result == c.SMSG_AUTH_OK:
self._doClientInit()
elif result == c.SMSG_AUTH_FAIL:
if self.version_min > 7:
self._handleVNCAuthError(self.__recv(4))
else:
raise VNCAuthentificationFailed("Authentication failed")
elif result == c.SMSG_AUTH_TOOMANY:
raise VNCAuthentificationFailed("Too many login attempts")
else:
self.log.error(f"Unknown Auth response ({result})")
def _handleVNCAuthError(self, data: bytes):
waitfor = es.return_uint32_val(data)
raise VNCAuthentificationFailed(
f"Authentication failed ({self.__recv(waitfor)})")
def _handleConnFailed(self, data: bytes):
waitfor = es.return_uint32_val(data)
resp = self.__recv(waitfor)
self.__close()
raise RFBHandshakeFailed(resp)
# ------------------------------------------------------------------
## Main request loop
# ------------------------------------------------------------------
def _mainRequestLoop(self):
time.sleep(0.2)
# first request is non incremental
self.framebufferUpdateRequest(incremental=False)
while not self._stop and self.connection:
try:
dType = self.__recv(1)
# when self.connection.close() is being called
# dType will be empty with length of 0
if len(dType) == 0:
continue
start = time.time()
self._handleConnection(dType)
self.log.debug(f"processing update took: {(time.time() - start)*1e3} ms")
except socket.timeout:
self.log.debug("timeout triggered")
continue
except s.error as e:
self.log.exception(str(e))
continue
except Exception as e:
self.onFatalError(e)
#print("AAA")
if self._requestFrameBufferUpdate:
self.framebufferUpdateRequest(
incremental=self._incrementalFrameBufferUpdate)
#print("BBB")
self.log.debug("loop exit")
# ------------------------------------------------------------------
## Server -> Client messages
# ------------------------------------------------------------------
def _handleConnection(self, data: bytes):
msgid = es.return_uint8_val(data)
if msgid == c.SMSG_FBUPDATE:
# Framebuffer Update
self._handleFramebufferUpdate(self.__recv(3))
elif msgid == c.SMSG_BELL:
# bell
self.onBell()
elif msgid == c.SMSG_SERVERCUTTEXT:
# server cut text
self._handleServerCutText(self.__recv(7))
elif msgid == c.SMSG_SETCOLORMAP:
# set color map entries
pass
else:
self.log.warning(f"Unknown message type recieved (id {msgid})")
raise RFBUnexpectedResponse
def _handleServerCutText(self, data: bytes):
datalength = s.unpack("!xxxI", data)[0]
data = self.__recv(datalength)
self.log.debug(f"Server clipboard: {data}")
# TODO: create callback
def _handleFramebufferUpdate(self, data: bytes):
numRectangles = s.unpack("!xH", data)[0]
self.log.debug(f"numRectangles: {numRectangles}")
self.onBeginUpdate()
for _ in range(numRectangles):
self._handleRectangle(self.__recv(12))
self.onFramebufferUpdateFinished()
def _handleRectangle(self, data: bytes):
xPos, yPos, width, height, encoding = s.unpack("!HHHHI", data)
rect = RFBRectangle(xPos, yPos, width, height)
self.log.debug(f"RECT: {rect}")
if encoding == c.ENC_RAW:
size = (width*height*self.pixformat.bitspp) // 8
self.log.debug(f"expected size: {size}")
start = time.time()
data = self.__recv(expectedSize=size)
self.log.debug(f"fetching data took: {(time.time() - start)*1e3} ms")
self._decodeRAW(data, rect)
del data
else:
raise TypeError(f"Unsupported encoding received ({encoding})")
# ------------------------------------------------------------------
## Image decoding stuff
# ------------------------------------------------------------------
def _decodeRAW(self, data: bytes, rectangle: RFBRectangle):
self.onRectangleUpdate(*rectangle.asTuple(), data)
# ------------------------------------------------------------------
## Client -> Server messages
# ------------------------------------------------------------------
def setPixelFormat(self, pixelformat: RFBPixelformat):
self.pixformat = pixelformat
pformat = s.pack("!BBBBHHHBBBxxx", *pixelformat.asTuple())
self.__send(s.pack("!Bxxx16s", c.CMSG_SETPIXELFORMAT, pformat))
def setEncodings(self, encodings: list):
self.__send(s.pack("!BxH", c.CMSG_SETENCODINGS, len(encodings)))
for encoding in encodings:
self.__send(es.return_sint32_bytes(encoding, True))
def framebufferUpdateRequest(self,
xPos=0, yPos=0, width=None, height=None,
incremental=False):
if not width: width = self.vncWidth - xPos
if not height: height = self.vncHeight - yPos
inc = 1 if incremental else 0
self.__send(s.pack(
"!BBHHHH",
c.CMSG_FBUPDATEREQ, inc,
xPos, yPos, width, height))
def keyEvent(self, key, down=1):
"""
For most ordinary keys, the "keysym" is the same as the corresponding ASCII value.
Other common keys are shown in the KEY_ constants
"""
self.log.debug(f'keyEvent: {key}, {"down" if down else "up"}')
self.__send(s.pack(
"!BBxxI",
c.CMSG_KEYEVENT, down, key))
def pointerEvent(self, x: int, y: int, buttommask=0):
"""
Indicates either pointer movement or a pointer button press or release. The pointer is
now at (x-position, y-position), and the current state of buttons 1 to 8 are represented
by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed)
"""
if not self._connected: return
self.log.debug(f"pointerEvent: {x}, {y}, {buttommask}")
self.__send(s.pack(
"!BBHH",
c.CMSG_POINTEREVENT, buttommask, x, y))
# ------------------------------------------------------------------
## Direct Calls
# ------------------------------------------------------------------
def startConnection(self):
self._mainLoop = Thread(target=self.__start)
self._mainLoop.start()
def sendPassword(self, password):
if type(password) is str:
password = password.encode("ascii")
password = (password + bytes(8))[:8]
des = RFBDes(password)
self.__send(des.encrypt(self._VNCAuthChallenge))
def reconnect(self):
self.closeConnection()
self.startConnection()
def closeConnection(self):
self._stop = True
self._connected = False
self.__close()
if self._mainLoop and self._mainLoop.is_alive():
self.log.debug("waiting for main loop to exit")
self._mainLoop.join()
# ------------------------------------------------------------------
## Callbacks
# ------------------------------------------------------------------
def onConnectionMade(self):
"""
connection is initialized and ready
the pixel format and encodings can be set here using
setPixelFormat() and setEncodings()
the RFB main update loop will start after this function is done
"""
def onBeginUpdate(self):
"""
called before a series of updateRectangle(),
copyRectangle() or fillRectangle().
"""
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
"""
new bitmap data. data are bytes in the pixel format set
up earlier.
"""
def onFramebufferUpdateFinished(self):
"""
called after a series of updateRectangle(), copyRectangle()
or fillRectangle() are finished.
"""
def onBell(self):
"""
a bell, yes that's right a BELL
"""
def vncRequestPassword(self):
"""
a password is needed to log on, use sendPassword() to
send one.
"""
if not self.password:
raise VNCAuthentificationFailed("No password specified")
else:
self.sendPassword(self.password)
def onFatalError(self, error: Exception):
"""
called when fatal error occurs
which caused the main loop to crash
you can try to reconnect here with reconnect()
"""
raise error

View File

@@ -0,0 +1,165 @@
from PyQt5.QtCore import Qt
## Encoding Type for SetEncodings()
# publicly documented
ENC_RAW = 0 # Raw
ENC_COPYRECT = 1 # CopyRect
ENC_RRE = 2 # RRE
ENC_HEXTILE = 5 # Hextile
ENC_TRLE = 15 # TRLE
ENC_ZRLE = 16 # ZRLE
# pseudo-encodings
ENC_CURSOR = -239 # Cursor position pseudo-encoding
ENC_DESKTOPSIZE = -223 # DesktopSize pseudo-encoding
# additional
ENC_CORRE = 4
ENC_ZLIB = 6
ENC_TIGHT = 7
ENC_ZLIBHEX = 8
## Keycodes for KeyEvent()
KEY_BackSpace = 0xff08
KEY_Tab = 0xff09
KEY_Return = 0xff0d
KEY_Escape = 0xff1b
KEY_Insert = 0xff63
KEY_Delete = 0xffff
KEY_Home = 0xff50
KEY_End = 0xff57
KEY_PageUp = 0xff55
KEY_PageDown = 0xff56
KEY_Left = 0xff51
KEY_Up = 0xff52
KEY_Right = 0xff53
KEY_Down = 0xff54
KEY_F1 = 0xffbe
KEY_F2 = 0xffbf
KEY_F3 = 0xffc0
KEY_F4 = 0xffc1
KEY_F5 = 0xffc2
KEY_F6 = 0xffc3
KEY_F7 = 0xffc4
KEY_F8 = 0xffc5
KEY_F9 = 0xffc6
KEY_F10 = 0xffc7
KEY_F11 = 0xffc8
KEY_F12 = 0xffc9
KEY_F13 = 0xFFCA
KEY_F14 = 0xFFCB
KEY_F15 = 0xFFCC
KEY_F16 = 0xFFCD
KEY_F17 = 0xFFCE
KEY_F18 = 0xFFCF
KEY_F19 = 0xFFD0
KEY_F20 = 0xFFD1
KEY_ShiftLeft = 0xffe1
KEY_ShiftRight = 0xffe2
KEY_ControlLeft = 0xffe3
KEY_ControlRight = 0xffe4
KEY_MetaLeft = 0xffe7
KEY_MetaRight = 0xffe8
KEY_AltLeft = 0xffe9
KEY_AltRight = 0xffea
KEY_Scroll_Lock = 0xFF14
KEY_Sys_Req = 0xFF15
KEY_Num_Lock = 0xFF7F
KEY_Caps_Lock = 0xFFE5
KEY_Pause = 0xFF13
KEY_Super_L = 0xFFEB
KEY_Super_R = 0xFFEC
KEY_Hyper_L = 0xFFED
KEY_Hyper_R = 0xFFEE
KEY_KP_0 = 0xFFB0
KEY_KP_1 = 0xFFB1
KEY_KP_2 = 0xFFB2
KEY_KP_3 = 0xFFB3
KEY_KP_4 = 0xFFB4
KEY_KP_5 = 0xFFB5
KEY_KP_6 = 0xFFB6
KEY_KP_7 = 0xFFB7
KEY_KP_8 = 0xFFB8
KEY_KP_9 = 0xFFB9
KEY_KP_Enter = 0xFF8D
# thanks to ken3 (https://github.com/ken3) for this
KEY_TRANSLATION_SPECIAL = {
Qt.Key.Key_Backspace: KEY_BackSpace,
Qt.Key.Key_Tab: KEY_Tab,
Qt.Key.Key_Return: KEY_Return,
Qt.Key.Key_Escape: KEY_Escape,
Qt.Key.Key_Insert: KEY_Insert,
Qt.Key.Key_Delete: KEY_Delete,
Qt.Key.Key_Home: KEY_Home,
Qt.Key.Key_End: KEY_End,
Qt.Key.Key_PageUp: KEY_PageUp,
Qt.Key.Key_PageDown: KEY_PageDown,
Qt.Key.Key_Left: KEY_Left,
Qt.Key.Key_Up: KEY_Up,
Qt.Key.Key_Right: KEY_Right,
Qt.Key.Key_Down: KEY_Down,
Qt.Key.Key_F1: KEY_F1,
Qt.Key.Key_F2: KEY_F2,
Qt.Key.Key_F3: KEY_F3,
Qt.Key.Key_F4: KEY_F4,
Qt.Key.Key_F5: KEY_F5,
Qt.Key.Key_F6: KEY_F6,
Qt.Key.Key_F7: KEY_F7,
Qt.Key.Key_F8: KEY_F8,
Qt.Key.Key_F9: KEY_F9,
Qt.Key.Key_F10: KEY_F10,
Qt.Key.Key_F11: KEY_F11,
Qt.Key.Key_F12: KEY_F12,
Qt.Key.Key_F13: KEY_F13,
Qt.Key.Key_F14: KEY_F14,
Qt.Key.Key_F15: KEY_F15,
Qt.Key.Key_F16: KEY_F16,
Qt.Key.Key_F17: KEY_F17,
Qt.Key.Key_F18: KEY_F18,
Qt.Key.Key_F19: KEY_F19,
Qt.Key.Key_F20: KEY_F20,
Qt.Key.Key_Shift: KEY_ShiftLeft,
Qt.Key.Key_Control: KEY_ControlLeft,
Qt.Key.Key_Meta: KEY_MetaLeft,
Qt.Key.Key_Alt: KEY_AltLeft,
Qt.Key.Key_ScrollLock: KEY_Scroll_Lock,
Qt.Key.Key_SysReq: KEY_Sys_Req,
Qt.Key.Key_NumLock: KEY_Num_Lock,
Qt.Key.Key_CapsLock: KEY_Caps_Lock,
Qt.Key.Key_Pause: KEY_Pause,
Qt.Key.Key_Super_L: KEY_Super_L,
Qt.Key.Key_Super_R: KEY_Super_R,
Qt.Key.Key_Hyper_L: KEY_Hyper_L,
Qt.Key.Key_Hyper_R: KEY_Hyper_R,
Qt.Key.Key_Enter: KEY_KP_Enter,
}
# Authentication protocol types
AUTH_FAIL = 0
AUTH_NONE = 1
AUTH_VNCAUTH = 2
# Authentication result types
SMSG_AUTH_OK = 0
SMSG_AUTH_FAIL = 1
SMSG_AUTH_TOOMANY = 2
# Server message types
SMSG_FBUPDATE = 0
SMSG_SETCOLORMAP = 1
SMSG_BELL = 2
SMSG_SERVERCUTTEXT = 3
# Client message types
CMSG_SETPIXELFORMAT = 0
CMSG_SETENCODINGS = 2
CMSG_FBUPDATEREQ = 3
CMSG_KEYEVENT = 4
CMSG_POINTEREVENT = 5
CMSG_CLIENTCUTTEXT = 6

View File

@@ -0,0 +1,19 @@
import pyDes
class RFBDes(pyDes.des):
def setKey(self, key):
"""
RFB protocol for authentication requires client to encrypt
challenge sent by server with password using DES method. However,
bits in each byte of the password are put in reverse order before
using it as encryption key.
"""
newkey = list()
for bsrc in key:
btgt = 0
for i in range(8):
if bsrc & (1 << i):
btgt = btgt | (1 << 7-i)
newkey.append(btgt)
super(RFBDes, self).setKey(newkey)

View File

@@ -0,0 +1,106 @@
import logging
import qvncwidget.rfbconstants as c
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtCore import Qt
class RFBPixelformat:
def __init__(self,
bpp=32, depth=24, bigendian=False, truecolor=True,
redmax=255, greenmax=255, bluemax=255,
redshift=0, greenshift=0, blueshift=16):
self.bitspp = bpp
self.depth = depth
self.bigendian = 1 if bigendian else 0
self.truecolor = 1 if truecolor else 0
self.redmax = redmax
self.greenmax = greenmax
self.bluemax = bluemax
self.redshift = redshift
self.greenshift = greenshift
self.blueshift = blueshift
@staticmethod
def getRGB32():
return RFBPixelformat(
bpp=32, depth=32,
redshift=16, greenshift=8, blueshift=0
)
@staticmethod
def getRGB16():
return RFBPixelformat(
bpp=16, depth=16,
redmax=31, greenmax=63, bluemax=31,
redshift=11, greenshift=5, blueshift=0
)
@staticmethod
def getRGB555():
return RFBPixelformat(
bpp=16, depth=15,
redmax=31, greenmax=31, bluemax=31,
redshift=10, greenshift=5, blueshift=0
)
def asTuple(self) -> tuple:
return (
self.bitspp, self.depth, self.bigendian, self.truecolor,
self.redmax, self.greenmax, self.bluemax,
self.redshift, self.greenshift, self.blueshift
)
def __str__(self) -> str:
return ";".join(str(x) for x in self.asTuple())
class RFBRectangle:
def __init__(self, xPos: int, yPos: int, width: int, height: int):
self.xPos = xPos
self.yPos = yPos
self.width = width
self.height = height
def asTuple(self) -> tuple:
return (self.xPos, self.yPos, self.width, self.height)
def __str__(self) -> str:
return f"x: {self.xPos} y: {self.yPos} width: {self.width} height: {self.height}"
class RFBInput:
# thanks to ken3 (https://github.com/ken3) for this
MOUSE_MAPPING = {
Qt.LeftButton: 1 << 0,
Qt.MidButton: 1 << 1,
Qt.RightButton: 1 << 2,
}
@staticmethod
def fromQKeyEvent(eventID: int, eventStr: str) -> int:
rfbKey = c.KEY_TRANSLATION_SPECIAL.get(eventID)
if not rfbKey:
try:
rfbKey = ord(eventStr)
except TypeError:
logging.warning(f"Unknown keytype: {eventID} | {eventStr}")
return 0
return rfbKey
@staticmethod
def fromQMouseEvent(eventID: QMouseEvent, pressEvent: bool, mask) -> int:
_mask = RFBInput.MOUSE_MAPPING.get(eventID.button())
# FIXME: return previous bitmask in case unknown key is pressed
# TODO: implement all RFB supported buttons
if not _mask: return mask
if pressEvent:
return mask | _mask
else:
return mask & ~_mask

View File

@@ -2,43 +2,19 @@
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
# /opt 目录识别
option=""
for path in `ls /opt`
do
echo /opt/$path
if [[ $path != wine-staging ]]; then
# 支持识别正确的 wine
mkdir -pv "$SHELL_FOLDER/opt/$path"
option="$option --dev-bind /opt/$path /opt/$path"
fi
done
wineName=(deepin-wine
deepin-wine8-stable
deepin-wine6-stable
deepin-wine5-stable
spark-wine
spark-wine8
deepin-wine6-vannila
spark-wine7-devel
spark-wine8-wow
deepin-wine5
ukylin-wine
okylin-wine
bookworm-run
)
for i in ${wineName[*]}; do
if [[ -e /usr/bin/$i ]]; then
option="$option --dev-bind /usr/bin/$i /usr/bin/$i"
if [[ ! -e "$SHELL_FOLDER/bin/$i" ]]; then
touch "$SHELL_FOLDER/bin/$i"
fi
fi
done
if [[ -d /usr/lib32 ]] && [[ -d $SHELL_FOLDER/lib32 ]]; then
option="$option --dev-bind $SHELL_FOLDER/lib32 /usr/lib32 "
fi
if [[ -d /usr/lib64 ]] && [[ -d $SHELL_FOLDER/lib64 ]]; then
option="$option --dev-bind $SHELL_FOLDER/lib64 /usr/lib64 "
fi
"$SHELL_FOLDER/bwrap" --dev-bind / / \
--dev-bind "$SHELL_FOLDER/opt" /opt \
--dev-bind "$SHELL_FOLDER/bin" /usr/bin \
--dev-bind "$SHELL_FOLDER/lib" /usr/lib \
--dev-bind "$SHELL_FOLDER/lib32" /usr/lib32 \
--dev-bind "$SHELL_FOLDER/lib64" /usr/lib64 \
--dev-bind /usr/lib/locale /usr/lib/locale \
--dev-bind "$SHELL_FOLDER/share" /usr/share \
$option \
$SHELL_FOLDER/runner/deepin-wine-runner
$SHELL_FOLDER/runner/deepin-wine-runner $*

View File

@@ -1,5 +1,5 @@
{
"Version": "4.0.0",
"Version": "4.0.0.2",
"Time": "未知",
"Thank": [
"感谢 @り哥拽的冇气质° 和 @杨 提供了 3a5000新世界的测试机器",

18
test.py Normal file
View File

@@ -0,0 +1,18 @@
import os
import sys
import globalenv
import PyQt5.QtWidgets as QtWidgets
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
# globalenv 的 init 是必须的,这样才能正确的 import Wine 运行器的窗口
globalenv._init()
globalenv.set_value("app", app) # 用于将该部分的 app 给子模块的 Qt 控件调用以解决 UI 异常以及其它问题
#import deepin_wine_packager
#modules = __import__("deepin-wine-packager")
#modules = __import__("deepin-wine-easy-packager")
#import mainwindow
# 使用 __import__ 可以引入带 - 文件名的模块
import wine.installwine
window.setCentralWidget(wine.installwine.window)
window.show()
app.exec_()

View File

@@ -21,6 +21,7 @@ import webbrowser
programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string
sys.path.append(f"{programPath}/../")
from Model import *
import globalenv
from PyQt5 import QtCore, QtGui, QtWidgets
programPath = os.path.split(os.path.realpath(__file__))[0] # 返回 string
# UI 布局(自动生成)
@@ -230,7 +231,29 @@ class GetInfo():
"/usr/lib/mips64el-linux-gnuabi64/ld.so.1",
"/usr/lib/powerpc64le-linux-gnu/ld64.so.2",
"/usr/lib/loongarch64-linux-gnu/ld-linux-loongarch-lp64d.so.1",
"/usr/lib/loongarch64-linux-gnu/ld.so.1"
"/usr/lib/loongarch64-linux-gnu/ld.so.1",
"/usr/lib/ld-linux.so.2",
"/usr/lib/ld-linux-x86-64.so.2",
"/usr/lib/ld-linux-armhf.so.3",
"/usr/lib/ld-linux.so.3",
"/usr/lib/ld-linux-aarch64.so.1",
"/usr/lib/ld-linux-aarch64.so.1",
"/usr/lib/ld-linux-riscv64-lp64d.so.1",
"/usr/lib/ld.so.1",
"/usr/lib/ld64.so.2",
"/usr/lib/ld-linux-loongarch-lp64d.so.1",
"/usr/lib64/ld.so.1",
"/usr/lib64/ld-linux.so.2",
"/usr/lib64/ld-linux-x86-64.so.2",
"/usr/lib64/ld-linux-armhf.so.3",
"/usr/lib64/ld-linux.so.3",
"/usr/lib64/ld-linux-aarch64.so.1",
"/usr/lib64/ld-linux-aarch64.so.1",
"/usr/lib64/ld-linux-riscv64-lp64d.so.1",
"/usr/lib64/ld.so.1",
"/usr/lib64/ld64.so.2",
"/usr/lib64/ld-linux-loongarch-lp64d.so.1",
"/usr/lib64/ld.so.1"
]
for i in glibcPathList:
if (os.path.exists(i)):
@@ -250,11 +273,15 @@ class GetInfo():
if (self.compareVersion(newestVersion, i) == -1):
newestVersion = i
version = newestVersion
if (version == None):
version = "2.28" # 默认值
self.glibcVersion = version
return version
def compareVersion(self, version1: str, version2: str):
# 判断是不是版本号
if (type(version1) != str or type(version2) != str):
return -2
if ((not "." in version1) or (not "." in version2)):
return -2
if (version1 == version2):
@@ -324,8 +351,13 @@ class GetInfo():
result = True
if (data == "termux" and self.isTermux):
result = True
if (self.compareVersion(self.glibcVersion, data) == 1):
result = True
try:
if (self.compareVersion(self.glibcVersion, data) == 1 or
self.compareVersion(self.glibcVersion, data) == 0):
result = True
except:
# 如果检查 tag 出现问题,强制返回符合条件
return True
if (os.path.exists(data)):
result = True
if (no):
@@ -553,65 +585,69 @@ def on_downloadWineFromCloudDisk_clicked():
def get_now_lang()->"获取当前语言":
return os.getenv('LANG')
if __name__ == "__main__":
localJsonList = []
internetJsonList = []
internetWineSourceList = [
"http://wine.wine-runner.gfdgdxi.top/",
"http://wine.wine-runner.gfdgdxi.top/sourceforge",
"https://gitee.com/gfdgd-xi/wine-mirrors-websize/raw/master/",
"https://github.com/gfdgd-xi/wine-mirrors-websize/raw/master/",
"http://127.0.0.1/wine-mirrors/" # 本地测试源
]
localJsonList = []
internetJsonList = []
internetWineSourceList = [
"http://wine.wine-runner.gfdgdxi.top/",
"http://wine.wine-runner.gfdgdxi.top/sourceforge",
"https://gitee.com/gfdgd-xi/wine-mirrors-websize/raw/master/",
"https://github.com/gfdgd-xi/wine-mirrors-websize/raw/master/",
"http://127.0.0.1/wine-mirrors/" # 本地测试源
]
if (__name__ == "__main__"):
app = QtWidgets.QApplication(sys.argv)
# 读取翻译
if not get_now_lang() == "zh_CN.UTF-8":
trans = QtCore.QTranslator()
trans.load(f"{programPath}/../LANG/installwine-en_US.qm")
app.installTranslator(trans)
# 获取信息
systemInfo = GetInfo()
# 窗口构建
window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
window.setWindowIcon(QtGui.QIcon(f"{programPath}/../deepin-wine-runner.svg"))
ui.setupUi(window)
else:
app = globalenv.get_value("app")
# 读取翻译
if not get_now_lang() == "zh_CN.UTF-8":
trans = QtCore.QTranslator()
trans.load(f"{programPath}/../LANG/installwine-en_US.qm")
app.installTranslator(trans)
# 获取信息
systemInfo = GetInfo()
# 窗口构建
window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
window.setWindowIcon(QtGui.QIcon(f"{programPath}/../deepin-wine-runner.svg"))
ui.setupUi(window)
if (__name__ == "__main__"):
window.show()
# 隐藏选项
ui.unzip.setVisible(False)
ui.deleteZip.setVisible(False)
# 判断机器所在国家并自动分配源
try:
isChina = requests.get("https://ip.useragentinfo.com/json").json()["country"] == "中国"
print("IsChina", isChina)
if isChina:
internetWineSource = internetWineSourceList[0]
ui.ipv6Action.setChecked(True)
else:
internetWineSource = internetWineSourceList[1]
ui.kgithubAction.setChecked(True)
except:
traceback.print_exc()
print("IsChina", False)
# 请求失败,默认使用国际源
# 隐藏选项
ui.unzip.setVisible(False)
ui.deleteZip.setVisible(False)
# 判断机器所在国家并自动分配源
try:
isChina = requests.get("https://ip.useragentinfo.com/json").json()["country"] == "中国"
print("IsChina", isChina)
if isChina:
internetWineSource = internetWineSourceList[0]
ui.ipv6Action.setChecked(True)
else:
internetWineSource = internetWineSourceList[1]
ui.kgithubAction.setChecked(True)
# 连接信号
ui.addButton.clicked.connect(on_addButton_clicked)
ui.delButton.clicked.connect(on_delButton_clicked)
ui.downloadWineFromCloudDisk.clicked.connect(on_downloadWineFromCloudDisk_clicked)
ui.addOtherWine.clicked.connect(InstallOtherWine)
ui.changeSourcesGroup.triggered.connect(ChangeSources)
ui.m_recommendWine.clicked.connect(ReadInternetInformation)
## 加载内容
# 设置列表双击不会编辑
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"))
except:
traceback.print_exc()
print("IsChina", False)
# 请求失败,默认使用国际源
internetWineSource = internetWineSourceList[1]
ui.kgithubAction.setChecked(True)
# 连接信号
ui.addButton.clicked.connect(on_addButton_clicked)
ui.delButton.clicked.connect(on_delButton_clicked)
ui.downloadWineFromCloudDisk.clicked.connect(on_downloadWineFromCloudDisk_clicked)
ui.addOtherWine.clicked.connect(InstallOtherWine)
ui.changeSourcesGroup.triggered.connect(ChangeSources)
ui.m_recommendWine.clicked.connect(ReadInternetInformation)
## 加载内容
# 设置列表双击不会编辑
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"))
if (__name__ == "__main__"):
app.exec_()

1
wine/installwine.py Symbolic link
View File

@@ -0,0 +1 @@
installwine

5856
winetricks

File diff suppressed because it is too large Load Diff