Skip to content
Team-QuaverPublic

About

A fast, high-end, custom wayland compositor

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

Repository files navigation

Lumine

中文翻译为荧光,是一个基于 Go 实现的,以简洁、百变、万能为名头的 Wayland 合成器

与此同时,我们正在基于 MyGo 开发一系列 Lumine 扩展的原生软件(如文件管理器 Phoenix、照片查看器 Charlotte、音频播放器 Fermata)

目前还是实验性产物,远远无法达到可使用层级

纯 Go 实现的 Wayland 合成器试验品。不使用 CGO。

Wayland 协议、Vulkan 绑定、共享内存、配置解析全部使用 Go 标准库与纯 Go 第三方库实现。需要原生库时(Vulkan loader)通过 purego 在运行时 dlopen,不链接任何 C 代码。

特性

  • Vulkan 渲染器:通过 purego 直接调用 Vulkan 1.0(单个纹理四边形管线, 离屏渲染后回读到 CPU 可见帧缓冲)。设备不可用时自动回退到软件渲染器, 两条路径共用同一套合成逻辑,便于对照验证。
  • 双布局模式:
    • tile:类似 Niri 的无限横向滚动平铺。窗口按竖直列排列,视口沿列条带 滚动,新窗口在焦点列右侧开新列。
    • stack:自由浮动窗口,按 z 序叠放。
  • 色彩管理:输出侧对合成结果做 sRGB → 目标色彩空间转换(PQ / HLG / linear),并通过 wp_color_manager_v1 暴露协议,客户端可创建参数化 / scRGB / BT.2100 image description、赋给 surface 并查询输出首选描述。
  • CSD 与 SSD 共存:实现 zxdg_decoration_manager_v1。合成器默认绘制 服务端装饰(标题栏 + 边框),客户端请求 CSD 时则完全不画,两种窗口可同时 存在于同一输出。[decor].mode 控制服务端偏好,客户端最终决定。
  • 配置热重载:INI 风格的 .conf 配置,inotify 监听文件变化,编辑保存即 生效(屏幕 / 布局 / 色彩 / WM 环境变量),无需重启,客户端不受影响。
  • luminectl:通过 Unix 控制套接字操作运行中的合成器(焦点、滚动、列 移动、模式切换、关闭窗口、生成进程、强制重载、状态查询)。
  • 无 CGO:CGO_ENABLED=0 go build ./... 可直接构建,产物是静态友好的 纯 Go 二进制。

目录结构

Lumine/
├── cmd/
│   ├── lumine/           # 合成器入口:后端、渲染器、60Hz 帧循环
│   └── luminectl/        # 控制工具
├── internal/
│   ├── wl/               # 手写 Wayland 线协议 + 代码生成绑定(gen_*.go 勿手改)
│   ├── compositor/       # 全局对象、surface 状态机、seat、布局粘合、IPC、色彩管理
│   ├── layout/           # tile(列 + 滚动)与 stack(浮动 z 序)算法
│   ├── render/           # Framebuffer、CPU 渲染器、Vulkan 渲染器、色彩变换
│   ├── backend/          # 嵌套 Wayland 后端(纯 Go Wayland 客户端)
│   ├── config/           # .conf(类 ini)配置 + inotify 热重载
│   ├── ipc/              # Unix socket 上的行式 JSON 控制协议
│   └── shm/              # memfd / mmap / 像素解码
├── protocol/             # Wayland 协议 XML(生成 internal/wl 绑定的输入)
└── config.example.conf   # 带注释的示例配置

构建

需要 Go(go.mod 指定 go 1.26)。无需 meson、无需 wlroots、无需 libwayland。

go build ./cmd/lumine ./cmd/luminectl

# 验证纯 Go(无 CGO)构建:
CGO_ENABLED=0 go build ./...

仓库根目录没有 .go 文件,因此不要执行 go build . 或 go run .。 使用 make build 构建到 ./bin,使用 make run ARGS="..." 启动,或者 明确指定包路径,例如 go run ./cmd/lumine ...。

说明:go list -deps 会列出 purego/internal/cgo 包,这是 purego 的源码 布局,编译时在 CGO_ENABLED=0 下走纯 Go 的 dlopen 分支,不会产生 CGO 依赖。用 CGO_ENABLED=0 构建即可确认。

运行

Lumine 目前提供三个后端:

  • nested(默认):作为一个 Wayland 客户端运行在现有合成器里,把帧提交为 wl_shm 缓冲并转发父合成器的输入。需要父合成器的 WAYLAND_DISPLAY。
  • drm:直接驱动 DRM/KMS 连接器(裸机,无宿主合成器)。用 Vulkan 离屏渲染 后把结果拷进 DRM dumb buffer 并 page-flip 上屏,键盘/鼠标直接读 /dev/input/event*。需要一块未被其他合成器占用的空闲 VT。
  • headless:不连任何父合成器,仅在内存中渲染,适合测试。
# 嵌套在现有会话中运行(父 WAYLAND_DISPLAY 指向当前桌面):
WAYLAND_DISPLAY=wayland-1 \
  go run ./cmd/lumine -config ~/.config/lumine-wm/config.conf

# 裸机 DRM/KMS(在空闲 VT 上运行;见下文 “DRM/KMS 后端”):
sudo ./lumine -backend drm

# 指定显卡节点:
./lumine -backend drm -drm-device /dev/dri/card0

# 无头运行:
go run ./cmd/lumine -backend headless -renderer cpu

# 指定渲染器与窗口尺寸:
go run ./cmd/lumine -renderer vulkan -size 1920x1080

命令行参数:

参数 默认值 说明
-backend nested nested / drm / headless
-renderer auto auto / vulkan / cpu;Vulkan 不可用时回退 CPU
-socket lumine-wayland Wayland socket 名或绝对路径
-config ~/.config/lumine-wm/config.conf 配置文件路径(类 ini 的 .conf)
-terminal $LUMINE_TERMINAL 或 kitty spawn-terminal 动作执行的命令
-size 1280x720 嵌套窗口初始尺寸
-drm-device 自动探测 -backend drm 使用的卡节点,如 /dev/dri/card0;也接受 LUMINE_DRM_DEVICE / WLR_DRM_DEVICES
-verbose false 打印渲染器与后端选择

DRM/KMS 后端(Vulkan + NVIDIA)

-backend drm 走纯 Go 的 DRM/KMS ioctl(internal/drm),不依赖 libdrm, 也没有 CGO。上屏流程刻意保持简单、可移植:

  1. 用现有 Vulkan 渲染器把整帧画进离屏 B8G8R8A8_UNORM 图像,再读回到 CPU (render.Framebuffer);
  2. 把这帧按行拷进一块 DRM dumb buffer(线性、可 mmap 的扫描缓冲);
  3. 用 DRM_IOCTL_MODE_PAGE_FLIP 把它翻到 CRTC 上,SYN_REPORT 完成事件回 收缓冲。

为什么不用 dma-buf 直接扫描(把 dumb buffer 作为 Vulkan 图像导入、GPU 直接写 进去):

  • i915 拒绝这种导入(vkGetImageSubresourceLayout 报 131584 > 131072 的分配 大小);
  • NVIDIA 上 GPU 写进导入的 dma-buf 后,CPU 映射读回不保证一致。

CPU 拷贝多花一点内存带宽,但在 Intel / AMD / NVIDIA 上都可靠,也让渲染器与输出 后端解耦(CPU 渲染器同样能配 -backend drm)。

NVIDIA 支持:internal/render 的 Vulkan 初始化会枚举物理设备,挑选第一个 能离屏渲染 B8G8R8A8_UNORM 的设备;NVIDIA 的 Vulkan ICD(nvidia / 开源 nvidia-open 内核模块)满足该条件,会被直接选中。DRM 侧则使用 nvidia-drm 暴露的通用 KMS 接口(dumb buffer + page flip + atomic modeset), 因此不依赖任何 NVIDIA 专有 API。混合显卡机器上可用 -drm-device 指定要让 NVIDIA 还是集显承担扫描输出,两者都能工作。

启动前需要:

  • 在空闲 VT 上运行(不要在当前图形会话里跑,否则 drmSetMaster 会因 "already mastered" 失败并给出提示);
  • 对 /dev/dri/card* 与 /dev/input/event* 有读写权限(加入 video / input 组,或用 udev 规则 / root)。

-drm-device、LUMINE_DRM_DEVICE 或 WLR_DRM_DEVICES(取第一项)可指定卡节点; 连接器、分辨率、刷新率来自配置文件的 [Display] 段(monitor / resolution / refresh-Rate),留空则选第一个已连接的连接器和它的首选模式。

DRM 输入的语义

-backend drm 直接读 /dev/input/event*,输入行为与其它合成器对齐:

  • 每个键盘/鼠标设备以 EVIOCGRAB 独占打开:VT 控制台不再消耗按键(不再回显、 不再由内核注入重复事件)。拿不到 grab 只在 -verbose 中警告,不阻塞启动。
  • 按键自动重复由合成器合成(节奏来自 [input.repeat] delay/rate,并通过 wl_keyboard.repeat_info 广播)。裸机上 evdev 只报一次按下,没有这个计时器 长按只会出一个字符;嵌套后端里宿主的重复事件会被透传且不会与本合成器的重复流 叠加。
  • 时间戳全链路携带:evdev 的单调时钟毫秒直接转成 Wayland 时间戳传给客户端。 指针加速度、平滑滚动与拖拽判定都依赖事件间隔,恒为 0 的时间戳会让 kitty、 Qt、Chrome 类客户端表现得"鼠标失灵"。
  • 每个 wl_pointer.motion 批次以 frame 收尾(GTK 等按 frame 提交的工具包 在收到 frame 之前会缓存运动事件)。
  • 点击窗口在 tile 与 stack 模式都会移动键盘焦点(此前只有 stack 会,tile 下点击焦点会被每帧的布局焦点回写吞掉)。
  • 滚轮只采用 高分辨率轴(设备同时报 REL_WHEEL 与 REL_WHEEL_HI_RES 时不 双计);BTN_TOOL_* 之类的接近/触笔状态不会误报为鼠标按键。
  • 触控板默认跳过(原始 ABS_MT 坐标直接映射会瞬移光标并产生幻影点击,手势 支持尚未实现)。设 LUMINE_MAP_TOUCHPAD=1 可强行按绝对指针映射; 数位板(BTN_TOOL_PEN)仍正常按绝对坐标工作。
  • 裸机上调试输入:LUMINE_DEBUG_INPUT=1 把每个原始 evdev 事件打到 stderr, 确认"内核是否看到了我的鼠标"。

启动子客户端

Lumine 通过 -socket(默认 lumine-wayland)在 $XDG_RUNTIME_DIR 下监听。 spawn-terminal 动作(以及 luminectl spawn)启动子进程时会剥离父进程的 WAYLAND_DISPLAY,改为指向 Lumine 自己的 socket,因此子客户端会连到 Lumine 而不是宿主桌面:

# 让一个终端连到已运行的 Lumine:
WAYLAND_DISPLAY=lumine-wayland kitty

# 或让 Lumine 直接拉起命令:
WAYLAND_DISPLAY=wayland-1 go run ./cmd/lumine kitty

配置与热重载

默认路径 $XDG_CONFIG_HOME/lumine-wm/config.conf (即 ~/.config/lumine-wm/config.conf)。文件是 INI 风格:; 与 # 开启注释,键名大小写不敏感,值可加引号。由 inotify 监听,保存后立即重新 加载并应用到运行中的窗口。完整示例见 config.example.conf。

[Display]
monitor          = eDP-1
resolution       = 2560x1440
refresh-Rate     = highest      ; 或具体数值(Hz)
color-managment  = icc          ; icc | edid | none
icc-file         = "/path/to/panel.icc"
main-monitor     = ""           ; true 表示该段是主屏
hdr              = false
triple-buffering = true

[auto-start]
exec-once = fcitx5 -d
exec-once = /usr/bin/fastfetch

[wm-environment]
GTK_THEME = Breeze

[keybinding]
"super+t" = "mode-toggle"

屏幕 / 分辨率

  • 每个输出写成一个 section。多屏使用 [Display-1] / [Display-2] / …, 顺序越前,优先级越高,最高优先级的一段即主输出;也可用 main-monitor = true 显式指定。
  • monitor 是连接器 / 输出名(如 eDP-1、HDMI-A-1),留空则使用后端默认输出。
  • resolution 形如 2560x1440,auto 表示沿用后端尺寸。嵌套后端下它只决定 宿主窗口的初始尺寸,无头后端下即最终输出尺寸。
  • refresh-Rate 可填 highest / auto,或 144、59.94 这样的数值。
  • color-managment(也接受正确拼写 color-management)取 icc / edid / none;icc 搭配 icc-file。
  • hdr = true 会把该输出切到 PQ / BT.2020;triple-buffering 作为渲染提示 保留给后续 DRM 后端。

自启动与环境变量

  • [auto-start] 里可重复书写 exec-once,每条命令在 compositor 开始监听后 只执行一次(热重载不会重跑)。
  • [wm-environment] 的键值会导入进程环境,自启动命令、终端快捷键及所有 spawn 出来的客户端都会继承。

说明

  • 缺失字段使用内置默认值;缺失文件不报错(等价于全默认配置)。
  • [keybinding] 与默认绑定合并,只写想改的按键即可;完整绑定语言后续 迭代再加入。
  • 热重载会应用屏幕 / 布局 / 色彩 / 环境变量;exec-once 与绑定表变化不重跑。
  • 也可用 luminectl reload 强制重载。

快捷键(Super = Logo)

按键 动作 说明
Super+Return spawn-terminal 打开终端
Super+T mode-toggle 切换 tile / stack
Super+Left/Right focus-left/right 跨列移动焦点
Super+Up/Down focus-up/down 列内移动焦点(stack 模式为 z 序循环)
Super+Shift+J/K move-left/right 在列条带中左右移动当前列
Super+Shift+Left/Right scroll-left/right 横向滚动词口
Super+Q close 关闭焦点窗口

动作名即 [keybinding] 里可填的值,与 luminectl 的语义一致。

luminectl

控制套接字位于 $XDG_RUNTIME_DIR/lumine-ctl.sock。请求为行式 JSON,响应为 JSON。

luminectl status                 # 当前布局状态
luminectl outputs                # 输出列表
luminectl focus left             # 移动焦点(left/right/up/down)
luminectl move right             # 当前列右移
luminectl scroll 240             # 视口右移 240px
luminectl mode stack             # 切换布局模式(tile/stack/toggle)
luminectl close                  # 关闭焦点窗口
luminectl spawn kitty            # 生成进程(连到 Lumine)
luminectl reload                 # 强制重载配置

色彩管理

  • 输出侧:渲染器在合成完成后按 [color] 的 transfer / primaries / max_luma 对整帧做 sRGB → 目标空间的转换。transfer = "srgb" 时为零开销 直通;pq / hlg / linear 会做逐像素重编码。
  • 协议侧(wp_color_manager_v1):对外声明 parametric、set_luminances、 windows_scrgb、windows_bt2100;客户端可创建 image description(命名 transfer function / primaries + 亮度范围),描述创建后立即置为 ready, get_information 可回读参数。wp_color_management_surface_v1 上的 image description 会被记录在对应 surface 上,surface feedback 的 get_preferred 返回输出首选描述。
  • 范围说明:per-surface 的像素级转换(ICC profile、3D LUT、逐窗口 色彩空间映射)尚未实现——描述目前作为元数据跟踪与回读,真正的像素变换在 输出侧统一完成。不支持的参数(如 set_tf_power、显式色度坐标)会按协议 返回 failed(unsupported),而不是静默接受。

测试

go test ./...
go vet ./...

# 纯 Go 路径:
CGO_ENABLED=0 go build ./... && CGO_ENABLED=0 go test ./...
  • internal/wl:线协议编解码、FD 传递、对象表。
  • internal/compositor:完整客户端会话(registry → shm → xdg_toplevel → buffer release → frame callback → keyboard keymap → SSD 几何)与 wp_color_manager_v1 参数化描述的就绪路径。
  • internal/layout:列生成/聚焦/滚动/重排、模式往返、浮动窗口夹取。
  • internal/config:.conf 解析、[Display] / 自启动 / 环境、绑定合并、 非法值回退与热重载。
  • internal/render:CPU 与 Vulkan 合成结果一致性。

已知限制(MVP 范围)

  • 裸机 DRM/KMS 后端已可用(-backend drm),但仍以单连接器为主;真实面板上的 HDR 输出元数据([Display].hdr 目前只在合成阶段生效)尚未落地。
  • layer-shell / foreign-toplevel / screencopy / xdg_popup 的协议绑定已在 internal/wl 生成,但尚未注册为全局对象,相关客户端暂不可用。
  • 指针拖拽/缩放未实现:xdg_toplevel.move/resize 为空操作,stack 模式窗口 目前不能拖动;也没有 wl_pointer.set_cursor 光标面渲染。
  • 触控板只有裸坐标映射,没有手势(点按、双指滚动、惯性)。DRM 后端默认跳过 触控板设备以免光标瞬移与幻影点击,LUMINE_MAP_TOUCHPAD=1 可强制启用。
  • per-surface 色彩像素转换(ICC/LUT)未实现(见上)。
  • 单输出为主,wl_output 热插拔未实现。
  • wl_surface 的 buffer scale / transform / damage 被忽略,每帧整屏重绘。

许可证

见仓库根目录。仓库历史中的旧 C / wlroots 实现已由当前 Go 实现取代。

About

A fast, high-end, custom wayland compositor

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages