在 WSL 里开发 Linux 图形应用:从脚本窗口到可维护的小工具
原创 · 约 22 分钟阅读 · 阅读 --

在 WSL 里开发 Linux 图形应用:从脚本窗口到可维护的小工具

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。

以前在 Windows 上写 Linux 图形程序,心理门槛很高。要么开虚拟机,要么配 X Server,要么直接放弃图形界面,做成命令行。

WSLg 把这件事变得很普通:你在 WSL 里启动一个 Qt、GTK、OpenCV 窗口,它就像普通 Windows 应用一样出现在桌面上,可以 Alt+Tab,可以复制粘贴,可以和浏览器、编辑器并排。

这不代表 WSL 适合发布所有桌面软件。但它很适合开发一类东西:给自己或团队内部用的小工具。

在 WSL 里开发 Linux GUI 小工具

这篇用一个可复制的小工具贯穿:批量选择图片目录,生成缩略图和压缩版本,实时预览结果。它一开始可以是 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,目标很小:

  1. 选择一个图片目录;
  2. 显示目录里的图片数量;
  3. 生成宽度 640 的缩略图;
  4. 生成完成后打开输出目录;
  5. 失败时显示错误信息。

先写命令行版本:

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 开发会很轻,不会变成新的负担。

参考资料: