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.
- package/.env.example +39 -0
- package/README.md +2 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +13 -5
- package/docs/integrations.md +4 -1
- package/flutter_app/.metadata +42 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +109 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +119 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
- package/flutter_app/lib/main.dart +23057 -0
- package/flutter_app/lib/main_app_shell.dart +1682 -0
- package/flutter_app/lib/main_integrations.dart +931 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3473 -0
- package/flutter_app/lib/main_shared.dart +2861 -0
- package/flutter_app/lib/main_theme.dart +204 -0
- package/flutter_app/lib/main_voice_assistant.dart +831 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1833 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1088 -0
- package/flutter_app/pubspec.yaml +53 -0
- package/flutter_app/test/messaging_access_summary_test.dart +22 -0
- package/flutter_app/test/recording_payloads_test.dart +53 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/manager.js +231 -7
- package/package.json +3 -1
- package/server/db/database.js +68 -0
- package/server/http/middleware.js +50 -0
- package/server/http/routes.js +3 -1
- package/server/index.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +61 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +65262 -64422
- package/server/routes/integrations.js +86 -0
- package/server/routes/memory.js +11 -2
- package/server/routes/screenHistory.js +46 -0
- package/server/routes/triggers.js +81 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +31 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/systemPrompt.js +1 -1
- package/server/services/ai/tools.js +35 -6
- package/server/services/browser/controller.js +47 -3
- package/server/services/desktop/screenRecorder.js +172 -0
- package/server/services/integrations/env.js +5 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/home_assistant/provider.js +306 -26
- package/server/services/integrations/manager.js +63 -7
- package/server/services/integrations/oauth_provider.js +13 -6
- package/server/services/integrations/provider_config_store.js +76 -0
- package/server/services/integrations/registry.js +4 -0
- package/server/services/integrations/trello/provider.js +744 -0
- package/server/services/integrations/whatsapp/provider.js +6 -2
- package/server/services/manager.js +22 -0
- package/server/services/memory/manager.js +39 -2
- package/server/services/skills/base_catalog.js +33 -0
- package/server/services/tasks/adapters/index.js +1 -0
- package/server/services/tasks/adapters/manual.js +12 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/providers.js +2 -1
- package/server/services/widgets/service.js +49 -4
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:convert';
|
|
3
|
+
import 'dart:io';
|
|
4
|
+
import 'dart:math';
|
|
5
|
+
|
|
6
|
+
import 'package:flutter/foundation.dart';
|
|
7
|
+
import 'package:shared_preferences/shared_preferences.dart';
|
|
8
|
+
|
|
9
|
+
import 'desktop_companion_actions.dart';
|
|
10
|
+
import 'desktop_screen_capture.dart';
|
|
11
|
+
|
|
12
|
+
const String desktopCompanionEnabledPrefsKey = 'desktop.companion.enabled';
|
|
13
|
+
const String desktopCompanionLabelPrefsKey = 'desktop.companion.label';
|
|
14
|
+
const String desktopCompanionDeviceIdPrefsKey = 'desktop.companion.deviceId';
|
|
15
|
+
const String desktopCompanionActivationIdPrefsKey =
|
|
16
|
+
'desktop.companion.activationId';
|
|
17
|
+
const String desktopCompanionPausedPrefsKey = 'desktop.companion.paused';
|
|
18
|
+
const String desktopCompanionActiveDisplayPrefsKey =
|
|
19
|
+
'desktop.companion.activeDisplayId';
|
|
20
|
+
|
|
21
|
+
class DesktopCompanionManager extends ChangeNotifier {
|
|
22
|
+
DesktopCompanionManager({required DesktopScreenCapture screenCapture})
|
|
23
|
+
: _actions = DesktopCompanionActions(screenCapture: screenCapture);
|
|
24
|
+
|
|
25
|
+
final DesktopCompanionActions _actions;
|
|
26
|
+
WebSocket? _socket;
|
|
27
|
+
Timer? _reconnectTimer;
|
|
28
|
+
|
|
29
|
+
String _backendUrl = '';
|
|
30
|
+
String _sessionCookie = '';
|
|
31
|
+
String _label = _defaultLabel();
|
|
32
|
+
String _deviceId = '';
|
|
33
|
+
String _activationId = '';
|
|
34
|
+
bool _enabled = false;
|
|
35
|
+
bool _paused = false;
|
|
36
|
+
bool _authenticated = false;
|
|
37
|
+
bool _connecting = false;
|
|
38
|
+
bool _connected = false;
|
|
39
|
+
String _activeDisplayId = 'primary';
|
|
40
|
+
String? _errorMessage;
|
|
41
|
+
Map<String, Object?> _status = const <String, Object?>{};
|
|
42
|
+
|
|
43
|
+
bool get enabled => _enabled;
|
|
44
|
+
bool get paused => _paused;
|
|
45
|
+
bool get connecting => _connecting;
|
|
46
|
+
bool get connected => _connected;
|
|
47
|
+
String? get errorMessage => _errorMessage;
|
|
48
|
+
String get label => _label;
|
|
49
|
+
String get deviceId => _deviceId;
|
|
50
|
+
String get activationId => _activationId;
|
|
51
|
+
Map<String, Object?> get status => _status;
|
|
52
|
+
|
|
53
|
+
Future<void> bootstrap(SharedPreferences prefs) async {
|
|
54
|
+
_enabled = prefs.getBool(desktopCompanionEnabledPrefsKey) ?? false;
|
|
55
|
+
_paused = prefs.getBool(desktopCompanionPausedPrefsKey) ?? false;
|
|
56
|
+
_label =
|
|
57
|
+
prefs.getString(desktopCompanionLabelPrefsKey)?.trim() ??
|
|
58
|
+
_defaultLabel();
|
|
59
|
+
_deviceId =
|
|
60
|
+
prefs.getString(desktopCompanionDeviceIdPrefsKey)?.trim() ??
|
|
61
|
+
_randomId();
|
|
62
|
+
_activationId =
|
|
63
|
+
prefs.getString(desktopCompanionActivationIdPrefsKey)?.trim() ??
|
|
64
|
+
_randomId();
|
|
65
|
+
_activeDisplayId =
|
|
66
|
+
prefs.getString(desktopCompanionActiveDisplayPrefsKey)?.trim() ??
|
|
67
|
+
'primary';
|
|
68
|
+
await prefs.setString(desktopCompanionDeviceIdPrefsKey, _deviceId);
|
|
69
|
+
await prefs.setString(desktopCompanionActivationIdPrefsKey, _activationId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Future<void> updateSession({
|
|
73
|
+
required String backendUrl,
|
|
74
|
+
required String sessionCookie,
|
|
75
|
+
required bool authenticated,
|
|
76
|
+
}) async {
|
|
77
|
+
_backendUrl = backendUrl.trim();
|
|
78
|
+
_sessionCookie = sessionCookie.trim();
|
|
79
|
+
_authenticated = authenticated;
|
|
80
|
+
if (!_authenticated || !_enabled || _sessionCookie.isEmpty) {
|
|
81
|
+
await disconnect();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await _ensureConnected();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Future<void> setEnabled(bool value, SharedPreferences prefs) async {
|
|
88
|
+
if (_enabled == value) return;
|
|
89
|
+
_enabled = value;
|
|
90
|
+
if (value) {
|
|
91
|
+
_activationId = _randomId();
|
|
92
|
+
await prefs.setString(
|
|
93
|
+
desktopCompanionActivationIdPrefsKey,
|
|
94
|
+
_activationId,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
await prefs.setBool(desktopCompanionEnabledPrefsKey, value);
|
|
98
|
+
notifyListeners();
|
|
99
|
+
if (!value) {
|
|
100
|
+
await disconnect();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await _ensureConnected();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Future<void> setLabel(String value, SharedPreferences prefs) async {
|
|
107
|
+
final normalized = value.trim().isEmpty ? _defaultLabel() : value.trim();
|
|
108
|
+
_label = normalized;
|
|
109
|
+
await prefs.setString(desktopCompanionLabelPrefsKey, normalized);
|
|
110
|
+
notifyListeners();
|
|
111
|
+
if (_connected) {
|
|
112
|
+
_status = {..._status, 'label': normalized};
|
|
113
|
+
await _sendEvent('statusChanged', <String, Object?>{'label': normalized});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Future<void> setPaused(bool value, SharedPreferences prefs) async {
|
|
118
|
+
_paused = value;
|
|
119
|
+
await prefs.setBool(desktopCompanionPausedPrefsKey, value);
|
|
120
|
+
notifyListeners();
|
|
121
|
+
if (_connected) {
|
|
122
|
+
await _sendEvent('statusChanged', <String, Object?>{'paused': value});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Future<void> disconnect() async {
|
|
127
|
+
_reconnectTimer?.cancel();
|
|
128
|
+
_reconnectTimer = null;
|
|
129
|
+
_connecting = false;
|
|
130
|
+
_connected = false;
|
|
131
|
+
final socket = _socket;
|
|
132
|
+
_socket = null;
|
|
133
|
+
if (socket != null) {
|
|
134
|
+
try {
|
|
135
|
+
await socket.close();
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
}
|
|
138
|
+
notifyListeners();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Future<void> rotateIdentity(SharedPreferences prefs) async {
|
|
142
|
+
_deviceId = _randomId();
|
|
143
|
+
_activationId = _randomId();
|
|
144
|
+
await prefs.setString(desktopCompanionDeviceIdPrefsKey, _deviceId);
|
|
145
|
+
await prefs.setString(desktopCompanionActivationIdPrefsKey, _activationId);
|
|
146
|
+
await disconnect();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Future<Map<String, Object?>> refreshLocalStatus() async {
|
|
150
|
+
final status = await _actions.getStatus(
|
|
151
|
+
label: _label,
|
|
152
|
+
paused: _paused,
|
|
153
|
+
activeDisplayId: _activeDisplayId,
|
|
154
|
+
);
|
|
155
|
+
_status = <String, Object?>{
|
|
156
|
+
..._status,
|
|
157
|
+
...status,
|
|
158
|
+
'activeDisplayId': status['activeDisplayId'] ?? _activeDisplayId,
|
|
159
|
+
'deviceId': _deviceId,
|
|
160
|
+
'activationId': _activationId,
|
|
161
|
+
'label': _label,
|
|
162
|
+
'platform': defaultTargetPlatform.name,
|
|
163
|
+
'hostname': _localHostname(),
|
|
164
|
+
'companionEnabled': _enabled,
|
|
165
|
+
'paused': _paused,
|
|
166
|
+
};
|
|
167
|
+
notifyListeners();
|
|
168
|
+
if (_connected) {
|
|
169
|
+
await _sendEvent('statusChanged', <String, Object?>{
|
|
170
|
+
'permissions': _status['permissions'],
|
|
171
|
+
'capabilities': _status['capabilities'],
|
|
172
|
+
'displays': _status['displays'],
|
|
173
|
+
'activeDisplayId': _status['activeDisplayId'],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return _status;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Future<void> openPermissionSettings(String permissionKey) async {
|
|
180
|
+
if (kIsWeb) {
|
|
181
|
+
throw UnsupportedError(
|
|
182
|
+
'Desktop companion permission settings are unavailable on web.',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
final key = permissionKey.trim().toLowerCase();
|
|
186
|
+
switch (defaultTargetPlatform) {
|
|
187
|
+
case TargetPlatform.macOS:
|
|
188
|
+
await _openMacPermissionSettings(key);
|
|
189
|
+
case TargetPlatform.windows:
|
|
190
|
+
await _openWindowsPermissionSettings(key);
|
|
191
|
+
case TargetPlatform.linux:
|
|
192
|
+
await _openLinuxPermissionSettings(key);
|
|
193
|
+
case TargetPlatform.android:
|
|
194
|
+
case TargetPlatform.iOS:
|
|
195
|
+
case TargetPlatform.fuchsia:
|
|
196
|
+
throw UnsupportedError(
|
|
197
|
+
'Desktop companion permission settings are unavailable on this platform.',
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Future<void> _ensureConnected() async {
|
|
203
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
204
|
+
if (_connecting || _connected) return;
|
|
205
|
+
_connecting = true;
|
|
206
|
+
_errorMessage = null;
|
|
207
|
+
notifyListeners();
|
|
208
|
+
try {
|
|
209
|
+
final uri = _desktopWsUri(_backendUrl);
|
|
210
|
+
final socket = await WebSocket.connect(
|
|
211
|
+
uri.toString(),
|
|
212
|
+
headers: <String, Object>{'Cookie': _sessionCookie},
|
|
213
|
+
);
|
|
214
|
+
_socket = socket;
|
|
215
|
+
socket.listen(
|
|
216
|
+
_handleMessage,
|
|
217
|
+
onDone: _handleSocketClosed,
|
|
218
|
+
onError: (Object error, StackTrace stackTrace) {
|
|
219
|
+
_errorMessage = '$error';
|
|
220
|
+
_handleSocketClosed();
|
|
221
|
+
},
|
|
222
|
+
cancelOnError: true,
|
|
223
|
+
);
|
|
224
|
+
final hello = await _actions.buildHello(
|
|
225
|
+
deviceId: _deviceId,
|
|
226
|
+
activationId: _activationId,
|
|
227
|
+
label: _label,
|
|
228
|
+
companionEnabled: _enabled,
|
|
229
|
+
paused: _paused,
|
|
230
|
+
activeDisplayId: _activeDisplayId,
|
|
231
|
+
);
|
|
232
|
+
socket.add(
|
|
233
|
+
jsonEncode(<String, Object?>{'type': 'hello', 'device': hello}),
|
|
234
|
+
);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
_connecting = false;
|
|
237
|
+
_connected = false;
|
|
238
|
+
_errorMessage = '$error';
|
|
239
|
+
notifyListeners();
|
|
240
|
+
_scheduleReconnect();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void _handleMessage(dynamic raw) {
|
|
245
|
+
try {
|
|
246
|
+
final message = jsonDecode(raw as String);
|
|
247
|
+
if (message is! Map) return;
|
|
248
|
+
final type = message['type']?.toString() ?? '';
|
|
249
|
+
if (type == 'hello') {
|
|
250
|
+
_connecting = false;
|
|
251
|
+
final ok = message['ok'] == true;
|
|
252
|
+
if (!ok) {
|
|
253
|
+
_connected = false;
|
|
254
|
+
_errorMessage =
|
|
255
|
+
message['error']?.toString() ?? 'Desktop companion rejected.';
|
|
256
|
+
notifyListeners();
|
|
257
|
+
_handleSocketClosed();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
_connected = true;
|
|
261
|
+
_errorMessage = null;
|
|
262
|
+
final device = message['device'];
|
|
263
|
+
_status = device is Map
|
|
264
|
+
? device.map((key, value) => MapEntry(key.toString(), value))
|
|
265
|
+
: const <String, Object?>{};
|
|
266
|
+
_activeDisplayId =
|
|
267
|
+
_status['activeDisplayId']?.toString() ?? _activeDisplayId;
|
|
268
|
+
notifyListeners();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (type != 'command') return;
|
|
272
|
+
unawaited(_handleCommand(message.cast<String, Object?>()));
|
|
273
|
+
} on FormatException catch (error) {
|
|
274
|
+
_errorMessage = 'Ignored malformed desktop companion message: $error';
|
|
275
|
+
notifyListeners();
|
|
276
|
+
return;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
_errorMessage = 'Desktop companion message handling failed: $error';
|
|
279
|
+
notifyListeners();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
Future<void> _handleCommand(Map<String, Object?> message) async {
|
|
285
|
+
final id = message['id']?.toString() ?? '';
|
|
286
|
+
final command = message['command']?.toString() ?? '';
|
|
287
|
+
final payload = message['payload'] is Map
|
|
288
|
+
? (message['payload'] as Map).map(
|
|
289
|
+
(key, value) => MapEntry(key.toString(), value),
|
|
290
|
+
)
|
|
291
|
+
: const <String, Object?>{};
|
|
292
|
+
try {
|
|
293
|
+
final response = await _dispatchCommand(command, payload);
|
|
294
|
+
_socket?.add(
|
|
295
|
+
jsonEncode(<String, Object?>{
|
|
296
|
+
'type': 'result',
|
|
297
|
+
'id': id,
|
|
298
|
+
'ok': true,
|
|
299
|
+
'payload': response,
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
_socket?.add(
|
|
304
|
+
jsonEncode(<String, Object?>{
|
|
305
|
+
'type': 'result',
|
|
306
|
+
'id': id,
|
|
307
|
+
'ok': false,
|
|
308
|
+
'error': '$error',
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
Future<Map<String, Object?>> _dispatchCommand(
|
|
315
|
+
String command,
|
|
316
|
+
Map<String, Object?> payload,
|
|
317
|
+
) async {
|
|
318
|
+
if (_paused && command != 'getStatus' && command != 'pauseControl') {
|
|
319
|
+
throw Exception('Desktop companion is paused locally.');
|
|
320
|
+
}
|
|
321
|
+
switch (command) {
|
|
322
|
+
case 'getStatus':
|
|
323
|
+
return _actions.getStatus(
|
|
324
|
+
label: _label,
|
|
325
|
+
paused: _paused,
|
|
326
|
+
activeDisplayId: _activeDisplayId,
|
|
327
|
+
);
|
|
328
|
+
case 'captureFrame':
|
|
329
|
+
return _actions.captureFrame(activeDisplayId: _activeDisplayId);
|
|
330
|
+
case 'observe':
|
|
331
|
+
return _actions.observe(
|
|
332
|
+
includeTree: payload['includeTree'] == true,
|
|
333
|
+
activeDisplayId: _activeDisplayId,
|
|
334
|
+
);
|
|
335
|
+
case 'click':
|
|
336
|
+
return _actions.click(
|
|
337
|
+
x: (payload['x'] as num?)?.round() ?? 0,
|
|
338
|
+
y: (payload['y'] as num?)?.round() ?? 0,
|
|
339
|
+
button: payload['button']?.toString() ?? 'left',
|
|
340
|
+
displayId: _activeDisplayId,
|
|
341
|
+
);
|
|
342
|
+
case 'drag':
|
|
343
|
+
return _actions.drag(
|
|
344
|
+
x1: (payload['x1'] as num?)?.round() ?? 0,
|
|
345
|
+
y1: (payload['y1'] as num?)?.round() ?? 0,
|
|
346
|
+
x2: (payload['x2'] as num?)?.round() ?? 0,
|
|
347
|
+
y2: (payload['y2'] as num?)?.round() ?? 0,
|
|
348
|
+
durationMs: (payload['durationMs'] as num?)?.round() ?? 280,
|
|
349
|
+
displayId: _activeDisplayId,
|
|
350
|
+
);
|
|
351
|
+
case 'scroll':
|
|
352
|
+
return _actions.scroll(
|
|
353
|
+
deltaX: (payload['deltaX'] as num?)?.round() ?? 0,
|
|
354
|
+
deltaY: (payload['deltaY'] as num?)?.round() ?? 0,
|
|
355
|
+
displayId: _activeDisplayId,
|
|
356
|
+
);
|
|
357
|
+
case 'typeText':
|
|
358
|
+
return _actions.typeText(
|
|
359
|
+
text: payload['text']?.toString() ?? '',
|
|
360
|
+
pressEnter: payload['pressEnter'] == true,
|
|
361
|
+
);
|
|
362
|
+
case 'pressKey':
|
|
363
|
+
return _actions.pressKey(key: payload['key']?.toString() ?? '');
|
|
364
|
+
case 'launchApp':
|
|
365
|
+
return _actions.launchApp(app: payload['app']?.toString() ?? '');
|
|
366
|
+
case 'listDisplays':
|
|
367
|
+
final status = await _actions.getStatus(
|
|
368
|
+
label: _label,
|
|
369
|
+
paused: _paused,
|
|
370
|
+
activeDisplayId: _activeDisplayId,
|
|
371
|
+
);
|
|
372
|
+
return <String, Object?>{
|
|
373
|
+
'displays': status['displays'] ?? const <Map<String, Object?>>[],
|
|
374
|
+
'activeDisplayId': status['activeDisplayId'] ?? 'primary',
|
|
375
|
+
};
|
|
376
|
+
case 'selectDisplay':
|
|
377
|
+
final displayId = payload['displayId']?.toString() ?? 'primary';
|
|
378
|
+
_activeDisplayId = displayId;
|
|
379
|
+
final prefs = await SharedPreferences.getInstance();
|
|
380
|
+
await prefs.setString(desktopCompanionActiveDisplayPrefsKey, displayId);
|
|
381
|
+
_status = <String, Object?>{..._status, 'activeDisplayId': displayId};
|
|
382
|
+
// TODO: Apply platform-specific active display switching when available.
|
|
383
|
+
notifyListeners();
|
|
384
|
+
return <String, Object?>{'success': true, 'activeDisplayId': displayId};
|
|
385
|
+
case 'getTree':
|
|
386
|
+
return _actions.getTree();
|
|
387
|
+
case 'pauseControl':
|
|
388
|
+
final paused = payload['paused'] != false;
|
|
389
|
+
_paused = paused;
|
|
390
|
+
final prefs = await SharedPreferences.getInstance();
|
|
391
|
+
await prefs.setBool(desktopCompanionPausedPrefsKey, paused);
|
|
392
|
+
notifyListeners();
|
|
393
|
+
return <String, Object?>{'success': true, 'paused': _paused};
|
|
394
|
+
case 'ping':
|
|
395
|
+
return <String, Object?>{'pong': true};
|
|
396
|
+
default:
|
|
397
|
+
throw Exception('Unsupported desktop companion command: $command');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
void _handleSocketClosed() {
|
|
402
|
+
_socket = null;
|
|
403
|
+
_connecting = false;
|
|
404
|
+
_connected = false;
|
|
405
|
+
notifyListeners();
|
|
406
|
+
_scheduleReconnect();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@override
|
|
410
|
+
void dispose() {
|
|
411
|
+
_reconnectTimer?.cancel();
|
|
412
|
+
_reconnectTimer = null;
|
|
413
|
+
_connecting = false;
|
|
414
|
+
_connected = false;
|
|
415
|
+
_enabled = false;
|
|
416
|
+
final socket = _socket;
|
|
417
|
+
_socket = null;
|
|
418
|
+
if (socket != null) {
|
|
419
|
+
try {
|
|
420
|
+
socket.close();
|
|
421
|
+
} catch (_) {}
|
|
422
|
+
}
|
|
423
|
+
super.dispose();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
void _scheduleReconnect() {
|
|
427
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
428
|
+
_reconnectTimer?.cancel();
|
|
429
|
+
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
|
430
|
+
unawaited(_ensureConnected());
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
Future<void> _sendEvent(String event, Map<String, Object?> payload) async {
|
|
435
|
+
final socket = _socket;
|
|
436
|
+
if (socket == null || !_connected) return;
|
|
437
|
+
socket.add(
|
|
438
|
+
jsonEncode(<String, Object?>{
|
|
439
|
+
'type': 'event',
|
|
440
|
+
'event': event,
|
|
441
|
+
'payload': payload,
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
Future<void> _openMacPermissionSettings(String key) async {
|
|
447
|
+
final uri = switch (key) {
|
|
448
|
+
'screencapture' =>
|
|
449
|
+
'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture',
|
|
450
|
+
'inputcontrol' || 'accessibility' =>
|
|
451
|
+
'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility',
|
|
452
|
+
_ => 'x-apple.systempreferences:com.apple.preference.security',
|
|
453
|
+
};
|
|
454
|
+
await _runCommand('open', <String>[uri]);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
Future<void> _openWindowsPermissionSettings(String key) async {
|
|
458
|
+
final uri = switch (key) {
|
|
459
|
+
'screencapture' => 'ms-settings:privacy-screencapture',
|
|
460
|
+
'inputcontrol' || 'accessibility' => 'ms-settings:easeofaccess-display',
|
|
461
|
+
_ => 'ms-settings:privacy',
|
|
462
|
+
};
|
|
463
|
+
await _runCommand('cmd', <String>['/c', 'start', '', uri]);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
Future<void> _openLinuxPermissionSettings(String key) async {
|
|
467
|
+
final commands = key == 'screencapture'
|
|
468
|
+
? <_ShellCommand>[
|
|
469
|
+
const _ShellCommand('gnome-control-center', <String>['privacy']),
|
|
470
|
+
const _ShellCommand('kcmshell6', <String>['kcm_screenlocker']),
|
|
471
|
+
const _ShellCommand('xdg-open', <String>['settings://privacy']),
|
|
472
|
+
]
|
|
473
|
+
: <_ShellCommand>[
|
|
474
|
+
const _ShellCommand('gnome-control-center', <String>[
|
|
475
|
+
'universal-access',
|
|
476
|
+
]),
|
|
477
|
+
const _ShellCommand('gnome-control-center', <String>['privacy']),
|
|
478
|
+
const _ShellCommand('xdg-open', <String>['settings://']),
|
|
479
|
+
];
|
|
480
|
+
Object? lastError;
|
|
481
|
+
for (final command in commands) {
|
|
482
|
+
try {
|
|
483
|
+
await _runCommand(command.command, command.args);
|
|
484
|
+
return;
|
|
485
|
+
} catch (error) {
|
|
486
|
+
lastError = error;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
throw Exception(
|
|
490
|
+
'Could not open Linux settings automatically.${lastError != null ? ' $lastError' : ''}',
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
Future<void> _runCommand(String command, List<String> args) async {
|
|
495
|
+
final result = await Process.run(command, args);
|
|
496
|
+
if (result.exitCode != 0) {
|
|
497
|
+
final stderr = result.stderr?.toString().trim();
|
|
498
|
+
final stdout = result.stdout?.toString().trim();
|
|
499
|
+
final details = stderr?.isNotEmpty == true
|
|
500
|
+
? stderr
|
|
501
|
+
: (stdout?.isNotEmpty == true ? stdout : 'unknown error');
|
|
502
|
+
throw Exception('Command failed ($command): $details');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
Uri _desktopWsUri(String backendUrl) {
|
|
508
|
+
final base = Uri.parse(backendUrl);
|
|
509
|
+
final scheme = base.scheme == 'https' ? 'wss' : 'ws';
|
|
510
|
+
return base.replace(scheme: scheme, path: '/api/desktop/ws', query: '');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
String _defaultLabel() {
|
|
514
|
+
final host = Platform.localHostname.trim();
|
|
515
|
+
if (host.isNotEmpty) return host;
|
|
516
|
+
return '${defaultTargetPlatform.name} desktop';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
String _randomId() {
|
|
520
|
+
final random = Random.secure();
|
|
521
|
+
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
|
|
522
|
+
return base64UrlEncode(bytes).replaceAll('=', '');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
String _localHostname() {
|
|
526
|
+
final host = Platform.localHostname.trim();
|
|
527
|
+
if (host.isNotEmpty) {
|
|
528
|
+
return host;
|
|
529
|
+
}
|
|
530
|
+
return defaultTargetPlatform.name;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class _ShellCommand {
|
|
534
|
+
const _ShellCommand(this.command, this.args);
|
|
535
|
+
|
|
536
|
+
final String command;
|
|
537
|
+
final List<String> args;
|
|
538
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import 'package:flutter/foundation.dart';
|
|
2
|
+
import 'package:shared_preferences/shared_preferences.dart';
|
|
3
|
+
|
|
4
|
+
import 'desktop_screen_capture.dart';
|
|
5
|
+
|
|
6
|
+
const String desktopCompanionEnabledPrefsKey = 'desktop.companion.enabled';
|
|
7
|
+
const String desktopCompanionLabelPrefsKey = 'desktop.companion.label';
|
|
8
|
+
const String desktopCompanionDeviceIdPrefsKey = 'desktop.companion.deviceId';
|
|
9
|
+
const String desktopCompanionActivationIdPrefsKey =
|
|
10
|
+
'desktop.companion.activationId';
|
|
11
|
+
const String desktopCompanionPausedPrefsKey = 'desktop.companion.paused';
|
|
12
|
+
|
|
13
|
+
class DesktopCompanionManager extends ChangeNotifier {
|
|
14
|
+
DesktopCompanionManager({required DesktopScreenCapture screenCapture});
|
|
15
|
+
|
|
16
|
+
bool get enabled => false;
|
|
17
|
+
bool get paused => false;
|
|
18
|
+
bool get connecting => false;
|
|
19
|
+
bool get connected => false;
|
|
20
|
+
String? get errorMessage => 'Desktop companion is not available here.';
|
|
21
|
+
String get label => 'Desktop';
|
|
22
|
+
String get deviceId => '';
|
|
23
|
+
String get activationId => '';
|
|
24
|
+
Map<String, Object?> get status => const <String, Object?>{};
|
|
25
|
+
|
|
26
|
+
Future<void> bootstrap(SharedPreferences prefs) async {}
|
|
27
|
+
|
|
28
|
+
Future<void> updateSession({
|
|
29
|
+
required String backendUrl,
|
|
30
|
+
required String sessionCookie,
|
|
31
|
+
required bool authenticated,
|
|
32
|
+
}) async {}
|
|
33
|
+
|
|
34
|
+
Future<void> setEnabled(bool value, SharedPreferences prefs) async {
|
|
35
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Future<void> setLabel(String value, SharedPreferences prefs) async {
|
|
39
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Future<void> setPaused(bool value, SharedPreferences prefs) async {
|
|
43
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Future<void> disconnect() async {}
|
|
47
|
+
|
|
48
|
+
Future<void> rotateIdentity(SharedPreferences prefs) async {
|
|
49
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Future<Map<String, Object?>> refreshLocalStatus() async {
|
|
53
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Future<void> openPermissionSettings(String permissionKey) async {
|
|
57
|
+
throw UnsupportedError('Desktop companion is not available here.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import 'package:flutter/services.dart';
|
|
2
|
+
|
|
3
|
+
class DesktopNativeBridge {
|
|
4
|
+
static const MethodChannel _channel = MethodChannel(
|
|
5
|
+
'neoagent/desktop_companion_native',
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
Future<Map<String, Object?>> getStatus() async {
|
|
9
|
+
final result = await _channel.invokeMapMethod<String, Object?>('getStatus');
|
|
10
|
+
return result ?? const <String, Object?>{};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Future<Map<String, Object?>> captureFrame({String? displayId}) async {
|
|
14
|
+
final result = await _channel
|
|
15
|
+
.invokeMapMethod<String, Object?>('captureFrame', <String, Object?>{
|
|
16
|
+
if (displayId != null && displayId.trim().isNotEmpty)
|
|
17
|
+
'displayId': displayId.trim(),
|
|
18
|
+
});
|
|
19
|
+
return result ?? const <String, Object?>{};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Future<List<Map<String, Object?>>> listDisplays() async {
|
|
23
|
+
final result = await _channel.invokeListMethod<Object?>('listDisplays');
|
|
24
|
+
return (result ?? const <Object?>[])
|
|
25
|
+
.whereType<Map<Object?, Object?>>()
|
|
26
|
+
.map(
|
|
27
|
+
(item) => item.map((key, value) => MapEntry(key.toString(), value)),
|
|
28
|
+
)
|
|
29
|
+
.toList(growable: false);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Future<void> click({
|
|
33
|
+
required int x,
|
|
34
|
+
required int y,
|
|
35
|
+
required String button,
|
|
36
|
+
String? displayId,
|
|
37
|
+
}) {
|
|
38
|
+
return _channel.invokeMethod<void>('click', <String, Object?>{
|
|
39
|
+
'x': x,
|
|
40
|
+
'y': y,
|
|
41
|
+
'button': button,
|
|
42
|
+
if (displayId != null && displayId.trim().isNotEmpty)
|
|
43
|
+
'displayId': displayId.trim(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Future<void> drag({
|
|
48
|
+
required int x1,
|
|
49
|
+
required int y1,
|
|
50
|
+
required int x2,
|
|
51
|
+
required int y2,
|
|
52
|
+
required int durationMs,
|
|
53
|
+
String? displayId,
|
|
54
|
+
}) {
|
|
55
|
+
return _channel.invokeMethod<void>('drag', <String, Object?>{
|
|
56
|
+
'x1': x1,
|
|
57
|
+
'y1': y1,
|
|
58
|
+
'x2': x2,
|
|
59
|
+
'y2': y2,
|
|
60
|
+
'durationMs': durationMs,
|
|
61
|
+
if (displayId != null && displayId.trim().isNotEmpty)
|
|
62
|
+
'displayId': displayId.trim(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Future<void> scroll({
|
|
67
|
+
required int deltaX,
|
|
68
|
+
required int deltaY,
|
|
69
|
+
String? displayId,
|
|
70
|
+
}) {
|
|
71
|
+
return _channel.invokeMethod<void>('scroll', <String, Object?>{
|
|
72
|
+
'deltaX': deltaX,
|
|
73
|
+
'deltaY': deltaY,
|
|
74
|
+
if (displayId != null && displayId.trim().isNotEmpty)
|
|
75
|
+
'displayId': displayId.trim(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Future<void> typeText({required String text, required bool pressEnter}) {
|
|
80
|
+
return _channel.invokeMethod<void>('typeText', <String, Object?>{
|
|
81
|
+
'text': text,
|
|
82
|
+
'pressEnter': pressEnter,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Future<void> pressKey(String key) {
|
|
87
|
+
return _channel.invokeMethod<void>('pressKey', <String, Object?>{
|
|
88
|
+
'key': key,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|