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');