[url_launcher_linux] Add Linux url_launcher plugin (#2857)
Adds url_launcher_linux, the federated implementation of url_launcher.
Not yet endorsed by url_launcher
Part of https://github.com/flutter/flutter/issues/41721
diff --git a/.ci/Dockerfile-LinuxDesktop b/.ci/Dockerfile-LinuxDesktop
index 25924d4..63e4516 100644
--- a/.ci/Dockerfile-LinuxDesktop
+++ b/.ci/Dockerfile-LinuxDesktop
@@ -21,3 +21,11 @@
RUN sudo apt-get install -y clang cmake ninja-build file pkg-config
# Install necessary libraries.
RUN sudo apt-get install -y libgtk-3-dev
+
+# Add repo for Google Chrome and install it, for url_launcher tests.
+RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
+RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list
+RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends google-chrome-stable
+# Make it the default so http: has a handler.
+RUN sudo apt-get install -y xdg-utils
+RUN xdg-settings set default-web-browser google-chrome.desktop
diff --git a/packages/url_launcher/url_launcher_linux/.gitignore b/packages/url_launcher/url_launcher_linux/.gitignore
new file mode 100644
index 0000000..53e92cc
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/.gitignore
@@ -0,0 +1,3 @@
+.packages
+.flutter-plugins
+pubspec.lock
diff --git a/packages/url_launcher/url_launcher_linux/.metadata b/packages/url_launcher/url_launcher_linux/.metadata
new file mode 100644
index 0000000..457a92a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/.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: 4b12050112afd581ddf53df848275fa681f908f3
+ channel: master
+
+project_type: plugin
diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
new file mode 100644
index 0000000..18ba82a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
@@ -0,0 +1,2 @@
+# 0.0.1
+* The initial implementation of url_launcher for Linux
diff --git a/packages/url_launcher/url_launcher_linux/LICENSE b/packages/url_launcher/url_launcher_linux/LICENSE
new file mode 100644
index 0000000..0c91662
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2020 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_linux/README.md b/packages/url_launcher/url_launcher_linux/README.md
new file mode 100644
index 0000000..9b16bdf
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/README.md
@@ -0,0 +1,22 @@
+# url_launcher_linux
+
+The Linux implementation of [`url_launcher`][1].
+
+## Usage
+
+### Import the package
+
+This package is an unendorsed Linux implementation of `url_launcher`.
+
+In order to use this now, you'll need to depend on `url_launcher_linux`.
+When this package is endorsed it will be automatically used by the `url_launcher` package and you can switch to that API.
+
+```yaml
+...
+dependencies:
+ ...
+ url_launcher_linux: ^0.0.1
+ ...
+```
+
+[1]: ../
diff --git a/packages/url_launcher/url_launcher_linux/example/.gitignore b/packages/url_launcher/url_launcher_linux/example/.gitignore
new file mode 100644
index 0000000..f3c2053
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/.gitignore
@@ -0,0 +1,44 @@
+# 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
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/url_launcher/url_launcher_linux/example/.metadata b/packages/url_launcher/url_launcher_linux/example/.metadata
new file mode 100644
index 0000000..99b1a74
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/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: 4b12050112afd581ddf53df848275fa681f908f3
+ channel: master
+
+project_type: app
diff --git a/packages/url_launcher/url_launcher_linux/example/README.md b/packages/url_launcher/url_launcher_linux/example/README.md
new file mode 100644
index 0000000..28dd90d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/README.md
@@ -0,0 +1,8 @@
+# url_launcher_example
+
+Demonstrates how to use the url_launcher plugin.
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](http://flutter.io/).
diff --git a/packages/url_launcher/url_launcher_linux/example/lib/main.dart b/packages/url_launcher/url_launcher_linux/example/lib/main.dart
new file mode 100644
index 0000000..b5cce74
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/lib/main.dart
@@ -0,0 +1,194 @@
+// 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/url_launcher.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<MyHomePage> {
+ Future<void> _launched;
+ String _phone = '';
+
+ Future<void> _launchInBrowser(String url) async {
+ if (await canLaunch(url)) {
+ await launch(
+ url,
+ forceSafariVC: false,
+ forceWebView: false,
+ headers: <String, String>{'my_header_key': 'my_header_value'},
+ );
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ Future<void> _launchInWebViewOrVC(String url) async {
+ if (await canLaunch(url)) {
+ await launch(
+ url,
+ forceSafariVC: true,
+ forceWebView: true,
+ headers: <String, String>{'my_header_key': 'my_header_value'},
+ );
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ Future<void> _launchInWebViewWithJavaScript(String url) async {
+ if (await canLaunch(url)) {
+ await launch(
+ url,
+ forceSafariVC: true,
+ forceWebView: true,
+ enableJavaScript: true,
+ );
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ Future<void> _launchInWebViewWithDomStorage(String url) async {
+ if (await canLaunch(url)) {
+ await launch(
+ url,
+ forceSafariVC: true,
+ forceWebView: true,
+ enableDomStorage: true,
+ );
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ Future<void> _launchUniversalLinkIos(String url) async {
+ if (await canLaunch(url)) {
+ final bool nativeAppLaunchSucceeded = await launch(
+ url,
+ forceSafariVC: false,
+ universalLinksOnly: true,
+ );
+ if (!nativeAppLaunchSucceeded) {
+ await launch(
+ url,
+ forceSafariVC: true,
+ );
+ }
+ }
+ }
+
+ Widget _launchStatus(BuildContext context, AsyncSnapshot<void> snapshot) {
+ if (snapshot.hasError) {
+ return Text('Error: ${snapshot.error}');
+ } else {
+ return const Text('');
+ }
+ }
+
+ Future<void> _makePhoneCall(String url) async {
+ if (await canLaunch(url)) {
+ await launch(url);
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ const String toLaunch = 'https://www.cylog.org/headers/';
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: ListView(
+ children: <Widget>[
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: TextField(
+ onChanged: (String text) => _phone = text,
+ decoration: const InputDecoration(
+ hintText: 'Input the phone number to launch')),
+ ),
+ RaisedButton(
+ onPressed: () => setState(() {
+ _launched = _makePhoneCall('tel:$_phone');
+ }),
+ child: const Text('Make phone call'),
+ ),
+ 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)),
+ RaisedButton(
+ onPressed: () => setState(() {
+ _launched = _launchInWebViewOrVC(toLaunch);
+ }),
+ child: const Text('Launch in app'),
+ ),
+ RaisedButton(
+ onPressed: () => setState(() {
+ _launched = _launchInWebViewWithJavaScript(toLaunch);
+ }),
+ child: const Text('Launch in app(JavaScript ON)'),
+ ),
+ RaisedButton(
+ onPressed: () => setState(() {
+ _launched = _launchInWebViewWithDomStorage(toLaunch);
+ }),
+ child: const Text('Launch in app(DOM storage ON)'),
+ ),
+ const Padding(padding: EdgeInsets.all(16.0)),
+ RaisedButton(
+ onPressed: () => setState(() {
+ _launched = _launchUniversalLinkIos(toLaunch);
+ }),
+ child: const Text(
+ 'Launch a universal link in a native app, fallback to Safari.(Youtube)'),
+ ),
+ const Padding(padding: EdgeInsets.all(16.0)),
+ FutureBuilder<void>(future: _launched, builder: _launchStatus),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/.gitignore b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt
new file mode 100644
index 0000000..0236a88
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_14)
+ target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+ target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+ target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+# Application build
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+ file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+ " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+ install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..94f43ff
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt
@@ -0,0 +1,86 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+ set(NEW_LIST "")
+ foreach(element ${${LIST_NAME}})
+ list(APPEND NEW_LIST "${PREFIX}${element}")
+ endforeach(element)
+ set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "fl_basic_message_channel.h"
+ "fl_binary_codec.h"
+ "fl_binary_messenger.h"
+ "fl_dart_project.h"
+ "fl_engine.h"
+ "fl_json_message_codec.h"
+ "fl_json_method_codec.h"
+ "fl_message_codec.h"
+ "fl_method_call.h"
+ "fl_method_channel.h"
+ "fl_method_codec.h"
+ "fl_method_response.h"
+ "fl_plugin_registrar.h"
+ "fl_plugin_registry.h"
+ "fl_standard_message_codec.h"
+ "fl_standard_method_codec.h"
+ "fl_string_codec.h"
+ "fl_value.h"
+ "fl_view.h"
+ "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+ PkgConfig::GTK
+ PkgConfig::GLIB
+ PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+ linux-x64 ${CMAKE_BUILD_TYPE}
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..026851f
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,13 @@
+//
+// Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+#include <url_launcher_linux/url_launcher_plugin.h>
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+ g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+ url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9bf7478
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+// Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..1fc8ed3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+ url_launcher_linux
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/main.cc b/packages/url_launcher/url_launcher_linux/example/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc
new file mode 100644
index 0000000..c2357f1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc
@@ -0,0 +1,44 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+ GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "example");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+
+ 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));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr));
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.h b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
new file mode 100644
index 0000000..a4d08f3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
@@ -0,0 +1,18 @@
+name: url_launcher_example
+description: Demonstrates how to use the url_launcher plugin.
+
+dependencies:
+ flutter:
+ sdk: flutter
+ url_launcher: any
+ url_launcher_linux:
+ path: ../
+
+dev_dependencies:
+ e2e: "^0.2.0"
+ flutter_driver:
+ sdk: flutter
+ pedantic: ^1.8.0
+
+flutter:
+ uses-material-design: true
diff --git a/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart
new file mode 100644
index 0000000..516835c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart
@@ -0,0 +1,23 @@
+// 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:e2e/e2e.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+void main() {
+ E2EWidgetsFlutterBinding.ensureInitialized();
+
+ test('canLaunch', () async {
+ expect(await canLaunch('randomstring'), false);
+
+ // Generally all devices should have some default browser.
+ expect(await canLaunch('http://flutter.dev'), true);
+
+ // Desktop will not necessarily support sms:.
+
+ // tel: and mailto: links may not be openable on every device. iOS
+ // simulators notably can't open these link types.
+ });
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart
new file mode 100644
index 0000000..1bcd0d3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart
@@ -0,0 +1,15 @@
+// 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:io';
+
+import 'package:flutter_driver/flutter_driver.dart';
+
+Future<void> main() async {
+ final FlutterDriver driver = await FlutterDriver.connect();
+ final String result =
+ await driver.requestData(null, timeout: const Duration(minutes: 1));
+ await driver.close();
+ exit(result == 'pass' ? 0 : 1);
+}
diff --git a/packages/url_launcher/url_launcher_linux/ios/.gitignore b/packages/url_launcher/url_launcher_linux/ios/.gitignore
new file mode 100644
index 0000000..aa479fd
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/ios/.gitignore
@@ -0,0 +1,37 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec
new file mode 100644
index 0000000..1359fd4
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec
@@ -0,0 +1,22 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint url_launcher_linux.podspec' to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'url_launcher_linux'
+ s.version = '0.0.1'
+ s.summary = 'url_launcher_linux iOS stub'
+ s.description = <<-DESC
+ No-op implementation of the Linux url_launcher plugin to avoid build issues on iOS
+ DESC
+ s.homepage = 'https://github.com/flutter/plugins'
+ s.license = { :type => 'BSD', :file => '../LICENSE' }
+ s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
+ s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux' }
+ s.dependency 'Flutter'
+ s.platform = :ios, '8.0'
+
+ # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
+ s.swift_version = '5.0'
+end
diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
new file mode 100644
index 0000000..18f7af1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
@@ -0,0 +1,3 @@
+// The url_launcher_platform_interface defaults to MethodChannelUrlLauncher
+// as its instance, which is all the Linux implementation needs. This file
+// is here to silence warnings when publishing to pub.
diff --git a/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt
new file mode 100644
index 0000000..1403d0c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "url_launcher_linux")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+ "url_launcher_plugin.cc"
+)
+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)
diff --git a/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h
new file mode 100644
index 0000000..efcfe62
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h
@@ -0,0 +1,31 @@
+// Copyright 2020 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_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
+#define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
+
+// A plugin to launch URLs.
+
+#include <flutter_linux/flutter_linux.h>
+
+G_BEGIN_DECLS
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
+#else
+#define FLUTTER_PLUGIN_EXPORT
+#endif
+
+G_DECLARE_FINAL_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, FL,
+ URL_LAUNCHER_PLUGIN, GObject)
+
+FLUTTER_PLUGIN_EXPORT FlUrlLauncherPlugin* fl_url_launcher_plugin_new(
+ FlPluginRegistrar* registrar);
+
+FLUTTER_PLUGIN_EXPORT void url_launcher_plugin_register_with_registrar(
+ FlPluginRegistrar* registrar);
+
+G_END_DECLS
+
+#endif // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
new file mode 100644
index 0000000..592bb96
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
@@ -0,0 +1,148 @@
+// Copyright 2020 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_linux/url_launcher_plugin.h"
+
+#include <flutter_linux/flutter_linux.h>
+#include <gtk/gtk.h>
+
+// See url_launcher_channel.dart for documentation.
+const char kChannelName[] = "plugins.flutter.io/url_launcher";
+const char kBadArgumentsError[] = "Bad Arguments";
+const char kLaunchError[] = "Launch Error";
+const char kCanLaunchMethod[] = "canLaunch";
+const char kLaunchMethod[] = "launch";
+const char kUrlKey[] = "url";
+
+struct _FlUrlLauncherPlugin {
+ GObject parent_instance;
+
+ FlPluginRegistrar* registrar;
+
+ // Connection to Flutter engine.
+ FlMethodChannel* channel;
+};
+
+G_DEFINE_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, g_object_get_type())
+
+// Gets the URL from the arguments or generates an error.
+static gchar* get_url(FlValue* args, GError** error) {
+ if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
+ g_set_error(error, 0, 0, "Argument map missing or malformed");
+ return nullptr;
+ }
+ FlValue* url_value = fl_value_lookup_string(args, kUrlKey);
+ if (url_value == nullptr) {
+ g_set_error(error, 0, 0, "Missing URL");
+ return nullptr;
+ }
+
+ return g_strdup(fl_value_get_string(url_value));
+}
+
+// Called to check if a URL can be launched.
+static FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) {
+ g_autoptr(GError) error = nullptr;
+ g_autofree gchar* url = get_url(args, &error);
+ if (url == nullptr) {
+ return FL_METHOD_RESPONSE(fl_method_error_response_new(
+ kBadArgumentsError, error->message, nullptr));
+ }
+
+ gboolean is_launchable = FALSE;
+ g_autofree gchar* scheme = g_uri_parse_scheme(url);
+ if (scheme != nullptr) {
+ g_autoptr(GAppInfo) app_info =
+ g_app_info_get_default_for_uri_scheme(scheme);
+ is_launchable = app_info != nullptr;
+ }
+
+ g_autoptr(FlValue) result = fl_value_new_bool(is_launchable);
+ return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+}
+
+// Called when a URL should launch.
+static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) {
+ g_autoptr(GError) error = nullptr;
+ g_autofree gchar* url = get_url(args, &error);
+ if (url == nullptr) {
+ return FL_METHOD_RESPONSE(fl_method_error_response_new(
+ kBadArgumentsError, error->message, nullptr));
+ }
+
+ FlView* view = fl_plugin_registrar_get_view(self->registrar);
+ gboolean launched;
+ if (view != nullptr) {
+ GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
+ launched = gtk_show_uri_on_window(window, url, GDK_CURRENT_TIME, &error);
+ } else {
+ launched = g_app_info_launch_default_for_uri(url, nullptr, &error);
+ }
+ if (!launched) {
+ g_autofree gchar* message =
+ g_strdup_printf("Failed to launch URL: %s", error->message);
+ return FL_METHOD_RESPONSE(
+ fl_method_error_response_new(kLaunchError, message, nullptr));
+ }
+
+ g_autoptr(FlValue) result = fl_value_new_bool(TRUE);
+ return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+}
+
+// Called when a method call is received from Flutter.
+static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
+ gpointer user_data) {
+ FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data);
+
+ const gchar* method = fl_method_call_get_name(method_call);
+ FlValue* args = fl_method_call_get_args(method_call);
+
+ g_autoptr(FlMethodResponse) response = nullptr;
+ if (strcmp(method, kCanLaunchMethod) == 0)
+ response = can_launch(self, args);
+ else if (strcmp(method, kLaunchMethod) == 0)
+ response = launch(self, args);
+ else
+ response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
+
+ g_autoptr(GError) error = nullptr;
+ if (!fl_method_call_respond(method_call, response, &error))
+ g_warning("Failed to send method call response: %s", error->message);
+}
+
+static void fl_url_launcher_plugin_dispose(GObject* object) {
+ FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(object);
+
+ g_clear_object(&self->registrar);
+ g_clear_object(&self->channel);
+
+ G_OBJECT_CLASS(fl_url_launcher_plugin_parent_class)->dispose(object);
+}
+
+static void fl_url_launcher_plugin_class_init(FlUrlLauncherPluginClass* klass) {
+ G_OBJECT_CLASS(klass)->dispose = fl_url_launcher_plugin_dispose;
+}
+
+FlUrlLauncherPlugin* fl_url_launcher_plugin_new(FlPluginRegistrar* registrar) {
+ FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(
+ g_object_new(fl_url_launcher_plugin_get_type(), nullptr));
+
+ self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar));
+
+ g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+ self->channel =
+ fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
+ kChannelName, FL_METHOD_CODEC(codec));
+ fl_method_channel_set_method_call_handler(self->channel, method_call_cb,
+ g_object_ref(self), g_object_unref);
+
+ return self;
+}
+
+static void fl_url_launcher_plugin_init(FlUrlLauncherPlugin* self) {}
+
+void url_launcher_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
+ FlUrlLauncherPlugin* plugin = fl_url_launcher_plugin_new(registrar);
+ g_object_unref(plugin);
+}
diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml
new file mode 100644
index 0000000..2662176
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml
@@ -0,0 +1,19 @@
+name: url_launcher_linux
+description: Linux implementation of the url_launcher plugin.
+version: 0.0.1
+homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux
+
+flutter:
+ plugin:
+ platforms:
+ linux:
+ pluginClass: UrlLauncherPlugin
+
+environment:
+ sdk: ">=2.1.0 <3.0.0"
+ flutter: ">=1.12.8 <2.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+