{"id":10021366,"date":"2026-06-01T02:43:45","date_gmt":"2026-05-31T18:43:45","guid":{"rendered":"https:\/\/itmax.cn\/?p=10021366"},"modified":"2026-06-01T02:43:45","modified_gmt":"2026-05-31T18:43:45","slug":"%e7%94%a8%e5%ae%98%e6%96%b9%e6%a8%a1%e6%9d%bf%e7%90%86%e8%a7%a3-decky-%e6%8f%92%e4%bb%b6%ef%bc%9a%e4%b8%80%e6%ac%a1%e4%bb%8e%e6%a8%a1%e6%9d%bf%e5%88%b0%e6%9e%b6%e6%9e%84%e7%9a%84%e9%80%9f%e8%a7%88","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10021366","title":{"rendered":"\u7528\u5b98\u65b9\u6a21\u677f\u7406\u89e3 Decky \u63d2\u4ef6\uff1a\u4e00\u6b21\u4ece\u6a21\u677f\u5230\u67b6\u6784\u7684\u901f\u89c8"},"content":{"rendered":"<blockquote><p>\u9762\u5411\u7b2c\u4e00\u6b21\u63a5\u89e6 Steam Deck \u63d2\u4ef6\u5f00\u53d1\u7684\u8bfb\u8005\u3002\u672c\u6587\u4ee5\u5b98\u65b9\u4ed3\u5e93 <a href=\"https:\/\/link.segmentfault.com\/?enc=8Uq41J8bbUViE4Nc4XO2Pw%3D%3D.be7xwiHYditFTv4rZUAUJXbsRHOg2PAMSigjT5w4sy%2FG4oj3ksq5Mr5WGIhf0gQDdSJulUxwwgrwMQi%2B0dHjPA%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">decky-plugin-template<\/a> \u4e3a\u7d22\u5f15\uff0c\u9010\u4e2a\u6587\u4ef6\u8bb2\u6e05\u5b83\u4eec\u4e3a\u4ec0\u4e48\u5b58\u5728\u3001\u5982\u4f55\u534f\u4f5c\uff0c\u5e76\u7ed9\u51fa\u6a21\u677f\u4e4b\u5916\u3001\u4e0a\u7ebf\u524d\u5fc5\u9047\u7684\u51e0\u4e2a\u5751\u3002<\/p><\/blockquote>\n<h2>TL;DR<\/h2>\n<ul>\n<li>\u4e00\u4e2a Decky \u63d2\u4ef6 = <strong>Steam CEF \u91cc\u7684 React \u7ec4\u4ef6<\/strong> + <strong>SteamOS \u4e0a\u7684 Python \u8fdb\u7a0b<\/strong>\uff0c\u4e24\u8005\u901a\u8fc7 Decky Loader \u63d0\u4f9b\u7684 <code>callable<\/code> \/ <code>emit<\/code> \u901a\u4fe1\uff1b<\/li>\n<li>\u524d\u7aef\u5165\u53e3\u56fa\u5b9a\u662f <code>src\/index.tsx<\/code> \u7684 <code>definePlugin(factory)<\/code>\uff0c\u540e\u7aef\u5165\u53e3\u56fa\u5b9a\u662f <code>main.py<\/code> \u91cc\u7684 <code>class Plugin<\/code>\uff0c\u65b9\u6cd5\u5fc5\u987b <code>async<\/code>\u3001\u53c2\u6570\u5fc5\u987b JSON-safe\uff1b<\/li>\n<li>\u8def\u5f84\u3001\u65e5\u5fd7\u3001\u914d\u7f6e\u5168\u90e8\u8d70 <code>decky.DECKY_PLUGIN_*<\/code> \u5e38\u91cf\uff0c<strong>\u4e0d\u8981<\/strong>\u786c\u7f16\u7801 <code>~\/homebrew\/...<\/code>\uff1b<\/li>\n<li>\u5f00\u53d1\u6d41\u7a0b\u9760 <code>.vscode\/<\/code> \u4e0b\u4e00\u5957 shell \u811a\u672c\u95ed\u73af\uff08build \u2192 rsync \u2192 \u91cd\u542f plugin_loader\uff09\uff0c\u4e0d\u7528 VS Code \u4e5f\u80fd\u590d\u523b\uff1b<\/li>\n<li>\u9700\u8981 root \u5c31\u52a0 <code>_root<\/code> flag\uff0c\u4f46\u80fd\u7528\u7cbe\u786e <code>sudo<\/code> \u89e3\u51b3\u7684\u5c31\u522b\u52a0\u2014\u2014\u5546\u5e97\u5ba1\u6838\u4e0d\u559c\u6b22\u3002<\/li>\n<\/ul>\n<h2>Decky \u63d2\u4ef6\u5230\u5e95\u662f\u4ec0\u4e48<\/h2>\n<p>Steam Deck \u7684\u6e38\u620f\u6a21\u5f0f\u5e76\u4e0d\u662f\u4e00\u4e2a\u72ec\u7acb UI\uff0c\u800c\u662f Steam \u5ba2\u6237\u7aef\u5185\u90e8\u7684\u4e00\u7ec4 CEF\uff08Chromium Embedded Framework\uff09\u9875\u9762\u3002Valve \u6ca1\u6709\u516c\u5f00\u6269\u5c55 API\uff0c\u4e8e\u662f\u793e\u533a\u505a\u4e86\u4e00\u4e2a\u6ce8\u5165\u6846\u67b6 \u2014\u2014 <a href=\"https:\/\/link.segmentfault.com\/?enc=siXcM%2F9kq%2BTyl2IKrminTw%3D%3D.fa%2FEo5xUMq5lp7sta74EkdW2Lt%2BvXc4h0EIURSq99HDtOHdICDTbUvMs%2FMZh0HiKND2Ozdr%2FaNNvHtdyfnyurA%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Decky Loader<\/a>\u3002\u5b83\u505a\u4e09\u4ef6\u4e8b\uff1a<\/p>\n<ol>\n<li><strong>\u6ce8\u5165 UI<\/strong>\uff1a\u5728\u6e38\u620f\u6a21\u5f0f\u7684\u4fa7\u8fb9\u680f\u6302\u4e00\u4e2a\u5165\u53e3\uff0c\u52a0\u8f7d\u7b2c\u4e09\u65b9\u63d2\u4ef6\u7684 React \u7ec4\u4ef6\uff1b<\/li>\n<li><strong>\u7ba1\u7406\u540e\u7aef<\/strong>\uff1a\u4e3a\u6bcf\u4e2a\u63d2\u4ef6\u62c9\u8d77\u4e00\u4e2a\u72ec\u7acb\u7684 Python \u8fdb\u7a0b\uff0c\u63d0\u4f9b\u751f\u547d\u5468\u671f\u94a9\u5b50\u4e0e RPC \u901a\u9053\uff1b<\/li>\n<li><strong>\u7ea6\u5b9a\u76ee\u5f55<\/strong>\uff1a\u7ed9\u6bcf\u4e2a\u63d2\u4ef6\u5212\u62e8\u56fa\u5b9a\u7684\u914d\u7f6e\u3001\u8fd0\u884c\u65f6\u3001\u65e5\u5fd7\u76ee\u5f55\uff0c\u5199\u5728 <code>decky.DECKY_PLUGIN_*<\/code> \u8fd9\u4e00\u7ec4\u73af\u5883\u53d8\u91cf\u91cc\u3002<\/li>\n<\/ol>\n<p>\u6240\u4ee5\u4e00\u4e2a Decky \u63d2\u4ef6\u7684\u6700\u5c0f\u8ba4\u77e5\u662f\uff1a<\/p>\n<blockquote><p>\u4e00\u4e2a\u8dd1\u5728 Steam \u5ba2\u6237\u7aef CEF \u91cc\u7684 React \u7ec4\u4ef6 + \u4e00\u4e2a\u8dd1\u5728 SteamOS \u4e0a\u7684 Python \u5c0f\u540e\u7aef\uff0c\u4e24\u8005\u901a\u8fc7 Decky Loader \u7684 RPC\/\u4e8b\u4ef6\u603b\u7ebf\u901a\u4fe1\u3002<\/p><\/blockquote>\n<p>\u4e0b\u6587\u4ee5\u5b98\u65b9\u6a21\u677f\u4f5c\u4e3a\u53c2\u7167\uff0c\u9010\u4e2a\u6587\u4ef6\u62c6\u5f00\u770b\u3002<\/p>\n<h2>\u6a21\u677f\u957f\u4ec0\u4e48\u6837<\/h2>\n<p>\u514b\u9686\u5b98\u65b9\u6a21\u677f\u540e\uff0c\u76ee\u5f55\u7ed3\u6784\u5927\u81f4\u5982\u4e0b\uff08\u65e0\u5173\u6587\u4ef6\u7701\u7565\uff09\uff1a<\/p>\n<pre><code>decky-plugin-template\/\n\u251c\u2500\u2500 plugin.json              # \u63d2\u4ef6\u5143\u6570\u636e\n\u251c\u2500\u2500 main.py                  # Python \u540e\u7aef\u5165\u53e3\uff0c\u7c7b\u540d\u5fc5\u987b\u662f Plugin\n\u251c\u2500\u2500 package.json             # \u524d\u7aef\u4f9d\u8d56\uff08pnpm \u7ba1\u7406\uff09\n\u251c\u2500\u2500 rollup.config.js         # \u4f7f\u7528 @decky\/rollup \u5b98\u65b9 preset\n\u251c\u2500\u2500 tsconfig.json\n\u251c\u2500\u2500 decky.pyi                # decky \u8fd0\u884c\u65f6\u6a21\u5757\u7684\u7c7b\u578b\u5b58\u6839\uff0c\u4f9b IDE \u7528\n\u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 index.tsx            # \u524d\u7aef\u5165\u53e3\uff0c\u9ed8\u8ba4\u5bfc\u51fa definePlugin(...)\n\u2502   \u2514\u2500\u2500 types.d.ts           # \u8ba9 TS \u8bc6\u522b *.png \/ *.svg \/ *.jpg \u8d44\u6e90\n\u251c\u2500\u2500 assets\/\n\u2502   \u2514\u2500\u2500 logo.png             # \u63d2\u4ef6\u56fe\u6807\/\u8d44\u6e90\n\u251c\u2500\u2500 defaults\/\n\u2502   \u2514\u2500\u2500 defaults.txt         # \u4f1a\u88ab\u6253\u8fdb\u63d2\u4ef6\u6839\u76ee\u5f55\u7684\u9759\u6001\u6587\u4ef6\n\u251c\u2500\u2500 py_modules\/              # \u7b2c\u4e09\u65b9 Python \u4f9d\u8d56\u653e\u8fd9\u91cc\uff08vendored\uff09\n\u251c\u2500\u2500 backend\/                 # \u539f\u751f\u540e\u7aef\uff08\u53ef\u9009\uff09\uff0c\u7528 Docker + Make \u6784\u5efa\n\u2502   \u251c\u2500\u2500 Dockerfile\n\u2502   \u251c\u2500\u2500 Makefile\n\u2502   \u251c\u2500\u2500 entrypoint.sh\n\u2502   \u2514\u2500\u2500 src\/main.c\n\u2514\u2500\u2500 .vscode\/                 # \u4e00\u952e setup\/build\/deploy \u4efb\u52a1\n    \u251c\u2500\u2500 tasks.json\n    \u251c\u2500\u2500 setup.sh\n    \u251c\u2500\u2500 build.sh\n    \u251c\u2500\u2500 config.sh\n    \u2514\u2500\u2500 defsettings.json<\/code><\/pre>\n<p>\u8fd9\u4e2a\u7ed3\u6784\u770b\u4f3c\u5f88\u591a\uff0c\u4f46\u6309\u804c\u8d23\u5176\u5b9e\u53ea\u6709\u56db\u7ec4\uff1a<strong>\u63d2\u4ef6\u58f0\u660e<\/strong>\uff08<code>plugin.json<\/code>\u3001<code>package.json<\/code>\uff09\u3001<strong>\u524d\u7aef\u8fd0\u884c\u65f6<\/strong>\uff08<code>src\/<\/code>\u3001<code>rollup.config.js<\/code>\u3001<code>tsconfig.json<\/code>\uff09\u3001<strong>\u540e\u7aef\u8fd0\u884c\u65f6<\/strong>\uff08<code>main.py<\/code>\u3001<code>py_modules\/<\/code>\u3001<code>backend\/<\/code>\u3001<code>defaults\/<\/code>\uff09\u3001<strong>\u5f00\u53d1\u5de5\u4f5c\u6d41<\/strong>\uff08<code>.vscode\/<\/code> \u4e0b\u7684\u811a\u672c\u4e0e\u4efb\u52a1\uff09\u3002\u4e0b\u9762\u6bcf\u4e00\u8282\u5c31\u6309\u8fd9\u56db\u7ec4\u5c55\u5f00\u3002<\/p>\n<p>\u8fd0\u884c\u65f6\u7684\u8c03\u7528\u94fe\u53ef\u4ee5\u7528\u4e00\u5f20\u56fe\u6536\u62e2\uff1a<\/p>\n<pre><code>        Steam Client (CEF)                    SteamOS (userland)\n+-----------------------------------+   +-----------------------------------+\n|  Steam UI                         |   |  Decky Loader (systemd service)   |\n|  (hosts shared React, external'd) |   |     |                             |\n|     |                             |   |     +--&gt; python main.py           |\n|     +--&gt; dist\/index.js            |   |          (class Plugin, 1 proc)   |\n|          (ESM, injected by Loader)|   |               ^                   |\n|               ^                   |   |               | async def xxx     |\n|               | definePlugin()    |   |               |                   |\n|               |                   |   |               |                   |\n|          callable(&quot;xxx&quot;, args) ---+---+---&gt; RPC -----&gt;|                   |\n|                                   |   |                                   |\n|          addEventListener &lt;-------+---+---- decky.emit(&quot;evt&quot;, ...)        |\n+-----------------------------------+   +-----------------------------------+<\/code><\/pre>\n<p>\u524d\u7aef ESM bundle \u548c\u540e\u7aef Python \u8fdb\u7a0b\u7269\u7406\u4e0a\u4e92\u4e0d\u77e5\u6653\uff0c\u552f\u4e00\u7684\u6865\u662f Decky Loader \u63d0\u4f9b\u7684\u4e00\u5bf9\u539f\u8bed\uff1a<code>callable<\/code> \/ <code>emit<\/code>\u3002\u540e\u9762\u6bcf\u4e00\u8282\u672c\u8d28\u4e0a\u90fd\u5728\u8bb2\u8fd9\u5f20\u56fe\u91cc\u67d0\u4e00\u5757\u7684\u7ec6\u8282\u3002<\/p>\n<h2>\u63d2\u4ef6\u58f0\u660e\uff1a<code>plugin.json<\/code> \u4e0e <code>package.json<\/code><\/h2>\n<p><code>plugin.json<\/code> \u662f Decky Loader \u5728\u52a0\u8f7d\u63d2\u4ef6\u65f6\u7b2c\u4e00\u4e2a\u8bfb\u5230\u7684\u6587\u4ef6\u3002\u5b98\u65b9\u6a21\u677f\u91cc\u957f\u8fd9\u6837\uff1a<\/p>\n<pre><code class=\"json\">{\n  &quot;name&quot;: &quot;Example Plugin&quot;,\n  &quot;author&quot;: &quot;John Doe&quot;,\n  &quot;flags&quot;: [&quot;debug&quot;, &quot;_root&quot;],\n  &quot;api_version&quot;: 1,\n  &quot;publish&quot;: {\n    &quot;tags&quot;: [&quot;template&quot;, &quot;root&quot;],\n    &quot;description&quot;: &quot;Decky example plugin.&quot;,\n    &quot;image&quot;: &quot;https:\/\/opengraph.githubassets.com\/1\/SteamDeckHomebrew\/PluginLoader&quot;\n  }\n}<\/code><\/pre>\n<p>\u51e0\u5904\u5bb9\u6613\u88ab\u5ffd\u7565\u7684\u7ec6\u8282\uff1a<\/p>\n<ul>\n<li>\n<p><strong><code>name<\/code> \u662f\u5c55\u793a\u540d\uff0c\u4e0d\u662f\u63d2\u4ef6\u76ee\u5f55\u540d<\/strong>\uff1a<\/p>\n<ul>\n<li>\u5b83\u662f\u73af\u5883\u53d8\u91cf <code>DECKY_PLUGIN_NAME<\/code>\uff0c\u4e5f\u662f\u83dc\u5355\/\u5546\u5e97\u91cc\u663e\u793a\u7ed9\u7528\u6237\u7684\u540d\u5b57\uff1b<\/li>\n<li>\u771f\u6b63\u7684\u63d2\u4ef6\u76ee\u5f55\u7531\u5b89\u88c5\u65f6\u7684 zip \u9876\u5c42\u76ee\u5f55\/\u5b89\u88c5\u8def\u5f84\u51b3\u5b9a\uff0cDecky Loader \u6e90\u7801\u91cc <code>plugin.json.name<\/code> \u548c\u5b9e\u9645\u7684 <code>plugin_directory<\/code> \u662f\u4e24\u4e2a\u72ec\u7acb\u6982\u5ff5\u2014\u2014\u914d\u7f6e\u3001\u65e5\u5fd7\u3001\u8fd0\u884c\u65f6\u76ee\u5f55\u7528\u7684\u662f&#8221;\u76ee\u5f55\u540d&#8221;\uff0c\u4e0d\u662f\u8fd9\u91cc\u7684\u5c55\u793a\u540d\uff1b<\/li>\n<li>\u6a21\u677f <code>.vscode\/defsettings.json<\/code> \u91cc\u7684 <code>pluginname<\/code> \u53ea\u662f\u90e8\u7f72\u811a\u672c\u7684\u53d8\u91cf\uff0c\u5b83\u51b3\u5b9a <code>rsync<\/code> \u5230 Deck \u4e0a\u7684\u76ee\u6807\u76ee\u5f55\u53eb\u4ec0\u4e48\uff0c\u5e76\u4e0d\u662f\u4e00\u79cd&#8221;\u7ed1\u5b9a\u5173\u7cfb&#8221;\uff0c\u53ea\u662f\u591a\u6570\u4eba\u4e60\u60ef\u8ba9\u4e24\u8005\u4fdd\u6301\u4e00\u81f4\uff1b<\/li>\n<li><strong>\u4e0d\u8981<\/strong>\u636e\u6b64\u624b\u62fc <code>~\/homebrew\/settings\/&lt;name&gt;\/<\/code> \u8fd9\u7c7b\u914d\u7f6e\u76ee\u5f55\uff0c\u771f\u5b9e\u7684\u8bbe\u7f6e\u3001\u8fd0\u884c\u65f6\u3001\u65e5\u5fd7\u8def\u5f84\u8bf7\u7edf\u4e00\u8bfb <code>decky.DECKY_PLUGIN_*_DIR<\/code> \u5e38\u91cf\u3002\u6539\u8fc7\u63d2\u4ef6\u76ee\u5f55\u540d\u3001\u5305\u540d\u6216\u5386\u53f2\u914d\u7f6e\u8def\u5f84\u65f6\uff0c\u7528 <code>_migration<\/code> \u94a9\u5b50\u8fc1\u79fb\u65e7\u6570\u636e\uff08\u4e0b\u6587\u8bb2\uff09\u3002<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong><code>flags<\/code><\/strong> \u662f\u6743\u9650\/\u884c\u4e3a\u58f0\u660e\uff0c\u76ee\u524d Decky Loader \u5b9e\u9645\u6d88\u8d39\u7684\u662f\u4e0b\u9762\u8fd9\u4e24\u4e2a\uff1a<\/p>\n<ul>\n<li><code>_root<\/code> \/ <code>root<\/code>\uff1a\u542b\u4e49\u662f\u8ba9\u540e\u7aef\u4ee5 root \u8eab\u4efd\u8fd0\u884c\uff0c\u80fd\u8bbf\u95ee <code>\/usr\/<\/code> \u4e0b\u7684\u7cfb\u7edf\u6587\u4ef6\u3002\u5386\u53f2\u4e0a\u6a21\u677f\u91cc\u7684 key \u4e0e Loader \u6e90\u7801\u4e2d\u5224\u65ad\u7684 key \u5b58\u5728\u8fc7\u547d\u540d\u4e0d\u4e00\u81f4\uff08<code>_root<\/code> vs <code>root<\/code>\uff09\uff0c\u8fd1\u671f\u6709\u6240\u7edf\u4e00\u2014\u2014<strong>\u63d0\u4ea4\u524d\u8bf7\u4ee5\u5f53\u524d <code>SteamDeckHomebrew\/decky-loader<\/code> \u6e90\u7801\u53ca <code>decky-plugin-database<\/code> CI \u7684\u6821\u9a8c\u7ed3\u679c\u4e3a\u51c6<\/strong>\uff0c\u4e0d\u8981\u628a\u4efb\u4f55\u4e00\u4fa7\u5f53\u4f5c\u6743\u5a01\uff1b<\/li>\n<li><code>debug<\/code>\uff1a\u5728\u5f00\u53d1\u671f\u6253\u5f00\u989d\u5916\u65e5\u5fd7\u3002<\/li>\n<li>\u6a21\u677f\u9ed8\u8ba4\u5f00\u7740 <code>_root<\/code> \u53ea\u662f\u4e3a\u4e86\u6f14\u793a\u80fd\u529b\uff0c<strong>\u771f\u5b9e\u63d2\u4ef6\u901a\u5e38\u4e0d\u8981\u4e3b\u52a8\u5f00 <code>_root<\/code><\/strong>\u2014\u2014\u80fd\u7528 <code>subprocess<\/code> + \u7cbe\u786e <code>sudo<\/code> \u547d\u4ee4\u89e3\u51b3\u7684\u4e8b\uff0c\u5c31\u4e0d\u8981\u8ba9\u6574\u4e2a\u540e\u7aef\u8fdb\u7a0b\u90fd\u5e26\u7279\u6743\u3002\u793e\u533a\u7ecf\u9a8c\u4e0a\uff0c\u5e26 <code>_root<\/code> \u7684\u63d2\u4ef6\u5728\u5546\u5e97\u5ba1\u6838\u65f6\u4e5f\u66f4\u5bb9\u6613\u88ab\u6253\u56de\u3002\u5176\u4ed6 <code>flags<\/code> \u503c Loader \u5f53\u524d\u4f1a\u5ffd\u7565\uff0c\u4e0d\u8981\u4f9d\u8d56\u672a\u6587\u6863\u5316\u7684\u884c\u4e3a\u3002<\/li>\n<\/ul>\n<\/li>\n<li><strong><code>api_version<\/code> \u76ee\u524d\u56fa\u5b9a\u662f <code>1<\/code><\/strong>\uff0c\u672a\u6765\u534f\u8bae\u5347\u7ea7\u65f6\u4f1a\u53d8\u3002<\/li>\n<li><strong><code>publish<\/code><\/strong> \u6bb5\u4ec5\u7528\u4e8e <a href=\"https:\/\/link.segmentfault.com\/?enc=JQb2ON7qneXCB38VvkESzw%3D%3D.C3DgXYGRI0KrVMBGloGJOGvd1X6zL2P3BySWQ%2BUybMANtWuZj0q4TBe1jz6pmRAk6ZC7e20%2BR9DXPP%2BJbyO0qA%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">decky-plugin-database<\/a> \u4e0a\u67b6\uff0c\u5f00\u53d1\u671f\u4e0d\u5199\u4e5f\u80fd\u8dd1\u3002<\/li>\n<\/ul>\n<p><code>package.json<\/code> \u5219\u58f0\u660e\u524d\u7aef\u4f9d\u8d56\u3002\u5b8c\u6574\u7248\u91cc\u8fd8\u4f1a\u5305\u542b\u4ed3\u5e93\u5143\u6570\u636e\u3001<code>test<\/code> \u5360\u4f4d\u811a\u672c\u7b49\u2014\u2014\u4e0b\u9762\u5217\u51fa\u4e0e\u6784\u5efa\/\u8fd0\u884c\u76f4\u63a5\u76f8\u5173\u7684\u5b57\u6bb5\uff1a<\/p>\n<pre><code class=\"json\">{\n  &quot;type&quot;: &quot;module&quot;,\n  &quot;scripts&quot;: {\n    &quot;build&quot;: &quot;rollup -c&quot;,\n    &quot;watch&quot;: &quot;rollup -c -w&quot;\n  },\n  &quot;devDependencies&quot;: {\n    &quot;@decky\/rollup&quot;: &quot;^1.0.2&quot;,\n    &quot;@decky\/ui&quot;: &quot;^4.11.0&quot;,\n    &quot;@rollup\/rollup-linux-x64-musl&quot;: &quot;^4.53.3&quot;,\n    &quot;@types\/react&quot;: &quot;19.1.1&quot;,\n    &quot;@types\/react-dom&quot;: &quot;19.1.1&quot;,\n    &quot;@types\/webpack&quot;: &quot;^5.28.5&quot;,\n    &quot;rollup&quot;: &quot;^4.53.3&quot;,\n    &quot;typescript&quot;: &quot;^5.6.2&quot;\n  },\n  &quot;dependencies&quot;: {\n    &quot;@decky\/api&quot;: &quot;^1.1.3&quot;,\n    &quot;react-icons&quot;: &quot;^5.3.0&quot;,\n    &quot;tslib&quot;: &quot;^2.7.0&quot;\n  },\n  &quot;pnpm&quot;: {\n    &quot;peerDependencyRules&quot;: {\n      &quot;ignoreMissing&quot;: [&quot;react&quot;, &quot;react-dom&quot;]\n    }\n  }\n}<\/code><\/pre>\n<p><code>@rollup\/rollup-linux-x64-musl<\/code> \u662f\u6a21\u677f\u663e\u5f0f\u58f0\u660e\u7684\u4f9d\u8d56\uff0c\u7528\u6765\u515c\u5e95 Rollup \u5728\u67d0\u4e9b\u6784\u5efa\u73af\u5883\u91cc\u52a0\u8f7d\u4e0d\u5230\u5bf9\u5e94 native binding \u7684\u60c5\u51b5\u2014\u2014\u5c11\u4e86\u5b83 Rollup \u53ef\u80fd\u76f4\u63a5\u62a5 &#8220;Cannot find module&#8221; \u800c\u7ec8\u6b62\u3002\u6ce8\u610f\uff1aSteamOS 3 \/ Holo \u672c\u8eab\u662f <strong>Arch \u7cfb glibc<\/strong> \u53d1\u884c\u7248\uff0c\u5e76\u4e0d\u662f musl \u53d1\u884c\u7248\uff0c\u8fd9\u91cc\u52a0\u8fd9\u4e2a\u4f9d\u8d56\u662f\u4e3a\u4e86\u8ba9 Rollup \u7684\u539f\u751f binding \u89e3\u6790\u66f4\u7a33\uff0c\u4e0d\u8981\u628a\u5b83\u7406\u89e3\u6210&#8221;holo \u955c\u50cf\u57fa\u4e8e musl&#8221;\u3002<\/p>\n<p>\u8fd9\u91cc\u6709\u4e24\u6761&#8221;\u53cd\u76f4\u89c9&#8221;\u7684\u70b9\u4e00\u5b9a\u8981\u8bb0\u4f4f\uff1a<\/p>\n<ul>\n<li>\n<p><strong>\u4e0d\u8981\u81ea\u5df1\u5b89\u88c5 <code>react<\/code> \/ <code>react-dom<\/code> \u4f5c\u4e3a\u8fd0\u884c\u65f6\u4f9d\u8d56<\/strong>\uff1aDecky Loader \u5728 Steam CEF \u91cc\u5df2\u7ecf\u63d0\u4f9b\u4e86\u4e00\u4efd\u5171\u4eab\u7684 React \u5b9e\u4f8b\uff0c\u4f60\u518d\u6253\u8fdb\u4e00\u4efd\uff0chook \u5f88\u5bb9\u6613\u56e0\u4e3a\u8fd0\u884c\u65f6\u5b9e\u4f8b\u4e0d\u4e00\u81f4\u800c\u62a5\u9519\u3002<code>@decky\/ui<\/code> \u58f0\u660e\u4e86 <code>react<\/code> \/ <code>react-dom<\/code> \u4f5c\u4e3a peerDependency\uff0c\u6a21\u677f\u91cc\u7684 <code>pnpm.peerDependencyRules.ignoreMissing<\/code> \u5c31\u662f\u5728\u544a\u8bc9 pnpm\uff1a&#8221;\u522b\u8b66\u544a\uff0c\u8fd9\u4fe9\u7531\u5bbf\u4e3b\u73af\u5883\u5728\u8fd0\u884c\u65f6\u63d0\u4f9b&#8221;\u3002\u987a\u5e26\u4e24\u6761\u8865\u5145\uff1a<\/p>\n<ul>\n<li><strong><code>@types\/react<\/code> \u7684\u5927\u7248\u672c\u8981\u8ddf Steam \u5ba2\u6237\u7aef CEF \u91cc\u7684 React \u5bf9\u9f50<\/strong>\uff08\u5f53\u524d\u662f 19.x\uff09\uff0c\u5426\u5219 hook \u7b7e\u540d \/ JSX \u7c7b\u578b\u4f1a\u5728\u7f16\u8bd1\u671f\u5c31\u62a5\u9519\uff1b<\/li>\n<li><strong>\u5305\u7ba1\u7406\u5668\u5efa\u8bae\u9501\u5b9a pnpm<\/strong>\uff1a<code>@decky\/rollup<\/code> \u4e0e <code>ignoreMissing<\/code> \u89c4\u5219\u90fd\u9ed8\u8ba4\u6309 pnpm \u7684 hoist \u884c\u4e3a\u8bbe\u8ba1\uff0c<code>npm i<\/code> \/ <code>yarn<\/code> \u53ef\u80fd\u4f1a\u628a <code>react<\/code> \u62c9\u6210\u76f4\u63a5\u4f9d\u8d56\u4e00\u8d77\u6253\u8fdb bundle\uff0c\u7ed5\u8fc7 external\u3002<\/li>\n<\/ul>\n<\/li>\n<li><strong><code>@decky\/ui<\/code> \u5fc5\u987b\u8ddf Decky Loader \u7684\u7248\u672c\u540c\u6b65<\/strong>\uff1a\u5b98\u65b9\u5728 tasks \u91cc\u4e13\u95e8\u51c6\u5907\u4e86 <code>updatefrontendlib<\/code> \u4efb\u52a1\uff08\u5373 <code>pnpm update @decky\/ui --latest<\/code>\uff09\uff0c\u6784\u5efa\u524d\u4e00\u523b\u5f3a\u5236\u5347\u7ea7\u4e00\u6b21\uff0c\u907f\u514d\u628a\u8fc7\u671f\u7684\u7c7b\u578b\u5b9a\u4e49\u5e26\u8fdb\u5546\u5e97\u5ba1\u6838\u3002<\/li>\n<\/ul>\n<h2>\u524d\u7aef\u8fd0\u884c\u65f6\uff1a<code>src\/<\/code>\u3001Rollup \u4e0e <code>definePlugin<\/code><\/h2>\n<h3>\u6784\u5efa\uff1a\u4e3a\u4ec0\u4e48\u662f Rollup \u800c\u4e0d\u662f Vite<\/h3>\n<p><code>rollup.config.js<\/code> \u53ea\u6709\u4e09\u884c\uff1a<\/p>\n<pre><code class=\"js\">import deckyPlugin from &quot;@decky\/rollup&quot;;\n\nexport default deckyPlugin({\n  \/\/ Add your extra Rollup options here\n});<\/code><\/pre>\n<p><code>@decky\/rollup<\/code> \u9884\u7f6e\u4e86\u63d2\u4ef6\u9700\u8981\u7684\u4e00\u5207 \u2014\u2014 TypeScript\u3001JSX\u3001\u8d44\u6e90\u5904\u7406\uff08\u7531 preset \u5185\u90e8\u7684\u8d44\u6e90\u63d2\u4ef6\u628a <code>import<\/code> \u91cd\u5199\u6210 Decky \u63d0\u4f9b\u7684\u672c\u5730\u8d44\u6e90 URL\uff09\u3001external React\u3001\u4ee5 <code>format: &quot;esm&quot;<\/code> \u8f93\u51fa\u5230 <code>dist\/index.js<\/code>\u3002Decky Loader \u52a0\u8f7d\u63d2\u4ef6\u65f6\uff0c\u4f1a\u628a\u8fd9\u4e2a\u5355\u6587\u4ef6\u8bfb\u6210\u5b57\u7b26\u4e32\u6ce8\u5165\u5230 Steam CEF\uff0c\u6240\u4ee5\uff1a<\/p>\n<ul>\n<li><strong>\u4e0d\u8981\u5206\u5305\u3001\u4e0d\u8981\u52a8\u6001 <code>import()<\/code><\/strong>\uff1a\u6700\u7ec8\u5fc5\u987b\u662f\u4e00\u4e2a\u6587\u4ef6\uff1b<\/li>\n<li><strong>\u4e0d\u8981\u5f15\u5165 Tailwind \/ CSS-in-JS \u8fd0\u884c\u65f6<\/strong>\uff1a\u5305\u4f53\u79ef\u4f1a\u5feb\u901f\u81a8\u80c0\uff0c\u800c\u4e14\u53ef\u80fd\u548c Steam \u539f\u751f\u6837\u5f0f\u51b2\u7a81\uff0c\u66f4\u63a8\u8350\u76f4\u63a5\u5728\u7ec4\u4ef6\u91cc\u5185\u5d4c <code>&lt;style&gt;{...}&lt;\/style&gt;<\/code>\uff1b<\/li>\n<li><strong>\u8d44\u6e90\u7531 preset \u5185\u7f6e\u7684\u8d44\u6e90\u5904\u7406\u63d2\u4ef6\u63a5\u7ba1<\/strong>\uff1a\u6a21\u677f\u91cc\u7ed9\u4e86\u4e00\u4efd <code>src\/types.d.ts<\/code>\uff0c\u58f0\u660e <code>*.png<\/code>\u3001<code>*.svg<\/code>\u3001<code>*.jpg<\/code> \u4e3a string \u6a21\u5757\u3002<code>import logo from &quot;..\/assets\/logo.png&quot;<\/code> \u62ff\u5230\u7684\u4e0d\u662f base64 data URL\uff0c\u800c\u662f\u7531 preset \u6ce8\u5165\u3001\u6307\u5411 Decky \u672c\u5730\u8d44\u6e90\u670d\u52a1\u7684\u76f8\u5bf9 URL\u2014\u2014\u8fd0\u884c\u65f6\u7531 Loader \u4ece\u63d2\u4ef6\u76ee\u5f55\u91cc\u771f\u5b9e\u8bfb\u53d6\u6587\u4ef6\u3002\u8fd9\u6837\u65e2\u4e0d\u4f1a\u628a\u56fe\u7247\u585e\u8fdb bundle \u6491\u5927\u4f53\u79ef\uff0c\u4e5f\u4fdd\u7559\u4e86\u7f13\u5b58\u80fd\u529b\uff08\u5177\u4f53\u63d2\u4ef6\u540d\u5728 <code>@decky\/rollup<\/code> \u5404\u7248\u672c\u95f4\u6709\u53d8\u52a8\uff0c\u4ee5\u5b9e\u9645 <code>pnpm list<\/code> \u4e3a\u51c6\uff09\u3002<\/li>\n<\/ul>\n<p><code>tsconfig.json<\/code> \u5f00\u4e86 <code>strict<\/code>\u3001<code>noUnusedLocals<\/code>\u3001<code>noUnusedParameters<\/code> \u7b49\u4e00\u63fd\u5b50\u4e25\u683c\u9009\u9879\uff0c<code>jsx: &quot;react-jsx&quot;<\/code> \u4fdd\u8bc1 JSX \u7f16\u8bd1\u5230\u5171\u4eab React \u8fd0\u884c\u65f6\u3002\u65b0\u5efa\u63d2\u4ef6\u65f6\u5efa\u8bae\u539f\u6837\u4fdd\u7559 \u2014\u2014 Decky Loader \u672c\u8eab\u4e0d\u5f3a\u5236\uff0c\u4f46\u4e25\u683c\u6a21\u5f0f\u80fd\u5e2e\u4f60\u907f\u5f00\u5927\u91cf\u8fd0\u884c\u65f6\u60ca\u559c\u3002<\/p>\n<h3>\u5165\u53e3\uff1a<code>definePlugin<\/code> \u7684\u8fd4\u56de\u503c\u5c31\u662f\u63d2\u4ef6<\/h3>\n<p>\u4e0b\u9762\u662f\u57fa\u4e8e\u6a21\u677f <code>src\/index.tsx<\/code> \u7684<strong>\u7cbe\u7b80\u6539\u5199\u7248<\/strong>\u2014\u2014\u5220\u6389\u4e86\u539f\u6587\u4ef6\u91cc\u6ce8\u91ca\u6389\u7684 router \/ logo \u793a\u4f8b\uff0c\u628a\u968f\u673a\u6570\u8303\u56f4\u4ece <code>Math.random()<\/code> \u6362\u6210 <code>Math.floor(Math.random() * 100)<\/code> \u4fbf\u4e8e\u6f14\u793a\uff0c\u9aa8\u67b6\u548c API \u7528\u6cd5\u4e0e\u6a21\u677f\u4e00\u81f4\uff1a<\/p>\n<pre><code class=\"tsx\">\/\/ src\/index.tsx\nimport {\n  ButtonItem,\n  PanelSection,\n  PanelSectionRow,\n  staticClasses,\n} from &quot;@decky\/ui&quot;;\nimport {\n  addEventListener,\n  removeEventListener,\n  callable,\n  definePlugin,\n  toaster,\n} from &quot;@decky\/api&quot;;\nimport { useState } from &quot;react&quot;;\nimport { FaShip } from &quot;react-icons\/fa&quot;;\n\n\/\/ \u524d\u7aef RPC \u4ee3\u7406\uff1a\u5bf9\u5e94 Python \u7aef\u7684 Plugin.add(left, right) -&gt; int\nconst add = callable&lt;[first: number, second: number], number&gt;(&quot;add&quot;);\n\/\/ \u89e6\u53d1\u4e00\u4e2a\u8017\u65f6 15s \u7684\u540e\u7aef\u4efb\u52a1\uff0c\u5b8c\u6210\u540e\u901a\u8fc7\u4e8b\u4ef6\u56de\u4f20\nconst startTimer = callable&lt;[], void&gt;(&quot;start_timer&quot;);\n\n\/** \u4fa7\u8fb9\u680f\u9762\u677f\u7684\u4e3b\u4f53\u5185\u5bb9 *\/\nfunction Content() {\n  const [result, setResult] = useState&lt;number | undefined&gt;();\n\n  \/** \u70b9\u51fb\u6309\u94ae\u65f6\u8c03\u7528\u540e\u7aef add \u5e76\u5c55\u793a\u7ed3\u679c *\/\n  const onClick = async () =&gt; {\n    const sum = await add(\n      Math.floor(Math.random() * 100),\n      Math.floor(Math.random() * 100),\n    );\n    setResult(sum);\n  };\n\n  return (\n    &lt;PanelSection title=&quot;Panel Section&quot;&gt;\n      &lt;PanelSectionRow&gt;\n        &lt;ButtonItem layout=&quot;below&quot; onClick={onClick}&gt;\n          {result ?? &quot;Add two numbers via Python&quot;}\n        &lt;\/ButtonItem&gt;\n      &lt;\/PanelSectionRow&gt;\n      &lt;PanelSectionRow&gt;\n        &lt;ButtonItem layout=&quot;below&quot; onClick={() =&gt; startTimer()}&gt;\n          Start Python timer\n        &lt;\/ButtonItem&gt;\n      &lt;\/PanelSectionRow&gt;\n    &lt;\/PanelSection&gt;\n  );\n}\n\nexport default definePlugin(() =&gt; {\n  \/\/ \u8ba2\u9605\u540e\u7aef\u901a\u8fc7 decky.emit \u53d1\u51fa\u7684\u4e8b\u4ef6\n  const listener = addEventListener&lt;[string, boolean, number]&gt;(\n    &quot;timer_event&quot;,\n    (a, b, c) =&gt; {\n      toaster.toast({ title: &quot;timer_event&quot;, body: `${a}, ${b}, ${c}` });\n    },\n  );\n\n  return {\n    name: &quot;Test Plugin&quot;,\n    titleView: &lt;div className={staticClasses.Title}&gt;Decky Example Plugin&lt;\/div&gt;,\n    content: &lt;Content \/&gt;,\n    icon: &lt;FaShip \/&gt;,\n    onDismount() {\n      \/\/ \u63d2\u4ef6\u53ef\u4ee5\u70ed\u91cd\u8f7d\uff0c\u5fc5\u987b\u5728\u5378\u8f7d\u65f6\u6ce8\u9500\u76d1\u542c\/\u8def\u7531\/\u8865\u4e01\n      removeEventListener(&quot;timer_event&quot;, listener);\n    },\n  };\n});<\/code><\/pre>\n<p>\u5173\u952e\u5fc3\u667a\u6a21\u578b\uff1a<\/p>\n<ol>\n<li><strong><code>definePlugin(factory)<\/code> \u8fd4\u56de\u7684\u5bf9\u8c61\u5c31\u662f\u63d2\u4ef6\u7684\u5f62\u72b6<\/strong>\u3002\u6700\u5e38\u7528\u7684\u56db\u4e2a\u5b57\u6bb5\uff1a<code>titleView<\/code>\u3001<code>content<\/code>\u3001<code>icon<\/code>\u3001<code>onDismount<\/code>\u3002\u5982\u679c\u4f60\u8fd8\u8981\u6ce8\u518c\u81ea\u5b9a\u4e49\u8def\u7531\uff0c\u5c31\u5728 factory \u91cc\u8c03 <code>routerHook.addRoute(...)<\/code>\uff0c\u5e76\u5728 <code>onDismount<\/code> \u91cc\u5bf9\u5e94\u5730 <code>removeRoute<\/code>\u3002<\/li>\n<li><strong>\u4ea4\u4e92\u63a7\u4ef6\u4f18\u5148\u6765\u81ea <code>@decky\/ui<\/code><\/strong>\uff1a<code>PanelSection<\/code>\u3001<code>PanelSectionRow<\/code>\u3001<code>ButtonItem<\/code>\u3001<code>ToggleField<\/code>\u3001<code>Focusable<\/code>\u3001<code>SidebarNavigation<\/code> \u7b49\u7b49\u3002\u8fd9\u4e9b\u7ec4\u4ef6\u5df2\u7ecf\u5904\u7406\u597d\u4e86\u624b\u67c4\u805a\u7126\u3001\u4e3b\u9898\u8272\u8ddf\u968f\u3001\u4e0e Steam CSS \u7684\u517c\u5bb9\u6027\u3002\u5c55\u793a\u578b <code>&lt;div&gt;<\/code> \u53ef\u4ee5\u7528\uff0c\u4f46\u53ef\u70b9\u51fb\u3001\u53ef\u9009\u62e9\u3001\u53ef\u6eda\u52a8\u7684\u81ea\u5b9a\u4e49\u5143\u7d20\u8981\u5305\u8fdb <code>Focusable<\/code>\uff0c\u5426\u5219\u624b\u67c4\u6a21\u5f0f\u4e0b\u5f88\u5bb9\u6613\u5931\u7126\u3002<\/li>\n<li>\n<p><strong>\u901a\u4fe1\u53ea\u6709\u4e24\u79cd\u5f62\u6001<\/strong>\uff1a<\/p>\n<ul>\n<li><strong>\u524d\u7aef \u2192 \u540e\u7aef<\/strong>\uff1a<code>callable&lt;[Args], Ret&gt;(name)<\/code> \u751f\u6210\u4e00\u4e2a\u5f3a\u7c7b\u578b RPC \u4ee3\u7406\uff1b<\/li>\n<li><strong>\u540e\u7aef \u2192 \u524d\u7aef<\/strong>\uff1aPython \u91cc <code>await decky.emit(&quot;event_name&quot;, ...)<\/code>\uff0c\u524d\u7aef\u7528 <code>addEventListener<\/code> \u8ba2\u9605\u3002<\/li>\n<\/ul>\n<\/li>\n<li><strong><code>onDismount<\/code> \u662f\u70ed\u91cd\u8f7d\u7684\u4fdd\u547d\u7b26<\/strong>\u3002Decky Loader \u5141\u8bb8\u5728\u8bbe\u7f6e\u91cc\u5355\u72ec\u91cd\u8f7d\u67d0\u4e2a\u63d2\u4ef6\uff0c\u4e0d\u6e05\u7406\u76d1\u542c\u4f1a\u6b8b\u7559&#8221;\u5e7d\u7075\u4e8b\u4ef6&#8221;\uff0c\u9875\u9762\u4e00\u5237\u65b0\u5c31\u4f1a\u770b\u5230\u91cd\u590d toast\u3002\u8981\u610f\u8bc6\u5230 Decky \u7684&#8221;\u70ed\u91cd\u8f7d&#8221;\u53ea\u91cd\u542f Python \u540e\u7aef\u8fdb\u7a0b\u5e76\u91cd\u65b0\u6ce8\u5165\u524d\u7aef bundle\uff0c<strong>CEF \u5168\u5c40\u72b6\u6001\uff08<code>window.*<\/code>\u3001\u5b9a\u65f6\u5668\u3001React Portal\uff09\u4e0d\u4f1a\u88ab\u6e05\u7406<\/strong>\u2014\u2014\u6240\u4ee5\u4e0d\u5149\u8981 <code>removeEventListener<\/code>\uff0c\u51e1\u662f\u4f60\u6302\u5230\u5168\u5c40\u5bf9\u8c61\u4e0a\u7684\u5b57\u6bb5\u3001\u6ce8\u518c\u7684 <code>setInterval<\/code>\u3001<code>routerHook.addRoute<\/code> \u7684\u8def\u7531\uff0c\u5168\u90fd\u8981\u5728 <code>onDismount<\/code> \u91cc\u663e\u5f0f\u56de\u6536\u3002<\/li>\n<\/ol>\n<blockquote><p>\ud83d\udca1 \u8bed\u6cd5\u7ec6\u8282\uff1a<code>callable&lt;[first: number, second: number], number&gt;(...)<\/code> \u91cc\u7684 <code>[first: number, second: number]<\/code> \u662f TypeScript 4.0+ \u5f15\u5165\u7684<strong>\u5e26\u6807\u7b7e\u5143\u7ec4<\/strong>\u7c7b\u578b\uff0c\u53ea\u5f71\u54cd IDE \u63d0\u793a\uff08\u53c2\u6570\u540d\u60ac\u6d6e\uff09\uff0c\u4e0d\u662f Decky \u7279\u6b8a DSL\uff0c\u4e5f\u4e0d\u53c2\u4e0e\u8fd0\u884c\u65f6\u3002\u5982\u679c\u4f60\u89c9\u5f97\u5570\u55e6\uff0c\u5199\u6210 <code>callable&lt;[number, number], number&gt;(...)<\/code> \u5b8c\u5168\u7b49\u4ef7\u3002<\/p><\/blockquote>\n<h2>\u540e\u7aef\u8fd0\u884c\u65f6\uff1a<code>main.py<\/code>\u3001<code>decky.pyi<\/code> \u4e0e\u76ee\u5f55\u7ea6\u5b9a<\/h2>\n<h3>Python \u5165\u53e3<\/h3>\n<p>\u6a21\u677f\u7684 <code>main.py<\/code> \u5c55\u793a\u4e86\u540e\u7aef\u7684\u6240\u6709\u9aa8\u67b6\uff1a<\/p>\n<pre><code class=\"python\"># main.py\nimport os\nimport asyncio\nimport decky\n\n\nclass Plugin:\n    &quot;&quot;&quot;Decky Loader \u901a\u8fc7\u53cd\u5c04\u52a0\u8f7d\u8fd9\u4e2a\u56fa\u5b9a\u540d\u5b57\u7684\u7c7b\u3002&quot;&quot;&quot;\n\n    async def add(self, left: int, right: int) -&gt; int:\n        &quot;&quot;&quot;\u7b80\u5355\u7684\u540c\u6b65\u98ce\u683c RPC\uff1a\u8fd4\u56de\u4e24\u6570\u4e4b\u548c\u3002&quot;&quot;&quot;\n        return left + right\n\n    async def long_running(self):\n        &quot;&quot;&quot;\u6f14\u793a\uff1a\u5f02\u6b65\u4efb\u52a1 + \u901a\u8fc7\u4e8b\u4ef6\u5411\u524d\u7aef\u56de\u4f20\u7ed3\u679c\u3002&quot;&quot;&quot;\n        await asyncio.sleep(15)\n        await decky.emit(&quot;timer_event&quot;, &quot;Hello from the backend!&quot;, True, 2)\n\n    async def start_timer(self):\n        &quot;&quot;&quot;\u88ab\u524d\u7aef\u901a\u8fc7 callable('start_timer') \u89e6\u53d1\u3002&quot;&quot;&quot;\n        self.loop.create_task(self.long_running())\n\n    async def _main(self):\n        &quot;&quot;&quot;\u63d2\u4ef6\u8fdb\u5165\u65f6\u8c03\u7528\u4e00\u6b21\uff0c\u9002\u5408\u505a\u521d\u59cb\u5316\/\u8bfb\u914d\u7f6e\u3002&quot;&quot;&quot;\n        self.loop = asyncio.get_event_loop()\n        decky.logger.info(&quot;Hello World!&quot;)\n\n    async def _unload(self):\n        &quot;&quot;&quot;\u88ab\u505c\u7528\/\u70ed\u91cd\u8f7d\u65f6\u8c03\u7528\uff0c\u6e05\u7406\u8d44\u6e90\u4f46\u4fdd\u7559\u8bbe\u7f6e\u3002&quot;&quot;&quot;\n        decky.logger.info(&quot;Goodnight World!&quot;)\n\n    async def _uninstall(self):\n        &quot;&quot;&quot;\u5f7b\u5e95\u5378\u8f7d\u65f6\u8c03\u7528\uff0c\u505a\u6700\u7ec8\u6e05\u7406\u3002&quot;&quot;&quot;\n        decky.logger.info(&quot;Goodbye World!&quot;)\n\n    async def _migration(self):\n        &quot;&quot;&quot;\u8fc1\u79fb\u5386\u53f2\u76ee\u5f55\/\u914d\u7f6e\uff1b\u7531 Loader \u5728 `_main` \u4e4b\u524d\u81ea\u52a8\u8c03\u7528\u4e00\u6b21\u3002&quot;&quot;&quot;\n        decky.migrate_logs(os.path.join(\n            decky.DECKY_USER_HOME, &quot;.config&quot;, &quot;decky-template&quot;, &quot;template.log&quot;))\n        decky.migrate_settings(\n            os.path.join(decky.DECKY_HOME, &quot;settings&quot;, &quot;template.json&quot;),\n            os.path.join(decky.DECKY_USER_HOME, &quot;.config&quot;, &quot;decky-template&quot;))\n        decky.migrate_runtime(\n            os.path.join(decky.DECKY_HOME, &quot;template&quot;),\n            os.path.join(decky.DECKY_USER_HOME, &quot;.local&quot;, &quot;share&quot;, &quot;decky-template&quot;))<\/code><\/pre>\n<p>\u63d0\u70bc\u51e0\u6761\u5bb9\u6613\u8e29\u7684\u5751\uff1a<\/p>\n<ul>\n<li><strong>\u7c7b\u540d\u5fc5\u987b\u53eb <code>Plugin<\/code><\/strong>\uff0cDecky Loader \u901a\u8fc7\u5b57\u7b26\u4e32\u53cd\u5c04\u62ff\u5b83\uff0c\u6539\u4e86\u5c31\u8d77\u4e0d\u6765\u3002<\/li>\n<li><strong>\u6240\u6709\u5bf9\u5916\u65b9\u6cd5\u90fd\u5fc5\u987b\u662f <code>async<\/code><\/strong>\uff0c\u54ea\u6015\u662f\u540c\u6b65\u64cd\u4f5c\u3002Decky Loader \u4f1a <code>await<\/code> \u6bcf\u4e00\u6b21 RPC\u3002<\/li>\n<li><strong>\u65b9\u6cd5\u53c2\u6570\u548c\u8fd4\u56de\u503c\u5fc5\u987b\u662f JSON-safe<\/strong>\uff08\u57fa\u672c\u7c7b\u578b\u3001<code>dict<\/code>\u3001<code>list<\/code>\uff09\u3002\u60f3\u8981\u7c7b\u578b\u63d0\u793a\u5c31\u7528 <code>TypedDict<\/code>\u3002<\/li>\n<li><strong>\u751f\u547d\u5468\u671f\u94a9\u5b50<\/strong>\u6709 <code>_main<\/code>\u3001<code>_unload<\/code>\u3001<code>_uninstall<\/code>\u3001<code>_migration<\/code> \u56db\u4e2a\uff0c\u547d\u540d\u56fa\u5b9a\u3001\u5168\u90e8\u53ef\u9009\u3002\u5176\u4e2d <code>_migration<\/code> \u7531 Decky Loader \u5728 <code>_main<\/code> \u4e4b\u524d\u81ea\u52a8\u8c03\u7528\u4e00\u6b21\uff0c\u4e0d\u9700\u8981\uff08\u4e5f\u4e0d\u5e94\u8be5\uff09\u5728 <code>_main<\/code> \u91cc\u518d\u624b\u52a8\u8c03\u7528\u3002\u6a21\u677f\u91cc\u628a\u8fd9\u4e9b\u90fd\u5199\u5168\u4e86\uff0c\u53ef\u4ee5\u4f5c\u4e3a&#8221;\u8981\u4e0d\u8981\u652f\u6301\u8fd9\u4e2a\u884c\u4e3a&#8221;\u7684 checklist\u3002<\/li>\n<li><strong><code>_migration<\/code> \u7684\u5e42\u7b49\u539f\u5219<\/strong>\uff1a\u4e0d\u8981\u9760\u7248\u672c\u53f7\uff0c\u800c\u662f<strong>\u770b\u76ee\u6807\u5b57\u6bb5\/\u76ee\u5f55\u662f\u5426\u5df2\u7ecf\u5b58\u5728<\/strong>\uff0c\u7528\u6237\u53ef\u80fd\u8de8\u591a\u4e2a\u7248\u672c\u5347\u7ea7\u3002<\/li>\n<\/ul>\n<h3><code>decky<\/code> \u6a21\u5757\uff1a\u4e00\u4e2a&#8221;\u53d7\u7ea6\u675f\u7684\u6807\u51c6\u5e93&#8221;<\/h3>\n<p>\u6a21\u677f\u91cc\u9644\u5e26\u4e00\u4efd <code>decky.pyi<\/code> \u2014\u2014 \u5b83\u662f Decky Loader \u6ce8\u5165\u5230 Python \u8fdb\u7a0b\u91cc\u7684 <code>decky<\/code> \u6a21\u5757\u7684\u7c7b\u578b\u5b58\u6839\u3002\u8bfb\u5b83\u7b49\u4e8e\u8bfb\u4e86\u4e00\u4efd\u540e\u7aef\u80fd\u7528\u7684 API \u6e05\u5355\u3002<\/p>\n<blockquote><p>\ud83d\udccc <strong>\u5e38\u91cf vs \u73af\u5883\u53d8\u91cf<\/strong>\uff1a\u4e0b\u8868\u4e2d\u4ee5 <code>DECKY_<\/code> \u5f00\u5934\u7684\u9879\u540c\u65f6\u4ee5 <code>decky.XXX<\/code> \u5e38\u91cf\u548c <code>os.environ[&quot;XXX&quot;]<\/code> \u73af\u5883\u53d8\u91cf\u4e24\u79cd\u5f62\u5f0f\u5b58\u5728\u3002\u4e8c\u8005\u5185\u5bb9\u4e00\u81f4\uff0c\u4f46\u5728\u4f60\u81ea\u5df1 <code>subprocess.Popen<\/code> \u542f\u52a8\u7684\u5b50\u8fdb\u7a0b\uff08\u4f8b\u5982 C\/Rust \u7f16\u51fa\u6765\u7684\u540e\u7aef\uff09\u91cc <strong>\u53ea\u80fd<\/strong> \u901a\u8fc7\u73af\u5883\u53d8\u91cf\u62ff\u5230\u2014\u2014<code>decky<\/code> \u6a21\u5757\u4e0d\u4f1a\u88ab\u81ea\u52a8\u7ee7\u627f\u4e0b\u53bb\u3002<\/p><\/blockquote>\n<table>\n<thead>\n<tr>\n<th>\u5e38\u91cf \/ \u51fd\u6570<\/th>\n<th>\u542b\u4e49<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>decky.HOME<\/code> \/ <code>decky.USER<\/code><\/td>\n<td>\u5f53\u524d\u8fdb\u7a0b\u7684 HOME \u4e0e\u7528\u6237\u540d\uff08\u53d7 <code>_root<\/code> \u5f71\u54cd\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_USER_HOME<\/code><\/td>\n<td>\u771f\u6b63\u7684 deck \u7528\u6237\u5bb6\u76ee\u5f55\uff0c<code>\/home\/deck<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_HOME<\/code><\/td>\n<td><code>~\/homebrew<\/code>\uff0cDecky \u81ea\u5df1\u7684\u6839\u76ee\u5f55<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_DIR<\/code><\/td>\n<td>\u5f53\u524d\u63d2\u4ef6\u89e3\u538b\u540e\u7684\u6839\u76ee\u5f55<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_NAME<\/code><\/td>\n<td>\u5f53\u524d\u63d2\u4ef6\u540d\uff0c\u6765\u81ea <code>plugin.json<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_VERSION<\/code> \/ <code>DECKY_PLUGIN_AUTHOR<\/code><\/td>\n<td>\u7248\u672c\u53f7\u3001\u4f5c\u8005\uff1b\u4e0a\u62a5\u9065\u6d4b\u6216\u65e5\u5fd7\u65f6\u6bd4\u786c\u7f16\u7801\u597d<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_VERSION<\/code><\/td>\n<td>Decky Loader \u81ea\u8eab\u7248\u672c\uff0c\u505a\u517c\u5bb9\u6027\u5224\u65ad\u7528<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_SETTINGS_DIR<\/code><\/td>\n<td><strong>\u63a8\u8350\u5199\u914d\u7f6e\u7684\u4f4d\u7f6e<\/strong>\uff0c\u5df2\u7531 loader \u81ea\u52a8\u521b\u5efa<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_RUNTIME_DIR<\/code><\/td>\n<td><strong>\u63a8\u8350\u5199\u8fd0\u884c\u65f6\u6570\u636e<\/strong>\uff08\u7f13\u5b58\u3001\u4e34\u65f6\u6587\u4ef6\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_LOG_DIR<\/code><\/td>\n<td><strong>\u63a8\u8350\u5199\u6301\u4e45\u65e5\u5fd7<\/strong><\/td>\n<\/tr>\n<tr>\n<td><code>decky.DECKY_PLUGIN_LOG<\/code><\/td>\n<td>\u4e3b\u65e5\u5fd7\u6587\u4ef6\u8def\u5f84<\/td>\n<\/tr>\n<tr>\n<td><code>decky.logger<\/code><\/td>\n<td>\u5df2\u7ed1\u5b9a\u5230\u4e0a\u9762\u65e5\u5fd7\u6587\u4ef6\u7684 <code>logging.Logger<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>decky.emit(event, *args)<\/code><\/td>\n<td>\u5411\u524d\u7aef\u63a8\u4e8b\u4ef6<\/td>\n<\/tr>\n<tr>\n<td><code>decky.migrate_settings \/ _runtime \/ _logs<\/code><\/td>\n<td>\u5206\u522b\u8fc1\u79fb\u914d\u7f6e\/\u8fd0\u884c\u65f6\/\u65e5\u5fd7\u5230\u7ea6\u5b9a\u76ee\u5f55<\/td>\n<\/tr>\n<tr>\n<td><code>decky.migrate_any(target_dir, *sources)<\/code><\/td>\n<td>\u4e0a\u9762\u4e09\u8005\u7684\u901a\u7528\u7248\uff1a\u628a\u4efb\u610f\u65e7\u8def\u5f84\u642c\u5230\u6307\u5b9a\u76ee\u6807\u76ee\u5f55\uff0c\u7528\u4e8e\u4e0d\u5c5e\u4e8e\u4e09\u7c7b\u6807\u51c6\u76ee\u5f55\u7684\u6570\u636e<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\u4e00\u6761\u5f88\u5173\u952e\u7684\u89c4\u5219\uff1a<strong>\u4e0d\u8981\u5f80 <code>DECKY_HOME<\/code> \u4e4b\u5916\u5199\u4efb\u4f55\u4e1c\u897f<\/strong>\u3002\u5199 <code>\/etc<\/code>\u3001<code>\/usr\/local<\/code> \u8fd9\u7c7b\u8def\u5f84\u5373\u4f7f\u62ff\u5230\u4e86 <code>_root<\/code> \u4e5f\u4f1a\u88ab\u5546\u5e97\u5ba1\u6838\u6253\u56de\u6765\uff0c\u800c\u4e14 SteamOS \u4e0b\u6b21\u66f4\u65b0\u4f1a\u628a\u53ea\u8bfb\u5206\u533a\u6574\u4e2a\u8986\u76d6\u6389\u3002<\/p>\n<h3>\u5e26\u539f\u751f\u540e\u7aef\uff1a<code>backend\/<\/code> \u76ee\u5f55<\/h3>\n<p>\u5982\u679c\u4f60\u9700\u8981 C\/C++\/Rust\/Go \u7f16\u51fa\u7684\u4e8c\u8fdb\u5236\uff08\u4f8b\u5982\u8c03\u7528\u5e95\u5c42\u9a71\u52a8\uff09\uff0c\u5c31\u628a\u6e90\u7801\u653e\u8fdb <code>backend\/src\/<\/code>\uff0c\u518d\u5199\u4e00\u4e2a <code>Makefile<\/code> \u628a\u4ea7\u7269\u4e22\u8fdb <code>backend\/out\/<\/code>\u3002\u6a21\u677f\u91cc\u7684 <code>backend\/Makefile<\/code> \u7b80\u5316\u5230\u6781\u81f4\uff1a<\/p>\n<pre><code class=\"make\">all: hello\n\nhello:\n    mkdir -p .\/out\n    gcc -o .\/out\/hello .\/src\/main.c\n\n.PHONY: clean\nclean:\n    rm -f hello<\/code><\/pre>\n<blockquote><p>\u26a0\ufe0f <strong>\u6a21\u677f <code>backend\/Makefile<\/code> \u7684 <code>clean<\/code> \u89c4\u5219\u4e0e\u5b9e\u9645\u4ea7\u7269\u8def\u5f84\u4e0d\u4e00\u81f4<\/strong>\uff1a<code>rm -f hello<\/code> \u60f3\u5220\u7684\u662f <code>backend\/hello<\/code>\uff0c\u4f46\u4ea7\u7269\u5b9e\u9645\u5728 <code>backend\/out\/hello<\/code>\u2014\u2014\u8fd9\u6761\u89c4\u5219\u5728\u6a21\u677f\u91cc\u662f\u4e2a no-op\u3002\u5957\u5230\u5b9e\u9645\u9879\u76ee\u65f6\uff0c\u6539\u6210 <code>rm -rf .\/out<\/code> \u6216\u7cbe\u786e\u5220\u9664 <code>.\/out\/&lt;binary&gt;<\/code>\u3002<\/p><\/blockquote>\n<p><code>Dockerfile<\/code> \u4f7f\u7528\u5b98\u65b9\u63d0\u4f9b\u7684 holo \u57fa\u7840\u955c\u50cf\uff08\u8fd8\u6709 <code>holo-toolchain-rust<\/code> \/ <code>holo-toolchain-go<\/code> \u53d8\u4f53\uff09\uff0c<code>entrypoint.sh<\/code> \u91cc\u53ea\u505a\u4e00\u4ef6\u4e8b\uff1a<code>cd \/backend &amp;&amp; make<\/code>\u3002Decky CLI \u5728\u6784\u5efa\u63d2\u4ef6\u65f6\u4f1a <code>docker run<\/code> \u8fd9\u4e2a\u955c\u50cf\uff0c\u5f97\u5230\u7684 <code>backend\/out\/*<\/code> \u4f1a\u88ab\u62f7\u8d1d\u5230\u6700\u7ec8 zip \u7684 <code>bin\/<\/code> \u4e0b\uff0c\u63d2\u4ef6\u8fd0\u884c\u65f6\u901a\u8fc7 <code>os.path.join(decky.DECKY_PLUGIN_DIR, &quot;bin&quot;, &quot;hello&quot;)<\/code> \u8c03\u7528\u3002<\/p>\n<p>\u8fd9\u4e48\u505a\u7684\u597d\u5904\u662f<strong>\u6784\u5efa\u73af\u5883\u548c Steam Deck \u5b8c\u5168\u4e00\u81f4<\/strong>\uff0c\u907f\u514d\u4e86&#8221;\u5728 Ubuntu \u7f16\u51fa\u6765\u6254\u5230 Deck \u4e0a\u627e\u4e0d\u5230 glibc&#8221;\u7684\u7ecf\u5178\u95ee\u9898\u3002<\/p>\n<h3>\u7b2c\u4e09\u65b9\u4f9d\u8d56\uff1a<code>py_modules\/<\/code><\/h3>\n<p>SteamOS \u7684 <code>\/usr<\/code> \u662f\u53ea\u8bfb\u7684\uff0c\u4f60\u6ca1\u6cd5 <code>pip install<\/code> \u5230\u7cfb\u7edf Python\u3002\u793e\u533a\u7ea6\u5b9a\u7684\u505a\u6cd5\u662f\uff1a<strong>\u628a\u7b2c\u4e09\u65b9 Python \u5305 vendored \u8fdb <code>py_modules\/<\/code><\/strong>\uff0cDecky Loader \u4f1a\u81ea\u52a8\u628a\u8fd9\u4e2a\u76ee\u5f55\u52a0\u5165 <code>sys.path<\/code>\u3002\u6a21\u677f\u91cc\u7559\u4e86\u4e00\u4e2a <code>.keep<\/code> \u5360\u4f4d\uff0c\u5f00\u53d1\u65f6\u4f60\u53ea\u9700\u8981 <code>pip install --target=py_modules xxx<\/code> \u5373\u53ef\u3002<\/p>\n<h3>\u9759\u6001\u6587\u4ef6\uff1a<code>defaults\/<\/code><\/h3>\n<p><code>defaults\/defaults.txt<\/code> \u7684\u6ce8\u91ca\u91cc\u8bf4\u5f97\u5f88\u6e05\u695a\uff1a<strong>\u8fd9\u4e2a\u76ee\u5f55\u91cc\u7684\u5185\u5bb9\u4f1a\u88ab\u539f\u6837\u6253\u8fdb\u63d2\u4ef6\u6839\u76ee\u5f55<\/strong>\u3002\u5e38\u89c1\u7528\u9014\uff1a\u9ed8\u8ba4 CSS \u4e3b\u9898\u3001\u79cd\u5b50\u914d\u7f6e\u3001\u79bb\u7ebf\u8d44\u6e90\u3002\u6ce8\u610f\u5b83<strong>\u4e0d\u80fd\u628a\u6587\u4ef6\u94fa\u5230\u4efb\u610f\u8def\u5f84<\/strong>\uff0c\u53ea\u80fd\u653e\u5728\u63d2\u4ef6\u76ee\u5f55\u5185\u90e8\u3002<\/p>\n<h2>\u5f00\u53d1\u5de5\u4f5c\u6d41\uff1a<code>.vscode\/<\/code> \u7684\u4e00\u5957&#8221;\u8fdc\u7a0b\u5f00\u53d1\u5957\u4ef6&#8221;<\/h2>\n<p>\u8fd9\u662f\u5f88\u591a\u6559\u7a0b\u4e00\u7b14\u5e26\u8fc7\u3001\u4f46\u5bf9\u65e5\u5e38\u4f53\u9a8c\u6700\u53cb\u597d\u7684\u90e8\u5206\u3002\u6a21\u677f\u7684 <code>.vscode\/<\/code> \u76ee\u5f55\u91cc\u662f\u4e00\u5957\u628a&#8221;\u672c\u5730\u6539\u4ee3\u7801&#8221;\u8fde\u63a5\u5230&#8221;Steam Deck \u4e0a\u91cd\u8f7d\u8fd0\u884c&#8221;\u7684\u811a\u672c\u3002\u6838\u5fc3\u6587\u4ef6\uff1a<\/p>\n<table>\n<thead>\n<tr>\n<th>\u6587\u4ef6<\/th>\n<th>\u4f5c\u7528<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>tasks.json<\/code><\/td>\n<td>\u58f0\u660e VS Code \u4efb\u52a1\uff1asetup \/ build \/ deploy \/ builddeploy \/ restartdecky<\/td>\n<\/tr>\n<tr>\n<td><code>setup.sh<\/code><\/td>\n<td>\u9996\u6b21\u521d\u59cb\u5316\uff1a\u68c0\u6d4b pnpm\u3001Docker\uff0c\u4e0b\u8f7d <a href=\"https:\/\/link.segmentfault.com\/?enc=DQLyndIXZg%2FLqqx%2B3C2NPw%3D%3D.wgCq%2Foh5L4EZDLZQcKc7SL9gcCBhpXvVgXAnANYd0Qc%2BzPRGeU00xsn1%2B1r1sxXS\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Decky CLI<\/a><\/td>\n<\/tr>\n<tr>\n<td><code>config.sh<\/code><\/td>\n<td>\u6821\u9a8c\u662f\u5426\u5df2\u6709 <code>.vscode\/settings.json<\/code>\uff0c\u6ca1\u6709\u5c31\u590d\u5236 <code>defsettings.json<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>build.sh<\/code><\/td>\n<td>\u8c03\u7528 Decky CLI \u628a\u5f53\u524d\u76ee\u5f55\u6253\u6210\u7b26\u5408\u5546\u5e97\u89c4\u8303\u7684 zip<\/td>\n<\/tr>\n<tr>\n<td><code>defsettings.json<\/code><\/td>\n<td>Deck \u7684 IP \/ \u7528\u6237\u540d \/ \u5bc6\u7801 \/ \u63d2\u4ef6\u540d\u7b49\u9ed8\u8ba4\u503c<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>\u9996\u6b21\u8bbe\u7f6e<\/h3>\n<p>\u6253\u5f00 VS Code \u540e\u8fd0\u884c <code>setup<\/code> \u4efb\u52a1\uff0c\u5b83\u4f1a\u6309\u987a\u5e8f\uff1a<\/p>\n<ol>\n<li>\u6267\u884c <code>setup.sh<\/code>\uff0c\u68c0\u67e5 pnpm \u4e0e Docker\u2014\u2014Docker \u53ea\u4f1a\u68c0\u6d4b\u662f\u5426\u5b58\u5728\u5e76\u7ed9\u51fa\u5b89\u88c5\u63d0\u793a\uff08\u4e0d\u4f1a\u66ff\u4f60\u88c5\uff09\uff0cpnpm \/ Decky CLI \u5219\u662f\u8f85\u52a9\u5b89\u88c5\u6216\u4e0b\u8f7d\u7f3a\u5931\u6587\u4ef6\uff1b<\/li>\n<li>\u6267\u884c <code>pnpm i<\/code>\uff1b<\/li>\n<li>\u6267\u884c <code>updatefrontendlib<\/code>\uff0c\u628a <code>@decky\/ui<\/code> \u5347\u5230\u6700\u65b0\u3002<\/li>\n<\/ol>\n<p>\u7136\u540e <code>config.sh<\/code> \u4f1a\u62f7\u8d1d <code>defsettings.json<\/code> \u751f\u6210 <code>.vscode\/settings.json<\/code>\u3002<\/p>\n<blockquote><p>\u26a0\ufe0f <strong>\u5148\u770b\u8fd9\u91cc\u518d\u590d\u5236<\/strong>\uff1a\u6a21\u677f\u91cc\u7684 <code>deckpass: &quot;ssap&quot;<\/code> \u53ea\u662f\u5360\u4f4d\u503c\uff0c<strong>\u4e0d\u8981<\/strong>\u628a\u771f\u5b9e\u5bc6\u7801\u5199\u8fdb\u751f\u6210\u7684 <code>.vscode\/settings.json<\/code> \u518d\u63d0\u4ea4\u5230\u4ed3\u5e93\u3002\u63a8\u8350\u7684\u505a\u6cd5\u662f\u751f\u6210\u4e00\u5bf9 SSH key\uff08<code>ssh-keygen<\/code> \u7136\u540e <code>ssh-copy-id deck@steamdeck.local<\/code>\uff09\uff0c\u628a <code>deckpass<\/code> \u7559\u7a7a\uff0c\u9760 <code>deckkey<\/code> \u6307\u5b9a\u7684\u79c1\u94a5\u514d\u5bc6\u767b\u5f55\uff1b\u90e8\u7f72\u811a\u672c\u91cc\u7684 <code>sudo -S<\/code> \u51e0\u5904\u786e\u5b9e\u8fd8\u9700\u8981\u5bc6\u7801\uff0c\u4f46\u81f3\u5c11 ssh \u672c\u8eab\u4e0d\u518d\u4f9d\u8d56\u660e\u6587\u3002\u6a21\u677f <code>.gitignore<\/code> \u9ed8\u8ba4\u5ffd\u7565\u4e86 <code>.vscode\/settings.json<\/code>\uff0c\u4f46\u5f88\u591a\u4eba\u4f1a&#8221;\u4e00\u4e0d\u5c0f\u5fc3&#8221; <code>git add -f<\/code> \u4e0a\u53bb\u2014\u2014\u517b\u6210 <code>git diff --cached<\/code> \u518d\u63d0\u4ea4\u7684\u4e60\u60ef\u3002<\/p><\/blockquote>\n<pre><code class=\"json\">{\n    &quot;deckip&quot;:     &quot;steamdeck.local&quot;,\n    &quot;deckport&quot;:   &quot;22&quot;,\n    &quot;deckuser&quot;:   &quot;deck&quot;,\n    &quot;deckpass&quot;:   &quot;&quot;,\n    &quot;deckkey&quot;:    &quot;-i ${env:HOME}\/.ssh\/id_rsa&quot;,\n    &quot;deckdir&quot;:    &quot;\/home\/deck&quot;,\n    &quot;pluginname&quot;: &quot;Example Plugin&quot;,\n    &quot;python.analysis.extraPaths&quot;: [&quot;.\/py_modules&quot;]\n}<\/code><\/pre>\n<p>\u628a\u524d\u51e0\u9879\u6539\u6210\u4f60\u81ea\u5df1\u7684 Deck \u914d\u7f6e\u3002\u9996\u6b21\u8fde\u63a5\u524d\u9700\u8981\u5728\u684c\u9762\u6a21\u5f0f\u7528 <code>passwd<\/code> \u7ed9 deck \u7528\u6237\u8bbe\u4e2a\u5bc6\u7801\uff08SteamOS \u9ed8\u8ba4\u65e0\u5bc6\u7801\uff09\uff0c\u7136\u540e <code>ssh-copy-id<\/code> \u63a8\u516c\u94a5\u4e0a\u53bb\uff0c\u4e4b\u540e\u5c31\u53ef\u4ee5\u628a <code>deckpass<\/code> \u6e05\u7a7a\u4e86\u3002<\/p>\n<h3>\u4e00\u6761\u547d\u4ee4\u4ece\u4ee3\u7801\u5230 Deck<\/h3>\n<p><code>build<\/code> \u4efb\u52a1\u4f1a\uff1a<\/p>\n<ol>\n<li>\u8dd1\u5b8c\u4e0a\u9762\u7684 <code>setup<\/code> + <code>settingscheck<\/code>\uff1b<\/li>\n<li>\n<p>\u6267\u884c <code>build.sh<\/code>\uff0c\u91cc\u5934\u53ea\u6709\u4e00\u884c\u6838\u5fc3\uff1a<\/p>\n<pre><code class=\"bash\">sudo -E $CLI_LOCATION\/decky plugin build $(pwd)<\/code><\/pre>\n<p>Decky CLI \u4f1a\u8bfb <code>plugin.json<\/code>\uff0c\u8dd1 <code>backend\/Dockerfile<\/code> \u7f16\u539f\u751f\u540e\u7aef\uff0c\u518d\u628a <code>dist\/<\/code>\u3001<code>main.py<\/code>\u3001<code>plugin.json<\/code> \u7b49\u6253\u6210 zip \u585e\u8fdb <code>out\/<\/code>\u3002<\/p>\n<\/li>\n<\/ol>\n<p><code>deploy<\/code> \u4efb\u52a1\u8d1f\u8d23\u628a zip \u4f20\u5230 Deck\uff1a<\/p>\n<ol>\n<li><code>chmodplugins<\/code>\uff1a\u5728 Deck \u4e0a <code>chown<\/code> \u63d2\u4ef6\u76ee\u5f55\uff0c\u907f\u514d rsync \u65f6\u56e0\u4e3a\u53ea\u8bfb\u62a5\u9519\uff1b<\/li>\n<li><code>copyzip<\/code>\uff1a<code>rsync<\/code> \u628a <code>out\/*.zip<\/code> \u4e0a\u4f20\uff1b<\/li>\n<li><code>extractzip<\/code>\uff1a\u5728 Deck \u4e0a <code>bsdtar -xzpf<\/code> \u89e3\u538b\u5230 <code>~\/homebrew\/plugins\/&lt;pluginname&gt;\/<\/code>\u3002<\/li>\n<\/ol>\n<p>\u7ec4\u5408\u4efb\u52a1 <code>builddeploy<\/code> \u4e00\u952e\u5b8c\u6210\u7f16\u8bd1 + \u4e0a\u4f20 + \u89e3\u538b\uff0c\u518d\u914d\u4e0a <code>restartdecky<\/code>\uff08<code>sudo systemctl restart plugin_loader<\/code>\uff09\u5c31\u5b8c\u6210\u4e86&#8221;\u6539\u4ee3\u7801 \u2192 \u4e00\u4e2a\u5feb\u6377\u952e \u2192 Deck \u4e0a\u770b\u6548\u679c&#8221;\u7684\u95ed\u73af\u3002<\/p>\n<blockquote><p>\u5982\u679c\u4f60\u4e0d\u7528 VS Code\uff0c\u5176\u5b9e\u53ea\u8981\u76f4\u63a5\u8c03\u7528 <code>pnpm run build<\/code> + Decky CLI + rsync \u5c31\u80fd\u590d\u523b\u540c\u6837\u7684\u6d41\u7a0b\u3002\u6574\u5957\u811a\u672c\u771f\u6b63\u7684\u4ef7\u503c\u5728\u4e8e<strong>\u628a\u5f00\u53d1\u8005\u5e38\u7528\u7684\u8fdc\u7a0b\u64cd\u4f5c\u505a\u6210\u4e86\u81ea\u5305\u542b\u3001\u5e42\u7b49\u7684 shell \u811a\u672c<\/strong>\uff0c\u53ef\u8bfb\u6027\u5f88\u9ad8\uff0c\u63a8\u8350\u9010\u5b57\u8bfb\u4e00\u904d\u3002<\/p><\/blockquote>\n<h2>\u6253\u5305\u4e0e\u5206\u53d1\uff1a\u63d2\u4ef6 zip \u7684\u76ee\u5f55\u7ed3\u6784<\/h2>\n<p>\u4e0a\u9762 <code>.vscode\/<\/code> \u90a3\u5957\u811a\u672c\u672c\u8d28\u4e0a\u5c31\u662f CI \u6d41\u6c34\u7ebf\u7684&#8221;\u672c\u5730\u7248&#8221;\u2014\u2014\u8dd1\u7684\u90fd\u662f\u540c\u4e00\u6761 <code>decky plugin build<\/code>\u3002\u641e\u61c2\u672c\u5730\u4ea7\u7269\u957f\u4ec0\u4e48\u6837\uff0c\u518d\u628a\u540c\u4e00\u6bb5 shell \u642c\u5230 GitHub Actions \u91cc\u5c31\u662f CI\u3002\u5f53\u4f60\u51c6\u5907\u628a\u63d2\u4ef6\u4ea4\u7ed9\u7528\u6237\u6216\u63d0\u4ea4\u5230 <a href=\"https:\/\/link.segmentfault.com\/?enc=%2BAvwiy%2B4HipK6DIr%2FSOGVQ%3D%3D.yGTvbFgwevmCf5ccOKVoFwSCuR4vSOEbUSjKG1%2Bek%2BLt0jm0gi%2Ff4rAXJ4ZBgCYEr3OwJnRXS5vmJMD9RLIkQg%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">decky-plugin-database<\/a> \u65f6\uff0czip \u7684\u7ed3\u6784\u662f\u6709\u4e25\u683c\u7ea6\u675f\u7684\uff1a<\/p>\n<pre><code>pluginname-v1.0.0.zip\n\u2514\u2500\u2500 pluginname\/\n    \u251c\u2500\u2500 bin\/              (\u53ef\u9009\uff0c\u539f\u751f\u540e\u7aef\u7684\u4ea7\u7269)\n    \u2502   \u2514\u2500\u2500 &lt;binary&gt;\n    \u251c\u2500\u2500 dist\/\n    \u2502   \u2514\u2500\u2500 index.js      (\u5fc5\u9700)\n    \u251c\u2500\u2500 package.json      (\u5fc5\u9700)\n    \u251c\u2500\u2500 plugin.json       (\u5fc5\u9700)\n    \u251c\u2500\u2500 main.py           (\u5fc5\u9700\uff0c\u5982\u679c\u7528\u4e86 Python \u540e\u7aef)\n    \u251c\u2500\u2500 README.md         (\u5efa\u8bae)\n    \u2514\u2500\u2500 LICENSE(.md)      (\u63d0\u4ea4\u5546\u5e97\u65f6\u5fc5\u9700)<\/code><\/pre>\n<p>\u51e0\u6761\u786c\u6027\u89c4\u5219\uff1a<\/p>\n<ul>\n<li><strong>LICENSE \u968f\u5305\u5206\u53d1<\/strong>\uff1a\u63d2\u4ef6\u5546\u5e97\uff08decky-plugin-database\uff09\u7684 README \u91cd\u70b9\u5728\u4e8e&#8221;\u5982\u679c\u8bb8\u53ef\u8bc1\u8981\u6c42\u968f\u6e90\u7801\/\u4e8c\u8fdb\u5236\u4e00\u8d77\u5206\u53d1\uff0c\u5546\u5e97\u4e0d\u4f1a\u63a5\u53d7\u7f3a\u5c11\u8bb8\u53ef\u8bc1\u7684\u63d0\u4ea4&#8221;\u2014\u2014\u6362\u8a00\u4e4b\uff0c\u662f\u5426\u5fc5\u9700\u53d6\u51b3\u4e8e\u4f60\u9009\u7684\u8bb8\u53ef\u8bc1\u672c\u8eab\u3002\u5b98\u65b9 zip \u76ee\u5f55\u7ed3\u6784\u5217\u8868\u628a\u5b83\u6807\u4e3a required\uff0c\u6700\u4fdd\u9669\u7684\u505a\u6cd5\u4ecd\u662f\u628a LICENSE \u653e\u4ed3\u5e93\u6839\u76ee\u5f55\uff0c\u7531\u6253\u5305\u6d41\u7a0b\u81ea\u52a8\u590d\u5236\u8fdb zip\uff1b<\/li>\n<li><strong>zip \u5185\u6709\u4e14\u4ec5\u6709\u4e00\u4e2a\u540c\u540d\u9876\u5c42\u76ee\u5f55<\/strong>\uff0cDecky Loader \u5c31\u662f\u9760\u8fd9\u4e2a\u76ee\u5f55\u540d\u8bc6\u522b\u63d2\u4ef6\uff1b<\/li>\n<li><strong><code>dist\/index.js<\/code> \u662f\u552f\u4e00\u5165\u53e3<\/strong>\uff0c\u6240\u6709\u524d\u7aef\u4ee3\u7801\u90fd\u5fc5\u987b\u6253\u8fdb\u8fd9\u4e00\u4e2a\u6587\u4ef6\uff1b<\/li>\n<li><strong><code>bin\/<\/code> \u4e0b\u7684\u4e8c\u8fdb\u5236\u8981\u53ef\u6267\u884c<\/strong>\uff0c\u6253\u5305\u811a\u672c\u4f1a\u81ea\u52a8 <code>chmod<\/code>\uff0c\u4f46\u4f60\u672c\u5730 <code>rsync<\/code> \u8c03\u8bd5\u65f6\u5f97\u6ce8\u610f\u6743\u9650\u3002<\/li>\n<\/ul>\n<p>\u7528\u6237\u4fa7\u5b89\u88c5\u9700\u8981\u5148\u5728 Decky Loader \u7684\u8bbe\u7f6e\u91cc\u6253\u5f00 <strong>Developer Mode<\/strong>\uff0c\u4e4b\u540e\u4f1a\u591a\u51fa\u4e24\u4e2a\u5b89\u88c5\u5165\u53e3\uff1a<\/p>\n<ul>\n<li><strong>Install Plugin from URL<\/strong>\uff1a\u7c98\u8d34\u4e00\u4e2a\u6307\u5411 zip \u7684\u516c\u5f00\u76f4\u94fe\u5373\u53ef\uff0cLoader \u4f1a\u81ea\u884c\u4e0b\u8f7d\u5e76\u89e3\u538b\u3002CI \u4ea7\u7269\u6700\u5e38\u89c1\u7684\u505a\u6cd5\u662f\u914d\u5408 <a href=\"https:\/\/link.segmentfault.com\/?enc=m91Tm80X3NY66WEBqiS8oA%3D%3D.tCTl5qJCUjZP%2FpdZXu8k%2BujHxxkQpvzLERzoZenu2MU%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">nightly.link<\/a> \u66b4\u9732 GitHub Actions artifact\uff0c\u7528\u6237\u4e00\u884c\u5730\u5740\u5c31\u80fd\u88c5\u4e0a\u6700\u65b0\u5f00\u53d1\u7248\uff1b<\/li>\n<li><strong>Install Plugin from ZIP File<\/strong>\uff1a\u628a\u672c\u5730 zip \u4e22\u8fdb\u53bb\uff0c\u9002\u5408\u79bb\u7ebf\u5206\u53d1\u6216\u5185\u90e8\u6d4b\u8bd5\u3002<\/li>\n<\/ul>\n<p>\u5982\u679c\u975e\u8981\u624b\u52a8\u5904\u7406\u6587\u4ef6\uff0c\u4e0d\u662f\u628a zip \u539f\u6837\u4e22\u8fdb <code>~\/homebrew\/plugins<\/code>\uff0c\u800c\u662f\u628a\u5b83\u89e3\u538b\u6210 <code>~\/homebrew\/plugins\/&lt;plugin-dir&gt;\/<\/code> \u8fd9\u79cd\u76ee\u5f55\u7ed3\u6784\u540e\u518d\u91cd\u542f loader\u3002<\/p>\n<h3>\u63d0\u4ea4\u5546\u5e97\u524d\u7684\u6700\u5c0f\u81ea\u68c0<\/h3>\n<p>\u6b63\u5f0f\u5411 decky-plugin-database \u63d0\u4ea4 PR \u524d\uff0c\u5efa\u8bae\u8fc7\u4e00\u904d\u8fd9\u4efd checklist\uff0c\u80fd\u6321\u4f4f\u7edd\u5927\u591a\u6570\u4e00\u773c\u9a73\u56de\uff1a<\/p>\n<ul>\n<li>[ ] zip \u5185\u7684\u9876\u5c42\u76ee\u5f55\u540d\u672a\u4e0e <code>decky-plugin-database<\/code> \u5df2\u6536\u5f55\u7684\u63d2\u4ef6\u76ee\u5f55\u51b2\u7a81\uff1b<\/li>\n<li>[ ] \u672a\u542f\u7528 <code>_root<\/code> \/ <code>root<\/code>\uff0c\u6216\u5728 PR \u63cf\u8ff0\u91cc\u89e3\u91ca\u5fc5\u8981\u6027\uff1b<\/li>\n<li>[ ] LICENSE \u6587\u4ef6\u968f zip \u5206\u53d1\uff0c\u4e14\u4e0e\u4ed3\u5e93\u5b9e\u9645\u8bb8\u53ef\u8bc1\u4e00\u81f4\uff1b<\/li>\n<li>[ ] CI \u4ea7\u7269\u80fd\u901a\u8fc7 <a href=\"https:\/\/link.segmentfault.com\/?enc=9YcDLLtL6Yvya6GirKYOSw%3D%3D.kuACDoP8ki4L829%2Bgw3gj9E95HGwCRuO4MuJ9rtNM94%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">nightly.link<\/a> \u516c\u5f00\u76f4\u94fe\u4e0b\u8f7d\uff08\u65b9\u4fbf\u5ba1\u6838\u8005\u590d\u73b0\uff09\uff1b<\/li>\n<li>[ ] README \u6807\u660e\u4e86\u652f\u6301\u7684 Decky Loader \u7248\u672c\u4e0b\u9650\uff1b<\/li>\n<li>[ ] zip \u5185<strong>\u53ea\u6709\u4e00\u4e2a<\/strong>\u4e0e <code>pluginname<\/code> \u4e00\u81f4\u7684\u9876\u5c42\u76ee\u5f55\uff0c\u6ca1\u6709\u591a\u4f59 dotfile\uff08<code>.DS_Store<\/code> \/ <code>.git\/<\/code> \/ <code>node_modules\/<\/code>\uff09\u3002<\/li>\n<\/ul>\n<h2>\u8c03\u8bd5\u4e0e\u6392\u9519<\/h2>\n<p>\u63d2\u4ef6\u4e00\u65e6\u8dd1\u8d77\u6765\u5c31\u5f88\u5bb9\u6613&#8221;\u5361\u5728\u67d0\u4e00\u5c42&#8221;\u2014\u2014\u524d\u7aef\u767d\u5c4f\u3001\u6309\u94ae\u70b9\u4e86\u6ca1\u53cd\u5e94\u3001\u540e\u7aef\u4e00\u542f\u52a8\u5c31\u5d29\u3002\u6309\u7167\u6570\u636e\u6d41\u5411\u4ece\u4e0a\u5230\u4e0b\u6392\u67e5\u6700\u7701\u65f6\u95f4\u3002<\/p>\n<h3>\u524d\u7aef\uff1aCEF DevTools<\/h3>\n<p>Steam Deck \u5f00\u542f\u5f00\u53d1\u8005\u6a21\u5f0f\u540e\uff0cSteam \u5ba2\u6237\u7aef\u4f1a\u628a CEF \u7684\u8fdc\u7a0b\u8c03\u8bd5\u7aef\u53e3\u5f00\u5728 <code>http:\/\/&lt;deck-ip&gt;:8081<\/code>\uff08Decky Loader \u81ea\u5e26\u7684 <code>scripts\/deckdebug.sh<\/code> \u5c31\u662f\u8fd9\u4e48\u7ea6\u5b9a\u7684\uff09\u3002\u7528\u684c\u9762 Chrome \u8bbf\u95ee\u8fd9\u4e2a\u5730\u5740\uff0c\u627e\u5230\u5bf9\u5e94\u7684 Steam UI \u9875\u9762\u70b9\u8fdb\u53bb\uff0c\u5c31\u662f\u719f\u6089\u7684 DevTools\uff1a\u65ad\u70b9\u3001Console\u3001Network\u3001React DevTools \u90fd\u80fd\u7528\u3002<\/p>\n<p>\u51e0\u4e2a\u9ad8\u9891\u573a\u666f\uff1a<\/p>\n<ul>\n<li><strong><code>callable(...)<\/code> \u8c03\u7528\u6ca1\u53cd\u5e94<\/strong>\uff1a\u5728 DevTools \u91cc <code>await<\/code> \u90a3\u4e2a\u4ee3\u7406\u51fd\u6570\u770b\u8fd4\u56de\u503c\u2014\u2014\u540e\u7aef\u629b\u5f02\u5e38\u65f6 <code>callable<\/code> \u8fd4\u56de\u7684 Promise \u4f1a reject\uff0c\u5fc5\u987b <code>try\/catch<\/code>\uff0c\u5426\u5219 UI \u53ea\u4f1a\u9759\u9ed8\u5931\u8d25\uff1b<\/li>\n<li><strong><code>addEventListener<\/code> \u6536\u4e0d\u5230\u4e8b\u4ef6<\/strong>\uff1a\u4e8b\u4ef6\u540d\u662f\u5b57\u7b26\u4e32\u5339\u914d\uff0c\u524d\u540e\u7aef\u62fc\u5199\u5fc5\u987b\u5b8c\u5168\u4e00\u81f4\uff1b\u540c\u65f6\u786e\u8ba4\u540e\u7aef\u7684 <code>decky.emit<\/code> \u662f\u5728 <code>_main<\/code> \u4e4b\u540e\u88ab\u8c03\u7528\u7684\uff0c<code>_main<\/code> \u4e4b\u524d emit \u4f1a\u4e22\uff1b<\/li>\n<li><strong>\u767d\u5c4f\u4f46\u6ca1\u62a5\u9519<\/strong>\uff1a\u591a\u534a\u662f <code>definePlugin<\/code> \u7684 factory \u91cc\u540c\u6b65\u629b\u4e86\u5f02\u5e38\uff0cLoader \u53ea\u4f1a\u9759\u9ed8\u8df3\u8fc7\u3002\u628a factory \u5185\u5bb9\u7528 <code>try\/catch<\/code> \u5305\u4e00\u5c42\uff0c\u9519\u8bef\u5199\u8fdb <code>console.error<\/code>\u3002<\/li>\n<\/ul>\n<h3>\u540e\u7aef\uff1a\u65e5\u5fd7\u4e0e\u76f4\u63a5\u8fd0\u884c<\/h3>\n<p>\u540e\u7aef\u7684 <code>print<\/code> \u4f1a\u5199\u5230 Decky Loader \u7684\u4e3b\u65e5\u5fd7\uff0c\u6df7\u5728\u6240\u6709\u63d2\u4ef6\u8f93\u51fa\u91cc\u5f88\u96be\u627e\u3002<strong>\u7528 <code>decky.logger<\/code> \u4ee3\u66ff <code>print<\/code><\/strong>\uff1a\u5b83\u5df2\u7ecf\u7ed1\u5b9a\u5230 <code>DECKY_PLUGIN_LOG<\/code> \u6307\u5411\u7684\u6587\u4ef6\u3002\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0cDecky Loader \u6bcf\u6b21\u542f\u52a8\u63d2\u4ef6\u65f6\u4f1a\u6309\u65f6\u95f4\u6233<strong>\u65b0\u5f00<\/strong>\u4e00\u4efd <code>.log<\/code> \u6587\u4ef6\uff0c<code>DECKY_PLUGIN_LOG<\/code> \u5e38\u91cf\u6307\u5411\u7684\u5c31\u662f&#8221;\u672c\u6b21\u542f\u52a8\u7684\u90a3\u4e00\u4efd&#8221;\uff08\u800c\u4e0d\u662f\u56fa\u5b9a\u7684 <code>plugin.log<\/code>\uff09\uff0c<code>decky.logger<\/code> \u4e5f\u5199\u5165\u8fd9\u540c\u4e00\u4e2a\u6587\u4ef6\u3002\u6240\u4ee5\u67e5\u770b\u65f6\u8981\u6309\u4fee\u6539\u65f6\u95f4\u6392\u5e8f\u62ff\u6700\u65b0\u4e00\u4efd\u3002\u5e38\u7528\u65b9\u5f0f\uff1a<\/p>\n<pre><code class=\"bash\"># 1. SSH \u5230 Deck \u4e0a\uff0c\u6309\u4fee\u6539\u65f6\u95f4\u6311\u6700\u65b0\u4e00\u4efd tail\nssh deck@steamdeck.local \/\n  &quot;LOG=\/$(ls -t ~\/homebrew\/logs\/&lt;plugin-dir&gt;\/*.log | head -n1) &amp;&amp; tail -f \/&quot;\/$LOG\/&quot;&quot;\n\n# 2. Decky Loader \u8bbe\u7f6e \u2192 Developer \u2192 Plugin Logs \u91cc\u70b9\u63d2\u4ef6\u540d<\/code><\/pre>\n<p>\u5982\u679c\u63d2\u4ef6\u6839\u672c\u8d77\u4e0d\u6765\uff08UI \u4fa7\u8fb9\u680f\u91cc\u770b\u4e0d\u5230\u56fe\u6807\uff09\uff0c\u8d70\u8fd9\u4e2a\u987a\u5e8f\uff1a<\/p>\n<ol>\n<li>\u770b <code>~\/homebrew\/services\/PluginLoader\/PluginLoader.log<\/code>\uff0cLoader \u52a0\u8f7d\u63d2\u4ef6\u5931\u8d25\u7684 traceback \u5728\u8fd9\u91cc\uff1b<\/li>\n<li>\u672c\u5730\u5148\u7528 <code>python3 -m py_compile main.py<\/code> \u505a\u4e00\u6b21\u8bed\u6cd5\u68c0\u67e5\uff1b\u771f\u6b63\u7684\u8fd0\u884c\u671f\u95ee\u9898\uff08\u5c24\u5176\u662f <code>import decky<\/code> \u7acb\u523b\u5931\u8d25\uff09<strong>\u4e0d\u80fd<\/strong>\u9760 <code>ssh<\/code> \u5230 Deck \u4e0a\u76f4\u63a5\u8dd1 <code>python3 main.py<\/code> \u590d\u73b0\u2014\u2014<code>decky<\/code> \u6a21\u5757\u662f Loader \u5728\u542f\u52a8\u63d2\u4ef6\u8fdb\u7a0b\u65f6\u6ce8\u5165\u5230 <code>sys.modules<\/code> \u7684\uff0c\u88f8\u8dd1\u4f1a\u7acb\u523b\u5728 import \u9636\u6bb5\u5c31\u5931\u8d25\uff0c\u8bef\u5bfc\u6392\u67e5\u3002\u8981\u4e48\u770b Loader \u81ea\u8eab\u548c\u63d2\u4ef6\u65e5\u5fd7\uff0c\u8981\u4e48\u81ea\u5df1\u5199\u4e00\u4e2a harness \u9884\u5148\u628a <code>decky<\/code> \u73af\u5883\u4f2a\u9020\u8fdb <code>sys.modules<\/code> \u518d\u8dd1\uff1b<\/li>\n<li><code>sudo systemctl status plugin_loader<\/code> \u770b Loader \u81ea\u8eab\u662f\u5426\u5065\u5eb7\uff0c\u5076\u5c14 SteamOS \u66f4\u65b0\u4f1a\u628a service \u641e\u6302\u3002<\/li>\n<\/ol>\n<h3>\u5e38\u89c1&#8221;\u770b\u8d77\u6765\u5f88\u8be1\u5f02&#8221;\u7684\u6545\u969c<\/h3>\n<ul>\n<li><strong>Steam \u5ba2\u6237\u7aef\u66f4\u65b0\u540e\u63d2\u4ef6\u767d\u5c4f<\/strong>\uff1a\u5927\u6982\u7387\u662f\u4f60\u52ab\u6301\u7684\u5185\u90e8 React \u7ec4\u4ef6\u6362\u4e86\u7ed3\u6784\u6216 CSS \u7c7b\u540d\u53d8\u4e86\u3002\u5148\u770b <code>console.error<\/code>\uff0c\u518d\u7528 React DevTools \u5bf9\u6bd4 DOM \u7ed3\u6784\uff1b\u957f\u671f\u65b9\u6848\u662f\u907f\u5f00 <code>afterPatch<\/code> \u6df1\u5c42\u6ce8\u5165\uff0c\u6539\u7528 <code>@decky\/ui<\/code> \u5b98\u65b9\u7ec4\u4ef6\uff1b<\/li>\n<li><strong>\u6539\u4ee3\u7801\u540e Deck \u4e0a\u6ca1\u53d8\u5316<\/strong>\uff1a\u68c0\u67e5 <code>builddeploy<\/code> \u662f\u5426\u771f\u7684\u8dd1\u5b8c\u3001<code>restartdecky<\/code> \u662f\u5426\u6267\u884c\uff1b\u6709\u65f6\u5019 <code>rsync<\/code> \u88ab Deck \u4e0a\u53ea\u8bfb\u6587\u4ef6\u7cfb\u7edf\u62e6\u4e0b\u6765\uff0c\u8868\u73b0\u4e3a\u9759\u9ed8\u5931\u8d25\uff1b<\/li>\n<li><strong>\u539f\u751f\u540e\u7aef exec \u62a5 <code>Permission denied<\/code><\/strong>\uff1a<code>bin\/<\/code> \u91cc\u7684\u4e8c\u8fdb\u5236\u4e22\u4e86\u53ef\u6267\u884c\u4f4d\uff0c<code>chmod +x<\/code> \u6216\u91cd\u65b0 <code>builddeploy<\/code> \u4e00\u6b21\u3002<\/li>\n<\/ul>\n<h2>\u6a21\u677f\u6ca1\u8986\u76d6\u3001\u4f46\u5f88\u5feb\u4f1a\u9047\u5230\u7684\u4e8b<\/h2>\n<p>\u8bfb\u5b8c\u8fd9\u5957\u6a21\u677f\uff0c\u4f60\u5df2\u7ecf\u5177\u5907\u4e00\u4e2a\u53ef\u8fd0\u884c\u7684&#8221;Hello World&#8221;\u3002\u771f\u6b63\u505a\u4ea7\u54c1\u5316\u8fd8\u6709\u51e0\u4e2a\u5e38\u89c1\u8bdd\u9898\uff1a<\/p>\n<ol>\n<li><strong>\u56fd\u9645\u5316<\/strong>\uff1aDecky \u5e76\u6ca1\u6709\u5b98\u65b9 i18n \u65b9\u6848\uff0c\u793e\u533a\u505a\u6cd5\u662f\u5728 <code>src\/data\/i18n\/*.json<\/code> \u4e0b\u653e\u7ffb\u8bd1\uff0c\u5c01\u88c5\u4e00\u4e2a <code>t(key, fallback)<\/code>\uff0c\u7b2c\u4e00\u6b21\u4f7f\u7528\u65f6\u8bfb <code>window.LocalizationManager<\/code> \u62ff\u5f53\u524d UI \u8bed\u8a00\u3002<\/li>\n<li><strong>\u6301\u4e45\u5316\u914d\u7f6e<\/strong>\uff1a\u5b98\u65b9\u65e9\u671f\u63d2\u4ef6\u666e\u904d\u4f9d\u8d56\u4e00\u4e2a\u53eb <code>settings.py<\/code> \u7684\u5c0f\u5e93\uff08\u53ef\u4ee5\u4ece\u5176\u5b83\u63d2\u4ef6\u4ed3\u5e93\u91cc\u590d\u5236\u4e00\u4efd\u5230 <code>py_modules\/settings.py<\/code>\uff09\uff0c\u5b83\u628a JSON \u914d\u7f6e\u843d\u5230 <code>DECKY_PLUGIN_SETTINGS_DIR<\/code>\uff0c\u4e24\u884c\u4ee3\u7801\u641e\u5b9a\u8bfb\u5199\u3002<\/li>\n<li><strong>\u8c03\u7528 Steam \u5185\u90e8 API<\/strong>\uff1a<code>SteamClient.*<\/code> \u662f Steam \u5ba2\u6237\u7aef\u5728 CEF \u91cc\u6302\u7684\u5168\u5c40\u5bf9\u8c61\uff0c\u80fd\u62ff\u5230\u6e38\u620f\u5217\u8868\u3001\u542f\u52a8\u53c2\u6570\u3001\u597d\u53cb\u72b6\u6001\u7b49\u3002\u6ca1\u6709\u5b98\u65b9\u6587\u6863\uff0c\u7c7b\u578b\u5b9a\u4e49\u4e3b\u8981\u6563\u843d\u5728 <code>@decky\/ui<\/code> \u4ee5\u53ca\u793e\u533a\u53cd\u5411\u5de5\u7a0b\u7684\u4ed3\u5e93\u91cc\uff0c\u5199\u7684\u65f6\u5019\u52a1\u5fc5\u505a <code>undefined<\/code> \u5224\u65ad\u3002<\/li>\n<li><strong>\u6253\u8865\u4e01 \/ \u6ce8\u5165 UI<\/strong>\uff1a<code>@decky\/ui<\/code> \u5bfc\u51fa\u4e86 <code>afterPatch<\/code>\u3001<code>findInReactTree<\/code>\u3001<code>findModuleByExport<\/code> \u7b49\u5de5\u5177\uff0c\u7528\u6765\u52ab\u6301 Steam \u81ea\u5df1\u7684 React \u7ec4\u4ef6\uff08\u4f8b\u5982\u5728\u6e38\u620f\u53f3\u952e\u83dc\u5355\u52a0\u4e00\u9879\uff09\u3002\u8fd9\u7c7b\u4ee3\u7801\u5bf9 Steam \u5ba2\u6237\u7aef\u7248\u672c\u975e\u5e38\u654f\u611f\uff0c\u6700\u597d\u5199\u597d <code>try\/catch<\/code> \u548c fallback\uff0c\u4e00\u6b21\u66f4\u65b0\u5c31\u53ef\u80fd\u5931\u6548\u3002<\/li>\n<li><strong>\u8c03\u7528\u539f\u751f\u4e8c\u8fdb\u5236<\/strong>\uff1a\u60f3\u5728 Python \u540e\u7aef\u91cc\u8c03 <code>backend\/out\/<\/code> \u7f16\u51fa\u7684\u7a0b\u5e8f\uff0c\u7528 <code>asyncio.create_subprocess_exec<\/code> \u6bd4 <code>subprocess.run<\/code> \u66f4\u5408\u9002\u2014\u2014\u4e0d\u963b\u585e\u4e8b\u4ef6\u5faa\u73af\uff0c\u80fd <code>await proc.communicate()<\/code> \u62ff stdout\/stderr\uff1b\u8def\u5f84\u7528 <code>os.path.join(decky.DECKY_PLUGIN_DIR, &quot;bin&quot;, &quot;&lt;name&gt;&quot;)<\/code> \u62fc\uff0c\u522b\u5199\u6b7b\u3002<\/li>\n<li><strong>\u957f\u4efb\u52a1\u53d6\u6d88<\/strong>\uff1a<code>asyncio.create_task<\/code> \u8fd4\u56de\u7684 Task \u5b58\u8d77\u6765\uff0c\u524d\u7aef\u8981\u4e2d\u6b62\u65f6\u901a\u8fc7\u4e00\u4e2a <code>cancel_*<\/code> RPC \u8c03 <code>task.cancel()<\/code>\uff1b\u4efb\u52a1\u91cc\u7528 <code>try\/except asyncio.CancelledError<\/code> \u505a\u6e05\u7406\u3002<\/li>\n<li><strong>\u5e76\u53d1\u5171\u4eab\u72b6\u6001<\/strong>\uff1a\u591a\u4e2a\u524d\u7aef RPC \u53ef\u80fd\u5e76\u53d1\u8fdb\u6765\uff08\u624b\u67c4\u8fde\u70b9\u3001\u591a\u4e2a\u9762\u677f\u540c\u65f6\u6253\u5f00\uff09\u3002\u6539\u5171\u4eab\u72b6\u6001\u524d\u5957 <code>asyncio.Lock<\/code>\uff0c\u6bd4\u4e8b\u540e debug \u7ade\u6001\u5feb\u5f97\u591a\u3002\u914d\u7f6e\u843d\u76d8\u540c\u7406\uff0c\u5efa\u8bae\u7528 <code>tempfile.NamedTemporaryFile<\/code> \u5199\u5b8c\u540e <code>os.replace<\/code> \u539f\u5b50\u66ff\u6362\uff0c\u800c\u4e0d\u662f\u76f4\u63a5 <code>open(path, 'w')<\/code>\u2014\u2014Steam Deck \u7535\u91cf\u8017\u5c3d\u7684\u4e00\u77ac\u95f4\uff0c<code>json.dump<\/code> \u5199\u4e86\u4e00\u534a\u4f1a\u7559\u4e0b\u4e00\u4e2a\u635f\u574f\u7684\u914d\u7f6e\u6587\u4ef6\uff0c\u4e0b\u6b21\u542f\u52a8\u63d2\u4ef6\u5c31\u76f4\u63a5\u70b8\u4e86\u3002<\/li>\n<li><strong>CI \u53d1\u5e03<\/strong>\uff1a<code>GitHub Actions<\/code> + <code>softprops\/action-gh-release<\/code> \u662f\u793e\u533a\u5e38\u7528\u65b9\u6848\uff1apush \u5230 <code>main<\/code> \u6253\u4e00\u4e2a artifact\uff08\u53ef\u4ee5\u7528 <a href=\"https:\/\/link.segmentfault.com\/?enc=gi8EV7R5vUSqQoC9STuKsQ%3D%3D.ANcSKVExKe32X1JMVHXhAgFkeqLxBsllEiYvi2URpmY%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">nightly.link<\/a> \u7ed9\u7528\u6237\u5206\u53d1\u5f00\u53d1\u7248\uff09\uff0c\u6253 tag \u65f6\u81ea\u52a8\u751f\u6210 Release \u548c zip\u3002<\/li>\n<\/ol>\n<h2>\u5199\u5728\u6700\u540e<\/h2>\n<p>\u4ece\u4e00\u4e2a\u6a21\u677f\u51fa\u53d1\u7406\u89e3 Decky\uff0c\u5176\u5b9e\u5c31\u662f\u8bb0\u4f4f\u8fd9\u56db\u5c42\uff1a<\/p>\n<ol>\n<li><strong><code>plugin.json<\/code> \u4e0e <code>package.json<\/code><\/strong> \u58f0\u660e&#8221;\u6211\u662f\u8c01&#8221;\uff1b<\/li>\n<li><strong><code>src\/index.tsx<\/code> + <code>definePlugin<\/code><\/strong> \u63d0\u4f9b\u5d4c\u5165 Steam \u7684 UI\uff1b<\/li>\n<li><strong><code>main.py<\/code> + <code>decky<\/code> \u6a21\u5757<\/strong>\u63d0\u4f9b\u53d7\u7ea6\u675f\u7684\u540e\u7aef\u80fd\u529b\uff1b<\/li>\n<li><strong><code>.vscode\/<\/code>\u3001<code>backend\/<\/code>\u3001Decky CLI<\/strong> \u628a\u5f00\u53d1\u5230\u53d1\u5e03\u7684\u6d41\u7a0b\u4e32\u8d77\u6765\u3002<\/li>\n<\/ol>\n<p>\u7b49\u8fd9\u56db\u5c42\u5728\u4f60\u8111\u5b50\u91cc\u8dd1\u901a\u4e86\uff0c\u5c31\u53ef\u4ee5\u5927\u80c6\u6254\u6389\u6a21\u677f\u3001\u6309\u81ea\u5df1\u7684\u5ba1\u7f8e\u91cd\u7ec4\u4ee3\u7801 \u2014\u2014 \u4f60\u505a\u7684\u4e8b\u60c5\u672c\u8d28\u4e0a\u53ea\u662f\u5728 Steam \u5ba2\u6237\u7aef\u91cc\u585e\u4e00\u4e2a React \u7ec4\u4ef6\uff0c\u4ee5\u53ca\u5728 Deck \u4e0a\u8dd1\u4e00\u4e2a Python \u5c0f\u670d\u52a1\u3002<\/p>\n<blockquote>\n<p>\u53c2\u8003\u8d44\u6e90\uff1a<\/p>\n<ul>\n<li>\u5b98\u65b9\u6a21\u677f\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=8R8rNf4OUkecZ%2BCu0Xtqag%3D%3D.CEPQfltwXmp%2BUEYZ7HoOBcu1JaLgS093BPe%2B%2FE2tC2oNCPZn6H0AVBwka75XZW%2BE6QqTGXZP8VWZAGAdSPOXiA%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/github.com\/SteamDeckHomebrew\/decky-plugin-template<\/a><\/li>\n<li>Decky Loader\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=ZAYag9HPdDfmAX6DqjEgeQ%3D%3D.5fnm5srcf6ukYZBKhmpzyPno6wcJjVfyqMnUrB5PJZww4yT0VIFJbqhaxTPv1criMGV83PzUyekT53XvdQGOaw%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/github.com\/SteamDeckHomebrew\/decky-loader<\/a><\/li>\n<li>Decky CLI\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=R6w%2Fx075PjV%2FsR2gm42REg%3D%3D.KTc85Zd2Q8411sTfMnlBzHKy7xYQy7TtnTgj0Q%2FqA9wvba5%2Bu9ZxhavbqnZEAH5Q\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/github.com\/SteamDeckHomebrew\/cli<\/a><\/li>\n<li>\u63d2\u4ef6\u5546\u5e97\u4e0e\u5ba1\u6838\u6d41\u7a0b\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=gJJkb5zH3ns8%2FsUeqm6a7w%3D%3D.L8fX6Il%2BTSI01Og%2BhCdOHpiQO4n4QgYezFSviYA9xCLhPqVWydhEnhhJUZuLxcKZNeLSORmQNdp78WoElRocbw%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/github.com\/SteamDeckHomebrew\/decky-plugin-database<\/a><\/li>\n<li>\u793e\u533a Wiki\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=bmkKrQnACmHw1%2Fx1PIWdJg%3D%3D.PbuH%2B9QU%2F47ZbKmPlpLcsTDr0QXdR9O2MzaLcaALqKXjT8YGMkdKkfCDtJYo4cPOlSOoKWD3HIdcSQTMghSoFw%3D%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/wiki.deckbrew.xyz\/en\/user-guide\/home#plugin-development<\/a><\/li>\n<\/ul>\n<\/blockquote>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000047756688\">https:\/\/segmentfault.com\/a\/1190000047756688<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u9762\u5411\u7b2c\u4e00\u6b21\u63a5\u89e6 Steam Deck \u63d2\u4ef6\u5f00\u53d1\u7684\u8bfb\u8005\u3002\u672c\u6587\u4ee5\u5b98\u65b9\u4ed3\u5e93 decky-plugin-templat [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10021366","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021366","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10021366"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021366\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10021366"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10021366"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10021366"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}