Forráskód Böngészése

Enable Linux build

1. BUILD_ON_LINUX.md is instruction of building Linux version
2. Update toml files in scripts/makefile/ and /Makefile.toml to support Linux

Building Windows/Linux release version is verified
Didn't get chance to verify building macos version
Alex to 3 éve
szülő
commit
d965d26e60
36 módosított fájl, 1234 hozzáadás és 9 törlés
  1. 19 2
      Makefile.toml
  2. 1 0
      app_flowy/linux/.gitignore
  3. 120 0
      app_flowy/linux/CMakeLists.txt
  4. 88 0
      app_flowy/linux/flutter/CMakeLists.txt
  5. 15 0
      app_flowy/linux/flutter/dart_ffi/binding.h
  6. 23 0
      app_flowy/linux/flutter/generated_plugin_registrant.cc
  7. 15 0
      app_flowy/linux/flutter/generated_plugin_registrant.h
  8. 18 0
      app_flowy/linux/flutter/generated_plugins.cmake
  9. 6 0
      app_flowy/linux/main.cc
  10. 104 0
      app_flowy/linux/my_application.cc
  11. 18 0
      app_flowy/linux/my_application.h
  12. 1 0
      app_flowy/packages/flowy_infra_ui/example/linux/.gitignore
  13. 116 0
      app_flowy/packages/flowy_infra_ui/example/linux/CMakeLists.txt
  14. 87 0
      app_flowy/packages/flowy_infra_ui/example/linux/flutter/CMakeLists.txt
  15. 15 0
      app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugin_registrant.cc
  16. 15 0
      app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugin_registrant.h
  17. 16 0
      app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugins.cmake
  18. 6 0
      app_flowy/packages/flowy_infra_ui/example/linux/main.cc
  19. 104 0
      app_flowy/packages/flowy_infra_ui/example/linux/my_application.cc
  20. 18 0
      app_flowy/packages/flowy_infra_ui/example/linux/my_application.h
  21. 4 0
      app_flowy/packages/flowy_infra_ui/ios/Classes/FlowyInfraUiPlugin.h
  22. 15 0
      app_flowy/packages/flowy_infra_ui/ios/Classes/FlowyInfraUiPlugin.m
  23. 14 0
      app_flowy/packages/flowy_infra_ui/ios/Classes/SwiftFlowyInfraUiPlugin.swift
  24. 26 0
      app_flowy/packages/flowy_infra_ui/linux/CMakeLists.txt
  25. 70 0
      app_flowy/packages/flowy_infra_ui/linux/flowy_infra_u_i_plugin.cc
  26. 70 0
      app_flowy/packages/flowy_infra_ui/linux/flowy_infra_ui_plugin.cc
  27. 26 0
      app_flowy/packages/flowy_infra_ui/linux/include/flowy_infra_ui/flowy_infra_u_i_plugin.h
  28. 26 0
      app_flowy/packages/flowy_infra_ui/linux/include/flowy_infra_ui/flowy_infra_ui_plugin.h
  29. 6 3
      app_flowy/packages/flowy_infra_ui/pubspec.yaml
  30. 1 0
      app_flowy/packages/flowy_sdk/lib/ffi.dart
  31. 15 0
      app_flowy/packages/flowy_sdk/linux/Classes/binding.h
  32. 88 0
      doc/BUILD_ON_LINUX.md
  33. 7 3
      doc/BUILD_ON_WNIDOWS.md
  34. 25 1
      scripts/makefile/desktop.toml
  35. 2 0
      scripts/makefile/env.toml
  36. 34 0
      scripts/makefile/flutter.toml

+ 19 - 2
Makefile.toml

@@ -60,6 +60,22 @@ PRODUCT_EXT = "exe"
 CRATE_TYPE = "cdylib"
 SDK_EXT = "dll"
 
+[env.development-linux-x86]
+TARGET_OS = "linux"
+RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
+BUILD_FLAG = "debug"
+CRATE_TYPE = "cdylib"
+FLUTTER_OUTPUT_DIR = "Debug"
+SDK_EXT = "so"
+
+[env.production-linux-x86]
+BUILD_FLAG = "release"
+TARGET_OS = "linux"
+RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
+CRATE_TYPE = "cdylib"
+FLUTTER_OUTPUT_DIR = "Release"
+SDK_EXT = "so"
+
 [tasks.echo_env]
 script = [
     '''
@@ -72,6 +88,7 @@ script = [
     echo ${platforms}
     '''
 ]
+script_runner = "@duckscript"
 
 [env.production-ios]
 BUILD_FLAG = "release"
@@ -87,7 +104,7 @@ private = true
 script = [
     """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
-      val = replace ${toml} "cdylib" ${CRATE_TYPE}
+      val = replace ${toml} "staticlib" ${CRATE_TYPE}
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
       assert ${result}
       """,
@@ -99,7 +116,7 @@ private = true
 script = [
     """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
-      val = replace ${toml} ${CRATE_TYPE} "cdylib"
+      val = replace ${toml} ${CRATE_TYPE} "staticlib"
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
       assert ${result}
       """,

+ 1 - 0
app_flowy/linux/.gitignore

@@ -0,0 +1 @@
+flutter/ephemeral

+ 120 - 0
app_flowy/linux/CMakeLists.txt

@@ -0,0 +1,120 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "app_flowy")
+set(APPLICATION_ID "com.example.app_flowy")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# 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)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# 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)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# 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(DART_FFI_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+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 "${DART_FFI_DLL}" DESTINATION "${DART_FFI_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()

+ 88 - 0
app_flowy/linux/flutter/CMakeLists.txt

@@ -0,0 +1,88 @@
+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(DART_FFI_DLL "${CMAKE_CURRENT_SOURCE_DIR}/dart_ffi/libdart_ffi.so" 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"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)

+ 15 - 0
app_flowy/linux/flutter/dart_ffi/binding.h

@@ -0,0 +1,15 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+int64_t init_sdk(char *path);
+
+void async_command(int64_t port, const uint8_t *input, uintptr_t len);
+
+const uint8_t *sync_command(const uint8_t *input, uintptr_t len);
+
+int32_t set_stream_port(int64_t port);
+
+void link_me_please(void);

+ 23 - 0
app_flowy/linux/flutter/generated_plugin_registrant.cc

@@ -0,0 +1,23 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+#include <flowy_infra_ui/flowy_infra_u_i_plugin.h>
+#include <url_launcher_linux/url_launcher_plugin.h>
+#include <window_size/window_size_plugin.h>
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin");
+  flowy_infra_u_i_plugin_register_with_registrar(flowy_infra_ui_registrar);
+  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);
+  g_autoptr(FlPluginRegistrar) window_size_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
+  window_size_plugin_register_with_registrar(window_size_registrar);
+}

+ 15 - 0
app_flowy/linux/flutter/generated_plugin_registrant.h

@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#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_

+ 18 - 0
app_flowy/linux/flutter/generated_plugins.cmake

@@ -0,0 +1,18 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  flowy_infra_ui
+  url_launcher_linux
+  window_size
+)
+
+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)

+ 6 - 0
app_flowy/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);
+}

+ 104 - 0
app_flowy/linux/my_application.cc

@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+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;
+#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;
+    }
+  }
+#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, "app_flowy");
+    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, "app_flowy");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  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);
+
+  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));
+}
+
+// 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;
+}
+
+// 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_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) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID,
+                                     "flags", G_APPLICATION_NON_UNIQUE,
+                                     nullptr));
+}

+ 18 - 0
app_flowy/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_

+ 1 - 0
app_flowy/packages/flowy_infra_ui/example/linux/.gitignore

@@ -0,0 +1 @@
+flutter/ephemeral

+ 116 - 0
app_flowy/packages/flowy_infra_ui/example/linux/CMakeLists.txt

@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "flowy_infra_ui_example")
+set(APPLICATION_ID "com.example.flowy_infra_ui")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# 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)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# 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)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# 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()

+ 87 - 0
app_flowy/packages/flowy_infra_ui/example/linux/flutter/CMakeLists.txt

@@ -0,0 +1,87 @@
+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"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)

+ 15 - 0
app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugin_registrant.cc

@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+#include <flowy_infra_ui/flowy_infra_ui_plugin.h>
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin");
+  flowy_infra_ui_plugin_register_with_registrar(flowy_infra_ui_registrar);
+}

+ 15 - 0
app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugin_registrant.h

@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#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_

+ 16 - 0
app_flowy/packages/flowy_infra_ui/example/linux/flutter/generated_plugins.cmake

@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  flowy_infra_ui
+)
+
+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)

+ 6 - 0
app_flowy/packages/flowy_infra_ui/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);
+}

+ 104 - 0
app_flowy/packages/flowy_infra_ui/example/linux/my_application.cc

@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+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;
+#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;
+    }
+  }
+#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, "flowy_infra_ui_example");
+    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, "flowy_infra_ui_example");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  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);
+
+  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));
+}
+
+// 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;
+}
+
+// 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_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) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID,
+                                     "flags", G_APPLICATION_NON_UNIQUE,
+                                     nullptr));
+}

+ 18 - 0
app_flowy/packages/flowy_infra_ui/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_

+ 4 - 0
app_flowy/packages/flowy_infra_ui/ios/Classes/FlowyInfraUiPlugin.h

@@ -0,0 +1,4 @@
+#import <Flutter/Flutter.h>
+
+@interface FlowyInfraUiPlugin : NSObject<FlutterPlugin>
+@end

+ 15 - 0
app_flowy/packages/flowy_infra_ui/ios/Classes/FlowyInfraUiPlugin.m

@@ -0,0 +1,15 @@
+#import "FlowyInfraUiPlugin.h"
+#if __has_include(<flowy_infra_ui/flowy_infra_ui-Swift.h>)
+#import <flowy_infra_ui/flowy_infra_ui-Swift.h>
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "flowy_infra_ui-Swift.h"
+#endif
+
+@implementation FlowyInfraUiPlugin
++ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
+  [SwiftFlowyInfraUiPlugin registerWithRegistrar:registrar];
+}
+@end

+ 14 - 0
app_flowy/packages/flowy_infra_ui/ios/Classes/SwiftFlowyInfraUiPlugin.swift

@@ -0,0 +1,14 @@
+import Flutter
+import UIKit
+
+public class SwiftFlowyInfraUiPlugin: NSObject, FlutterPlugin {
+  public static func register(with registrar: FlutterPluginRegistrar) {
+    let channel = FlutterMethodChannel(name: "flowy_infra_ui", binaryMessenger: registrar.messenger())
+    let instance = SwiftFlowyInfraUiPlugin()
+    registrar.addMethodCallDelegate(instance, channel: channel)
+  }
+
+  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+    result("iOS " + UIDevice.current.systemVersion)
+  }
+}

+ 26 - 0
app_flowy/packages/flowy_infra_ui/linux/CMakeLists.txt

@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "flowy_infra_ui")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+# This value is used when generating builds using this plugin, so it must
+# not be changed
+set(PLUGIN_NAME "flowy_infra_ui_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+  "flowy_infra_ui_plugin.cc"
+  "flowy_infra_u_i_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)
+
+# List of absolute paths to libraries that should be bundled with the plugin
+set(flowy_infra_ui_bundled_libraries
+  ""
+  PARENT_SCOPE
+)

+ 70 - 0
app_flowy/packages/flowy_infra_ui/linux/flowy_infra_u_i_plugin.cc

@@ -0,0 +1,70 @@
+#include "include/flowy_infra_ui/flowy_infra_u_i_plugin.h"
+
+#include <flutter_linux/flutter_linux.h>
+#include <gtk/gtk.h>
+#include <sys/utsname.h>
+
+#include <cstring>
+
+#define FLOWY_INFRA_U_I_PLUGIN(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), flowy_infra_u_i_plugin_get_type(), \
+                              FlowyInfraUiPlugin))
+
+struct _FlowyInfraUiPlugin {
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE(FlowyInfraUiPlugin, flowy_infra_u_i_plugin, g_object_get_type())
+
+// Called when a method call is received from Flutter.
+static void flowy_infra_u_i_plugin_handle_method_call(
+    FlowyInfraUiPlugin* self,
+    FlMethodCall* method_call) {
+  g_autoptr(FlMethodResponse) response = nullptr;
+
+  const gchar* method = fl_method_call_get_name(method_call);
+
+  if (strcmp(method, "getPlatformVersion") == 0) {
+    struct utsname uname_data = {};
+    uname(&uname_data);
+    g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
+    g_autoptr(FlValue) result = fl_value_new_string(version);
+    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+  } else {
+    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
+  }
+
+  fl_method_call_respond(method_call, response, nullptr);
+}
+
+static void flowy_infra_u_i_plugin_dispose(GObject* object) {
+  G_OBJECT_CLASS(flowy_infra_u_i_plugin_parent_class)->dispose(object);
+}
+
+static void flowy_infra_u_i_plugin_class_init(FlowyInfraUiPluginClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = flowy_infra_u_i_plugin_dispose;
+}
+
+static void flowy_infra_u_i_plugin_init(FlowyInfraUiPlugin* self) {}
+
+static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
+                           gpointer user_data) {
+  FlowyInfraUiPlugin* plugin = FLOWY_INFRA_U_I_PLUGIN(user_data);
+  flowy_infra_u_i_plugin_handle_method_call(plugin, method_call);
+}
+
+void flowy_infra_u_i_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
+  FlowyInfraUiPlugin* plugin = FLOWY_INFRA_U_I_PLUGIN(
+      g_object_new(flowy_infra_u_i_plugin_get_type(), nullptr));
+
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel =
+      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
+                            "flowy_infra_u_i",
+                            FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(channel, method_call_cb,
+                                            g_object_ref(plugin),
+                                            g_object_unref);
+
+  g_object_unref(plugin);
+}

+ 70 - 0
app_flowy/packages/flowy_infra_ui/linux/flowy_infra_ui_plugin.cc

@@ -0,0 +1,70 @@
+#include "include/flowy_infra_ui/flowy_infra_ui_plugin.h"
+
+#include <flutter_linux/flutter_linux.h>
+#include <gtk/gtk.h>
+#include <sys/utsname.h>
+
+#include <cstring>
+
+#define FLOWY_INFRA_UI_PLUGIN(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), flowy_infra_ui_plugin_get_type(), \
+                              FlowyInfraUiPlugin))
+
+struct _FlowyInfraUiPlugin {
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE(FlowyInfraUiPlugin, flowy_infra_ui_plugin, g_object_get_type())
+
+// Called when a method call is received from Flutter.
+static void flowy_infra_ui_plugin_handle_method_call(
+    FlowyInfraUiPlugin* self,
+    FlMethodCall* method_call) {
+  g_autoptr(FlMethodResponse) response = nullptr;
+
+  const gchar* method = fl_method_call_get_name(method_call);
+
+  if (strcmp(method, "getPlatformVersion") == 0) {
+    struct utsname uname_data = {};
+    uname(&uname_data);
+    g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
+    g_autoptr(FlValue) result = fl_value_new_string(version);
+    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+  } else {
+    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
+  }
+
+  fl_method_call_respond(method_call, response, nullptr);
+}
+
+static void flowy_infra_ui_plugin_dispose(GObject* object) {
+  G_OBJECT_CLASS(flowy_infra_ui_plugin_parent_class)->dispose(object);
+}
+
+static void flowy_infra_ui_plugin_class_init(FlowyInfraUiPluginClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = flowy_infra_ui_plugin_dispose;
+}
+
+static void flowy_infra_ui_plugin_init(FlowyInfraUiPlugin* self) {}
+
+static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
+                           gpointer user_data) {
+  FlowyInfraUiPlugin* plugin = FLOWY_INFRA_UI_PLUGIN(user_data);
+  flowy_infra_ui_plugin_handle_method_call(plugin, method_call);
+}
+
+void flowy_infra_ui_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
+  FlowyInfraUiPlugin* plugin = FLOWY_INFRA_UI_PLUGIN(
+      g_object_new(flowy_infra_ui_plugin_get_type(), nullptr));
+
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel =
+      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
+                            "flowy_infra_ui",
+                            FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(channel, method_call_cb,
+                                            g_object_ref(plugin),
+                                            g_object_unref);
+
+  g_object_unref(plugin);
+}

+ 26 - 0
app_flowy/packages/flowy_infra_ui/linux/include/flowy_infra_ui/flowy_infra_u_i_plugin.h

@@ -0,0 +1,26 @@
+#ifndef FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_
+#define FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_
+
+#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
+
+typedef struct _FlowyInfraUiPlugin FlowyInfraUiPlugin;
+typedef struct {
+  GObjectClass parent_class;
+} FlowyInfraUiPluginClass;
+
+FLUTTER_PLUGIN_EXPORT GType flowy_infra_u_i_plugin_get_type();
+
+FLUTTER_PLUGIN_EXPORT void flowy_infra_u_i_plugin_register_with_registrar(
+    FlPluginRegistrar* registrar);
+
+G_END_DECLS
+
+#endif  // FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_

+ 26 - 0
app_flowy/packages/flowy_infra_ui/linux/include/flowy_infra_ui/flowy_infra_ui_plugin.h

@@ -0,0 +1,26 @@
+#ifndef FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_
+#define FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_
+
+#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
+
+typedef struct _FlowyInfraUiPlugin FlowyInfraUiPlugin;
+typedef struct {
+  GObjectClass parent_class;
+} FlowyInfraUiPluginClass;
+
+FLUTTER_PLUGIN_EXPORT GType flowy_infra_ui_plugin_get_type();
+
+FLUTTER_PLUGIN_EXPORT void flowy_infra_ui_plugin_register_with_registrar(
+    FlPluginRegistrar* registrar);
+
+G_END_DECLS
+
+#endif  // FLUTTER_PLUGIN_FLOWY_INFRA_UI_PLUGIN_H_

+ 6 - 3
app_flowy/packages/flowy_infra_ui/pubspec.yaml

@@ -39,14 +39,17 @@ dev_dependencies:
 flutter:
   plugin:
     platforms:
-      android:
-        package: com.example.flowy_infra_ui
-        pluginClass: FlowyInfraUIPlugin
+      # TODO: uncomment android part will fail the Linux build process, will resolve later
+      # android:
+      #   package: com.example.flowy_infra_ui
+      #   pluginClass: FlowyInfraUIPlugin
       ios:
         pluginClass: FlowyInfraUIPlugin
       macos:
         pluginClass: FlowyInfraUIPlugin
       windows:
         pluginClass: FlowyInfraUIPlugin
+      linux:
+        pluginClass: FlowyInfraUIPlugin
       web:
         default_package: flowy_infra_ui_web

+ 1 - 0
app_flowy/packages/flowy_sdk/lib/ffi.dart

@@ -16,6 +16,7 @@ DynamicLibrary _open() {
   if (Platform.isMacOS) return DynamicLibrary.executable();
   if (Platform.isIOS) return DynamicLibrary.executable();
   if (Platform.isWindows) return DynamicLibrary.open('dart_ffi.dll');
+  if (Platform.isLinux) return DynamicLibrary.open('libdart_ffi.so');
   throw UnsupportedError('This platform is not supported.');
 }
 

+ 15 - 0
app_flowy/packages/flowy_sdk/linux/Classes/binding.h

@@ -0,0 +1,15 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+int64_t init_sdk(char *path);
+
+void async_command(int64_t port, const uint8_t *input, uintptr_t len);
+
+const uint8_t *sync_command(const uint8_t *input, uintptr_t len);
+
+int32_t set_stream_port(int64_t port);
+
+void link_me_please(void);

+ 88 - 0
doc/BUILD_ON_LINUX.md

@@ -0,0 +1,88 @@
+## How to build on Linux, please follow these simple steps.
+
+**Step 1:**
+
+```shell
+git clone https://github.com/AppFlowy-IO/appflowy.git
+```
+
+**Step 2:**
+
+Note: Follow steps are verified on Ubuntu 20.04
+
+1. Install pre-requests
+```shell
+sudo apt-get install build-essential
+sudo apt-get install libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
+```
+2. Install brew on Linux (TODO: rust installation should not depend on brew)
+```shell
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/user/.profile
+eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
+```
+3. Install flutter according to https://docs.flutter.dev/get-started/install/linux
+```shell
+git clone https://github.com/flutter/flutter.git
+cd flutter
+echo "export PATH=\$PATH:"`pwd`"/bin" >> ~/.profile
+export PATH="$PATH:`pwd`/flutter/bin"
+flutter channel dev
+flutter config --enable-linux-desktop
+```
+4. Fix problem reported by flutter doctor
+```shell
+flutter doctor
+# install Android toolchian (optional)
+# install Chrome (optional)
+```
+5. Install rust
+```shell
+# TODO: replace by rust offical installation step
+brew install rustup-init
+source $HOME/.cargo/env
+rustup toolchain install nightly
+rustup default nightly
+```
+5. Install cargo make
+```shell
+cd appflowy
+cargo install --force cargo-make
+```
+6. Install duckscript
+```shell
+cargo install --force duckscript_cli
+```
+7. Check pre-request
+```shell
+cargo make flowy_dev
+```
+8. Generate protobuf for dart (optional)
+```shell
+cargo make -p development-linux-x86 pb
+```
+9. Build flowy-sdk-dev (dart-ffi)
+```shell
+# TODO: for development
+
+# for production
+cargo make --profile production-linux-x86 flowy-sdk-release
+```
+10. Build app_flowy
+```shell
+# TODO: for development
+
+# for production
+cargo make -p production-linux-x86 appflowy-linux
+### tips
+# run Linux GUI application through x11 on windows (use MobaXterm)
+# export DISPLAY=localhost:10
+# cd app_flowy/product/0.0.2/linux/Release/AppFlowy
+# ./app_flowy
+```
+
+**Step 3:**  Server side application
+
+Note: You can launch postgresql server by using docker container
+
+TBD

+ 7 - 3
doc/BUILD_ON_WNIDOWS.md

@@ -40,13 +40,17 @@ cargo make flowy_dev
 ```shell
 cargo make -p development-windows pb
 ```
-10. Build flowy-sdk-dev (dart-ffi)
+10. Build flowy-sdk (dart-ffi)
 ```shell
-cargo make --profile development-windows flowy-sdk-dev
+# TODO: for development
+# for production
+cargo make --profile production-desktop-windows-x86 flowy-sdk-release
 ```
 11. Build app_flowy
 ```shell
-cargo make -p development-windows appflowy-windows
+# TODO: for development
+# for production
+cargo make -p production-desktop-windows-x86 appflowy-windows
 ```
 
 **Step 3:**  Server side application

+ 25 - 1
scripts/makefile/desktop.toml

@@ -11,6 +11,7 @@ condition = { env_set = [ "BUILD_FLAG", "RUST_COMPILE_TARGET", "CRATE_TYPE", "TA
 [tasks.flowy-sdk-dev]
 mac_alias = "flowy-sdk-dev-macos"
 windows_alias = "flowy-sdk-dev-windows"
+linux_alias = "flowy-sdk-dev-linux"
 
 [tasks.flowy-sdk-dev-macos]
 category = "Build"
@@ -22,6 +23,11 @@ category = "Build"
 dependencies = ["env_check"]
 run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type"] }
 
+[tasks.flowy-sdk-dev-linux]
+category = "Build"
+dependencies = ["env_check"]
+run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type"] }
+
 #
 [tasks.sdk-build]
 private = true
@@ -56,6 +62,7 @@ script_runner = "@shell"
 [tasks.post-desktop]
 mac_alias = "post-desktop-macos"
 windows_alias = "post-desktop-windows"
+linux_alias = "post-desktop-linux"
 
 [tasks.post-desktop-macos]
 private = true
@@ -90,6 +97,24 @@ script = [
 ]
 script_runner = "@duckscript"
 
+[tasks.post-desktop-linux]
+private = true
+script = [
+  """
+    echo "🚀 🚀 🚀  Flowy-SDK(linux) build success"
+    dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/linux/flutter/dart_ffi
+
+    # copy dll
+    cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/lib${CARGO_MAKE_CRATE_FS_NAME}.${SDK_EXT} \
+    ${dart_ffi_dir}/lib${CARGO_MAKE_CRATE_FS_NAME}.${SDK_EXT}
+
+    # copy binding.h
+    cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/binding.h \
+    ${dart_ffi_dir}/binding.h
+  """,
+]
+script_runner = "@duckscript"
+
 #
 [tasks.copy-to-sys-tmpdir]
 private = true
@@ -103,4 +128,3 @@ script = [
   """,
 ]
 script_runner = "@duckscript"
-

+ 2 - 0
scripts/makefile/env.toml

@@ -70,6 +70,8 @@ rustup target add x86_64-apple-darwin
 rustup target add aarch64-apple-ios
 rustup target add aarch64-apple-darwin
 rustup target add x86_64-pc-windows-msvc
+rustup target add x86_64-pc-windows-msvc
+rustup target add x86_64-unknown-linux-gnu
 """
 
 [tasks.install_prerequests]

+ 34 - 0
scripts/makefile/flutter.toml

@@ -1,6 +1,7 @@
 [tasks.appflowy]
 mac_alias = "appflowy-macos"
 windows_alias = "appflowy-windows"
+linux_alias = "appflowy-linux"
 
 [tasks.appflowy-macos]
 dependencies = ["flowy-sdk-release"]
@@ -11,9 +12,15 @@ script_runner = "@shell"
 dependencies = ["flowy-sdk-release"]
 run_task = { name = ["flutter-build", "copy-to-product"] }
 
+[tasks.appflowy-linux]
+dependencies = ["flowy-sdk-release"]
+run_task = { name = ["flutter-build", "copy-to-product"] }
+script_runner = "@shell"
+
 [tasks.copy-to-product]
 mac_alias = "copy-to-product-macos"
 windows_alias = "copy-to-product-windows"
+linux_alias = "copy-to-product-linux"
 
 [tasks.copy-to-product-macos]
 script = [
@@ -32,6 +39,22 @@ script = [
 ]
 script_runner = "@shell"
 
+[tasks.copy-to-product-linux]
+script = [
+  """
+  product_path=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/product/${VERSION}
+  output_path=${product_path}/${TARGET_OS}/${FLUTTER_OUTPUT_DIR}
+  if [ -d "${output_path}" ]; then
+    rm -rf ${output_path}/
+  fi
+  mkdir -p ${output_path}
+
+  product=${PRODUCT_NAME}
+  cp -R ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/build/${TARGET_OS}/x64/${BUILD_FLAG}/bundle \
+  ${output_path}/${product}
+  """,
+]
+script_runner = "@shell"
 
 [tasks.copy-to-product-windows]
 script = [
@@ -62,6 +85,17 @@ script = [
 ]
 script_runner = "@shell"
 
+[tasks.flutter-build.linux]
+script = [
+  """
+  cd app_flowy/
+  flutter clean
+  flutter pub get
+  flutter build ${TARGET_OS} --${BUILD_FLAG}
+  """,
+]
+script_runner = "@shell"
+
 [tasks.flutter-build.windows]
 script = [
   """