diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000000..f6cb8ad931f5 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Google diff --git a/packages/url_launcher/url_launcher_windows/.gitignore b/packages/url_launcher/url_launcher_windows/.gitignore new file mode 100644 index 000000000000..53e92cc4181f --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/.gitignore @@ -0,0 +1,3 @@ +.packages +.flutter-plugins +pubspec.lock diff --git a/packages/url_launcher/url_launcher_windows/.metadata b/packages/url_launcher/url_launcher_windows/.metadata new file mode 100644 index 000000000000..720a4596c087 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 6d1c244b79f3a2747281f718297ce248bd5ad099 + channel: master + +project_type: plugin diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md new file mode 100644 index 000000000000..dd3395afab0a --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial Windows implementation of `url_launcher`. diff --git a/packages/url_launcher/url_launcher_windows/LICENSE b/packages/url_launcher/url_launcher_windows/LICENSE new file mode 100644 index 000000000000..507569823f1b --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/LICENSE @@ -0,0 +1,25 @@ +Copyright 2019 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/url_launcher/url_launcher_windows/README.md b/packages/url_launcher/url_launcher_windows/README.md new file mode 100644 index 000000000000..685adc59a1a4 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/README.md @@ -0,0 +1,31 @@ +# url_launcher_windows + +The Windows implementation of [`url_launcher`][1]. + +## Backward compatible 1.0.0 version is coming +The plugin has reached a stable API, we guarantee that version `1.0.0` will be backward compatible with `0.0.y+z`. If you use +url_launcher_windows directly, rather than as an implementation detail +of `url_launcher`, please use `url_launcher_windows: '>=0.0.y+x <2.0.0'` +as your dependency constraint to allow a smoother ecosystem migration. +For more details see: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 + +## Usage + +### Import the package + +This package has not yet been endorsed. Once it is you only need to add +`url_launcher` as a dependency in your `pubspec.yaml`, but for now you +need to include both `url_launcher` and `url_launcher_windows`. + +This is what the above means to your `pubspec.yaml`: + +```yaml +... +dependencies: + ... + url_launcher: ^5.5.3 + url_launcher_windows: ^0.0.1 + ... +``` + +[1]: ../url_launcher/url_launcher diff --git a/packages/url_launcher/url_launcher_windows/example/.gitignore b/packages/url_launcher/url_launcher_windows/example/.gitignore new file mode 100644 index 000000000000..9d532b18a01f --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/url_launcher/url_launcher_windows/example/.metadata b/packages/url_launcher/url_launcher_windows/example/.metadata new file mode 100644 index 000000000000..82cce8b18642 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 6d1c244b79f3a2747281f718297ce248bd5ad099 + channel: master + +project_type: app diff --git a/packages/url_launcher/url_launcher_windows/example/README.md b/packages/url_launcher/url_launcher_windows/example/README.md new file mode 100644 index 000000000000..e444852697b9 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/README.md @@ -0,0 +1,3 @@ +# url_launcher_windows_example + +Demonstrates the url_launcher_windows plugin. diff --git a/packages/url_launcher/url_launcher_windows/example/lib/main.dart b/packages/url_launcher/url_launcher_windows/example/lib/main.dart new file mode 100644 index 000000000000..db59af1e5b95 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/lib/main.dart @@ -0,0 +1,93 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'URL Launcher', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'URL Launcher'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + Future _launched; + + Future _launchInBrowser(String url) async { + if (await UrlLauncherPlatform.instance.canLaunch(url)) { + await UrlLauncherPlatform.instance.launch( + url, + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}, + ); + } else { + throw 'Could not launch $url'; + } + } + + Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return const Text(''); + } + } + + @override + Widget build(BuildContext context) { + const String toLaunch = 'https://www.cylog.org/headers/'; + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: ListView( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text(toLaunch), + ), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchInBrowser(toLaunch); + }), + child: const Text('Launch in browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + FutureBuilder(future: _launched, builder: _launchStatus), + ], + ), + ], + ), + ); + } +} diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml new file mode 100644 index 000000000000..8d6c22057bc2 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -0,0 +1,19 @@ +name: url_launcher_example +description: Demonstrates the Windows implementation of the url_launcher plugin. + +dependencies: + flutter: + sdk: flutter + url_launcher_platform_interface: any + url_launcher_windows: + path: ../ + +dev_dependencies: + integration_test: + path: ../../../integration_test + flutter_driver: + sdk: flutter + pedantic: ^1.8.0 + +flutter: + uses-material-design: true diff --git a/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e.dart new file mode 100644 index 000000000000..7a33e00fec0a --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e.dart @@ -0,0 +1,20 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + test('canLaunch', () async { + UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; + + expect(await launcher.canLaunch('randomstring'), false); + + // Generally all devices should have some default browser. + expect(await launcher.canLaunch('http://flutter.dev'), true); + }); +} diff --git a/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e_test.dart new file mode 100644 index 000000000000..7a2c21338786 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/example/test_driver/url_launcher_e2e_test.dart @@ -0,0 +1,17 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String data = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.close(); + final Map result = jsonDecode(data); + exit(result['result'] == 'true' ? 0 : 1); +} diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart new file mode 100644 index 000000000000..83435f981838 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -0,0 +1,3 @@ +// The url_launcher_platform_interface defaults to MethodChannelUrlLauncher +// as its instance, which is all the Windows implementation needs. This file +// is here to silence warnings when publishing to pub. diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml new file mode 100644 index 000000000000..8a0beb70163a --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -0,0 +1,21 @@ +name: url_launcher_windows +description: Windows implementation of the url_launcher plugin. +# 0.0.y+z is compatible with 1.0.0, if you land a breaking change bump +# the version to 2.0.0. +# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 +version: 0.0.1 +homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_windows + +flutter: + plugin: + platforms: + windows: + pluginClass: UrlLauncherPlugin + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" + +dependencies: + flutter: + sdk: flutter diff --git a/packages/url_launcher/url_launcher_windows/windows/.gitignore b/packages/url_launcher/url_launcher_windows/windows/.gitignore new file mode 100644 index 000000000000..b3eb2be169a5 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt new file mode 100644 index 000000000000..57d87e3f6f85 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "url_launcher_windows") +project(${PROJECT_NAME} LANGUAGES CXX) + +set(PLUGIN_NAME "${PROJECT_NAME}_plugin") + +add_library(${PLUGIN_NAME} SHARED + "url_launcher_plugin.cpp" +) +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 flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin +set(file_chooser_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/url_launcher/url_launcher_windows/windows/include/url_launcher_windows/url_launcher_plugin.h b/packages/url_launcher/url_launcher_windows/windows/include/url_launcher_windows/url_launcher_plugin.h new file mode 100644 index 000000000000..cdd018a0924e --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/include/url_launcher_windows/url_launcher_plugin.h @@ -0,0 +1,26 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ +#define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void UrlLauncherPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp new file mode 100644 index 000000000000..dfb406385787 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp @@ -0,0 +1,148 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "include/url_launcher_windows/url_launcher_plugin.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +using flutter::EncodableMap; +using flutter::EncodableValue; + +// Converts the given UTF-8 string to UTF-16. +std::wstring Utf16FromUtf8(const std::string& utf8_string) { + if (utf8_string.empty()) { + return std::wstring(); + } + int target_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + static_cast(utf8_string.length()), nullptr, 0); + if (target_length == 0) { + return std::wstring(); + } + std::wstring utf16_string; + utf16_string.resize(target_length); + int converted_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + static_cast(utf8_string.length()), + utf16_string.data(), target_length); + if (converted_length == 0) { + return std::wstring(); + } + return utf16_string; +} + +// Returns the URL argument from |method_call| if it is present, otherwise +// returns an empty string. +std::string GetUrlArgument(const flutter::MethodCall<>& method_call) { + std::string url; + const auto* arguments = std::get_if(method_call.arguments()); + if (arguments) { + auto url_it = arguments->find(EncodableValue("url")); + if (url_it != arguments->end()) { + url = std::get(url_it->second); + } + } + return url; +} + +class UrlLauncherPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar); + + virtual ~UrlLauncherPlugin(); + + private: + UrlLauncherPlugin(); + + // Called when a method is called on plugin channel; + void HandleMethodCall(const flutter::MethodCall<>& method_call, + std::unique_ptr> result); +}; + +// static +void UrlLauncherPlugin::RegisterWithRegistrar( + flutter::PluginRegistrar* registrar) { + auto channel = std::make_unique>( + registrar->messenger(), "plugins.flutter.io/url_launcher", + &flutter::StandardMethodCodec::GetInstance()); + + // Uses new instead of make_unique due to private constructor. + std::unique_ptr plugin(new UrlLauncherPlugin()); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto& call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +UrlLauncherPlugin::UrlLauncherPlugin() = default; + +UrlLauncherPlugin::~UrlLauncherPlugin() = default; + +void UrlLauncherPlugin::HandleMethodCall( + const flutter::MethodCall<>& method_call, + std::unique_ptr> result) { + if (method_call.method_name().compare("launch") == 0) { + std::string url = GetUrlArgument(method_call); + if (url.empty()) { + result->Error("argument_error", "No URL provided"); + return; + } + std::wstring url_wide = Utf16FromUtf8(url); + + int status = static_cast(reinterpret_cast( + ::ShellExecute(nullptr, TEXT("open"), url_wide.c_str(), nullptr, + nullptr, SW_SHOWNORMAL))); + + if (status <= 32) { + std::ostringstream error_message; + error_message << "Failed to open " << url << ": ShellExecute error code " + << status; + result->Error("open_error", error_message.str()); + return; + } + result->Success(EncodableValue(true)); + } else if (method_call.method_name().compare("canLaunch") == 0) { + std::string url = GetUrlArgument(method_call); + if (url.empty()) { + result->Error("argument_error", "No URL provided"); + return; + } + + bool can_launch = false; + size_t separator_location = url.find(":"); + if (separator_location != std::string::npos) { + std::wstring scheme = Utf16FromUtf8(url.substr(0, separator_location)); + HKEY key = nullptr; + if (::RegOpenKeyEx(HKEY_CLASSES_ROOT, scheme.c_str(), 0, KEY_QUERY_VALUE, + &key) == ERROR_SUCCESS) { + can_launch = ::RegQueryValueEx(key, L"URL Protocol", nullptr, nullptr, + nullptr, nullptr) == ERROR_SUCCESS; + ::RegCloseKey(key); + } + } + result->Success(EncodableValue(can_launch)); + } else { + result->NotImplemented(); + } +} + +} // namespace + +void UrlLauncherPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + UrlLauncherPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +}