@sirenodeck/sirenodeck 0.1.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/bin/_wrapper-shared.js +42 -0
  4. package/bin/dev.js +95 -0
  5. package/bin/sirenodeck.js +42 -0
  6. package/config/default-config.yml +117 -0
  7. package/config/starter-config.yml +54 -0
  8. package/dist/main.mjs +64702 -0
  9. package/package.json +140 -0
  10. package/src/action/executor.ts +156 -0
  11. package/src/action/index.ts +7 -0
  12. package/src/addon/api-types.ts +15 -0
  13. package/src/addon/api.ts +461 -0
  14. package/src/addon/check-runner.ts +84 -0
  15. package/src/addon/index.ts +5 -0
  16. package/src/addon/loader.ts +441 -0
  17. package/src/addon/registry.ts +230 -0
  18. package/src/addon/spec.ts +69 -0
  19. package/src/api/addon.ts +33 -0
  20. package/src/api/asset-url.ts +24 -0
  21. package/src/api/index.ts +50 -0
  22. package/src/api/protocol-internal.ts +656 -0
  23. package/src/api/protocol.ts +2 -0
  24. package/src/api/react/index.ts +11 -0
  25. package/src/api/react/registry.ts +91 -0
  26. package/src/api/react/use-addon-channel.ts +26 -0
  27. package/src/api/react/use-button-state.tsx +23 -0
  28. package/src/api/react/use-deck.ts +24 -0
  29. package/src/assets/logo.png +0 -0
  30. package/src/assets/logo72x72.png +0 -0
  31. package/src/assets/logoFull.png +0 -0
  32. package/src/builtin-addons/brightness/README.md +51 -0
  33. package/src/builtin-addons/brightness/buttons/brightness/backend.ts +13 -0
  34. package/src/builtin-addons/brightness/buttons/brightness/config.ts +10 -0
  35. package/src/builtin-addons/brightness/buttons/brightness/frontend.tsx +50 -0
  36. package/src/builtin-addons/brightness/domain/macos.ts +46 -0
  37. package/src/builtin-addons/brightness/index.d.ts +5 -0
  38. package/src/builtin-addons/brightness/index.ts +20 -0
  39. package/src/builtin-addons/brightness/poller.ts +27 -0
  40. package/src/builtin-addons/brightness/providers/darwin.ts +107 -0
  41. package/src/builtin-addons/brightness/providers/index.ts +81 -0
  42. package/src/builtin-addons/brightness/providers/linux.ts +108 -0
  43. package/src/builtin-addons/brightness/providers/windows.ts +77 -0
  44. package/src/builtin-addons/brightness/sirenodeck.json +6 -0
  45. package/src/builtin-addons/coding-agents/assets/claude-code.svg +1 -0
  46. package/src/builtin-addons/coding-agents/assets/opencode-dark-square.svg +1 -0
  47. package/src/builtin-addons/coding-agents/buttons/agent/backend.ts +82 -0
  48. package/src/builtin-addons/coding-agents/buttons/agent/config.ts +13 -0
  49. package/src/builtin-addons/coding-agents/buttons/agent/frontend.tsx +130 -0
  50. package/src/builtin-addons/coding-agents/buttons/agent/pages.tsx +150 -0
  51. package/src/builtin-addons/coding-agents/buttons/summary/backend.ts +48 -0
  52. package/src/builtin-addons/coding-agents/buttons/summary/config.ts +11 -0
  53. package/src/builtin-addons/coding-agents/buttons/summary/frontend.tsx +254 -0
  54. package/src/builtin-addons/coding-agents/decks/agents.ts +69 -0
  55. package/src/builtin-addons/coding-agents/global/backend.ts +402 -0
  56. package/src/builtin-addons/coding-agents/global-entry.ts +7 -0
  57. package/src/builtin-addons/coding-agents/hook/script.ts +123 -0
  58. package/src/builtin-addons/coding-agents/index.ts +15 -0
  59. package/src/builtin-addons/coding-agents/manifest.ts +115 -0
  60. package/src/builtin-addons/coding-agents/providers/claude-code.ts +470 -0
  61. package/src/builtin-addons/coding-agents/providers/claude-instances.ts +184 -0
  62. package/src/builtin-addons/coding-agents/providers/opencode-instances.ts +108 -0
  63. package/src/builtin-addons/coding-agents/providers/opencode.ts +636 -0
  64. package/src/builtin-addons/coding-agents/providers/registry.ts +120 -0
  65. package/src/builtin-addons/coding-agents/providers/spawn.ts +112 -0
  66. package/src/builtin-addons/coding-agents/shared/claude-hook-events.ts +55 -0
  67. package/src/builtin-addons/coding-agents/shared/claude-status.ts +236 -0
  68. package/src/builtin-addons/coding-agents/shared/live-count.ts +39 -0
  69. package/src/builtin-addons/coding-agents/shared/notifier.ts +58 -0
  70. package/src/builtin-addons/coding-agents/shared/opencode-status.ts +82 -0
  71. package/src/builtin-addons/coding-agents/shared/snapshot.ts +66 -0
  72. package/src/builtin-addons/coding-agents/shared/state.ts +72 -0
  73. package/src/builtin-addons/coding-agents/sirenodeck.json +6 -0
  74. package/src/builtin-addons/coding-agents/types/types.ts +33 -0
  75. package/src/builtin-addons/core/README.md +89 -0
  76. package/src/builtin-addons/core/buttons/action/backend.ts +7 -0
  77. package/src/builtin-addons/core/buttons/action/config.ts +13 -0
  78. package/src/builtin-addons/core/buttons/action/frontend.tsx +16 -0
  79. package/src/builtin-addons/core/buttons/change-deck/backend.ts +18 -0
  80. package/src/builtin-addons/core/buttons/change-deck/config.ts +15 -0
  81. package/src/builtin-addons/core/buttons/change-deck/frontend.tsx +18 -0
  82. package/src/builtin-addons/core/buttons/media-sample/backend.ts +8 -0
  83. package/src/builtin-addons/core/buttons/media-sample/config.ts +7 -0
  84. package/src/builtin-addons/core/buttons/media-sample/frontend.tsx +2 -0
  85. package/src/builtin-addons/core/buttons/page-nav/backend.ts +28 -0
  86. package/src/builtin-addons/core/buttons/page-nav/config.ts +9 -0
  87. package/src/builtin-addons/core/buttons/page-nav/frontend.tsx +68 -0
  88. package/src/builtin-addons/core/buttons/toggle/backend.ts +119 -0
  89. package/src/builtin-addons/core/buttons/toggle/config.ts +57 -0
  90. package/src/builtin-addons/core/buttons/toggle/frontend.tsx +57 -0
  91. package/src/builtin-addons/core/buttons/toggle/global-service.ts +174 -0
  92. package/src/builtin-addons/core/index.ts +120 -0
  93. package/src/builtin-addons/core/sirenodeck.json +6 -0
  94. package/src/builtin-addons/date-time/README.md +73 -0
  95. package/src/builtin-addons/date-time/buttons/analog-clock/backend.ts +10 -0
  96. package/src/builtin-addons/date-time/buttons/analog-clock/config.ts +4 -0
  97. package/src/builtin-addons/date-time/buttons/analog-clock/frontend.tsx +88 -0
  98. package/src/builtin-addons/date-time/buttons/custom/backend.ts +10 -0
  99. package/src/builtin-addons/date-time/buttons/custom/config.ts +9 -0
  100. package/src/builtin-addons/date-time/buttons/custom/frontend.tsx +26 -0
  101. package/src/builtin-addons/date-time/buttons/date/backend.ts +10 -0
  102. package/src/builtin-addons/date-time/buttons/date/config.ts +10 -0
  103. package/src/builtin-addons/date-time/buttons/date/frontend.tsx +42 -0
  104. package/src/builtin-addons/date-time/buttons/time/backend.ts +10 -0
  105. package/src/builtin-addons/date-time/buttons/time/config.ts +9 -0
  106. package/src/builtin-addons/date-time/buttons/time/frontend.tsx +29 -0
  107. package/src/builtin-addons/date-time/index.d.ts +5 -0
  108. package/src/builtin-addons/date-time/index.ts +30 -0
  109. package/src/builtin-addons/date-time/poller.ts +11 -0
  110. package/src/builtin-addons/date-time/shared/format-date.ts +36 -0
  111. package/src/builtin-addons/date-time/shared/format.ts +105 -0
  112. package/src/builtin-addons/date-time/shared/use-now.ts +10 -0
  113. package/src/builtin-addons/date-time/sirenodeck.json +6 -0
  114. package/src/builtin-addons/emoji-selector/README.md +45 -0
  115. package/src/builtin-addons/emoji-selector/assets/launcher.svg +10 -0
  116. package/src/builtin-addons/emoji-selector/assets/smileys.svg +8 -0
  117. package/src/builtin-addons/emoji-selector/buttons/category/backend.ts +14 -0
  118. package/src/builtin-addons/emoji-selector/buttons/category/config.ts +8 -0
  119. package/src/builtin-addons/emoji-selector/buttons/category/frontend.tsx +19 -0
  120. package/src/builtin-addons/emoji-selector/buttons/emoji/backend.ts +7 -0
  121. package/src/builtin-addons/emoji-selector/buttons/emoji/config.ts +9 -0
  122. package/src/builtin-addons/emoji-selector/buttons/emoji/frontend.tsx +11 -0
  123. package/src/builtin-addons/emoji-selector/buttons/launcher/backend.ts +13 -0
  124. package/src/builtin-addons/emoji-selector/buttons/launcher/config.ts +6 -0
  125. package/src/builtin-addons/emoji-selector/buttons/launcher/frontend.tsx +31 -0
  126. package/src/builtin-addons/emoji-selector/data/categories.ts +1084 -0
  127. package/src/builtin-addons/emoji-selector/decks/index.ts +137 -0
  128. package/src/builtin-addons/emoji-selector/index.d.ts +5 -0
  129. package/src/builtin-addons/emoji-selector/index.ts +29 -0
  130. package/src/builtin-addons/emoji-selector/sirenodeck.json +6 -0
  131. package/src/builtin-addons/emoji-selector/support.ts +49 -0
  132. package/src/builtin-addons/index.ts +13 -0
  133. package/src/builtin-addons/internal-settings/README.md +26 -0
  134. package/src/builtin-addons/internal-settings/backend.ts +58 -0
  135. package/src/builtin-addons/internal-settings/buttons/app-info/backend.ts +7 -0
  136. package/src/builtin-addons/internal-settings/buttons/app-info/config.ts +13 -0
  137. package/src/builtin-addons/internal-settings/buttons/app-info/frontend.tsx +18 -0
  138. package/src/builtin-addons/internal-settings/buttons/brightness/common/frontend.tsx +50 -0
  139. package/src/builtin-addons/internal-settings/buttons/brightness-down/backend.ts +16 -0
  140. package/src/builtin-addons/internal-settings/buttons/brightness-down/config.ts +6 -0
  141. package/src/builtin-addons/internal-settings/buttons/brightness-down/frontend.tsx +8 -0
  142. package/src/builtin-addons/internal-settings/buttons/brightness-up/backend.ts +16 -0
  143. package/src/builtin-addons/internal-settings/buttons/brightness-up/config.ts +6 -0
  144. package/src/builtin-addons/internal-settings/buttons/brightness-up/frontend.tsx +8 -0
  145. package/src/builtin-addons/internal-settings/decks/settings.ts +25 -0
  146. package/src/builtin-addons/internal-settings/index.d.ts +4 -0
  147. package/src/builtin-addons/internal-settings/index.ts +53 -0
  148. package/src/builtin-addons/internal-settings/logo72x72.png +0 -0
  149. package/src/builtin-addons/internal-settings/sirenodeck.json +6 -0
  150. package/src/builtin-addons/media/README.md +45 -0
  151. package/src/builtin-addons/media/backend.ts +143 -0
  152. package/src/builtin-addons/media/buttons/media-player/backend.ts +19 -0
  153. package/src/builtin-addons/media/buttons/media-player/components/MediaStatus.tsx +25 -0
  154. package/src/builtin-addons/media/buttons/media-player/components/MediaSurface.tsx +52 -0
  155. package/src/builtin-addons/media/buttons/media-player/components/ProgressBar.tsx +21 -0
  156. package/src/builtin-addons/media/buttons/media-player/components/status-meta.ts +36 -0
  157. package/src/builtin-addons/media/buttons/media-player/config.ts +4 -0
  158. package/src/builtin-addons/media/buttons/media-player/frontend.tsx +29 -0
  159. package/src/builtin-addons/media/buttons/volume/common/frontend.tsx +52 -0
  160. package/src/builtin-addons/media/buttons/volume/mute/backend.ts +13 -0
  161. package/src/builtin-addons/media/buttons/volume/mute/config.ts +4 -0
  162. package/src/builtin-addons/media/buttons/volume/mute/frontend.tsx +26 -0
  163. package/src/builtin-addons/media/buttons/volume/volume-down/backend.ts +14 -0
  164. package/src/builtin-addons/media/buttons/volume/volume-down/config.ts +6 -0
  165. package/src/builtin-addons/media/buttons/volume/volume-down/frontend.tsx +10 -0
  166. package/src/builtin-addons/media/buttons/volume/volume-up/backend.ts +14 -0
  167. package/src/builtin-addons/media/buttons/volume/volume-up/config.ts +6 -0
  168. package/src/builtin-addons/media/buttons/volume/volume-up/frontend.tsx +8 -0
  169. package/src/builtin-addons/media/checks.ts +104 -0
  170. package/src/builtin-addons/media/executor.ts +36 -0
  171. package/src/builtin-addons/media/index.d.ts +5 -0
  172. package/src/builtin-addons/media/index.ts +53 -0
  173. package/src/builtin-addons/media/progress.ts +16 -0
  174. package/src/builtin-addons/media/providers/darwin.ts +391 -0
  175. package/src/builtin-addons/media/providers/index.ts +27 -0
  176. package/src/builtin-addons/media/providers/linux.ts +196 -0
  177. package/src/builtin-addons/media/providers/types.ts +48 -0
  178. package/src/builtin-addons/media/providers/windows.ts +121 -0
  179. package/src/builtin-addons/media/sirenodeck.json +6 -0
  180. package/src/builtin-addons/media/state.ts +19 -0
  181. package/src/builtin-addons/register-builtins.ts +65 -0
  182. package/src/builtin-addons/session/README.md +14 -0
  183. package/src/builtin-addons/session/buttons/session-info/backend.ts +8 -0
  184. package/src/builtin-addons/session/buttons/session-info/config.ts +4 -0
  185. package/src/builtin-addons/session/buttons/session-info/frontend.tsx +2 -0
  186. package/src/builtin-addons/session/buttons/time/backend.ts +7 -0
  187. package/src/builtin-addons/session/buttons/time/config.ts +6 -0
  188. package/src/builtin-addons/session/buttons/time/frontend.tsx +2 -0
  189. package/src/builtin-addons/session/index.d.ts +5 -0
  190. package/src/builtin-addons/session/index.ts +18 -0
  191. package/src/builtin-addons/session/sirenodeck.json +6 -0
  192. package/src/builtin-addons/system-status/README.md +169 -0
  193. package/src/builtin-addons/system-status/buttons/_shared.ts +131 -0
  194. package/src/builtin-addons/system-status/buttons/system-status/frontend.tsx +224 -0
  195. package/src/builtin-addons/system-status/buttons/system-status/schemas.ts +67 -0
  196. package/src/builtin-addons/system-status/domain/catalog.ts +53 -0
  197. package/src/builtin-addons/system-status/domain/chart-sampler.ts +58 -0
  198. package/src/builtin-addons/system-status/domain/display-metrics.ts +128 -0
  199. package/src/builtin-addons/system-status/domain/index.ts +10 -0
  200. package/src/builtin-addons/system-status/domain/live-metrics.ts +1021 -0
  201. package/src/builtin-addons/system-status/domain/metric-ids.ts +0 -0
  202. package/src/builtin-addons/system-status/index.ts +16 -0
  203. package/src/builtin-addons/system-status/manifest.ts +105 -0
  204. package/src/builtin-addons/system-status/shared/metrics-catalog.ts +316 -0
  205. package/src/builtin-addons/system-status/sirenodeck.json +6 -0
  206. package/src/builtin-addons/test-buildin/decks/action-buttons-test.ts +56 -0
  207. package/src/builtin-addons/test-buildin/index.d.ts +5 -0
  208. package/src/builtin-addons/test-buildin/index.ts +16 -0
  209. package/src/builtin-addons/value-display/README.md +59 -0
  210. package/src/builtin-addons/value-display/backend.ts +86 -0
  211. package/src/builtin-addons/value-display/buttons/value-display/backend.ts +17 -0
  212. package/src/builtin-addons/value-display/buttons/value-display/config.ts +36 -0
  213. package/src/builtin-addons/value-display/buttons/value-display/frontend.tsx +54 -0
  214. package/src/builtin-addons/value-display/index.d.ts +5 -0
  215. package/src/builtin-addons/value-display/index.ts +23 -0
  216. package/src/builtin-addons/value-display/sirenodeck.json +6 -0
  217. package/src/builtin-addons/weather/README.md +46 -0
  218. package/src/builtin-addons/weather/backend.ts +121 -0
  219. package/src/builtin-addons/weather/buttons/weather/backend.ts +27 -0
  220. package/src/builtin-addons/weather/buttons/weather/config.ts +29 -0
  221. package/src/builtin-addons/weather/buttons/weather/frontend.tsx +63 -0
  222. package/src/builtin-addons/weather/buttons/weather/icons.tsx +88 -0
  223. package/src/builtin-addons/weather/buttons/weather/pages.tsx +109 -0
  224. package/src/builtin-addons/weather/domain/codes.ts +35 -0
  225. package/src/builtin-addons/weather/domain/fetch.ts +47 -0
  226. package/src/builtin-addons/weather/index.d.ts +5 -0
  227. package/src/builtin-addons/weather/index.ts +23 -0
  228. package/src/builtin-addons/weather/poller.ts +70 -0
  229. package/src/builtin-addons/weather/provider/city-key.ts +9 -0
  230. package/src/builtin-addons/weather/provider/codes.ts +35 -0
  231. package/src/builtin-addons/weather/provider/fetch.ts +97 -0
  232. package/src/builtin-addons/weather/provider/geocode.ts +67 -0
  233. package/src/builtin-addons/weather/provider/types.ts +42 -0
  234. package/src/builtin-addons/weather/sirenodeck.json +6 -0
  235. package/src/cli/coding-agents-onboarding.ts +434 -0
  236. package/src/cli/commands/addon-decks.ts +441 -0
  237. package/src/cli/commands/addon-registry.ts +639 -0
  238. package/src/cli/commands/emulator-mode.ts +289 -0
  239. package/src/cli/commands/install.ts +128 -0
  240. package/src/cli/commands/logs.ts +74 -0
  241. package/src/cli/commands/network-bind.ts +111 -0
  242. package/src/cli/commands/pipeline/helpers.ts +75 -0
  243. package/src/cli/commands/pipeline/preflight.ts +20 -0
  244. package/src/cli/commands/port-identity.ts +152 -0
  245. package/src/cli/commands/reload.ts +37 -0
  246. package/src/cli/commands/restart.ts +139 -0
  247. package/src/cli/commands/run.ts +2270 -0
  248. package/src/cli/commands/service/install.ts +121 -0
  249. package/src/cli/commands/service/render-template.ts +158 -0
  250. package/src/cli/commands/service-manager.ts +184 -0
  251. package/src/cli/commands/spawn-daemon.ts +251 -0
  252. package/src/cli/commands/start.ts +1061 -0
  253. package/src/cli/commands/status.ts +110 -0
  254. package/src/cli/commands/stop.ts +121 -0
  255. package/src/cli/commands/subprocess-supervisor.ts +203 -0
  256. package/src/cli/commands/system-requirements.ts +699 -0
  257. package/src/cli/cwd.ts +28 -0
  258. package/src/cli/first-run.ts +149 -0
  259. package/src/cli/http-server.ts +244 -0
  260. package/src/cli/index.ts +355 -0
  261. package/src/cli/install-root.ts +26 -0
  262. package/src/cli/main.ts +194 -0
  263. package/src/cli/package-manager.ts +175 -0
  264. package/src/cli/probe-deps.ts +45 -0
  265. package/src/cli/prompt.ts +22 -0
  266. package/src/cli/startup-display.ts +452 -0
  267. package/src/config/builtin-icons.ts +57 -0
  268. package/src/config/config-diff.ts +54 -0
  269. package/src/config/discovery.ts +64 -0
  270. package/src/config/icon-resolver.ts +90 -0
  271. package/src/config/include-resolver.ts +202 -0
  272. package/src/config/index.ts +9 -0
  273. package/src/config/loader.ts +187 -0
  274. package/src/config/mutation.ts +514 -0
  275. package/src/config/reference-expander.ts +84 -0
  276. package/src/config/schemas.ts +223 -0
  277. package/src/config/validation.ts +254 -0
  278. package/src/core/asset-id.ts +21 -0
  279. package/src/core/gesture-state.ts +344 -0
  280. package/src/core/icon-asset-registry.ts +127 -0
  281. package/src/core/icon-source.ts +86 -0
  282. package/src/core/index.ts +30 -0
  283. package/src/core/mime.ts +16 -0
  284. package/src/core/pagination.ts +90 -0
  285. package/src/core/pub-sub.ts +126 -0
  286. package/src/core/settings.ts +153 -0
  287. package/src/core/store.ts +88 -0
  288. package/src/core/watcher.ts +73 -0
  289. package/src/deck/addon-handler-bridge.ts +516 -0
  290. package/src/deck/deck-config.ts +271 -0
  291. package/src/deck/editor-surfaces.ts +87 -0
  292. package/src/deck/host-context.ts +18 -0
  293. package/src/deck/index.ts +102 -0
  294. package/src/deck/macro-parse.ts +182 -0
  295. package/src/deck/methods.ts +348 -0
  296. package/src/deck/paginate-deck.ts +98 -0
  297. package/src/deck/position-buttons.ts +57 -0
  298. package/src/deck/runtime/index.ts +5 -0
  299. package/src/deck/runtime/runtime.ts +1060 -0
  300. package/src/deck/runtime-subscriptions.ts +45 -0
  301. package/src/deck/system-back-injection.ts +62 -0
  302. package/src/deck/system-buttons/registry.tsx +97 -0
  303. package/src/deck/system-buttons/types.ts +14 -0
  304. package/src/device/black-frame.ts +73 -0
  305. package/src/device/index.ts +13 -0
  306. package/src/device/linux-udev.ts +36 -0
  307. package/src/device/models.ts +89 -0
  308. package/src/device/reconnecting.ts +193 -0
  309. package/src/device/registry.ts +38 -0
  310. package/src/device/stream-deck.ts +155 -0
  311. package/src/index.ts +74 -0
  312. package/src/outputClient/emulator.ts +357 -0
  313. package/src/outputClient/index.ts +26 -0
  314. package/src/outputClient/open-browser.ts +26 -0
  315. package/src/outputClient/real.ts +393 -0
  316. package/src/outputClient/types.ts +83 -0
  317. package/src/render/browser-renderer.ts +240 -0
  318. package/src/render/buffer-hash.ts +20 -0
  319. package/src/render/editor-handler.ts +197 -0
  320. package/src/render/emulator-server.ts +128 -0
  321. package/src/render/icon-source-resolver.ts +69 -0
  322. package/src/render/index.ts +16 -0
  323. package/src/render/protocol.ts +61 -0
  324. package/src/render/push-raw-image.ts +100 -0
  325. package/src/render/screenshot-cadence.ts +87 -0
  326. package/src/render/state-publisher.ts +110 -0
  327. package/src/render/vite-server.ts +138 -0
  328. package/src/render/ws-bridge.ts +315 -0
  329. package/src/system/device-selection.ts +80 -0
  330. package/src/system/glob-match.ts +74 -0
  331. package/src/system/providers/active-app/darwin.ts +117 -0
  332. package/src/system/providers/active-app/linux.ts +245 -0
  333. package/src/system/providers/active-app/wayland-gnome.ts +303 -0
  334. package/src/system/providers/active-app/windows.ts +94 -0
  335. package/src/system/providers/active-app.ts +97 -0
  336. package/src/system/providers/clipboard/darwin.ts +65 -0
  337. package/src/system/providers/clipboard/linux.ts +163 -0
  338. package/src/system/providers/clipboard/windows.ts +94 -0
  339. package/src/system/providers/clipboard.ts +84 -0
  340. package/src/system/providers/error.ts +16 -0
  341. package/src/system/providers/key-macro/darwin.ts +221 -0
  342. package/src/system/providers/key-macro/linux.ts +651 -0
  343. package/src/system/providers/key-macro/parser.ts +215 -0
  344. package/src/system/providers/key-macro/windows.ts +476 -0
  345. package/src/system/providers/key-macro.ts +79 -0
  346. package/src/system/providers/notification/darwin.ts +36 -0
  347. package/src/system/providers/notification/linux.ts +115 -0
  348. package/src/system/providers/notification/windows.ts +72 -0
  349. package/src/system/providers/notification.ts +75 -0
  350. package/src/system/providers/session/darwin.ts +82 -0
  351. package/src/system/providers/session/linux.ts +175 -0
  352. package/src/system/providers/session/windows.ts +73 -0
  353. package/src/system/providers/session.ts +89 -0
  354. package/src/system/providers/shared.ts +65 -0
  355. package/src/system/requirements.ts +235 -0
  356. package/src/system/setup-wizard/config-seed.ts +30 -0
  357. package/src/system/setup-wizard/format.ts +275 -0
  358. package/src/system/setup-wizard/index.ts +45 -0
  359. package/src/system/setup-wizard/open-settings.ts +26 -0
  360. package/src/system/setup-wizard/plan.ts +230 -0
  361. package/src/system/setup-wizard/probe-cache.ts +34 -0
  362. package/src/system/setup-wizard/probe.ts +486 -0
  363. package/src/system/setup-wizard/runtime-features.ts +85 -0
  364. package/src/system/setup-wizard/sudo.ts +178 -0
  365. package/src/system/setup-wizard/types.ts +133 -0
  366. package/src/system/virtual-stream-deck.ts +67 -0
  367. package/src/themes/css.ts +210 -0
  368. package/src/themes/default/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  369. package/src/themes/default/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
  370. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
  371. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf +0 -0
  372. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf +0 -0
  373. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf +0 -0
  374. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Italic.ttf +0 -0
  375. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Light.ttf +0 -0
  376. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf +0 -0
  377. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
  378. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf +0 -0
  379. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
  380. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf +0 -0
  381. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf +0 -0
  382. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Thin.ttf +0 -0
  383. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf +0 -0
  384. package/src/themes/default/assets/IBM_Plex_Mono/OFL.txt +93 -0
  385. package/src/themes/default/assets/OFL.txt +93 -0
  386. package/src/themes/default/assets/README.txt +106 -0
  387. package/src/themes/default/assets/static/IBMPlexSans-Bold.ttf +0 -0
  388. package/src/themes/default/assets/static/IBMPlexSans-BoldItalic.ttf +0 -0
  389. package/src/themes/default/assets/static/IBMPlexSans-ExtraLight.ttf +0 -0
  390. package/src/themes/default/assets/static/IBMPlexSans-ExtraLightItalic.ttf +0 -0
  391. package/src/themes/default/assets/static/IBMPlexSans-Italic.ttf +0 -0
  392. package/src/themes/default/assets/static/IBMPlexSans-Light.ttf +0 -0
  393. package/src/themes/default/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
  394. package/src/themes/default/assets/static/IBMPlexSans-Medium.ttf +0 -0
  395. package/src/themes/default/assets/static/IBMPlexSans-MediumItalic.ttf +0 -0
  396. package/src/themes/default/assets/static/IBMPlexSans-Regular.ttf +0 -0
  397. package/src/themes/default/assets/static/IBMPlexSans-SemiBold.ttf +0 -0
  398. package/src/themes/default/assets/static/IBMPlexSans-SemiBoldItalic.ttf +0 -0
  399. package/src/themes/default/assets/static/IBMPlexSans-Thin.ttf +0 -0
  400. package/src/themes/default/assets/static/IBMPlexSans-ThinItalic.ttf +0 -0
  401. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Bold.ttf +0 -0
  402. package/src/themes/default/assets/static/IBMPlexSans_Condensed-BoldItalic.ttf +0 -0
  403. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLight.ttf +0 -0
  404. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLightItalic.ttf +0 -0
  405. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Italic.ttf +0 -0
  406. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Light.ttf +0 -0
  407. package/src/themes/default/assets/static/IBMPlexSans_Condensed-LightItalic.ttf +0 -0
  408. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Medium.ttf +0 -0
  409. package/src/themes/default/assets/static/IBMPlexSans_Condensed-MediumItalic.ttf +0 -0
  410. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Regular.ttf +0 -0
  411. package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBold.ttf +0 -0
  412. package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBoldItalic.ttf +0 -0
  413. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Thin.ttf +0 -0
  414. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ThinItalic.ttf +0 -0
  415. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Bold.ttf +0 -0
  416. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-BoldItalic.ttf +0 -0
  417. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLight.ttf +0 -0
  418. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLightItalic.ttf +0 -0
  419. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Italic.ttf +0 -0
  420. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Light.ttf +0 -0
  421. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-LightItalic.ttf +0 -0
  422. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Medium.ttf +0 -0
  423. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-MediumItalic.ttf +0 -0
  424. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Regular.ttf +0 -0
  425. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBold.ttf +0 -0
  426. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  427. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Thin.ttf +0 -0
  428. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ThinItalic.ttf +0 -0
  429. package/src/themes/default/components.css +81 -0
  430. package/src/themes/default/sirenodeck.json +170 -0
  431. package/src/themes/index.ts +14 -0
  432. package/src/themes/light/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  433. package/src/themes/light/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
  434. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
  435. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
  436. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
  437. package/src/themes/light/assets/IBM_Plex_Mono/OFL.txt +93 -0
  438. package/src/themes/light/assets/OFL.txt +93 -0
  439. package/src/themes/light/assets/README.txt +106 -0
  440. package/src/themes/light/assets/static/IBMPlexSans-Light.ttf +0 -0
  441. package/src/themes/light/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
  442. package/src/themes/light/assets/static/IBMPlexSans-Thin.ttf +0 -0
  443. package/src/themes/light/components.css +70 -0
  444. package/src/themes/light/sirenodeck.json +170 -0
  445. package/src/themes/loader.ts +343 -0
  446. package/src/themes/manifest.ts +164 -0
  447. package/src/themes/use-resolved-theme.tsx +32 -0
  448. package/src/ui/ButtonFrame.tsx +230 -0
  449. package/src/ui/contexts/AssetCacheContext.tsx +43 -0
  450. package/src/ui/index.ts +20 -0
  451. package/src/ui/primitives/Chip.tsx +53 -0
  452. package/src/ui/primitives/Icon.tsx +209 -0
  453. package/src/ui/primitives/Label.tsx +82 -0
  454. package/src/ui/primitives/ProgressBar.tsx +38 -0
  455. package/src/ui/primitives/TapIndicator.tsx +67 -0
  456. package/src/ui/primitives/Text.tsx +810 -0
  457. package/src/ui/primitives/index.ts +6 -0
  458. package/src/ui/surfaces/BarsSurface.tsx +211 -0
  459. package/src/ui/surfaces/IconLabelProgressSurface.tsx +133 -0
  460. package/src/ui/surfaces/IconLabelSurface.tsx +59 -0
  461. package/src/ui/surfaces/LabelValueListSurface.tsx +202 -0
  462. package/src/ui/surfaces/PaginatedSurface.tsx +164 -0
  463. package/src/ui/surfaces/SplitActionSurface.tsx +98 -0
  464. package/src/ui/surfaces/TemporaryErrorSurface.tsx +68 -0
  465. package/src/ui/surfaces/ValueChart.tsx +218 -0
  466. package/src/ui/surfaces/index.ts +32 -0
  467. package/src/ui/surfaces/paginated-schema.ts +22 -0
  468. package/src/ui/theme-presentation-builder.ts +184 -0
  469. package/src/ui/theme-presentation.tsx +146 -0
  470. package/src/ui/utils/cn.ts +12 -0
  471. package/src/util/cache-paths.ts +38 -0
  472. package/src/util/config-diff.ts +16 -0
  473. package/src/util/daemon-control.ts +85 -0
  474. package/src/util/daemon.ts +575 -0
  475. package/src/util/device-config.ts +56 -0
  476. package/src/util/errors.ts +13 -0
  477. package/src/util/log-reader.ts +114 -0
  478. package/src/util/log-tail.ts +103 -0
  479. package/src/util/logger.ts +417 -0
  480. package/src/util/process-title.ts +79 -0
  481. package/src/util/single-instance.ts +202 -0
  482. package/src/version.ts +10 -0
  483. package/src/vite/index.ts +6 -0
  484. package/src/vite/virtual-modules.ts +631 -0
  485. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,245 @@
1
+ import type pino from "pino"
2
+
3
+ import type { ActiveAppProvider, ActiveAppSnapshot } from "../active-app"
4
+ import {
5
+ type CommandExecutor,
6
+ type LinuxDbusBus,
7
+ type LinuxDbusProxyObject,
8
+ } from "../shared"
9
+ import {
10
+ createWaylandGnomeProvider,
11
+ hasWaylandGnomeSession,
12
+ shouldUseWaylandGnomeProvider,
13
+ } from "./wayland-gnome"
14
+
15
+ export interface LinuxActiveAppDeps {
16
+ readonly dbus?: LinuxDbusBus
17
+ readonly executor: CommandExecutor
18
+ readonly logger: pino.Logger
19
+ readonly pollIntervalMs?: number
20
+ readonly env?: NodeJS.ProcessEnv
21
+ }
22
+
23
+ const DEFAULT_POLL_MS = 1_000
24
+
25
+ const parseDbusEvalResult = (
26
+ raw: unknown,
27
+ ): { wmClass: string; title: string; pid: number | null } | null => {
28
+ if (typeof raw !== "string" || raw.length === 0) return null
29
+ try {
30
+ const parsed = JSON.parse(raw) as {
31
+ wm_class?: string
32
+ title?: string
33
+ pid?: number
34
+ }
35
+ const wmClass = typeof parsed.wm_class === "string" ? parsed.wm_class : ""
36
+ const title = typeof parsed.title === "string" ? parsed.title : ""
37
+ const pid =
38
+ typeof parsed.pid === "number" && Number.isFinite(parsed.pid)
39
+ ? parsed.pid
40
+ : null
41
+ if (wmClass.length === 0 && title.length === 0) return null
42
+ return { wmClass, title, pid }
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
48
+ const readProcName = async (
49
+ executor: CommandExecutor,
50
+ ): Promise<string | null> => {
51
+ const result = await executor.run("sh", [
52
+ "-c",
53
+ "cat /proc/$$/comm 2>/dev/null || true",
54
+ ])
55
+ const name = result.stdout.trim()
56
+ return name.length > 0 ? name : null
57
+ }
58
+
59
+ const readForegroundProc = async (
60
+ executor: CommandExecutor,
61
+ ): Promise<{ name: string; pid: number | null } | null> => {
62
+ const winIdResult = await executor.run("sh", [
63
+ "-c",
64
+ "xdotool getactivewindow 2>/dev/null || xprop -root _NET_ACTIVE_WINDOW 2>/dev/null | awk '{print $5}' || true",
65
+ ])
66
+ const winId = winIdResult.stdout.trim()
67
+ if (winId.length === 0) {
68
+ const name = await readProcName(executor)
69
+ return name === null ? null : { name, pid: null }
70
+ }
71
+ const pidResult = await executor.run("sh", [
72
+ "-c",
73
+ `xdotool getwindowpid ${winId} 2>/dev/null || true`,
74
+ ])
75
+ const pid = Number.parseInt(pidResult.stdout.trim(), 10)
76
+ if (!Number.isFinite(pid)) {
77
+ const name = await readProcName(executor)
78
+ return name === null ? null : { name, pid: null }
79
+ }
80
+ const nameResult = await executor.run("sh", [
81
+ "-c",
82
+ `cat /proc/${pid}/comm 2>/dev/null || true`,
83
+ ])
84
+ const name = nameResult.stdout.trim()
85
+ if (name.length === 0) {
86
+ return { name: `pid:${pid}`, pid }
87
+ }
88
+ return { name, pid }
89
+ }
90
+
91
+ const D_BUS_GNOME_SHELL = "org.gnome.Shell"
92
+ const D_BUS_GNOME_SHELL_PATH = "/org/gnome/Shell"
93
+
94
+ const dbusSnapshot = async (
95
+ bus: LinuxDbusBus,
96
+ ): Promise<ActiveAppSnapshot | null> => {
97
+ const proxy: LinuxDbusProxyObject = await bus.getProxyObject(
98
+ D_BUS_GNOME_SHELL,
99
+ D_BUS_GNOME_SHELL_PATH,
100
+ )
101
+ const iface = proxy.getInterface("org.gnome.Shell")
102
+ if (iface.Eval === undefined) return null
103
+ const result = await iface.Eval(
104
+ "global.display.focus_window && JSON.stringify({wm_class: global.display.focus_window.wm_class, title: global.display.focus_window.title, pid: global.display.focus_window.pid})",
105
+ )
106
+ return parseDbusEvalResult(result) === null
107
+ ? null
108
+ : (() => {
109
+ const parsed = parseDbusEvalResult(result) as {
110
+ wmClass: string
111
+ title: string
112
+ pid: number | null
113
+ }
114
+ return {
115
+ name: parsed.wmClass.length > 0 ? parsed.wmClass : parsed.title,
116
+ windowTitle: parsed.title.length > 0 ? parsed.title : null,
117
+ processId: parsed.pid,
118
+ }
119
+ })()
120
+ }
121
+
122
+ const fallbackSnapshot = async (
123
+ executor: CommandExecutor,
124
+ ): Promise<ActiveAppSnapshot | null> => {
125
+ const fg = await readForegroundProc(executor)
126
+ if (fg === null) return null
127
+ return {
128
+ name: fg.name,
129
+ windowTitle: null,
130
+ processId: fg.pid,
131
+ }
132
+ }
133
+
134
+ const sameSnapshot = (
135
+ a: ActiveAppSnapshot | null,
136
+ b: ActiveAppSnapshot | null,
137
+ ): boolean => {
138
+ if (a === null && b === null) return true
139
+ if (a === null || b === null) return false
140
+ return (
141
+ a.name === b.name &&
142
+ a.windowTitle === b.windowTitle &&
143
+ a.processId === b.processId
144
+ )
145
+ }
146
+
147
+ export const createLinuxActiveAppProvider = async (
148
+ deps: LinuxActiveAppDeps,
149
+ ): Promise<ActiveAppProvider> => {
150
+ if (
151
+ shouldUseWaylandGnomeProvider(deps.env) ||
152
+ (await hasWaylandGnomeSession({ env: deps.env, executor: deps.executor }))
153
+ ) {
154
+ deps.logger.info(
155
+ "active-app: detected GNOME Wayland session, using 'Window Calls Extended' provider",
156
+ )
157
+ try {
158
+ return await createWaylandGnomeProvider({
159
+ ...(deps.dbus !== undefined ? { dbus: deps.dbus } : {}),
160
+ logger: deps.logger,
161
+ ...(deps.pollIntervalMs !== undefined
162
+ ? { pollIntervalMs: deps.pollIntervalMs }
163
+ : {}),
164
+ })
165
+ } catch (err) {
166
+ // ponytail: extension probe or interface lookup can throw if GNOME Shell
167
+ // is reachable but the extension isn't installed; fall through to the
168
+ // null provider below instead of crashing the daemon.
169
+ deps.logger.warn(
170
+ { err },
171
+ "active-app: GNOME Wayland provider init failed, using null provider",
172
+ )
173
+ }
174
+ }
175
+
176
+ const pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS
177
+ const subscribers = new Set<(s: ActiveAppSnapshot | null) => void>()
178
+ let last: ActiveAppSnapshot | null = null
179
+ let stopped = false
180
+ const bus: LinuxDbusBus | null = deps.dbus ?? null
181
+ let dbusFailed = false
182
+
183
+ const snapshot = async (): Promise<ActiveAppSnapshot | null> => {
184
+ if (bus && !dbusFailed) {
185
+ try {
186
+ const snap = await dbusSnapshot(bus)
187
+ if (snap !== null) return snap
188
+ dbusFailed = true
189
+ } catch (err) {
190
+ deps.logger.debug(
191
+ { err },
192
+ "active-app: D-Bus snapshot failed, falling back to /proc",
193
+ )
194
+ dbusFailed = true
195
+ }
196
+ }
197
+ try {
198
+ return await fallbackSnapshot(deps.executor)
199
+ } catch (err) {
200
+ deps.logger.warn({ err }, "active-app: /proc fallback failed")
201
+ return last
202
+ }
203
+ }
204
+
205
+ let interval: ReturnType<typeof setInterval> | null = null
206
+ const start = (): void => {
207
+ if (interval !== null) return
208
+ interval = setInterval(() => {
209
+ if (stopped) return
210
+ void snapshot().then((s) => {
211
+ if (stopped) return
212
+ if (!sameSnapshot(last, s)) {
213
+ last = s
214
+ for (const handler of subscribers) handler(s)
215
+ }
216
+ })
217
+ }, pollMs)
218
+ }
219
+
220
+ start()
221
+
222
+ return {
223
+ async getActive() {
224
+ return snapshot()
225
+ },
226
+ subscribe(handler) {
227
+ subscribers.add(handler)
228
+ return () => {
229
+ subscribers.delete(handler)
230
+ }
231
+ },
232
+ async stop() {
233
+ stopped = true
234
+ if (interval !== null) {
235
+ clearInterval(interval)
236
+ interval = null
237
+ }
238
+ try {
239
+ bus?.disconnect?.()
240
+ } catch {
241
+ // ignore disconnect failures
242
+ }
243
+ },
244
+ }
245
+ }
@@ -0,0 +1,303 @@
1
+ import { sessionBus } from "dbus-next"
2
+
3
+ import type pino from "pino"
4
+
5
+ import type { ActiveAppProvider, ActiveAppSnapshot } from "../active-app"
6
+ import {
7
+ logNull,
8
+ type CommandExecutor,
9
+ type LinuxDbusBus,
10
+ type LinuxDbusProxyObject,
11
+ } from "../shared"
12
+
13
+ export interface WaylandGnomeDeps {
14
+ readonly dbus?: LinuxDbusBus
15
+ readonly logger: pino.Logger
16
+ readonly pollIntervalMs?: number
17
+ }
18
+
19
+ const DEFAULT_POLL_MS = 500
20
+ const MAX_CONSECUTIVE_POLL_FAILURES = 5
21
+ const PROBE_TIMEOUT_MS = 3_000
22
+
23
+ const GNOME_SHELL_SERVICE = "org.gnome.Shell"
24
+ const GNOME_EXTENSION_PATH = "/org/gnome/Shell/Extensions/WindowsExt"
25
+ const GNOME_EXTENSION_IFACE = "org.gnome.Shell.Extensions.WindowsExt"
26
+ export const EXTENSION_INSTALL_URL =
27
+ "https://extensions.gnome.org/extension/4974/window-calls-extended/"
28
+
29
+ const serializeError = (error: unknown): unknown => {
30
+ if (error instanceof Error) {
31
+ return { message: error.message, name: error.name, stack: error.stack }
32
+ }
33
+ if (typeof error === "object" && error !== null) {
34
+ return { ...error }
35
+ }
36
+ return error
37
+ }
38
+
39
+ const isGnomeDesktop = (env: NodeJS.ProcessEnv): boolean => {
40
+ const desktop = env["XDG_CURRENT_DESKTOP"] ?? ""
41
+ return desktop.toUpperCase().includes("GNOME")
42
+ }
43
+
44
+ const isWaylandSession = (env: NodeJS.ProcessEnv): boolean => {
45
+ const sessionType = env["XDG_SESSION_TYPE"] ?? ""
46
+ if (sessionType === "wayland") return true
47
+ return env["WAYLAND_DISPLAY"] !== undefined && env["WAYLAND_DISPLAY"] !== ""
48
+ }
49
+
50
+ export const shouldUseWaylandGnomeProvider = (
51
+ env: NodeJS.ProcessEnv = process.env,
52
+ ): boolean => isWaylandSession(env) && isGnomeDesktop(env)
53
+
54
+ const sessionProperty = (output: string, name: string): string => {
55
+ const line = output
56
+ .split("\n")
57
+ .find((candidate) => candidate.startsWith(`${name}=`))
58
+ return line?.slice(name.length + 1) ?? ""
59
+ }
60
+
61
+ // A systemd user service does not necessarily inherit the graphical session
62
+ // variables. Ask logind for the user's active session before falling back.
63
+ export const hasWaylandGnomeSession = async ({
64
+ env = process.env,
65
+ executor,
66
+ }: {
67
+ readonly env?: NodeJS.ProcessEnv
68
+ readonly executor: CommandExecutor
69
+ }): Promise<boolean> => {
70
+ if (shouldUseWaylandGnomeProvider(env)) return true
71
+ if (
72
+ env["XDG_SESSION_TYPE"] !== undefined ||
73
+ env["WAYLAND_DISPLAY"] !== undefined ||
74
+ env["DISPLAY"] !== undefined ||
75
+ env["XDG_CURRENT_DESKTOP"] !== undefined
76
+ ) {
77
+ return false
78
+ }
79
+ if (typeof process.getuid !== "function") return false
80
+
81
+ try {
82
+ const sessions = await executor.run("loginctl", [
83
+ "show-user",
84
+ String(process.getuid()),
85
+ "--property=Sessions",
86
+ ])
87
+ if (sessions.exitCode !== 0) return false
88
+ const sessionIds = sessionProperty(sessions.stdout, "Sessions")
89
+ .split(" ")
90
+ .filter((id) => id.length > 0)
91
+ for (const sessionId of sessionIds) {
92
+ const session = await executor.run("loginctl", [
93
+ "show-session",
94
+ sessionId,
95
+ "--property=Active",
96
+ "--property=Type",
97
+ "--property=Desktop",
98
+ ])
99
+ if (session.exitCode !== 0) continue
100
+ if (sessionProperty(session.stdout, "Active") !== "yes") continue
101
+ if (sessionProperty(session.stdout, "Type") !== "wayland") continue
102
+ if (
103
+ isGnomeDesktop({
104
+ XDG_CURRENT_DESKTOP: sessionProperty(session.stdout, "Desktop"),
105
+ })
106
+ )
107
+ return true
108
+
109
+ // Ubuntu's logind session can omit Desktop. systemd's user manager
110
+ // retains the graphical session variables that the daemon inherits.
111
+ const environment = await executor.run("systemctl", [
112
+ "--user",
113
+ "show-environment",
114
+ ])
115
+ if (
116
+ environment.exitCode === 0 &&
117
+ isGnomeDesktop({
118
+ XDG_CURRENT_DESKTOP: sessionProperty(
119
+ environment.stdout,
120
+ "XDG_CURRENT_DESKTOP",
121
+ ),
122
+ })
123
+ )
124
+ return true
125
+ }
126
+ } catch {
127
+ // No logind is available in some containers and non-Linux environments.
128
+ }
129
+ return false
130
+ }
131
+
132
+ interface ProbeResult {
133
+ bus: LinuxDbusBus
134
+ focusClass: () => Promise<string>
135
+ focusTitle?: () => Promise<string>
136
+ }
137
+
138
+ const probeExtension = async (
139
+ deps: WaylandGnomeDeps,
140
+ ): Promise<ProbeResult | null> => {
141
+ let bus: LinuxDbusBus | null = deps.dbus ?? null
142
+ if (bus === null) {
143
+ try {
144
+ bus = (await sessionBus()) as unknown as LinuxDbusBus
145
+ } catch (error) {
146
+ deps.logger.warn(
147
+ {
148
+ error: serializeError(error),
149
+ installUrl: EXTENSION_INSTALL_URL,
150
+ },
151
+ "active-app: GNOME session bus unavailable",
152
+ )
153
+ return null
154
+ }
155
+ }
156
+
157
+ let proxy: LinuxDbusProxyObject
158
+ try {
159
+ proxy = await Promise.race([
160
+ bus.getProxyObject(GNOME_SHELL_SERVICE, GNOME_EXTENSION_PATH),
161
+ new Promise<never>((_resolve, reject) =>
162
+ setTimeout(
163
+ () => reject(new Error("extension probe timed out")),
164
+ PROBE_TIMEOUT_MS,
165
+ ),
166
+ ),
167
+ ])
168
+ } catch (error) {
169
+ deps.logger.warn(
170
+ {
171
+ error: serializeError(error),
172
+ timeoutMs: PROBE_TIMEOUT_MS,
173
+ installUrl: EXTENSION_INSTALL_URL,
174
+ },
175
+ "active-app: GNOME extension probe failed — install 'Window Calls Extended' to enable active-app detection on Wayland",
176
+ )
177
+ bus.disconnect?.()
178
+ return null
179
+ }
180
+
181
+ const iface = proxy.getInterface(GNOME_EXTENSION_IFACE)
182
+ const focusClass = iface.FocusClass
183
+ if (typeof focusClass !== "function") {
184
+ deps.logger.warn(
185
+ { ifaceKeys: Object.keys(iface) },
186
+ "active-app: GNOME extension reached but FocusClass method missing",
187
+ )
188
+ bus.disconnect?.()
189
+ return null
190
+ }
191
+
192
+ const focusTitle =
193
+ typeof iface.FocusTitle === "function"
194
+ ? iface.FocusTitle.bind(iface)
195
+ : undefined
196
+
197
+ return {
198
+ bus,
199
+ focusClass: focusClass.bind(iface),
200
+ ...(focusTitle ? { focusTitle } : {}),
201
+ }
202
+ }
203
+
204
+ export const createWaylandGnomeProvider = async (
205
+ deps: WaylandGnomeDeps,
206
+ ): Promise<ActiveAppProvider> => {
207
+ const probe = await probeExtension(deps)
208
+ if (probe === null) {
209
+ logNull(deps.logger, "active-app", "gnome-extension-missing")
210
+ return {
211
+ async getActive() {
212
+ return null
213
+ },
214
+ subscribe() {
215
+ return () => undefined
216
+ },
217
+ async stop() {
218
+ return
219
+ },
220
+ }
221
+ }
222
+
223
+ const pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS
224
+ let timer: ReturnType<typeof setTimeout> | null = null
225
+ let lastName: string | null = null
226
+ let lastTitle: string | null = null
227
+ let consecutiveFailures = 0
228
+ let stopped = false
229
+ const subscribers = new Set<(s: ActiveAppSnapshot | null) => void>()
230
+
231
+ const emit = (name: string | null, title: string | null): void => {
232
+ if (name === lastName && title === lastTitle) return
233
+ lastName = name
234
+ lastTitle = title
235
+ const snapshot: ActiveAppSnapshot | null =
236
+ name === null ? null : { name, windowTitle: title, processId: null }
237
+ for (const handler of subscribers) handler(snapshot)
238
+ }
239
+
240
+ const poll = async (): Promise<void> => {
241
+ if (stopped) return
242
+ try {
243
+ const [rawClass, rawTitle] = await Promise.all([
244
+ probe.focusClass(),
245
+ probe.focusTitle ? probe.focusTitle() : Promise.resolve(""),
246
+ ])
247
+ consecutiveFailures = 0
248
+ const name =
249
+ typeof rawClass === "string" && rawClass.length > 0 ? rawClass : null
250
+ const title =
251
+ typeof rawTitle === "string" && rawTitle.length > 0 ? rawTitle : null
252
+ emit(name, title)
253
+ } catch (error) {
254
+ consecutiveFailures += 1
255
+ deps.logger.debug(
256
+ { error: serializeError(error), consecutiveFailures },
257
+ "active-app: wayland-gnome poll failed",
258
+ )
259
+ if (consecutiveFailures === MAX_CONSECUTIVE_POLL_FAILURES) {
260
+ deps.logger.warn(
261
+ {
262
+ error: serializeError(error),
263
+ maxFailures: MAX_CONSECUTIVE_POLL_FAILURES,
264
+ },
265
+ "active-app: wayland-gnome poll failed repeatedly — disabling poller",
266
+ )
267
+ stopped = true
268
+ if (timer !== null) clearTimeout(timer)
269
+ return
270
+ }
271
+ }
272
+ if (!stopped) timer = setTimeout(() => void poll(), pollMs)
273
+ }
274
+
275
+ void poll()
276
+
277
+ return {
278
+ async getActive() {
279
+ return lastName === null
280
+ ? null
281
+ : { name: lastName, windowTitle: lastTitle, processId: null }
282
+ },
283
+ subscribe(handler) {
284
+ subscribers.add(handler)
285
+ const snapshot: ActiveAppSnapshot | null =
286
+ lastName === null
287
+ ? null
288
+ : { name: lastName, windowTitle: lastTitle, processId: null }
289
+ handler(snapshot)
290
+ return () => {
291
+ subscribers.delete(handler)
292
+ }
293
+ },
294
+ async stop() {
295
+ stopped = true
296
+ if (timer !== null) {
297
+ clearTimeout(timer)
298
+ timer = null
299
+ }
300
+ probe.bus.disconnect?.()
301
+ },
302
+ }
303
+ }
@@ -0,0 +1,94 @@
1
+ import type pino from "pino"
2
+
3
+ import type { ActiveAppProvider, ActiveAppSnapshot } from "../active-app"
4
+ import type { CommandExecutor } from "../shared"
5
+
6
+ export interface WindowsActiveAppDeps {
7
+ readonly executor: CommandExecutor
8
+ readonly logger: pino.Logger
9
+ readonly pollIntervalMs?: number
10
+ }
11
+
12
+ const DEFAULT_POLL_MS = 1_000
13
+
14
+ const PS_GET_ACTIVE = `Add-Type -AssemblyName UIAutomationClient,UIAutomationTypes; $el=[System.Windows.Automation.AutomationElement]::FocusedElement; "{0}|{1}" -f $el.Current.Name, $el.Current.ProcessId`
15
+
16
+ const parseOutput = (
17
+ raw: string,
18
+ ): { name: string; pid: number | null } | null => {
19
+ const trimmed = raw.trim()
20
+ if (trimmed.length === 0) return null
21
+ const idx = trimmed.lastIndexOf("|")
22
+ if (idx < 0) return null
23
+ const name = trimmed.substring(0, idx).trim()
24
+ const pidStr = trimmed.substring(idx + 1).trim()
25
+ const pid = Number.parseInt(pidStr, 10)
26
+ if (name.length === 0) return null
27
+ return { name, pid: Number.isFinite(pid) ? pid : null }
28
+ }
29
+
30
+ export const createWindowsActiveAppProvider = async (
31
+ deps: WindowsActiveAppDeps,
32
+ ): Promise<ActiveAppProvider> => {
33
+ const pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS
34
+ const subscribers = new Set<(s: ActiveAppSnapshot | null) => void>()
35
+ let last: ActiveAppSnapshot | null = null
36
+ let stopped = false
37
+
38
+ const snapshot = async (): Promise<ActiveAppSnapshot | null> => {
39
+ try {
40
+ const result = await deps.executor.run(
41
+ "powershell",
42
+ ["-NoProfile", "-Command", PS_GET_ACTIVE],
43
+ { timeoutMs: 3_000 },
44
+ )
45
+ if (result.exitCode !== 0) return last
46
+ const parsed = parseOutput(result.stdout)
47
+ if (parsed === null) return last
48
+ return {
49
+ name: parsed.name,
50
+ windowTitle: parsed.name,
51
+ processId: parsed.pid,
52
+ }
53
+ } catch (err) {
54
+ deps.logger.warn({ err }, "active-app: powershell failed")
55
+ return last
56
+ }
57
+ }
58
+
59
+ let interval: ReturnType<typeof setInterval> | null = null
60
+ interval = setInterval(() => {
61
+ if (stopped) return
62
+ void snapshot().then((s) => {
63
+ if (stopped) return
64
+ const same =
65
+ last !== null &&
66
+ s !== null &&
67
+ last.name === s.name &&
68
+ last.processId === s.processId
69
+ if (!same) {
70
+ last = s
71
+ for (const h of subscribers) h(s)
72
+ }
73
+ })
74
+ }, pollMs)
75
+
76
+ return {
77
+ async getActive() {
78
+ return snapshot()
79
+ },
80
+ subscribe(handler) {
81
+ subscribers.add(handler)
82
+ return () => {
83
+ subscribers.delete(handler)
84
+ }
85
+ },
86
+ async stop() {
87
+ stopped = true
88
+ if (interval !== null) {
89
+ clearInterval(interval)
90
+ interval = null
91
+ }
92
+ },
93
+ }
94
+ }