neoagent 2.3.1-beta.2 → 2.3.1-beta.21

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 (264) hide show
  1. package/.env.example +39 -0
  2. package/README.md +2 -0
  3. package/docs/capabilities.md +2 -2
  4. package/docs/configuration.md +13 -5
  5. package/docs/integrations.md +4 -1
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +109 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  30. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  31. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  32. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  33. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  35. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  36. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  37. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  43. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  44. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  45. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  46. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  48. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  49. package/flutter_app/android/build.gradle.kts +24 -0
  50. package/flutter_app/android/ci-release.keystore +0 -0
  51. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  52. package/flutter_app/android/gradle.properties +3 -0
  53. package/flutter_app/android/key.properties +4 -0
  54. package/flutter_app/android/settings.gradle.kts +26 -0
  55. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  56. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  57. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  58. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  59. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  60. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  62. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  63. package/flutter_app/lib/features/location/location_service.dart +119 -0
  64. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  65. package/flutter_app/lib/main.dart +23057 -0
  66. package/flutter_app/lib/main_app_shell.dart +1682 -0
  67. package/flutter_app/lib/main_integrations.dart +931 -0
  68. package/flutter_app/lib/main_launcher.dart +959 -0
  69. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  70. package/flutter_app/lib/main_models.dart +3473 -0
  71. package/flutter_app/lib/main_shared.dart +2861 -0
  72. package/flutter_app/lib/main_theme.dart +204 -0
  73. package/flutter_app/lib/main_voice_assistant.dart +831 -0
  74. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  75. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  76. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  77. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  78. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  79. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  80. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  81. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  82. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  83. package/flutter_app/lib/src/backend_client.dart +1833 -0
  84. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  85. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  86. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  87. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  88. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  89. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  90. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  91. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  92. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  93. package/flutter_app/lib/src/health_bridge.dart +136 -0
  94. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  95. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  96. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  97. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  98. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  99. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  100. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  101. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  102. package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
  103. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  104. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  105. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  106. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  107. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  108. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  109. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  110. package/flutter_app/lib/src/theme/palette.dart +81 -0
  111. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  112. package/flutter_app/linux/CMakeLists.txt +128 -0
  113. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  114. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
  115. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  116. package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
  117. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  118. package/flutter_app/linux/runner/main.cc +6 -0
  119. package/flutter_app/linux/runner/my_application.cc +144 -0
  120. package/flutter_app/linux/runner/my_application.h +18 -0
  121. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  122. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  123. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  124. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
  125. package/flutter_app/macos/Podfile +42 -0
  126. package/flutter_app/macos/Podfile.lock +87 -0
  127. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  128. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  129. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  130. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  131. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  132. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  133. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  134. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  135. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  136. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  137. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  138. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  139. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  140. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  141. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  142. package/flutter_app/macos/Runner/Info.plist +36 -0
  143. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  144. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  145. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  146. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  147. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  148. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  149. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  150. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  151. package/flutter_app/patch_strings.py +12 -0
  152. package/flutter_app/pubspec.lock +1088 -0
  153. package/flutter_app/pubspec.yaml +53 -0
  154. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  155. package/flutter_app/test/recording_payloads_test.dart +53 -0
  156. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  157. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  158. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  159. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  160. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  161. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  162. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  163. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  164. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  165. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  166. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  167. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  168. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  169. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  170. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  171. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  172. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  173. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  174. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  175. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  176. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  177. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  178. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  179. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  180. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  181. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  182. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  183. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  184. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  185. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  186. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  187. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  188. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  189. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  190. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  191. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  192. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  193. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  194. package/flutter_app/web/favicon.png +0 -0
  195. package/flutter_app/web/favicon.svg +12 -0
  196. package/flutter_app/web/icons/Icon-192.png +0 -0
  197. package/flutter_app/web/icons/Icon-512.png +0 -0
  198. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  199. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  200. package/flutter_app/web/index.html +39 -0
  201. package/flutter_app/web/manifest.json +35 -0
  202. package/flutter_app/windows/CMakeLists.txt +108 -0
  203. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  204. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
  205. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  206. package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
  207. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  208. package/flutter_app/windows/runner/Runner.rc +121 -0
  209. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  210. package/flutter_app/windows/runner/flutter_window.h +37 -0
  211. package/flutter_app/windows/runner/main.cpp +53 -0
  212. package/flutter_app/windows/runner/resource.h +16 -0
  213. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  214. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  215. package/flutter_app/windows/runner/utils.cpp +65 -0
  216. package/flutter_app/windows/runner/utils.h +19 -0
  217. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  218. package/flutter_app/windows/runner/win32_window.h +102 -0
  219. package/lib/manager.js +231 -7
  220. package/package.json +3 -1
  221. package/server/db/database.js +68 -0
  222. package/server/http/middleware.js +50 -0
  223. package/server/http/routes.js +3 -1
  224. package/server/index.js +1 -0
  225. package/server/public/.last_build_id +1 -1
  226. package/server/public/assets/NOTICES +61 -0
  227. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  228. package/server/public/flutter_bootstrap.js +1 -1
  229. package/server/public/main.dart.js +65262 -64422
  230. package/server/routes/integrations.js +86 -0
  231. package/server/routes/memory.js +11 -2
  232. package/server/routes/screenHistory.js +46 -0
  233. package/server/routes/triggers.js +81 -0
  234. package/server/services/ai/models.js +30 -0
  235. package/server/services/ai/providers/githubCopilot.js +97 -0
  236. package/server/services/ai/providers/openai.js +2 -1
  237. package/server/services/ai/providers/openaiCodex.js +31 -0
  238. package/server/services/ai/settings.js +20 -0
  239. package/server/services/ai/systemPrompt.js +1 -1
  240. package/server/services/ai/tools.js +35 -6
  241. package/server/services/browser/controller.js +47 -3
  242. package/server/services/desktop/screenRecorder.js +172 -0
  243. package/server/services/integrations/env.js +5 -0
  244. package/server/services/integrations/github/common.js +106 -0
  245. package/server/services/integrations/github/provider.js +499 -0
  246. package/server/services/integrations/github/repos.js +1124 -0
  247. package/server/services/integrations/home_assistant/provider.js +306 -26
  248. package/server/services/integrations/manager.js +63 -7
  249. package/server/services/integrations/oauth_provider.js +13 -6
  250. package/server/services/integrations/provider_config_store.js +76 -0
  251. package/server/services/integrations/registry.js +4 -0
  252. package/server/services/integrations/trello/provider.js +744 -0
  253. package/server/services/integrations/whatsapp/provider.js +6 -2
  254. package/server/services/manager.js +22 -0
  255. package/server/services/memory/manager.js +39 -2
  256. package/server/services/skills/base_catalog.js +33 -0
  257. package/server/services/tasks/adapters/index.js +1 -0
  258. package/server/services/tasks/adapters/manual.js +12 -0
  259. package/server/services/tasks/runtime.js +1 -1
  260. package/server/services/voice/openaiClient.js +4 -1
  261. package/server/services/voice/providers.js +2 -1
  262. package/server/services/widgets/service.js +49 -4
  263. package/server/utils/local_secrets.js +56 -0
  264. package/server/utils/logger.js +37 -9
@@ -0,0 +1,692 @@
1
+ #include "include/audio_capture/audio_capture_plugin.h"
2
+ #include "include/audio_capture/mic_capture_plugin.h"
3
+
4
+ #include <flutter_linux/flutter_linux.h>
5
+ #include <glib-object.h>
6
+ #include <glib.h>
7
+ #include <pulse/error.h>
8
+ #include <pulse/simple.h>
9
+
10
+ #include <algorithm>
11
+ #include <cmath>
12
+ #include <cstdint>
13
+ #include <memory>
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ namespace {
18
+
19
+ constexpr char kMethodChannelName[] = "com.system_audio_transcriber/audio_capture";
20
+ constexpr char kEventChannelName[] = "com.system_audio_transcriber/audio_stream";
21
+ constexpr char kStatusEventChannelName[] = "com.system_audio_transcriber/audio_status";
22
+ constexpr char kDecibelEventChannelName[] = "com.system_audio_transcriber/audio_decibel";
23
+
24
+ constexpr int kDefaultSampleRate = 16000;
25
+ constexpr int kDefaultChannels = 1;
26
+ constexpr int kDefaultBitsPerSample = 16;
27
+ constexpr int kDefaultChunkDurationMs = 1000;
28
+ constexpr float kDefaultGainBoost = 2.5f;
29
+ constexpr float kDefaultInputVolume = 1.0f;
30
+
31
+ struct AudioChunkPayload {
32
+ AudioChunkPayload(AudioCapturePlugin* plugin, GBytes* bytes, double decibel)
33
+ : plugin(plugin), bytes(bytes), decibel(decibel) {}
34
+
35
+ AudioCapturePlugin* plugin;
36
+ GBytes* bytes;
37
+ double decibel;
38
+ };
39
+
40
+ struct CaptureThreadContext {
41
+ AudioCapturePlugin* plugin;
42
+ pa_simple* stream;
43
+ size_t chunk_size;
44
+ int sample_rate;
45
+ int channels;
46
+ int bits_per_sample;
47
+ float gain_boost;
48
+ float input_volume;
49
+ };
50
+
51
+ gboolean EmitAudioOnMainThread(gpointer user_data);
52
+ gpointer CaptureThread(gpointer user_data);
53
+ double CalculateDecibel(const int16_t* samples, size_t sample_count);
54
+
55
+ } // namespace
56
+
57
+ struct _AudioCapturePlugin {
58
+ GObject parent_instance;
59
+
60
+ FlMethodChannel* method_channel;
61
+ FlEventChannel* event_channel;
62
+ FlEventChannel* status_event_channel;
63
+ FlEventChannel* decibel_event_channel;
64
+ GMainContext* main_context;
65
+
66
+ GMutex lock;
67
+ gint should_stop;
68
+ gboolean is_capturing;
69
+ gboolean has_listener;
70
+ gboolean has_status_listener;
71
+ gboolean has_decibel_listener;
72
+
73
+ GThread* capture_thread;
74
+ };
75
+
76
+ G_DEFINE_TYPE(AudioCapturePlugin, audio_capture_plugin, G_TYPE_OBJECT)
77
+
78
+ namespace {
79
+
80
+ bool OpenPulseStream(int sample_rate, int channels, int bits_per_sample,
81
+ size_t chunk_size, pa_simple** out_stream, std::string* error_message) {
82
+ pa_sample_spec spec;
83
+ spec.rate = sample_rate;
84
+ spec.channels = static_cast<uint8_t>(channels);
85
+ if (bits_per_sample == 16) {
86
+ spec.format = PA_SAMPLE_S16LE;
87
+ } else {
88
+ spec.format = PA_SAMPLE_S16LE;
89
+ }
90
+
91
+ pa_buffer_attr attr;
92
+ attr.maxlength = static_cast<uint32_t>(chunk_size * 4);
93
+ attr.tlength = (uint32_t)-1;
94
+ attr.prebuf = (uint32_t)-1;
95
+ attr.minreq = (uint32_t)-1;
96
+ attr.fragsize = static_cast<uint32_t>(chunk_size);
97
+
98
+ int error = 0;
99
+
100
+ pa_simple* stream =
101
+ pa_simple_new(nullptr, "Voxa", PA_STREAM_RECORD, "@DEFAULT_MONITOR@",
102
+ "System Capture", &spec, nullptr, &attr, &error);
103
+
104
+ if (stream == nullptr) {
105
+ // Fallback to default source (microphone) if monitor is unavailable.
106
+ stream = pa_simple_new(nullptr, "Voxa", PA_STREAM_RECORD, nullptr,
107
+ "Default Capture", &spec, nullptr, &attr, &error);
108
+ }
109
+
110
+ if (stream == nullptr) {
111
+ if (error_message != nullptr) {
112
+ *error_message = pa_strerror(error);
113
+ }
114
+ return false;
115
+ }
116
+
117
+ *out_stream = stream;
118
+ return true;
119
+ }
120
+
121
+ size_t CalculateChunkSize(int sample_rate, int channels, int bits_per_sample,
122
+ int chunk_duration_ms) {
123
+ const int bytes_per_sample = std::max(bits_per_sample / 8, 1);
124
+ const size_t bytes_per_second =
125
+ static_cast<size_t>(sample_rate) * static_cast<size_t>(channels) *
126
+ static_cast<size_t>(bytes_per_sample);
127
+ size_t chunk_size =
128
+ (bytes_per_second * static_cast<size_t>(chunk_duration_ms)) / 1000;
129
+ if (chunk_size == 0) {
130
+ chunk_size = bytes_per_second / 20; // 50 ms fallback
131
+ }
132
+ const size_t frame_size = static_cast<size_t>(channels) * bytes_per_sample;
133
+ chunk_size = std::max(chunk_size, frame_size);
134
+ return chunk_size;
135
+ }
136
+
137
+ void ApplyGainBoostAndConvertToMono(const int16_t* input, int16_t* output,
138
+ size_t frame_count, int input_channels,
139
+ float gain_boost) {
140
+ const float max_value = 32767.0f;
141
+ const float min_value = -32768.0f;
142
+
143
+ if (input_channels == 1) {
144
+ // Mono: just apply gain boost
145
+ for (size_t i = 0; i < frame_count; ++i) {
146
+ float sample = static_cast<float>(input[i]) * gain_boost;
147
+ sample = std::max(min_value, std::min(max_value, sample));
148
+ output[i] = static_cast<int16_t>(sample);
149
+ }
150
+ } else {
151
+ // Stereo: convert to mono and apply gain boost
152
+ for (size_t i = 0; i < frame_count; ++i) {
153
+ float left = static_cast<float>(input[i * 2]);
154
+ float right = static_cast<float>(input[i * 2 + 1]);
155
+ float mono = (left + right) / 2.0f * gain_boost;
156
+ mono = std::max(min_value, std::min(max_value, mono));
157
+ output[i] = static_cast<int16_t>(mono);
158
+ }
159
+ }
160
+ }
161
+
162
+ double CalculateDecibel(const int16_t* samples, size_t sample_count) {
163
+ if (sample_count == 0) {
164
+ return -120.0;
165
+ }
166
+
167
+ // Calculate RMS (Root Mean Square)
168
+ double sum_of_squares = 0.0;
169
+ for (size_t i = 0; i < sample_count; ++i) {
170
+ double value = static_cast<double>(samples[i]);
171
+ sum_of_squares += value * value;
172
+ }
173
+ double mean_square = sum_of_squares / static_cast<double>(sample_count);
174
+ double rms = sqrt(mean_square);
175
+
176
+ // Calculate decibel: dB = 20 * log10(RMS / max_value)
177
+ // For Int16, max_value is 32767.0
178
+ const double max_value = 32767.0;
179
+ if (rms <= 0.0) {
180
+ return -120.0; // Avoid log(0)
181
+ }
182
+
183
+ double decibel = 20.0 * log10(rms / max_value);
184
+
185
+ // Clamp to reasonable range (-120 dB to 0 dB)
186
+ return std::max(-120.0, std::min(0.0, decibel));
187
+ }
188
+
189
+ gboolean EmitAudioOnMainThread(gpointer user_data) {
190
+ std::unique_ptr<AudioChunkPayload> payload(
191
+ static_cast<AudioChunkPayload*>(user_data));
192
+ AudioCapturePlugin* plugin = payload->plugin;
193
+
194
+ gsize length = 0;
195
+ const guint8* data =
196
+ static_cast<const guint8*>(g_bytes_get_data(payload->bytes, &length));
197
+
198
+ g_mutex_lock(&plugin->lock);
199
+ const gboolean can_emit =
200
+ plugin->event_channel != nullptr && plugin->has_listener;
201
+ const gboolean can_emit_decibel =
202
+ plugin->decibel_event_channel != nullptr && plugin->has_decibel_listener;
203
+ g_mutex_unlock(&plugin->lock);
204
+
205
+ if (can_emit && length > 0) {
206
+ g_autoptr(FlValue) value = fl_value_new_uint8_list(data, length);
207
+ g_autoptr(GError) error = nullptr;
208
+
209
+ if (!fl_event_channel_send(plugin->event_channel, value, nullptr, &error)) {
210
+ g_warning("Failed to send audio chunk: %s",
211
+ error != nullptr ? error->message : "unknown error");
212
+ }
213
+ }
214
+
215
+ // Send decibel data
216
+ if (can_emit_decibel) {
217
+ g_autoptr(FlValue) decibel_map = fl_value_new_map();
218
+ fl_value_set_string_take(decibel_map, "decibel", fl_value_new_float(payload->decibel));
219
+ fl_value_set_string_take(decibel_map, "timestamp", fl_value_new_float(g_get_real_time() / 1000000.0));
220
+
221
+ g_autoptr(GError) error = nullptr;
222
+ if (!fl_event_channel_send(plugin->decibel_event_channel, decibel_map, nullptr, &error)) {
223
+ g_warning("Failed to send decibel data: %s",
224
+ error != nullptr ? error->message : "unknown error");
225
+ }
226
+ }
227
+
228
+ g_bytes_unref(payload->bytes);
229
+ g_object_unref(plugin);
230
+
231
+ return G_SOURCE_REMOVE;
232
+ }
233
+
234
+ gpointer CaptureThread(gpointer user_data) {
235
+ std::unique_ptr<CaptureThreadContext> context(
236
+ static_cast<CaptureThreadContext*>(user_data));
237
+ AudioCapturePlugin* plugin = context->plugin;
238
+
239
+ // Read raw audio from PulseAudio
240
+ std::vector<uint8_t> raw_buffer(context->chunk_size);
241
+
242
+ // Output buffer for processed audio (mono)
243
+ const size_t output_frame_count = context->chunk_size / (sizeof(int16_t) * context->channels);
244
+ std::vector<int16_t> output_buffer(output_frame_count);
245
+
246
+ while (!g_atomic_int_get(&plugin->should_stop)) {
247
+ int error = 0;
248
+ if (pa_simple_read(context->stream, raw_buffer.data(), raw_buffer.size(),
249
+ &error) < 0) {
250
+ g_warning("PulseAudio read error: %s", pa_strerror(error));
251
+ break;
252
+ }
253
+
254
+ if (g_atomic_int_get(&plugin->should_stop)) {
255
+ break;
256
+ }
257
+
258
+ // Apply input volume
259
+ if (context->input_volume < 1.0f) {
260
+ int16_t* samples = reinterpret_cast<int16_t*>(raw_buffer.data());
261
+ const size_t sample_count = raw_buffer.size() / sizeof(int16_t);
262
+ for (size_t i = 0; i < sample_count; ++i) {
263
+ samples[i] = static_cast<int16_t>(
264
+ static_cast<float>(samples[i]) * context->input_volume);
265
+ }
266
+ }
267
+
268
+ // Process audio: convert to mono and apply gain boost
269
+ const int16_t* input_samples =
270
+ reinterpret_cast<const int16_t*>(raw_buffer.data());
271
+ const size_t input_frame_count =
272
+ raw_buffer.size() / (sizeof(int16_t) * context->channels);
273
+ const size_t frames_to_process =
274
+ std::min(input_frame_count, output_frame_count);
275
+
276
+ // Always process to ensure mono output and gain boost application
277
+ ApplyGainBoostAndConvertToMono(input_samples, output_buffer.data(),
278
+ frames_to_process, context->channels,
279
+ context->gain_boost);
280
+
281
+ // Create output bytes (mono)
282
+ const size_t output_bytes = frames_to_process * sizeof(int16_t);
283
+
284
+ // Calculate decibel from output buffer
285
+ double decibel = CalculateDecibel(output_buffer.data(), frames_to_process);
286
+
287
+ GBytes* bytes = g_bytes_new(output_buffer.data(), output_bytes);
288
+ auto* payload = new AudioChunkPayload(plugin, bytes, decibel);
289
+ g_object_ref(plugin);
290
+ g_main_context_invoke_full(plugin->main_context, G_PRIORITY_DEFAULT,
291
+ EmitAudioOnMainThread, payload, nullptr);
292
+ }
293
+
294
+ pa_simple_free(context->stream);
295
+
296
+ g_mutex_lock(&plugin->lock);
297
+ plugin->is_capturing = FALSE;
298
+ plugin->capture_thread = nullptr;
299
+ g_mutex_unlock(&plugin->lock);
300
+
301
+ // Send status update
302
+ g_mutex_lock(&plugin->lock);
303
+ const gboolean has_status_listener = plugin->has_status_listener;
304
+ g_mutex_unlock(&plugin->lock);
305
+
306
+ if (has_status_listener && plugin->status_event_channel != nullptr) {
307
+ g_autoptr(FlValue) status_map = fl_value_new_map();
308
+ fl_value_set_string_take(status_map, "isActive", fl_value_new_bool(FALSE));
309
+ fl_value_set_string_take(status_map, "timestamp", fl_value_new_float(g_get_real_time() / 1000000.0));
310
+
311
+ g_autoptr(GError) error = nullptr;
312
+ fl_event_channel_send(plugin->status_event_channel, status_map, nullptr, &error);
313
+ }
314
+
315
+ g_object_unref(plugin);
316
+ return nullptr;
317
+ }
318
+
319
+ static FlMethodErrorResponse* OnListenHandler(FlEventChannel* channel,
320
+ FlValue* arguments,
321
+ gpointer user_data) {
322
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
323
+ (void)channel;
324
+ (void)arguments;
325
+ g_mutex_lock(&plugin->lock);
326
+ plugin->has_listener = TRUE;
327
+ g_mutex_unlock(&plugin->lock);
328
+ return nullptr;
329
+ }
330
+
331
+ static FlMethodErrorResponse* OnCancelHandler(FlEventChannel* channel,
332
+ FlValue* arguments,
333
+ gpointer user_data) {
334
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
335
+ (void)channel;
336
+ (void)arguments;
337
+ g_mutex_lock(&plugin->lock);
338
+ plugin->has_listener = FALSE;
339
+ g_mutex_unlock(&plugin->lock);
340
+ return nullptr;
341
+ }
342
+
343
+ static FlMethodErrorResponse* OnStatusListenHandler(FlEventChannel* channel,
344
+ FlValue* arguments,
345
+ gpointer user_data) {
346
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
347
+ (void)channel;
348
+ (void)arguments;
349
+ g_mutex_lock(&plugin->lock);
350
+ plugin->has_status_listener = TRUE;
351
+ const gboolean is_active = plugin->is_capturing;
352
+ g_mutex_unlock(&plugin->lock);
353
+
354
+ // Send current status immediately
355
+ g_autoptr(FlValue) status_map = fl_value_new_map();
356
+ fl_value_set_string_take(status_map, "isActive", fl_value_new_bool(is_active));
357
+ fl_value_set_string_take(status_map, "timestamp", fl_value_new_float(g_get_real_time() / 1000000.0));
358
+
359
+ g_autoptr(GError) error = nullptr;
360
+ fl_event_channel_send(plugin->status_event_channel, status_map, nullptr, &error);
361
+
362
+ return nullptr;
363
+ }
364
+
365
+ static FlMethodErrorResponse* OnStatusCancelHandler(FlEventChannel* channel,
366
+ FlValue* arguments,
367
+ gpointer user_data) {
368
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
369
+ (void)channel;
370
+ (void)arguments;
371
+ g_mutex_lock(&plugin->lock);
372
+ plugin->has_status_listener = FALSE;
373
+ g_mutex_unlock(&plugin->lock);
374
+ return nullptr;
375
+ }
376
+
377
+ static FlMethodErrorResponse* OnDecibelListenHandler(FlEventChannel* channel,
378
+ FlValue* arguments,
379
+ gpointer user_data) {
380
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
381
+ (void)channel;
382
+ (void)arguments;
383
+ g_mutex_lock(&plugin->lock);
384
+ plugin->has_decibel_listener = TRUE;
385
+ g_mutex_unlock(&plugin->lock);
386
+ return nullptr;
387
+ }
388
+
389
+ static FlMethodErrorResponse* OnDecibelCancelHandler(FlEventChannel* channel,
390
+ FlValue* arguments,
391
+ gpointer user_data) {
392
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
393
+ (void)channel;
394
+ (void)arguments;
395
+ g_mutex_lock(&plugin->lock);
396
+ plugin->has_decibel_listener = FALSE;
397
+ g_mutex_unlock(&plugin->lock);
398
+ return nullptr;
399
+ }
400
+
401
+ bool StartCapture(AudioCapturePlugin* plugin, FlValue* args) {
402
+ int sample_rate = kDefaultSampleRate;
403
+ int channels = kDefaultChannels;
404
+ int bits_per_sample = kDefaultBitsPerSample;
405
+ int chunk_duration_ms = kDefaultChunkDurationMs;
406
+ float gain_boost = kDefaultGainBoost;
407
+ float input_volume = kDefaultInputVolume;
408
+
409
+ if (args != nullptr && fl_value_get_type(args) == FL_VALUE_TYPE_MAP) {
410
+ FlValue* value = nullptr;
411
+
412
+ value = fl_value_lookup_string(args, "sampleRate");
413
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_INT) {
414
+ sample_rate = fl_value_get_int(value);
415
+ }
416
+
417
+ value = fl_value_lookup_string(args, "channels");
418
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_INT) {
419
+ channels = fl_value_get_int(value);
420
+ }
421
+
422
+ value = fl_value_lookup_string(args, "bitsPerSample");
423
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_INT) {
424
+ bits_per_sample = fl_value_get_int(value);
425
+ }
426
+
427
+ value = fl_value_lookup_string(args, "chunkDurationMs");
428
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_INT) {
429
+ chunk_duration_ms = fl_value_get_int(value);
430
+ }
431
+
432
+ value = fl_value_lookup_string(args, "gainBoost");
433
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_FLOAT) {
434
+ gain_boost = fl_value_get_float(value);
435
+ gain_boost = std::max(0.1f, std::min(10.0f, gain_boost));
436
+ }
437
+
438
+ value = fl_value_lookup_string(args, "inputVolume");
439
+ if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_FLOAT) {
440
+ input_volume = fl_value_get_float(value);
441
+ input_volume = std::max(0.0f, std::min(1.0f, input_volume));
442
+ }
443
+ }
444
+
445
+ sample_rate = std::max(sample_rate, 8000);
446
+ channels = std::max(1, std::min(channels, 2));
447
+ bits_per_sample = 16;
448
+ chunk_duration_ms = std::max(chunk_duration_ms, 10);
449
+ gain_boost = std::max(0.1f, std::min(10.0f, gain_boost));
450
+ input_volume = std::max(0.0f, std::min(1.0f, input_volume));
451
+
452
+ size_t chunk_size =
453
+ CalculateChunkSize(sample_rate, channels, bits_per_sample,
454
+ chunk_duration_ms);
455
+
456
+ pa_simple* stream = nullptr;
457
+ std::string error_message;
458
+
459
+ if (!OpenPulseStream(sample_rate, channels, bits_per_sample, chunk_size,
460
+ &stream, &error_message)) {
461
+ g_warning("Failed to open PulseAudio stream: %s", error_message.c_str());
462
+ return false;
463
+ }
464
+
465
+ g_mutex_lock(&plugin->lock);
466
+ if (plugin->is_capturing) {
467
+ g_mutex_unlock(&plugin->lock);
468
+ pa_simple_free(stream);
469
+ return false;
470
+ }
471
+
472
+ g_atomic_int_set(&plugin->should_stop, 0);
473
+ plugin->is_capturing = TRUE;
474
+
475
+ auto* context = new CaptureThreadContext{
476
+ plugin,
477
+ stream,
478
+ chunk_size,
479
+ sample_rate,
480
+ channels,
481
+ bits_per_sample,
482
+ gain_boost,
483
+ input_volume,
484
+ };
485
+
486
+ g_object_ref(plugin);
487
+ plugin->capture_thread = g_thread_new("voxa-audio-capture", CaptureThread,
488
+ context);
489
+ g_mutex_unlock(&plugin->lock);
490
+
491
+ if (plugin->capture_thread == nullptr) {
492
+ g_warning("Failed to create capture thread");
493
+ g_mutex_lock(&plugin->lock);
494
+ plugin->is_capturing = FALSE;
495
+ g_mutex_unlock(&plugin->lock);
496
+ pa_simple_free(stream);
497
+ g_object_unref(plugin);
498
+ delete context;
499
+ return false;
500
+ }
501
+
502
+ // Send status update
503
+ g_mutex_lock(&plugin->lock);
504
+ const gboolean has_status_listener = plugin->has_status_listener;
505
+ g_mutex_unlock(&plugin->lock);
506
+
507
+ if (has_status_listener && plugin->status_event_channel != nullptr) {
508
+ g_autoptr(FlValue) status_map = fl_value_new_map();
509
+ fl_value_set_string_take(status_map, "isActive", fl_value_new_bool(TRUE));
510
+ fl_value_set_string_take(status_map, "timestamp", fl_value_new_float(g_get_real_time() / 1000000.0));
511
+
512
+ g_autoptr(GError) error = nullptr;
513
+ fl_event_channel_send(plugin->status_event_channel, status_map, nullptr, &error);
514
+ }
515
+
516
+ return true;
517
+ }
518
+
519
+ bool StopCapture(AudioCapturePlugin* plugin) {
520
+ g_mutex_lock(&plugin->lock);
521
+ if (!plugin->is_capturing) {
522
+ g_mutex_unlock(&plugin->lock);
523
+ return false;
524
+ }
525
+ g_atomic_int_set(&plugin->should_stop, 1);
526
+ GThread* thread = plugin->capture_thread;
527
+ g_mutex_unlock(&plugin->lock);
528
+
529
+ if (thread != nullptr) {
530
+ g_thread_join(thread);
531
+ }
532
+
533
+ g_mutex_lock(&plugin->lock);
534
+ plugin->capture_thread = nullptr;
535
+ plugin->is_capturing = FALSE;
536
+ const gboolean has_status_listener = plugin->has_status_listener;
537
+ g_mutex_unlock(&plugin->lock);
538
+
539
+ // Wait a bit to ensure thread has fully stopped
540
+ g_usleep(100000); // 0.1 seconds
541
+
542
+ // Send status update
543
+ if (has_status_listener && plugin->status_event_channel != nullptr) {
544
+ g_autoptr(FlValue) status_map = fl_value_new_map();
545
+ fl_value_set_string_take(status_map, "isActive", fl_value_new_bool(FALSE));
546
+ fl_value_set_string_take(status_map, "timestamp", fl_value_new_float(g_get_real_time() / 1000000.0));
547
+
548
+ g_autoptr(GError) error = nullptr;
549
+ fl_event_channel_send(plugin->status_event_channel, status_map, nullptr, &error);
550
+ }
551
+
552
+ return true;
553
+ }
554
+
555
+ void HandleMethodCall(AudioCapturePlugin* plugin, FlMethodCall* method_call) {
556
+ const gchar* method = fl_method_call_get_name(method_call);
557
+ g_autoptr(FlMethodResponse) response = nullptr;
558
+
559
+ if (strcmp(method, "requestPermissions") == 0) {
560
+ g_autoptr(FlValue) result = fl_value_new_bool(TRUE);
561
+ response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
562
+ } else if (strcmp(method, "startCapture") == 0) {
563
+ FlValue* args = fl_method_call_get_args(method_call);
564
+ const bool started = StartCapture(plugin, args);
565
+ g_autoptr(FlValue) result = fl_value_new_bool(started);
566
+ response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
567
+ } else if (strcmp(method, "stopCapture") == 0) {
568
+ const bool stopped = StopCapture(plugin);
569
+ g_autoptr(FlValue) result = fl_value_new_bool(stopped);
570
+ response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
571
+ } else {
572
+ response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
573
+ }
574
+
575
+ g_autoptr(GError) error = nullptr;
576
+ if (!fl_method_call_respond(method_call, response, &error)) {
577
+ g_warning("Failed to send method call response: %s", error->message);
578
+ }
579
+ }
580
+
581
+ static void MethodCallHandler(FlMethodChannel* channel,
582
+ FlMethodCall* method_call,
583
+ gpointer user_data) {
584
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(user_data);
585
+ HandleMethodCall(plugin, method_call);
586
+ }
587
+
588
+ } // namespace
589
+
590
+ static void audio_capture_plugin_dispose(GObject* object) {
591
+ AudioCapturePlugin* plugin = AUDIO_CAPTURE_PLUGIN(object);
592
+
593
+ StopCapture(plugin);
594
+
595
+ if (plugin->method_channel != nullptr) {
596
+ g_clear_object(&plugin->method_channel);
597
+ }
598
+
599
+ if (plugin->event_channel != nullptr) {
600
+ g_clear_object(&plugin->event_channel);
601
+ }
602
+
603
+ if (plugin->status_event_channel != nullptr) {
604
+ g_clear_object(&plugin->status_event_channel);
605
+ }
606
+
607
+ if (plugin->decibel_event_channel != nullptr) {
608
+ g_clear_object(&plugin->decibel_event_channel);
609
+ }
610
+
611
+ if (plugin->main_context != nullptr) {
612
+ g_main_context_unref(plugin->main_context);
613
+ plugin->main_context = nullptr;
614
+ }
615
+
616
+ g_mutex_clear(&plugin->lock);
617
+
618
+ G_OBJECT_CLASS(audio_capture_plugin_parent_class)->dispose(object);
619
+ }
620
+
621
+ static void audio_capture_plugin_class_init(AudioCapturePluginClass* klass) {
622
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
623
+ object_class->dispose = audio_capture_plugin_dispose;
624
+ }
625
+
626
+ static void audio_capture_plugin_init(AudioCapturePlugin* plugin) {
627
+ g_mutex_init(&plugin->lock);
628
+ plugin->main_context = g_main_context_ref_thread_default();
629
+ plugin->is_capturing = FALSE;
630
+ plugin->has_listener = FALSE;
631
+ plugin->has_status_listener = FALSE;
632
+ plugin->has_decibel_listener = FALSE;
633
+ plugin->method_channel = nullptr;
634
+ plugin->event_channel = nullptr;
635
+ plugin->status_event_channel = nullptr;
636
+ plugin->decibel_event_channel = nullptr;
637
+ plugin->capture_thread = nullptr;
638
+ g_atomic_int_set(&plugin->should_stop, 0);
639
+ }
640
+
641
+ void audio_capture_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
642
+ FlBinaryMessenger* messenger = fl_plugin_registrar_get_messenger(registrar);
643
+ audio_capture_plugin_register_with_messenger(messenger);
644
+ // Also register the mic capture plugin
645
+ mic_capture_plugin_register_with_messenger(messenger);
646
+ }
647
+
648
+ void audio_capture_plugin_register_with_messenger(FlBinaryMessenger* messenger) {
649
+ AudioCapturePlugin* plugin =
650
+ AUDIO_CAPTURE_PLUGIN(g_object_new(audio_capture_plugin_get_type(), nullptr));
651
+
652
+ g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
653
+
654
+ plugin->method_channel = fl_method_channel_new(
655
+ messenger, kMethodChannelName, FL_METHOD_CODEC(codec));
656
+ fl_method_channel_set_method_call_handler(
657
+ plugin->method_channel, MethodCallHandler, g_object_ref(plugin),
658
+ g_object_unref);
659
+
660
+ plugin->event_channel = fl_event_channel_new(
661
+ messenger, kEventChannelName, FL_METHOD_CODEC(codec));
662
+
663
+ // Use the newer API with proper function signatures
664
+ fl_event_channel_set_stream_handlers(
665
+ plugin->event_channel,
666
+ OnListenHandler,
667
+ OnCancelHandler,
668
+ g_object_ref(plugin),
669
+ g_object_unref);
670
+
671
+ // Register status event channel
672
+ plugin->status_event_channel = fl_event_channel_new(
673
+ messenger, kStatusEventChannelName, FL_METHOD_CODEC(codec));
674
+ fl_event_channel_set_stream_handlers(
675
+ plugin->status_event_channel,
676
+ OnStatusListenHandler,
677
+ OnStatusCancelHandler,
678
+ g_object_ref(plugin),
679
+ g_object_unref);
680
+
681
+ // Register decibel event channel
682
+ plugin->decibel_event_channel = fl_event_channel_new(
683
+ messenger, kDecibelEventChannelName, FL_METHOD_CODEC(codec));
684
+ fl_event_channel_set_stream_handlers(
685
+ plugin->decibel_event_channel,
686
+ OnDecibelListenHandler,
687
+ OnDecibelCancelHandler,
688
+ g_object_ref(plugin),
689
+ g_object_unref);
690
+
691
+ g_object_unref(plugin);
692
+ }