diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e24ace..8582cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.6.0](https://github.com/mysteriumnetwork/wireguard_dart/tree/0.6.0) (2024-02-05) + +- android: Update API to match darwin implementation +- windows: Implement connection status streaming via event channel +- windows: minor fixes + +[Full Changelog](https://github.com/mysteriumnetwork/wireguard_dart/compare/0.5.0...0.6.0) + ## [0.5.0](https://github.com/mysteriumnetwork/wireguard_dart/tree/0.5.0) (2024-01-18) - darwin: Fix connection status streaming. Two available methods are `.status()` and `.statusStream()` diff --git a/README.md b/README.md index 2b38cc6..d9926fe 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,4 @@ To use this plugin, add `wireguard_dart` as a [dependency in your pubspec.yaml f - Add [minor] if it has new features - Otherwise, it's a patch release, don't add anything - After status checks are passed and PR is approved, merge it -- Changes are automatically released as a new semantic version based on tags in the title +- ~~Changes are automatically released as a new semantic version based on tags in the title~~ Changelog should be provided and committed manually diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index f6ffae7..81213d2 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.wireguard_dart_example" + applicationId "network.mysterium.wireguard_dart_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 21 diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt index 930d207..903f489 100644 --- a/example/windows/flutter/CMakeLists.txt +++ b/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt index 17411a8..394917c 100644 --- a/example/windows/runner/CMakeLists.txt +++ b/example/windows/runner/CMakeLists.txt @@ -33,6 +33,7 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp index c10f08d..60608d0 100644 --- a/example/windows/runner/win32_window.cpp +++ b/example/windows/runner/win32_window.cpp @@ -1,13 +1,31 @@ #include "win32_window.h" +#include #include #include "resource.h" namespace { +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; @@ -31,8 +49,8 @@ void EnableFullDpiSupportIfAvailable(HWND hwnd) { GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); } + FreeLibrary(user32_module); } } // namespace @@ -42,7 +60,7 @@ class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. + // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); @@ -102,9 +120,9 @@ Win32Window::~Win32Window() { Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size) { +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { Destroy(); const wchar_t* window_class = @@ -117,7 +135,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); @@ -126,9 +144,15 @@ bool Win32Window::CreateAndShow(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, @@ -188,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -243,3 +271,18 @@ bool Win32Window::OnCreate() { void Win32Window::OnDestroy() { // No-op; provided for subclasses. } + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h index 17ba431..e901dde 100644 --- a/example/windows/runner/win32_window.h +++ b/example/windows/runner/win32_window.h @@ -28,15 +28,16 @@ class Win32Window { Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size); + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); // Release OS resources associated with window. void Destroy(); @@ -76,7 +77,7 @@ class Win32Window { // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, @@ -86,6 +87,9 @@ class Win32Window { // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + bool quit_on_close_ = false; // window handle for top level window. diff --git a/pubspec.yaml b/pubspec.yaml index c8e1543..ce02d93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 flutter: plugin: diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 8111f4c..8a99b74 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -28,6 +28,10 @@ list(APPEND PLUGIN_SOURCES "config_writer.h" "service_control.cpp" "service_control.h" + "connection_status.h" + "connection_status.cpp" + "connection_status_observer.h" + "connection_status_observer.cpp" "utils.cpp" "utils.h" ) diff --git a/windows/connection_status.cpp b/windows/connection_status.cpp new file mode 100644 index 0000000..19df2d0 --- /dev/null +++ b/windows/connection_status.cpp @@ -0,0 +1,42 @@ +#include "connection_status.h" + +#include + +#include + +namespace wireguard_dart { + +std::string ConnectionStatusToString(const ConnectionStatus status) { + switch (status) { + case ConnectionStatus::connected: + return "connected"; + case ConnectionStatus::disconnected: + return "disconnected"; + case ConnectionStatus::connecting: + return "connecting"; + case ConnectionStatus::disconnecting: + return "disconnecting"; + default: + return "unknown"; + } +} + +ConnectionStatus ConnectionStatusFromWinSvcState(DWORD dwCurrentState) { + switch (dwCurrentState) { + case SERVICE_RUNNING: + return ConnectionStatus::connected; + case SERVICE_STOPPED: + case SERVICE_PAUSED: + return ConnectionStatus::disconnected; + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + return ConnectionStatus::connecting; + case SERVICE_STOP_PENDING: + case SERVICE_PAUSE_PENDING: + return ConnectionStatus::disconnecting; + default: + return ConnectionStatus::unknown; + } +} + +} // namespace wireguard_dart diff --git a/windows/connection_status.h b/windows/connection_status.h new file mode 100644 index 0000000..e63b4f7 --- /dev/null +++ b/windows/connection_status.h @@ -0,0 +1,18 @@ +#ifndef WIREGUARD_DART_CONNECTION_STATUS_H +#define WIREGUARD_DART_CONNECTION_STATUS_H + +#include + +#include + +namespace wireguard_dart { + +enum ConnectionStatus { connected, disconnected, connecting, disconnecting, unknown }; + +std::string ConnectionStatusToString(const ConnectionStatus status); + +ConnectionStatus ConnectionStatusFromWinSvcState(DWORD dwCurrentState); + +} // namespace wireguard_dart + +#endif diff --git a/windows/connection_status_observer.cpp b/windows/connection_status_observer.cpp new file mode 100644 index 0000000..08f0970 --- /dev/null +++ b/windows/connection_status_observer.cpp @@ -0,0 +1,94 @@ +#include "connection_status_observer.h" + +#include + +#include +#include + +#include "connection_status.h" + +namespace wireguard_dart { + +ConnectionStatusObserver::ConnectionStatusObserver() {} + +ConnectionStatusObserver::~ConnectionStatusObserver() { Shutdown(); } + +void ConnectionStatusObserver::StartObserving(std::wstring service_name) { + if (m_running.load() == true) { + return; + } + watch_thread = std::thread(&ConnectionStatusObserver::StartObservingThreadProc, this, service_name); +} + +void ConnectionStatusObserver::StopObserving() { m_watch_thread_stop.store(true); } + +void ConnectionStatusObserver::Shutdown() { + m_watch_thread_stop.store(true); + if (watch_thread.joinable()) { + watch_thread.join(); + } +} + +void ConnectionStatusObserver::StartObservingThreadProc(std::wstring service_name) { + m_running.store(true); + SC_HANDLE service_manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (service_manager == NULL) { + return; + } + SC_HANDLE service = OpenService(service_manager, &service_name[0], SERVICE_QUERY_STATUS | SERVICE_INTERROGATE); + if (service == NULL) { + CloseServiceHandle(service_manager); + return; + } + SERVICE_NOTIFY s_notify = {0}; + s_notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE; + s_notify.pfnNotifyCallback = &ServiceNotifyCallback; + s_notify.pContext = static_cast(this); + while (m_watch_thread_stop.load() == false) { + if (NotifyServiceStatusChange(service, + SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOPPED | + SERVICE_NOTIFY_STOP_PENDING, + &s_notify) == ERROR_SUCCESS) { + ::SleepEx(INFINITE, true); + } else { + CloseServiceHandle(service); + CloseServiceHandle(service_manager); + break; + } + } + m_running.store(false); +} + +void CALLBACK ConnectionStatusObserver::ServiceNotifyCallback(void* ptr) { + SERVICE_NOTIFY* serviceNotify = static_cast(ptr); + ConnectionStatusObserver* instance = static_cast(serviceNotify->pContext); + + if (!instance || serviceNotify->dwNotificationStatus != ERROR_SUCCESS) { + return; + } + + auto service_status = &serviceNotify->ServiceStatus; + auto status = ConnectionStatusFromWinSvcState(service_status->dwCurrentState); + + if (instance->sink_) { + instance->sink_->Success(flutter::EncodableValue(ConnectionStatusToString(status))); + } +} + +std::unique_ptr> ConnectionStatusObserver::OnListenInternal( + const flutter::EncodableValue* arguments, std::unique_ptr>&& events) { + sink_ = std::move(events); + // sink_->Success(flutter::EncodableValue(ConnectionStatusToString(ConnectionStatus::disconnected))); + return nullptr; +} + +std::unique_ptr> ConnectionStatusObserver::OnCancelInternal( + const flutter::EncodableValue* arguments) { + if (sink_) { + sink_.reset(); + } + + return nullptr; +} + +} // namespace wireguard_dart diff --git a/windows/connection_status_observer.h b/windows/connection_status_observer.h new file mode 100644 index 0000000..98894dc --- /dev/null +++ b/windows/connection_status_observer.h @@ -0,0 +1,40 @@ +#ifndef WIREGUARD_DART_CONNECTION_STATUS_OBSERVER_H +#define WIREGUARD_DART_CONNECTION_STATUS_OBSERVER_H + +#include +#include +#include + +#include + +namespace wireguard_dart { + +class ConnectionStatusObserver : public flutter::StreamHandler { + public: + ConnectionStatusObserver(); + virtual ~ConnectionStatusObserver(); + void StartObserving(std::wstring service_name); + void StopObserving(); + static void CALLBACK ServiceNotifyCallback(void* ptr); + + protected: + virtual std::unique_ptr> OnListenInternal( + const flutter::EncodableValue* arguments, std::unique_ptr>&& events); + + virtual std::unique_ptr> OnCancelInternal( + const flutter::EncodableValue* arguments); + + private: + std::unique_ptr> sink_; + PSC_NOTIFICATION_REGISTRATION subscription_; + void StartObservingThreadProc(std::wstring service_name); + + void Shutdown(); + std::thread watch_thread; + std::atomic_bool m_watch_thread_stop; + std::atomic_bool m_running; +}; + +} // namespace wireguard_dart + +#endif diff --git a/windows/service_control.cpp b/windows/service_control.cpp index 85e4794..54e9ff8 100644 --- a/windows/service_control.cpp +++ b/windows/service_control.cpp @@ -31,28 +31,37 @@ void ServiceControl::Create(CreateArgs args) { throw ServiceControlException("Failed to open service manager", GetLastError()); } - SC_HANDLE service = OpenService(service_manager, &service_name_[0], SC_MANAGER_ALL_ACCESS); - if (service != NULL) { - DeleteService(service); - CloseServiceHandle(service); - } - - service = CreateService(service_manager, // SCM database - &service_name_[0], // name of service - &service_name_[0], // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - args.executable_and_args.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - args.dependencies.c_str(), - NULL, // LocalSystem account - NULL); + // Attempt to open and re-configure existing service by name. + // Otherwise create a new one. + SC_HANDLE service = OpenService(service_manager, &service_name_[0], SERVICE_ALL_ACCESS); if (service == NULL) { - CloseServiceHandle(service_manager); - throw ServiceControlException("Failed to create the service", GetLastError()); + // Create a new service + service = CreateService(service_manager, // SCM database + &service_name_[0], // name of service + &service_name_[0], // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + args.executable_and_args.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + args.dependencies.c_str(), + NULL, // LocalSystem account + NULL); + if (service == NULL) { + CloseServiceHandle(service_manager); + throw ServiceControlException("Failed to create the service", GetLastError()); + } + } else { + // Attempt to re-configure existing service + if (!ChangeServiceConfig(service, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + args.executable_and_args.c_str(), NULL, NULL, args.dependencies.c_str(), NULL, NULL, + &service_name_[0])) { + CloseServiceHandle(service); + CloseServiceHandle(service_manager); + throw ServiceControlException("Failed to re-configure the service", GetLastError()); + } } auto sid_type = SERVICE_SID_TYPE_UNRESTRICTED; @@ -212,4 +221,32 @@ void ServiceControl::Disable() { } } +ConnectionStatus ServiceControl::Status() { + SC_HANDLE service_manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (service_manager == NULL) { + throw ServiceControlException("Failed to open service manager", GetLastError()); + } + + SC_HANDLE service = OpenService(service_manager, &service_name_[0], SERVICE_QUERY_STATUS); + if (service == NULL) { + CloseServiceHandle(service_manager); + return ConnectionStatus::disconnected; + } + + SERVICE_STATUS_PROCESS service_status; + DWORD service_status_bytes_needed; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&service_status, sizeof(SERVICE_STATUS_PROCESS), + &service_status_bytes_needed)) { + CloseServiceHandle(service); + CloseServiceHandle(service_manager); + throw ServiceControlException("Failed to query service status", GetLastError()); + } + + ConnectionStatus status = ConnectionStatusFromWinSvcState(service_status.dwCurrentState); + + CloseServiceHandle(service); + CloseServiceHandle(service_manager); + return status; +} + } // namespace wireguard_dart diff --git a/windows/service_control.h b/windows/service_control.h index 34bafb7..662baf1 100644 --- a/windows/service_control.h +++ b/windows/service_control.h @@ -3,6 +3,8 @@ #include +#include "connection_status.h" + namespace wireguard_dart { struct CreateArgs { @@ -19,6 +21,7 @@ class ServiceControl { void Start(); void Stop(); void Disable(); + ConnectionStatus Status(); }; } // namespace wireguard_dart diff --git a/windows/wireguard_dart_plugin.cpp b/windows/wireguard_dart_plugin.cpp index 2671eb5..f53ed1f 100644 --- a/windows/wireguard_dart_plugin.cpp +++ b/windows/wireguard_dart_plugin.cpp @@ -1,6 +1,8 @@ #include "wireguard_dart_plugin.h" // This must be included before many other Windows headers. +#include +#include #include #include #include @@ -11,6 +13,8 @@ #include #include "config_writer.h" +#include "connection_status.h" +#include "connection_status_observer.h" #include "key_generator.h" #include "service_control.h" #include "tunnel.h" @@ -30,12 +34,29 @@ void WireguardDartPlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows plugin_pointer->HandleMethodCall(call, std::move(result)); }); + auto status_channel = std::make_unique>( + registrar->messenger(), "wireguard_dart/status", &flutter::StandardMethodCodec::GetInstance()); + + plugin->connection_status_observer_ = std::make_unique(); + auto status_channel_handler = std::make_unique>( + [plugin_pointer = plugin.get()]( + const flutter::EncodableValue *args, + std::unique_ptr> &&events) -> std::unique_ptr> { + return plugin_pointer->connection_status_observer_->OnListen(args, std::move(events)); + }, + [plugin_pointer = + plugin.get()](const flutter::EncodableValue *arguments) -> std::unique_ptr> { + return plugin_pointer->connection_status_observer_->OnCancel(arguments); + }); + + status_channel->SetStreamHandler(std::move(status_channel_handler)); + registrar->AddPlugin(std::move(plugin)); } WireguardDartPlugin::WireguardDartPlugin() {} -WireguardDartPlugin::~WireguardDartPlugin() {} +WireguardDartPlugin::~WireguardDartPlugin() { this->connection_status_observer_.get()->StopObserving(); } void WireguardDartPlugin::HandleMethodCall(const flutter::MethodCall &call, std::unique_ptr> result) { @@ -76,6 +97,8 @@ void WireguardDartPlugin::HandleMethodCall(const flutter::MethodCalltunnel_service_ = std::make_unique(Utf8ToWide(*arg_service_name)); + this->connection_status_observer_.get()->StartObserving(Utf8ToWide(*arg_service_name)); + result->Success(); return; } @@ -106,8 +129,8 @@ void WireguardDartPlugin::HandleMethodCall(const flutter::MethodCallservice_name_ + L" WireGuard tunnel"; csa.executable_and_args = service_exec; csa.dependencies = L"Nsi\0TcpIp\0"; - tunnel_service->Create(csa); } catch (std::exception &e) { result->Error(std::string(e.what())); return; } - try { tunnel_service->Start(); } catch (std::exception &e) { result->Error(std::string(e.what())); return; } - result->Success(); return; } @@ -150,6 +170,22 @@ void WireguardDartPlugin::HandleMethodCall(const flutter::MethodCalltunnel_service_.get(); + if (tunnel_service == nullptr) { + result->Error("Invalid state: call 'setupTunnel' first"); + return; + } + + try { + auto status = tunnel_service->Status(); + result->Success(ConnectionStatusToString(status)); + } catch (std::exception &e) { + result->Error(std::string(e.what())); + } + return; + } + result->NotImplemented(); } diff --git a/windows/wireguard_dart_plugin.h b/windows/wireguard_dart_plugin.h index 9877ebe..1083769 100644 --- a/windows/wireguard_dart_plugin.h +++ b/windows/wireguard_dart_plugin.h @@ -7,6 +7,7 @@ #include #include "service_control.h" +#include "connection_status_observer.h" namespace wireguard_dart { @@ -28,6 +29,7 @@ class WireguardDartPlugin : public flutter::Plugin { std::unique_ptr> result); std::unique_ptr tunnel_service_; + std::unique_ptr connection_status_observer_; }; } // namespace wireguard_dart