From 048b1363f7a7358bf7179d23747f57d75bd00489 Mon Sep 17 00:00:00 2001 From: qianlifeng Date: Tue, 28 May 2024 16:29:54 +0800 Subject: [PATCH] Add support for deep linking Deep linking capability was added to the application along with handling for custom 'wox' scheme. This change enables the application to receive and process deep links, allowing navigation to specific parts of the application from schema-formatted URLs. This will let users to directly install a new plugin from plugin store, and also improve inter-app and website-app interactions. --- Wox.UI.Flutter/wox/lib/api/wox_api.dart | 7 +++ Wox.UI.Flutter/wox/lib/main.dart | 33 ++++++++++++- .../Flutter/GeneratedPluginRegistrant.swift | 2 + Wox.UI.Flutter/wox/macos/Podfile.lock | 6 +++ Wox.UI.Flutter/wox/macos/Runner/Info.plist | 13 +++++ Wox.UI.Flutter/wox/pubspec.lock | 48 +++++++++++++++++++ Wox.UI.Flutter/wox/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 ++ .../windows/flutter/generated_plugins.cmake | 1 + Wox.UI.Flutter/wox/windows/runner/main.cpp | 13 +++++ Wox/ui/manager.go | 15 ++++++ Wox/ui/router.go | 27 +++++++++++ docs/plugin_store.md | 3 +- 13 files changed, 169 insertions(+), 3 deletions(-) diff --git a/Wox.UI.Flutter/wox/lib/api/wox_api.dart b/Wox.UI.Flutter/wox/lib/api/wox_api.dart index a33f5d3e..fc39fb0c 100644 --- a/Wox.UI.Flutter/wox/lib/api/wox_api.dart +++ b/Wox.UI.Flutter/wox/lib/api/wox_api.dart @@ -97,4 +97,11 @@ class WoxApi { "query": query.toJson(), }); } + + Future onProtocolUrlReceived(String command, Map arguments) async { + await WoxHttpUtil.instance.postData("/deeplink", { + "command": command, + "arguments": arguments, + }); + } } diff --git a/Wox.UI.Flutter/wox/lib/main.dart b/Wox.UI.Flutter/wox/lib/main.dart index f7dcff31..4962cba2 100644 --- a/Wox.UI.Flutter/wox/lib/main.dart +++ b/Wox.UI.Flutter/wox/lib/main.dart @@ -3,9 +3,9 @@ import 'dart:io'; import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:get/get.dart'; +import 'package:protocol_handler/protocol_handler.dart'; import 'package:uuid/v4.dart'; import 'package:window_manager/window_manager.dart'; import 'package:wox/api/wox_api.dart'; @@ -23,6 +23,7 @@ import 'package:wox/utils/wox_websocket_msg_util.dart'; void main(List arguments) async { await initialServices(arguments); await initWindow(); + await initDeepLink(); runApp(const MyApp()); } @@ -58,6 +59,12 @@ Future initialServices(List arguments) async { Get.put(WoxSettingController()); } +Future initDeepLink() async { + // Register a custom protocol + // For macOS platform needs to declare the scheme in ios/Runner/Info.plist + await protocolHandler.register('wox'); +} + Future initWindow() async { await windowManager.ensureInitialized(); await Window.initialize(); @@ -99,11 +106,13 @@ class WoxApp extends StatefulWidget { State createState() => _WoxAppState(); } -class _WoxAppState extends State with WindowListener { +class _WoxAppState extends State with WindowListener, ProtocolListener { @override void initState() { super.initState(); + protocolHandler.addListener(this); + setAcrylicEffect(); var launcherController = Get.find(); @@ -144,8 +153,28 @@ class _WoxAppState extends State with WindowListener { } } + @override + void onProtocolUrlReceived(String url) { + Logger.instance.info(const UuidV4().generate(), "deep link received: $url"); + //replace %20 with space in the url + url = url.replaceAll("%20", " "); + // split the command and argument + // wox://command?argument=value&argument2=value2 + var command = url.split("?")[0].split("//")[1]; + var arguments = url.split("?")[1].split("&"); + var argumentMap = {}; + for (var argument in arguments) { + var key = argument.split("=")[0]; + var value = argument.split("=")[1]; + argumentMap[key] = value; + } + + WoxApi.instance.onProtocolUrlReceived(command, argumentMap); + } + @override void dispose() { + protocolHandler.removeListener(this); windowManager.removeListener(this); super.dispose(); } diff --git a/Wox.UI.Flutter/wox/macos/Flutter/GeneratedPluginRegistrant.swift b/Wox.UI.Flutter/wox/macos/Flutter/GeneratedPluginRegistrant.swift index df1299f8..ab46531a 100644 --- a/Wox.UI.Flutter/wox/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/Wox.UI.Flutter/wox/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import device_info_plus import hotkey_manager_macos import macos_window_utils import path_provider_foundation +import protocol_handler_macos import screen_retriever import syncfusion_pdfviewer_macos import url_launcher_macos @@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ProtocolHandlerMacosPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerMacosPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/Wox.UI.Flutter/wox/macos/Podfile.lock b/Wox.UI.Flutter/wox/macos/Podfile.lock index 94ef8abd..d175b20b 100644 --- a/Wox.UI.Flutter/wox/macos/Podfile.lock +++ b/Wox.UI.Flutter/wox/macos/Podfile.lock @@ -13,6 +13,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - protocol_handler_macos (0.0.1): + - FlutterMacOS - screen_retriever (0.0.1): - FlutterMacOS - syncfusion_pdfviewer_macos (0.0.1): @@ -29,6 +31,7 @@ DEPENDENCIES: - hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - syncfusion_pdfviewer_macos (from `Flutter/ephemeral/.symlinks/plugins/syncfusion_pdfviewer_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -51,6 +54,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + protocol_handler_macos: + :path: Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos screen_retriever: :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos syncfusion_pdfviewer_macos: @@ -68,6 +73,7 @@ SPEC CHECKSUMS: hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160 macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 syncfusion_pdfviewer_macos: e9194851581cad04b28b53913d0636d39a4ed4b2 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 diff --git a/Wox.UI.Flutter/wox/macos/Runner/Info.plist b/Wox.UI.Flutter/wox/macos/Runner/Info.plist index 4789daa6..fcfda545 100644 --- a/Wox.UI.Flutter/wox/macos/Runner/Info.plist +++ b/Wox.UI.Flutter/wox/macos/Runner/Info.plist @@ -26,6 +26,19 @@ $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + + CFBundleURLSchemes + + wox + + + NSPrincipalClass NSApplication diff --git a/Wox.UI.Flutter/wox/pubspec.lock b/Wox.UI.Flutter/wox/pubspec.lock index 17525a67..6d9710e9 100644 --- a/Wox.UI.Flutter/wox/pubspec.lock +++ b/Wox.UI.Flutter/wox/pubspec.lock @@ -517,6 +517,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + protocol_handler: + dependency: "direct main" + description: + name: protocol_handler + sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + protocol_handler_android: + dependency: transitive + description: + name: protocol_handler_android + sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + protocol_handler_ios: + dependency: transitive + description: + name: protocol_handler_ios + sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + protocol_handler_macos: + dependency: transitive + description: + name: protocol_handler_macos + sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + protocol_handler_platform_interface: + dependency: transitive + description: + name: protocol_handler_platform_interface + sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + protocol_handler_windows: + dependency: transitive + description: + name: protocol_handler_windows + sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4 + url: "https://pub.dev" + source: hosted + version: "0.2.0" recase: dependency: transitive description: diff --git a/Wox.UI.Flutter/wox/pubspec.yaml b/Wox.UI.Flutter/wox/pubspec.yaml index 56ea6399..f1624298 100644 --- a/Wox.UI.Flutter/wox/pubspec.yaml +++ b/Wox.UI.Flutter/wox/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: url_launcher: ^6.2.5 flutter_image_slideshow: ^0.1.6 dynamic_tabbar: ^1.0.6 + protocol_handler: ^0.2.0 dev_dependencies: flutter_test: diff --git a/Wox.UI.Flutter/wox/windows/flutter/generated_plugin_registrant.cc b/Wox.UI.Flutter/wox/windows/flutter/generated_plugin_registrant.cc index 5cfbc7d8..b940325e 100644 --- a/Wox.UI.Flutter/wox/windows/flutter/generated_plugin_registrant.cc +++ b/Wox.UI.Flutter/wox/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterAcrylicPlugin")); HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); + ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); SyncfusionPdfviewerWindowsPluginRegisterWithRegistrar( diff --git a/Wox.UI.Flutter/wox/windows/flutter/generated_plugins.cmake b/Wox.UI.Flutter/wox/windows/flutter/generated_plugins.cmake index c826e4b2..e87e9214 100644 --- a/Wox.UI.Flutter/wox/windows/flutter/generated_plugins.cmake +++ b/Wox.UI.Flutter/wox/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop flutter_acrylic hotkey_manager_windows + protocol_handler_windows screen_retriever syncfusion_pdfviewer_windows url_launcher_windows diff --git a/Wox.UI.Flutter/wox/windows/runner/main.cpp b/Wox.UI.Flutter/wox/windows/runner/main.cpp index 56bc0796..1cc5508e 100644 --- a/Wox.UI.Flutter/wox/windows/runner/main.cpp +++ b/Wox.UI.Flutter/wox/windows/runner/main.cpp @@ -5,8 +5,21 @@ #include "flutter_window.h" #include "utils.h" +#include + int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { + + // Replace protocol_handler_example with your_window_title. + HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"wox"); + if (hwnd != NULL) { + DispatchToProtocolHandler(hwnd); + + ::ShowWindow(hwnd, SW_NORMAL); + ::SetForegroundWindow(hwnd); + return EXIT_FAILURE; + } + // 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()) { diff --git a/Wox/ui/manager.go b/Wox/ui/manager.go index ea46c2a3..a7d0155e 100644 --- a/Wox/ui/manager.go +++ b/Wox/ui/manager.go @@ -479,3 +479,18 @@ func (m *Manager) SetActiveWindowName(name string) { func (m *Manager) GetActiveWindowName() string { return m.activeWindowName } + +func (m *Manager) PostDeeplink(ctx context.Context, command string, arguments map[string]string) { + logger.Info(ctx, fmt.Sprintf("deeplink: %s, %v", command, arguments)) + + if command == "query" { + query := arguments["q"] + if query != "" { + m.ui.ChangeQuery(ctx, share.PlainQuery{ + QueryType: plugin.QueryTypeInput, + QueryText: query, + }) + m.ui.ShowApp(ctx, share.ShowContext{SelectAll: false}) + } + } +} diff --git a/Wox/ui/router.go b/Wox/ui/router.go index c33b79b1..877f8318 100644 --- a/Wox/ui/router.go +++ b/Wox/ui/router.go @@ -62,6 +62,7 @@ var routers = map[string]func(w http.ResponseWriter, r *http.Request){ "/backup/all": handleBackupAll, "/hotkey/available": handleHotkeyAvailable, "/query/icon": handleQueryIcon, + "/deeplink": handleDeeplink, } func handleHome(w http.ResponseWriter, r *http.Request) { @@ -718,3 +719,29 @@ func handleQueryIcon(w http.ResponseWriter, r *http.Request) { iconImage := plugin.ConvertIcon(ctx, iconImg, pluginInstance.PluginDirectory) writeSuccessResponse(w, iconImage) } + +func handleDeeplink(w http.ResponseWriter, r *http.Request) { + ctx := util.NewTraceContext() + + body, _ := io.ReadAll(r.Body) + commandResult := gjson.GetBytes(body, "command") + if !commandResult.Exists() { + writeErrorResponse(w, "command is empty") + return + } + + // arguments is map[string]string + argumentsResult := gjson.GetBytes(body, "arguments") + var arguments = make(map[string]string) + if argumentsResult.Exists() { + err := json.Unmarshal([]byte(argumentsResult.String()), &arguments) + if err != nil { + writeErrorResponse(w, err.Error()) + return + } + } + + GetUIManager().PostDeeplink(ctx, commandResult.String(), arguments) + + writeSuccessResponse(w, "") +} diff --git a/docs/plugin_store.md b/docs/plugin_store.md index 491df21f..225c2850 100644 --- a/docs/plugin_store.md +++ b/docs/plugin_store.md @@ -11,7 +11,7 @@ fetch('https://raw.githubusercontent.com/Wox-launcher/Wox/v2/plugin-store.json') let thead = document.createElement('thead'); let headerRow = document.createElement('tr'); - let headers = ['Icon', 'Name', 'Description', 'Author', 'Version']; + let headers = ['Icon', 'Name', 'Description', 'Author', 'Version', 'Install']; headers.forEach(header => { let th = document.createElement('th'); if (header === 'Icon') { @@ -45,6 +45,7 @@ fetch('https://raw.githubusercontent.com/Wox-launcher/Wox/v2/plugin-store.json') plugin.Description, plugin.Author, `v${plugin.Version}`, + `Install` ]; cells.forEach(cell => { let td = document.createElement('td');