From 96842d64d45b80c54e026b2b9b783fa9571dc9fd Mon Sep 17 00:00:00 2001 From: Manav Agarwal Date: Fri, 8 May 2026 00:44:26 -0700 Subject: [PATCH 1/2] add linux platform support --- linux/CMakeLists.txt | 27 +++++ .../secure_application_plugin.h | 26 +++++ linux/secure_application_plugin.cc | 106 ++++++++++++++++++ pubspec.yaml | 2 + 4 files changed, 161 insertions(+) create mode 100644 linux/CMakeLists.txt create mode 100644 linux/include/secure_application/secure_application_plugin.h create mode 100644 linux/secure_application_plugin.cc diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..3132067 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "secure_application") +project(${PROJECT_NAME} LANGUAGES CXX) + +set(PLUGIN_NAME "${PROJECT_NAME}_plugin") + +list(APPEND PLUGIN_SOURCES + "secure_application_plugin.cc" +) + +add_library(${PLUGIN_NAME} SHARED + ${PLUGIN_SOURCES} +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin +set(secure_application_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/linux/include/secure_application/secure_application_plugin.h b/linux/include/secure_application/secure_application_plugin.h new file mode 100644 index 0000000..bcab58d --- /dev/null +++ b/linux/include/secure_application/secure_application_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_SECURE_APPLICATION_PLUGIN_H_ +#define FLUTTER_PLUGIN_SECURE_APPLICATION_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _SecureApplicationPlugin SecureApplicationPlugin; +typedef struct { + GObjectClass parent_class; +} SecureApplicationPluginClass; + +FLUTTER_PLUGIN_EXPORT GType secure_application_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void secure_application_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_SECURE_APPLICATION_PLUGIN_H_ diff --git a/linux/secure_application_plugin.cc b/linux/secure_application_plugin.cc new file mode 100644 index 0000000..bc834b1 --- /dev/null +++ b/linux/secure_application_plugin.cc @@ -0,0 +1,106 @@ +#include "include/secure_application/secure_application_plugin.h" + +#include +#include +#include + +#include + +#define SECURE_APPLICATION_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), secure_application_plugin_get_type(), \ + SecureApplicationPlugin)) + +struct _SecureApplicationPlugin { + GObject parent_instance; + FlMethodChannel* channel; + GtkWindow* window; + gulong window_state_handler_id; +}; + +G_DEFINE_TYPE(SecureApplicationPlugin, secure_application_plugin, + g_object_get_type()) + +// Mirrors Windows: respond Success() to every method call. The actual lock / +// blur UI is rendered Dart-side by SecureGate, so the native plugin only needs +// to acknowledge channel calls and invoke "lock" on system events. +static void secure_application_plugin_handle_method_call( + SecureApplicationPlugin* self, FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getPlatformVersion") == 0) { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar* version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } else { + response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + fl_method_call_respond(method_call, response, nullptr); +} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + SecureApplicationPlugin* plugin = SECURE_APPLICATION_PLUGIN(user_data); + secure_application_plugin_handle_method_call(plugin, method_call); +} + +// Mirrors the Windows SC_MINIMIZE hook: when the toplevel window is iconified, +// invoke the Dart-side "lock" handler so the SecureGate overlay engages before +// the user-restored window is repainted. +static gboolean window_state_event_cb(GtkWidget* widget, + GdkEventWindowState* event, + gpointer user_data) { + SecureApplicationPlugin* self = SECURE_APPLICATION_PLUGIN(user_data); + if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && + (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) { + fl_method_channel_invoke_method(self->channel, "lock", nullptr, nullptr, + nullptr, nullptr); + } + return FALSE; +} + +static void secure_application_plugin_dispose(GObject* object) { + SecureApplicationPlugin* self = SECURE_APPLICATION_PLUGIN(object); + if (self->window != nullptr && self->window_state_handler_id != 0) { + g_signal_handler_disconnect(self->window, self->window_state_handler_id); + self->window_state_handler_id = 0; + self->window = nullptr; + } + g_clear_object(&self->channel); + G_OBJECT_CLASS(secure_application_plugin_parent_class)->dispose(object); +} + +static void secure_application_plugin_class_init( + SecureApplicationPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = secure_application_plugin_dispose; +} + +static void secure_application_plugin_init(SecureApplicationPlugin* self) {} + +void secure_application_plugin_register_with_registrar( + FlPluginRegistrar* registrar) { + SecureApplicationPlugin* plugin = SECURE_APPLICATION_PLUGIN( + g_object_new(secure_application_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + plugin->channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + "secure_application", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + plugin->channel, method_call_cb, g_object_ref(plugin), g_object_unref); + + FlView* view = fl_plugin_registrar_get_view(registrar); + if (view != nullptr) { + GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(view)); + if (GTK_IS_WINDOW(toplevel)) { + plugin->window = GTK_WINDOW(toplevel); + plugin->window_state_handler_id = + g_signal_connect(toplevel, "window-state-event", + G_CALLBACK(window_state_event_cb), plugin); + } + } + + g_object_unref(plugin); +} diff --git a/pubspec.yaml b/pubspec.yaml index 1f19783..92548b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,3 +32,5 @@ flutter: fileName: secure_application_web.dart windows: pluginClass: SecureApplicationPlugin + linux: + pluginClass: SecureApplicationPlugin From 4e5e9318f3733144060c3221560573590ff445be Mon Sep 17 00:00:00 2001 From: Manav Agarwal Date: Fri, 8 May 2026 22:18:52 -0700 Subject: [PATCH 2/2] drop iconify-lock hook on linux local_auth has no linux platform implementation; once SecureGate enters its locked state via the channel, authenticate() throws MissingPluginException and the user is stranded on the lock screen with no way back. keep the no-op method handler so apps don't hit MissingPluginException for opacity/lock/secure calls. apps that want explicit lock-on-minimize behaviour on linux can drive it from a dart-side WindowListener once local_auth_linux exists. --- linux/secure_application_plugin.cc | 38 +++++------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/linux/secure_application_plugin.cc b/linux/secure_application_plugin.cc index bc834b1..ec8869c 100644 --- a/linux/secure_application_plugin.cc +++ b/linux/secure_application_plugin.cc @@ -13,8 +13,6 @@ struct _SecureApplicationPlugin { GObject parent_instance; FlMethodChannel* channel; - GtkWindow* window; - gulong window_state_handler_id; }; G_DEFINE_TYPE(SecureApplicationPlugin, secure_application_plugin, @@ -46,28 +44,8 @@ static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, secure_application_plugin_handle_method_call(plugin, method_call); } -// Mirrors the Windows SC_MINIMIZE hook: when the toplevel window is iconified, -// invoke the Dart-side "lock" handler so the SecureGate overlay engages before -// the user-restored window is repainted. -static gboolean window_state_event_cb(GtkWidget* widget, - GdkEventWindowState* event, - gpointer user_data) { - SecureApplicationPlugin* self = SECURE_APPLICATION_PLUGIN(user_data); - if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && - (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) { - fl_method_channel_invoke_method(self->channel, "lock", nullptr, nullptr, - nullptr, nullptr); - } - return FALSE; -} - static void secure_application_plugin_dispose(GObject* object) { SecureApplicationPlugin* self = SECURE_APPLICATION_PLUGIN(object); - if (self->window != nullptr && self->window_state_handler_id != 0) { - g_signal_handler_disconnect(self->window, self->window_state_handler_id); - self->window_state_handler_id = 0; - self->window = nullptr; - } g_clear_object(&self->channel); G_OBJECT_CLASS(secure_application_plugin_parent_class)->dispose(object); } @@ -91,16 +69,12 @@ void secure_application_plugin_register_with_registrar( fl_method_channel_set_method_call_handler( plugin->channel, method_call_cb, g_object_ref(plugin), g_object_unref); - FlView* view = fl_plugin_registrar_get_view(registrar); - if (view != nullptr) { - GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(view)); - if (GTK_IS_WINDOW(toplevel)) { - plugin->window = GTK_WINDOW(toplevel); - plugin->window_state_handler_id = - g_signal_connect(toplevel, "window-state-event", - G_CALLBACK(window_state_event_cb), plugin); - } - } + // NOTE: We deliberately do NOT hook the GTK window-state-event to invoke + // "lock" on iconify the way the Windows plugin hooks SC_MINIMIZE. On Linux, + // local_auth has no platform implementation, so an app that engages + // SecureGate's locked state has no way to authenticate back out — the user + // is stranded on the lock screen. Apps that want explicit lock-on-minimize + // on Linux can call `lock()` themselves from a Dart-side WindowListener. g_object_unref(plugin); }