Skip to content

Commit 3cc8cfa

Browse files
committed
feat: add first post
0 parents  commit 3cc8cfa

7 files changed

Lines changed: 146 additions & 0 deletions

File tree

.github/workflows/hugo.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Sample workflow for building and deploying a Hugo site to GitHub Pages
2+
name: Deploy Hugo site to Pages
3+
4+
on:
5+
# Runs on pushes targeting the default branch
6+
push:
7+
branches: ["main"]
8+
9+
# Allows you to run this workflow manually from the Actions tab
10+
workflow_dispatch:
11+
12+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13+
permissions:
14+
contents: read
15+
pages: write
16+
id-token: write
17+
18+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20+
concurrency:
21+
group: "pages"
22+
cancel-in-progress: false
23+
24+
# Default to bash
25+
defaults:
26+
run:
27+
shell: bash
28+
29+
jobs:
30+
# Build job
31+
build:
32+
runs-on: ubuntu-latest
33+
env:
34+
HUGO_VERSION: 0.128.0
35+
steps:
36+
- name: Install Hugo CLI
37+
run: |
38+
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
39+
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
40+
- name: Install Dart Sass
41+
run: sudo snap install dart-sass
42+
- name: Checkout
43+
uses: actions/checkout@v4
44+
with:
45+
submodules: recursive
46+
- name: Setup Pages
47+
id: pages
48+
uses: actions/configure-pages@v5
49+
- name: Install Node.js dependencies
50+
run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
51+
- name: Build with Hugo
52+
env:
53+
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
54+
HUGO_ENVIRONMENT: production
55+
run: |
56+
hugo \
57+
--minify \
58+
--baseURL "${{ steps.pages.outputs.base_url }}/"
59+
- name: Upload artifact
60+
uses: actions/upload-pages-artifact@v3
61+
with:
62+
path: ./public
63+
64+
# Deployment job
65+
deploy:
66+
environment:
67+
name: github-pages
68+
url: ${{ steps.deployment.outputs.page_url }}
69+
runs-on: ubuntu-latest
70+
needs: build
71+
steps:
72+
- name: Deploy to GitHub Pages
73+
id: deployment
74+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.hugo_build.lock
2+
public

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "themes/hugo-PaperMod"]
2+
path = themes/hugo-PaperMod
3+
url = https://github.com/adityatelange/hugo-PaperMod.git

archetypes/default.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
+++
2+
date = '{{ .Date }}'
3+
draft = true
4+
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
5+
+++
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
+++
2+
date = '2025-10-27T17:41:01+08:00'
3+
draft = true
4+
title = 'Wayland Compatibility'
5+
+++
6+
7+
# 从mpv闪退引出的wayland协议兼容性思考
8+
9+
在UOS的最新kwin wayland环境下,mpv播放器在反复全屏和还原过程中会出现闪退的问题,笔者经过一番排查,最终确定根因是kwin_wayland向mpv发送了一条mpv无法处理的高版本版本的wl_pointer事件,导致客户端在libwayland中abort了。借此机会,笔者仔细研究了出现该问题的条件和libwayland的机制,本文记录一些笔者对于系统中wayland协议兼容性的思考。
10+
11+
传统上,我们将兼容性分级为api兼容性和abi兼容性。没有保证api兼容的库,应用将无法在新版本上编译,库应首先保证api兼容性,以确保新版本推出时,适配旧版本的应用可以进行重构建。仅仅没有保证abi兼容性的库,但保证了api兼容的库,应用可以在新版本的库中编译,但是在运行时会产生问题,比如无法访问某一个数据成员,库实现方往往会采用一些隐藏数据成员的方法来实现abi兼容,如C语言编程中的结构体数据成员隐藏和Qt编程中的d指针都是此种方法。api兼容是abi兼容的基础,首先要保证api兼容性,才有可能谈abi兼容性。对于没有破坏api兼容性但是破坏了abi兼容性的库,应用程序也可以通过重编的方法重新保证abi一致(只是保持单次一致,兼容性问题仍然存在)。上述分类是针对于动态库的,静态库因为其每次都需要和应用程序一起重新编译,不存在所谓的abi兼容性。
12+
13+
笔者在本文要讨论的wayland协议,即wayland.xml,它作为文本文件,在各个项目中共享,经过wayland-scanner扫描后生成代码,和应用一起编译,可以看作是一种标准的静态库分发过程。那么对于wayland协议,我们应该如何讨论它的兼容性呢?笔者认为,上述问题应分解为:我们应该在何种情况下讨论wayland协议的何种兼容性。考虑到实际情况和wayland实际处理,应该考虑分发渠道和应用重构建状态的组合。应用可能通过自维护和系统共享两种分发方式获取到wayland协议,在系统wayland协议升级之后,它也会存在已重构建和未重构建这两种状态。即应用存在自分发已重构建、自分发未重构建、系统共享已重构建和系统共享未重构建这四种情况。对于兼容性层面,我们应该考虑api兼容性,以及客户端和合成器采用不同版本协议的通信兼容性。本文只讨论由libwayland提供的wayland层面保证的兼容性,即应用对兼容性无感知,它只会对自己有感知的版本进行实现,并假定对端也是使用相同版本。笔者认为,该情况对于客户端是很常见的情形,对于合成器这种责任重大的应用则见仁见智。
14+
15+
综上,本文讨论的情况可总结为如下表格:
16+
17+
| 分发形式 / 重构建状态 | API / 源码兼容性 | 协议通信兼容性 (客户端 vs. 合成器) |
18+
| :--------------------------------- | :--------------: | :--------------------------------: |
19+
| **自分发wayland.xml (已重构建)** |||
20+
| **自分发wayland.xml (未重构建)** |||
21+
| **系统共享wayland.xml (已重构建)** |||
22+
| **系统共享wayland.xml (未重构建)** |||
23+
24+
从表格中可以看到,wayland保证了大部分情况的两种兼容性,唯独没办法保证系统共享wayland.xml在已重构建情况的兼容性。
25+
26+
wayland.xml中添加新协议的方式,保证了wayland.xml在所有情况下的源码兼容性。每次往wayland.xml中添加一个event或者一个新的enum entry的时候,审核者要求开发者将请求顺序添加在interface的最后面或者enum定义的后面,这样通过wayland scanner生成的代码中的数据结构的前半部分会兼容旧版本,同一含义的枚举值也保持不变,该行为在一定程度上也保证了通信兼容性。对于修改原有协议和删除原有请求或事件,这种行为是被禁止的,需要通过另外开启一个新版本协议的方式进行,这也能解释为什么wayland实现同一个功能的协议版本这么多,当你觉得原版本的功能不满足你的需求的时候,就会新开一个版本,例如text input现在就已经迭代到v4了。
27+
28+
对于协议通信兼容性,wayland.xml中要求对每一个新加的事件或者请求添加一个since字段,说明事件或者请求添加的最低版本。但事实上,since字段起到的作用十分有限,它只能防止客户端调用某一个合成器不支持的请求,但是并不能有效防止合成器向客户端发送一个高版本的事件消息。但是libwayland并不是完全没有对该情况进行检查,从libwayland的代码中可以看到:
29+
30+
```C
31+
static int
32+
queue_event(struct wl_display *display, int len)
33+
{
34+
......
35+
36+
if (opcode >= proxy->object.interface->event_count) {
37+
wl_log("interface '%s' has no event %u\n",
38+
proxy->object.interface->name, opcode);
39+
return -1;
40+
}
41+
42+
......
43+
return size;
44+
}
45+
```
46+
47+
libwayland会在queue_event,也就是读取到消息将消息添加到队列中的时候,检查opcode和interface的event count的大小关系,从而达到防止接收到高版本事件的目的。但是笔者为什么说该防护是有限的呢?因为interface的event_count可能并不是客户端预期的值。
48+
49+
讨论上表中唯一没有保证兼容性的情况,应用使用系统共享的wayland.xml,在v1版本的时候,应用实现了v1版本定义的所有事件,合成器实现的也是v1版本,此时双方可以正常通信。当wayland.xml升级到v2版本,添加了一个事件,合成器跟进升级了v2版本,实现了这一个事件。客户端因为没有升级到v2版本的需求,仍然只实现v1版本,在系统提供的wayland.xml升级之后它进行了重构建,因为事件添加的特性,此次从v1升级到v2并不会破坏客户端的构建,客户端能够正常地构建,不过一旦运行,合成器往客户端发送v2版本的事件的时候,事件就会突破上述检查直至处理的时候找到null的handler而引发abort,因为客户端在重构建后使用的v2版本的wayland.xml,event_count是通过xml生成,自然也是v2版本的event_count。对于另外三种情况,应用使用的都是应用已感知版本的wayland.xml协议构建,event_count是正确的,也就可以在上述代码中被拦截,保证了协议通信兼容性。
50+
51+
细心的读者可以发现,系统共享wayland.xml并且已重构建的情况,相当于应用使用某一版本的wayland.xml协议但是并没有将所有handler实现(哪怕handler不处理任何逻辑)。根据开发经验,这在wayland开发里面显然是一种错误的做法,当合成器发送客户端未实现的事件时,客户端就会直接终止。虽然这是一个在开发者看来明显缺乏审查和被忽视的场景,但对于整个系统维护来说,这是一个再正常不过的场景(系统中wayland.xml升级,应用在无感知的情况下重新构建),比如本文的引子mpv播放器。它是一个三方应用,系统开发者在升级wayland.xml的时候显然不会帮mpv升级wayland的实现,而只会确认它是否能在新版本的wayland.xml协议上编译通过并基本正常运行。或者退一步,系统管理员发现了mpv存在兼容性问题,那如何去对它进行修改也是一个问题。如若mpv上游社区是一个活跃的社区,那更新版本或许是一个轻松的解决方案,但mpv版本和libmpv版本绑定,深度影院依赖libmpv,升级mpv意味着深度影院要重新适配新版的libmpv。为了一个用户可能不常使用到的三方应用而重新适配新版libmpv,只能说单就这个问题本身而言代价太大了,不易推动。
52+
53+
要解决上述问题,笔者认为目前有三种办法:
54+
55+
1. 改进libwayland,让其支持运行时动态检测协议通信兼容性,客户端在bind的时候已经指定了想要使用的协议版本,但是libwayland并没有充分利用这个版本号。
56+
2. 每次libwayland升级时,所有应用均升级到相同的协议版本。这显然工作量很大,可以通过添加空handler减少工作量,但同样为后期维护带来了相当大的复杂度。
57+
3. 每一个使用wayland.xml生成代码的应用,都自己维护一个版本的xml,也就是表格中的自分发方式。Qt项目实际上就是使用这种方式。系统中直接接触到wayland.xml的应用并不多,可能只有GTK和Qt这种底层组件需要做这种工作,但是这种做法加剧了wayland生态的碎片化,从工程领域也不推荐。

hugo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
baseURL = 'https://example.org/'
2+
languageCode = 'en-us'
3+
title = 'My New Hugo Site'
4+
theme = 'hugo-PaperMod'

themes/hugo-PaperMod

Submodule hugo-PaperMod added at 1cf5327

0 commit comments

Comments
 (0)