@legendapp/spark 0.0.1-next.2 → 0.0.1-next.3

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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -4
  3. package/ai-codex.ts +1 -0
  4. package/ai.ts +1 -0
  5. package/app-documents.ts +1 -0
  6. package/config-plugin.d.cts +2 -0
  7. package/contracts.ts +2 -0
  8. package/diagnostics.ts +1 -0
  9. package/expo-config.d.cts +1 -0
  10. package/metro.cjs +4 -1
  11. package/metro.d.cts +12 -0
  12. package/native.cjs +3 -1
  13. package/native.d.cts +6 -0
  14. package/package.json +82 -14
  15. package/schema.json +276 -58
  16. package/settings-observable.ts +1 -0
  17. package/settings-window.ts +1 -0
  18. package/shortcuts-commands.ts +1 -0
  19. package/shortcuts-keyboard.ts +3 -0
  20. package/ui-classnames.ts +1 -0
  21. package/ui-glass.ts +1 -0
  22. package/ui-search.ts +1 -0
  23. package/ui-sidebar.ts +1 -0
  24. package/ui-split-view.ts +1 -0
  25. package/ui-symbol.ts +1 -0
  26. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  27. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  28. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  29. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  30. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  31. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  32. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  33. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  34. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  35. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  36. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  37. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  38. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  39. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  40. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  41. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  42. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  43. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  44. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  45. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  46. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  47. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  48. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  49. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  50. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  51. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  52. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  53. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  54. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  55. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  56. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  57. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
  58. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
  59. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
  60. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
  61. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
  62. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
  63. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  85. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  86. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  87. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
  88. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  89. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  90. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  91. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  92. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  93. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  94. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  95. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  96. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  98. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  99. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  100. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  108. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  109. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  110. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  111. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  112. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  113. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  114. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  115. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  116. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  117. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  118. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  119. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  120. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  121. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  122. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  123. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  124. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  125. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  126. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  127. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  128. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  129. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  130. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  131. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  132. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  137. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  138. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  139. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  140. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  141. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  142. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  143. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  144. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  145. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  146. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  147. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  148. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  149. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  150. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  151. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  152. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  153. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  154. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  155. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  156. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  157. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  158. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  159. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  160. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  161. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  162. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  176. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  178. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
  179. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  180. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  182. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
  184. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  185. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  188. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  189. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  190. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  191. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  192. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  193. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  194. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  195. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  196. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  197. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  199. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  201. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  203. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  204. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  205. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  206. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  207. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  208. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  209. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  210. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  211. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  212. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  213. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  214. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  215. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
  216. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  218. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  219. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
  221. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  223. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  225. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  237. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  238. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  239. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  240. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  241. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  242. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  243. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  244. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  245. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  246. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  247. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  248. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  249. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  250. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  251. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  252. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  253. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  254. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  255. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  256. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  257. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  258. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  259. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  260. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  261. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  262. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  263. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  264. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  265. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  266. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  267. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  268. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  269. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  270. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  271. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  272. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  273. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  274. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  275. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  276. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  277. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  278. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  279. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  280. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  281. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  282. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  283. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  284. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  285. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  286. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  287. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  288. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  289. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  290. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  291. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  292. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  293. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  294. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
  295. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  296. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  297. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  298. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  299. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  300. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  301. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  302. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  303. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  304. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  305. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  306. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  307. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  308. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  309. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  310. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  311. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  312. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  313. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  314. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  315. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  316. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  317. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  318. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  319. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  320. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  321. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  322. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  325. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  326. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  327. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  328. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  329. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  330. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  331. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  332. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  333. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  334. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
  335. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  336. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  337. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  338. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  339. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  340. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  341. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  342. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  343. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  344. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  345. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  346. package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
  347. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  348. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  349. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
  350. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
  351. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  352. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  357. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  358. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  359. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  360. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  361. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  365. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  368. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  369. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  370. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  374. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  375. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  376. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  377. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  378. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  379. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  380. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  381. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  382. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  384. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  385. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  386. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  387. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  388. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  389. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  390. package/windows-macos.ts +1 -0
  391. package/expo-metro.cjs +0 -1
  392. package/message-dialog.ts +0 -1
  393. package/universal.cjs +0 -1
  394. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  395. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  396. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  397. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  398. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  399. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -40,7 +40,7 @@ struct WindowResources : winrt::implements<WindowResources, RNW::Composition::IC
40
40
  void ResourcesChanged(winrt::event_token const &token) noexcept { changed.remove(token); }
41
41
  void SetTheme(int value) { theme = value; changed(*this, nullptr); }
42
42
  };
43
- struct Window { RNW::ReactNativeWindow view{nullptr}; UI::AppWindow app{nullptr}; UI::OverlappedPresenter normal{nullptr}; int32_t minWidth = 100, minHeight = 100, maxWidth = 20000, maxHeight = 20000; std::string parentId; bool modal = false; bool overlay = false; bool transparent = false; bool shadow = true; bool disabledByModal = false; bool enabledBeforeModal = false; bool guard = false; bool pending = false; bool allow = false; uint64_t request = 0; HMONITOR monitor = nullptr; bool fullscreen = false; bool closable = true; bool restoreFrame = false; int theme = -1; winrt::com_ptr<WindowResources> resources; winrt::Microsoft::UI::Composition::SpriteVisual background{nullptr}; };
43
+ struct Window { RNW::ReactNativeWindow view{nullptr}; UI::AppWindow app{nullptr}; UI::OverlappedPresenter normal{nullptr}; double minWidth = 100, minHeight = 100, maxWidth = 20000, maxHeight = 20000; std::string instanceId, guardId; std::vector<RNW::ReactPromise<std::string>> closeWaiters; std::string parentId; bool modal = false; bool overlay = false; bool transparent = false; bool shadow = true; bool disabledByModal = false; bool enabledBeforeModal = false; bool guard = false; bool pending = false; bool allow = false; uint64_t request = 0; HMONITOR monitor = nullptr; bool fullscreen = false; bool closable = true; bool restoreFrame = false; int theme = -1; winrt::com_ptr<WindowResources> resources; winrt::Microsoft::UI::Composition::SpriteVisual background{nullptr}; };
44
44
  static std::map<std::string, Window> windows;
45
45
  static RNW::ReactNativeHost host{nullptr};
46
46
  static winrt::Microsoft::UI::Composition::Compositor compositor{nullptr};
@@ -50,7 +50,9 @@ static void QueueNotificationResponse(std::string const &);
50
50
  static std::string NotificationResponses();
51
51
  static void EmitNotificationEvents();
52
52
  static void SetNotificationWindow(HWND);
53
- static bool quitGuard = false, quitPending = false;
53
+ static bool quitPending = false, quitAccepted = false;
54
+ static std::set<std::string> quitGuards, pendingQuitGuards;
55
+ static std::vector<RNW::ReactPromise<std::string>> quitWaiters;
54
56
  static uint64_t generation = 0, quitRequest = 0;
55
57
  static Json::JsonArray pendingURLs{nullptr};
56
58
  static std::string initialURL;
@@ -64,7 +66,6 @@ static std::map<std::string, Shortcut> shortcuts;
64
66
  static HHOOK keyboardHook = nullptr;
65
67
  static std::map<UINT, RNW::JSValueObject> menuActions;
66
68
  static HMENU currentMenu = nullptr;
67
- static std::string menuOwner;
68
69
  static Json::JsonArray menus{nullptr};
69
70
  static HACCEL acceleratorTable = nullptr;
70
71
  static HWND Hwnd(Window const &window) { return winrt::Microsoft::UI::GetWindowFromWindowId(window.app.Id()); }
@@ -77,7 +78,29 @@ static void UpdateModality() {
77
78
  }
78
79
  }
79
80
  static std::string Id(HWND hwnd) { auto root = GetAncestor(hwnd, GA_ROOT); for (auto const &[id, window] : windows) if (Hwnd(window) == root) return id; return ""; }
80
- static void Emit(RNW::JSValueObject value) { if (context) context.EmitJSEvent(L"RCTDeviceEventEmitter", L"desktop", std::move(value)); }
81
+ static Json::JsonObject Info(std::string const &id);
82
+ static RNW::JSValue Value(Json::IJsonValue const &value);
83
+ static void FinishClose(std::string const &id, bool closed) {
84
+ auto found = windows.find(id); if (found == windows.end()) return;
85
+ auto waiters = std::move(found->second.closeWaiters); found->second.closeWaiters.clear();
86
+ for (auto const &waiter : waiters) waiter.Resolve(closed ? "{\"closed\":true}" : "{\"closed\":false,\"reason\":\"vetoed\"}");
87
+ }
88
+ static void Emit(RNW::JSValueObject value) {
89
+ auto id = value.find("windowId"), type = value.find("type");
90
+ if (id != value.end() && type != value.end()) {
91
+ auto found = windows.find(id->second.AsString());
92
+ if (found != windows.end()) {
93
+ const auto name = type->second.AsString();
94
+ value["instanceId"] = found->second.instanceId;
95
+ if (name == "beforeClose" || name == "closeGuardTimeout") value["guardId"] = found->second.guardId;
96
+ if (name == "move" || name == "resize" || name == "screenChanged") { value["type"] = "boundsChanged"; value["bounds"] = Value(Info(found->first).GetNamedObject(L"bounds")); }
97
+ else if (name == "focus" || name == "blur") { value["type"] = "focusChanged"; value["focused"] = name == "focus"; }
98
+ else if (name == "enterFullscreen" || name == "leaveFullscreen") { value["type"] = "fullscreenChanged"; value["fullscreen"] = name == "enterFullscreen"; }
99
+ else if (name == "visibilityChanged") value["visible"] = !!IsWindowVisible(Hwnd(found->second));
100
+ }
101
+ }
102
+ if (context) context.EmitJSEvent(L"RCTDeviceEventEmitter", L"desktop", std::move(value));
103
+ }
81
104
  static void WindowTransitions(std::string const &id) {
82
105
  auto found = windows.find(id); if (found == windows.end()) return;
83
106
  auto &window = found->second;
@@ -112,52 +135,91 @@ static void WriteJson(RNW::IJSValueWriter const &writer, Json::IJsonValue const
112
135
  default: writer.WriteNull();
113
136
  }
114
137
  }
115
- static Json::JsonObject Info(std::string const &id) {
116
- auto &window = windows.at(id); auto hwnd = Hwnd(window); RECT frame{}; GetWindowRect(hwnd, &frame);
117
- Json::JsonObject result, rect;
118
- result.SetNamedValue(L"id", Json::JsonValue::CreateStringValue(to_hstring(id))); result.SetNamedValue(L"title", Json::JsonValue::CreateStringValue(window.app.Title()));
119
- result.SetNamedValue(L"kind", Json::JsonValue::CreateStringValue(window.overlay ? L"overlay" : L"window"));
120
- result.SetNamedValue(L"visible", Json::JsonValue::CreateBooleanValue(IsWindowVisible(hwnd))); result.SetNamedValue(L"focused", Json::JsonValue::CreateBooleanValue(GetForegroundWindow() == hwnd));
121
- result.SetNamedValue(L"minimized", Json::JsonValue::CreateBooleanValue(IsIconic(hwnd)));
122
- result.SetNamedValue(L"resizable", Json::JsonValue::CreateBooleanValue((GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_THICKFRAME) != 0));
123
- result.SetNamedValue(L"alwaysOnTop", Json::JsonValue::CreateBooleanValue((GetWindowLongPtrW(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0));
124
- result.SetNamedValue(L"fullscreen", Json::JsonValue::CreateBooleanValue(window.app.Presenter().Kind() == UI::AppWindowPresenterKind::FullScreen));
125
- result.SetNamedValue(L"minWidth", Json::JsonValue::CreateNumberValue(window.minWidth)); result.SetNamedValue(L"maxWidth", Json::JsonValue::CreateNumberValue(window.maxWidth));
126
- rect.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue(frame.left)); rect.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue(frame.top)); rect.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue(frame.right-frame.left)); rect.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue(frame.bottom-frame.top)); result.SetNamedValue(L"frame", rect); return result;
127
- }
128
- static Json::JsonObject Rect(RECT const &frame) {
129
- Json::JsonObject result;
130
- result.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue(frame.left));
131
- result.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue(frame.top));
132
- result.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue(frame.right - frame.left));
133
- result.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue(frame.bottom - frame.top));
134
- return result;
135
- }
136
- static Json::JsonArray Displays() {
137
- struct State { Json::JsonArray result; std::exception_ptr error; } state;
138
+ struct DisplayGeometry { HMONITOR monitor; std::wstring id, persistentId, name; RECT frame, work; double scale; bool primary; };
139
+ static std::vector<DisplayGeometry> DisplayGeometries() {
140
+ struct State { std::vector<DisplayGeometry> result; std::exception_ptr error; } state;
138
141
  const auto enumerated = EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR monitor, HDC, LPRECT, LPARAM param) -> BOOL {
139
142
  auto &state = *reinterpret_cast<State *>(param);
140
143
  try {
141
144
  MONITORINFOEXW info{}; info.cbSize = sizeof(info);
142
145
  if (!GetMonitorInfoW(monitor, reinterpret_cast<MONITORINFO *>(&info))) winrt::throw_last_error();
143
- UINT dpiX = 96, dpiY = 96; winrt::check_hresult(GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY));
144
- DISPLAY_DEVICEW device{}; device.cb = sizeof(device); EnumDisplayDevicesW(info.szDevice, 0, &device, 0);
145
- Json::JsonObject display;
146
- display.SetNamedValue(L"id", Json::JsonValue::CreateStringValue(info.szDevice));
147
- display.SetNamedValue(L"name", Json::JsonValue::CreateStringValue(device.DeviceString[0] ? device.DeviceString : info.szDevice));
148
- display.SetNamedValue(L"frame", Rect(info.rcMonitor)); display.SetNamedValue(L"workArea", Rect(info.rcWork));
149
- display.SetNamedValue(L"scale", Json::JsonValue::CreateNumberValue(dpiX / 96.0)); state.result.Append(display);
146
+ UINT x = 96, y = 96; winrt::check_hresult(GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &x, &y));
147
+ DISPLAY_DEVICEW device{}; device.cb = sizeof(device); EnumDisplayDevicesW(info.szDevice, 0, &device, EDD_GET_DEVICE_INTERFACE_NAME);
148
+ state.result.push_back({monitor, info.szDevice, device.DeviceID, device.DeviceString[0] ? device.DeviceString : info.szDevice, info.rcMonitor, info.rcWork, x / 96.0, (info.dwFlags & MONITORINFOF_PRIMARY) != 0});
150
149
  return TRUE;
151
150
  } catch (...) { state.error = std::current_exception(); return FALSE; }
152
151
  }, reinterpret_cast<LPARAM>(&state));
153
152
  if (state.error) std::rethrow_exception(state.error);
154
- if (!enumerated) winrt::throw_last_error();
153
+ if (!enumerated || state.result.empty()) winrt::throw_last_error();
155
154
  return state.result;
156
155
  }
157
- static int32_t Dimension(Json::JsonObject const &args, wchar_t const *key, int32_t fallback) {
156
+ static DisplayGeometry OwningDisplay(RECT const &frame) {
157
+ auto displays = DisplayGeometries(); auto best = displays.front(); double area = -1;
158
+ for (auto const &display : displays) {
159
+ RECT intersection{}; IntersectRect(&intersection, &frame, &display.frame);
160
+ double next = static_cast<double>(intersection.right - intersection.left) * (intersection.bottom - intersection.top);
161
+ if (next > area || (next == area && display.primary)) { best = display; area = next; }
162
+ }
163
+ return best;
164
+ }
165
+ static DisplayGeometry DisplayWithId(winrt::hstring const &id) {
166
+ for (auto const &display : DisplayGeometries()) if (display.id == id.c_str()) return display;
167
+ throw winrt::hresult_error(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), L"Display is disconnected");
168
+ }
169
+ static Json::JsonObject LogicalRect(RECT const &rect, DisplayGeometry const &display) {
170
+ Json::JsonObject result;
171
+ result.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue((rect.left - display.frame.left) / display.scale));
172
+ result.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue((rect.top - display.frame.top) / display.scale));
173
+ result.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue((rect.right - rect.left) / display.scale));
174
+ result.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue((rect.bottom - rect.top) / display.scale));
175
+ return result;
176
+ }
177
+ static Json::JsonObject Info(std::string const &id) {
178
+ auto &window = windows.at(id); auto hwnd = Hwnd(window); RECT frame{}; if (!GetWindowRect(hwnd, &frame)) winrt::throw_last_error();
179
+ if (IsIconic(hwnd)) { WINDOWPLACEMENT placement{}; placement.length = sizeof(placement); if (GetWindowPlacement(hwnd, &placement)) { frame = placement.rcNormalPosition; auto display = OwningDisplay(frame); OffsetRect(&frame, display.work.left - display.frame.left, display.work.top - display.frame.top); } }
180
+ auto display = OwningDisplay(frame); auto bounds = LogicalRect(frame, display); bounds.SetNamedValue(L"displayId", Json::JsonValue::CreateStringValue(display.id));
181
+ Json::JsonObject result;
182
+ result.SetNamedValue(L"id", Json::JsonValue::CreateStringValue(to_hstring(id))); result.SetNamedValue(L"instanceId", Json::JsonValue::CreateStringValue(to_hstring(window.instanceId)));
183
+ result.SetNamedValue(L"title", Json::JsonValue::CreateStringValue(window.app.Title())); result.SetNamedValue(L"kind", Json::JsonValue::CreateStringValue(window.overlay ? L"overlay" : L"window"));
184
+ result.SetNamedValue(L"parentId", window.parentId.empty() ? Json::JsonValue::CreateNullValue() : Json::JsonValue::CreateStringValue(to_hstring(window.parentId)));
185
+ result.SetNamedValue(L"modal", Json::JsonValue::CreateBooleanValue(window.modal)); result.SetNamedValue(L"visible", Json::JsonValue::CreateBooleanValue(IsWindowVisible(hwnd)));
186
+ result.SetNamedValue(L"focused", Json::JsonValue::CreateBooleanValue(GetForegroundWindow() == hwnd)); result.SetNamedValue(L"minimized", Json::JsonValue::CreateBooleanValue(IsIconic(hwnd)));
187
+ result.SetNamedValue(L"maximized", Json::JsonValue::CreateBooleanValue(IsZoomed(hwnd) != FALSE));
188
+ result.SetNamedValue(L"fullscreen", Json::JsonValue::CreateBooleanValue(window.app.Presenter().Kind() == UI::AppWindowPresenterKind::FullScreen)); result.SetNamedValue(L"bounds", bounds);
189
+ return result;
190
+ }
191
+ static Json::JsonArray Displays() {
192
+ Json::JsonArray result;
193
+ for (auto const &display : DisplayGeometries()) {
194
+ Json::JsonObject item, size;
195
+ item.SetNamedValue(L"id", Json::JsonValue::CreateStringValue(display.id)); item.SetNamedValue(L"persistentId", display.persistentId.empty() ? Json::JsonValue::CreateNullValue() : Json::JsonValue::CreateStringValue(display.persistentId));
196
+ item.SetNamedValue(L"name", Json::JsonValue::CreateStringValue(display.name)); item.SetNamedValue(L"primary", Json::JsonValue::CreateBooleanValue(display.primary));
197
+ size.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue((display.frame.right - display.frame.left) / display.scale)); size.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue((display.frame.bottom - display.frame.top) / display.scale));
198
+ item.SetNamedValue(L"size", size); item.SetNamedValue(L"workArea", LogicalRect(display.work, display)); item.SetNamedValue(L"scaleFactor", Json::JsonValue::CreateNumberValue(display.scale)); result.Append(item);
199
+ }
200
+ return result;
201
+ }
202
+ static void SetBounds(Window &window, Json::JsonObject const &bounds) {
203
+ auto display = DisplayWithId(bounds.GetNamedString(L"displayId"));
204
+ const double width = bounds.GetNamedNumber(L"width"), height = bounds.GetNamedNumber(L"height"), x = bounds.GetNamedNumber(L"x"), y = bounds.GetNamedNumber(L"y");
205
+ if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || !std::isfinite(height) || width < window.minWidth || height < window.minHeight || width > window.maxWidth || height > window.maxHeight || std::abs(x) > 10000000 || std::abs(y) > 10000000) throw winrt::hresult_invalid_argument(L"Bounds are outside window constraints");
206
+ window.app.MoveAndResize({static_cast<int32_t>(std::round(display.frame.left + x * display.scale)), static_cast<int32_t>(std::round(display.frame.top + y * display.scale)), static_cast<int32_t>(std::round(width * display.scale)), static_cast<int32_t>(std::round(height * display.scale))});
207
+ }
208
+ static Json::JsonObject CanonicalStyle(Json::JsonObject const &options) {
209
+ Json::JsonObject style;
210
+ for (auto key : {L"title", L"resizable", L"minimizable", L"alwaysOnTop", L"closable", L"appearance", L"backgroundColor", L"titleBarStyle", L"transparent", L"hasShadow"}) if (options.HasKey(key)) style.SetNamedValue(key, options.GetNamedValue(key));
211
+ for (auto key : {L"minSize", L"maxSize"}) if (options.HasKey(key)) {
212
+ bool minimum = std::wstring(key) == L"minSize"; auto value = options.GetNamedValue(key);
213
+ auto size = value.ValueType() == Json::JsonValueType::Null ? Json::JsonObject() : value.GetObject();
214
+ style.SetNamedValue(minimum ? L"minWidth" : L"maxWidth", Json::JsonValue::CreateNumberValue(size.GetNamedNumber(L"width", minimum ? 100 : 20000)));
215
+ style.SetNamedValue(minimum ? L"minHeight" : L"maxHeight", Json::JsonValue::CreateNumberValue(size.GetNamedNumber(L"height", minimum ? 100 : 20000)));
216
+ }
217
+ return style;
218
+ }
219
+ static double Dimension(Json::JsonObject const &args, wchar_t const *key, double fallback) {
158
220
  const auto value = args.GetNamedNumber(key, fallback);
159
221
  if (!std::isfinite(value) || value < 100 || value > 20000) throw winrt::hresult_invalid_argument(L"Invalid window dimension");
160
- return static_cast<int32_t>(value);
222
+ return value;
161
223
  }
162
224
  static void ApplyAppearance(Window &window) {
163
225
  auto foreground = winrt::Windows::UI::ViewManagement::UISettings().GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Foreground);
@@ -174,14 +236,18 @@ static void RestoreFrame(std::string const &id) {
174
236
  auto &window = windows.at(id); if (!window.restoreFrame) return;
175
237
  try {
176
238
  std::ifstream stream(SparkFile(id)); std::string text((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
177
- auto frame = Json::JsonObject::Parse(to_hstring(text));
178
- double x = frame.GetNamedNumber(L"x"), y = frame.GetNamedNumber(L"y");
179
- if (!std::isfinite(x) || !std::isfinite(y) || std::abs(x) > INT32_MAX / 2 || std::abs(y) > INT32_MAX / 2) return;
180
- auto width = Dimension(frame, L"width", 640), height = Dimension(frame, L"height", 480);
181
- width = std::clamp(width, window.minWidth, window.maxWidth); height = std::clamp(height, window.minHeight, window.maxHeight);
182
- RECT rect{static_cast<LONG>(x), static_cast<LONG>(y), static_cast<LONG>(x) + width, static_cast<LONG>(y) + height};
183
- if (MonitorFromRect(&rect, MONITOR_DEFAULTTONULL)) window.app.MoveAndResize({rect.left, rect.top, width, height});
184
- } catch (...) { /* Missing or invalid saved geometry uses configured defaults. */ }
239
+ auto saved = Json::JsonObject::Parse(to_hstring(text)); if (saved.GetNamedNumber(L"version", 0) != 1) return;
240
+ auto bounds = saved.GetNamedObject(L"bounds"); auto displays = DisplayGeometries(); auto display = displays.front();
241
+ for (auto const &candidate : displays) if (candidate.primary) display = candidate;
242
+ auto persistent = saved.GetNamedString(L"persistentId", L"");
243
+ for (auto const &candidate : displays) if (!persistent.empty() && candidate.persistentId == persistent.c_str()) { display = candidate; break; }
244
+ auto width = std::clamp(Dimension(bounds, L"width", 640), window.minWidth, window.maxWidth), height = std::clamp(Dimension(bounds, L"height", 480), window.minHeight, window.maxHeight);
245
+ auto work = LogicalRect(display.work, display); const auto left = work.GetNamedNumber(L"x"), top = work.GetNamedNumber(L"y");
246
+ bounds.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue(width)); bounds.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue(height));
247
+ bounds.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue(std::clamp(bounds.GetNamedNumber(L"x"), left, left + std::max(0.0, work.GetNamedNumber(L"width") - width))));
248
+ bounds.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue(std::clamp(bounds.GetNamedNumber(L"y"), top, top + std::max(0.0, work.GetNamedNumber(L"height") - height))));
249
+ bounds.SetNamedValue(L"displayId", Json::JsonValue::CreateStringValue(display.id)); SetBounds(window, bounds);
250
+ } catch (...) { /* Invalid or old development data uses configured defaults. */ }
185
251
  }
186
252
  static void ApplyOptions(Window &window, Json::JsonObject const &options) {
187
253
  static const std::set<std::wstring> supported{L"title", L"width", L"height", L"minWidth", L"minHeight", L"maxWidth", L"maxHeight", L"resizable", L"minimizable", L"alwaysOnTop", L"closable", L"appearance", L"backgroundColor", L"restoreFrame", L"titleBarStyle", L"transparent", L"hasShadow"};
@@ -189,9 +255,9 @@ static void ApplyOptions(Window &window, Json::JsonObject const &options) {
189
255
  const auto minWidth = Dimension(options, L"minWidth", window.minWidth), minHeight = Dimension(options, L"minHeight", window.minHeight);
190
256
  const auto maxWidth = Dimension(options, L"maxWidth", window.maxWidth), maxHeight = Dimension(options, L"maxHeight", window.maxHeight);
191
257
  if (minWidth > maxWidth || minHeight > maxHeight) throw winrt::hresult_invalid_argument(L"Invalid window constraints");
192
- auto size = window.app.Size();
193
- const auto width = Dimension(options, L"width", std::clamp(size.Width, minWidth, maxWidth));
194
- const auto height = Dimension(options, L"height", std::clamp(size.Height, minHeight, maxHeight));
258
+ auto size = window.app.Size(); const double scale = GetDpiForWindow(Hwnd(window)) / 96.0;
259
+ const auto width = Dimension(options, L"width", std::clamp(size.Width / scale, minWidth, maxWidth));
260
+ const auto height = Dimension(options, L"height", std::clamp(size.Height / scale, minHeight, maxHeight));
195
261
  if (width < minWidth || width > maxWidth || height < minHeight || height > maxHeight) throw winrt::hresult_invalid_argument(L"Window size is outside its constraints");
196
262
  window.minWidth = minWidth; window.minHeight = minHeight; window.maxWidth = maxWidth; window.maxHeight = maxHeight;
197
263
  if (options.HasKey(L"titleBarStyle")) {
@@ -235,13 +301,16 @@ static void ApplyOptions(Window &window, Json::JsonObject const &options) {
235
301
  }
236
302
  window.background.Brush(compositor.CreateColorBrush(winrt::Windows::UI::Color{static_cast<uint8_t>(rgba & 255), static_cast<uint8_t>((rgba >> 24) & 255), static_cast<uint8_t>((rgba >> 16) & 255), static_cast<uint8_t>((rgba >> 8) & 255)}));
237
303
  }
238
- if (window.app.Presenter().Kind() != UI::AppWindowPresenterKind::FullScreen) window.app.Resize({width, height});
304
+ if (window.app.Presenter().Kind() != UI::AppWindowPresenterKind::FullScreen) window.app.Resize({static_cast<int32_t>(std::round(width * scale)), static_cast<int32_t>(std::round(height * scale))});
239
305
  }
240
306
  static void QueueLaunch(std::wstring const &commandLine, bool initial) {
241
307
  int count = 0; auto argv = CommandLineToArgvW(commandLine.c_str(), &count);
242
308
  if (!argv) return;
243
309
  if (initial) for (int i = 0; i < count; ++i) launchArguments.Append(Json::JsonValue::CreateStringValue(argv[i]));
244
- else Emit({{"type", "secondInstance"}});
310
+ else {
311
+ RNW::JSValueArray arguments; for (int i = 0; i < count; ++i) arguments.push_back(to_string(argv[i]));
312
+ Emit({{"type", "secondInstance"}, {"arguments", std::move(arguments)}});
313
+ }
245
314
  for (int i = 1; i < count; ++i) {
246
315
  std::wstring candidate(argv[i]);
247
316
  if (candidate.rfind(L"--spark-dock-action=", 0) == 0) {
@@ -297,15 +366,30 @@ static bool ForwardLaunch() {
297
366
  static void SaveFrame(std::string const &id, HWND hwnd) {
298
367
  if (dataDirectory.empty() || !windows.at(id).restoreFrame || IsIconic(hwnd) || IsZoomed(hwnd) || windows.at(id).fullscreen) return;
299
368
  auto file = SparkFile(id), temporary = file; temporary += L".pending";
300
- auto text = to_string(Info(id).GetNamedObject(L"frame").Stringify()); std::ofstream stream(temporary, std::ios::binary | std::ios::trunc); stream << text; stream.close();
369
+ RECT frame{}; if (!GetWindowRect(hwnd, &frame)) return;
370
+ Json::JsonObject saved; saved.SetNamedValue(L"version", Json::JsonValue::CreateNumberValue(1)); saved.SetNamedValue(L"bounds", Info(id).GetNamedObject(L"bounds")); saved.SetNamedValue(L"persistentId", Json::JsonValue::CreateStringValue(OwningDisplay(frame).persistentId));
371
+ auto text = to_string(saved.Stringify()); std::ofstream stream(temporary, std::ios::binary | std::ios::trunc); stream << text; stream.close();
301
372
  if (stream) MoveFileExW(temporary.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
302
373
  }
374
+ static void FinishQuit(bool allow) {
375
+ if (!quitPending) return;
376
+ quitPending = false; pendingQuitGuards.clear(); if (!allow) FinishClose("main", false);
377
+ if (windows.count("main")) KillTimer(Hwnd(windows.at("main")), 0x4C48);
378
+ auto waiters = std::move(quitWaiters); quitWaiters.clear();
379
+ const auto result = allow ? "{\"quitRequested\":true}" : "{\"quitRequested\":false,\"reason\":\"vetoed\"}";
380
+ for (auto const &waiter : waiters) waiter.Resolve(result);
381
+ if (allow) {
382
+ quitAccepted = true; Emit({{"type", "willQuit"}});
383
+ windows.at("main").allow = true; windows.at("main").app.Destroy();
384
+ }
385
+ }
303
386
  static void RequestQuit() {
304
387
  if (quitPending) return;
305
- if (!quitGuard) { windows.at("main").allow = true; windows.at("main").app.Destroy(); return; }
306
388
  quitPending = true; auto request = quitRequest = ++generation;
307
- Emit({{"type", "beforeQuit"}, {"requestId", static_cast<int64_t>(request)}});
308
- SetTimer(Hwnd(windows.at("main")), 0x4C47, 30000, nullptr);
389
+ if (quitGuards.empty()) { FinishQuit(true); return; }
390
+ pendingQuitGuards = quitGuards;
391
+ for (auto const &guard : pendingQuitGuards) Emit({{"type", "beforeQuit"}, {"guardId", guard}, {"requestId", static_cast<int64_t>(request)}});
392
+ if (!SetTimer(Hwnd(windows.at("main")), 0x4C48, 30000, nullptr)) FinishQuit(false);
309
393
  }
310
394
  static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR) {
311
395
  auto id = Id(hwnd);
@@ -320,13 +404,16 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARA
320
404
  auto text = static_cast<wchar_t const *>(notification->lpData); if (text[notification->cbData / sizeof(wchar_t) - 1]) return FALSE;
321
405
  try { QueueNotificationResponse(to_string(text)); return TRUE; } catch (...) { return FALSE; }
322
406
  }
323
- auto data = reinterpret_cast<COPYDATASTRUCT *>(lParam); if (data && data->dwData == 0x4C47 && data->cbData >= sizeof(wchar_t) && data->cbData <= 65536 && data->cbData % sizeof(wchar_t) == 0) { auto text = static_cast<wchar_t *>(data->lpData); if (text && text[data->cbData / sizeof(wchar_t) - 1] == 0) { QueueLaunch(text, false); Emit({{"type", "reopen"}}); ActivateApplication(); return TRUE; } } return FALSE; }
407
+ auto data = reinterpret_cast<COPYDATASTRUCT *>(lParam); if (data && data->dwData == 0x4C47 && data->cbData >= sizeof(wchar_t) && data->cbData <= 65536 && data->cbData % sizeof(wchar_t) == 0) { auto text = static_cast<wchar_t *>(data->lpData); if (text && text[data->cbData / sizeof(wchar_t) - 1] == 0) { QueueLaunch(text, false);
408
+ bool visible = false; for (auto const &[key, window] : windows) if (IsWindowVisible(Hwnd(window)) && !window.overlay) visible = true;
409
+ Emit({{"type", "reopen"}, {"hasVisibleWindows", visible}, {"mainWindowWasVisible", !!IsWindowVisible(Hwnd(windows.at("main")))}}); ActivateApplication(); return TRUE; } } return FALSE; }
324
410
  if (message == WM_GETMINMAXINFO) {
325
411
  auto limits = reinterpret_cast<MINMAXINFO *>(lParam); auto const &window = windows.at(id);
326
- limits->ptMinTrackSize = {window.minWidth, window.minHeight}; limits->ptMaxTrackSize = {window.maxWidth, window.maxHeight}; return 0;
412
+ const double scale = GetDpiForWindow(hwnd) / 96.0;
413
+ limits->ptMinTrackSize = {static_cast<LONG>(std::ceil(window.minWidth * scale)), static_cast<LONG>(std::ceil(window.minHeight * scale))}; limits->ptMaxTrackSize = {static_cast<LONG>(std::floor(window.maxWidth * scale)), static_cast<LONG>(std::floor(window.maxHeight * scale))}; return 0;
327
414
  }
328
415
  if (message == WM_SHOWWINDOW) PostMessageW(hwnd, WM_APP + 18, 0, 0);
329
- if (message == WM_APP + 18) { UpdateModality(); return 0; }
416
+ if (message == WM_APP + 18) { UpdateModality(); Emit({{"type", "visibilityChanged"}, {"windowId", id}}); return 0; }
330
417
  if (message == WM_SETTINGCHANGE || message == WM_THEMECHANGED) ApplyAppearance(windows.at(id));
331
418
  if (message == WM_SYSCOMMAND && (wParam & 0xfff0) == SC_CLOSE && !windows.at(id).closable) return 0;
332
419
  if (message == WM_EXITSIZEMOVE) SaveFrame(id, hwnd);
@@ -347,12 +434,13 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARA
347
434
  if (message == WM_COMMAND && (HIWORD(wParam) == 0 || HIWORD(wParam) == 1)) {
348
435
  auto found = menuActions.find(LOWORD(wParam)); if (found != menuActions.end() && context) { context.EmitJSEvent(L"RCTDeviceEventEmitter", L"NativeMenuAction", found->second.Copy()); return 0; }
349
436
  }
350
- if (message == WM_TIMER && wParam == 0x4C47) { if (id == "main") quitPending = false; windows.at(id).pending = false; KillTimer(hwnd, 0x4C47); return 0; }
437
+ if (message == WM_TIMER && wParam == 0x4C48) { FinishQuit(false); return 0; }
438
+ if (message == WM_TIMER && wParam == 0x4C47) { windows.at(id).pending = false; KillTimer(hwnd, 0x4C47); FinishClose(id, false); Emit({{"type", "closeGuardTimeout"}, {"windowId", id}}); return 0; }
351
439
  }
352
440
  return DefSubclassProc(hwnd, message, wParam, lParam);
353
441
  }
354
442
  static void Attach(std::string id, RNW::ReactNativeWindow view) {
355
- Window window; window.view = view; window.app = view.AppWindow(); window.normal = window.app.Presenter().as<UI::OverlappedPresenter>(); window.monitor = MonitorFromWindow(Hwnd(window), MONITOR_DEFAULTTONEAREST); windows.emplace(id, window);
443
+ Window window; window.instanceId = std::to_string(++generation); window.view = view; window.app = view.AppWindow(); window.normal = window.app.Presenter().as<UI::OverlappedPresenter>(); window.monitor = MonitorFromWindow(Hwnd(window), MONITOR_DEFAULTTONEAREST); windows.emplace(id, window);
356
444
  SetPropW(Hwnd(window), (L"Spark.Window." + std::wstring(to_hstring(id))).c_str(), reinterpret_cast<HANDLE>(1));
357
445
  SetPropW(Hwnd(window), L"Spark.ReactWindow", reinterpret_cast<HANDLE>(1));
358
446
  if (id == "main") SetNotificationWindow(Hwnd(window));
@@ -366,11 +454,13 @@ static void Attach(std::string id, RNW::ReactNativeWindow view) {
366
454
  if (window.guard) {
367
455
  args.Cancel(true); if (window.pending) return;
368
456
  window.pending = true; window.request = ++generation;
369
- SetTimer(Hwnd(window), 0x4C47, 30000, nullptr);
457
+ if (!SetTimer(Hwnd(window), 0x4C47, 30000, nullptr)) { window.pending = false; FinishClose(id, false); return; }
370
458
  Emit({{"type", "beforeClose"}, {"windowId", id}, {"requestId", static_cast<int64_t>(window.request)}});
371
- } else if (id == "main" && quitGuard) { args.Cancel(true); RequestQuit(); }
459
+ } else if (id == "main" && !quitGuards.empty()) { args.Cancel(true); RequestQuit(); }
372
460
  });
373
461
  app.Destroying([id](auto const &, auto const &) {
462
+ FinishClose(id, true);
463
+ if (id == "main" && !quitAccepted) { quitAccepted = true; Emit({{"type", "willQuit"}}); }
374
464
  Emit({{"type", "closed"}, {"windowId", id}});
375
465
  if (id != "main") {
376
466
  // A menu attached to a destroyed HWND is otherwise destroyed by Windows.
@@ -397,9 +487,13 @@ static LRESULT CALLBACK Keyboard(int code, WPARAM remove, LPARAM param) {
397
487
  if (GetKeyState(VK_MENU) < 0) modifiers |= 1 << 19;
398
488
  if (GetKeyState(VK_SHIFT) < 0) modifiers |= 1 << 17;
399
489
  if (GetKeyState(VK_LWIN) < 0 || GetKeyState(VK_RWIN) < 0) modifiers |= 1 << 20;
490
+ Shortcut const *match = nullptr;
400
491
  for (auto const &[key, shortcut] : shortcuts) if (!id.empty() && (shortcut.windowId.empty() || shortcut.windowId == id) && shortcut.key == message->wParam && shortcut.modifiers == modifiers) {
401
- if (shortcut.repeat || !(message->lParam & (1LL << 30))) context.EmitJSEvent(L"RCTDeviceEventEmitter", L"shortcut", RNW::JSValueObject{{"id", key}});
402
- message->message = WM_NULL; break;
492
+ if (!match || !shortcut.windowId.empty()) match = &shortcut;
493
+ }
494
+ if (match) {
495
+ if (match->repeat || !(message->lParam & (1LL << 30))) context.EmitJSEvent(L"RCTDeviceEventEmitter", L"shortcut", RNW::JSValueObject{{"id", match->id}});
496
+ message->message = WM_NULL;
403
497
  }
404
498
  }
405
499
  }
@@ -418,15 +512,21 @@ static RNW::JSValue Value(Json::IJsonValue const &value) {
418
512
  static std::wstring MenuTitle(winrt::hstring const &title) {
419
513
  std::wstring result; for (auto c : title) { result += c; if (c == L'&') result += c; } return result;
420
514
  }
421
- static void RebuildMenus() {
422
- auto next = CreateMenu(); std::map<UINT, RNW::JSValueObject> actions; std::vector<ACCEL> accelerators; UINT command = 0x5000;
423
- for (auto const &value : menus) {
424
- auto menu = value.GetObject(); auto popup = CreatePopupMenu();
425
- for (auto const &entry : menu.GetNamedArray(L"items")) {
515
+ static HMENU BuildMenuItems(Json::JsonArray const &items, std::map<UINT, RNW::JSValueObject> &actions, std::vector<ACCEL> &accelerators, UINT &command, bool parentEnabled, bool root = false) {
516
+ auto popup = root ? CreateMenu() : CreatePopupMenu(); if (!popup) throw winrt::hresult_error(E_FAIL, L"Could not allocate application menu");
517
+ try {
518
+ for (auto const &entry : items) {
426
519
  auto item = entry.GetObject(); if (item.GetNamedBoolean(L"hidden", false)) continue;
427
- if (item.GetNamedBoolean(L"separator", false)) { AppendMenuW(popup, MF_SEPARATOR, 0, nullptr); continue; }
428
- auto flags = MF_STRING | (item.GetNamedBoolean(L"enabled", true) ? MF_ENABLED : MF_GRAYED) | (item.GetNamedBoolean(L"checked", false) ? MF_CHECKED : 0);
520
+ if (item.GetNamedBoolean(L"separator", false)) { if (!AppendMenuW(popup, MF_SEPARATOR, 0, nullptr)) throw winrt::hresult_error(E_FAIL, L"Could not add separator"); continue; }
521
+ const bool enabled = parentEnabled && item.GetNamedBoolean(L"enabled", true);
522
+ auto flags = MF_STRING | (enabled ? MF_ENABLED : MF_GRAYED) | (item.GetNamedBoolean(L"checked", false) ? MF_CHECKED : 0);
429
523
  auto title = MenuTitle(item.GetNamedString(L"title", L""));
524
+ if (item.HasKey(L"items")) {
525
+ auto child = BuildMenuItems(item.GetNamedArray(L"items"), actions, accelerators, command, enabled);
526
+ if (!AppendMenuW(popup, flags | MF_POPUP, reinterpret_cast<UINT_PTR>(child), title.c_str())) { DestroyMenu(child); throw winrt::hresult_error(E_FAIL, L"Could not add submenu"); }
527
+ continue;
528
+ }
529
+ if (command > 0xffff) throw winrt::hresult_invalid_argument(L"Too many application menu actions");
430
530
  if (item.HasKey(L"shortcut") && item.GetNamedValue(L"shortcut").ValueType() == Json::JsonValueType::Object) {
431
531
  auto shortcut = item.GetNamedObject(L"shortcut"); auto key = shortcut.GetNamedString(L"key", L"");
432
532
  if (key.size() == 1 && command <= 0xffff) {
@@ -439,23 +539,39 @@ static void RebuildMenus() {
439
539
  else if (c == 0xf728) code = VK_DELETE; else if (c == 0x7f) code = VK_BACK;
440
540
  else { auto scan = VkKeyScanW(c); if (scan != -1) { code = LOBYTE(scan); if (HIBYTE(scan) & 1) modifiers |= FSHIFT; if (HIBYTE(scan) & 2) modifiers |= FCONTROL; if (HIBYTE(scan) & 4) modifiers |= FALT; } }
441
541
  if (code) {
442
- if (item.GetNamedBoolean(L"enabled", true)) accelerators.push_back({modifiers, code, static_cast<WORD>(command)});
542
+ if (enabled) accelerators.push_back({modifiers, code, static_cast<WORD>(command)});
443
543
  title += L"\t"; if (modifiers & FCONTROL) title += L"Ctrl+"; if (modifiers & FALT) title += L"Alt+"; if (modifiers & FSHIFT) title += L"Shift+";
444
544
  wchar_t label[80]{}; auto scan = MapVirtualKeyW(code, MAPVK_VK_TO_VSC); if (code == VK_LEFT || code == VK_RIGHT || code == VK_UP || code == VK_DOWN || code == VK_DELETE) scan |= 0x100;
445
545
  GetKeyNameTextW(static_cast<LONG>(scan << 16), label, 80); title += label;
446
546
  }
447
547
  }
448
548
  }
449
- AppendMenuW(popup, flags, command, title.c_str());
450
- actions.emplace(command++, RNW::JSValueObject{{"ownerId", to_string(item.GetNamedString(L"_sparkOwner", to_hstring(menuOwner)))}, {"menuId", to_string(item.GetNamedString(L"_sparkMenu", menu.GetNamedString(L"id")))}, {"itemId", to_string(item.GetNamedString(L"id"))}, {"payload", Value(item.GetNamedObject(L"payload", Json::JsonObject()))}});
549
+ if (!AppendMenuW(popup, flags, command, title.c_str())) throw winrt::hresult_error(E_FAIL, L"Could not add menu action");
550
+ if (enabled) actions.emplace(command, RNW::JSValueObject{{"ownerId", to_string(item.GetNamedString(L"_sparkOwner"))}, {"itemId", to_string(item.GetNamedString(L"id"))}});
551
+ command++;
451
552
  }
452
- AppendMenuW(next, MF_POPUP, reinterpret_cast<UINT_PTR>(popup), MenuTitle(menu.GetNamedString(L"title")).c_str());
553
+ return popup;
554
+ } catch (...) { DestroyMenu(popup); throw; }
555
+ }
556
+ static void RebuildMenus(Json::JsonArray const &configuration) {
557
+ if (configuration.Size() == 0) {
558
+ for (auto const &[id, window] : windows) if (currentMenu && GetMenu(Hwnd(window)) == currentMenu) { SetMenu(Hwnd(window), nullptr); DrawMenuBar(Hwnd(window)); }
559
+ if (currentMenu) DestroyMenu(currentMenu); currentMenu = nullptr; menuActions.clear();
560
+ if (acceleratorTable) DestroyAcceleratorTable(acceleratorTable); acceleratorTable = nullptr;
561
+ return;
453
562
  }
454
- for (auto const &[id, window] : windows) SetMenu(Hwnd(window), nullptr);
455
- auto target = GetForegroundWindow(); if (Id(target).empty()) target = Hwnd(windows.at("main")); SetMenu(target, next); DrawMenuBar(target);
563
+ std::map<UINT, RNW::JSValueObject> actions; std::vector<ACCEL> accelerators; UINT command = 0x5000;
564
+ auto next = BuildMenuItems(configuration, actions, accelerators, command, true, true);
565
+ HACCEL keys = nullptr;
566
+ try {
567
+ if (!accelerators.empty()) { keys = CreateAcceleratorTableW(accelerators.data(), static_cast<int>(accelerators.size())); if (!keys) throw winrt::hresult_error(E_FAIL, L"Could not create menu accelerators"); }
568
+ auto target = GetForegroundWindow(); if (Id(target).empty()) { auto main = windows.find("main"); if (main == windows.end()) throw winrt::hresult_error(E_FAIL, L"Main window is unavailable"); target = Hwnd(main->second); }
569
+ if (!SetMenu(target, next)) throw winrt::hresult_error(E_FAIL, L"Could not attach application menu");
570
+ for (auto const &[id, window] : windows) if (Hwnd(window) != target && GetMenu(Hwnd(window)) == currentMenu) SetMenu(Hwnd(window), nullptr);
571
+ DrawMenuBar(target);
572
+ } catch (...) { if (keys) DestroyAcceleratorTable(keys); DestroyMenu(next); throw; }
456
573
  auto old = currentMenu; currentMenu = next; menuActions = std::move(actions); if (old) DestroyMenu(old);
457
- if (acceleratorTable) DestroyAcceleratorTable(acceleratorTable);
458
- acceleratorTable = accelerators.empty() ? nullptr : CreateAcceleratorTableW(accelerators.data(), static_cast<int>(accelerators.size()));
574
+ if (acceleratorTable) DestroyAcceleratorTable(acceleratorTable); acceleratorTable = keys;
459
575
  }
460
576
  static void Initialize(RNW::ReactNativeWin32App const &app) {
461
577
  // WinRT activation must follow init_apartment, never static initialization.
@@ -489,7 +605,7 @@ static void Initialize(RNW::ReactNativeWin32App const &app) {
489
605
  REACT_MODULE(SparkDesktopApp, L"NativeDesktopApp")
490
606
  struct SparkDesktopApp {
491
607
  REACT_INIT(Initialize)
492
- void Initialize(winrt::Microsoft::ReactNative::ReactContext const &context) noexcept { if (context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId<bool>{L"Spark", L"SecondaryRuntime"}).value_or(false)) return; SparkWin::context = context; SparkWin::quitGuard = false; SparkWin::quitPending = false; SparkWin::shortcuts.clear(); for (auto &[id, window] : SparkWin::windows) { window.guard = false; window.pending = false; } }
608
+ void Initialize(winrt::Microsoft::ReactNative::ReactContext const &context) noexcept { if (context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId<bool>{L"Spark", L"SecondaryRuntime"}).value_or(false)) return; SparkWin::context = context; SparkWin::quitGuards.clear(); SparkWin::pendingQuitGuards.clear(); SparkWin::quitWaiters.clear(); SparkWin::quitPending = false; SparkWin::quitAccepted = false; SparkWin::shortcuts.clear(); for (auto &[id, window] : SparkWin::windows) { window.guard = false; window.pending = false; window.guardId.clear(); window.closeWaiters.clear(); KillTimer(SparkWin::Hwnd(window), 0x4C47); } }
493
609
  REACT_METHOD(addListener) void addListener(std::string) noexcept {}
494
610
  REACT_METHOD(removeListeners) void removeListeners(double) noexcept {}
495
611
  REACT_METHOD(call)
@@ -499,10 +615,18 @@ struct SparkDesktopApp {
499
615
  try {
500
616
  auto args = Json::JsonObject::Parse(to_hstring(encoded));
501
617
  if (method == "notificationResponses") { promise.Resolve(NotificationResponses()); return; }
502
- if (method == "quitGuard") quitGuard = args.GetNamedBoolean(L"enabled");
503
- else if (method == "quit") RequestQuit();
618
+ if (method == "quitGuard") {
619
+ const auto id = to_string(args.GetNamedString(L"id"));
620
+ if (id.empty()) throw winrt::hresult_invalid_argument(L"Expected quit guard ID");
621
+ if (args.GetNamedBoolean(L"enabled")) quitGuards.insert(id); else quitGuards.erase(id);
622
+ FinishQuit(false);
623
+ } else if (method == "quit") { quitWaiters.push_back(promise); RequestQuit(); return; }
504
624
  else if (method == "replyQuit") {
505
- if (quitPending && args.GetNamedNumber(L"requestId") == quitRequest) { quitPending = false; KillTimer(Hwnd(windows.at("main")), 0x4C47); if (args.GetNamedBoolean(L"allow")) { windows.at("main").allow = true; windows.at("main").app.Destroy(); } }
625
+ auto id = to_string(args.GetNamedString(L"id"));
626
+ if (quitPending && args.GetNamedNumber(L"requestId") == quitRequest && pendingQuitGuards.erase(id)) {
627
+ if (!args.GetNamedBoolean(L"allow")) FinishQuit(false);
628
+ else if (pendingQuitGuards.empty()) FinishQuit(true);
629
+ }
506
630
  } else if (method == "hide") HideApplication();
507
631
  else if (method == "activate") ActivateApplication();
508
632
  else if (method == "context") {
@@ -522,13 +646,12 @@ struct SparkDesktopWindows {
522
646
  void call(std::string method, std::string encoded, winrt::Microsoft::ReactNative::ReactPromise<std::string> promise) noexcept {
523
647
  SparkWin::context.UIDispatcher().Post([method, encoded, promise]() {
524
648
  using namespace SparkWin;
649
+ std::string candidateId;
525
650
  try {
526
651
  auto args = Json::JsonObject::Parse(to_hstring(encoded)); auto id = to_string(args.GetNamedString(L"id", L"main"));
527
652
  if (method == "open") {
528
- if (windows.count(id)) {
529
- auto &existing = windows.at(id); auto hwnd = Hwnd(existing); ShowWindow(hwnd, existing.overlay ? SW_SHOWNOACTIVATE : IsIconic(hwnd) ? SW_RESTORE : SW_SHOW); UpdateModality(); if (!existing.overlay) SetForegroundWindow(hwnd);
530
- promise.Resolve(to_string(Info(id).Stringify())); return;
531
- }
653
+ if (id == "main") { promise.Reject(RNW::ReactError{"E_INVALID_ARGUMENT", "Main window is host-created"}); return; }
654
+ if (windows.count(id)) { promise.Reject(RNW::ReactError{"E_ALREADY_EXISTS", "Window ID is already open"}); return; }
532
655
  const auto parentId = to_string(args.GetNamedString(L"parentId", L""));
533
656
  const auto kind = args.GetNamedString(L"kind", L"window");
534
657
  if (kind != L"window" && kind != L"overlay") throw winrt::hresult_invalid_argument(L"Invalid window kind");
@@ -540,64 +663,91 @@ struct SparkDesktopWindows {
540
663
  if (modal) for (auto const &[childId, child] : windows) if (child.parentId == parentId && child.modal) { promise.Reject(RNW::ReactError{"E_BUSY", "Parent already has a modal window"}); return; }
541
664
  auto view = RNW::ReactNativeWindow::CreateFromCompositor(compositor); auto app = view.AppWindow();
542
665
  app.Title(args.GetNamedString(L"title", SparkWindowsEnv(L"SPARK_PROJECT_NAME"))); app.Resize({static_cast<int32_t>(args.GetNamedNumber(L"width", 640)), static_cast<int32_t>(args.GetNamedNumber(L"height", 480))});
543
- RNW::ReactViewOptions options; options.ComponentName(L"main");
544
- Json::JsonObject props; props.SetNamedValue(L"windowId", Json::JsonValue::CreateStringValue(to_hstring(id))); props.SetNamedValue(L"windowProps", args.GetNamedObject(L"props", Json::JsonObject()));
666
+ RNW::ReactViewOptions options; options.ComponentName(args.GetNamedString(L"component"));
667
+ auto props = args.GetNamedObject(L"props", Json::JsonObject());
545
668
  // Write JSON props through the public JSValue writer contract.
546
669
  auto propsJson = to_string(props.Stringify());
547
670
  options.InitialProps([propsJson](RNW::IJSValueWriter const &writer) { WriteJson(writer, Json::JsonObject::Parse(to_hstring(propsJson))); });
548
671
  // Validate all options before attaching/showing a new React root.
549
- Json::JsonObject style;
550
- for (auto const &pair : args) if (pair.Key() != L"id" && pair.Key() != L"props" && pair.Key() != L"parentId" && pair.Key() != L"modal" && pair.Key() != L"kind") style.SetNamedValue(pair.Key(), pair.Value());
672
+ auto style = CanonicalStyle(args);
551
673
  Window pending; pending.overlay = overlay; pending.view = view; pending.app = app; pending.normal = app.Presenter().as<UI::OverlappedPresenter>();
552
674
  try { ApplyOptions(pending, style); } catch (...) { app.Destroy(); throw; }
553
675
  if (overlay) {
554
676
  auto hwnd = Hwnd(pending);
555
677
  SetWindowLongPtrW(hwnd, GWL_EXSTYLE, (GetWindowLongPtrW(hwnd, GWL_EXSTYLE) | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
556
678
  }
557
- Attach(id, view);
679
+ Attach(id, view); candidateId = id;
558
680
  auto &attached = windows.at(id); attached.parentId = parentId; attached.modal = modal; attached.overlay = overlay; attached.transparent = pending.transparent; attached.shadow = pending.shadow;
559
681
  if (!parentId.empty()) {
560
682
  SetLastError(0); SetWindowLongPtrW(Hwnd(attached), GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(Hwnd(windows.at(parentId))));
561
683
  if (GetLastError()) { app.Destroy(); throw winrt::hresult_error(HRESULT_FROM_WIN32(GetLastError())); }
562
684
  }
563
- attached.minWidth = pending.minWidth; attached.minHeight = pending.minHeight; attached.maxWidth = pending.maxWidth; attached.maxHeight = pending.maxHeight; attached.closable = pending.closable; attached.restoreFrame = pending.restoreFrame; attached.theme = pending.theme; attached.resources = pending.resources; attached.background = pending.background;
564
- view.ReactNativeIsland().ReactViewHost(RNW::ReactCoreInjection::MakeViewHost(host, options)); RestoreFrame(id); ApplyAppearance(attached); app.Show(!overlay); UpdateModality(); Emit({{"type", "opened"}, {"windowId", id}});
685
+ attached.minWidth = pending.minWidth; attached.minHeight = pending.minHeight; attached.maxWidth = pending.maxWidth; attached.maxHeight = pending.maxHeight; attached.closable = pending.closable; attached.restoreFrame = args.GetNamedBoolean(L"restoreBounds", false); attached.theme = pending.theme; attached.resources = pending.resources; attached.background = pending.background;
686
+ view.ReactNativeIsland().ReactViewHost(RNW::ReactCoreInjection::MakeViewHost(host, options));
687
+ auto displays = DisplayGeometries(); auto display = displays.front();
688
+ for (auto const &candidate : displays) if (candidate.primary) display = candidate;
689
+ if (!parentId.empty()) { RECT parentFrame{}; GetWindowRect(Hwnd(windows.at(parentId)), &parentFrame); display = OwningDisplay(parentFrame); }
690
+ if (args.HasKey(L"position")) display = DisplayWithId(args.GetNamedObject(L"position").GetNamedString(L"displayId"));
691
+ auto size = args.GetNamedObject(L"size", Json::JsonObject()); double width = std::clamp(size.GetNamedNumber(L"width", 640), attached.minWidth, attached.maxWidth), height = std::clamp(size.GetNamedNumber(L"height", 480), attached.minHeight, attached.maxHeight);
692
+ auto work = LogicalRect(display.work, display); Json::JsonObject bounds;
693
+ bounds.SetNamedValue(L"displayId", Json::JsonValue::CreateStringValue(display.id)); bounds.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue(width)); bounds.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue(height));
694
+ bounds.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue(work.GetNamedNumber(L"x") + (work.GetNamedNumber(L"width") - width) / 2)); bounds.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue(work.GetNamedNumber(L"y") + (work.GetNamedNumber(L"height") - height) / 2));
695
+ SetBounds(attached, bounds); RestoreFrame(id); bounds = Info(id).GetNamedObject(L"bounds");
696
+ if (args.HasKey(L"size")) { bounds.SetNamedValue(L"width", Json::JsonValue::CreateNumberValue(width)); bounds.SetNamedValue(L"height", Json::JsonValue::CreateNumberValue(height)); }
697
+ if (args.HasKey(L"position")) for (auto const &entry : args.GetNamedObject(L"position")) bounds.SetNamedValue(entry.Key(), entry.Value());
698
+ SetBounds(attached, bounds); ApplyAppearance(attached); if (args.GetNamedBoolean(L"show", true)) app.Show(!overlay); UpdateModality(); Emit({{"type", "opened"}, {"windowId", id}});
565
699
  promise.Resolve(to_string(Info(id).Stringify())); return;
566
700
  }
567
701
  if (method == "displays") { promise.Resolve(to_string(Displays().Stringify())); return; }
702
+ if (method == "cursorPoint") {
703
+ POINT cursor{}; if (!GetCursorPos(&cursor)) winrt::throw_last_error();
704
+ RECT probe{cursor.x, cursor.y, cursor.x + 1, cursor.y + 1};
705
+ auto display = OwningDisplay(probe);
706
+ auto point = Json::JsonObject();
707
+ point.SetNamedValue(L"displayId", Json::JsonValue::CreateStringValue(display.id));
708
+ point.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue((cursor.x - display.frame.left) / display.scale));
709
+ point.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue((cursor.y - display.frame.top) / display.scale));
710
+ promise.Resolve(to_string(point.Stringify())); return;
711
+ }
568
712
  if (method == "list") { Json::JsonArray result; for (auto const &[name, window] : windows) result.Append(Info(name)); promise.Resolve(to_string(result.Stringify())); return; }
569
713
  auto found = windows.find(id); if (found == windows.end()) { promise.Reject(RNW::ReactError{"E_NOT_FOUND", "Window not found"}); return; }
570
714
  auto &window = found->second; auto hwnd = Hwnd(window);
571
- if (method == "info") { promise.Resolve(to_string(Info(id).Stringify())); return; }
572
- if (method == "options") ApplyOptions(window, args.GetNamedObject(L"options"));
715
+ if (args.HasKey(L"instanceId") && to_string(args.GetNamedString(L"instanceId")) != window.instanceId) { promise.Reject(RNW::ReactError{"E_NOT_FOUND", "Window instance has closed"}); return; }
716
+ if (method == "info" || method == "observe") { promise.Resolve(to_string(Info(id).Stringify())); return; }
717
+ if (method == "options") ApplyOptions(window, CanonicalStyle(args.GetNamedObject(L"options")));
573
718
  else if (method == "center") {
574
- MONITORINFO monitor{}; monitor.cbSize = sizeof(monitor);
575
- if (!GetMonitorInfoW(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &monitor)) winrt::throw_last_error();
576
- auto size = window.app.Size();
577
- window.app.Move({monitor.rcWork.left + (monitor.rcWork.right - monitor.rcWork.left - size.Width) / 2, monitor.rcWork.top + (monitor.rcWork.bottom - monitor.rcWork.top - size.Height) / 2});
578
- } else if (method == "frame") {
579
- auto frame = args.GetNamedObject(L"frame"); const auto x = frame.GetNamedNumber(L"x"), y = frame.GetNamedNumber(L"y");
580
- if (!std::isfinite(x) || !std::isfinite(y) || x < INT32_MIN || x > INT32_MAX || y < INT32_MIN || y > INT32_MAX) throw winrt::hresult_invalid_argument(L"Invalid frame origin");
581
- auto width = Dimension(frame, L"width", window.app.Size().Width), height = Dimension(frame, L"height", window.app.Size().Height);
582
- if (width < window.minWidth || width > window.maxWidth || height < window.minHeight || height > window.maxHeight) throw winrt::hresult_invalid_argument(L"Frame is outside window constraints");
583
- window.app.MoveAndResize({static_cast<int32_t>(x), static_cast<int32_t>(y), width, height});
719
+ RECT frame{}; if (!GetWindowRect(hwnd, &frame)) winrt::throw_last_error();
720
+ auto display = args.HasKey(L"displayId") ? DisplayWithId(args.GetNamedString(L"displayId")) : OwningDisplay(frame);
721
+ auto bounds = Info(id).GetNamedObject(L"bounds"), work = LogicalRect(display.work, display);
722
+ bounds.SetNamedValue(L"displayId", Json::JsonValue::CreateStringValue(display.id));
723
+ bounds.SetNamedValue(L"x", Json::JsonValue::CreateNumberValue(work.GetNamedNumber(L"x") + (work.GetNamedNumber(L"width") - bounds.GetNamedNumber(L"width")) / 2));
724
+ bounds.SetNamedValue(L"y", Json::JsonValue::CreateNumberValue(work.GetNamedNumber(L"y") + (work.GetNamedNumber(L"height") - bounds.GetNamedNumber(L"height")) / 2)); SetBounds(window, bounds);
725
+ } else if (method == "bounds") { SetBounds(window, args.GetNamedObject(L"bounds"));
584
726
  } else if (method == "fullscreen") {
585
727
  if (args.GetNamedBoolean(L"enabled")) window.app.SetPresenter(UI::AppWindowPresenterKind::FullScreen);
586
728
  else window.app.SetPresenter(window.normal);
587
729
  WindowTransitions(id);
588
- } else if (method == "title") window.app.Title(args.GetNamedString(L"title"));
589
- else if (method == "closeGuard") { window.guard = args.GetNamedBoolean(L"enabled"); window.pending = false; window.request = ++generation; KillTimer(hwnd, 0x4C47); }
590
- else if (method == "replyClose") {
591
- if (window.pending && args.GetNamedNumber(L"requestId", 0) == window.request) { window.pending = false; KillTimer(hwnd, 0x4C47); if (args.GetNamedBoolean(L"allow")) { if (id == "main") RequestQuit(); else { window.allow = true; window.app.Destroy(); } } }
592
- } else if (method == "close") PostMessageW(hwnd, WM_CLOSE, 0, 0);
593
- else if (method == "show") { ShowWindow(hwnd, window.overlay ? SW_SHOWNOACTIVATE : IsIconic(hwnd) ? SW_RESTORE : SW_SHOW); UpdateModality(); if (!window.overlay) SetForegroundWindow(hwnd); }
730
+ } else if (method == "closeGuard") {
731
+ auto guardId = to_string(args.GetNamedString(L"guardId")); bool enabled = args.GetNamedBoolean(L"enabled");
732
+ if (enabled && window.guard && window.guardId != guardId) { promise.Reject(RNW::ReactError{"E_BUSY", "Window already has a close guard"}); return; }
733
+ if (enabled || window.guardId == guardId) { window.guard = enabled; window.guardId = enabled ? guardId : ""; window.pending = false; window.request = ++generation; KillTimer(hwnd, 0x4C47); FinishClose(id, false); }
734
+ } else if (method == "replyClose") {
735
+ if (window.pending && args.GetNamedNumber(L"requestId", 0) == window.request && to_string(args.GetNamedString(L"guardId")) == window.guardId) {
736
+ window.pending = false; KillTimer(hwnd, 0x4C47);
737
+ if (args.GetNamedBoolean(L"allow")) { if (id == "main") RequestQuit(); else { window.allow = true; window.app.Destroy(); } } else FinishClose(id, false);
738
+ }
739
+ } else if (method == "close") {
740
+ window.closeWaiters.push_back(promise); if (!window.pending) SendMessageW(hwnd, WM_CLOSE, 0, 0);
741
+ auto stillOpen = windows.find(id); if (stillOpen != windows.end() && !stillOpen->second.pending && !(id == "main" && quitPending)) FinishClose(id, false);
742
+ return;
743
+ } else if (method == "show") { const bool focus = args.GetNamedBoolean(L"focus", true) && !window.overlay; ShowWindow(hwnd, !focus ? SW_SHOWNOACTIVATE : IsIconic(hwnd) ? SW_RESTORE : SW_SHOW); UpdateModality(); if (focus) SetForegroundWindow(hwnd); }
594
744
  else if (method == "hide") { window.app.Hide(); UpdateModality(); }
595
745
  else if (method == "minimize") ShowWindow(hwnd, SW_MINIMIZE);
596
746
  else if (method == "maximize") ShowWindow(hwnd, SW_MAXIMIZE);
597
747
  else if (method == "unmaximize") ShowWindow(hwnd, SW_RESTORE);
598
748
  else throw winrt::hresult_invalid_argument(L"Unsupported window operation");
599
749
  promise.Resolve("null");
600
- } catch (winrt::hresult_error const &error) { promise.Reject(RNW::ReactError{"E_WINDOW", to_string(error.message())}); }
750
+ } catch (winrt::hresult_error const &error) { if (!candidateId.empty() && windows.count(candidateId)) windows.at(candidateId).app.Destroy(); promise.Reject(RNW::ReactError{error.code() == E_INVALIDARG ? "E_INVALID_ARGUMENT" : error.code() == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) ? "E_NOT_FOUND" : "E_NATIVE", to_string(error.message())}); }
601
751
  });
602
752
  }
603
753
  };
@@ -615,7 +765,12 @@ struct SparkDesktopShortcuts {
615
765
  else if (method == "register") {
616
766
  auto text = args.GetNamedString(L"key"); if (text.size() != 1) throw winrt::hresult_invalid_argument(L"Invalid shortcut");
617
767
  wchar_t key = text[0]; UINT code = key >= 0xf704 && key <= 0xf717 ? VK_F1 + key - 0xf704 : key == 0xf700 ? VK_UP : key == 0xf701 ? VK_DOWN : key == 0xf702 ? VK_LEFT : key == 0xf703 ? VK_RIGHT : key == 0xf728 ? VK_DELETE : key == 0x7f ? VK_BACK : static_cast<UINT>(towupper(key));
618
- shortcuts[id] = {id, to_string(args.GetNamedString(L"windowId", L"")), code, static_cast<int>(args.GetNamedNumber(L"modifiers")), args.GetNamedBoolean(L"repeat", false)};
768
+ auto windowId = to_string(args.GetNamedString(L"windowId", L""));
769
+ auto modifiers = static_cast<int>(args.GetNamedNumber(L"modifiers"));
770
+ for (auto const &[existingId, existing] : shortcuts) if (existing.key == code && existing.modifiers == modifiers && existing.windowId == windowId) {
771
+ promise.Reject(RNW::ReactError{"E_BUSY", "Shortcut already registered in this scope"}); return;
772
+ }
773
+ shortcuts[id] = {id, windowId, code, modifiers, args.GetNamedBoolean(L"repeat", false)};
619
774
  } else throw winrt::hresult_invalid_argument(L"Unknown shortcut operation"); promise.Resolve("null");
620
775
  } catch (winrt::hresult_error const &error) { promise.Reject(RNW::ReactError{"E_SHORTCUT", to_string(error.message())}); }
621
776
  });
@@ -626,14 +781,15 @@ struct SparkNativeMenu {
626
781
  REACT_INIT(Initialize) void Initialize(winrt::Microsoft::ReactNative::ReactContext const &context) noexcept { if (context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId<bool>{L"Spark", L"SecondaryRuntime"}).value_or(false)) return; SparkWin::context = context; }
627
782
  REACT_METHOD(addListener) void addListener(std::string) noexcept {}
628
783
  REACT_METHOD(removeListeners) void removeListeners(double) noexcept {}
629
- REACT_METHOD(configureMenus) void configureMenus(std::string owner, std::string json) noexcept { SparkWin::context.UIDispatcher().Post([owner, json]() { SparkWin::menuOwner = owner; SparkWin::menus = SparkWin::Json::JsonArray::Parse(winrt::to_hstring(json)); SparkWin::RebuildMenus(); }); }
630
- REACT_METHOD(clearMenus) void clearMenus(std::string owner) noexcept { SparkWin::context.UIDispatcher().Post([owner]() { if (owner == SparkWin::menuOwner) { SparkWin::menus.Clear(); SparkWin::RebuildMenus(); } }); }
631
- REACT_METHOD(clearAllMenus) void clearAllMenus() noexcept { clearMenus(SparkWin::menuOwner); }
632
- REACT_METHOD(updateMenuItems) void updateMenuItems(std::string owner, std::string json) noexcept { SparkWin::context.UIDispatcher().Post([owner, json]() {
633
- if (owner != SparkWin::menuOwner) return; auto patches = SparkWin::Json::JsonArray::Parse(winrt::to_hstring(json));
634
- for (auto const &menu : SparkWin::menus) for (auto const &value : menu.GetObject().GetNamedArray(L"items")) for (auto const &patchValue : patches) { auto item = value.GetObject(), patch = patchValue.GetObject(); if (item.GetNamedString(L"id") == patch.GetNamedString(L"id")) for (auto const &pair : patch) item.SetNamedValue(pair.Key(), pair.Value()); }
635
- SparkWin::RebuildMenus();
636
- }); }
784
+ REACT_METHOD(publish) void publish(std::string json, RNW::ReactPromise<void> promise) noexcept {
785
+ SparkWin::context.UIDispatcher().Post([json, promise]() {
786
+ try {
787
+ auto next = SparkWin::Json::JsonArray::Parse(winrt::to_hstring(json));
788
+ SparkWin::RebuildMenus(next); SparkWin::menus = next; promise.Resolve();
789
+ } catch (winrt::hresult_error const &error) { promise.Reject(RNW::ReactError{"E_NATIVE", winrt::to_string(error.message())}); }
790
+ catch (std::exception const &error) { promise.Reject(RNW::ReactError{"E_NATIVE", error.what()}); }
791
+ });
792
+ }
637
793
  };
638
794
 
639
795
  // Development clients never update themselves. Match the macOS development API