From 839e4819f62b8b44b8de24746b0f06e28350d76d Mon Sep 17 00:00:00 2001 From: jigar-f <132374182+jigar-f@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:19:33 +0530 Subject: [PATCH] Deeplink Support for Desktop (#1272) * implemented deeplink for macos * Setup for windows. * Setup for Linux. * Fix compile issue and other changes. * Update checksum. * Remove entitlements from debug, added swift format in macos. --- .run/desktop.run.xml | 7 -- Makefile | 3 +- lib/app.dart | 3 - linux/my_application.cc | 149 +++++++++++++----------- macos/Runner.xcodeproj/project.pbxproj | 8 +- macos/Runner/AppDelegate.swift | 18 +++ macos/Runner/Info.plist | 8 +- macos/Runner/Release.entitlements | 5 + pubspec.lock | 44 ++++++- pubspec.yaml | 17 ++- windows/packaging/msix/make_config.yaml | 2 + windows/runner/main.cpp | 44 +++++++ 12 files changed, 218 insertions(+), 90 deletions(-) delete mode 100644 .run/desktop.run.xml diff --git a/.run/desktop.run.xml b/.run/desktop.run.xml deleted file mode 100644 index c9e939683d..0000000000 --- a/.run/desktop.run.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/Makefile b/Makefile index c53efdf46b..a70d79f1ce 100644 --- a/Makefile +++ b/Makefile @@ -591,6 +591,7 @@ macos: macos-arm64 ${DESKTOP_LIB_NAME}_amd64.dylib \ -output ${DARWIN_LIB_NAME} install_name_tool -id "@rpath/${DARWIN_LIB_NAME}" ${DARWIN_LIB_NAME} + rm -Rf ${DESKTOP_LIB_NAME}_arm64.dylib ${DESKTOP_LIB_NAME}_amd64.dylib $(INSTALLER_NAME).dmg: require-version require-appdmg require-retry require-magick @echo "Generating distribution package for darwin/amd64..." && \ @@ -733,7 +734,7 @@ assert-go-version: .PHONY: swift-format swift-format: - swift-format --in-place --recursive DBModule ios/Runner ios/Tunnel ios/LanternTests + swift-format --in-place --recursive DBModule ios/Runner ios/Tunnel ios/LanternTests macos/Runner clean: rm -f liblantern*.aar && \ diff --git a/lib/app.dart b/lib/app.dart index 3dafd90ee0..d54f759347 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -204,9 +204,6 @@ class _LanternAppState extends State } DeepLink navigateToDeepLink(PlatformDeepLink deepLink) { - if (!Platform.isAndroid) { - return DeepLink.defaultPath; - } logger.d("DeepLink configuration: ${deepLink.configuration.toString()}"); if (deepLink.path.toLowerCase().startsWith('/report-issue')) { logger.d("DeepLink uri: ${deepLink.uri.toString()}"); diff --git a/linux/my_application.cc b/linux/my_application.cc index 35209eccb5..6a43c2b5aa 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -1,6 +1,7 @@ #include "my_application.h" #include + #ifdef GDK_WINDOWING_X11 #include #endif @@ -8,95 +9,105 @@ #include "flutter/generated_plugin_registrant.h" struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; + GtkApplication parent_instance; + char **dart_entrypoint_arguments; }; -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION +) // Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; +static void my_application_activate(GApplication *application) { + MyApplication * self = MY_APPLICATION(application); + GList *windows = gtk_application_get_windows(GTK_APPLICATION(application)); + if (windows) { + gtk_window_present(GTK_WINDOW(windows->data)); + return; + } + GtkWindow *window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } } - } #endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "Lantern"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "Lantern"); - } + if (use_header_bar) { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "Lantern"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "Lantern"); + } - gtk_window_set_default_size(window, 360, 712); - gtk_widget_show(GTK_WIDGET(window)); + gtk_window_set_default_size(window, 360, 712); + gtk_widget_show(GTK_WIDGET(window)); - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + g_autoptr(FlDartProject) + project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + FlView *view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - gtk_widget_grab_focus(GTK_WIDGET(view)); + gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; +static gboolean +my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) { + MyApplication * self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) + error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return FALSE; } // Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +static void my_application_dispose(GObject *object) { + MyApplication * self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +static void my_application_class_init(MyApplicationClass *klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } -static void my_application_init(MyApplication* self) {} +static void my_application_init(MyApplication * self) {} -MyApplication* my_application_new() { - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - nullptr)); } +MyApplication *my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, + nullptr)); +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 1cbcd726a2..efedbbd434 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -570,10 +570,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = ACZRKC3LQ9; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; LD_RUNPATH_SEARCH_PATHS = ( @@ -724,10 +724,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = ACZRKC3LQ9; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index a6f73a80ad..b92cebadd4 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,9 +1,27 @@ import Cocoa import FlutterMacOS +import app_links @main class AppDelegate: FlutterAppDelegate { + + public override func application( + _ application: NSApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void + ) -> Bool { + + guard let url = AppLinks.shared.getUniversalLink(userActivity) else { + return false + } + + AppLinks.shared.handleLink(link: url.absoluteString) + + return false + } + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return false } + } diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index e277023973..5fdc29e8d7 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -2,6 +2,10 @@ + FlutterDeepLinkingEnabled + + LSApplicationCategoryType + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -28,7 +32,7 @@ MainMenu NSPrincipalClass NSApplication - com.apple.security.network.client - + com.apple.security.network.client + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a472..a17c81038a 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -2,6 +2,11 @@ + com.apple.developer.associated-domains + + webcredentials:lantern.io + applinks:lantern.io + com.apple.security.app-sandbox diff --git a/pubspec.lock b/pubspec.lock index b3261af03c..54129718bf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,6 +70,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + url: "https://pub.dev" + source: hosted + version: "4.0.2" args: dependency: transitive description: @@ -350,6 +358,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.dev" + source: hosted + version: "4.1.0" convert: dependency: transitive description: @@ -880,10 +896,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "7.7.0" gettext_parser: dependency: transitive description: @@ -988,6 +1004,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.6" + image: + dependency: transitive + description: + name: image + sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" + url: "https://pub.dev" + source: hosted + version: "4.5.2" in_app_purchase: dependency: "direct main" description: @@ -1193,6 +1217,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.4" + msix: + dependency: "direct dev" + description: + name: msix + sha256: c50d6bd1aafe0d071a3c1e5a5ccb056404502935cb0a549e3178c4aae16caf33 + url: "https://pub.dev" + source: hosted + version: "3.16.8" nested: dependency: transitive description: @@ -1401,6 +1433,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6674b34047..1fbb5adf1f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -132,7 +132,7 @@ dependencies: # Deeplink handling app_links: ^6.3.3 # Service Locator - get_it: ^8.0.3 + get_it: ^7.7.0 #Loading animated_loading_border: ^0.0.2 shimmer: ^3.0.0 @@ -149,6 +149,7 @@ dev_dependencies: mockito: ^5.4.4 auto_route_generator: ^9.0.0 build_runner: ^2.4.13 + msix: ^3.16.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -206,4 +207,16 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file + # see https://flutter.dev/custom-fonts/#from-packages + + +# Windows related setup +msix_config: + display_name: Lantern + publisher_display_name: lantern + identity_name: org.getlantern.lantern + msix_version: 3.0.0.0 + protocol_activation: https + app_uri_handler_hosts: www.lantern.io, lantern.io # Add the app uri handler hosts. You + logo_path: windows/runner/resources/app_icon.ico + capabilities: internetClient, internetClientServer, privateNetworkClientServer \ No newline at end of file diff --git a/windows/packaging/msix/make_config.yaml b/windows/packaging/msix/make_config.yaml index d3ab30c377..1808b84f2d 100644 --- a/windows/packaging/msix/make_config.yaml +++ b/windows/packaging/msix/make_config.yaml @@ -3,5 +3,7 @@ msix_version: 2.1.5.0 publisher_display_name: lantern identity_name: org.getlantern.lantern logo_path: windows/runner/resources/app_icon.ico +protocol_activation: https +app_uri_handler_hosts: www.lantern.io, lantern.io # Add the app uri handler hosts. You capabilities: internetClient, internetClientServer, privateNetworkClientServer install_certificate: "false" \ No newline at end of file diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index f228a5058e..fd56925ca3 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -4,9 +4,52 @@ #include "flutter_window.h" #include "utils.h" +#include "app_links/app_links_plugin_c_api.h" + + +bool SendAppLinkToInstance(const std::wstring& title) { + // Find our exact window + HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", title.c_str()); + + if (hwnd) { + // Dispatch new link to current window + SendAppLink(hwnd); + + // (Optional) Restore our window to front in same state + WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; + GetWindowPlacement(hwnd, &place); + + switch(place.showCmd) { + case SW_SHOWMAXIMIZED: + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + break; + case SW_SHOWMINIMIZED: + ShowWindow(hwnd, SW_RESTORE); + break; + default: + ShowWindow(hwnd, SW_NORMAL); + break; + } + + SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(hwnd); + // END (Optional) Restore + + // Window has been found, don't create another one. + return true; + } + + return false; +} + int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { + // You may ignore the result if you need to create another window. + if (SendAppLinkToInstance(L"Lantern")) { + return EXIT_SUCCESS; + } + // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { @@ -41,3 +84,4 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, ::CoUninitialize(); return EXIT_SUCCESS; } +