diff --git a/.gitignore b/.gitignore index a83095b8..adce1e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -out .vscode -build.sh -build_all.sh compile_commands.json .clang-format -build \ No newline at end of file +out +build +build_debug +build_release \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e5d556e..7c201efc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ if(NOT FLUTTER_EMBEDDER_HEADER) if(NOT FLUTTER_ENGINE_SHA) if(NOT CHANNEL) - set(CHANNEL "stable" CACHE STRING "Choose the channel, options are: master, dev, beta, stable" FORCE) - message(STATUS "Flutter Channel not set, defaulting to stable") + set(CHANNEL "stable" CACHE STRING "The flutter channel to be used for downloading the flutter_embedder.h header file. Choose: master, dev, beta, stable" FORCE) + message(STATUS "Flutter Channel not set, defaulting to stable") endif() message(STATUS "Flutter Channel ........ ${CHANNEL}") @@ -56,11 +56,11 @@ if(NOT FLUTTER_EMBEDDER_HEADER) FetchContent_GetProperties(engine-version) if(NOT engine-version_POPULATED) - FetchContent_Populate(engine-version) - file(READ ${CMAKE_BINARY_DIR}/engine.version FLUTTER_ENGINE_SHA) - string(REPLACE "\n" "" FLUTTER_ENGINE_SHA ${FLUTTER_ENGINE_SHA}) + FetchContent_Populate(engine-version) + file(READ ${CMAKE_BINARY_DIR}/engine.version FLUTTER_ENGINE_SHA) + string(REPLACE "\n" "" FLUTTER_ENGINE_SHA ${FLUTTER_ENGINE_SHA}) else() - MESSAGE(FATAL "Unable to determine engine-version, please override FLUTTER_ENGINE_SHA") + MESSAGE(FATAL "Unable to determine engine-version, please override FLUTTER_ENGINE_SHA") endif() endif() @@ -72,12 +72,12 @@ if(NOT FLUTTER_EMBEDDER_HEADER) # Download and setup the flutter engine library header. if(NOT EXISTS ${FLUTTER_EMBEDDER_HEADER}) file(DOWNLOAD - https://github.com/flutter/engine/blob/${FLUTTER_ENGINE_SHA}/shell/platform/embedder/embedder.h + https://raw.githubusercontent.com/flutter/engine/${FLUTTER_ENGINE_SHA}/shell/platform/embedder/embedder.h ${FLUTTER_EMBEDDER_HEADER} ) endif() else() - message(STATUS "Engine ................. ${FLUTTER_ENGINE_LIBRARY}") + message(STATUS "Flutter Header ......... ${FLUTTER_EMBEDDER_HEADER}") endif() include(ExternalProject) @@ -93,7 +93,12 @@ pkg_check_modules(GLESV2 REQUIRED glesv2) pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) pkg_check_modules(LIBINPUT REQUIRED libinput) pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) -pkg_check_modules(LIBUDEV libudev) +pkg_check_modules(LIBUDEV REQUIRED libudev) + +set(BUILD_TEXT_INPUT_PLUGIN ON CACHE BOOL "Include the text input plugin in the finished binary. Enables text input (to flutter text fields, for example) via attached keyboards.") +set(BUILD_RAW_KEYBOARD_PLUGIN ON CACHE BOOL "Include the raw keyboard plugin in the finished binary. Enables raw keycode listening in flutter via the flutter RawKeyboard interface.") +set(BUILD_TEST_PLUGIN OFF CACHE BOOL "Include the test plugin in the finished binary. Allows testing platform channel communication.") +set(BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN ON CACHE BOOL "Include the omxplayer_video_player plugin in the finished binary. Allows for hardware accelerated video playback in flutter using omxplayer.") set(FLUTTER_PI_SRC src/flutter-pi.c @@ -106,15 +111,19 @@ set(FLUTTER_PI_SRC src/cursor.c src/keyboard.c src/plugins/services.c - src/plugins/testplugin.c - src/plugins/text_input.c - src/plugins/raw_keyboard.c - src/plugins/omxplayer_video_player.c ) -if (NOT LIBUDEV_FOUND) - message(STATUS "Could not find libudev.so and libudev development headers. flutter-pi will be built without udev (hotplugging) support. To install, execute 'sudo apt install libudev-dev'") - add_compile_options(-DBUILD_WITHOUT_UDEV_SUPPORT) +if (BUILD_TEXT_INPUT_PLUGIN) + list(APPEND FLUTTER_PI_SRC src/plugins/text_input.c) +endif() +if (BUILD_RAW_KEYBOARD_PLUGIN) + list(APPEND FLUTTER_PI_SRC src/plugins/raw_keyboard.c) +endif() +if (BUILD_TEST_PLUGIN) + list(APPEND FLUTTER_PI_SRC src/plugins/testplugin.c) +endif() +if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) + list(APPEND FLUTTER_PI_SRC src/plugins/omxplayer_video_player.c) endif() add_executable(flutter-pi ${FLUTTER_PI_SRC}) @@ -154,12 +163,22 @@ target_compile_options(flutter-pi PRIVATE ${LIBINPUT_CFLAGS} ${LIBUDEV_CFLAGS} ${LIBXKBCOMMON_CFLAGS} - -ggdb - -DBUILD_TEXT_INPUT_PLUGIN - -DBUILD_TEST_PLUGIN - -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN + $<$:-O0 -ggdb> ) +if (BUILD_TEXT_INPUT_PLUGIN) + target_compile_definitions(flutter-pi PRIVATE "BUILD_TEXT_INPUT_PLUGIN") +endif() +if (BUILD_RAW_KEYBOARD_PLUGIN) + target_compile_definitions(flutter-pi PRIVATE "BUILD_RAW_KEYBOARD_PLUGIN") +endif() +if (BUILD_TEST_PLUGIN) + target_compile_definitions(flutter-pi PRIVATE "BUILD_TEST_PLUGIN") +endif() +if (BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN) + target_compile_definitions(flutter-pi PRIVATE "BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN") +endif() + target_link_options(flutter-pi PRIVATE -rdynamic ) diff --git a/Makefile b/Makefile deleted file mode 100644 index 90775e43..00000000 --- a/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd libinput libudev xkbcommon) \ - -DBUILD_TEXT_INPUT_PLUGIN \ - -DBUILD_TEST_PLUGIN \ - -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ - -O0 -ggdb \ - $(CFLAGS) - -REAL_LDFLAGS = \ - $(shell pkg-config --libs gbm libdrm glesv2 egl libsystemd libinput libudev xkbcommon) \ - -lrt \ - -lpthread \ - -ldl \ - -lm \ - -rdynamic \ - $(LDFLAGS) - -SOURCES = src/flutter-pi.c \ - src/platformchannel.c \ - src/pluginregistry.c \ - src/texture_registry.c \ - src/compositor.c \ - src/modesetting.c \ - src/collection.c \ - src/cursor.c \ - src/keyboard.c \ - src/plugins/services.c \ - src/plugins/testplugin.c \ - src/plugins/text_input.c \ - src/plugins/raw_keyboard.c \ - src/plugins/omxplayer_video_player.c - -OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) - -all: out/flutter-pi - -out/obj/%.o: src/%.c - @mkdir -p $(@D) - $(CC) -c $(REAL_CFLAGS) $< -o $@ - -out/flutter-pi: $(OBJECTS) - @mkdir -p $(@D) - $(CC) $(REAL_CFLAGS) $(OBJECTS) $(REAL_LDFLAGS) -o out/flutter-pi - -clean: - @mkdir -p out - rm -rf $(OBJECTS) out/flutter-pi out/obj/* diff --git a/README.md b/README.md index 42b42cc1..add0e2ca 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## 📰 NEWS +- flutter-pi now uses only CMake for building. See the updated build instructions for more info. - I created an improved touchscreen driver for Raspberry Pi 4, for lower latency & higher polling rate. See [this repo](https://github.com/ardera/raspberrypi-fast-ts) for details. The difference is noticeable, it looks a lot better and more responsive with this new driver. - flutter-pi now requires `libxkbcommon`. Install using `sudo apt install libxkbcommon-dev` - keyboard input works better now. You can now use any keyboard connected to the Raspberry Pi for text and raw keyboard input. @@ -11,7 +12,7 @@ You can now **theoretically** run every flutter app you want using flutter-pi, i _The difference between packages and plugins is that packages don't include any native code, they are just pure Dart. Plugins (like the [connectivity plugin](https://github.com/flutter/plugins/tree/master/packages/connectivity)) include platform-specific code._ -## Supported Platforms +## 🖥️ Supported Platforms Although flutter-pi is only tested on a Rasberry Pi 4 2GB, it should work fine on other linux platforms, with the following conditions: - support for hardware 3D acceleration. more precisely support for kernel-modesetting (KMS) and the direct rendering infrastructure (DRI) @@ -21,115 +22,235 @@ This means flutter-pi won't work on a Pi Zero, Pi 1, or Pi 2. A Pi 3 works fine, If you encounter issues running flutter-pi on any of the supported platforms listed above, please report them to me and I'll fix them. -## Contents - -1. **[Running your App on the Raspberry Pi](#running-your-app-on-the-raspberry-pi)** -1.1 [Configuring your Raspberry Pi](#configuring-your-raspberry-pi) -1.2 [Building the Asset bundle](#building-the-asset-bundle) -1.3 [Building the `app.so` (for running your app in Release/Profile mode)](#building-the-appso-for-running-your-app-in-releaseprofile-mode) -1.4 [Running your App with flutter-pi](#running-your-app-with-flutter-pi) -2. **[Dependencies](#dependencies)** -3. **[Compiling flutter-pi (on the Raspberry Pi)](#compiling-flutter-pi-on-the-raspberry-pi)** -4. **[Performance](#performance)** -5. **[Keyboard Input](#keyboard-input)** -6. **[Touchscreen Latency](#touchscreen-latency)** - - -## Running your App on the Raspberry Pi +## 📑 Contents + +1. **[Building flutter-pi on the Raspberry Pi](#-building-flutter-pi-on-the-raspberry-pi)** +1.1 [Dependencies](#dependencies) +1.2 [Compiling](#compiling) +2. **[Running your App on the Raspberry Pi](#-running-your-app-on-the-raspberry-pi)** +2.1 [Configuring your Raspberry Pi](#configuring-your-raspberry-pi) +2.2 [Building the Asset bundle](#building-the-asset-bundle) +2.3 [Building the `app.so` (for running your app in Release/Profile mode)](#building-the-appso-for-running-your-app-in-releaseprofile-mode) +2.4 [Running your App with flutter-pi](#running-your-app-with-flutter-pi) +3. **[Performance](#-performance)** +3.1 [Graphics Performance](#graphics-performance) +3.2 [Touchscreen latency](#touchscreen-latency) + +## 🛠 Building flutter-pi on the Raspberry Pi +- If you want to update flutter-pi, you check out the latest commit using `git pull && git checkout origin/master` and continue with [compiling](#compiling), step 2. + +### Dependencies +1. Install the flutter engine binaries using the instructions in the [in the _engine-binaries_ branch of this project.](https://github.com/ardera/flutter-pi/tree/engine-binaries). +
+ More Info + + flutter-pi needs flutters `flutter_embedder.h` to compile and `icudtl.dat` at runtime. It also needs `libflutter_engine.so.release` at runtime when invoked with the `--release` flag and `libflutter_engine.so.debug` when invoked without. + You actually have two options here: + + - you build the engine yourself. takes a lot of time, and it most probably won't work on the first try. But once you have it set up, you have unlimited freedom on which engine version you want to use. You can find some rough guidelines [here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1). + - you can use the pre-built engine binaries I am providing [in the _engine-binaries_ branch of this project.](https://github.com/ardera/flutter-pi/tree/engine-binaries). I will only provide binaries for some engine versions though (most likely the stable ones). + +
+ +2. Install graphics & system libraries and fonts: + ```bash + sudo apt install libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev + ``` +
+ More Info + + - flutter-pi needs the mesa OpenGL ES and EGL implementation and libdrm & libgbm. It may work with non-mesa implementations too, but that's untested. + - The flutter engine depends on the _Arial_ font. Since that doesn't come included with Raspbian, you need to install it. + - `libsystemd` is not systemd, it's just an utility library. It provides the event loop and dbus support for flutter-pi. + - `libinput-dev`, `libudev-dev` and `libxkbcommon-dev` are needed for (touch, mouse, raw keyboard and text) input support. + - `libudev-dev` is required, but actual udev is not. Flutter-pi will just open all `event` devices inside `/dev/input` (unless overwritten using `-i`) if udev is not present. + - `gpiod` and `libgpiod-dev` where required in the past, but aren't anymore since the `flutter_gpiod` plugin will directly access the kernel interface. +
+ +3. Update the system fonts. + ```bash + sudo fc-cache + ``` + +### Compiling +1. Clone flutter-pi and cd into the cloned directory: + ```bash + git clone https://github.com/ardera/flutter-pi + cd flutter-pi + ``` +2. Compile: + ```bash + mkdir build && cd build + cmake .. + make -j`nproc` + ``` +3. Install: + ```bash + sudo make install + ``` + +## 🚀 Running your App on the Raspberry Pi ### Configuring your Raspberry Pi -#### Switching to Console mode -flutter-pi only works when Raspbian is in console mode (no X11 or Wayland server running). To switch the Pi into console mode, -go to `raspi-config -> Boot Options -> Desktop / CLI` and select `Console` or `Console (Autologin)`. - -#### Enabling the V3D driver -flutter-pi doesn't support the legacy broadcom-proprietary graphics stack anymore. You need to make sure the V3D driver in raspi-config. -Go to `raspi-config -> Advanced Options -> GL Driver` and select `GL (Fake-KMS)`. - -With this driver, it's best to give the GPU as little RAM as possible in `raspi-config -> Advanced Options -> Memory Split`, which is `16MB`. This is because the V3D driver doesn't need GPU RAM anymore. NOTE: If you want to use the [`omxplayer_video_player`](https://pub.dev/packages/omxplayer_video_player) plugin to play back videos in flutter, you need to give the GPU some more RAM, like 64MB. - -#### Fixing the GPU permissions -It seems like with newer versions of Raspbian, the `pi` user doesn't have sufficient permissions to directly access the GPU anymore. IIRC, this is because of some privilege escalation / arbitrary code execution problems of the GPU interface. - -You can fix this by adding the `pi` user to the `render` group, but keep in mind that may be a security hazard: -```bash -usermod -a -G render pi -``` -Then, restart your terminal session so the changes take effect. (reconnect if you're using ssh or else just reboot the Pi) - -Otherwise, you'll need to run `flutter-pi` with `sudo`. +1. Open raspi-config: + ```bash + sudo raspi-config + ``` + +2. Switch to console mode: + `System Options -> Boot / Auto Login` and select `Console` or `Console (Autologin)`. + +3. Enable the V3D graphics driver + `Advanced Options -> GL Driver -> GL (Fake KMS)` + +4. Configure the GPU memory + `Performance Options -> GPU Memory` and enter `64`. + +5. Leave `raspi-config`. + +6. Give the `pi` permission to use 3D acceleration. (**NOTE:** potential security hazard. If you don't want to do this, launch `flutter-pi` using `sudo` instead.) + ```bash + usermod -a -G render pi + ``` + +5. Finish and reboot. + +
+ More information + + - flutter-pi requires that no other process, like a X11- or wayland-server, is using the video output. So to disable the desktop environment, we boot into console instead. + - The old broadcom-proprietary GL driver was bugged and not working with flutter, so we have to use the Fake KMS driver. + - Actually, you can also configure 16MB of GPU memory if you want to. 64MB are needed when you want to use the [`omxplayer_video_player`](https://pub.dev/packages/omxplayer_video_player) plugin. + - `pi` isn't allowed to directly access the GPU because IIRC this has some privilege escalation bugs. Raspberry Pi has quite a lot of system-critical, not graphics-related stuff running on the GPU. I read somewhere it's easily possible to gain control of the GPU by writing malicious shaders. From there you can gain control of the CPU and thus the linux kernel. So basically the `pi` user could escalate privileges and become `root` just by directly accessing the GPU. But maybe this has already been fixed, I'm not sure. +
### Building the Asset bundle -Then to build the asset bundle, run the following commands on your host machine. You can't build the asset bundle on target (== your Raspberry Pi), since the flutter SDK doesn't support linux on ARM yet. - -My host machine is actually running Windows. But I'm also using [WSL](https://docs.microsoft.com/de-de/windows/wsl/install-win10) to upload the binaries to the Raspberry Pi, since `rsync` is a linux tool. - -**Be careful** to use a flutter SDK that's compatible to the engine version you're using. -- use flutter stable and keep it up to date. `flutter channel stable` && `flutter upgrade` -- use the latest engine binaries ([explained later](#flutter-engine)) and keep them up to date - -If you encounter error messages like `Invalid kernel binary format version`, `Invalid SDK hash` or `Invalid engine hash`: -1. Make sure your flutter SDK is on `stable` and up to date and your engine binaries are up to date. -2. If you made sure that's the case and the error still happens, create a new issue for it. - -I'm using [`flutter_gallery`](https://github.com/flutter/gallery) in this example. flutter_gallery is developed against flutter master. So you need to use an older version of flutter_gallery to run it with flutter stable. It seems commit [9b11f12](https://github.com/flutter/gallery/commit/9b11f127fb46cb08e70b2a7cdfe8eaa8de977d5f) is the latest one working with flutter 1.20. - +- The asset bundle must be built on your development machine. Note that you can't use a Raspberry Pi as your development machine. + +1. Make sure you've installed the flutter SDK. **You must** use a flutter SDK that's compatible to the installed engine binaries. + - for the flutter SDK, use flutter stable and keep it up to date. + - always use the latest available [engine binaries](https://github.com/ardera/flutter-pi/tree/engine-binaries) + + If you encounter error messages like `Invalid kernel binary format version`, `Invalid SDK hash` or `Invalid engine hash`: + 1. Make sure your flutter SDK is on `stable` and up to date and your engine binaries are up to date. + 2. If you made sure that's the case and the error still happens, create a new issue. + +2. Open terminal or commandline and `cd` into your app directory. + +3. `flutter build bundle` + +4. Deploy the asset bundle to the Raspberry Pi using `rsync` or `scp`. + - Using `rsync` (available on linux and macOS or on Windows when using [WSL](https://docs.microsoft.com/de-de/windows/wsl/install-win10)) + ```bash + rsync -a --info=progress2 ./build/flutter_assets/ pi@raspberrypi:/home/pi/my_apps_flutter_assets + ``` + - Using `scp` (available on linux, macOS and Windows) + ```bash + scp -r ./build/flutter_assets/ pi@raspberrypi:/home/pi/my_apps_flutter_assets + ``` + +#### Example +1. We'll build the asset bundle for `flutter_gallery` and deploy it using `rsync` in this example. ```bash git clone https://github.com/flutter/gallery.git flutter_gallery cd flutter_gallery git checkout 9b11f127fb46cb08e70b2a7cdfe8eaa8de977d5f flutter build bundle +rsync -a ./build/flutter_assets/ pi@raspberrypi:/home/pi/flutter_gallery/ ``` +3. Done. You can now run this app in debug-mode using `flutter-pi /home/pi/flutter_gallery`. -Then just upload the asset bundle to your Raspberry Pi. `pi@raspberrypi` is of course just an example `@` combination, your need to substitute your username and hostname there. -```bash -$ rsync -a --info=progress2 ./build/flutter_assets/ pi@raspberrypi:/home/pi/flutter_gallery_assets -``` +
+ More information + + - flutter_gallery is developed against flutter master. `9b11f127fb46cb08e70b2a7cdfe8eaa8de977d5f` is currently the latest flutter gallery + commit working flutter stable. +
### Building the `app.so` (for running your app in Release/Profile mode) -This is done entirely on the host machine as well. - -1. First, find out the path to your flutter SDK. For me it's `C:\flutter`. (I'm on Windows) -2. Open the commandline, `cd` into your app directory. -``` -git clone https://github.com/flutter/gallery.git flutter_gallery -cd flutter_gallery -git checkout 9b11f127fb46cb08e70b2a7cdfe8eaa8de977d5f -``` -3. Build the kernel snapshot. -```cmd -C:\flutter\bin\cache\dart-sdk\bin\dart.exe ^ - C:\flutter\bin\cache\dart-sdk\bin\snapshots\frontend_server.dart.snapshot ^ - --sdk-root C:\flutter\bin\cache\artifacts\engine\common\flutter_patched_sdk_product ^ - --target=flutter ^ - --aot ^ - --tfa ^ - -Ddart.vm.product=true ^ - --packages .packages ^ - --output-dill build\kernel_snapshot.dill ^ - --verbose ^ - --depfile build\kernel_snapshot.d ^ - package:gallery/main.dart -``` -4. Build the `app.so`. This uses the `gen_snapshot_linux_x64` executable I provide in the engine-binaries branch. It needs to be executed under linux. If you're on Windows, you need to use [WSL](https://docs.microsoft.com/de-de/windows/wsl/install-win10). -```bash -$ git clone --branch engine-binaries https://github.com/ardera/flutter-pi ~/engine-binaries -$ cd /path/to/your/app -$ ~/engine-binaries/gen_snapshot_linux_x64 \ - --causal_async_stacks \ - --deterministic \ - --snapshot_kind=app-aot-elf \ - --elf=build/app.so \ - --strip \ - --sim_use_hardfp \ - --no-use-integer-division \ - build/kernel_snapshot.dill -``` -5. Upload the asset bundle and the `app.so` to your Raspberry Pi. Flutter-pi expects the `app.so` to be located inside the asset bundle directory. -```bash -$ rsync -a --info=progress2 ./build/flutter_assets/ pi@raspberrypi:/home/pi/flutter_gallery_assets -$ scp ./build/app.so pi@raspberrypi:/home/pi/flutter_gallery_assets/app.so -``` -6. When starting your app, make sure you invoke flutter-pi with the `--release` flag. +- This is done entirely on your development machine as well. + +1. Find out the path to your flutter SDK. For me it's `C:\flutter`. (I'm on Windows) +2. Open terminal or commandline and `cd` into your app directory. +3. Build the asset bundle. + ``` + flutter build bundle + ``` +4. Build the kernel snapshot. (Replace `my_app_name` with the name of your app) + ```cmd + C:\flutter\bin\cache\dart-sdk\bin\dart.exe ^ + C:\flutter\bin\cache\dart-sdk\bin\snapshots\frontend_server.dart.snapshot ^ + --sdk-root C:\flutter\bin\cache\artifacts\engine\common\flutter_patched_sdk_product ^ + --target=flutter ^ + --aot ^ + --tfa ^ + -Ddart.vm.product=true ^ + --packages .packages ^ + --output-dill build\kernel_snapshot.dill ^ + --verbose ^ + --depfile build\kernel_snapshot.d ^ + package:my_app_name/main.dart + ``` + +5. Fetch the latest `gen_snapshot_linux_x64` I provide in the [engine-binaries branch](https://github.com/ardera/flutter-pi/tree/engine-binaries). +6. The following steps must be executed on a linux x64 machine. If you're on windows, you can use [WSL](https://docs.microsoft.com/de-de/windows/wsl/install-win10). If you're on macOS, you can use a linux VM. +7. Build the `app.so`. + ```bash + gen_snapshot_linux_x64 \ + --causal_async_stacks \ + --deterministic \ + --snapshot_kind=app-aot-elf \ + --elf=build/flutter_assets/app.so \ + --strip \ + --sim_use_hardfp \ + --no-use-integer-division \ + build/kernel_snapshot.dill + ``` +8. Now you can switch to your normal OS again. +9. Upload the asset bundle and the `app.so` to your Raspberry Pi. + ```bash + rsync -a --info=progress2 ./build/flutter_assets/ pi@raspberrypi:/home/pi/my_app + ``` + or + ``` + scp -r ./build/flutter_assets/ pi@raspberrypi:/home/pi/my_app + ``` +10. You can now launch the app in release mode using `flutter-pi --release /home/pi/my_app` + +#### Complete example on Windows +1. We'll build the asset bundle for `flutter_gallery` and deploy it using `rsync` in this example. + ```bash + git clone https://github.com/flutter/gallery.git flutter_gallery + git clone https://github.com/ardera/flutter-pi -b engine-binaries engine-binaries + cd flutter_gallery + git checkout 9b11f127fb46cb08e70b2a7cdfe8eaa8de977d5f + flutter build bundle + C:\flutter\bin\cache\dart-sdk\bin\dart.exe ^ + C:\flutter\bin\cache\dart-sdk\bin\snapshots\frontend_server.dart.snapshot ^ + --sdk-root C:\flutter\bin\cache\artifacts\engine\common\flutter_patched_sdk_product ^ + --target=flutter ^ + --aot ^ + --tfa ^ + -Ddart.vm.product=true ^ + --packages .packages ^ + --output-dill build\kernel_snapshot.dill ^ + --verbose ^ + --depfile build\kernel_snapshot.d ^ + package:gallery/main.dart + wsl + ../engine-binaries/arm/gen_snapshot_linux_x64 \ + --causal_async_stacks \ + --deterministic \ + --snapshot_kind=app-aot-elf \ + --elf=build/flutter_assets/app.so \ + --strip \ + --sim_use_hardfp \ + --no-use-integer-division \ + build/kernel_snapshot.dill + mv ./build/app.so ./build/flutter_assets/ + rsync -a --info=progress2 ./build/flutter_assets/ pi@raspberrypi:/home/pi/flutter_gallery + exit + ``` +3. Done. You can now run this app in release mode using `flutter-pi --release /home/pi/flutter_gallery`. ### Running your App with flutter-pi ```txt @@ -137,29 +258,11 @@ USAGE: flutter-pi [options] [flutter engine options] OPTIONS: - -i, --input Appends all files matching this glob pattern to the - list of input (touchscreen, mouse, touchpad, - keyboard) devices. Brace and tilde expansion is - enabled. - Every file that matches this pattern, but is not - a valid touchscreen / -pad, mouse or keyboard is - silently ignored. - If no -i options are given, flutter-pi will try to - use all input devices assigned to udev seat0. - If that fails, or udev is not installed, flutter-pi - will fallback to using all devices matching - "/dev/input/event*" as inputs. - In most cases, there's no need to specify this - option. - Note that you need to properly escape each glob - pattern you use as a parameter so it isn't - implicitly expanded by your shell. - --release Run the app in release mode. The AOT snapshot of the app ("app.so") must be located inside the asset bundle directory. - This also requires a libflutter_engine.so that was - built with --runtime-mode=release. + This also requires a libflutter_engine.so that was + built with --runtime-mode=release. -o, --orientation Start the app in this orientation. Valid for are: portrait_up, landscape_left, @@ -178,24 +281,39 @@ OPTIONS: -d, --dimensions "width_mm,height_mm" The width & height of your display in millimeters. Useful if your GPU doesn't provide - valid physical dimensions for your display. - The physical dimensions of your display are used - to calculate the flutter device-pixel-ratio, which - in turn basically "scales" the UI. + valid physical dimensions for your display. + The physical dimensions of your display are used + to calculate the flutter device-pixel-ratio, which + in turn basically "scales" the UI. - --no-text-input Disable text input from the console. - This means flutter-pi won't configure the console - to raw/non-canonical mode. + -i, --input Appends all files matching this glob pattern to the + list of input (touchscreen, mouse, touchpad, + keyboard) devices. Brace and tilde expansion is + enabled. + Every file that matches this pattern, but is not + a valid touchscreen / -pad, mouse or keyboard is + silently ignored. + If no -i options are given, flutter-pi will try to + use all input devices assigned to udev seat0. + If that fails, or udev is not installed, flutter-pi + will fallback to using all devices matching + "/dev/input/event*" as inputs. + In most cases, there's no need to specify this + option. + Note that you need to properly escape each glob + pattern you use as a parameter so it isn't + implicitly expanded by your shell. -h, --help Show this help and exit. EXAMPLES: + flutter-pi ~/hello_world_app + flutter-pi --release ~/hello_world_app + flutter-pi -o portrait_up ./my_app + flutter-pi -r 90 ./my_app + flutter-pi -d "155, 86" ./my_app flutter-pi -i "/dev/input/event{0,1}" -i "/dev/input/event{2,3}" /home/pi/helloworld_flutterassets flutter-pi -i "/dev/input/mouse*" /home/pi/helloworld_flutterassets - flutter-pi -o portrait_up ./flutter_assets - flutter-pi -r 90 ./flutter_assets - flutter-pi -d "155, 86" ./flutter_assets - flutter-pi /home/pi/helloworld_flutterassets SEE ALSO: Author: Hannes Winkler, a.k.a ardera @@ -213,41 +331,11 @@ of the flutter app you're trying to run. `[flutter engine options...]` will be passed as commandline arguments to the flutter engine. You can find a list of commandline options for the flutter engine [Here](https://github.com/flutter/engine/blob/master/shell/common/switches.h). -## Dependencies -### flutter engine -flutter-pi needs `libflutter_engine.so` and `flutter_embedder.h` to compile. It also needs the flutter engine's `icudtl.dat` at runtime. -You have two options here: - -- you build the engine yourself. takes a lot of time, and it most probably won't work on the first try. But once you have it set up, you have unlimited freedom on which engine version you want to use. You can find some rough guidelines [here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1). -- you can use the pre-built engine binaries I am providing [in the _engine-binaries_ branch of this project.](https://github.com/ardera/flutter-pi/tree/engine-binaries). I will only provide binaries for some engine versions though (most likely the stable ones). - -### graphics libs -Additionally, flutter-pi depends on mesa's OpenGL, OpenGL ES, EGL implementation and libdrm & libgbm. -You can easily install those with `sudo apt install libgl1-mesa-dev libgles2-mesa-dev libegl-mesa0 libdrm-dev libgbm-dev`. - -### fonts -The flutter engine, by default, uses the _Arial_ font. Since that doesn't come included with Raspbian, you need to install it using: -```bash -sudo apt install ttf-mscorefonts-installer fontconfig -sudo fc-cache -``` -### libgpiod (for the included GPIO plugin), libsystemd, libinput, libudev -```bash -sudo apt-get install gpiod libgpiod-dev libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev -``` - -## Compiling flutter-pi (on the Raspberry Pi) -fetch all the dependencies, clone this repo and run -```bash -cd /path/to/the/cloned/flutter-pi/directory -make -``` -The _flutter-pi_ executable will then be located at this path: `/path/to/the/cloned/flutter-pi/directory/out/flutter-pi` - -## Performance -Performance is actually better than I expected. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps. +## 📊 Performance +### Graphics Performance +Graphics performance is actually pretty good. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps on the Pi 4 2GB and Pi 3 A+. -## Touchscreen Latency +### Touchscreen Latency Due to the way the touchscreen driver works in raspbian, there's some delta between an actual touch of the touchscreen and a touch event arriving at userspace. The touchscreen driver in the raspbian kernel actually just repeatedly polls some buffer shared with the firmware running on the VideoCore, and the videocore repeatedly polls the touchscreen. (both at 60Hz) So on average, there's a delay of 17ms (minimum 0ms, maximum 34ms). Actually, the firmware is polling correctly at ~60Hz, but the linux driver is not because there's a bug. The linux side actually polls at 25Hz, which makes touch applications look terrible. (When you drag something in a touch application, but the application only gets new touch data at 25Hz, it'll look like the application itself is _redrawing_ at 25Hz, making it look very laggy) The github issue for this raspberry pi kernel bug is [here](https://github.com/raspberrypi/linux/issues/3777). Leave a like on the issue if you'd like to see this fixed in the kernel. This is why I created my own (userspace) touchscreen driver, for improved latency & polling rate. See [this repo](https://github.com/ardera/raspberrypi-fast-ts) for details. The driver is very easy to use and the difference is noticeable, flutter apps look and feel a lot better with this driver. diff --git a/src/compositor.c b/src/compositor.c index 9a22b585..57d04b57 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -954,24 +954,38 @@ static bool on_present_layers( bool use_atomic_modesetting; int ok; + // TODO: proper error handling + compositor = userdata; drmdev = compositor->drmdev; schedule_fake_page_flip_event = compositor->do_blocking_atomic_commits; use_atomic_modesetting = drmdev->supports_atomic_modesetting; + req = NULL; if (use_atomic_modesetting) { - drmdev_new_atomic_req(compositor->drmdev, &req); + ok = drmdev_new_atomic_req(compositor->drmdev, &req); + if (ok != 0) { + return false; + } } else { planes = PSET_INITIALIZER_STATIC(planes_storage, 32); for_each_plane_in_drmdev(drmdev, plane) { if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { - pset_put(&planes, plane); + ok = pset_put(&planes, plane); + if (ok != 0) { + return false; + } } } } cpset_lock(&compositor->cbs); + EGLDisplay stored_display = eglGetCurrentDisplay(); + EGLSurface stored_read_surface = eglGetCurrentSurface(EGL_READ); + EGLSurface stored_write_surface = eglGetCurrentSurface(EGL_DRAW); + EGLContext stored_context = eglGetCurrentContext(); + eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); @@ -1313,7 +1327,7 @@ static bool on_present_layers( } } - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglMakeCurrent(stored_display, stored_read_surface, stored_write_surface, stored_context); if (use_atomic_modesetting) { do_commit: @@ -1324,7 +1338,7 @@ static bool on_present_layers( } ok = drmdev_atomic_req_commit(req, req_flags, NULL); - if ((compositor->do_blocking_atomic_commits == false) && (ok < 0) && (errno == EBUSY)) { + if ((compositor->do_blocking_atomic_commits == false) && (ok == EBUSY)) { printf("[compositor] Non-blocking drmModeAtomicCommit failed with EBUSY.\n" " Future drmModeAtomicCommits will be executed blockingly.\n" " This may have have an impact on performance.\n"); @@ -1332,6 +1346,11 @@ static bool on_present_layers( compositor->do_blocking_atomic_commits = true; schedule_fake_page_flip_event = true; goto do_commit; + } else if (ok != 0) { + fprintf(stderr, "[compositor] Could not present frame. drmModeAtomicCommit: %s\n", strerror(ok)); + drmdev_destroy_atomic_req(req); + cpset_unlock(&compositor->cbs); + return false; } drmdev_destroy_atomic_req(req); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 18d6dfb8..38761d61 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -59,24 +59,6 @@ USAGE:\n\ flutter-pi [options] [flutter engine options]\n\ \n\ OPTIONS:\n\ - -i, --input Appends all files matching this glob pattern to the\n\ - list of input (touchscreen, mouse, touchpad, \n\ - keyboard) devices. Brace and tilde expansion is \n\ - enabled.\n\ - Every file that matches this pattern, but is not\n\ - a valid touchscreen / -pad, mouse or keyboard is \n\ - silently ignored.\n\ - If no -i options are given, flutter-pi will try to\n\ - use all input devices assigned to udev seat0.\n\ - If that fails, or udev is not installed, flutter-pi\n\ - will fallback to using all devices matching \n\ - \"/dev/input/event*\" as inputs.\n\ - In most cases, there's no need to specify this\n\ - option.\n\ - Note that you need to properly escape each glob \n\ - pattern you use as a parameter so it isn't \n\ - implicitly expanded by your shell.\n\ - \n\ --release Run the app in release mode. The AOT snapshot\n\ of the app (\"app.so\") must be located inside the\n\ asset bundle directory.\n\ @@ -105,19 +87,34 @@ OPTIONS:\n\ to calculate the flutter device-pixel-ratio, which\n\ in turn basically \"scales\" the UI.\n\ \n\ - --no-text-input Disable text input from the console.\n\ - This means flutter-pi won't configure the console\n\ - to raw/non-canonical mode.\n\ + -i, --input Appends all files matching this glob pattern to the\n\ + list of input (touchscreen, mouse, touchpad, \n\ + keyboard) devices. Brace and tilde expansion is \n\ + enabled.\n\ + Every file that matches this pattern, but is not\n\ + a valid touchscreen / -pad, mouse or keyboard is \n\ + silently ignored.\n\ + If no -i options are given, flutter-pi will try to\n\ + use all input devices assigned to udev seat0.\n\ + If that fails, or udev is not installed, flutter-pi\n\ + will fallback to using all devices matching \n\ + \"/dev/input/event*\" as inputs.\n\ + In most cases, there's no need to specify this\n\ + option.\n\ + Note that you need to properly escape each glob \n\ + pattern you use as a parameter so it isn't \n\ + implicitly expanded by your shell.\n\ \n\ -h, --help Show this help and exit.\n\ \n\ EXAMPLES:\n\ + flutter-pi ~/hello_world_app\n\ + flutter-pi --release ~/hello_world_app\n\ + flutter-pi -o portrait_up ./my_app\n\ + flutter-pi -r 90 ./my_app\n\ + flutter-pi -d \"155, 86\" ./my_app\n\ flutter-pi -i \"/dev/input/event{0,1}\" -i \"/dev/input/event{2,3}\" /home/pi/helloworld_flutterassets\n\ flutter-pi -i \"/dev/input/mouse*\" /home/pi/helloworld_flutterassets\n\ - flutter-pi -o portrait_up ./flutter_assets\n\ - flutter-pi -r 90 ./flutter_assets\n\ - flutter-pi -d \"155, 86\" ./flutter_assets\n\ - flutter-pi /home/pi/helloworld_flutterassets\n\ \n\ SEE ALSO:\n\ Author: Hannes Winkler, a.k.a ardera\n\ @@ -125,7 +122,7 @@ SEE ALSO:\n\ License: MIT\n\ \n\ For instructions on how to build an asset bundle or an AOT snapshot\n\ - of your app, please see the linked git repository.\n\ + of your app, please see the linked github repository.\n\ For a list of options you can pass to the flutter engine, look here:\n\ https://github.com/flutter/engine/blob/master/shell/common/switches.h\n\ "; @@ -1959,8 +1956,24 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void }; } } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { - data->keyboard_state = keyboard_state_new(flutterpi.input.keyboard_config, NULL, NULL); +#if BUILD_TEXT_INPUT_PLUGIN || BUILD_RAW_KEYBOARD_PLUGIN + if (flutterpi.input.disable_text_input == false) { + data->keyboard_state = keyboard_state_new(flutterpi.input.keyboard_config, NULL, NULL); + } +#endif + } + } else if (type == LIBINPUT_EVENT_DEVICE_REMOVED) { + device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); + + if (data) { + if (data->keyboard_state) { + free(data->keyboard_state); + } + free(data); } + + libinput_device_set_user_data(device, NULL); } else if (LIBINPUT_EVENT_IS_TOUCH(type)) { touch_event = libinput_event_get_touch_event(event); data = libinput_device_get_user_data(libinput_event_get_device(event)); @@ -2150,13 +2163,14 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void } else if (type == LIBINPUT_EVENT_POINTER_AXIS) { } - } else if (LIBINPUT_EVENT_IS_KEYBOARD(type)) { + } else if (LIBINPUT_EVENT_IS_KEYBOARD(type) && !flutterpi.input.disable_text_input) { +#if BUILD_RAW_KEYBOARD_PLUGIN || BUILD_TEXT_INPUT_PLUGIN struct keyboard_modifier_state mods; enum libinput_key_state key_state; xkb_keysym_t keysym; uint32_t codepoint, plain_codepoint; uint16_t evdev_keycode; - + keyboard_event = libinput_event_get_keyboard_event(event); data = libinput_device_get_user_data(libinput_event_get_device(event)); evdev_keycode = libinput_event_keyboard_get_key(keyboard_event); @@ -2172,10 +2186,9 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void &codepoint ); - printf("[key event] keycode: 0x%04X, type: %s, keysym: 0x%08X, codepoint: 0x%08X\n", evdev_keycode, key_state? "down" : " up ", keysym, codepoint); - plain_codepoint = keyboard_state_get_plain_codepoint(data->keyboard_state, evdev_keycode, 1); - + +#ifdef BUILD_RAW_KEYBOARD_PLUGIN rawkb_send_gtk_keyevent( plain_codepoint, (uint32_t) keysym, @@ -2188,7 +2201,9 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void | (keyboard_state_is_meta_active(data->keyboard_state) << 28), key_state ); +#endif +#ifdef BUILD_TEXT_INPUT_PLUGIN if (codepoint) { if (codepoint < 0x80) { if (isprint(codepoint)) { @@ -2220,6 +2235,8 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void if (keysym) { textin_on_xkb_keysym(keysym); } +#endif +#endif } libinput_event_destroy(event); @@ -2461,13 +2478,15 @@ static int init_user_input(void) { # endif return -ok; } - + +#ifdef BUILD_TEXT_INPUT_PLUGIN if (flutterpi.input.disable_text_input == false) { kbdcfg = keyboard_config_new(); if (kbdcfg == NULL) { fprintf(stderr, "[flutter-pi] Could not initialize keyboard configuration. Flutter-pi will run without text/raw keyboard input.\n"); } } +#endif } else { fprintf(stderr, "[flutter-pi] Could not initialize input. Flutter-pi will run without user input.\n"); } diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 19987bf3..d3f6edfc 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -18,15 +18,12 @@ #ifdef BUILD_TEXT_INPUT_PLUGIN # include #endif +#ifdef BUILD_RAW_KEYBOARD_PLUGIN +# include +#endif #ifdef BUILD_TEST_PLUGIN # include #endif -#ifdef BUILD_GPIOD_PLUGIN -# include -#endif -#ifdef BUILD_SPIDEV_PLUGIN -# include -#endif #ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN # include #endif @@ -50,14 +47,17 @@ struct plugin_registry { /// array of plugins that are statically included in flutter-pi. struct flutterpi_plugin hardcoded_plugins[] = { {.name = "services", .init = services_init, .deinit = services_deinit}, - {.name = "raw_keyboard", .init = rawkb_init, .deinit = rawkb_deinit}, - + #ifdef BUILD_TEXT_INPUT_PLUGIN {.name = "text_input", .init = textin_init, .deinit = textin_deinit}, #endif +#ifdef BUILD_RAW_KEYBOARD_PLUGIN + {.name = "raw_keyboard", .init = rawkb_init, .deinit = rawkb_deinit}, +#endif + #ifdef BUILD_TEST_PLUGIN - {.name = "testplugin", .init = testp_init, .deinit = testp_deinit}, + {.name = "testplugin", .init = testp_init .deinit = testp_deinit}, #endif #ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN