<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dotfile 術語卡 on Tarragon</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/</link><description>Recent content in Dotfile 術語卡 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 29 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/index.xml" rel="self" type="application/rss+xml"/><item><title>Lua 腳本語言</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/lua-scripting-language/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/lua-scripting-language/</guid><description>&lt;p>Lua 是一個輕量級腳本語言，1993 年在巴西開發，名字是葡萄牙語的「月亮」。整個直譯器約 300KB，設計目標是&lt;strong>嵌入到其他程式當配置和擴展語言&lt;/strong>，不是當獨立的通用語言。&lt;/p>
&lt;p>Hyprland（v0.55+ 的配置格式）、Neovim（整個 plugin 和配置生態）、WezTerm（terminal emulator 配置）都用 Lua 作為配置語言。在 dotfile 管理的脈絡裡，Lua 是讀懂和寫好這些工具配置的前提知識。&lt;/p>
&lt;h2 id="配置檔用到的核心語法">配置檔用到的核心語法&lt;/h2>
&lt;h3 id="變數和型別">變數和型別&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hello&amp;#34;&lt;/span> &lt;span class="c1">-- 字串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">42&lt;/span> &lt;span class="c1">-- 數字&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="c1">-- 布林&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">nothing&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="c1">-- 空值（類似其他語言的 null）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>local&lt;/code> 宣告區域變數。沒有 &lt;code>local&lt;/code> 的變數是全域的，配置檔裡幾乎都該用 &lt;code>local&lt;/code>。&lt;/p>
&lt;h3 id="table唯一的複合資料結構">Table：唯一的複合資料結構&lt;/h3>
&lt;p>Lua 只有一種複合型別——table，同時當 array 和 dictionary 用：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 當 array（index 從 1 開始，不是 0）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">fruits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;apple&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;banana&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;cherry&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fruits&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">-- &amp;#34;apple&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 當 dictionary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">gaps_in&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">border_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">layout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dwindle&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config.gaps_in&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 巢狀 table（配置檔最常見的形式）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">decoration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">rounding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">blur&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">passes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Hyprland 的 &lt;code>hl.config()&lt;/code> 接收的就是一個巢狀 table：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">hl.config&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">general&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">gaps_in&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="n">gaps_out&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="n">decoration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="n">rounding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="function">Function&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="kr">function&lt;/span> &lt;span class="nf">greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">who&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="kr">return&lt;/span> &lt;span class="s2">&amp;#34;hello &amp;#34;&lt;/span> &lt;span class="o">..&lt;/span> &lt;span class="n">who&lt;/span> &lt;span class="c1">-- .. 是字串串接&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="kr">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 匿名 function（Neovim 配置常見）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">vim.keymap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;leader&amp;gt;f&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">function&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;telescope.builtin&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">find_files&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="kr">end&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="條件判斷">條件判斷&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kr">if&lt;/span> &lt;span class="n">hostname&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;work-laptop&amp;#34;&lt;/span> &lt;span class="kr">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 工作機設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="kr">elseif&lt;/span> &lt;span class="n">hostname&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;home-desktop&amp;#34;&lt;/span> &lt;span class="kr">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 家裡桌機設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="kr">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 預設&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="kr">end&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>只有 &lt;code>nil&lt;/code> 和 &lt;code>false&lt;/code> 是 falsy。&lt;code>0&lt;/code> 和 &lt;code>&amp;quot;&amp;quot;&lt;/code> 是 truthy（跟 Python 不同）。&lt;/p>
&lt;h3 id="迴圈">迴圈&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 數字 for（Hyprland 批次產生 workspace keybind）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kr">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">hl.bind&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;SUPER&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tostring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s2">&amp;#34;workspace&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tostring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kr">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 遍歷 table&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="kd">local&lt;/span> &lt;span class="n">tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;zsh&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;nvim&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;tmux&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kr">for&lt;/span> &lt;span class="n">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tool&lt;/span> &lt;span class="kr">in&lt;/span> &lt;span class="n">ipairs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tool&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="kr">end&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="模組化require">模組化（require）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- hyprland.lua 裡載入同目錄的其他 .lua 檔&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;keybinds&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 keybinds.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 rules.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;appearance&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 appearance.lua&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>require()&lt;/code> 是 Lua 原生的模組載入，取代了舊 Hyprland &lt;code>.conf&lt;/code> 格式的 &lt;code>source = ...&lt;/code> 指令。&lt;/p>
&lt;h2 id="為什麼配置工具選-lua">為什麼配置工具選 Lua&lt;/h2>
&lt;p>Lua 被嵌入到配置層的原因是一組特定的 trade-off：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>比 JSON/TOML/YAML 強&lt;/strong>：有變數、迴圈、條件判斷。配置檔可以用 &lt;code>for&lt;/code> 產生重複項目、用 &lt;code>if&lt;/code> 處理機器差異，不需要外部 template engine&lt;/li>
&lt;li>&lt;strong>比 Python/JavaScript 輕&lt;/strong>：300KB 的直譯器可以嵌入 C/C++ 程式，不需要拖一個完整的 runtime&lt;/li>
&lt;li>&lt;strong>沙盒化容易&lt;/strong>：宿主程式可以控制 Lua 能存取哪些 API，限制配置檔的能力範圍&lt;/li>
&lt;/ul>
&lt;p>這也是 Neovim 從 VimScript 遷移到 Lua 的理由——plugin 生態需要一個真正的程式語言（有資料結構、有錯誤處理），但又不能讓配置檔變成一個安全隱患。&lt;/p></description><content:encoded><![CDATA[<p>Lua 是一個輕量級腳本語言，1993 年在巴西開發，名字是葡萄牙語的「月亮」。整個直譯器約 300KB，設計目標是<strong>嵌入到其他程式當配置和擴展語言</strong>，不是當獨立的通用語言。</p>
<p>Hyprland（v0.55+ 的配置格式）、Neovim（整個 plugin 和配置生態）、WezTerm（terminal emulator 配置）都用 Lua 作為配置語言。在 dotfile 管理的脈絡裡，Lua 是讀懂和寫好這些工具配置的前提知識。</p>
<h2 id="配置檔用到的核心語法">配置檔用到的核心語法</h2>
<h3 id="變數和型別">變數和型別</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">local</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;hello&#34;</span>       <span class="c1">-- 字串</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">local</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">42</span>            <span class="c1">-- 數字</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">local</span> <span class="n">enabled</span> <span class="o">=</span> <span class="kc">true</span>        <span class="c1">-- 布林</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">local</span> <span class="n">nothing</span> <span class="o">=</span> <span class="kc">nil</span>         <span class="c1">-- 空值（類似其他語言的 null）</span></span></span></code></pre></div><p><code>local</code> 宣告區域變數。沒有 <code>local</code> 的變數是全域的，配置檔裡幾乎都該用 <code>local</code>。</p>
<h3 id="table唯一的複合資料結構">Table：唯一的複合資料結構</h3>
<p>Lua 只有一種複合型別——table，同時當 array 和 dictionary 用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 當 array（index 從 1 開始，不是 0）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">local</span> <span class="n">fruits</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;apple&#34;</span><span class="p">,</span> <span class="s2">&#34;banana&#34;</span><span class="p">,</span> <span class="s2">&#34;cherry&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">print</span><span class="p">(</span><span class="n">fruits</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>  <span class="c1">-- &#34;apple&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">-- 當 dictionary</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">local</span> <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">gaps_in</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">border_size</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">layout</span> <span class="o">=</span> <span class="s2">&#34;dwindle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">print</span><span class="p">(</span><span class="n">config.gaps_in</span><span class="p">)</span>  <span class="c1">-- 5</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">-- 巢狀 table（配置檔最常見的形式）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kd">local</span> <span class="n">decoration</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">rounding</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">blur</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">enabled</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">size</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">passes</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Hyprland 的 <code>hl.config()</code> 接收的就是一個巢狀 table：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">general</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">gaps_in</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">gaps_out</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">decoration</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">rounding</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><h3 id="function">Function</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">greet</span><span class="p">(</span><span class="n">who</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="kr">return</span> <span class="s2">&#34;hello &#34;</span> <span class="o">..</span> <span class="n">who</span>   <span class="c1">-- .. 是字串串接</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">-- 匿名 function（Neovim 配置常見）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;f&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;telescope.builtin&#34;</span><span class="p">).</span><span class="n">find_files</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kr">end</span><span class="p">)</span></span></span></code></pre></div><h3 id="條件判斷">條件判斷</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">if</span> <span class="n">hostname</span> <span class="o">==</span> <span class="s2">&#34;work-laptop&#34;</span> <span class="kr">then</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1">-- 工作機設定</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">elseif</span> <span class="n">hostname</span> <span class="o">==</span> <span class="s2">&#34;home-desktop&#34;</span> <span class="kr">then</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1">-- 家裡桌機設定</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kr">else</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1">-- 預設</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="kr">end</span></span></span></code></pre></div><p>只有 <code>nil</code> 和 <code>false</code> 是 falsy。<code>0</code> 和 <code>&quot;&quot;</code> 是 truthy（跟 Python 不同）。</p>
<h3 id="迴圈">迴圈</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 數字 for（Hyprland 批次產生 workspace keybind）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">for</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">9</span> <span class="kr">do</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="n">tostring</span><span class="p">(</span><span class="n">i</span><span class="p">),</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="n">tostring</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">-- 遍歷 table</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">local</span> <span class="n">tools</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;zsh&#34;</span><span class="p">,</span> <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="s2">&#34;nvim&#34;</span><span class="p">,</span> <span class="s2">&#34;tmux&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kr">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">tool</span> <span class="kr">in</span> <span class="n">ipairs</span><span class="p">(</span><span class="n">tools</span><span class="p">)</span> <span class="kr">do</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">print</span><span class="p">(</span><span class="n">tool</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kr">end</span></span></span></code></pre></div><h3 id="模組化require">模組化（require）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- hyprland.lua 裡載入同目錄的其他 .lua 檔</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;keybinds&#34;</span><span class="p">)</span>     <span class="c1">-- 載入 keybinds.lua</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;rules&#34;</span><span class="p">)</span>        <span class="c1">-- 載入 rules.lua</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;appearance&#34;</span><span class="p">)</span>   <span class="c1">-- 載入 appearance.lua</span></span></span></code></pre></div><p><code>require()</code> 是 Lua 原生的模組載入，取代了舊 Hyprland <code>.conf</code> 格式的 <code>source = ...</code> 指令。</p>
<h2 id="為什麼配置工具選-lua">為什麼配置工具選 Lua</h2>
<p>Lua 被嵌入到配置層的原因是一組特定的 trade-off：</p>
<ul>
<li><strong>比 JSON/TOML/YAML 強</strong>：有變數、迴圈、條件判斷。配置檔可以用 <code>for</code> 產生重複項目、用 <code>if</code> 處理機器差異，不需要外部 template engine</li>
<li><strong>比 Python/JavaScript 輕</strong>：300KB 的直譯器可以嵌入 C/C++ 程式，不需要拖一個完整的 runtime</li>
<li><strong>沙盒化容易</strong>：宿主程式可以控制 Lua 能存取哪些 API，限制配置檔的能力範圍</li>
</ul>
<p>這也是 Neovim 從 VimScript 遷移到 Lua 的理由——plugin 生態需要一個真正的程式語言（有資料結構、有錯誤處理），但又不能讓配置檔變成一個安全隱患。</p>
<h2 id="其他使用-lua-的場景">其他使用 Lua 的場景</h2>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>用法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Neovim</td>
          <td>整個配置和 plugin 生態基於 Lua</td>
      </tr>
      <tr>
          <td>WezTerm</td>
          <td>terminal emulator 配置（<code>wezterm.lua</code>）</td>
      </tr>
      <tr>
          <td>Awesome WM</td>
          <td>X11 tiling WM 的配置和擴展</td>
      </tr>
      <tr>
          <td>Redis</td>
          <td><code>EVAL</code> 指令在 server 端執行 Lua script</td>
      </tr>
      <tr>
          <td>Nginx/OpenResty</td>
          <td>用 Lua 寫高效能的 request 處理邏輯</td>
      </tr>
      <tr>
          <td>遊戲</td>
          <td>World of Warcraft UI mod、Roblox、很多遊戲引擎的腳本層</td>
      </tr>
  </tbody>
</table>
<p>共同模式：一個用 C/C++ 寫的高效能核心，把 Lua 嵌入進去當配置和擴展語言。</p>
<h2 id="跟-pythonjavascript-的差異速查">跟 Python/JavaScript 的差異速查</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>Lua</th>
          <th>Python</th>
          <th>JavaScript</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Array index 起始</td>
          <td><strong>1</strong></td>
          <td>0</td>
          <td>0</td>
      </tr>
      <tr>
          <td>字串串接</td>
          <td><code>..</code></td>
          <td><code>+</code></td>
          <td><code>+</code></td>
      </tr>
      <tr>
          <td>不等於</td>
          <td><code>~=</code></td>
          <td><code>!=</code></td>
          <td><code>!==</code></td>
      </tr>
      <tr>
          <td>邏輯運算</td>
          <td><code>and</code> <code>or</code> <code>not</code></td>
          <td><code>and</code> <code>or</code> <code>not</code></td>
          <td><code>&amp;&amp;</code> <code>||</code> <code>!</code></td>
      </tr>
      <tr>
          <td>空值</td>
          <td><code>nil</code></td>
          <td><code>None</code></td>
          <td><code>null</code>/<code>undefined</code></td>
      </tr>
      <tr>
          <td>Falsy 值</td>
          <td><code>nil</code>, <code>false</code></td>
          <td><code>None</code>, <code>False</code>, <code>0</code>, <code>&quot;&quot;</code>, <code>[]</code></td>
          <td><code>null</code>, <code>undefined</code>, <code>false</code>, <code>0</code>, <code>&quot;&quot;</code></td>
      </tr>
      <tr>
          <td>沒有 <code>+=</code></td>
          <td><code>x = x + 1</code></td>
          <td><code>x += 1</code></td>
          <td><code>x += 1</code></td>
      </tr>
      <tr>
          <td>註解</td>
          <td><code>--</code></td>
          <td><code>#</code></td>
          <td><code>//</code></td>
      </tr>
      <tr>
          <td>多行註解</td>
          <td><code>--[[ ... ]]</code></td>
          <td><code>&quot;&quot;&quot; ... &quot;&quot;&quot;</code></td>
          <td><code>/* ... */</code></td>
      </tr>
  </tbody>
</table>
<p>寫 Hyprland 或 Neovim 配置用到的 Lua 知識量很小——主要是 table（配置結構）、for loop（批次 keybind）、if-else（機器差異）、require（模組拆分）。不需要學 metatable、coroutine、metatmethod 這些進階功能。</p>
]]></content:encoded></item><item><title>GNU Stow</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/</guid><description>&lt;p>GNU Stow 是一個 symlink farm manager，原本設計給軟體安裝用（把 &lt;code>/usr/local/stow/program/&lt;/code> 下的檔案 symlink 到 &lt;code>/usr/local/&lt;/code>），在 dotfile 管理場景被借來做「把 repo 裡的配置檔 symlink 到家目錄」。&lt;/p>
&lt;h2 id="核心規則">核心規則&lt;/h2>
&lt;p>Stow 的核心規則只有一條：&lt;strong>package 目錄內的路徑結構，就是安裝後相對於目標目錄的路徑結構&lt;/strong>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">~/dotfiles/zsh/.zshrc → ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">~/dotfiles/nvim/.config/nvim/ → ~/.config/nvim/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">~/dotfiles/git/.gitconfig → ~/.gitconfig&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個頂層目錄（&lt;code>zsh/&lt;/code>、&lt;code>nvim/&lt;/code>、&lt;code>git/&lt;/code>）是一個 stow package，可以獨立安裝或移除。&lt;/p>
&lt;h2 id="常用指令">常用指令&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 安裝：在目標目錄（預設 $HOME 的上一層，通常用 --target）建立 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">stow zsh &lt;span class="c1"># 安裝 zsh package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">stow zsh git nvim tmux &lt;span class="c1"># 批次安裝多個 package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">stow */ &lt;span class="c1"># 安裝所有 package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 移除：刪除該 package 建立的 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">stow -D nvim
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 重新安裝（移除 + 安裝）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">stow -R zsh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 收養：如果目標位置已有檔案，--adopt 把它移進 repo 再建 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">stow --adopt zsh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>--adopt&lt;/code> 是首次把現有配置納入 dotfile 管理時的關鍵操作——它把家目錄的既有檔案「收養」進 repo（移動過去），然後建 symlink。之後 &lt;code>git diff&lt;/code> 就能看到 repo 版本跟原版的差異。&lt;/p>
&lt;h2 id="folding-與-unfolding">Folding 與 Unfolding&lt;/h2>
&lt;p>Stow 會自動判斷要 symlink 整個目錄還是逐一 symlink 檔案：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Folding&lt;/strong>：目標目錄不存在、或目錄內所有檔案都由同一個 package 管理 → symlink 整個目錄&lt;/li>
&lt;li>&lt;strong>Unfolding&lt;/strong>：目標目錄已有其他來源的檔案 → 展開成逐檔 symlink，保留既有檔案不受影響&lt;/li>
&lt;/ul>
&lt;p>這個機制讓多個 package 可以共存於同一個目標目錄（如 &lt;code>~/.config/&lt;/code>）。&lt;/p>
&lt;h2 id="限制">限制&lt;/h2>
&lt;ul>
&lt;li>只管 symlink 映射，不管套件安裝（套件由 Brewfile 或 packages.txt 處理）&lt;/li>
&lt;li>不管 file permission——需要 0600 的 secret 檔靠 symlink 繼承來源權限，無法在部署時自動 chmod&lt;/li>
&lt;li>沒有 template 機制——同一份配置在不同機器要不同內容時，需要在配置檔內用 shell 的 OS 判斷處理&lt;/li>
&lt;/ul>
&lt;p>完整選型比較見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>GNU Stow 是一個 symlink farm manager，原本設計給軟體安裝用（把 <code>/usr/local/stow/program/</code> 下的檔案 symlink 到 <code>/usr/local/</code>），在 dotfile 管理場景被借來做「把 repo 裡的配置檔 symlink 到家目錄」。</p>
<h2 id="核心規則">核心規則</h2>
<p>Stow 的核心規則只有一條：<strong>package 目錄內的路徑結構，就是安裝後相對於目標目錄的路徑結構</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">~/dotfiles/zsh/.zshrc          → ~/.zshrc
</span></span><span class="line"><span class="ln">2</span><span class="cl">~/dotfiles/nvim/.config/nvim/  → ~/.config/nvim/
</span></span><span class="line"><span class="ln">3</span><span class="cl">~/dotfiles/git/.gitconfig      → ~/.gitconfig</span></span></code></pre></div><p>每個頂層目錄（<code>zsh/</code>、<code>nvim/</code>、<code>git/</code>）是一個 stow package，可以獨立安裝或移除。</p>
<h2 id="常用指令">常用指令</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nb">cd</span> ~/dotfiles
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 安裝：在目標目錄（預設 $HOME 的上一層，通常用 --target）建立 symlink</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">stow zsh                <span class="c1"># 安裝 zsh package</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">stow zsh git nvim tmux  <span class="c1"># 批次安裝多個 package</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">stow */                 <span class="c1"># 安裝所有 package</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 移除：刪除該 package 建立的 symlink</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">stow -D nvim
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 重新安裝（移除 + 安裝）</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">stow -R zsh
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 收養：如果目標位置已有檔案，--adopt 把它移進 repo 再建 symlink</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">stow --adopt zsh</span></span></code></pre></div><p><code>--adopt</code> 是首次把現有配置納入 dotfile 管理時的關鍵操作——它把家目錄的既有檔案「收養」進 repo（移動過去），然後建 symlink。之後 <code>git diff</code> 就能看到 repo 版本跟原版的差異。</p>
<h2 id="folding-與-unfolding">Folding 與 Unfolding</h2>
<p>Stow 會自動判斷要 symlink 整個目錄還是逐一 symlink 檔案：</p>
<ul>
<li><strong>Folding</strong>：目標目錄不存在、或目錄內所有檔案都由同一個 package 管理 → symlink 整個目錄</li>
<li><strong>Unfolding</strong>：目標目錄已有其他來源的檔案 → 展開成逐檔 symlink，保留既有檔案不受影響</li>
</ul>
<p>這個機制讓多個 package 可以共存於同一個目標目錄（如 <code>~/.config/</code>）。</p>
<h2 id="限制">限制</h2>
<ul>
<li>只管 symlink 映射，不管套件安裝（套件由 Brewfile 或 packages.txt 處理）</li>
<li>不管 file permission——需要 0600 的 secret 檔靠 symlink 繼承來源權限，無法在部署時自動 chmod</li>
<li>沒有 template 機制——同一份配置在不同機器要不同內容時，需要在配置檔內用 shell 的 OS 判斷處理</li>
</ul>
<p>完整選型比較見<a href="/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型</a>。</p>
]]></content:encoded></item><item><title>Rice（桌面視覺客製化）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/</guid><description>&lt;p>Rice 在 Linux 桌面社群指的是&lt;strong>桌面視覺客製化&lt;/strong>——把系統外觀調教成個人化的美學呈現。動詞 ricing 是「正在美化桌面」，名詞 a rice 是「一套美化成果/配置」，做這件事的人叫 ricer。&lt;/p>
&lt;h2 id="詞源">詞源&lt;/h2>
&lt;p>最被廣泛接受的說法是源自汽車改裝文化的 &amp;ldquo;rice burner&amp;rdquo; / &amp;ldquo;ricer&amp;rdquo;——原本指對（通常是日系的）平價車裝上浮誇外觀套件（大尾翼、炫光、貼紙），看起來拉風但實際性能沒提升。後來 Linux 社群借用這個概念：把桌面打扮得花俏好看，本質也是「外觀的炫技」。&lt;/p>
&lt;p>也有人提出 &amp;ldquo;Race Inspired Cosmetic Enhancements&amp;rdquo; 的逆向縮寫，但普遍被認為是事後湊的解釋。&lt;/p>
&lt;p>在 Linux 圈裡，rice 的原始貶意和種族色彩已經淡化，變成中性甚至帶自豪的自稱——r/unixporn 社群就是圍繞 ricing 成果的分享運轉的。&lt;/p>
&lt;h2 id="rice-涵蓋的範圍">Rice 涵蓋的範圍&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>配色方案&lt;/strong>：Catppuccin、Tokyo Night、Gruvbox、Nord 等跨工具統一的色彩定義&lt;/li>
&lt;li>&lt;strong>狀態列&lt;/strong>：Waybar、Eww 的模組設計和 CSS 外觀&lt;/li>
&lt;li>&lt;strong>啟動器&lt;/strong>：Wofi、Rofi 的搜尋框外觀&lt;/li>
&lt;li>&lt;strong>通知&lt;/strong>：Mako、Dunst 的通知氣泡樣式&lt;/li>
&lt;li>&lt;strong>鎖屏&lt;/strong>：Hyprlock 的登入畫面設計&lt;/li>
&lt;li>&lt;strong>桌布&lt;/strong>：靜態桌布或動態桌布（Swww）&lt;/li>
&lt;li>&lt;strong>終端機配色&lt;/strong>：Alacritty / Kitty / Foot 的 ANSI 色碼&lt;/li>
&lt;li>&lt;strong>字型&lt;/strong>：Nerd Font 的 icon glyph&lt;/li>
&lt;/ul>
&lt;p>Caelestia 這類「desktop shell」專案把上述元件統一設計出貨，是「打包好的 rice」。手動逐一挑選和調教各元件是「DIY rice」。兩者的目標相同——視覺上協調、好看、符合個人美學。&lt;/p>
&lt;p>完整的 rice 配置實務見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">桌面 Rice 設計&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Rice 在 Linux 桌面社群指的是<strong>桌面視覺客製化</strong>——把系統外觀調教成個人化的美學呈現。動詞 ricing 是「正在美化桌面」，名詞 a rice 是「一套美化成果/配置」，做這件事的人叫 ricer。</p>
<h2 id="詞源">詞源</h2>
<p>最被廣泛接受的說法是源自汽車改裝文化的 &ldquo;rice burner&rdquo; / &ldquo;ricer&rdquo;——原本指對（通常是日系的）平價車裝上浮誇外觀套件（大尾翼、炫光、貼紙），看起來拉風但實際性能沒提升。後來 Linux 社群借用這個概念：把桌面打扮得花俏好看，本質也是「外觀的炫技」。</p>
<p>也有人提出 &ldquo;Race Inspired Cosmetic Enhancements&rdquo; 的逆向縮寫，但普遍被認為是事後湊的解釋。</p>
<p>在 Linux 圈裡，rice 的原始貶意和種族色彩已經淡化，變成中性甚至帶自豪的自稱——r/unixporn 社群就是圍繞 ricing 成果的分享運轉的。</p>
<h2 id="rice-涵蓋的範圍">Rice 涵蓋的範圍</h2>
<ul>
<li><strong>配色方案</strong>：Catppuccin、Tokyo Night、Gruvbox、Nord 等跨工具統一的色彩定義</li>
<li><strong>狀態列</strong>：Waybar、Eww 的模組設計和 CSS 外觀</li>
<li><strong>啟動器</strong>：Wofi、Rofi 的搜尋框外觀</li>
<li><strong>通知</strong>：Mako、Dunst 的通知氣泡樣式</li>
<li><strong>鎖屏</strong>：Hyprlock 的登入畫面設計</li>
<li><strong>桌布</strong>：靜態桌布或動態桌布（Swww）</li>
<li><strong>終端機配色</strong>：Alacritty / Kitty / Foot 的 ANSI 色碼</li>
<li><strong>字型</strong>：Nerd Font 的 icon glyph</li>
</ul>
<p>Caelestia 這類「desktop shell」專案把上述元件統一設計出貨，是「打包好的 rice」。手動逐一挑選和調教各元件是「DIY rice」。兩者的目標相同——視覺上協調、好看、符合個人美學。</p>
<p>完整的 rice 配置實務見<a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">桌面 Rice 設計</a>。</p>
]]></content:encoded></item><item><title>Compositor（合成器）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/</guid><description>&lt;p>Compositor（合成器）是 Wayland 下負責把各個應用視窗的畫面合成到螢幕、同時管理視窗位置與輸入的核心程式。它一個角色承擔了舊 X11 世界裡分給多個程式的責任——畫面合成、視窗管理、輸入處理，在 Wayland 架構裡合在同一個程式。&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland&lt;/a> 就是一個 Wayland compositor。&lt;/p>
&lt;p>跟 X11 的分工對照能看清它的定位。X11 時代，X server 負責畫面、一個獨立的 window manager 負責視窗排列，兩者透過協定溝通；Wayland 取消這個分家，compositor 直接兼任兩者。所以在 Wayland 語境裡，「compositor」和「window manager」常指同一個東西——Hyprland 既是 compositor 也是 tiling window manager。&lt;/p>
&lt;p>它跟桌面環境（desktop environment）是不同層次。桌面環境（GNOME、KDE）是一整套元件（面板、設定、通知、檔案管理），其中內含一個 compositor；而 Hyprland 這類「只有 compositor」的方案不含那圈桌面服務，面板、啟動器、鎖屏都要另外自己接。這條「整合度 vs 自己組裝」的軸線，是&lt;a href="https://tarrragon.github.io/blog/linux/tools/gui/desktop-environment-selection/" data-link-title="桌面環境選型：整合度與組裝自由度的取捨" data-link-desc="從 Windows/macOS 轉來或要挑第一個 Linux 桌面、在 GNOME / KDE / Hyprland / XFCE / Cinnamon 之間拿不定、想知道各自的定位與代價（資源、客製自由、穩定性、Wayland 支援）時回來讀">桌面環境選型&lt;/a>的主題。&lt;/p>
&lt;p>compositor 在故障排除中是一個關鍵的責任邊界，因為多個系統狀態由它持有、而非別的層：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>它握著 DRM master&lt;/strong>：直接畫到顯示裝置的獨佔權由 compositor 持有，這是為什麼 compositor 必須從實體圖形 VT 起、不能從 SSH pty 起（SSH 連線裡沒有那個 seat 與 DRM 資源），也是為什麼它跟同樣要畫到 DRM 的 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">kmscon 之類 userspace console&lt;/a> 會相衝。&lt;/li>
&lt;li>&lt;strong>它持有 session lock 狀態&lt;/strong>：Wayland 的 &lt;code>ext-session-lock&lt;/code> 鎖是 compositor 層的狀態、跟 logind 獨立，殺掉鎖屏程式 compositor 仍保持鎖定——詳見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>。&lt;/li>
&lt;li>&lt;strong>它掛了、桌面才真的黑&lt;/strong>：反過來說，只要 kernel 還活著、compositor 以外的東西壞了，&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY&lt;/a> 仍能登入操作。判斷「畫面黑」是 compositor 掛了、還是沒 getty、還是顯示輸出沒接，是桌面故障排除的第一個分岔。&lt;/li>
&lt;/ul>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY&lt;/a>（不依賴 compositor 的救生通道）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>（compositor 持有的鎖定狀態）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice&lt;/a>（在 compositor 上做視覺客製）。&lt;/p></description><content:encoded><![CDATA[<p>Compositor（合成器）是 Wayland 下負責把各個應用視窗的畫面合成到螢幕、同時管理視窗位置與輸入的核心程式。它一個角色承擔了舊 X11 世界裡分給多個程式的責任——畫面合成、視窗管理、輸入處理，在 Wayland 架構裡合在同一個程式。<a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland</a> 就是一個 Wayland compositor。</p>
<p>跟 X11 的分工對照能看清它的定位。X11 時代，X server 負責畫面、一個獨立的 window manager 負責視窗排列，兩者透過協定溝通；Wayland 取消這個分家，compositor 直接兼任兩者。所以在 Wayland 語境裡，「compositor」和「window manager」常指同一個東西——Hyprland 既是 compositor 也是 tiling window manager。</p>
<p>它跟桌面環境（desktop environment）是不同層次。桌面環境（GNOME、KDE）是一整套元件（面板、設定、通知、檔案管理），其中內含一個 compositor；而 Hyprland 這類「只有 compositor」的方案不含那圈桌面服務，面板、啟動器、鎖屏都要另外自己接。這條「整合度 vs 自己組裝」的軸線，是<a href="/blog/linux/tools/gui/desktop-environment-selection/" data-link-title="桌面環境選型：整合度與組裝自由度的取捨" data-link-desc="從 Windows/macOS 轉來或要挑第一個 Linux 桌面、在 GNOME / KDE / Hyprland / XFCE / Cinnamon 之間拿不定、想知道各自的定位與代價（資源、客製自由、穩定性、Wayland 支援）時回來讀">桌面環境選型</a>的主題。</p>
<p>compositor 在故障排除中是一個關鍵的責任邊界，因為多個系統狀態由它持有、而非別的層：</p>
<ul>
<li><strong>它握著 DRM master</strong>：直接畫到顯示裝置的獨佔權由 compositor 持有，這是為什麼 compositor 必須從實體圖形 VT 起、不能從 SSH pty 起（SSH 連線裡沒有那個 seat 與 DRM 資源），也是為什麼它跟同樣要畫到 DRM 的 <a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">kmscon 之類 userspace console</a> 會相衝。</li>
<li><strong>它持有 session lock 狀態</strong>：Wayland 的 <code>ext-session-lock</code> 鎖是 compositor 層的狀態、跟 logind 獨立，殺掉鎖屏程式 compositor 仍保持鎖定——詳見 <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>。</li>
<li><strong>它掛了、桌面才真的黑</strong>：反過來說，只要 kernel 還活著、compositor 以外的東西壞了，<a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY</a> 仍能登入操作。判斷「畫面黑」是 compositor 掛了、還是沒 getty、還是顯示輸出沒接，是桌面故障排除的第一個分岔。</li>
</ul>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY</a>（不依賴 compositor 的救生通道）、<a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>（compositor 持有的鎖定狀態）、<a href="/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice</a>（在 compositor 上做視覺客製）。</p>
]]></content:encoded></item><item><title>TTY</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/</guid><description>&lt;p>TTY（TeleTYpewriter）是 Linux 核心直接提供的純文字終端機介面，獨立於任何桌面環境或圖形介面。&lt;/p>
&lt;p>名稱來自早期電腦透過電報打字機（teletypewriter）做輸入輸出的歷史。現代 Linux 的 TTY 是 virtual console——核心在記憶體中模擬的文字終端機，不需要實體硬體。&lt;/p>
&lt;p>systemd 預設配置下有 6 個 virtual console（TTY1-TTY6）。Wayland compositor（如 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland&lt;/a>）通常佔用 TTY1 顯示圖形桌面，TTY2-TTY6 保持為純文字介面可用。&lt;/p>
&lt;p>切換方式：&lt;code>Ctrl+Alt+F2&lt;/code>（切到 TTY2）到 &lt;code>Ctrl+Alt+F6&lt;/code>（切到 TTY6）。&lt;code>Ctrl+Alt+F1&lt;/code> 切回圖形桌面（TTY1）。&lt;/p>
&lt;p>TTY 在桌面故障排除中的價值在於它&lt;strong>不依賴 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">compositor&lt;/a>&lt;/strong>（Wayland 下負責畫面與視窗的核心程式）。Compositor 掛了、GPU driver 出問題導致畫面凍結——只要 kernel 還活著，TTY 就能登入操作。這是 Linux 桌面環境「掛了不等於崩潰」的關鍵機制，詳見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">桌面環境維護與故障排除&lt;/a>。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice&lt;/a>（桌面客製化）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/" data-link-title="GNU Stow" data-link-desc="dotfile 管理文章裡提到 stow、symlink、package 看不懂時回來讀 — stow 的核心概念和常用指令">GNU Stow&lt;/a>（dotfile 管理）。&lt;/p></description><content:encoded><![CDATA[<p>TTY（TeleTYpewriter）是 Linux 核心直接提供的純文字終端機介面，獨立於任何桌面環境或圖形介面。</p>
<p>名稱來自早期電腦透過電報打字機（teletypewriter）做輸入輸出的歷史。現代 Linux 的 TTY 是 virtual console——核心在記憶體中模擬的文字終端機，不需要實體硬體。</p>
<p>systemd 預設配置下有 6 個 virtual console（TTY1-TTY6）。Wayland compositor（如 <a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland</a>）通常佔用 TTY1 顯示圖形桌面，TTY2-TTY6 保持為純文字介面可用。</p>
<p>切換方式：<code>Ctrl+Alt+F2</code>（切到 TTY2）到 <code>Ctrl+Alt+F6</code>（切到 TTY6）。<code>Ctrl+Alt+F1</code> 切回圖形桌面（TTY1）。</p>
<p>TTY 在桌面故障排除中的價值在於它<strong>不依賴 <a href="/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">compositor</a></strong>（Wayland 下負責畫面與視窗的核心程式）。Compositor 掛了、GPU driver 出問題導致畫面凍結——只要 kernel 還活著，TTY 就能登入操作。這是 Linux 桌面環境「掛了不等於崩潰」的關鍵機制，詳見<a href="/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">桌面環境維護與故障排除</a>。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice</a>（桌面客製化）、<a href="/blog/linux/dotfile/knowledge-cards/gnu-stow/" data-link-title="GNU Stow" data-link-desc="dotfile 管理文章裡提到 stow、symlink、package 看不懂時回來讀 — stow 的核心概念和常用指令">GNU Stow</a>（dotfile 管理）。</p>
]]></content:encoded></item><item><title>initramfs</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/</guid><description>&lt;p>initramfs（initial RAM filesystem）是 kernel 開機初期、在真正的 root 檔案系統被掛起來之前，載入記憶體的一個小型臨時根檔系統。&lt;/p>
&lt;p>它的責任是「把掛載真 root 所需的東西先備齊」。kernel 本身不內建所有硬體與檔案系統的驅動，當 root 位在一個需要額外驅動才讀得到的裝置上——LVM 邏輯卷、LUKS 加密卷、特殊磁碟控制器——kernel 沒辦法直接掛它。initramfs 提供一個臨時環境，把這些驅動與工具載進來、把真 root 掛起來，然後把控制權交給真 root 上的 init。&lt;/p>
&lt;p>因為它要跟 kernel 一起被 bootloader 載入，所以它跟 kernel 放在同一個地方——&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>裡的 ESP，或獨立的 &lt;code>/boot&lt;/code>。這也是為什麼估 ESP 大小時要把它算進去：一個 kernel 加上它的 initramfs（含 fallback 版本）大約一兩百 MB。&lt;/p>
&lt;p>生成工具依發行版而異：Arch 用 &lt;code>mkinitcpio&lt;/code>、Fedora 用 &lt;code>dracut&lt;/code>。換了 kernel 或改了開機需要的驅動，要重新生成 initramfs，否則新 kernel 可能掛不起 root。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別&lt;/a>。安裝時 ESP 大小怎麼估，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>initramfs（initial RAM filesystem）是 kernel 開機初期、在真正的 root 檔案系統被掛起來之前，載入記憶體的一個小型臨時根檔系統。</p>
<p>它的責任是「把掛載真 root 所需的東西先備齊」。kernel 本身不內建所有硬體與檔案系統的驅動，當 root 位在一個需要額外驅動才讀得到的裝置上——LVM 邏輯卷、LUKS 加密卷、特殊磁碟控制器——kernel 沒辦法直接掛它。initramfs 提供一個臨時環境，把這些驅動與工具載進來、把真 root 掛起來，然後把控制權交給真 root 上的 init。</p>
<p>因為它要跟 kernel 一起被 bootloader 載入，所以它跟 kernel 放在同一個地方——<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>裡的 ESP，或獨立的 <code>/boot</code>。這也是為什麼估 ESP 大小時要把它算進去：一個 kernel 加上它的 initramfs（含 fallback 版本）大約一兩百 MB。</p>
<p>生成工具依發行版而異：Arch 用 <code>mkinitcpio</code>、Fedora 用 <code>dracut</code>。換了 kernel 或改了開機需要的驅動，要重新生成 initramfs，否則新 kernel 可能掛不起 root。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>、<a href="/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別</a>。安裝時 ESP 大小怎麼估，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>UEFI 開機鏈</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/</guid><description>&lt;p>UEFI 開機鏈是現代機器從通電到 kernel 跑起來的一段交棒過程：韌體 → 開機項 → bootloader → kernel。理解這條鏈，bootloader 選型與「重開機找不到系統」的故障才有判讀依據。&lt;/p>
&lt;p>UEFI 韌體開機時，要找到一個 EFI 執行檔來載入。它從兩個來源找：NVRAM（韌體用來存開機項的非揮發記憶體）裡登記的開機項，或 ESP（EFI System Partition，一個 FAT32 格式的分區）裡的標準路徑。找到的 EFI 執行檔可能是一個獨立的 bootloader，也可能直接是 kernel。&lt;/p>
&lt;p>這對應兩種風格。EFISTUB 讓韌體直接載入 kernel、不經過獨立 bootloader，最精簡，但典型上依賴 NVRAM 裡的開機項。獨立 bootloader（GRUB、systemd-boot）則多一層：它有開機選單、能救援、還能裝到 ESP 的 fallback 路徑（&lt;code>\EFI\BOOT\BOOT&amp;lt;ARCH&amp;gt;.EFI&lt;/code>，aarch64 是 &lt;code>BOOTAA64.EFI&lt;/code>、x86_64 是 &lt;code>BOOTX64.EFI&lt;/code>）。&lt;/p>
&lt;p>fallback 路徑是這條鏈的保命機制。NVRAM 的開機項可能丟失——QEMU 系的虛擬機尤其容易——這時靠 NVRAM 開機項的 EFISTUB 會開不了機，而 fallback 路徑上有 bootloader 的機器，韌體仍找得到。這就是「VM 上偏好獨立 bootloader」的根據。&lt;/p>
&lt;p>這條鏈預設 Secure Boot 關閉。Secure Boot 開啟時，韌體會拒載沒簽章的 EFI 執行檔（kernel 或 bootloader），這也是「重開後找不到 kernel」的一類成因——最小 VM 安裝通常把它關掉，但實體機若開著就要處理簽章。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs&lt;/a>（bootloader 載入的對象之一）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別&lt;/a>。bootloader 選型的判讀，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>UEFI 開機鏈是現代機器從通電到 kernel 跑起來的一段交棒過程：韌體 → 開機項 → bootloader → kernel。理解這條鏈，bootloader 選型與「重開機找不到系統」的故障才有判讀依據。</p>
<p>UEFI 韌體開機時，要找到一個 EFI 執行檔來載入。它從兩個來源找：NVRAM（韌體用來存開機項的非揮發記憶體）裡登記的開機項，或 ESP（EFI System Partition，一個 FAT32 格式的分區）裡的標準路徑。找到的 EFI 執行檔可能是一個獨立的 bootloader，也可能直接是 kernel。</p>
<p>這對應兩種風格。EFISTUB 讓韌體直接載入 kernel、不經過獨立 bootloader，最精簡，但典型上依賴 NVRAM 裡的開機項。獨立 bootloader（GRUB、systemd-boot）則多一層：它有開機選單、能救援、還能裝到 ESP 的 fallback 路徑（<code>\EFI\BOOT\BOOT&lt;ARCH&gt;.EFI</code>，aarch64 是 <code>BOOTAA64.EFI</code>、x86_64 是 <code>BOOTX64.EFI</code>）。</p>
<p>fallback 路徑是這條鏈的保命機制。NVRAM 的開機項可能丟失——QEMU 系的虛擬機尤其容易——這時靠 NVRAM 開機項的 EFISTUB 會開不了機，而 fallback 路徑上有 bootloader 的機器，韌體仍找得到。這就是「VM 上偏好獨立 bootloader」的根據。</p>
<p>這條鏈預設 Secure Boot 關閉。Secure Boot 開啟時，韌體會拒載沒簽章的 EFI 執行檔（kernel 或 bootloader），這也是「重開後找不到 kernel」的一類成因——最小 VM 安裝通常把它關掉，但實體機若開著就要處理簽章。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs</a>（bootloader 載入的對象之一）、<a href="/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別</a>。bootloader 選型的判讀，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>分區識別（PARTUUID / FSUUID）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/</guid><description>&lt;p>分區識別是 &lt;code>fstab&lt;/code>（開機時決定哪個分區掛到哪的設定檔）與 bootloader 指涉某個分區時用的名字，它的選擇決定一件事：重開機或重格式化後，系統還找不找得到自己的分區。&lt;/p>
&lt;p>有三種識別方式，穩定性不同。PARTUUID 是寫在 GPT 分區表裡的 ID，綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變。FSUUID 是檔案系統 superblock（檔案系統開頭記錄自身中繼資料的區塊）裡的 UUID，綁在檔案系統上，所以一重新格式化就變，會讓引用它的 &lt;code>fstab&lt;/code> 失效。kernel 名稱（&lt;code>/dev/sda1&lt;/code>、&lt;code>/dev/vda1&lt;/code>）則隨偵測順序浮動，多接一顆磁碟就可能對調，最不穩。&lt;code>fstab&lt;/code> 還吃 &lt;code>LABEL=&lt;/code> / &lt;code>PARTLABEL=&lt;/code>（你自己給的標籤），穩定性看你維不維護那個標籤，跟前三種「系統生成」的識別不同層級，這裡不展開。&lt;/p>
&lt;p>穩定性排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱。在 GPT 磁碟上用 PARTUUID，得到「綁分區、重格不變」的最穩識別。這也是為什麼安裝程式問「device name scheme」時，GPT 磁碟選 PARTUUID。&lt;/p>
&lt;p>理解這個差異，能解釋一類典型故障：重新格式化某個分區後機器開不了機，往往是因為 &lt;code>fstab&lt;/code> 或 bootloader 用了 FSUUID，而格式化讓那個 UUID 變了。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs&lt;/a>。安裝時的識別方式選擇，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>分區識別是 <code>fstab</code>（開機時決定哪個分區掛到哪的設定檔）與 bootloader 指涉某個分區時用的名字，它的選擇決定一件事：重開機或重格式化後，系統還找不找得到自己的分區。</p>
<p>有三種識別方式，穩定性不同。PARTUUID 是寫在 GPT 分區表裡的 ID，綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變。FSUUID 是檔案系統 superblock（檔案系統開頭記錄自身中繼資料的區塊）裡的 UUID，綁在檔案系統上，所以一重新格式化就變，會讓引用它的 <code>fstab</code> 失效。kernel 名稱（<code>/dev/sda1</code>、<code>/dev/vda1</code>）則隨偵測順序浮動，多接一顆磁碟就可能對調，最不穩。<code>fstab</code> 還吃 <code>LABEL=</code> / <code>PARTLABEL=</code>（你自己給的標籤），穩定性看你維不維護那個標籤，跟前三種「系統生成」的識別不同層級，這裡不展開。</p>
<p>穩定性排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱。在 GPT 磁碟上用 PARTUUID，得到「綁分區、重格不變」的最穩識別。這也是為什麼安裝程式問「device name scheme」時，GPT 磁碟選 PARTUUID。</p>
<p>理解這個差異，能解釋一類典型故障：重新格式化某個分區後機器開不了機，往往是因為 <code>fstab</code> 或 bootloader 用了 FSUUID，而格式化讓那個 UUID 變了。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>、<a href="/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs</a>。安裝時的識別方式選擇，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>字型的可用集合在 process 啟動時決定</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/</guid><description>&lt;p>一個 process 能用哪些字型，是在它&lt;strong>啟動的當下&lt;/strong>由 fontconfig（Linux 上統一管理字型搜尋與匹配的底層服務）決定並載入記憶體的。之後往系統裝新字型，不會回頭改變已經在跑的 process——它手上那份字型清單是啟動時的快照。「裝了字型卻還是豆腐」多數時候指向的是這個時序問題，而非安裝本身失敗。&lt;/p>
&lt;p>這個機制發生在 fontconfig + process 記憶體層，跟顯示協議無關——Wayland 和 X11 下的行為相同。&lt;/p>
&lt;h2 id="同一時刻兩種查詢結果">同一時刻、兩種查詢結果&lt;/h2>
&lt;p>裝完新字型後，在終端機用 fontconfig 的查詢工具 &lt;code>fc-match&lt;/code>（每次執行都是新 process）去查：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">fc-match &lt;span class="s2">&amp;#34;:lang=zh-tw&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># Noto Sans CJK → 系統快取已有這支字&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>查得到。但同時間，一直開著的狀態列或通知 daemon 仍顯示豆腐。矛盾的根源是兩者的啟動時間不同：&lt;code>fc-match&lt;/code> 剛啟動、讀到的是最新系統快取；那個豆腐的 daemon 是在裝字型&lt;strong>之前&lt;/strong>啟動的，記憶體裡的字型清單沒有這支字。&lt;/p>
&lt;p>套件管理器安裝字型時，post-install hook 通常已更新 fontconfig 的系統快取（pacman 會印 &lt;code>Updating fontconfig cache&lt;/code>）。手動把字型檔放進 &lt;code>~/.local/share/fonts/&lt;/code> 的情況下，需要自己跑 fontconfig 的快取重建工具 &lt;code>fc-cache&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">fc-cache -fv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># -f 忽略時間戳、強制全部重建&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># -v 印出處理了哪些目錄&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>fc-cache&lt;/code> 只更新系統快取層——磁碟上的索引檔。它不會觸及任何已啟動 process 的記憶體，跑再多次也改變不了舊 process 的字型清單。&lt;/p>
&lt;h2 id="判讀與操作">判讀與操作&lt;/h2>
&lt;p>&lt;strong>判讀訊號&lt;/strong>：&lt;code>fc-match&lt;/code> 在命令列回得出正確字型，但某個一直開著的程式仍顯示豆腐，幾乎可確定是「那個程式啟動早於裝字型」。&lt;/p>
&lt;p>&lt;strong>修法是重啟該程式，不是 reload&lt;/strong>。&lt;code>reload&lt;/code> 類指令（如 &lt;code>makoctl reload&lt;/code>、送 SIGHUP）重讀的是&lt;strong>設定檔&lt;/strong>——能換到 daemon 啟動時已可見的字型（例如從 A 字族改成 B 字族），但看不到啟動後才新裝的字型檔。根源是 reload 不重建記憶體裡的字型清單，只有重啟 process 才會從系統快取重新載入。&lt;/p>
&lt;p>&lt;strong>重啟的範圍&lt;/strong>取決於受影響的程式數量。單一 daemon（通知、狀態列）重啟那一個即可；由 compositor &lt;code>exec-once&lt;/code> 拉起的一批元件要同時吃到新字型，最乾淨的做法是重新登入，讓它們全部重新啟動。&lt;/p>
&lt;p>&lt;strong>正常開機不會踩到這個坑&lt;/strong>——字型在開機早期就裝好，&lt;code>exec-once&lt;/code> 啟動的元件從一開始就看得到完整字型集合。這個時序問題集中在「系統已經在跑、中途才補裝字型」的除錯情境。&lt;/p>
&lt;p>&lt;strong>延伸閱讀&lt;/strong>：Nerd Font 不含 CJK、需另裝 fallback 字型的具體案例見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/desktop-shell-components/" data-link-title="桌面 Shell 元件：狀態列、啟動器與通知" data-link-desc="Hyprland 桌面要拼哪些元件、各元件的配置檔怎麼寫時回來讀">桌面 Shell 元件：狀態列、啟動器與通知&lt;/a>；字型安裝方式見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/terminal-emulator-config/" data-link-title="Terminal Emulator 配置" data-link-desc="選 terminal emulator 時需要比對配置格式和跨平台能力、或想把配色和字型統一管理時回來讀">終端機與編輯器配置&lt;/a>的字型管理段。&lt;/p>
&lt;h2 id="邊界與例外">邊界與例外&lt;/h2>
&lt;p>&lt;strong>fc-match 也查不到&lt;/strong>：連新 process 都找不到剛裝的字型，問題在系統快取層（fontconfig 索引未更新），跑 &lt;code>fc-cache -fv&lt;/code> 解決。兩層的修法不同，&lt;code>fc-match&lt;/code> 是分辨在哪一層的第一步。&lt;/p>
&lt;p>&lt;strong>部分應用程式支援熱載入&lt;/strong>：瀏覽器等有獨立字型服務的程式可能在開新分頁時重新掃描字型，不需要重啟整個 process。長駐 daemon（mako、waybar）與狀態列預設是啟動時載入一次。&lt;/p>
&lt;p>&lt;strong>Flatpak / Snap 的字型隔離是不同問題&lt;/strong>：沙箱化應用程式看不到 host 的字型目錄，重啟 process 也無法解決——原因不是時序，而是沙箱的檔案系統隔離。需要透過 Flatpak 的 filesystem override 或把字型放進沙箱可存取的路徑。&lt;/p></description><content:encoded><![CDATA[<p>一個 process 能用哪些字型，是在它<strong>啟動的當下</strong>由 fontconfig（Linux 上統一管理字型搜尋與匹配的底層服務）決定並載入記憶體的。之後往系統裝新字型，不會回頭改變已經在跑的 process——它手上那份字型清單是啟動時的快照。「裝了字型卻還是豆腐」多數時候指向的是這個時序問題，而非安裝本身失敗。</p>
<p>這個機制發生在 fontconfig + process 記憶體層，跟顯示協議無關——Wayland 和 X11 下的行為相同。</p>
<h2 id="同一時刻兩種查詢結果">同一時刻、兩種查詢結果</h2>
<p>裝完新字型後，在終端機用 fontconfig 的查詢工具 <code>fc-match</code>（每次執行都是新 process）去查：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">fc-match <span class="s2">&#34;:lang=zh-tw&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># Noto Sans CJK → 系統快取已有這支字</span></span></span></code></pre></div><p>查得到。但同時間，一直開著的狀態列或通知 daemon 仍顯示豆腐。矛盾的根源是兩者的啟動時間不同：<code>fc-match</code> 剛啟動、讀到的是最新系統快取；那個豆腐的 daemon 是在裝字型<strong>之前</strong>啟動的，記憶體裡的字型清單沒有這支字。</p>
<p>套件管理器安裝字型時，post-install hook 通常已更新 fontconfig 的系統快取（pacman 會印 <code>Updating fontconfig cache</code>）。手動把字型檔放進 <code>~/.local/share/fonts/</code> 的情況下，需要自己跑 fontconfig 的快取重建工具 <code>fc-cache</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">fc-cache -fv
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># -f 忽略時間戳、強制全部重建</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># -v 印出處理了哪些目錄</span></span></span></code></pre></div><p><code>fc-cache</code> 只更新系統快取層——磁碟上的索引檔。它不會觸及任何已啟動 process 的記憶體，跑再多次也改變不了舊 process 的字型清單。</p>
<h2 id="判讀與操作">判讀與操作</h2>
<p><strong>判讀訊號</strong>：<code>fc-match</code> 在命令列回得出正確字型，但某個一直開著的程式仍顯示豆腐，幾乎可確定是「那個程式啟動早於裝字型」。</p>
<p><strong>修法是重啟該程式，不是 reload</strong>。<code>reload</code> 類指令（如 <code>makoctl reload</code>、送 SIGHUP）重讀的是<strong>設定檔</strong>——能換到 daemon 啟動時已可見的字型（例如從 A 字族改成 B 字族），但看不到啟動後才新裝的字型檔。根源是 reload 不重建記憶體裡的字型清單，只有重啟 process 才會從系統快取重新載入。</p>
<p><strong>重啟的範圍</strong>取決於受影響的程式數量。單一 daemon（通知、狀態列）重啟那一個即可；由 compositor <code>exec-once</code> 拉起的一批元件要同時吃到新字型，最乾淨的做法是重新登入，讓它們全部重新啟動。</p>
<p><strong>正常開機不會踩到這個坑</strong>——字型在開機早期就裝好，<code>exec-once</code> 啟動的元件從一開始就看得到完整字型集合。這個時序問題集中在「系統已經在跑、中途才補裝字型」的除錯情境。</p>
<p><strong>延伸閱讀</strong>：Nerd Font 不含 CJK、需另裝 fallback 字型的具體案例見<a href="/blog/linux/dotfile/06-rice-design/desktop-shell-components/" data-link-title="桌面 Shell 元件：狀態列、啟動器與通知" data-link-desc="Hyprland 桌面要拼哪些元件、各元件的配置檔怎麼寫時回來讀">桌面 Shell 元件：狀態列、啟動器與通知</a>；字型安裝方式見<a href="/blog/linux/dotfile/03-terminal-ecosystem/terminal-emulator-config/" data-link-title="Terminal Emulator 配置" data-link-desc="選 terminal emulator 時需要比對配置格式和跨平台能力、或想把配色和字型統一管理時回來讀">終端機與編輯器配置</a>的字型管理段。</p>
<h2 id="邊界與例外">邊界與例外</h2>
<p><strong>fc-match 也查不到</strong>：連新 process 都找不到剛裝的字型，問題在系統快取層（fontconfig 索引未更新），跑 <code>fc-cache -fv</code> 解決。兩層的修法不同，<code>fc-match</code> 是分辨在哪一層的第一步。</p>
<p><strong>部分應用程式支援熱載入</strong>：瀏覽器等有獨立字型服務的程式可能在開新分頁時重新掃描字型，不需要重啟整個 process。長駐 daemon（mako、waybar）與狀態列預設是啟動時載入一次。</p>
<p><strong>Flatpak / Snap 的字型隔離是不同問題</strong>：沙箱化應用程式看不到 host 的字型目錄，重啟 process 也無法解決——原因不是時序，而是沙箱的檔案系統隔離。需要透過 Flatpak 的 filesystem override 或把字型放進沙箱可存取的路徑。</p>
]]></content:encoded></item><item><title>Wayland Session Lock（鎖屏安全狀態）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/</guid><description>&lt;h2 id="鎖屏是-compositor-持有的安全狀態">鎖屏是 compositor 持有的安全狀態&lt;/h2>
&lt;p>Wayland 下的 compositor（如 Hyprland、Sway）同時管理視窗排列與畫面輸出。鎖屏工具（Hyprlock、Swaylock）一旦啟動，桌面的「鎖定」狀態就由 compositor 透過 ext-session-lock-v1（Wayland 生態系的跨 compositor 鎖屏協議）持有。解鎖的正常動作是鎖屏 client 通過認證後呼叫 &lt;code>unlock_and_destroy&lt;/code>（協議定義的 request），compositor 收到這個信號才釋放鎖定。&lt;/p>
&lt;p>這個責任邊界在自動化測試、VM 演練、遠端操作時最容易出事，因為這些情境常用「殺 process」當「關掉一個東西」的通用手段。殺掉鎖屏 client 跳過了認證，compositor 不會釋放鎖——畫面會卡在失效保護狀態而非回到桌面。&lt;/p>
&lt;h2 id="logind-提示與-compositor-鎖的值可以不一致">logind 提示與 compositor 鎖的值可以不一致&lt;/h2>
&lt;p>鎖屏狀態牽涉兩個獨立的層，觸發方向和持有者不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>持有者&lt;/th>
 &lt;th>查看方式&lt;/th>
 &lt;th>語意&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>logind 會話鎖&lt;/td>
 &lt;td>systemd-logind&lt;/td>
 &lt;td>&lt;code>loginctl show-session &amp;lt;id&amp;gt; -p LockedHint&lt;/code>&lt;/td>
 &lt;td>會話的鎖定提示，給登入管理器 / 螢幕保護程式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>compositor 鎖&lt;/td>
 &lt;td>Wayland compositor&lt;/td>
 &lt;td>畫面是否進得去、鎖屏 surface 是否在最上層&lt;/td>
 &lt;td>實際擋住畫面的那層&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>loginctl lock-session&lt;/code> 走 logind 層觸發鎖屏，鎖屏 client 收到信號後啟動、再向 compositor 取得 session lock。觸發方向是 logind → client → compositor；持有與強制執行方向是 compositor → 畫面。兩者方向相反，正好印證兩層是獨立的。&lt;/p>
&lt;p>實測會遇到 &lt;code>LockedHint=no&lt;/code>（logind 層說沒鎖）但畫面仍進不去——因為擋住畫面的是 compositor 的 ext-session-lock，跟 logind 提示是兩回事。判斷畫面進不進得去，看 compositor 層，不看 logind 層。&lt;/p>
&lt;h2 id="鎖屏-client-非正常結束時的失效保護">鎖屏 client 非正常結束時的失效保護&lt;/h2>
&lt;p>鎖屏 client 在持有鎖的狀態下死掉（被 &lt;code>kill&lt;/code>、crash），compositor 沒有收到認證通過的信號，只能維持鎖定並顯示失效保護畫面。Hyprland 的失效保護畫面會直接給恢復指令：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">hyprctl --instance 0 &amp;#39;keyword misc:allow_session_lock_restore 1&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hyprctl --instance 0 &amp;#39;dispatch exec hyprlock&amp;#39;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>allow_session_lock_restore&lt;/code> 允許新的鎖屏 client 接管既有的鎖（否則新 client 會因「已經鎖了」被拒）。接管後是乾淨的鎖屏 prompt，用密碼正常解鎖。&lt;/p>
&lt;p>備好 restore 路徑時，殺掉無回應的鎖屏 client 是合理操作——問題不在「殺」、在「以為殺完就回桌面」。restore 的前提是有另一個可操作的 session：另一個 TTY 或 SSH 連線。ext-session-lock 的安全語意允許 compositor 攔截 VT 切換快捷鍵（&lt;code>Ctrl+Alt+Fn&lt;/code>），遇到 TTY 切不過去的情況，SSH 是替代救援通道（事先配好 SSH server，見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作&lt;/a>的 GPU hang 段）。&lt;/p>
&lt;h2 id="判讀與操作">判讀與操作&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>判讀鎖定狀態&lt;/strong>：&lt;code>loginctl show-session $(loginctl show-user $USER -p Display --value) -p LockedHint&lt;/code> 查 logind 層；compositor 層看畫面能否操作。兩層不一致時以 compositor 層為準。&lt;/li>
&lt;li>&lt;strong>正常解鎖&lt;/strong>：通過鎖屏 client 的認證（密碼 / 指紋），client 呼叫 &lt;code>unlock_and_destroy&lt;/code>，compositor 釋放鎖。&lt;/li>
&lt;li>&lt;strong>失效保護恢復&lt;/strong>：從另一個 TTY 或 SSH 執行 &lt;code>hyprctl --instance 0 'keyword misc:allow_session_lock_restore 1'&lt;/code> + &lt;code>hyprctl --instance 0 'dispatch exec hyprlock'&lt;/code>，重新拉起鎖屏 prompt 後認證解鎖。&lt;/li>
&lt;li>&lt;strong>自動化流程的代價&lt;/strong>：啟動鎖屏後，畫面會留在鎖定狀態直到有人通過認證。自動化測試若會觸發鎖屏，要把「需人工解鎖」算進代價。&lt;/li>
&lt;li>&lt;strong>診斷路由&lt;/strong>：「畫面卡住 / 螢幕鎖了沒」當成一般 Linux 狀態判讀問題（跟判程式活著、判服務歸屬同類）時，見&lt;a href="https://tarrragon.github.io/blog/linux/debug/process-service-state-diagnosis/" data-link-title="程序、服務與狀態怎麼判" data-link-desc="要判斷一個程式活著沒、某個系統服務現在由誰提供、桌面 session 有沒有被鎖、或終端機多工器的 session 還在不在時，用對的權威來源而不是靠畫面或猜的名字">程序、服務與狀態怎麼判&lt;/a>——它把「判 session 有沒有被鎖」放進「讀權威狀態、別看畫面猜」的通用診斷紀律裡。&lt;/li>
&lt;li>&lt;strong>延伸閱讀&lt;/strong>：鎖屏的視覺配置（背景、輸入框、時鐘 label）見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/color-system-theming/" data-link-title="配色系統、鎖屏與 GTK 主題" data-link-desc="桌面配色散亂看起來雜、或要換主題不知道該改哪些檔案時回來讀">配色系統、鎖屏與 GTK 主題&lt;/a>的 Hyprlock 段；桌面故障恢復流程見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作&lt;/a>。持鎖的那個 compositor 到底是什麼、還握著哪些系統狀態，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">Compositor 術語卡&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h2 id="邊界條件">邊界條件&lt;/h2>
&lt;p>正常認證解鎖（走 &lt;code>unlock_and_destroy&lt;/code>）後鎖屏 client 結束，compositor 已回到非鎖定狀態，不觸發失效保護。失效保護只在「持鎖中非正常結束」時出現。&lt;/p></description><content:encoded><![CDATA[<h2 id="鎖屏是-compositor-持有的安全狀態">鎖屏是 compositor 持有的安全狀態</h2>
<p>Wayland 下的 compositor（如 Hyprland、Sway）同時管理視窗排列與畫面輸出。鎖屏工具（Hyprlock、Swaylock）一旦啟動，桌面的「鎖定」狀態就由 compositor 透過 ext-session-lock-v1（Wayland 生態系的跨 compositor 鎖屏協議）持有。解鎖的正常動作是鎖屏 client 通過認證後呼叫 <code>unlock_and_destroy</code>（協議定義的 request），compositor 收到這個信號才釋放鎖定。</p>
<p>這個責任邊界在自動化測試、VM 演練、遠端操作時最容易出事，因為這些情境常用「殺 process」當「關掉一個東西」的通用手段。殺掉鎖屏 client 跳過了認證，compositor 不會釋放鎖——畫面會卡在失效保護狀態而非回到桌面。</p>
<h2 id="logind-提示與-compositor-鎖的值可以不一致">logind 提示與 compositor 鎖的值可以不一致</h2>
<p>鎖屏狀態牽涉兩個獨立的層，觸發方向和持有者不同：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>持有者</th>
          <th>查看方式</th>
          <th>語意</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>logind 會話鎖</td>
          <td>systemd-logind</td>
          <td><code>loginctl show-session &lt;id&gt; -p LockedHint</code></td>
          <td>會話的鎖定提示，給登入管理器 / 螢幕保護程式</td>
      </tr>
      <tr>
          <td>compositor 鎖</td>
          <td>Wayland compositor</td>
          <td>畫面是否進得去、鎖屏 surface 是否在最上層</td>
          <td>實際擋住畫面的那層</td>
      </tr>
  </tbody>
</table>
<p><code>loginctl lock-session</code> 走 logind 層觸發鎖屏，鎖屏 client 收到信號後啟動、再向 compositor 取得 session lock。觸發方向是 logind → client → compositor；持有與強制執行方向是 compositor → 畫面。兩者方向相反，正好印證兩層是獨立的。</p>
<p>實測會遇到 <code>LockedHint=no</code>（logind 層說沒鎖）但畫面仍進不去——因為擋住畫面的是 compositor 的 ext-session-lock，跟 logind 提示是兩回事。判斷畫面進不進得去，看 compositor 層，不看 logind 層。</p>
<h2 id="鎖屏-client-非正常結束時的失效保護">鎖屏 client 非正常結束時的失效保護</h2>
<p>鎖屏 client 在持有鎖的狀態下死掉（被 <code>kill</code>、crash），compositor 沒有收到認證通過的信號，只能維持鎖定並顯示失效保護畫面。Hyprland 的失效保護畫面會直接給恢復指令：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">hyprctl --instance 0 &#39;keyword misc:allow_session_lock_restore 1&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">hyprctl --instance 0 &#39;dispatch exec hyprlock&#39;</span></span></code></pre></div><p><code>allow_session_lock_restore</code> 允許新的鎖屏 client 接管既有的鎖（否則新 client 會因「已經鎖了」被拒）。接管後是乾淨的鎖屏 prompt，用密碼正常解鎖。</p>
<p>備好 restore 路徑時，殺掉無回應的鎖屏 client 是合理操作——問題不在「殺」、在「以為殺完就回桌面」。restore 的前提是有另一個可操作的 session：另一個 TTY 或 SSH 連線。ext-session-lock 的安全語意允許 compositor 攔截 VT 切換快捷鍵（<code>Ctrl+Alt+Fn</code>），遇到 TTY 切不過去的情況，SSH 是替代救援通道（事先配好 SSH server，見<a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作</a>的 GPU hang 段）。</p>
<h2 id="判讀與操作">判讀與操作</h2>
<ul>
<li><strong>判讀鎖定狀態</strong>：<code>loginctl show-session $(loginctl show-user $USER -p Display --value) -p LockedHint</code> 查 logind 層；compositor 層看畫面能否操作。兩層不一致時以 compositor 層為準。</li>
<li><strong>正常解鎖</strong>：通過鎖屏 client 的認證（密碼 / 指紋），client 呼叫 <code>unlock_and_destroy</code>，compositor 釋放鎖。</li>
<li><strong>失效保護恢復</strong>：從另一個 TTY 或 SSH 執行 <code>hyprctl --instance 0 'keyword misc:allow_session_lock_restore 1'</code> + <code>hyprctl --instance 0 'dispatch exec hyprlock'</code>，重新拉起鎖屏 prompt 後認證解鎖。</li>
<li><strong>自動化流程的代價</strong>：啟動鎖屏後，畫面會留在鎖定狀態直到有人通過認證。自動化測試若會觸發鎖屏，要把「需人工解鎖」算進代價。</li>
<li><strong>診斷路由</strong>：「畫面卡住 / 螢幕鎖了沒」當成一般 Linux 狀態判讀問題（跟判程式活著、判服務歸屬同類）時，見<a href="/blog/linux/debug/process-service-state-diagnosis/" data-link-title="程序、服務與狀態怎麼判" data-link-desc="要判斷一個程式活著沒、某個系統服務現在由誰提供、桌面 session 有沒有被鎖、或終端機多工器的 session 還在不在時，用對的權威來源而不是靠畫面或猜的名字">程序、服務與狀態怎麼判</a>——它把「判 session 有沒有被鎖」放進「讀權威狀態、別看畫面猜」的通用診斷紀律裡。</li>
<li><strong>延伸閱讀</strong>：鎖屏的視覺配置（背景、輸入框、時鐘 label）見<a href="/blog/linux/dotfile/06-rice-design/color-system-theming/" data-link-title="配色系統、鎖屏與 GTK 主題" data-link-desc="桌面配色散亂看起來雜、或要換主題不知道該改哪些檔案時回來讀">配色系統、鎖屏與 GTK 主題</a>的 Hyprlock 段；桌面故障恢復流程見<a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作</a>。持鎖的那個 compositor 到底是什麼、還握著哪些系統狀態，見 <a href="/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">Compositor 術語卡</a>。</li>
</ul>
<h2 id="邊界條件">邊界條件</h2>
<p>正常認證解鎖（走 <code>unlock_and_destroy</code>）後鎖屏 client 結束，compositor 已回到非鎖定狀態，不觸發失效保護。失效保護只在「持鎖中非正常結束」時出現。</p>
<p>Sway/swaylock 在 client 死掉時沒有畫面上的恢復提示（不像 Hyprland 會印指令），得預先知道走 TTY 或 SSH 執行 restore。「鎖是 compositor 持有、解鎖要認證」是 ext-session-lock 協議層的共通約束；失效保護的具體呈現方式因 compositor 而異。</p>
]]></content:encoded></item><item><title>fontconfig — 字型搜尋、匹配與 fallback 服務</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/fontconfig/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/fontconfig/</guid><description>&lt;p>fontconfig 是 Linux 上統一管理字型搜尋、匹配與 fallback 的底層服務。應用程式透過 fontconfig 的 API 查詢可用字型，而非自行掃描字型目錄——無論是終端機、狀態列、通知 daemon 還是瀏覽器，底層都走同一套查詢介面。&lt;/p>
&lt;h2 id="fc--工具分工">fc-* 工具分工&lt;/h2>
&lt;p>fontconfig 附帶一組命令列工具，各自負責一件事：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;th>常用情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>fc-list&lt;/code>&lt;/td>
 &lt;td>列出系統已知的所有字型（字族名、檔案路徑）&lt;/td>
 &lt;td>確認某支字型有沒有裝、查實際字族名&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>fc-match&lt;/code>&lt;/td>
 &lt;td>查詢指定條件的最佳匹配結果&lt;/td>
 &lt;td>確認 config 裡寫的字族名會匹配到哪支字&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>fc-cache&lt;/code>&lt;/td>
 &lt;td>重建 fontconfig 的系統快取&lt;/td>
 &lt;td>手動放字型檔後更新快取（套件安裝通常自動跑）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>fc-pattern&lt;/code>&lt;/td>
 &lt;td>印出字型的完整屬性（除錯用）&lt;/td>
 &lt;td>查字型支援的語言、字重、字形變體&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>fc-list&lt;/code> 和 &lt;code>fc-match&lt;/code> 每次執行都是新 process，讀到的是當下最新的系統快取。這跟已啟動的長駐程式不同——長駐程式的字型清單是啟動時的快照，詳見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup&lt;/a>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">fc-list &lt;span class="p">|&lt;/span> grep -i meslo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># 確認 MesloLGS Nerd Font 有沒有裝、實際字族名是什麼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">fc-match &lt;span class="s2">&amp;#34;MesloLGS Nerd Font&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查 config 裡寫的名字會匹配到哪支字型檔&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">fc-match &lt;span class="s2">&amp;#34;:lang=zh-tw&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查系統有沒有可用的繁體中文字型&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="fallback-chain">Fallback chain&lt;/h2>
&lt;p>應用程式在 config 裡指定字族名（如 &lt;code>MesloLGS Nerd Font&lt;/code>），fontconfig 依以下順序處理：&lt;/p>
&lt;ol>
&lt;li>在已知字型中找&lt;strong>完全匹配&lt;/strong>的字族&lt;/li>
&lt;li>找不到就沿 fallback chain 往下找候選——fontconfig 的預設 fallback 規則定義在 &lt;code>/etc/fonts/conf.d/&lt;/code> 的 XML 設定檔中&lt;/li>
&lt;li>CJK fallback 依語言優先序決定——&lt;code>fc-match &amp;quot;:lang=zh-tw&amp;quot;&lt;/code> 回的是 fontconfig 認為最適合該語言的字型&lt;/li>
&lt;/ol>
&lt;p>Nerd Font（MesloLGS、JetBrainsMono 等）只含 Latin 字元與圖示 glyph，CJK 字元靠 fallback 到另一支字型（如 &lt;code>noto-fonts-cjk&lt;/code>）補齊。fontconfig 的 fallback 對應用程式透明——應用程式只指定主字型，缺字時 fontconfig 自動補。&lt;/p>
&lt;h2 id="系統快取">系統快取&lt;/h2>
&lt;p>fontconfig 把字型目錄的掃描結果存成快取檔，避免每次查詢都重新掃描整個檔案系統：&lt;/p>
&lt;ul>
&lt;li>系統層快取：&lt;code>/var/cache/fontconfig/&lt;/code>&lt;/li>
&lt;li>使用者層快取：&lt;code>~/.cache/fontconfig/&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>套件管理器安裝字型時，post-install hook 會自動執行 &lt;code>fc-cache&lt;/code> 更新系統快取（pacman 安裝完會印 &lt;code>Updating fontconfig cache&lt;/code>）。手動把字型檔放進 &lt;code>~/.local/share/fonts/&lt;/code> 時需要自己跑 &lt;code>fc-cache&lt;/code>——不跑的話 fontconfig 看不到新字型。&lt;/p>
&lt;p>&lt;code>fc-cache -f&lt;/code> 的 &lt;code>-f&lt;/code> 是 force，忽略時間戳全部重建；不加 &lt;code>-f&lt;/code> 只更新有變動的目錄。兩者都只動系統快取層——已啟動的 process 記憶體中的字型清單不受影響，那是另一個層級的問題（見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup&lt;/a>）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>字型安裝方式：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器&lt;/a>的字型管理段&lt;/li>
&lt;li>裝了字型但應用程式還是看不到：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup&lt;/a>（process 啟動時快照的時序問題）&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>fontconfig 是 Linux 上統一管理字型搜尋、匹配與 fallback 的底層服務。應用程式透過 fontconfig 的 API 查詢可用字型，而非自行掃描字型目錄——無論是終端機、狀態列、通知 daemon 還是瀏覽器，底層都走同一套查詢介面。</p>
<h2 id="fc--工具分工">fc-* 工具分工</h2>
<p>fontconfig 附帶一組命令列工具，各自負責一件事：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>用途</th>
          <th>常用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>fc-list</code></td>
          <td>列出系統已知的所有字型（字族名、檔案路徑）</td>
          <td>確認某支字型有沒有裝、查實際字族名</td>
      </tr>
      <tr>
          <td><code>fc-match</code></td>
          <td>查詢指定條件的最佳匹配結果</td>
          <td>確認 config 裡寫的字族名會匹配到哪支字</td>
      </tr>
      <tr>
          <td><code>fc-cache</code></td>
          <td>重建 fontconfig 的系統快取</td>
          <td>手動放字型檔後更新快取（套件安裝通常自動跑）</td>
      </tr>
      <tr>
          <td><code>fc-pattern</code></td>
          <td>印出字型的完整屬性（除錯用）</td>
          <td>查字型支援的語言、字重、字形變體</td>
      </tr>
  </tbody>
</table>
<p><code>fc-list</code> 和 <code>fc-match</code> 每次執行都是新 process，讀到的是當下最新的系統快取。這跟已啟動的長駐程式不同——長駐程式的字型清單是啟動時的快照，詳見 <a href="/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup</a>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">fc-list <span class="p">|</span> grep -i meslo
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 確認 MesloLGS Nerd Font 有沒有裝、實際字族名是什麼</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">fc-match <span class="s2">&#34;MesloLGS Nerd Font&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 查 config 裡寫的名字會匹配到哪支字型檔</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">fc-match <span class="s2">&#34;:lang=zh-tw&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 查系統有沒有可用的繁體中文字型</span></span></span></code></pre></div><h2 id="fallback-chain">Fallback chain</h2>
<p>應用程式在 config 裡指定字族名（如 <code>MesloLGS Nerd Font</code>），fontconfig 依以下順序處理：</p>
<ol>
<li>在已知字型中找<strong>完全匹配</strong>的字族</li>
<li>找不到就沿 fallback chain 往下找候選——fontconfig 的預設 fallback 規則定義在 <code>/etc/fonts/conf.d/</code> 的 XML 設定檔中</li>
<li>CJK fallback 依語言優先序決定——<code>fc-match &quot;:lang=zh-tw&quot;</code> 回的是 fontconfig 認為最適合該語言的字型</li>
</ol>
<p>Nerd Font（MesloLGS、JetBrainsMono 等）只含 Latin 字元與圖示 glyph，CJK 字元靠 fallback 到另一支字型（如 <code>noto-fonts-cjk</code>）補齊。fontconfig 的 fallback 對應用程式透明——應用程式只指定主字型，缺字時 fontconfig 自動補。</p>
<h2 id="系統快取">系統快取</h2>
<p>fontconfig 把字型目錄的掃描結果存成快取檔，避免每次查詢都重新掃描整個檔案系統：</p>
<ul>
<li>系統層快取：<code>/var/cache/fontconfig/</code></li>
<li>使用者層快取：<code>~/.cache/fontconfig/</code></li>
</ul>
<p>套件管理器安裝字型時，post-install hook 會自動執行 <code>fc-cache</code> 更新系統快取（pacman 安裝完會印 <code>Updating fontconfig cache</code>）。手動把字型檔放進 <code>~/.local/share/fonts/</code> 時需要自己跑 <code>fc-cache</code>——不跑的話 fontconfig 看不到新字型。</p>
<p><code>fc-cache -f</code> 的 <code>-f</code> 是 force，忽略時間戳全部重建；不加 <code>-f</code> 只更新有變動的目錄。兩者都只動系統快取層——已啟動的 process 記憶體中的字型清單不受影響，那是另一個層級的問題（見 <a href="/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup</a>）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>字型安裝方式：<a href="/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器</a>的字型管理段</li>
<li>裝了字型但應用程式還是看不到：<a href="/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">font-availability-at-startup</a>（process 啟動時快照的時序問題）</li>
</ul>
]]></content:encoded></item></channel></rss>