<?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>Windows on idiotfan</title><link>https://idiotfan.wang/tags/windows/</link><description>Recent content in Windows on idiotfan</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 01 Sep 2026 17:15:00 +0800</lastBuildDate><atom:link href="https://idiotfan.wang/tags/windows/index.xml" rel="self" type="application/rss+xml"/><item><title>DSH Web 跨平台桌面启动器全教程：macOS 原生 .app + Windows 一键窗口</title><link>https://idiotfan.wang/posts/dsh-web-cross-platform-desktop-app/</link><pubDate>Tue, 01 Sep 2026 17:15:00 +0800</pubDate><guid>https://idiotfan.wang/posts/dsh-web-cross-platform-desktop-app/</guid><description>把 DeepSeek Harness 的 Web 界面做成真正的桌面应用：macOS 用 Swift + WKWebView 编译原生 .app，Windows 用 Edge/Chrome --app 模式或 WebView2。图标直接用 DSH 官方鲸鱼 SVG 渲染。全文自包含，照着做或把链接丢给 AI 都能复现。</description><content:encoded><![CDATA[<p>之前<a href="/posts/dsh-macos-launcher-app/">写过一版 DSH 的 macOS 启动器</a>：一个 shell 脚本包装成的 <code>.app</code>，双击后自动拉起 <code>dsh web</code> 服务再开浏览器。能用，但打开的还是浏览器标签页，混在一堆网页里不够&quot;应用&quot;。</p>
<p>这次升级成<strong>原生应用方案</strong>，并且把整套方法整理成跨平台教程：</p>
<ul>
<li><strong>macOS</strong>：Swift + WKWebView 编译成原生 <code>.app</code>，独立窗口、Dock 图标、断线自动重连</li>
<li><strong>Windows</strong>：Edge/Chrome <code>--app</code> 模式快捷方式（零依赖），或 WebView2 原生窗口（进阶）</li>
<li><strong>图标</strong>：不再手绘，直接从 DSH 源码里提取<strong>官方鲸鱼 SVG</strong> 渲染，配深蓝渐变圆角背景</li>
</ul>
<p>最终效果（图标）：</p>
<p><img alt="DSH Web 应用图标" loading="lazy" src="/images/dsh-web-app/icon.png"></p>
<p>本文自包含：所有源码完整附上，照抄即可复现；也可以直接把本文链接发给任何 AI agent，让它照着做。</p>
<hr>
<h2 id="一原理">一、原理</h2>
<p>DSH 的 Web 界面跑在 <code>http://127.0.0.1:3080</code>。所谓&quot;桌面应用&quot;，本质就是<strong>一个固定加载这个地址的独立窗口</strong>：</p>
<table>
	<thead>
			<tr>
					<th>平台</th>
					<th>方案</th>
					<th>窗口技术</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>macOS</td>
					<td>编译原生 <code>.app</code></td>
					<td>WKWebView</td>
			</tr>
			<tr>
					<td>Windows（推荐）</td>
					<td>快捷方式 + <code>--app=</code> 参数</td>
					<td>Edge/Chrome 应用模式</td>
			</tr>
			<tr>
					<td>Windows（进阶）</td>
					<td>.NET WinForms</td>
					<td>WebView2</td>
			</tr>
	</tbody>
</table>
<p>三种方案都不需要打包网页资源，服务还是那个服务，只是入口变成了双击图标。</p>
<h2 id="二通用部分制作图标">二、通用部分：制作图标</h2>
<h3 id="21-提取官方鲸鱼-svg">2.1 提取官方鲸鱼 SVG</h3>
<p>DSH 前端的产物目录里自带 <code>favicon.svg</code>，就是官方鲸鱼 Logo。在本机 DSH 安装目录下找：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm root -g
</span></span><span class="line"><span class="cl"><span class="c1"># 拼上相对路径：</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   &lt;npm全局根&gt;/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-web-frontend/dist/favicon.svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir -p dsh-app <span class="o">&amp;&amp;</span> <span class="nb">cd</span> dsh-app
</span></span><span class="line"><span class="cl">cp <span class="s2">&#34;</span><span class="k">$(</span>npm root -g<span class="k">)</span><span class="s2">/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-web-frontend/dist/favicon.svg&#34;</span> .
</span></span></code></pre></td></tr></table>
</div>
</div><p>Windows 上 <code>npm root -g</code> 输出形如 <code>C:\Users\&lt;你&gt;\AppData\Roaming\npm\node_modules</code>，同样拼上后面的相对路径。找不到的话用 <code>find &quot;$(npm root -g)/@deepseek-ai&quot; -name favicon.svg</code> 定位。</p>
<h3 id="22-图标生成脚本双平台通用">2.2 图标生成脚本（双平台通用）</h3>
<p>依赖：Python 3 + Pillow（<code>pip install pillow</code>）。保存为 <code>make_icon.py</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;DSH Web app icon: official DeepSeek Harness whale (from favicon.svg)
</span></span></span><span class="line"><span class="cl"><span class="s2">on a deep-blue gradient rounded square. 4x supersampled; self-checks at end.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span><span class="p">,</span> <span class="n">ImageDraw</span><span class="p">,</span> <span class="n">ImageFilter</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">re</span><span class="o">,</span> <span class="nn">subprocess</span><span class="o">,</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">OUT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">S</span> <span class="o">=</span> <span class="mi">4096</span>   <span class="c1"># supersample canvas</span>
</span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="o">=</span> <span class="mi">1024</span>   <span class="c1"># final master</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">lerp</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">t</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">tuple</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">*</span> <span class="n">t</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ---------- minimal SVG path parser (official favicon uses M/C only) ----------</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">parse_svg_path</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">tokens</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;([MmLlCcQqZzHhVvSsTtAa])|(-?\d*\.?\d+(?:e-?\d+)?)&#39;</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">tokens</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">or</span> <span class="nb">float</span><span class="p">(</span><span class="n">t</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">num</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">nonlocal</span> <span class="n">i</span>
</span></span><span class="line"><span class="cl">        <span class="n">v</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="n">tokens</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">v</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">cubic</span><span class="p">(</span><span class="n">p0</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span><span class="p">,</span> <span class="n">p3</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">40</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">[(</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">3</span><span class="o">*</span><span class="n">p0</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="mi">3</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="mi">3</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p2</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">t</span><span class="o">**</span><span class="mi">3</span><span class="o">*</span><span class="n">p3</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                  <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">3</span><span class="o">*</span><span class="n">p0</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="mi">3</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p1</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="mi">3</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p2</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">t</span><span class="o">**</span><span class="mi">3</span><span class="o">*</span><span class="n">p3</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="p">(</span><span class="n">k</span><span class="o">/</span><span class="n">n</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="o">+</span><span class="mi">1</span><span class="p">))]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">quad</span><span class="p">(</span><span class="n">p0</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">30</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">[(</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="o">*</span><span class="n">p0</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">t</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p2</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                  <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="o">*</span><span class="n">p0</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">t</span><span class="p">)</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p1</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">t</span><span class="o">*</span><span class="n">t</span><span class="o">*</span><span class="n">p2</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="p">(</span><span class="n">k</span><span class="o">/</span><span class="n">n</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="o">+</span><span class="mi">1</span><span class="p">))]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">subpaths</span><span class="p">,</span> <span class="n">cur</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">pos</span> <span class="o">=</span> <span class="n">start</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span><span class="p">;</span> <span class="n">last_letter</span> <span class="o">=</span> <span class="s1">&#39;M&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">tokens</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">tokens</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">c</span> <span class="o">=</span> <span class="n">tokens</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">c</span> <span class="ow">in</span> <span class="s1">&#39;Zz&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">cur</span> <span class="ow">and</span> <span class="n">cur</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="n">start</span><span class="p">:</span> <span class="n">cur</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">cur</span><span class="p">:</span> <span class="n">subpaths</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cur</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">cur</span> <span class="o">=</span> <span class="p">[];</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">c</span> <span class="o">=</span> <span class="n">last_letter</span>  <span class="c1"># implicit repeat (M -&gt; L)</span>
</span></span><span class="line"><span class="cl">        <span class="n">rel</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">islower</span><span class="p">();</span> <span class="n">C</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">last_letter</span> <span class="o">=</span> <span class="s1">&#39;L&#39;</span> <span class="k">if</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;M&#39;</span> <span class="k">else</span> <span class="n">C</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;M&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">cur</span><span class="p">:</span> <span class="n">subpaths</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cur</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">cur</span> <span class="o">=</span> <span class="p">[</span><span class="n">pos</span><span class="p">];</span> <span class="n">start</span> <span class="o">=</span> <span class="n">pos</span><span class="p">;</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;L&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">cur</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;H&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x</span> <span class="o">=</span> <span class="n">num</span><span class="p">();</span> <span class="n">pos</span> <span class="o">=</span> <span class="p">((</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span> <span class="n">cur</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;V&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">();</span> <span class="n">pos</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="n">y</span><span class="p">);</span> <span class="n">cur</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;C&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">,</span> <span class="n">x2</span><span class="p">,</span> <span class="n">y2</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x1</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y1</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p2</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x2</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y2</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x2</span><span class="p">,</span> <span class="n">y2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p3</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">cur</span> <span class="o">+=</span> <span class="n">cubic</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span><span class="p">,</span> <span class="n">p3</span><span class="p">);</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">p3</span><span class="p">;</span> <span class="n">last_c</span> <span class="o">=</span> <span class="n">p2</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;S&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x2</span><span class="p">,</span> <span class="n">y2</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span> <span class="o">=</span> <span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-</span><span class="n">last_c</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="mi">2</span><span class="o">*</span><span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">-</span><span class="n">last_c</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">if</span> <span class="n">last_c</span> <span class="k">else</span> <span class="n">pos</span>
</span></span><span class="line"><span class="cl">            <span class="n">p2</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x2</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y2</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x2</span><span class="p">,</span> <span class="n">y2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p3</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">cur</span> <span class="o">+=</span> <span class="n">cubic</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span><span class="p">,</span> <span class="n">p3</span><span class="p">);</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">p3</span><span class="p">;</span> <span class="n">last_c</span> <span class="o">=</span> <span class="n">p2</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">C</span> <span class="o">==</span> <span class="s1">&#39;Q&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">(),</span> <span class="n">num</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x1</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y1</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p2</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">y</span><span class="p">)</span> <span class="k">if</span> <span class="n">rel</span> <span class="k">else</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">cur</span> <span class="o">+=</span> <span class="n">quad</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span><span class="p">);</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">p2</span><span class="p">;</span> <span class="n">last_c</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;unhandled command &#34;</span> <span class="o">+</span> <span class="n">c</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cur</span><span class="p">:</span> <span class="n">subpaths</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cur</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">subpaths</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">build_official_whale</span><span class="p">(</span><span class="n">canvas</span><span class="p">,</span> <span class="n">span</span><span class="o">=</span><span class="mf">0.62</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Render the official DSH whale (favicon.svg) white, centered, spanning `span` of canvas.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">src</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUT</span><span class="p">,</span> <span class="s2">&#34;favicon.svg&#34;</span><span class="p">))</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">d</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\bd=&#34;([^&#34;]+)&#34;&#39;</span><span class="p">,</span> <span class="n">src</span><span class="p">)</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">subpaths</span> <span class="o">=</span> <span class="n">parse_svg_path</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">xs</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">sp</span> <span class="ow">in</span> <span class="n">subpaths</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">sp</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">ys</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">sp</span> <span class="ow">in</span> <span class="n">subpaths</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">sp</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">bw</span><span class="p">,</span> <span class="n">bh</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">xs</span><span class="p">)</span> <span class="o">-</span> <span class="nb">min</span><span class="p">(</span><span class="n">xs</span><span class="p">),</span> <span class="nb">max</span><span class="p">(</span><span class="n">ys</span><span class="p">)</span> <span class="o">-</span> <span class="nb">min</span><span class="p">(</span><span class="n">ys</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">scale</span> <span class="o">=</span> <span class="n">canvas</span> <span class="o">*</span> <span class="n">span</span> <span class="o">/</span> <span class="nb">max</span><span class="p">(</span><span class="n">bw</span><span class="p">,</span> <span class="n">bh</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ox</span> <span class="o">=</span> <span class="n">canvas</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">xs</span><span class="p">)</span> <span class="o">+</span> <span class="nb">max</span><span class="p">(</span><span class="n">xs</span><span class="p">))</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">scale</span>
</span></span><span class="line"><span class="cl">    <span class="n">oy</span> <span class="o">=</span> <span class="n">canvas</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">ys</span><span class="p">)</span> <span class="o">+</span> <span class="nb">max</span><span class="p">(</span><span class="n">ys</span><span class="p">))</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">scale</span> <span class="o">-</span> <span class="n">canvas</span> <span class="o">*</span> <span class="mf">0.015</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">mask</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">canvas</span><span class="p">,</span> <span class="n">canvas</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dr</span> <span class="o">=</span> <span class="n">ImageDraw</span><span class="o">.</span><span class="n">Draw</span><span class="p">(</span><span class="n">mask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">sp</span> <span class="ow">in</span> <span class="n">subpaths</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">dr</span><span class="o">.</span><span class="n">polygon</span><span class="p">([(</span><span class="n">p</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="n">scale</span> <span class="o">+</span> <span class="n">ox</span><span class="p">,</span> <span class="n">p</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">*</span><span class="n">scale</span> <span class="o">+</span> <span class="n">oy</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">sp</span><span class="p">],</span> <span class="n">fill</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># white -&gt; ice-blue vertical gradient inside the whale mask</span>
</span></span><span class="line"><span class="cl">    <span class="n">g1</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;RGBA&#34;</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">canvas</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">canvas</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">g1</span><span class="o">.</span><span class="n">putpixel</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="n">lerp</span><span class="p">((</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="p">(</span><span class="mi">190</span><span class="p">,</span> <span class="mi">224</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="n">y</span> <span class="o">/</span> <span class="n">canvas</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="mi">255</span><span class="p">,))</span>
</span></span><span class="line"><span class="cl">    <span class="n">grad</span> <span class="o">=</span> <span class="n">g1</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">canvas</span><span class="p">,</span> <span class="n">canvas</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">Image</span><span class="o">.</span><span class="n">composite</span><span class="p">(</span><span class="n">grad</span><span class="p">,</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;RGBA&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">canvas</span><span class="p">,</span> <span class="n">canvas</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span> <span class="n">mask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># background: diagonal navy -&gt; blue -&gt; teal</span>
</span></span><span class="line"><span class="cl">    <span class="n">bg</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;RGBA&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">R</span><span class="p">,</span> <span class="n">R</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">px</span> <span class="o">=</span> <span class="n">bg</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">c1</span><span class="p">,</span> <span class="n">c2</span><span class="p">,</span> <span class="n">c3</span> <span class="o">=</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">40</span><span class="p">),</span> <span class="p">(</span><span class="mi">18</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">124</span><span class="p">),</span> <span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">118</span><span class="p">,</span> <span class="mi">168</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">R</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">R</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">R</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">c</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">c1</span><span class="p">,</span> <span class="n">c2</span><span class="p">,</span> <span class="n">t</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="k">if</span> <span class="n">t</span> <span class="o">&lt;</span> <span class="mf">0.5</span> <span class="k">else</span> <span class="n">lerp</span><span class="p">(</span><span class="n">c2</span><span class="p">,</span> <span class="n">c3</span><span class="p">,</span> <span class="p">(</span><span class="n">t</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">px</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="p">(</span><span class="mi">255</span><span class="p">,)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># rounded-rect mask</span>
</span></span><span class="line"><span class="cl">    <span class="n">mask</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">R</span><span class="p">,</span> <span class="n">R</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ImageDraw</span><span class="o">.</span><span class="n">Draw</span><span class="p">(</span><span class="n">mask</span><span class="p">)</span><span class="o">.</span><span class="n">rounded_rectangle</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">R</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">R</span> <span class="o">-</span> <span class="mi">1</span><span class="p">],</span> <span class="n">radius</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">R</span> <span class="o">*</span> <span class="mf">0.2237</span><span class="p">),</span> <span class="n">fill</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">bg</span><span class="o">.</span><span class="n">putalpha</span><span class="p">(</span><span class="n">mask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># whale: 4x supersample -&gt; glow -&gt; composite</span>
</span></span><span class="line"><span class="cl">    <span class="n">whale_small</span> <span class="o">=</span> <span class="n">build_official_whale</span><span class="p">(</span><span class="n">S</span><span class="p">)</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">R</span><span class="p">,</span> <span class="n">R</span><span class="p">),</span> <span class="n">Image</span><span class="o">.</span><span class="n">LANCZOS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">glow</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;RGBA&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">S</span><span class="p">,</span> <span class="n">S</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">ImageDraw</span><span class="o">.</span><span class="n">Draw</span><span class="p">(</span><span class="n">glow</span><span class="p">)</span><span class="o">.</span><span class="n">ellipse</span><span class="p">([</span><span class="n">S</span><span class="o">*</span><span class="mf">0.15</span><span class="p">,</span> <span class="n">S</span><span class="o">*</span><span class="mf">0.20</span><span class="p">,</span> <span class="n">S</span><span class="o">*</span><span class="mf">0.85</span><span class="p">,</span> <span class="n">S</span><span class="o">*</span><span class="mf">0.85</span><span class="p">],</span> <span class="n">fill</span><span class="o">=</span><span class="p">(</span><span class="mi">70</span><span class="p">,</span> <span class="mi">190</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">150</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">glow</span> <span class="o">=</span> <span class="n">glow</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">ImageFilter</span><span class="o">.</span><span class="n">GaussianBlur</span><span class="p">(</span><span class="n">S</span> <span class="o">*</span> <span class="mf">0.09</span><span class="p">))</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">R</span><span class="p">,</span> <span class="n">R</span><span class="p">),</span> <span class="n">Image</span><span class="o">.</span><span class="n">LANCZOS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">glow</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">composite</span><span class="p">(</span><span class="n">glow</span><span class="p">,</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;RGBA&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">R</span><span class="p">,</span> <span class="n">R</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span> <span class="n">whale_small</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">3</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">bg</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">alpha_composite</span><span class="p">(</span><span class="n">bg</span><span class="p">,</span> <span class="n">glow</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">bg</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">alpha_composite</span><span class="p">(</span><span class="n">bg</span><span class="p">,</span> <span class="n">whale_small</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">bg</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUT</span><span class="p">,</span> <span class="s2">&#34;icon_1024.png&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># ---- Windows .ico ----</span>
</span></span><span class="line"><span class="cl">    <span class="n">bg</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUT</span><span class="p">,</span> <span class="s2">&#34;AppIcon.ico&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">sizes</span><span class="o">=</span><span class="p">[(</span><span class="mi">16</span><span class="p">,</span> <span class="mi">16</span><span class="p">),</span> <span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="mi">32</span><span class="p">),</span> <span class="p">(</span><span class="mi">48</span><span class="p">,</span> <span class="mi">48</span><span class="p">),</span> <span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">64</span><span class="p">),</span> <span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">128</span><span class="p">),</span> <span class="p">(</span><span class="mi">256</span><span class="p">,</span> <span class="mi">256</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># ---- macOS .icns（仅 macOS 有 iconutil）----</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s2">&#34;darwin&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">iconset</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUT</span><span class="p">,</span> <span class="s2">&#34;AppIcon.iconset&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">iconset</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">size</span> <span class="ow">in</span> <span class="p">{</span><span class="s2">&#34;icon_16x16.png&#34;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span> <span class="s2">&#34;icon_16x16@2x.png&#34;</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span> <span class="s2">&#34;icon_32x32.png&#34;</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="s2">&#34;icon_32x32@2x.png&#34;</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span> <span class="s2">&#34;icon_128x128.png&#34;</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span> <span class="s2">&#34;icon_128x128@2x.png&#34;</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="s2">&#34;icon_256x256.png&#34;</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span> <span class="s2">&#34;icon_256x256@2x.png&#34;</span><span class="p">:</span> <span class="mi">512</span><span class="p">,</span> <span class="s2">&#34;icon_512x512.png&#34;</span><span class="p">:</span> <span class="mi">512</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="s2">&#34;icon_512x512@2x.png&#34;</span><span class="p">:</span> <span class="mi">1024</span><span class="p">}</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="n">bg</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">size</span><span class="p">,</span> <span class="n">size</span><span class="p">),</span> <span class="n">Image</span><span class="o">.</span><span class="n">LANCZOS</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">iconset</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">([</span><span class="s2">&#34;iconutil&#34;</span><span class="p">,</span> <span class="s2">&#34;-c&#34;</span><span class="p">,</span> <span class="s2">&#34;icns&#34;</span><span class="p">,</span> <span class="n">iconset</span><span class="p">,</span> <span class="s2">&#34;-o&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUT</span><span class="p">,</span> <span class="s2">&#34;AppIcon.icns&#34;</span><span class="p">)],</span> <span class="n">check</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># self-check</span>
</span></span><span class="line"><span class="cl">    <span class="n">px</span> <span class="o">=</span> <span class="n">bg</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">bright</span> <span class="o">=</span> <span class="p">[(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">R</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">R</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="k">if</span> <span class="n">px</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">][</span><span class="mi">3</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">200</span> <span class="ow">and</span> <span class="n">px</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">200</span> <span class="ow">and</span> <span class="n">px</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">200</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">bright</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">30000</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;whale too small: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">bright</span><span class="p">)</span><span class="si">}</span><span class="s2"> bright pixels&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">xs</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">bright</span><span class="p">];</span> <span class="n">ys</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">bright</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;bright pixels: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">bright</span><span class="p">)</span><span class="si">}</span><span class="s2">  whale bbox: x[</span><span class="si">{</span><span class="nb">min</span><span class="p">(</span><span class="n">xs</span><span class="p">)</span><span class="si">}</span><span class="s2">..</span><span class="si">{</span><span class="nb">max</span><span class="p">(</span><span class="n">xs</span><span class="p">)</span><span class="si">}</span><span class="s2">] y[</span><span class="si">{</span><span class="nb">min</span><span class="p">(</span><span class="n">ys</span><span class="p">)</span><span class="si">}</span><span class="s2">..</span><span class="si">{</span><span class="nb">max</span><span class="p">(</span><span class="n">ys</span><span class="p">)</span><span class="si">}</span><span class="s2">]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;OK icon_1024.png + AppIcon.ico&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="s2">&#34; + AppIcon.icns&#34;</span> <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s2">&#34;darwin&#34;</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">main</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>运行：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 make_icon.py        <span class="c1"># Windows 用 python make_icon.py</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>产物：</p>
<table>
	<thead>
			<tr>
					<th>文件</th>
					<th>用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>icon_1024.png</code></td>
					<td>1024px 预览图</td>
			</tr>
			<tr>
					<td><code>AppIcon.icns</code></td>
					<td>macOS 应用图标（仅 macOS 生成）</td>
			</tr>
			<tr>
					<td><code>AppIcon.ico</code></td>
					<td>Windows 快捷方式/程序图标（16~256 共 6 档尺寸）</td>
			</tr>
	</tbody>
</table>
<p>脚本要点：内置了一个极简 SVG path 解析器（支持 M/L/C/S/Q/H/V/Z，官方 favicon 只用到了 M/C/Z），把鲸鱼路径 4 倍超采样渲染成白色渐变鲸鱼，再叠光晕合成到深蓝渐变圆角底板上；末尾自动做像素自检，鲸鱼没画出来会直接 assert 报错。脚本按平台自动决定生成 <code>.icns</code> 还是只出 <code>.ico</code>。</p>
<h2 id="三macos编译原生-app">三、macOS：编译原生 <code>.app</code></h2>
<h3 id="31-依赖">3.1 依赖</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">xcode-select --install   <span class="c1"># 提供 swiftc / iconutil，装过可跳过</span>
</span></span><span class="line"><span class="cl">python3 -m pip install pillow
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="32-应用源码-mainswift">3.2 应用源码 <code>main.swift</code></h3>
<p>一个完整的原生应用，不到 200 行：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">import</span> <span class="nc">Cocoa</span>
</span></span><span class="line"><span class="cl"><span class="kd">import</span> <span class="nc">WebKit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">final</span> <span class="kd">class</span> <span class="nc">AppDelegate</span><span class="p">:</span> <span class="n">NSObject</span><span class="p">,</span> <span class="n">NSApplicationDelegate</span><span class="p">,</span> <span class="n">WKNavigationDelegate</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">var</span> <span class="nv">window</span><span class="p">:</span> <span class="n">NSWindow</span><span class="p">!</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">var</span> <span class="nv">webView</span><span class="p">:</span> <span class="n">WKWebView</span><span class="p">!</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">var</span> <span class="nv">errorView</span><span class="p">:</span> <span class="n">NSView</span><span class="p">?</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">var</span> <span class="nv">retryTimer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">?</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">let</span> <span class="nv">homeURL</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="s">&#34;http://127.0.0.1:3080&#34;</span><span class="p">)</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">applicationDidFinishLaunching</span><span class="p">(</span><span class="kc">_</span> <span class="n">notification</span><span class="p">:</span> <span class="n">Notification</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">buildMenu</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">rect</span> <span class="p">=</span> <span class="n">NSRect</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">width</span><span class="p">:</span> <span class="mi">1280</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mi">840</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span> <span class="p">=</span> <span class="n">NSWindow</span><span class="p">(</span><span class="n">contentRect</span><span class="p">:</span> <span class="n">rect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">styleMask</span><span class="p">:</span> <span class="p">[.</span><span class="n">titled</span><span class="p">,</span> <span class="p">.</span><span class="n">closable</span><span class="p">,</span> <span class="p">.</span><span class="n">miniaturizable</span><span class="p">,</span> <span class="p">.</span><span class="n">resizable</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                          <span class="n">backing</span><span class="p">:</span> <span class="p">.</span><span class="n">buffered</span><span class="p">,</span> <span class="k">defer</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="s">&#34;DSH Web&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">minSize</span> <span class="p">=</span> <span class="n">NSSize</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mi">800</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mi">560</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">setFrameAutosaveName</span><span class="p">(</span><span class="s">&#34;DSHWebWindow&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">center</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">config</span> <span class="p">=</span> <span class="n">WKWebViewConfiguration</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">webView</span> <span class="p">=</span> <span class="n">WKWebView</span><span class="p">(</span><span class="n">frame</span><span class="p">:</span> <span class="n">rect</span><span class="p">,</span> <span class="n">configuration</span><span class="p">:</span> <span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">webView</span><span class="p">.</span><span class="n">navigationDelegate</span> <span class="p">=</span> <span class="kc">self</span>
</span></span><span class="line"><span class="cl">        <span class="n">webView</span><span class="p">.</span><span class="n">allowsBackForwardNavigationGestures</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">contentView</span> <span class="p">=</span> <span class="n">webView</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">window</span><span class="p">.</span><span class="n">makeKeyAndOrderFront</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">NSApp</span><span class="p">.</span><span class="n">activate</span><span class="p">(</span><span class="n">ignoringOtherApps</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">loadHome</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">applicationShouldTerminateAfterLastWindowClosed</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="n">NSApplication</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Bool</span> <span class="p">{</span> <span class="kc">true</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Loading</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">loadHome</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">hideError</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nv">req</span> <span class="p">=</span> <span class="n">URLRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">homeURL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">.</span><span class="n">cachePolicy</span> <span class="p">=</span> <span class="p">.</span><span class="n">reloadIgnoringLocalCacheData</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">.</span><span class="n">timeoutInterval</span> <span class="p">=</span> <span class="mi">8</span>
</span></span><span class="line"><span class="cl">        <span class="n">webView</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - WKNavigationDelegate</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">webView</span><span class="p">(</span><span class="kc">_</span> <span class="n">webView</span><span class="p">:</span> <span class="n">WKWebView</span><span class="p">,</span> <span class="n">didFinish</span> <span class="n">navigation</span><span class="p">:</span> <span class="n">WKNavigation</span><span class="p">!)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">stopAutoRetry</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">hideError</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">webView</span><span class="p">(</span><span class="kc">_</span> <span class="n">webView</span><span class="p">:</span> <span class="n">WKWebView</span><span class="p">,</span> <span class="n">didFail</span> <span class="n">navigation</span><span class="p">:</span> <span class="n">WKNavigation</span><span class="p">!,</span> <span class="n">withError</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">showError</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">webView</span><span class="p">(</span><span class="kc">_</span> <span class="n">webView</span><span class="p">:</span> <span class="n">WKWebView</span><span class="p">,</span> <span class="n">didFailProvisionalNavigation</span> <span class="n">navigation</span><span class="p">:</span> <span class="n">WKNavigation</span><span class="p">!,</span> <span class="n">withError</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">showError</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Error screen (auto-retry while server is not up yet)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">showError</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">errorView</span> <span class="p">==</span> <span class="kc">nil</span> <span class="p">{</span> <span class="n">buildErrorView</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">errorView</span><span class="p">?.</span><span class="n">isHidden</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">        <span class="n">startAutoRetry</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">hideError</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">errorView</span><span class="p">?.</span><span class="n">isHidden</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">startAutoRetry</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">guard</span> <span class="n">retryTimer</span> <span class="p">==</span> <span class="kc">nil</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">retryTimer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">.</span><span class="n">scheduledTimer</span><span class="p">(</span><span class="n">withTimeInterval</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="kr">weak</span> <span class="kc">self</span><span class="p">]</span> <span class="kc">_</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">            <span class="kc">self</span><span class="p">?.</span><span class="n">loadHome</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">stopAutoRetry</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">retryTimer</span><span class="p">?.</span><span class="n">invalidate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">retryTimer</span> <span class="p">=</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">buildErrorView</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">guard</span> <span class="kd">let</span> <span class="nv">content</span> <span class="p">=</span> <span class="n">window</span><span class="p">.</span><span class="n">contentView</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">v</span> <span class="p">=</span> <span class="n">NSView</span><span class="p">(</span><span class="n">frame</span><span class="p">:</span> <span class="n">content</span><span class="p">.</span><span class="n">bounds</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">v</span><span class="p">.</span><span class="n">autoresizingMask</span> <span class="p">=</span> <span class="p">[.</span><span class="n">width</span><span class="p">,</span> <span class="p">.</span><span class="n">height</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">v</span><span class="p">.</span><span class="n">wantsLayer</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="n">v</span><span class="p">.</span><span class="n">layer</span><span class="p">?.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="n">NSColor</span><span class="p">(</span><span class="n">calibratedRed</span><span class="p">:</span> <span class="mf">0.05</span><span class="p">,</span> <span class="n">green</span><span class="p">:</span> <span class="mf">0.08</span><span class="p">,</span> <span class="n">blue</span><span class="p">:</span> <span class="mf">0.17</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mi">1</span><span class="p">).</span><span class="n">cgColor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">title</span> <span class="p">=</span> <span class="n">NSTextField</span><span class="p">(</span><span class="n">labelWithString</span><span class="p">:</span> <span class="s">&#34;无法连接到 DSH Web&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="p">.</span><span class="n">font</span> <span class="p">=</span> <span class="p">.</span><span class="n">systemFont</span><span class="p">(</span><span class="n">ofSize</span><span class="p">:</span> <span class="mi">22</span><span class="p">,</span> <span class="n">weight</span><span class="p">:</span> <span class="p">.</span><span class="n">semibold</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="p">.</span><span class="n">textColor</span> <span class="p">=</span> <span class="p">.</span><span class="n">white</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">sub</span> <span class="p">=</span> <span class="n">NSTextField</span><span class="p">(</span><span class="n">labelWithString</span><span class="p">:</span> <span class="s">&#34;请先启动 dsh 服务（127.0.0.1:3080），正在自动重试，也可以点击下方按钮。&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">sub</span><span class="p">.</span><span class="n">font</span> <span class="p">=</span> <span class="p">.</span><span class="n">systemFont</span><span class="p">(</span><span class="n">ofSize</span><span class="p">:</span> <span class="mi">13</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">sub</span><span class="p">.</span><span class="n">textColor</span> <span class="p">=</span> <span class="n">NSColor</span><span class="p">(</span><span class="n">white</span><span class="p">:</span> <span class="mf">0.72</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">btn</span> <span class="p">=</span> <span class="n">NSButton</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">&#34;立即重试&#34;</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="kc">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">retryTapped</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">btn</span><span class="p">.</span><span class="n">bezelStyle</span> <span class="p">=</span> <span class="p">.</span><span class="n">rounded</span>
</span></span><span class="line"><span class="cl">        <span class="n">btn</span><span class="p">.</span><span class="n">controlSize</span> <span class="p">=</span> <span class="p">.</span><span class="n">large</span>
</span></span><span class="line"><span class="cl">        <span class="n">btn</span><span class="p">.</span><span class="n">keyEquivalent</span> <span class="p">=</span> <span class="s">&#34;</span><span class="se">\r</span><span class="s">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">stack</span> <span class="p">=</span> <span class="n">NSStackView</span><span class="p">(</span><span class="n">views</span><span class="p">:</span> <span class="p">[</span><span class="n">title</span><span class="p">,</span> <span class="n">sub</span><span class="p">,</span> <span class="n">btn</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">.</span><span class="n">orientation</span> <span class="p">=</span> <span class="p">.</span><span class="n">vertical</span>
</span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">.</span><span class="n">alignment</span> <span class="p">=</span> <span class="p">.</span><span class="n">centerX</span>
</span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">.</span><span class="n">spacing</span> <span class="p">=</span> <span class="mi">14</span>
</span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">        <span class="n">v</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">stack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">NSLayoutConstraint</span><span class="p">.</span><span class="n">activate</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="n">stack</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span> <span class="n">v</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">stack</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span> <span class="n">v</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">positioned</span><span class="p">:</span> <span class="p">.</span><span class="n">above</span><span class="p">,</span> <span class="n">relativeTo</span><span class="p">:</span> <span class="n">webView</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">errorView</span> <span class="p">=</span> <span class="n">v</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">retryTapped</span><span class="p">()</span> <span class="p">{</span> <span class="n">loadHome</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">reloadHome</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span> <span class="n">loadHome</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">goBack</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span> <span class="n">webView</span><span class="p">.</span><span class="n">goBack</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">goForward</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span> <span class="n">webView</span><span class="p">.</span><span class="n">goForward</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Menus (Cmd+Q / Cmd+R / Cmd+W / copy-paste)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">buildMenu</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">mainMenu</span> <span class="p">=</span> <span class="n">NSMenu</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">appItem</span> <span class="p">=</span> <span class="n">NSMenuItem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">mainMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">appItem</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">appMenu</span> <span class="p">=</span> <span class="n">NSMenu</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">appMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;关于 DSH Web&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSApplication</span><span class="p">.</span><span class="n">orderFrontStandardAboutPanel</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">appMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(.</span><span class="n">separator</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">appMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;隐藏 DSH Web&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSApplication</span><span class="p">.</span><span class="n">hide</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;h&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">appMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(.</span><span class="n">separator</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">appMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;退出 DSH Web&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSApplication</span><span class="p">.</span><span class="n">terminate</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;q&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">appItem</span><span class="p">.</span><span class="n">submenu</span> <span class="p">=</span> <span class="n">appMenu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">navItem</span> <span class="p">=</span> <span class="n">NSMenuItem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">mainMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">navItem</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">navMenu</span> <span class="p">=</span> <span class="n">NSMenu</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">&#34;导航&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">navMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;重新加载&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">reloadHome</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;r&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">navMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;后退&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">goBack</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;[&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">navMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;前进&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">goForward</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">navMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(.</span><span class="n">separator</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">navMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;关闭窗口&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSWindow</span><span class="p">.</span><span class="n">performClose</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;w&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">navItem</span><span class="p">.</span><span class="n">submenu</span> <span class="p">=</span> <span class="n">navMenu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">editItem</span> <span class="p">=</span> <span class="n">NSMenuItem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">mainMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">editItem</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">editMenu</span> <span class="p">=</span> <span class="n">NSMenu</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">&#34;编辑&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;撤销&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">Selector</span><span class="p">((</span><span class="s">&#34;undo:&#34;</span><span class="p">)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;z&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;重做&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">Selector</span><span class="p">((</span><span class="s">&#34;redo:&#34;</span><span class="p">)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;Z&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(.</span><span class="n">separator</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;剪切&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;拷贝&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;c&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;粘贴&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">paste</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;v&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;全选&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">selectAll</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">editItem</span><span class="p">.</span><span class="n">submenu</span> <span class="p">=</span> <span class="n">editMenu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">winItem</span> <span class="p">=</span> <span class="n">NSMenuItem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">mainMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">winItem</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">winMenu</span> <span class="p">=</span> <span class="n">NSMenu</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">&#34;窗口&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">winMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;最小化&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSWindow</span><span class="p">.</span><span class="n">performMiniaturize</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;m&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">winMenu</span><span class="p">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;缩放&#34;</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">NSWindow</span><span class="p">.</span><span class="n">performZoom</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">keyEquivalent</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">winItem</span><span class="p">.</span><span class="n">submenu</span> <span class="p">=</span> <span class="n">winMenu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">NSApp</span><span class="p">.</span><span class="n">mainMenu</span> <span class="p">=</span> <span class="n">mainMenu</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">app</span> <span class="p">=</span> <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">delegate</span> <span class="p">=</span> <span class="n">AppDelegate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="n">delegate</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">setActivationPolicy</span><span class="p">(.</span><span class="n">regular</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>功能：独立原生窗口（记住大小位置）、dsh 服务没起时显示提示页并<strong>每 4 秒自动重试</strong>（先开应用再启服务也行）、完整菜单快捷键（Cmd+R 重载、Cmd+[/] 前进后退、Cmd+W 关窗、Cmd+Q 退出、复制粘贴）。</p>
<h3 id="33-infoplist">3.3 <code>Info.plist</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE plist PUBLIC &#34;-//Apple//DTD PLIST 1.0//EN&#34; &#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd&#34;&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">&#34;1.0&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dict&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleName<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>DSH Web<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleDisplayName<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>DSH Web<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleIdentifier<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>com.deepseek.dsh-web<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleExecutable<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>DSHWeb<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundlePackageType<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>APPL<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleIconFile<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>AppIcon<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleIconName<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>AppIcon<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleShortVersionString<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>1.0<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>CFBundleVersion<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>1<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>LSMinimumSystemVersion<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>12.0<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>LSApplicationCategoryType<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;string&gt;</span>public.app-category.developer-tools<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>LSMultipleInstancesProhibited<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;true/&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>NSHighResolutionCapable<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;true/&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;key&gt;</span>NSAppTransportSecurity<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;dict&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&lt;key&gt;</span>NSAllowsLocalNetworking<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&lt;true/&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&lt;/dict&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dict&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/plist&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>⚠️ <code>NSAllowsLocalNetworking</code> 必须有，否则 ATS 可能拦截 <code>http://127.0.0.1</code> 的明文请求，窗口一直空白。</p>
</blockquote>
<h3 id="34-一键构建-buildsh">3.4 一键构建 <code>build.sh</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># Build &#34;DSH Web.app&#34;: native WKWebView wrapper for http://127.0.0.1:3080</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="nv">$0</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">APP</span><span class="o">=</span><span class="s2">&#34;DSH Web.app&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;==&gt; 生成图标&#34;</span>
</span></span><span class="line"><span class="cl">python3 make_icon.py
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;==&gt; 编译 Swift&#34;</span>
</span></span><span class="line"><span class="cl">swiftc -O -o DSHWeb main.swift -framework Cocoa -framework WebKit
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;==&gt; 组装 bundle&#34;</span>
</span></span><span class="line"><span class="cl">rm -rf <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">/Contents/MacOS&#34;</span> <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">/Contents/Resources&#34;</span>
</span></span><span class="line"><span class="cl">cp DSHWeb <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">/Contents/MacOS/DSHWeb&#34;</span>
</span></span><span class="line"><span class="cl">cp Info.plist <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">/Contents/Info.plist&#34;</span>
</span></span><span class="line"><span class="cl">cp AppIcon.icns <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">/Contents/Resources/AppIcon.icns&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;==&gt; ad-hoc 签名&#34;</span>
</span></span><span class="line"><span class="cl">codesign --force --deep -s - <span class="s2">&#34;</span><span class="nv">$APP</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;==&gt; 完成: </span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span><span class="s2">/</span><span class="nv">$APP</span><span class="s2">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="35-构建安装验证">3.5 构建、安装、验证</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod +x build.sh
</span></span><span class="line"><span class="cl">./build.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 安装到用户应用目录（Launchpad / 聚焦搜索可见）</span>
</span></span><span class="line"><span class="cl">cp -R <span class="s2">&#34;DSH Web.app&#34;</span> ~/Applications/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 验证</span>
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> http://127.0.0.1:3080/   <span class="c1"># 期望 200</span>
</span></span><span class="line"><span class="cl">open ~/Applications/<span class="s2">&#34;DSH Web.app&#34;</span>
</span></span><span class="line"><span class="cl">pgrep -fl DSHWeb                                                  <span class="c1"># 有进程即成功</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="四windows两种方案">四、Windows：两种方案</h2>
<h3 id="方案-a推荐edgechrome---app-快捷方式">方案 A（推荐）：Edge/Chrome <code>--app</code> 快捷方式</h3>
<p>零编译依赖，效果接近原生应用：独立窗口、无地址栏、独立任务栏图标。</p>
<ol>
<li>按第二节生成 <code>AppIcon.ico</code>（Windows 上直接 <code>python make_icon.py</code>）。</li>
<li>保存下面脚本为 <code>make_shortcut.ps1</code>，与 <code>AppIcon.ico</code> 放同一目录：</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># make_shortcut.ps1 — 创建 DSH Web 桌面快捷方式（独立应用窗口）</span>
</span></span><span class="line"><span class="cl"><span class="nv">$edge86</span>  <span class="p">=</span> <span class="s2">&#34;C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$edge64</span>  <span class="p">=</span> <span class="s2">&#34;C:\Program Files\Microsoft\Edge\Application\msedge.exe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$chrome</span>  <span class="p">=</span> <span class="s2">&#34;C:\Program Files\Google\Chrome\Application\chrome.exe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$browser</span> <span class="p">=</span> <span class="k">if</span> <span class="p">(</span><span class="nb">Test-Path</span> <span class="nv">$edge86</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$edge86</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">           <span class="k">elseif</span> <span class="p">(</span><span class="nb">Test-Path</span> <span class="nv">$edge64</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$edge64</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">           <span class="k">elseif</span> <span class="p">(</span><span class="nb">Test-Path</span> <span class="nv">$chrome</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$chrome</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">           <span class="k">else</span> <span class="p">{</span> <span class="k">throw</span> <span class="s2">&#34;未找到 Edge 或 Chrome&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$ws</span> <span class="p">=</span> <span class="nb">New-Object</span> <span class="n">-ComObject</span> <span class="n">WScript</span><span class="p">.</span><span class="py">Shell</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># 桌面快捷方式</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span> <span class="p">=</span> <span class="nv">$ws</span><span class="p">.</span><span class="py">CreateShortcut</span><span class="p">(</span><span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\Desktop\DSH Web.lnk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">TargetPath</span>       <span class="p">=</span> <span class="nv">$browser</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">Arguments</span>        <span class="p">=</span> <span class="s2">&#34;--app=http://127.0.0.1:3080&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">IconLocation</span>     <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$PSScriptRoot</span><span class="s2">\AppIcon.ico&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">WorkingDirectory</span> <span class="p">=</span> <span class="nv">$PSScriptRoot</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">Description</span>      <span class="p">=</span> <span class="s2">&#34;DeepSeek Harness Web&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$sc</span><span class="p">.</span><span class="py">Save</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># 开始菜单（可选，便于搜索）</span>
</span></span><span class="line"><span class="cl"><span class="nv">$start</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$env:APPDATA</span><span class="s2">\Microsoft\Windows\Start Menu\Programs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Copy-Item</span> <span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\Desktop\DSH Web.lnk&#34;</span> <span class="s2">&#34;</span><span class="nv">$start</span><span class="s2">\DSH Web.lnk&#34;</span> <span class="n">-Force</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;OK: 已创建快捷方式（浏览器: </span><span class="nv">$browser</span><span class="s2">）&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ol start="3">
<li>运行（如遇执行策略限制）：</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">powershell</span> <span class="n">-ExecutionPolicy</span> <span class="n">Bypass</span> <span class="o">-File</span> <span class="n">make_shortcut</span><span class="p">.</span><span class="py">ps1</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ol start="4">
<li>双击桌面的 <strong>DSH Web</strong> 即可，可右键快捷方式 →「固定到任务栏」。</li>
</ol>
<h3 id="方案-b进阶webview2-原生窗口">方案 B（进阶）：WebView2 原生窗口</h3>
<p>需要 <a href="https://dotnet.microsoft.com/download">.NET SDK</a>（Win11 自带 WebView2 运行时）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">dotnet</span> <span class="n">new</span> <span class="n">winforms</span> <span class="n">-n</span> <span class="n">DshWeb</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd </span><span class="n">DshWeb</span>
</span></span><span class="line"><span class="cl"><span class="n">dotnet</span> <span class="n">add</span> <span class="n">package</span> <span class="n">Microsoft</span><span class="p">.</span><span class="py">Web</span><span class="p">.</span><span class="py">WebView2</span>
</span></span><span class="line"><span class="cl"><span class="nb">copy </span><span class="p">..\</span><span class="n">AppIcon</span><span class="p">.</span><span class="py">ico</span> <span class="p">.</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>把 <code>Program.cs</code> 改成：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Web.WebView2.WinForms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Windows.Forms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">form</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Form</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Text</span> <span class="p">=</span> <span class="s">&#34;DSH Web&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Width</span> <span class="p">=</span> <span class="m">1280</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Height</span> <span class="p">=</span> <span class="m">840</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">StartPosition</span> <span class="p">=</span> <span class="n">FormStartPosition</span><span class="p">.</span><span class="n">CenterScreen</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Icon</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Drawing</span><span class="p">.</span><span class="n">Icon</span><span class="p">(</span><span class="s">&#34;AppIcon.ico&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">web</span> <span class="p">=</span> <span class="k">new</span> <span class="n">WebView2</span> <span class="p">{</span> <span class="n">Dock</span> <span class="p">=</span> <span class="n">DockStyle</span><span class="p">.</span><span class="n">Fill</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="n">form</span><span class="p">.</span><span class="n">Controls</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">web</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">form</span><span class="p">.</span><span class="n">Shown</span> <span class="p">+=</span> <span class="kd">async</span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">web</span><span class="p">.</span><span class="n">EnsureCoreWebView2Async</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">web</span><span class="p">.</span><span class="n">CoreWebView2</span><span class="p">.</span><span class="n">Navigate</span><span class="p">(</span><span class="s">&#34;http://127.0.0.1:3080&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="n">Application</span><span class="p">.</span><span class="n">Run</span><span class="p">(</span><span class="n">form</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>发布为单文件：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">dotnet</span> <span class="n">publish</span> <span class="n">-c</span> <span class="n">Release</span> <span class="n">-r</span> <span class="nb">win-x64</span> <span class="p">-</span><span class="n">-self-contained</span> <span class="n">false</span> <span class="n">-p:PublishSingleFile</span><span class="p">=</span><span class="n">true</span>
</span></span><span class="line"><span class="cl"><span class="c"># 产物：bin\Release\net8.0-windows\win-x64\publish\DshWeb.exe</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="五验证清单">五、验证清单</h2>
<table>
	<thead>
			<tr>
					<th>检查项</th>
					<th>方法</th>
					<th>期望</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>dsh 服务在线</td>
					<td>访问 <code>http://127.0.0.1:3080/</code></td>
					<td>HTTP 200</td>
			</tr>
			<tr>
					<td>图标正常</td>
					<td>看 Dock / 桌面快捷方式</td>
					<td>深蓝渐变底 + 白色鲸鱼</td>
			</tr>
			<tr>
					<td>应用启动</td>
					<td>双击图标</td>
					<td>独立窗口打开 DSH Web</td>
			</tr>
			<tr>
					<td>服务未启动</td>
					<td>先开应用再启服务</td>
					<td>提示页自动重试并成功加载</td>
			</tr>
	</tbody>
</table>
<h2 id="六自定义">六、自定义</h2>
<ul>
<li><strong>换端口/地址</strong>：macOS 改 <code>main.swift</code> 里的 <code>homeURL</code>；Windows 改快捷方式 <code>--app=</code> 参数，改完分别重跑 <code>build.sh</code> / <code>make_shortcut.ps1</code></li>
<li><strong>图标鲸鱼大小</strong>：<code>make_icon.py</code> 里 <code>build_official_whale(S, span=0.62)</code> 的 <code>span</code>（0~1）</li>
<li><strong>背景配色</strong>：<code>make_icon.py</code> 里 <code>c1, c2, c3</code> 三个 RGB</li>
<li><strong>窗口默认尺寸</strong>：<code>main.swift</code> 里 <code>NSRect(... width: 1280, height: 840)</code></li>
</ul>
<h2 id="七常见问题">七、常见问题</h2>
<ul>
<li><strong>macOS 提示&quot;无法打开，因为无法验证开发者&quot;</strong>：本机编译 + ad-hoc 签名一般不会有；如果是从别的机器拷贝过来的，先 <code>xattr -dr com.apple.quarantine &quot;DSH Web.app&quot;</code>。</li>
<li><strong>窗口空白/打不开</strong>：确认 <code>dsh web</code> 已在运行且端口是 3080；macOS 版会自动重试，Windows 快捷方式需手动 Ctrl+R 刷新。</li>
<li><strong><code>favicon.svg</code> 找不到</strong>：DSH 版本目录结构可能有差异，用 <code>find &quot;$(npm root -g)/@deepseek-ai&quot; -name favicon.svg</code> 定位。</li>
</ul>
<hr>
<p>相比<a href="/posts/dsh-macos-launcher-app/">上一版 shell 脚本启动器</a>，这版是真正的独立应用窗口，快捷键、菜单栏、Dock 图标一应俱全；图标也从手搓 SVG 渲染换成了直接复用官方鲸鱼——教训是：<strong>先翻翻项目自带的资源，比自己重画省事得多</strong>。</p>
]]></content:encoded></item></channel></rss>