在 WSL 里开发 Linux 图形应用:从脚本窗口到可维护的小工具
古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。
以前在 Windows 上写 Linux 图形程序,心理门槛很高。要么开虚拟机,要么配 X Server,要么直接放弃图形界面,做成命令行。
WSLg 把这件事变得很普通:你在 WSL 里启动一个 Qt、GTK、OpenCV 窗口,它就像普通 Windows 应用一样出现在桌面上,可以 Alt+Tab,可以复制粘贴,可以和浏览器、编辑器并排。
这不代表 WSL 适合发布所有桌面软件。但它很适合开发一类东西:给自己或团队内部用的小工具。

这篇用一个可复制的小工具贯穿:批量选择图片目录,生成缩略图和压缩版本,实时预览结果。它一开始可以是 shell + Python 脚本,后来长成一个小 GUI。
先确认 GUI 链路,而不是直接装框架
Microsoft 的 WSL GUI 文档说明,现在 WSL 支持在 Windows 上运行 X11 和 Wayland Linux GUI 应用,并能和 Windows 桌面集成。WSLg 项目本身的目标也是让 Linux GUI 应用在 Windows 上获得集成体验。
但写程序前,先做最小验证:
sudo apt update
sudo apt install -y x11-apps
xeyes
再测图片窗口:
sudo apt install -y feh
feh sample.png
如果这些都不出来,不要急着怪 Qt 或 GTK。先更新 WSL:
wsl --update
wsl --shutdown
重新进 Ubuntu 后再试。
确认环境变量:
echo "$DISPLAY"
echo "$WAYLAND_DISPLAY"
ls -la /mnt/wslg
这一步的意义是:先证明 WSLg 基础链路能跑,再引入应用框架。
需求不要一开始就做大
我们要做的工具叫 thumb-lab,目标很小:
- 选择一个图片目录;
- 显示目录里的图片数量;
- 生成宽度 640 的缩略图;
- 生成完成后打开输出目录;
- 失败时显示错误信息。
先写命令行版本:
mkdir -p ~/work/thumb-lab
cd ~/work/thumb-lab
python3 -m venv .venv
source .venv/bin/activate
python -m pip install pillow
thumb.py:
from pathlib import Path
from PIL import Image
import sys
src = Path(sys.argv[1]).expanduser().resolve()
out = Path(sys.argv[2]).expanduser().resolve()
out.mkdir(parents=True, exist_ok=True)
for path in src.glob("*"):
if path.suffix.lower() not in {".jpg", ".jpeg", ".png", ".webp"}:
continue
img = Image.open(path)
img.thumbnail((640, 640))
img.save(out / f"{path.stem}.jpg", quality=88)
print(out)
运行:
python thumb.py ~/Pictures/raw ~/Pictures/thumbs
explorer.exe "$(wslpath -w ~/Pictures/thumbs)"
先把核心逻辑做成纯函数或命令行脚本,这是 GUI 可维护的基础。不要把图片处理逻辑直接写在按钮回调里。
用 PySide6 做一个最小窗口
Qt for Python 官方文档说明,PySide6 是 Qt6 的官方 Python 绑定。它适合做稍微复杂一点的桌面小工具:按钮、列表、表格、进度条、文件选择、预览,都比较成熟。
安装:
source .venv/bin/activate
python -m pip install PySide6 pillow
app.py:
from pathlib import Path
import subprocess
import sys
from PIL import Image
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
def make_thumbnails(src: Path, out: Path) -> int:
out.mkdir(parents=True, exist_ok=True)
count = 0
for path in src.glob("*"):
if path.suffix.lower() not in {".jpg", ".jpeg", ".png", ".webp"}:
continue
img = Image.open(path)
img.thumbnail((640, 640))
img.convert("RGB").save(out / f"{path.stem}.jpg", quality=88)
count += 1
return count
class ThumbLab(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Thumb Lab")
self.resize(420, 180)
self.status = QLabel("选择一个图片目录")
self.pick_button = QPushButton("选择目录并生成缩略图")
self.pick_button.clicked.connect(self.pick_and_run)
layout = QVBoxLayout()
layout.addWidget(self.status)
layout.addWidget(self.pick_button)
self.setLayout(layout)
def pick_and_run(self):
directory = QFileDialog.getExistingDirectory(self, "选择图片目录")
if not directory:
return
src = Path(directory)
out = src / "_thumbs"
try:
count = make_thumbnails(src, out)
except Exception as exc:
self.status.setText(f"失败:{exc}")
return
self.status.setText(f"完成:生成 {count} 张缩略图,输出到 {out}")
subprocess.run(["explorer.exe", str(out)], check=False)
app = QApplication(sys.argv)
window = ThumbLab()
window.show()
sys.exit(app.exec())
运行:
python app.py
窗口会出现在 Windows 桌面上。你选择的目录如果在 WSL 文件系统里,处理速度更稳定。如果选择 /mnt/c/Users/... 下的大量图片,也能跑,但大量文件读取会跨边界。
GUI 里不要阻塞主线程
上面的代码有一个明显问题:处理很多图片时,窗口会卡住。小工具刚开始可以忍,但只要处理超过几秒,就应该把耗时任务放到后台线程。
Qt 里可以用 QThread。这里先不展开完整代码,只强调结构:
UI 线程:
- 响应按钮
- 更新状态
- 显示进度
Worker 线程:
- 扫描文件
- 读取图片
- 生成缩略图
- 发进度信号
这是 GUI 程序和命令行脚本最大的区别。命令行脚本阻塞没关系,窗口阻塞用户会觉得程序死了。
如果你只是做一次性内部工具,可以先接受同步版本。但一旦工具给同事用,进度条、取消按钮、错误提示就不是装饰。
GTK 适合更 Linux 味的小工具
如果你更偏 GNOME/Linux 生态,可以看 PyGObject。GTK 官方 Python 绑定文档介绍了 PyGObject,它提供 GTK、GStreamer、WebKitGTK、GLib、GIO 等库的绑定。
Ubuntu 里装 GTK4 相关包:
sudo apt install -y python3-gi gir1.2-gtk-4.0
最小窗口:
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
class App(Gtk.Application):
def do_activate(self):
window = Gtk.ApplicationWindow(application=self)
window.set_title("GTK on WSL")
window.set_default_size(360, 120)
label = Gtk.Label(label="Hello from GTK running in WSL")
window.set_child(label)
window.present()
app = App(application_id="com.example.ThumbLab")
app.run()
GTK 的优点是和 Linux 桌面生态贴得更近。Qt 的优点是跨平台、控件成熟、资料多。内部工具我通常选自己团队更熟的那个,不会为了“正统”纠结。
OpenCV 的窗口适合算法调试,不适合产品界面
图像算法开发里,经常用:
import cv2
img = cv2.imread("sample.jpg")
cv2.imshow("preview", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在 WSLg 下,这个窗口也能弹到 Windows 桌面。
它非常适合:
- 看二值化结果;
- 看检测框;
- 看 OCR 裁剪;
- 比较前处理前后;
- 调摄像头或视频帧。
但它不适合做正式 UI。按钮、列表、布局、异步任务、错误提示,都不是 OpenCV 窗口擅长的。
我的取舍是:
算法调试:OpenCV window
内部小工具:PySide6 / GTK
面向普通用户:Windows 原生或 Web
文件路径是 GUI 工具最容易踩的坑
WSL GUI 窗口里选择文件时,看到的是 Linux 路径:
/home/alex/Pictures/raw
/mnt/c/Users/Alex/Pictures
但用户脑子里可能是 Windows 路径:
C:\Users\Alex\Pictures
如果工具要把结果交给 Windows 程序,可以用:
wslpath -w ~/Pictures/thumbs
Python 里:
import subprocess
from pathlib import Path
def open_in_explorer(path: Path) -> None:
subprocess.run(["explorer.exe", str(path)], check=False)
explorer.exe 能接受 WSL 路径,通常会打开对应目录。团队工具里最好把输出目录显示清楚,并提供“打开目录”按钮。
不要让用户自己在 /home、\\wsl.localhost、/mnt/c 之间猜。
打包和分发:内部工具不要过度工程化
在 WSL 里开发 GUI,不等于最终一定在 WSL 里发布。
内部工具可以这样分发:
git clone ...
cd thumb-lab
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py
再加一个启动脚本:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
source .venv/bin/activate
python app.py
给熟悉 WSL 的同事用,足够了。
如果要给非技术同事用,我会重新评估:
- 做成 Web 页面;
- 做成 Windows 原生应用;
- 做成公司内部平台功能;
- 或者用 Electron / Tauri / .NET / Qt 正式打包。
WSL GUI 适合开发和内部效率工具,不适合拿来绕过正式发布流程。
调试清单
GUI 程序不启动时,我按这个顺序查:
# 1. WSLg 基础
xeyes
# 2. 环境变量
echo "$DISPLAY"
echo "$WAYLAND_DISPLAY"
# 3. Python 环境
which python
python -V
python -c "import PySide6; print('PySide6 ok')"
# 4. 依赖库
ldd "$(python -c 'import PySide6, pathlib; import PySide6.QtCore as c; print(c.__file__)')" 2>/dev/null | head
# 5. 路径
pwd
df -T .
如果 xeyes 不工作,先修 WSLg。
如果 xeyes 工作,PySide6 不工作,再看 Python 包。
如果程序启动但处理很慢,再看文件是不是在 /mnt/c。
分层排查比盯着一屏 traceback 有用。
小结
WSLg 让 Linux GUI 应用进入 Windows 桌面,这件事对开发者的价值,不是让你把 Windows 变成 Linux 桌面。
它更适合这样的工作流:
核心逻辑先写成脚本。
脚本稳定后,加一个小窗口。
窗口只负责选择文件、显示进度、打开结果。
真正要给更多人用时,再决定是否做 Web 或 Windows 原生。
用这个思路,WSL 里的 GUI 开发会很轻,不会变成新的负担。
参考资料: