为Rust+QT编程搭建【伪】win32开发环境
细心的读者一定会注意到文章标题内的【伪】字。哎!实属无奈。Rust Qt Binding对win32编译环境实在不友善。在windows操作系统上,qmetaobject始终编译失败 --- 这是一个已备案的缺陷。幸运的是,我本地操作系统是Windows 10 x64,所以还有一条“曲线救国”的“狗血”技术路线。概括地讲,
在Windows 10 x64上,安装Ubuntu-20.04子系统(绝不是VM,而是WSL2)。即是传说中的Windows Subsystem for Linux 2。
在Ubuntu-20.04子系统内,安装全套的
rustup工具链
Qt SDK
题外话,即便安装一个nwjs for Linux,其也是能够正常运行的。
将Windows 10 x64上的VSCode远程连接至Ubuntu-20.04。这样一来,
既将VSCode人机交互界面继续保留在Windows端 --- 开发体验不降级。
又将rustc编译与Qt link都迁移至Linux端执行 --- 绕开qmetaobject对windows编译环境的不兼容。
将Ubuntu-20.04的图形界面投影至Windows 10 x64。以便,能够在windows 10环境上,观察GUI程序的修改效果 --- 这还是为保持开发体验不降级。
在功能开发结束之后,在target/release和target/debug目录下的就是Linux版的GUI应用程序分发包。
需要借助【交叉编译】,才能在target/x86_64-pc-windows-msvc文件夹内,获得Windows版的GUI应用程序分发包。
下面就是这一出既“狗血”又超级麻烦的自虐之旅的详细内容。希望世界变得更平,别让我这样的Windows狗活得那么辛苦。
为什么选择QT?
前不久,我写的另一篇文章为Rust原生gui编程,搭建win32开发环境分享了如何将Rust链接Gnome.GTK3实现GUI应用程序开发。对照两款图形界面解决方案,Gnome.GTK3上手容易,文档齐备,社区活跃。但是,QT强烈吸引我的有两点:
覆盖全平台。
从【桌面】,到【移动端】,再到【嵌入式设备】
从Windows,到Linux与Mac,再到Android与iOS
虽然眼下Rust Qt Binding对windows操作系统的兼容性还有不足,但我相信这仅只是临时缺陷,会被尽快修复。
可移植性强。
至少它依赖的动态链接库里没有与win32内置dll重名的。Gnome.GTK3的这个毛病可是把我给恶心到了。我之前可是下了佬大的时间与精力才定位出此crash的原因。
我甚至有一个不成熟的想法:“QT才是前端开发者最终的技术归宿”,因为它:
全平台
高性能
亲和IoT --- 还是有相当多的IoT设备是拥有独立屏显的。不可能在所有的IoT硬件上都运行一个web service等着别人用浏览器访问(这不是高端玩家的作法)。
不过在这里,我还是想分别对Gnome.GTK3与QT分别吐槽三点:
Gnome.GTK3对·新人上手·真是没得说、太棒了。但是,其【高级组件】(比如,webview)不支持windows平台太挫伤开发者的深度使用热情了。
QT几乎无所不能。但是,Rust Qt Binding对win32编译环境的适用性太差。这对普通玩家的入手门槛有些高了。
最后,上面两个问题都不是新问题,而都是陈年老梗了。官方怎么对这些缺陷的解决这么不上心呀?
为什么选择qmetaobject?
qmetaobject是QT官方团队维护的项目。我相信其后续迭代有保障。QT官宣软文看这里。
你不会以为其它的Rust Qt Binding解决方案对win32编译环境友善吧?Naively! 事实上,以下几款Rust Qt Binding开源项目,我都试过了。它们中没一个对win32友好的。此时,我脸上只有一个表情就是“悲愤”。世界一点儿也不平!
ritual
qmlrs
qml-rust
劝退理由
整个开发环境包括子系统Ubuntu-20.04 (WSL)和宿主Windows 10 x64两个部分。其对开发环境的软件与硬件条件都有一点儿要求:
低版本的Windows操作系统不具备WSL2功能。所以,如果你的操作系统是Windows 7,推荐先研究一下如何免费地升级到Windows 10。
从微软应用程序市场下载安装的Ubuntu-20.04 (WSL2)仅只是一个Linux Kernel(大约1.2GB)。所以,后续需要安装的软件包非常地多。你的系统盘足够大吗?若系统盘的存储余额小于25GB,推荐先研究一下【系统盘】“搬家”以腾出足够的空间折腾。
开发环境搭建概述
被用来验证开发环境的Rust + QT工程是来自官方的演示例程todos
整个安装配置大约是以下若干步:
安装与配置Ubuntu-20.04 (WSL2)
配置宿主端Windows 10
回到Ubuntu-20.04 (WSL2)安装Qt SDK。因为QT的连线安装程序是GUI的,所以必须先在第二步解锁【从WSL2子系统向宿主系统投影图形界面】的技能。
从Ubuntu-20.04端,使用VSCode打开todos工程。
从VSCode集成终端,执行cargo run弹出应用程序窗口,验证开发环境正常工作。
开发环境搭建之子系统端 - Ubuntu-20.04 (WSL2)
安装Ubuntu 20.04 (WSL2)子系统
我给WSL2子系统安装Ubuntu不是因为Ubuntu有什么独一无二的技术优势;而仅是,相对于CentOS,Ubuntu 20.04子系统不要钱。在微软应用程序商店,除了Mac以外(知识产权保护)的桌面系统都有WSL2镜像安装包。它们之间最大的差别就是【收费】与【免费】。
再次劝退
Windows Subsystem for Linux 2会被强制安装于系统C盘。所以,在安装Ubuntu 20.04 (WSL2)子系统之前,请先确认你的系统盘是否还有足够的剩余空间。在我的使用场景里,子系统满载体量大约14GB(以后,可能还会更大)。虽然WSL2既好看又好用,但它是有成本的。
Ubuntu 20.04 (WSL2)子系统与Windows 10 x64宿主系统之间的关系
硬盘关系
子系统的硬盘对宿主系统是不可见。
宿主系统的硬盘是被逐个mounted到Ubuntu 20.04 (WSL2)【挂载点】/mnt虚拟文件夹下,所以宿主系统文件系统对子系统皆是可见的,且被视作外部存储设备。
环境变量关系
子系统会继承(甚至,重写)宿主系统的全部环境变量 --- 这个潜在地造成了混乱,我后面会给出规避方式。
宿主系统访问不到子系统内的环境变量。
程序执行关系
子系统可运行宿主系统的.exe文件。比如,ipconfig.exe --- 后面会用到。
宿主系统既看不到,也执行不了子系统的可执行文件。
安装步骤
以【管理员】身份运行PowerShell
激活Windows Subsystem for Linux 2功能
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
下载与安装Linux Kernel 更新包
将WSL2设置成为默认启动项
wsl --set-default-version 2
从【微软应用程序商店】搜索Ubuntu 20.04 LTS并安装之。除了Ubuntu外,还提供有CentOS子系统。但,这是收费的。
WSL2子系统基本操作
开机
wsl -d Ubuntu-20.04
在第一次启动Ubuntu 20.04 LTS子系统时,你会被要求创建一个管理员账号。根据提示,你照做就是了。
关机
wsl --shutdown
# 或
wsl -t Ubuntu-20.04
打开运行中子系统的终端
wsl
列出所有已安装的子系统
wsl -l -v
上面所有操作在cmd和PowerShell均可完成。但,我还是推荐安装windows terminal。
配置Ubuntu 20.04 (WSL2)子系统
启动Ubuntu 20.04 (WSL2)子系统
打开cmd或PowerShell终端,执行
wsl -d Ubuntu-20.04
在第一次启动Ubuntu 20.04 LTS子系统时,你会被要求创建一个管理员账号。根据提示,你照做就是了。
软件包安装
sudo apt-get install -y build-essential libfontconfig1 mesa-common-dev libglu1-mesa-dev libxkbcommon-x11-0 libwayland-cursor0 net-tools curl libxcb-icccm4 libxcb-image0 libxcb-shm0-dev libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcursor1 mingw-w64 desktop-file-utils libnss3-tools libcups2-dev libgconf-2-4 libpangocairo-1.0-0 libxss1 libatk1.0-0 libgtk-3-dev fonts-arphic-ukai fonts-arphic-uming language-pack-zh* chinese* ruby ruby-dev
上面这些软件包涉及了:
gcc编译工具链
代码编辑工具
版本控制工具
图形界面基础库
交叉编译(面向windows操作系统)
中文字符集
等等吧。可谓是一应俱全。也足以支持nwjs for Linux正常运行。
配置vi编辑器
虽然VSCode能够直接编辑Ubuntu 20.04 (WSL2)子系统内任何文本文件,但是vi仍旧是一款很有仪式感且使用便捷的文本编辑器。
vi ~/.vimrc
# 添加如下内容
set textwidth=200
set shiftwidth=2
set tabstop=2
set expandtab
set nu
set autoindent
set cindent
set fileencodings=utf-8
set termencoding=utf-8
set encoding=prc
# 并保存退出
重置环境变量
默认情况下,WSL2子系统会继承宿主操作系统的环境变量。我遇到多次因为Ubuntu 20.04 (WSL2)少装了软件包,而误载入了同名的Windows 10宿主动态链接库的情况。这类问题一旦出现很难定位原因,错误日志的内容也很让人费解。解决起来好是耽误功夫!于是,我才总结出这么一条来。将它们“切隔”得泾渭分明。
vi ~/.bashrc
# 在文件的最顶端,添加
export PATH="/usr/local/sbin:/usr/local/bin";
export PATH="$PATH:/usr/sbin:/usr/bin";
export PATH="$PATH:/sbin:/bin";
export PATH="$PATH:/usr/games:/usr/local/games";
export PATH="$PATH:/snap/bin";
# 保存文件和退出文件
source ~/.bashrc
# 给宿主端的 VSCode 启动脚本创建符号链接。
# - 你本地的 VSCode 安装目录,可能有所不同,注意替换。
sudo ln -s '/mnt/c/Program Files/Microsoft VS Code/bin/code' code
另一方面,倘若你真的有必要在WSL2子系统里执行宿主系统的.exe文件(比如,执行ipconfig.exe来获得宿主系统的ip地址):
要么,直接写绝对地址
要么,在/snap/bin目录下,创建符号链接
sudo ln -s /mnt/c/Windows/System32/ipconfig.exe /snap/bin/ipconfig.exe
千万不要改PATH环境变量,因为PATH仅只对目录有效,一次至少会导入一个文件夹的动态链接库,简直是后患无穷。在本次开发环境搭建过程中,涉及到的符号链接只有两个:
ipconfig.exe - 下文会提到使用它来实时地获取宿主操作系统的ip地址。
VSCode的启动脚本文件${VSCode_安装目录}/bin/code - 以便从子系统端打开一个工程和编辑代码。
安装node.js
先安装nvm版本管理器,再安装node,npm与pnpm。
提示:安装脚本可能需要“科学上网”才能被下载。所以,export http_proxy=与export https_proxy=两个环境变量没准需要你配置好,以指向某个有效的地址。
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
source ~/.bashrc
nvm install 10.24.1
nvm use 10.24.1
nvm alias default v10.24.1
npm i -g pnpm
安装ruby依赖包
sudo gem install compass
sudo gem install sass
安装rustup toolchain
提示:工具链模块可能需要“科学上网”才能被下载。所以,export http_proxy=与export https_proxy=两个环境变量没准需要你配置好,以指向某个有效的地址。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# `nightly`频道最新版本的`rustup toolchain`不兼容于`Rust Qt Binding`,所以再安装老一点的版本。
rustup toolchain install nightly-2021-03-25-x86_64-unknown-linux-gnu
# 这一步理论上是可选的,因为`cargo`工程能够在工程层级重写`rustup toolchain`的版本指向。
rustup default nightly-2021-03-25-x86_64-unknown-linux-gnu
# 向 windows 10 x64 交叉编译时,用得上。
rustup target add x86_64-pc-windows-msvc
vi ~/.bashrc
# 在文件的最尾端,添加
# - 调试程序时,可看得见完整的调用栈
export RUST_BACKTRACE=1;
# 保存文件和退出文件
source ~/.bashrc
到写这篇文章时止,qmetaobject与最新版的nightly-x86_64-unknown-linux-gnu工具链还有兼容性问题(编译报错),所以我本地安装的nightly rustup toolchain还是2021年3月的版本。借助我所做的另一款开源工具rustup-components-history,你能找到更多的nightly rustup toolchain的历史版本。毕竟,官方给出的历史版本可用性清单仅提供最新7天的信息。
大家看,我的相关功课做得全吧?是不是,无处不惊喜?所以,请帮忙,多转发,多参阅。
配置XSTATA向宿主系统投影图形界面
题外话,XSTATA与X11 Server是个啥子关系?
虽然官方文档与名称暗示都是将X11 Server摆在了【后端】的位置上,但真实情况正好是对调的。X11 Server负责收集使用者的键盘与鼠标输入,像是个网页;而XSTATA居中调度,完成图形计算与给出反馈,像是个web service。
在这段配置里有一个痛点。即,WSL2子系统需要明确地知道X11 Server所在的Windows 10宿主操作系统的ip地址。请不要自做聪明地认为127.0.0.1可能搪塞过去。127.0.0.1是指向Ubuntu 20.04 (WSL2)自身的 --- 咱们一定得把WSL2子系统与宿主看作是两个独立的workstation。若你的电脑正在连接一个DHCP路由器,那么情况会更糟一些,因为电脑的ip地址会经常地变化。而反复地修改Linux配置文件往往是各种坏事情的开端。
为了缓解这个痛点,我采取的措施包括两步:
在/snap/bin目录下,给C:\Windows\System32\ipconfig.exe创建一个符号连接/snap/bin/ipconfig.exe
sudo ln -s /mnt/c/Windows/System32/ipconfig.exe /snap/bin/ipconfig.exe
在$HOME目录下,编写了一个.get_hot_ip.js脚本程序,来
执行/snap/bin/ipconfig.exe命令
解析/snap/bin/ipconfig.exe在标准输出打印的文本内容
从输出内容里扣出宿主系统的ip地址 --- 需要一点儿简单的正则匹配
打印此ip地址字符串至标准输出
然后,就可以简单地修改~/.bashrc文件了:
vi ~/.bashrc
# 在文件的最尾端,添加
export LIBGL_ALWAYS_INDIRECT=;
# 执行 js 文件获取 ip 地址
export DISPLAY="$(~/.get_hot_ip.js):0.0";
# 保存文件并退出
source ~/.bashrc
于是,即便DHCP路由器时不时地改变了我们的ip地址,咱们只要source ~/.bashrc一下,新ip地址就生效了。多亏了有nodejs,要是用shell来写这个字符串解析程序,那得是多费劲呀。
配置显示中文(包括命令行与GUI)
sudo locale-gen zh_CN.utf8
sudo update-locale LANG=zh_CN.utf8
然后,关闭当前终端和打开一个新终端,语言切换便随之生效了。另外,敲入命令locale也能查阅当前的【语言本地化】配置。
开发环境搭建之Windows宿主系统端
安装X-Server for WinNT
这是将Ubuntu 20.04 (WSL2)子系统的图形界面投影至Windows 10宿主系统的关键组件。虽然它的名字叫X11 Server,但就它的功能而言,其更像是B/S架构中的网页端。与X-Server呼应的、在Ubuntu 20.04 (WSL2)子系统内的Client端程序是XSTATA。
下载与安装VcXsrv
设置【高DPI显示模式】
打开VcXsrv安装目录
鼠标右键点击xlaunch.exe文件。
点击【属性】菜单项
在【属性】对话框内,切签至【兼容性】选项卡
点击【更高DPI设置】按钮
在新对话框内,选中【替代高DPI缩放行为】复选框。
在【缩放执行】下拉框内选择【系统(增强)】
最后,一路【确定】下来。
启动X-Server for WinNT
生成启动配置文件
双击VcXsrv安装目录(下文记作:%VCXSRV_HOME%)内的XLaunch.exe文件
打开Display Settings配置窗口
选择Multiple Windows(就是左一项)
在Display number数字录入框内,输入0
点击【下一页】按钮
跳转至Client startup配置窗口
选择Start no Client
点击【下一页】按钮
跳转至Extra Settings配置窗口
选中Clipboard
选中Primary Selection
取消选中Native opengl
选中Disable access control
点击【下一页】按钮
跳转至Finish configuration配置窗口
点击按钮save configuration,保存配置为文件%VCXSRV_HOME%\x11-server.xlaunch
点击【取消】按钮。
将配置文件应用于快捷方式
鼠标右(键)击XLaunch快捷方式
在【属性】对话框中,修改【目标】输入框的内容为
"%VCXSRV_HOME%\xlaunch.exe" -run "%VCXSRV_HOME%\x11-server.xlaunch"
点击【确定】按钮
启动X11 Server
双击新XLaunch快捷方式
配置VSCode
安装插件
Remote - WSL
Remote Development
在【用户配置】文件%AppData%\Code\User\settings.json里,添加一个新配置项"http.proxySupport": "off"。否则,在WSL2模式下,安装扩展插件容易遭遇下载网络失败。
关闭当前VSCode实例
从Ubuntu-20.04(WSL)终端,敲入指令code .。在WSL2模式下,启动另一个VSCode实例。
给运行于WSL2模式下的VSCode实例安装三个rust插件
rust-analyzer
vscode-rust-syntax
CodeLLDB
给Ubuntu-20.04 (WSL2)子系统安装Qt-5.12.11
准备工作
在Windows 10 x64宿主系统,双击XLaunch快捷方式启动X11 Server服务,准备接受来自WSL2子系统的图形界面投影。
在Ubuntu-20.04终端命令行,执行source ~/.bashrc确保之前的配置修改皆奏效。
安装框架与组件库
打开Ubuntu-20.04 (WSL2)命令行终端
wsl
在Ubuntu-20.04 (WSL2)命令行终端,执行如下命令,下载与启动【Qt连线安装器】
wget https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run
chmod +x qt-unified-linux-x64-online.run
./qt-unified-linux-x64-online.run
于是,在Windows 10 x64宿主端,Qt安装向导对话框就会被弹出。此时,若完全安装Qt-5.12.11框架和组件库,则需要几十个GB的空间。我推荐:
只安装Desktop gcc 64-bit与Qt WebEngine(webview这是刚需)就足够了。
选择安装目录为$HOME/Qt。
配置环境变量
vi ~/.bashrc
# 在文件的最尾端,添加
export PATH="$HOME/Qt/5.12.11/gcc_64/bin:$PATH";
export LD_LIBRARY_PATH="$HOME/Qt/5.12.11/gcc_64/lib:$LD_LIBRARY_PATH";
# 保存文件并退出
source ~/.bashrc
验证Qt安装成功
执行如下脚本,启动Qt的图形界面开发工具Qt Creator。
~/Qt/Tools/QtCreator/bin/qtcreator.sh
验证Ubuntu-20.04 (WSL2)开发环境
检查桌面右下角的托盘,确认X11 Server是否已经启动。
克隆工程从github至本地git clone https://github.com/woboq/qmetaobject-rs.git。
这一步发生在宿主系统或Ubuntu-20.04 (WSL2)子系统不重要。
在WSL2命令行终端,cd到工程根目录/examples/todos
输入指令code .,回车。以WSL2模式,启动一个VSCode实例。
打开VSCode集成终端,敲入cargo run命令,开始编译/运行程序。
另一方面,若你本地已经配置好了CodeLLDB插件与编写了正确.vscode/lanuch.json,直接F5开始断点调试就更酷了。
如果一切正常的话,此时此刻
VSCode代码编辑器是在宿主环境Windows 10 x64。请继续你之前的编程习惯。是不是很舒服?
cargo run使用的是Ubuntu-20.04(WSL)子系统的gcc工具链
甚至,包括更高级的F5断点调试也都是走的Ubuntu-20.04(WSL)子系统gcc工具链
todos图形窗口则是从Ubuntu-20.04(WSL),透过XSTATA -> X11 Server,被投影到宿主Windows 10 x64操作系统的。
交叉编译
就目前的Windows编译工具链现状而言,若想支持向Windows平台分发应用程序软件包,这一步还是绕不过去的。我相信这个情况后续一定会有改善的。这个事已经在github上向官方技术团队备案了,且已经被受理,和正在处理中。
首先,要明确【rust交叉编译】是有限制的。即,rustup工具链的nightly频道对【交叉编译】都不可用。我们仅能使用stable rustup工具链。在这,你是不是也感觉心拔凉拔凉的?
然后,咱们再讲怎么搞【交叉编译】。
在上文中的【rustup toolchain安装】章节,咱们已经预安装了x86_64-pc-windows-msvc``target。
此target可不认识nightly rust语法,会编译报错的。
除此之外,我们还要准备一套Qt for Windows。
直接在你的宿主端windows 10 x64上,下载与运行Qt for Windows 连线安装向导
勾选安装Qt 5.12.11 -> MSVC 2017 64-bit。 注意:我勾选安装MSVC 2017 64-bit是因为我本地已经有Visual Studio 2017了。 请先确定你自己电脑的预装了哪一版的Visual Studio再合理地选择Qt MSVC类型。
接着,仅交叉编译时,临时配置环境变量QT_INCLUDE_PATH与QT_LIBRARY_PATH。
前者,export QT_INCLUDE_PATH=/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/bin
后者,export QT_LIBRARY_PATH=/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/lib
因为QT_INCLUDE_PATH与QT_LIBRARY_PATH优先级高于PATH与LD_LIBRARY_PATH,所以后续的【交叉编译】会优先链接QT for Windows,而不是开发时依赖的QT for Linux。
最后,cd到工程根目录/examples/todos和执行命令cargo build --target=x86_64-pc-windows-msvc。完成。
rust自身的交叉编译就是这么简单得,不要,不要的。复杂度都集中在它所链接的cpp链接库上了。
增补篇:将整个Ubuntu-20.04(WSL)桌面投影至Windows 10宿主系统
更多软件包安装
sudo apt-get install -y ubuntu-desktop apt-transport-https systemd-genie unzip imagemagick
允许当前账号·免密·运行systemd-genie
在编辑sudoer过程中,请将下文中的account_name替换成你的真实账号名(即,echo $USER的值)。
sudo visudo --file /etc/sudoers.d/$USER
# 添加如下一行内容
account_name ALL=(ALL) NOPASSWD:/usr/bin/genie
# 保存与退出:`Ctrl + o`,`Ctrl + x`。
若你想图省事,不防试试下面这条一步到位的指令。不过,这就少了一次熟悉与练习visudo编辑器用法的机会。
echo "$USER ALL=(ALL) NOPASSWD:/usr/bin/genie" | sudo EDITOR="tee" visudo --file /etc/sudoers.d/$USER
若是不小心把sudoer给改坏了,也不用着急。
首先,从win32命令行执行wsl -u root以超级用户登录Ubuntu-20.04(WSL)
然后,强制编辑/etc/sudoers.d/$USER文件,纠正错误配置记录。
千万用不着,为此,重装整个Linux子系统 --- 这算不上一次事故。
向apt-get源清单添加Microsoft站点
# 进入管理员模式
sudo --shell
# 安装 Microsoft 站点公钥
apt-key adv --fetch-keys https://packages.microsoft.com/keys/microsoft.asc
# 添加 Microsoft 地址
vi /etc/apt/sources.list.d/microsoft-prod.list
# 添加如下一行内容
deb [arch=amd64] https://packages.microsoft.com/ubuntu/20.04/prod focal main
# 保存与退出
apt update
exit
向apt-get源清单添加Arkane站点
# 进入管理员模式
sudo --shell
# 安装 GPG 公钥
wget --output-document /etc/apt/trusted.gpg.d/wsl-transdebian.gpg https://arkane-systems.github.io/wsl-transdebian/apt/wsl-transdebian.gpg
chmod a+r /etc/apt/trusted.gpg.d/wsl-transdebian.gpg
# 添加 Arkane 地址
vi /etc/apt/sources.list.d/wsl-transdebian.list
# 添加如下两行内容
deb https://arkane-systems.github.io/wsl-transdebian/apt/ focal main
deb-src https://arkane-systems.github.io/wsl-transdebian/apt/ focal main
# 保存与退出
apt update
exit
编写启动脚本
创建启动脚本保存目录
在windows 10的%USERPROFILE%目录下,创建文件夹.ubuntu来保存Ubuntu-20.04的桌面系统启动脚本。这样方便从宿主环境创建快捷方式和“一站式”地启动Ubuntu-20.04(WSL)桌面。
export USERNAME=$(wslvar USERNAME);
export USERPROFILE=/mnt/c/users/$USERNAME;
mkdir --parents $USERPROFILE/.ubuntu/
cd $USERPROFILE/.ubuntu/
编写GNOME.GTK3启动shell脚本
vi start_ubuntu.sh
# 添加如下内容
#!/usr/bin/env bash
# 显示环境变量,用于连接 X11 Server
if [ -z "$DISPLAY" ]; then
# 初始化 NVM 与 NODE 环境
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# 执行 nodejs 脚本程序获取宿主系统的 ip 地址。其在前文已经提过。
export DISPLAY="$(~/.get_hot_ip.js):0.0"
fi
# GNOME.GTK3 环境变量
export DESKTOP_SESSION="ubuntu"
export GDMSESSION="ubuntu"
export XDG_SESSION_DESKTOP="ubuntu"
export XDG_CURRENT_DESKTOP="ubuntu:GNOME"
export XDG_SESSION_TYPE="x11"
export XDG_BACKEND="x11"
export XDG_SESSION_CLASS="user"
export XDG_DATA_DIRS="/usr/local/share/:/usr/share/:/var/lib/snapd/desktop"
export XDG_CONFIG_DIRS="/etc/xdg"
export XDG_RUNTIME_DIR="$HOME/xdg"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_DESKTOP_DIR="$HOME/Desktop"
export XDG_DOCUMENTS_DIR="$HOME/Documents"
export XDG_DOWNLOAD_DIR="$HOME/Downloads"
export XDG_MUSIC_DIR="$HOME/Music"
export XDG_PICTURES_DIR="$HOME/Pictures"
export XDG_PUBLICSHARE_DIR="$HOME/Public"
export XDG_TEMPLATES_DIR="$HOME/Templates"
export XDG_VIDEOS_DIR="$HOME/Videos"
# 启动 GNOME.GTK3 桌面环境
gnome-session
保存并退出
编写wsl2启动javascript脚本
因为有一段比较麻烦的“重试”逻辑需要被实现,所以我选择了js --- 既简单,又强大。需实现的业务逻辑包括:
执行wsl genie -c /mnt/c/Users/${process.env.USERNAME}/.ubuntu/start_ubuntu.sh指令
跟踪标准输出内容,判断其是否包含字符串Timed out waiting for systemd to enter running state.?
若在标准输出内包含Timed out waiting for systemd to enter running state.,就杀进程和回到#1。
否则结束。
vi start_ubuntu.js
# 添加如下内容
#!/usr/bin/env node
const {spawn} = require('child_process');
(async () => {
const {stdout} = await exec('wsl', ['genie', '--is-in-bottle']).catch(output => output);
const shellPath = `/mnt/c/Users/${process.env.USERNAME}/.ubuntu/start_ubuntu.sh`;
if (/inside/.test(stdout)) {
await exec(shellPath);
} else {
const run = () => {
return exec('wsl', ['genie', '-c', shellPath], (_, output) => {
if (/Timed out waiting for systemd to enter running state\./.test(output.stdout)) {
output.prc.kill();
}
}).catch(err => {
if (/Timed out waiting for systemd to enter running state\./.test(err.stdout)) {
return run();
}
return Promise.reject(err);
});
}
await run();
}
})();
function exec(cmd, args = [], notify = () => {}){
return new Promise((resolve, reject) => {
const prc = spawn(cmd, args, {shell: true});
const output = {
prc,
stdout: '',
stderr: ''
};
prc.stdout.on('data', data => {
output.stdout += data.toString();
notify('stdout', output);
});
prc.stderr.on('data', data => {
output.stderr += data.toString();
notify('stderr', output);
});
prc.on('close', code => {
if (code == 0) {
resolve(output);
} else {
reject(output);
}
});
});
}
编写VcXsrv启动配置xml文件
vi ubuntu-desktop-wsl2-preset.xlaunch
# 添加如下内容
LocalProgram="xcalc" RemoteProgram="xterm" RemotePassword="" PrivateKey="" RemoteHost="" RemoteUser="" XDMCPHost="" XDMCPBroadcast="False" XDMCPIndirect="False" Clipboard="True" ClipboardPrimary="True" ExtraParams="" Wgl="False" DisableAC="True" XDMCPTerminate="False"/> 保存并退出 编写VcXsrv重启powershell脚本 vi restart_vcxsrv.ps1 # 添加如下内容 # 终止正监听于 0.0 显示码的 X11 Server 实例 get-process vcxsrv | where { $_.mainwindowtitle -like "*0.0*" } | stop-process # 启动监听于 0.0 显示码的 X11 Server 实例 start-process "C:\Program Files\VcXsrv\xlaunch.exe" -argument "-run `"$env:USERPROFILE\.ubuntu\ubuntu-desktop-wsl2-preset.xlaunch`"" 编写Ubuntu-20.04(WSL)桌面启动引导VBS程序 在此步骤,使用VBS而不是javascript。其主要原因是:由VBS唤起的【命令行窗口】会自动隐藏起来,而由nodejs唤起的命令行窗口就一直在那儿,不能被收起了。若强制收起,则整个进程就结束了。这虽然不影响功能,但太膈应了。 vi start_ubuntu.vbs # 添加如下内容 set shell_object = createobject("wscript.shell") userprofile = shell_object.ExpandEnvironmentStrings("%USERPROFILE%") ' 首先,启动 X11 Server set application = createobject("shell.application") application.shellexecute "powershell", "-file " & userprofile & "\.ubuntu\restart_vcxsrv.ps1", "", "", 0 ' 给 X11 Server 启动,留一点时间 wscript.sleep 1000 ' 再启动 wsl2 shell_object.run "node " & userprofile & "\.ubuntu\start_ubuntu.js", 0 制作Ubuntu-20.04(WSL)图标 # 下载 Ubuntu 图标与图片集 wget https://assets.ubuntu.com/v1/9fbc8a44-circle-of-friends-web.zip # 解压缩之 unzip 9fbc8a44-circle-of-friends-web.zip # 重新设定 Logo 图片大小,文件类型与文件名 convert -resize 64x64 ./circle-of-friends-web/png/cof_orange_hex.png ubuntu.ico # 删除无用文件 rm -f 9fbc8a44-circle-of-friends-web.zip rm -fr 9fbc8a44-circle-of-friends-web 为Ubuntu-20.04(WSL)桌面启动,创建快捷方式 在PowerShell中,执行如下指令 # Define location variables $shortcut_location = "$env:userprofile\.ubuntu\Ubuntu.lnk" $program_location = "$env:userprofile\.ubuntu\start_ubuntu.vbs" $icon_location = "$env:userprofile\.ubuntu\ubuntu.ico" # Create shortcut $object = new-object -comobject wscript.shell $shortcut = $object.createshortcut($shortcut_location) $shortcut.targetpath = $program_location $shortcut.iconlocation = $icon_location $shortcut.save() 便会在%USERPROFILE%\.ubuntu目录下,看到一个Ubuntu快捷方式。双击之就会 先启动X11 Server 再启动WSL2 接着,初始化GNOME.GTK3会话 显示Ubuntu桌面 收尾工作 双击Ubuntu快捷方式,启动Ubuntu桌面 从Activaties打开Terminal终端 执行如下命令 # 关闭【屏幕锁】功能。一旦被锁,非重启不能解锁。实在太恶心了。 gsettings set org.gnome.desktop.lockdown disable-lock-screen true # 安装应用商店 sudo snap install snap-store 结束 最初,我仅只想解决Rust + QT在win32平台上的编译问题。谁知经历了一番探索与实践却在Windows 10平台内搞出一套完整的Linux子系统来。这似乎又范了我做事不够专注的老毛病了。得改,得改!但是,我真心希望这里分享的内容能够帮助到技术同路人们。搞技术不容易,大家多分享,多相互帮助,共同进步。