<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
  <title>飞飞的自留地</title>
  <id>https://blog.fxs.life/</id>
  <link href="https://blog.fxs.life/" rel="alternate" />
  <link href="https://blog.fxs.life/atom.xml" rel="self" type="application/atom+xml" />
  <updated>2026-04-17T00:00:00.000Z</updated>
  <subtitle>有一个夜晚我烧毁了所有的记忆，从此我的梦就透明了；有一个早晨我扔掉了所有的昨天，从此我的脚步就轻盈了</subtitle>
  <generator>Astro</generator>
  <rights>Copyright © 2026 Fei_xiangShi. All rights reserved.</rights>
  <icon>https://blog.fxs.life/favicon.ico</icon>
  <logo>https://blog.fxs.life/_astro/Avatar.7eQWf9xs.avif</logo>
  <author>
    <name>Fei_xiangShi</name>
    <email>admin@fxs.life</email>
    <uri>https://blog.fxs.life/about/</uri>
  </author>
  <category term="笔梦生花" />
  <category term="记忆" />
  <category term="随笔" />
  <category term="芝士分享" />
  <category term="自言自语" />
  <category term="Folo" />
  <category term="Hyprland" />
  <category term="Linux" />
  <category term="Niri" />
  <category term="Rust" />
  <category term="Wayland" />
  <category term="Winit" />
<entry>
  <title>认证 Folo 订阅源</title>
  <id>https://blog.fxs.life/post/%E8%AE%A4%E8%AF%81-folo-%E8%AE%A2%E9%98%85%E6%BA%90/</id>
  <link href="https://blog.fxs.life/post/%E8%AE%A4%E8%AF%81-folo-%E8%AE%A2%E9%98%85%E6%BA%90/" rel="alternate" />
  <updated>2026-04-17T00:00:00.000Z</updated>
  <published>2026-04-17T00:00:00.000Z</published>
  <summary type="html"><![CDATA[<p>我也多么想做一个优秀的开源开发者</p>]]></summary>
  <content type="html"><![CDATA[<p>This message is used to verify that this feed (feedId:267643909570995200) belongs to me (userId:267643635721383936). Join me in enjoying the next generation information browser <a href="https://folo.is" target="_blank" rel="noopener noreferrer">https://folo.is<span class="sr-only">（新标签页打开）</span></a>.</p>
<p>我什么时候才能成为像 DIYgod 一样优秀的人呢</p>]]></content>
  <author>
    <name>Fei_xiangShi</name>
    <email>admin@fxs.life</email>
    <uri>https://blog.fxs.life/about/</uri>
  </author>
  <category term="自言自语" />
  <category term="Folo" />
</entry>
<entry>
  <title>Rust 在 Wayland 下实现窗口最小化和复原</title>
  <id>https://blog.fxs.life/post/wayland-%E4%B8%8B%E5%AE%9E%E7%8E%B0%E7%AA%97%E5%8F%A3%E6%9C%80%E5%B0%8F%E5%8C%96%E5%92%8C%E5%A4%8D%E5%8E%9F/</id>
  <link href="https://blog.fxs.life/post/wayland-%E4%B8%8B%E5%AE%9E%E7%8E%B0%E7%AA%97%E5%8F%A3%E6%9C%80%E5%B0%8F%E5%8C%96%E5%92%8C%E5%A4%8D%E5%8E%9F/" rel="alternate" />
  <updated>2026-04-15T00:00:00.000Z</updated>
  <published>2026-04-15T00:00:00.000Z</published>
  <summary type="html"><![CDATA[<p>从源码分析为什么 Wayland 下最小化如此困难以及我们应该如何实现</p>]]></summary>
  <content type="html"><![CDATA[<p><img src="https://blog.fxs.life/_astro/wayland.Berucgts_22vLtv.avif" alt="Rust 在 Wayland 下实现窗口最小化和复原" /></p><h2 id="阅前提醒">阅前提醒</h2>
<p><strong>本文有较长的分析过程和大量的底层协议分析和对应代码讲解，请在清醒状态下尝试阅读本文。</strong></p>
<h2 id="来自-niri-的神秘的-bug">来自 Niri 的神秘的 Bug</h2>
<p>在我的音乐软件 Rustle 发布到 GitHub 上的一个月不到，我就连续收到了 3 条 issue, 真是受宠若惊，不过这三个都是难修至极的 bug, 尤其是<a href="https://github.com/Fei-xiangShi/Rustle/issues/1" target="_blank" rel="noopener noreferrer">这个<span class="sr-only">（新标签页打开）</span></a>。让人震惊的是我在 Hyprland 下从来没有遇到过这样的情况，为什么 Niri 会有这个协议错误但是 Hyprland 没有呢？不都是 Wayland 吗？</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#B392F0">xdg_surface</span><span style="color:#9ECBFF"> error</span><span style="color:#9ECBFF"> 3:</span><span style="color:#9ECBFF"> must</span><span style="color:#9ECBFF"> ack</span><span style="color:#9ECBFF"> the</span><span style="color:#9ECBFF"> initial</span><span style="color:#9ECBFF"> configure</span><span style="color:#9ECBFF"> before</span><span style="color:#9ECBFF"> attaching</span><span style="color:#9ECBFF"> buffer</span></span></code></pre>
<h2 id="wayland-窗口协议">Wayland 窗口协议</h2>
<h3 id="概念定义">概念定义</h3>
<h4 id="1-wl_surface原始的画布">1. <code>wl_surface</code>：原始的画布</h4>
<ul>
<li><strong>职责</strong>：它只负责维护 Buffer（像素数据）、处理损伤区域（Damaged regions）和接收渲染同步信号（Frame callbacks）。</li>
<li><strong>状态</strong>：它是<strong>协议无关</strong>的。它不知道自己是一个窗口、一个图标、还是一个右键菜单。</li>
<li><strong>生命周期</strong>：它是<strong>持久存在</strong>的。即便窗口隐藏了，只要应用存在，就依然在内存里 。</li>
<li><strong>操作</strong>：unmap wl_surface 之后应用界面消失，remap wl_surface 之后可以重新显示。unmap 的操作是 <code>attach(None)</code> 之后 <code>commit()</code>，remap 的操作是 <code>attach(buffer)</code> 之后 <code>commit()</code></li>
</ul>
<h4 id="2-xdg_surface通用的窗口化入口">2. <code>xdg_surface</code>：通用的“窗口化”入口</h4>
<p>当决定让一个 <code>wl_surface</code> 变成某种意义上的“窗口”时，你必须先为它创建一个 <code>xdg_surface</code>。</p>
<ul>
<li><strong>职责（中间人）</strong>：它是底层的 <code>wl_surface</code> 与 <code>xdg_shell</code> 协议之间的<strong>协调层</strong>。</li>
<li><strong>核心功能：Configure 机制</strong>。它处理合成器发来的配置请求。只有当 <code>xdg_surface</code> 确认了配置（Ack configure），合成器才会允许把内容显示出来。</li>
<li><strong>意义</strong>：它本身不代表具体窗口，它是 <code>xdg_toplevel</code>（顶层窗口）或 <code>xdg_popup</code>（弹出菜单）的共同基类。</li>
<li><strong>操作</strong>：<code>xdg_surface.configure(serial)</code> 和 <code>xdg_surface.ack_configure(serial)</code> 等，前者是 compositor 主动调用的，是合成器的实现，后者是 client 接收用的，是本软件所用 Wayland client 即 SCTK 实现的，本文遇到的问题与此相关。</li>
</ul>
<h4 id="3-xdg_toplevel真正的顶层窗口角色">3. <code>xdg_toplevel</code>：真正的“顶层窗口”角色</h4>
<p>这是我们在桌面交互中感知到的那个“窗口”。</p>
<ul>
<li><strong>职责（管理权限）</strong>：它负责一切与桌面环境交互的行为。
<ul>
<li><strong>设置元数据</strong>：标题（Title）、应用 ID（App ID） 。</li>
<li><strong>窗口状态</strong>：最大化、最小化、全屏、交互式缩放 。</li>
</ul>
</li>
<li><strong>约束</strong>：它不能独立存在，必须依附于一个 <code>xdg_surface</code>。</li>
<li><strong>操作</strong>：<code>set_title</code>，<code>set_app_id</code>，<code>set_maximized</code>，<code>unset_maximized</code>，<code>set_fullscreen</code>，<code>unset_fullscreen</code>，<del><code>set_minimized</code></del>，<code>xdg_toplevel.configure(width, height, states)</code>， 本文遇到的协议错误是 xdg_surface 的，不是 toplevel 的 configure 引起的。</li>
</ul>
<h3 id="wayland-的最小化机制">Wayland 的最小化机制</h3>
<p>知道了 <code>xdg_toplevel</code> 是负责控制最小化的，那么我们在 <code>xdg_shell</code> 里面寻找最小化请求<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">request</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"set_minimized"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;</span><span style="color:#85E89D">description</span><span style="color:#B392F0"> summary</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"set the window as minimized"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">		Request that the compositor minimize your surface. There is no</span></span>
<span class="line"><span style="color:#E1E4E8">		way to know if the surface is currently minimized, nor is there</span></span>
<span class="line"><span style="color:#E1E4E8">		any way to unset minimization on this surface.</span></span>
<span class="line"><span style="color:#E1E4E8">	</span></span>
<span class="line"><span style="color:#E1E4E8">		If you are looking to throttle redrawing when minimized, please</span></span>
<span class="line"><span style="color:#E1E4E8">		instead use the wl_surface.frame event for this, as this will</span></span>
<span class="line"><span style="color:#E1E4E8">		also work with live previews on windows in Alt-Tab, Expose or</span></span>
<span class="line"><span style="color:#E1E4E8">		similar compositor features.</span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;/</span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">request</span><span style="color:#E1E4E8">></span></span></code></pre>
<p>竟然如此诡异！为什么定义了 <code>set_minimized</code> 但是没有 <code>unset_minimized</code> 而且应用也无法知道自己是否被最小化了？何意味？另外，这个 API 也只是请求最小化，经过测试，Hyprland 和 Niri 都直接忽视了这个请求。</p>
<p>也就是说，Wayland 官方没有给最小化和还原的功能！但是这很明显不符合我们平常的使用体验：在 Windows 和 MacOS 上，使用最小化是最常见不过的手段，然后应用会出现在任务栏或者 Dock 里面，虽然 Linux 桌面现在没有名义上的任务栏或 Dock 的统一实现，但是关闭应用后台运行，然后通过系统托盘再唤出，这算是非常常见的行为了吧？虽然新出的任务栏协议 <code>wlr-foreign-toplevel-management-unstable-v1</code> 已经被 Waybar 支持了，但是我用的 Quickshell 还没有支持。另外早在 X11 时代，就有众多的例如 <code>XEmbed</code> 和 <code>StatusNotifierItem</code> 协议来实现系统托盘功能了，虽然万恶的 GNOME 并不喜欢，但是这并不是 Wayland 不考虑的原因。</p>
<p>由于以上原因，<code>winit</code> 系列的窗口管理在实现这一部分的时候选择了原教旨主义</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> set_visible</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">, _visible</span><span style="color:#F97583">:</span><span style="color:#B392F0"> bool</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D">    // Not possible on Wayland.</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>不过我并不信 Wayland 教，所以我准备提出修正主义。平时在 Linux 上使用软件的时候，观察到比如 QQ, Telegram 这种软件，它们是可以关闭后再从系统托盘里打开的，所以我们作为好用的播放器，也需要做到这样的使用体验。既然 Chromium 能实现，那么就说明这在技术上是可以实现的，我们只需要照抄它的实现就好了。</p>
<h2 id="首次尝试解决办法">首次尝试解决办法</h2>
<p>其实我早就知道 Wayland 没有实现最小化了，并且我在发布到 GitHub 之前就自己尝试修复了，那么我如何抄袭 Chromium 来实现最小化呢？</p>
<ol>
<li>我修改了 winit, 让他支持了 Wayland 下的 <code>set_visible(false)</code>，这样就可以做到关闭窗口了，此处 <code>surface: window.wl_surface().clone()</code>，提交了空 buffer, 这样 compositor 就不会显示应用画面了。
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#E1E4E8">surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">attach</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">None</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">commit</span><span style="color:#E1E4E8">();</span></span></code></pre>
</li>
<li>在要显示窗口的时候，重置帧回调状态，然后 <code>surface.commit()</code>，再 <code>request_redraw()</code>，希望应用重新画一帧，把 buffer attach 回去，这样就能继续显示窗口了。
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">let</span><span style="color:#F97583"> mut</span><span style="color:#E1E4E8"> state </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window_state</span><span style="color:#F97583">.</span><span style="color:#B392F0">lock</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">.</span><span style="color:#B392F0">unwrap</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">state</span><span style="color:#F97583">.</span><span style="color:#B392F0">set_visible</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#6A737D">// Reset frame callback state to break the deadlock.</span></span>
<span class="line"><span style="color:#6A737D">// When hidden, compositor stops sending frame callbacks, leaving state as Requested.</span></span>
<span class="line"><span style="color:#6A737D">// We need to reset it so the event loop will dispatch RedrawRequested.</span></span>
<span class="line"><span style="color:#E1E4E8">state</span><span style="color:#F97583">.</span><span style="color:#B392F0">frame_callback_reset</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#6A737D">// Commit to signal the compositor.</span></span>
<span class="line"><span style="color:#E1E4E8">surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">commit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#6A737D">// Ask the application to redraw so that a buffer is re-attached.</span></span>
<span class="line"><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#B392F0">request_redraw</span><span style="color:#E1E4E8">();</span></span></code></pre>
<code>request_redraw</code> 的实现如下：
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> request_redraw</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#F97583">    if</span><span style="color:#F97583"> !</span><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window_state</span><span style="color:#F97583">.</span><span style="color:#B392F0">lock</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">.</span><span style="color:#B392F0">unwrap</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">.</span><span style="color:#B392F0">visible</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> 	   return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8"> 	}</span></span>
<span class="line"><span style="color:#E1E4E8">   </span></span>
<span class="line"><span style="color:#F97583">    if</span><span style="color:#79B8FF"> self</span></span>
<span class="line"><span style="color:#F97583"> 	   .</span><span style="color:#E1E4E8">window_requests</span></span>
<span class="line"><span style="color:#F97583"> 	   .</span><span style="color:#E1E4E8">redraw_requested</span></span>
<span class="line"><span style="color:#F97583"> 	   .</span><span style="color:#B392F0">compare_exchange</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">false</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">Ordering</span><span style="color:#F97583">::</span><span style="color:#B392F0">Relaxed</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">Ordering</span><span style="color:#F97583">::</span><span style="color:#B392F0">Relaxed</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> 	   .</span><span style="color:#B392F0">is_ok</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">    {</span></span>
<span class="line"><span style="color:#79B8FF"> 	   self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">event_loop_awakener</span><span style="color:#F97583">.</span><span style="color:#B392F0">ping</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
</li>
<li>事件循环不给隐藏窗口分发 RedrawRequested，即隐藏时不再允许 <code>request_redraw()</code>。</li>
</ol>
<p>这看起来就是对的，也就是把 <code>set_visible(false)</code> 解释成了 “unmap <strong>wl_surface</strong>”，把 <code>set_visible(true)</code> 解释成了 “commit 一个空 <strong>wl_surface</strong> 触发 remap，然后请求 redraw 重新
attach buffer”。这里注意：我们 unmap 的，attach 的，commit 的都是 <strong><code>wl_surface</code></strong>。</p>
<p>事实上，在 Hyprland 上，这样确实是对的，因为我用的就是 Hyprland, 并且我自己用了很久都没有问题，那为什么在 Niri 上就要协议报错呢？</p>
<h2 id="深入-wayland-协议">深入 Wayland 协议</h2>
<p>为了搞清楚 Wayland 到底如何看待应用尝试最小化和还原，以及 Chromium 是如何在 Niri 和 Hyprland 上都实现效果相同的最小化，我决定从协议层面和源码研究。观察到，在 <code>xdg_surface.configure</code> 协议中写道<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">event</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"configure"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;</span><span style="color:#85E89D">description</span><span style="color:#B392F0"> summary</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"suggest a surface change"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">		The configure event marks the end of a configure sequence. A configure</span></span>
<span class="line"><span style="color:#E1E4E8">		sequence is a set of one or more events configuring the state of the</span></span>
<span class="line"><span style="color:#E1E4E8">		xdg_surface, including the final xdg_surface.configure event.</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		Where applicable, xdg_surface surface roles will during a configure</span></span>
<span class="line"><span style="color:#E1E4E8">		sequence extend this event as a latched state sent as events before the</span></span>
<span class="line"><span style="color:#E1E4E8">		xdg_surface.configure event. Such events should be considered to make up</span></span>
<span class="line"><span style="color:#E1E4E8">		a set of atomically applied configuration states, where the</span></span>
<span class="line"><span style="color:#E1E4E8">		xdg_surface.configure commits the accumulated state.</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		Clients should arrange their surface for the new states, and then send</span></span>
<span class="line"><span style="color:#E1E4E8">		an ack_configure request with the serial sent in this configure event at</span></span>
<span class="line"><span style="color:#E1E4E8">		some point before committing the new surface.</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		If the client receives multiple configure events before it can respond</span></span>
<span class="line"><span style="color:#E1E4E8">		to one, it is free to discard all but the last event it received.</span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;/</span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;</span><span style="color:#85E89D">arg</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"serial"</span><span style="color:#B392F0"> type</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"uint"</span><span style="color:#B392F0"> summary</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"serial of the configure event"</span><span style="color:#E1E4E8">/></span></span>
<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">event</span><span style="color:#E1E4E8">></span></span></code></pre>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">interface</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"xdg_toplevel"</span><span style="color:#B392F0"> version</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"7"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">	&#x3C;</span><span style="color:#85E89D">description</span><span style="color:#B392F0"> summary</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"toplevel surface"</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">		This interface defines an xdg_surface role which allows a surface to,</span></span>
<span class="line"><span style="color:#E1E4E8">		among other things, set window-like properties such as maximize,</span></span>
<span class="line"><span style="color:#E1E4E8">		fullscreen, and minimize, set application-specific metadata like title </span></span>
<span class="line"><span style="color:#E1E4E8">		and id, and well as trigger user interactive operations such as </span></span>
<span class="line"><span style="color:#E1E4E8">		interactive resize and move.</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		A xdg_toplevel by default is responsible for providing the full intended</span></span>
<span class="line"><span style="color:#E1E4E8">		visual representation of the toplevel, which depending on the window</span></span>
<span class="line"><span style="color:#E1E4E8">		state, may mean things like a title bar, window controls and drop shadow.</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		Unmapping an xdg_toplevel means that the surface cannot be shown</span></span>
<span class="line"><span style="color:#E1E4E8">		by the compositor until it is explicitly mapped again.</span></span>
<span class="line"><span style="color:#E1E4E8">		All active operations (e.g., move, resize) are canceled and all</span></span>
<span class="line"><span style="color:#E1E4E8">		attributes (e.g. title, state, stacking, ...) are discarded for</span></span>
<span class="line"><span style="color:#E1E4E8">		an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to</span></span>
<span class="line"><span style="color:#E1E4E8">		the state it had right after xdg_surface.get_toplevel. The client</span></span>
<span class="line"><span style="color:#E1E4E8">		can re-map the toplevel by performing a commit without any buffer</span></span>
<span class="line"><span style="color:#E1E4E8">		attached, waiting for a configure event and handling it as usual (see</span></span>
<span class="line"><span style="color:#E1E4E8">		xdg_surface description).</span></span>
<span class="line"><span style="color:#E1E4E8">		</span></span>
<span class="line"><span style="color:#E1E4E8">		Attaching a null buffer to a toplevel unmaps the surface.</span></span>
<span class="line"><span style="color:#E1E4E8">    &#x3C;/</span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#E1E4E8">    ...</span></span>
<span class="line"><span style="color:#E1E4E8">    ...</span></span>
<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">interface</span><span style="color:#E1E4E8">></span></span></code></pre>
<p>上面两段的大概意思是说 Wayland 要求创建窗口的流程符合如下顺序：</p>
<p><img __ASTRO_IMAGE_="{&#x22;src&#x22;:&#x22;Wayland 最小化流程.png&#x22;,&#x22;alt&#x22;:&#x22;Wayland 窗口首次映射、unmap 与 remap 时的 configure 和 commit 时序图&#x22;,&#x22;index&#x22;:0}"></p>
<p>根据协议报错，我应该是在接受 configure 之前就 attach 了 buffer, 事实也确实如此，根据我们上面的代码，我们在 commit 了新的 wl_surface 之后直接请求了 redraw, 在 redraw 过程中 attach 了新的 buffer, 按照 Wayland 协议，这样做顺序不对，Niri 也完成了对这个顺序的校验，如果顺序不对，那么就会抛出协议报错。但目前看来，Hyprland 看上去是没有校验这个顺序的。</p>
<h2 id="第二次尝试解决方法">第二次尝试解决方法</h2>
<p>我们知道了 Wayland 标准是如何要求我们按照流程进行最小化的，于是便可以着手修复这个问题了，这次我在 remap 之后添加了等待 compositor 发送 configure 的逻辑，并在 ack 之后再进行请求重绘，这次在 Niri 上也能非常丝滑地最小化和复原了。但是事情真的有这么顺利吗？如果到此为止，这不过是一次简单的协议利用开发经历，跨平台适配 Wayland 案例罢了。然而在这个时候，我发现 Hyprland 竟然不能复原窗口了！</p>
<p>我明明是按照 Wayland 协议来完整实现了 unmap 和 remap 的流程了啊，为什么一开始能用的 Hyprland 反而不能用了呢？就连严格实现流程校验的 Niri 都能正常使用了，Hyprland 反而不能使用了，就简单加了一个等待 configure 的逻辑，难道说——Hyprland 其实不是 Wayland 的标准实现？</p>
<h2 id="不同-compositor-实现问题">不同 Compositor 实现问题</h2>
<h3 id="hyprland-对于-wl_surface-的-unmap-行为的处理">Hyprland 对于 wl_surface 的 unmap 行为的处理</h3>
<p>同样代码的情况下，Niri 和 Hyprland 行为不同，并且 Hyprland 很有可能是卡在了<strong>等待 configure</strong> 这一步上，那么我们应该细看 Hyprland 对于这个行为是怎么处理的，见 <code>Hyprland/src/protocols/XDGShell.cpp</code> 第 421 行<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="" aria-describedby="footnote-label">3</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="cpp"><code><span class="line"><span style="color:#F97583">if</span><span style="color:#B392F0"> UNLIKELY</span><span style="color:#E1E4E8"> (m_initialCommit </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> m_surface->m_pending.buffer) {</span></span>
<span class="line"><span style="color:#E1E4E8">	m_resource-></span><span style="color:#B392F0">error</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">-</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"Buffer attached before initial commit"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#F97583">	return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> (m_surface->m_current.texture </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#F97583"> !</span><span style="color:#E1E4E8">m_mapped) {</span></span>
<span class="line"><span style="color:#6A737D">	// this forces apps to not draw CSD.</span></span>
<span class="line"><span style="color:#F97583">	if</span><span style="color:#E1E4E8"> (m_toplevel)</span></span>
<span class="line"><span style="color:#E1E4E8">		m_toplevel-></span><span style="color:#B392F0">setMaximized</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">	m_mapped </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">	m_surface-></span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">	m_events.map.</span><span style="color:#B392F0">emit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">	return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">m_surface->m_current.texture </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> m_mapped) {</span></span>
<span class="line"><span style="color:#E1E4E8">	m_mapped </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">	m_events.unmap.</span><span style="color:#B392F0">emit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">	m_surface-></span><span style="color:#B392F0">unmap</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">	return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">m_events.commit.</span><span style="color:#B392F0">emit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">m_initialCommit </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span></code></pre>
<p>那么 Hyprland 是如何发送 configure 的呢，见 <code>Hyprland/src/protocols/XDGShell.cpp</code> 第 538 行<sup><a href="#user-content-fn-3" id="user-content-fnref-3-2" data-footnote-ref="" aria-describedby="footnote-label">3</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="cpp"><code><span class="line"><span style="color:#F97583">uint32_t</span><span style="color:#B392F0"> CXDGSurfaceResource</span><span style="color:#E1E4E8">::</span><span style="color:#B392F0">scheduleConfigure</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (m_configureSource)</span></span>
<span class="line"><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> m_scheduledSerial;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">    m_configureSource </span><span style="color:#F97583">=</span><span style="color:#B392F0"> wl_event_loop_add_idle</span><span style="color:#E1E4E8">(g_pCompositor->m_wlEventLoop, onConfigure, </span><span style="color:#79B8FF">this</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">    m_scheduledSerial </span><span style="color:#F97583">=</span><span style="color:#B392F0"> wl_display_next_serial</span><span style="color:#E1E4E8">(g_pCompositor->m_wlDisplay);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> m_scheduledSerial;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> CXDGSurfaceResource</span><span style="color:#E1E4E8">::</span><span style="color:#B392F0">configure</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#E1E4E8">    m_configureSource </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> nullptr</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    m_resource-></span><span style="color:#B392F0">sendConfigure</span><span style="color:#E1E4E8">(m_scheduledSerial);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>那么 Hyprland 是多久调用 <code>scheduleConfigure()</code> 的呢，见 <code>Hyprland/src/desktop/view/Window.cpp</code> 第 2318 行<sup><a href="#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref="" aria-describedby="footnote-label">4</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="cpp"><code><span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> CWindow</span><span style="color:#E1E4E8">::</span><span style="color:#B392F0">commitWindow</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">m_isX11 </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> m_xdgSurface->m_initialCommit) {</span></span>
<span class="line"><span style="color:#6A737D">        // try to calculate static rules already for any floats</span></span>
<span class="line"><span style="color:#E1E4E8">        m_ruleApplicator-></span><span style="color:#B392F0">readStaticRules</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">        const</span><span style="color:#E1E4E8"> Vector2D predSize </span><span style="color:#F97583">=</span><span style="color:#F97583"> !</span><span style="color:#E1E4E8">m_ruleApplicator->static_.floating.</span><span style="color:#B392F0">value_or</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">false</span><span style="color:#E1E4E8">)</span><span style="color:#6A737D"> // no float rule</span></span>
<span class="line"><span style="color:#F97583">                &#x26;&#x26;</span><span style="color:#F97583"> !</span><span style="color:#E1E4E8">m_isFloating</span><span style="color:#6A737D">                                                      // not floating</span></span>
<span class="line"><span style="color:#F97583">                &#x26;&#x26;</span><span style="color:#F97583"> !</span><span style="color:#B392F0">parent</span><span style="color:#E1E4E8">()</span><span style="color:#6A737D">                                                          // no parents</span></span>
<span class="line"><span style="color:#F97583">                &#x26;&#x26;</span><span style="color:#F97583"> !</span><span style="color:#E1E4E8">g_pXWaylandManager-></span><span style="color:#B392F0">shouldBeFloated</span><span style="color:#E1E4E8">(m_self.</span><span style="color:#B392F0">lock</span><span style="color:#E1E4E8">(), </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">)</span><span style="color:#6A737D">          // should not be floated</span></span>
<span class="line"><span style="color:#F97583">            ?</span></span>
<span class="line"><span style="color:#E1E4E8">            g_layoutManager-></span><span style="color:#B392F0">predictSizeForNewTiledTarget</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">value_or</span><span style="color:#E1E4E8">(Vector2D{}) </span><span style="color:#F97583">:</span></span>
<span class="line"><span style="color:#E1E4E8">            Vector2D{};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">        Log</span><span style="color:#E1E4E8">::logger-></span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Log</span><span style="color:#E1E4E8">::DEBUG, </span><span style="color:#9ECBFF">"Layout predicts size {} for {}"</span><span style="color:#E1E4E8">, predSize, m_self.</span><span style="color:#B392F0">lock</span><span style="color:#E1E4E8">());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">        m_xdgSurface->m_toplevel-></span><span style="color:#B392F0">setSize</span><span style="color:#E1E4E8">(predSize);</span></span>
<span class="line"><span style="color:#F97583">        return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">    ...</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>可见，Hyprland 的实现中，<code>m_initialCommit</code> 是调用 <code>scheduleConfigure()</code> 的必要条件，而 <code>m_initialCommit</code> 是一个 <code>m_xdgSurface</code> 的属性，同一个 <code>xdg_surface</code> 一旦完成过第一次 initial commit/configure，它的 <code>m_initialCommit</code> 就永久变成 false, unmap 只会把它标成 unmapped，不会把它送回“需要新的 initial configure”状态。但是我们当前的修复并没有销毁 <code>xdg_surface</code>，而是只对 <code>wl_surface</code> 进行了 unmap, 除非我们按照 Wayland 协议使用 <code>xdg_toplevel</code> API 调用 <code>setSize()</code> 之类的函数，否则 Hyprland 再也不会发送 configure 给我们。</p>
<h3 id="niri-对于-wl_surface-的-unmap-行为的处理">Niri 对于 wl_surface 的 unmap 行为的处理</h3>
<p>Hyprland 将认为 <code>wl_surface</code> 的 unmap 和是否需要 <code>initialCommit</code> 的 configure 是无关的，那 Niri 呢？Niri 非常直接明了地说明了：新 unmap 的窗口必须重新初始化。见 <code>niri/src/handlers/compositor.rs</code> 第 311 行<sup><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref="" aria-describedby="footnote-label">5</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// Newly-unmapped toplevels must perform the initial commit-configure sequence</span></span>
<span class="line"><span style="color:#6A737D">// afresh.</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> unmapped </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Unmapped</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">(window);</span></span>
<span class="line"><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">niri</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">unmapped_windows</span><span style="color:#F97583">.</span><span style="color:#B392F0">insert</span><span style="color:#E1E4E8">(surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">(), unmapped);</span></span></code></pre>
<p>而且 Niri 专门为 unmap 这个行为写了处理逻辑，见 <code>niri/src/window/unmapped.rs</code> 第 80 行<sup><a href="#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref="" aria-describedby="footnote-label">6</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Unmapped</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D">    /// Wraps a newly created window that hasn't been initially configured yet.</span></span>
<span class="line"><span style="color:#F97583">    pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> new</span><span style="color:#E1E4E8">(window</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Window</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> Self</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF">        Self</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8">            window,</span></span>
<span class="line"><span style="color:#E1E4E8">            state</span><span style="color:#F97583">:</span><span style="color:#B392F0"> InitialConfigureState</span><span style="color:#F97583">::</span><span style="color:#B392F0">NotConfigured</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8">                wants_fullscreen</span><span style="color:#F97583">:</span><span style="color:#B392F0"> None</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">                wants_maximized</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">            },</span></span>
<span class="line"><span style="color:#E1E4E8">            activation_token_data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> None</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">        }</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">    pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> needs_initial_configure</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> bool</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">        matches!</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">state, </span><span style="color:#B392F0">InitialConfigureState</span><span style="color:#F97583">::</span><span style="color:#B392F0">NotConfigured</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">..</span><span style="color:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">    pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> toplevel</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &#x26;</span><span style="color:#B392F0">ToplevelSurface</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF">        self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window</span><span style="color:#F97583">.</span><span style="color:#B392F0">toplevel</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">.</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"no X11 support"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>之后，如果这个已经 unmapped 的 surface 又 commit 了，Niri 会检查，见 <code>niri/src/handlers/compositor.rs</code> 第 255 行<sup><a href="#user-content-fn-5" id="user-content-fnref-5-2" data-footnote-ref="" aria-describedby="footnote-label">5</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> unmapped</span><span style="color:#F97583">.</span><span style="color:#B392F0">needs_initial_configure</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> toplevel </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> unmapped</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window</span><span style="color:#F97583">.</span><span style="color:#B392F0">toplevel</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">.</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"no x11 support"</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#B392F0">queue_initial_configure</span><span style="color:#E1E4E8">(toplevel);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>排队结束后正式发送，见 <code>niri/src/handlers/xdg_shell.rs</code> 第 1192 行<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref="" aria-describedby="footnote-label">7</a></sup>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">if</span><span style="color:#F97583"> let</span><span style="color:#B392F0"> Some</span><span style="color:#E1E4E8">(unmapped) </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> state</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">niri</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">unmapped_windows</span><span style="color:#F97583">.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(toplevel</span><span style="color:#F97583">.</span><span style="color:#B392F0">wl_surface</span><span style="color:#E1E4E8">()) {</span></span>
<span class="line"><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> unmapped</span><span style="color:#F97583">.</span><span style="color:#B392F0">needs_initial_configure</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#E1E4E8">	    state</span><span style="color:#F97583">.</span><span style="color:#B392F0">send_initial_configure</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#E1E4E8">toplevel);</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>对应的，Hyprland 在得知一个 <code>wl_surface</code> 被 unmap 之后会干什么呢？</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> (m_surface</span><span style="color:#F97583">-></span><span style="color:#E1E4E8">m_current</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">texture </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#F97583"> !</span><span style="color:#E1E4E8">m_mapped) {</span></span>
<span class="line"><span style="color:#E1E4E8">    m_mapped </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    m_surface</span><span style="color:#F97583">-></span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">    m_events</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">map</span><span style="color:#F97583">.</span><span style="color:#B392F0">emit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">m_surface</span><span style="color:#F97583">-></span><span style="color:#E1E4E8">m_current</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">texture </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> m_mapped) {</span></span>
<span class="line"><span style="color:#E1E4E8">    m_mapped </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    m_events</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">unmap</span><span style="color:#F97583">.</span><span style="color:#B392F0">emit</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">    m_surface</span><span style="color:#F97583">-></span><span style="color:#B392F0">unmap</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>答案是什么都不干，单纯标记它一下。</p>
<h2 id="最终解决方法">最终解决方法</h2>
<p>既然知道了不同 Compositor 对 Wayland 协议的实现不同，尤其是对 configure 的发送时机有着不一样的理解，那我们最终的解决方法就是找他们共同的，会发送 configure 的时机——重建 <code>xdg_surface</code> 的时候。我们之前只是单纯 unmap 了 <code>wl_surface</code>，虽然 Niri 能正确地处理它，但是 Hyprland 并不觉得需要再次 initial Commit 和 Configure, 而是可以直接 attach buffer。所以我们为了多平台表现一致，决定重新实现我们的最小化步骤，而不是在恢复的时候等待不同 Compositor 给我们可能永远不会存在的 Configure。</p>
<p>在实现过程中，发现旧版 <code>smithay-client-toolkit</code> 在实现上把 wl_surface 包装成 <code>xdg_surface</code> + <code>xdg_toplevel</code>，并把它们存进 WindowInner；只有 WindowInner 被 drop 时，才会按协议销毁这些对象，也就是说必须要让 <code>wl_surface</code> 给  <code>xdg_surface</code> 和 <code>xdg_toplevel</code> 陪葬。但是如果直接 drop <code>wl_surface</code>, 恢复时重建会消耗大量资源，导致窗口出现速度非常慢，这不是我们想看到的。</p>
<p>所以我重新设计了 <code>smithay-client-toolkit</code> 的设计，方便单独销毁 <code>xdg_surface</code> 和 <code>xdg_toplevel</code>，并给 <code>winit</code> 加入了更多的状态：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> struct</span><span style="color:#B392F0"> Surface</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8">    wl_surface</span><span style="color:#F97583">:</span><span style="color:#B392F0"> wl_surface</span><span style="color:#F97583">::</span><span style="color:#B392F0">WlSurface</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">    owns_wl_surface</span><span style="color:#F97583">:</span><span style="color:#B392F0"> bool</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Surface</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">    pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> adopt</span><span style="color:#E1E4E8">(surface</span><span style="color:#F97583">:</span><span style="color:#B392F0"> wl_surface</span><span style="color:#F97583">::</span><span style="color:#B392F0">WlSurface</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> Self</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF">	    Self</span><span style="color:#E1E4E8"> { wl_surface</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> surface, owns_wl_surface</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Clone</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> Surface</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">    fn</span><span style="color:#B392F0"> clone</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> Self</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF">	    Self</span><span style="color:#E1E4E8"> { wl_surface</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">wl_surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">(), owns_wl_surface</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Drop</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> Surface</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">    fn</span><span style="color:#B392F0"> drop</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#F97583">	    if</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">owns_wl_surface {</span></span>
<span class="line"><span style="color:#79B8FF">	  	    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">wl_surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">destroy</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">	    }</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>这样就可以获得一个 <code>wl_surface</code> 的 clone, 然后可以在 <code>winit</code> 里进行重新创建 <code>xdg_surface</code> 和 <code>xdg_toplevel</code>：</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> hide_role</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">visible </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">pending_show_configure </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">frame_callback_state </span><span style="color:#F97583">=</span><span style="color:#B392F0"> FrameCallbackState</span><span style="color:#F97583">::</span><span style="color:#B392F0">None</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> window </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window</span><span style="color:#F97583">.</span><span style="color:#B392F0">take</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#B392F0">    drop</span><span style="color:#E1E4E8">(window);</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">frame </span><span style="color:#F97583">=</span><span style="color:#B392F0"> None</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> show_role</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&#x26;mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">visible </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">pending_show_configure </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">frame_callback_state </span><span style="color:#F97583">=</span><span style="color:#B392F0"> FrameCallbackState</span><span style="color:#F97583">::</span><span style="color:#B392F0">None</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">frame </span><span style="color:#F97583">=</span><span style="color:#B392F0"> None</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> window </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">xdg_shell</span><span style="color:#F97583">.</span><span style="color:#B392F0">create_window</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#79B8FF">	    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">surface</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#79B8FF">	    self</span><span style="color:#F97583">.</span><span style="color:#B392F0">requested_window_decorations</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#F97583">	    &#x26;</span><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">queue_handle,</span></span>
<span class="line"><span style="color:#E1E4E8">    );</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">window </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Some</span><span style="color:#E1E4E8">(window);</span></span>
<span class="line"><span style="color:#79B8FF">    self</span><span style="color:#F97583">.</span><span style="color:#B392F0">replay_role_state</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>这样就可以完美自己控制 <code>wl_surface</code> 的生命周期并能让所有的 Compositor 都发送 Configure 了。</p>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p><a href="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/stable/xdg-shell/xdg-shell.xml#:~:text=%3Crequest%20name%3D%22set_minimized,%3C/request%3E" target="_blank" rel="noopener noreferrer">xdg-shell.set_minimized 协议<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p><a href="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/stable/xdg-shell/xdg-shell.xml#:~:text=name%3D%22configure%22%3E-,%3Cdescription%20summary%3D%22suggest%20a%20surface%20change%22%3E,%3C/description%3E,-%3Carg%20name%3D%22serial" target="_blank" rel="noopener noreferrer">xdg_surface.configure 协议<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p><a href="https://github.com/hyprwm/Hyprland/blob/main/src/protocols/XDGShell.cpp" target="_blank" rel="noopener noreferrer">Hyprland 的 XGD Shell 协议实现<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a> <a href="#user-content-fnref-3-2" data-footnote-backref="" aria-label="Back to reference 3-2" class="data-footnote-backref">↩<sup>2</sup></a></p>
</li>
<li id="user-content-fn-7">
<p><a href="https://github.com/hyprwm/Hyprland/blob/main/src/desktop/view/Window.cpp" target="_blank" rel="noopener noreferrer">Hyprland 对于 Configure 时机的设计<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p><a href="https://github.com/niri-wm/niri/blob/main/src/handlers/compositor.rs" target="_blank" rel="noopener noreferrer">Niri 的 Compositor 实现<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a> <a href="#user-content-fnref-5-2" data-footnote-backref="" aria-label="Back to reference 5-2" class="data-footnote-backref">↩<sup>2</sup></a></p>
</li>
<li id="user-content-fn-6">
<p><a href="https://github.com/niri-wm/niri/blob/main/src/window/unmapped.rs" target="_blank" rel="noopener noreferrer">Niri 对于窗口 unmap 的处理<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p><a href="https://github.com/niri-wm/niri/blob/main/src/handlers/xdg_shell.rs" target="_blank" rel="noopener noreferrer">Niri 的 xdg_shell 实现<span class="sr-only">（新标签页打开）</span></a> <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
  <author>
    <name>Fei_xiangShi</name>
    <email>admin@fxs.life</email>
    <uri>https://blog.fxs.life/about/</uri>
  </author>
  <category term="芝士分享" />
  <category term="Linux" />
  <category term="Wayland" />
  <category term="Rust" />
  <category term="Winit" />
  <category term="Hyprland" />
  <category term="Niri" />
</entry>
<entry>
  <title>我的博客旧梦</title>
  <id>https://blog.fxs.life/post/%E6%88%91%E7%9A%84%E5%8D%9A%E5%AE%A2%E6%97%A7%E6%A2%A6/</id>
  <link href="https://blog.fxs.life/post/%E6%88%91%E7%9A%84%E5%8D%9A%E5%AE%A2%E6%97%A7%E6%A2%A6/" rel="alternate" />
  <updated>2026-04-15T00:00:00.000Z</updated>
  <published>2026-04-15T00:00:00.000Z</published>
  <summary type="html"><![CDATA[<p>也许多年以来关于如何拥有一个博客的困扰，现在终于解决了吧</p>]]></summary>
  <content type="html"><![CDATA[<p><img src="https://blog.fxs.life/_astro/typecho.Dhu88Jb6_ZirhyP.avif" alt="我的博客旧梦" /></p><h2 id="初识互联网和博客的最初记忆">初识互联网和博客的最初记忆</h2>
<p>在初中的时候，第一次意识到了互联网是可以认识其他人的，那是一个在没有 MC 服务器玩的下午，我偶然在 B 站或者 QQ 空间刷到了老腐竹樱花的一条动态，上面有一条网站链接，因为太期待服务器能复活了，所以迫不及待地点进去想看看是不是有服务器相关的消息，但是很可惜，只是一些日常的分享，但是这个网站却十足地吸引到了我，这不是一个官网，不是互联网上的那种随处可见的网站，没有很多信息，排版非常优美，页面非常简洁，没有广告，也没有代表哪家企业，看上去完全就是樱花个人拥有的网站，给了我非常大的震撼，个人也能拥有网站吗？再次之前，我一直以为网站是一种高科技产物，是只有大公司聘请计算机专业的程序员才能做出来的，如果要做的好看，更是需要顶尖技术的资深程序员才能做出来的，但是我看着这个页面，它甚至还有一个 A Player 播放器可以放歌，之前我只知道网易云这种音乐网站才有播放音乐的功能，但是居然个人网站也能自由地放歌吗？这也太酷了，虽然我忘记了所有页面上的信息，但是我知道我需要记住一句话——页脚的 “Powered By Typecho”，我知道如果记住了这个，我以后也能获得一个个人网站。于是拥有一个个人网站就成了我的一个小梦想，我知道我迟早会有一个的。</p>
<p>时间很快过去了，我已经逐渐忘掉了这件事情，直到疫情突然爆发，在家里去不了学校，于是名正言顺地可以在家里用电脑上网课，在此之前，我用电脑是绝对会被家长说玩物丧志。虽然事实上就是没有认真上网课，网课挂后台静音，自己想做什么就做什么，点名了就切回去答个到。直到玩手机玩到累了，突然想起来可不可以自己做一个网站，但是我已经忘掉了应该用什么了，想了很久之后虽然想起来了，但是当我真的要上手尝试的时候我发现：居然不能一直让网站在线，我没办法一直开机挂着 Frp。或者让 Typecho 在服务器上运行，虽然我想过要去买服务器，但是毕竟对于当时的我来说太贵了，而且我觉得网课结束之后我大概率没有精力来维护，所以就放弃了买一个服务器的想法。于是我就在网上搜索有什么办法可以不用服务器也能搭建一个博客，在学习了一些知识之后选择了看上去比较不错的 <a href="https://gridea.dev/" target="_blank" rel="noopener noreferrer">Gridea<span class="sr-only">（新标签页打开）</span></a>, 他使用的是 GitHub Pages, 我虽然听说 GitHub 已久了，但是还从来没有使用过，Git 什么的更是完全不知道，更别说什么 Markdown 了，完全不知道这个东西是什么，虽然这个软件已经不需要手写 HTML 了，但是我还是花了比较长的一段时间学习了 Markdown, 虽然最后我完全没有用 Markdown 写作。如果你想访问一下，部署的网站在 <a href="https://fei-xiangshi.github.io/" target="_blank" rel="noopener noreferrer">这里<span class="sr-only">（新标签页打开）</span></a>。</p>
<p>不得不说，Gridea 的审美是很在线的，或者说是我当初选择的模板非常好看，我在高中部署的网站我现在每次回去拜访，都觉得审美依旧没有过时。那个时候觉得 GitHub 真是大善人，就这样让我的网站跑了好久，虽然给其他同学说的时候，他们都不知道是什么意思。</p>
<h2 id="博客部署自由后的思考与选择">博客部署自由后的思考与选择</h2>
<p>上大学之后，有了自己的电脑，玩电脑更方便了，虽然拥有一个自己的网站已经实现了，但是当时觉得，GitHub Pages 的限制还是太大了，首先是不方便自定义域名，就算有自己的域名了，在部署的时候仍然不能部署到域名根目录，而是要在域名后添加 <code>/仓库名</code> 这样的路由，其次是不支持动态网站，刚接触计算机科学的我思考了很久动态网站和静态网站的区别，我当时觉得：必须要是动态网站，因为这样很高级，而且 Typecho 就是这样的。所以我放弃了继续使用 Gridea 的想法。我这个时候想用：WordPress。虽然当时就非常嫌弃 WordPress 还在用 PHP, 但是他是为数不多的动态网站博客，只有这一个选择。我怂恿室友和我一起买了一台服务器，说是迟早有用，可以练习 Linux 并且以后可能会上线什么产品，顺便还能搭个人网站（其实就是我想搭）。于是乎我终于能够自己搭建博客了，我为了让室友觉得和我买服务器不亏，我和他一起在上面部署了两个 WordPress, 用的 Apache, 当时为了让 Apache 支持两个域名访问两个 PHP 服务还费劲配置了一会，古法编程的时代，真是辛苦。</p>
<p>虽然搭建好了博客，但是效果却一般般，毕竟是 PHP 和 WordPress, 插件管理麻烦，SFTP 上传不了东西，没有邮件服务器等等等问题，甚至后来还被别人用漏洞打了一次，把数据库给删了，留下一串钱包地址，要求我们打 0.000… 几个比特币过去才能恢复，那自然是不管然后重装系统。</p>
<p>这件事情过后，虽然我还是对动态网站有一点执念，但是已经没有那么固执了，我坚信，不好用是 WordPress 的问题，我还是要再试一下 Typecho, 毕竟是白月光。这个时候服务器过期了，没办法，又开始寻找不需要服务器的方法，这个时候室友觉得买服务器就为了搭博客也太亏了，不和我一起买了，我只好仰仗着我在大学期间学到的知识再找一下有没有什么办法，于是还真让我找到了，有可以免费部署动态网站的大善人，虽然带宽很小，操作也比较麻烦，还不支持数据库，而 Typecho 得用数据库，还不支持 SQLite, 所以又找了一个免费数据库。免费动态网站托管服务感觉还是比较好，但是这个免费数据库就太差了，每2小时只能请求15次，刷新一下网站瞬间就没额度了，让网站成了完全不可用的状态，于是我打算再和同学合买一个服务器。这次买的美国的服务器，2c2g, 不好用，延迟大，IP 不纯净，延迟大，不好用。在这种情况下，我还是搭建好了完全自己控制的 Typecho, 并且也给他加上了 A Player, 到这个时候，再也没有执念了，真正拥有了一个个人网站。</p>
<h2 id="持久与永久">持久与永久</h2>
<p>服务器是需要租的，服务商是会跑路的，钱是偶尔没有的，数据是需要迁移的。我真的厌倦了。直到有一次服务器过期了我都懒得去迁移，部分文章就这样永久遗失了，这让我很长一段时间都失去了写博客的兴趣和热情。大概有一整年，我完全不想写博客，拥有个人网站什么的，早就实现过了，还运营着自己开发的用户数量超过 2w 的产品，已经非常满足自己的虚荣心了，虽然前前后后用 WordPress 和 Typecho 用了好几轮好多个主题了，什么 Hexo 和 React 一起生成网站也看过了，但是运营真的很疲惫。我思考着永恒的问题，毕竟作为一个 INTP, 我是很在意持续性的，没有一个方案能永久存在，并且不用过多维护，直到有一天我刷到了 XLOG, 一个号称永远在线的博客系统。我第一次深入了解 Web3 和区块链也是这个时候，我抱着怎么可能有永恒的网站的心态去了解，发现这确实是可以永久运行的，他使用 CrossBell 的服务器来 pin IPFS, 并且前端是开源的，也就是说，依托于 Web3 技术，可以实现理论上的永久存在，既然是理论存在，那么就是不存在——CrossBell 跑路了。</p>
<p>我是完全能理解的，矿工凭什么一直给不出钱的用户保存文件，只不过这个幻影实在是太美好了，美好得我都不愿意承认它其实是一个泡影，用 XLOG 作为博客的第二年，它跑路了。它的默认主题就已经非常符合我的审美，并且还自带 AI 翻译和良好的 SEO, 写作面板也非常好用，相关设施非常完善，完善到我觉得能请如此厉害的资深程序员开发网站的公司，不可能说跑就跑。但是它还是跑路了，Web3 的主旋律就是跑路，我如今也承认了这一点。我真的很喜欢 XLOG 和它的理念，可惜没有钱什么也做不了。</p>
<p>最近我又想写博客了，因为我总是觉得我不是很笨，我有些想法是真的值得记录的，我做的事，我的思考，我的经历，至少我自己会怀念，我的记忆非常零碎，记不住很多事情，写下来是帮助以后回忆的好办法，写完之后，就可以归档这一部分记忆了，为我本来就不多的内存腾开一些空间。这一次我选择 Astro, 毕竟是赛博活佛 Cloudflare 手下的技术栈，我相信 Cloudflare 至少能活到我下一次想换技术栈的时候，我甚至有一个幻想，它会比我活的还久，毕竟是互联网最大的基建了，这样也许我能一直轻维护，直到我死后还能持续一段时间，靠你了，Cloudflare Pages！</p>
<blockquote>
<p>年少不可得之物，必将困其一生。</p>
</blockquote>]]></content>
  <author>
    <name>Fei_xiangShi</name>
    <email>admin@fxs.life</email>
    <uri>https://blog.fxs.life/about/</uri>
  </author>
  <category term="笔梦生花" />
  <category term="记忆" />
  <category term="随笔" />
</entry>
</feed>
