mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge ddf77cac057fab538b9de6f9fbb79db5c2af02dc into 06df71c51446e96939c6a615b7c34ce9123806ba
This commit is contained in:
commit
1ef8eceed9
37
AGENTS.md
Normal file
37
AGENTS.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `packages/`: Flutter framework and core Dart packages (`flutter`, `flutter_test`, `flutter_tools`, etc.).
|
||||
- `engine/`: C++/Skia-based engine sources and build files (see `engine/README.md` for engine build steps).
|
||||
- `bin/`: Flutter CLI entrypoints (`bin/flutter`) and cached tooling.
|
||||
- `dev/`: Internal tooling, CI scripts, and large test suites (e.g., `dev/bots`, `dev/devicelab`).
|
||||
- `examples/`: Sample apps and integration examples.
|
||||
- `docs/`: Project documentation and contributing references.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `./bin/flutter --version`: Bootstraps the repo toolchain (downloads the Dart SDK if needed).
|
||||
- `./bin/flutter test`: Run tests for a package (run from the package directory, e.g., `packages/flutter`).
|
||||
- `bin/cache/dart-sdk/bin/dart dev/bots/test.dart`: CI-style test runner; use shards like `SHARD=framework_tests`.
|
||||
- `bin/cache/dart-sdk/bin/dart --enable-asserts dev/bots/analyze.dart`: Repository-wide analysis and lint checks.
|
||||
- `./dev/tools/format.sh`: Applies Dart formatting used by CI.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Dart uses 2-space indentation; rely on the formatter (`./dev/tools/format.sh`) instead of manual alignment.
|
||||
- Follow `analysis_options.yaml` at the repo root (and in `dev/`) for lints and analyzer settings.
|
||||
- File names are lower_snake_case; test files must end with `_test.dart`.
|
||||
|
||||
## Testing Guidelines
|
||||
- Unit/widget tests live in `*/test/` and are run with `flutter test`.
|
||||
- CI uses `dev/bots/test.dart`; prefer shards when validating a focused area.
|
||||
- New features or bug fixes should include tests near the affected package or tool.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Recent commits use a concise summary with a PR number suffix, e.g. `Roll Skia … (#181780)`.
|
||||
- Keep commit subjects imperative and scoped to the change.
|
||||
- PRs should include: a clear description, linked issue (if any), and the tests run.
|
||||
- Follow `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` for process and community rules.
|
||||
|
||||
## Agent Notes (GTK4 Porting)
|
||||
- Engine deps are synced from the repo root using `.gclient` copied from `engine/scripts/standard.gclient`; do not use the archived `https://github.com/flutter/engine.git` mirror.
|
||||
- Run `gclient sync` at the repo root; it may take a long time and needs network access.
|
||||
- GN/Ninja builds can take >10 minutes; use longer timeouts (e.g., 600s) when running `ninja -C engine/src/out/host_debug_unopt` via automation.
|
||||
51
docs/gtk4-linux-plan.md
Normal file
51
docs/gtk4-linux-plan.md
Normal file
@ -0,0 +1,51 @@
|
||||
# GTK4 Linux Default Plan
|
||||
|
||||
## Goal
|
||||
Make the Linux desktop embedder use GTK4 by default while keeping a short transition path for existing GTK3-based apps and plugins.
|
||||
|
||||
## Scope
|
||||
- Engine/Embedder: GTK4-backed Linux shell and `flutter_linux` surface.
|
||||
- Tooling/Templates: update generated Linux runner (`my_application.*`, CMake, manifest).
|
||||
- Tests/CI: validate GTK4 in bots and add regression coverage.
|
||||
|
||||
## Workstreams
|
||||
|
||||
### 1) Embedder + Engine
|
||||
- Add a GTK4 backend in `engine/src/flutter/shell/platform/linux` (or port the existing GTK3 implementation).
|
||||
- Replace GTK3-only APIs with GTK4 equivalents (e.g., container/child APIs, event controllers, header bar wiring).
|
||||
- Ensure IME, clipboard, accessibility, windowing, and input paths still function under GTK4.
|
||||
- Update build configuration to link against `gtk4`/`gdk-4` via pkg-config and document system deps.
|
||||
|
||||
### 2) Tooling + Templates
|
||||
- Update Linux runner templates in `packages/flutter_tools/templates/app/linux-gtk4.tmpl/runner/`:
|
||||
- `my_application.cc.tmpl` and `my_application.h` to GTK4 APIs.
|
||||
- `CMakeLists.txt` to find and link GTK4 packages.
|
||||
- Verify `template_manifest.json` stays consistent and new projects generate GTK4 code by default.
|
||||
- Provide a migration note for existing apps: re-run `flutter create --platforms=linux .` or manually port the runner code.
|
||||
|
||||
### 3) Plugins + Platform Interfaces
|
||||
- Audit `packages/` Linux plugins for GTK3-specific types or assumptions.
|
||||
- Update plugin examples and registrant usage if API surface changes in `flutter_linux`.
|
||||
|
||||
### 4) Testing + CI
|
||||
- Add/extend Linux integration tests in `dev/integration_tests/` to cover windowing, input, IME, and accessibility on GTK4.
|
||||
- Wire a GTK4 Linux shard into `dev/bots/test.dart` (and `analyze.dart` if needed) so GTK4 is exercised in CI.
|
||||
- Keep a temporary GTK3 shard for comparison until GTK4 stabilizes, then remove or demote.
|
||||
|
||||
## Test Plan (Proposed)
|
||||
- **Engine unit tests (GTK4)**: build with `use_gtk4=true` and run `flutter_linux_unittests`.
|
||||
- **Engine unit tests (GTK3)**: keep a parity run with `use_gtk4=false` during transition.
|
||||
- **Tooling sanity**: `flutter create --linux-gtk=gtk4` and `--linux-gtk=gtk3` generate and build runner code.
|
||||
- **Integration coverage**: run targeted tests in `dev/integration_tests/` (windowing, text input, a11y) against GTK4 builds.
|
||||
- **CI shard**: add a Linux GTK4 shard to run the above in LUCI, keeping GTK3 for comparison until stable.
|
||||
|
||||
## Milestones
|
||||
1. **Prototype**: GTK4 embedder builds and runs a minimal app (no regressions in windowing/input).
|
||||
2. **Template switch**: new Linux apps generate GTK4 runner code by default.
|
||||
3. **CI coverage**: GTK4 shard green for key test suites.
|
||||
4. **Default rollout**: document GTK4 as default; keep GTK3 escape hatch for one cycle.
|
||||
|
||||
## Risks & Open Questions
|
||||
- GTK4 API differences (container hierarchy, event handling, header bars) may require non-trivial refactors.
|
||||
- Plugin ecosystem breakage: need a compatibility story and clear migration guidance.
|
||||
- Distribution requirements: confirm minimum distro versions that ship GTK4.
|
||||
76
docs/gtk4-rendering-notes.md
Normal file
76
docs/gtk4-rendering-notes.md
Normal file
@ -0,0 +1,76 @@
|
||||
# GTK4 Rendering Notes (Linux Embedder)
|
||||
|
||||
## Overview: current rendering pipeline
|
||||
- `FlEngine` selects the renderer (`kOpenGL` or `kSoftware`) and wires the embedder callbacks in `engine/src/flutter/shell/platform/linux/fl_engine.cc`.
|
||||
- `FlView` owns the GTK widget tree, creates the GL context (or software path), and dispatches frames to the compositor in `engine/src/flutter/shell/platform/linux/fl_view.cc`.
|
||||
- `FlCompositor` abstracts composition. Implementations:
|
||||
- OpenGL path: `engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc`
|
||||
- Software path: `engine/src/flutter/shell/platform/linux/fl_compositor_software.cc`
|
||||
- Frame storage and interop live in:
|
||||
- `engine/src/flutter/shell/platform/linux/fl_framebuffer.*`
|
||||
- `engine/src/flutter/shell/platform/linux/fl_renderable.*`
|
||||
- `engine/src/flutter/shell/platform/linux/fl_texture_gl.*`
|
||||
- GL context lifecycle/sharing lives in `engine/src/flutter/shell/platform/linux/fl_opengl_manager.*`.
|
||||
|
||||
## Files that are rendering-critical
|
||||
- `engine/src/flutter/shell/platform/linux/fl_engine.cc`: renderer selection, embedder config, compositor callbacks.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_view.cc`: widget tree, GL context creation, render callbacks, window metrics, input/event plumbing.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_compositor.h`: compositor API (currently uses `GdkWindow*` in render path).
|
||||
- `engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc`: GL shader setup, framebuffer compositing, EGL/GLX sharing decisions.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_compositor_software.cc`: Cairo-based software rendering.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_framebuffer.*`: OpenGL FBO/texture management.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_texture_gl.*`: GL texture interop with GTK contexts.
|
||||
- `engine/src/flutter/shell/platform/linux/fl_window_state_monitor.*`: window state and scale factor (uses `GdkWindow*`).
|
||||
- `engine/src/flutter/shell/platform/linux/fl_text_input_handler.cc`: IME integration uses `gtk_widget_get_window`.
|
||||
|
||||
## GTK4 adaptation points (API shifts to address)
|
||||
These are the main GTK3 -> GTK4 deltas that touch rendering and windowing:
|
||||
|
||||
1) Window/surface APIs
|
||||
- GTK3 uses `GdkWindow*` from `gtk_widget_get_window`. GTK4 replaces this with `GdkSurface*` accessed via `GtkNative`.
|
||||
- Update compositor render signatures to use `GdkSurface*` (or an abstracted surface type) and replace calls like:
|
||||
- `gdk_window_get_width/height/scale_factor/display` -> GTK4 surface equivalents.
|
||||
|
||||
2) Child attachment and containers
|
||||
- GTK4 removes `GtkContainer` APIs. Replace `gtk_container_add` with widget-specific APIs (e.g., `gtk_window_set_child`, `gtk_box_append`).
|
||||
- In the embedder, this affects `fl_application.cc` and `fl_view.cc` widget trees.
|
||||
|
||||
3) Event handling
|
||||
- `GtkEventBox` is removed in GTK4. Replace with `GtkEventController*` and attach controllers to the widget that should receive input.
|
||||
- Update input plumbing in `fl_view.cc` (motion, button, scroll, touch) to use GTK4 event controllers and gestures.
|
||||
|
||||
4) GL/Vulkan context creation
|
||||
- GTK3 uses `gdk_window_create_gl_context`. GTK4 uses surface-native context creation APIs.
|
||||
- Update `fl_view.cc` and any GL interop helpers to use GTK4 context creation and to fetch the correct drawable/surface handle.
|
||||
|
||||
5) Cursor + monitor queries
|
||||
- Cursor setting and monitor lookup currently use `GdkWindow*`. Switch to surface-based APIs in `fl_view.cc` and `fl_window_state_monitor.cc`.
|
||||
|
||||
## Rendering options (is there more than one?)
|
||||
Yes. There are at least three viable rendering paths:
|
||||
|
||||
1) **GTK4 + OpenGL (EGL/GLX)**
|
||||
- Keep the existing OpenGL renderer and compositor but replace GTK3 APIs with GTK4 equivalents.
|
||||
- Lowest risk, reuses current embedder code. Still benefits from GTK4 event/input improvements.
|
||||
|
||||
2) **GTK4 + Vulkan (Impeller/Vulkan)**
|
||||
- Requires adding Vulkan support to the Linux embedder. Today `fl_engine.cc` rejects `kVulkan`.
|
||||
- GTK4 exposes Vulkan-capable surfaces; the embedder would need:
|
||||
- Vulkan device/swapchain setup per surface.
|
||||
- A Vulkan-backed compositor path (similar to `fl_compositor_opengl` but for Vulkan).
|
||||
- Impeller/Vulkan integration in the embedder config.
|
||||
- Highest upside if Vulkan stability/perf are a goal, but also the largest change.
|
||||
|
||||
3) **Software (Cairo)**
|
||||
- Already supported via `fl_compositor_software.cc` and remains a fallback for unsupported GPU paths.
|
||||
- Useful for headless or minimal environments, but not performance-competitive.
|
||||
|
||||
## Recommended approach order
|
||||
- Start with **GTK4 + OpenGL** to land GTK4 without destabilizing rendering.
|
||||
- Parallel-plan a **Vulkan/Impeller** track: add renderer selection, Vulkan context creation, and compositor.
|
||||
- Keep **software** as a fallback path during transition.
|
||||
|
||||
## Test scope (rendering-specific)
|
||||
- `engine/src/flutter/shell/platform/linux/*_test.cc` for compositor, framebuffer, and view lifecycle.
|
||||
- Add GTK4-only test runs by building `flutter_linux_unittests` with `use_gtk4=true`.
|
||||
- Add GTK4 integration tests for window metrics, input, and first-frame behavior using `dev/integration_tests/`.
|
||||
48
docs/gtk4_porting_todos.md
Normal file
48
docs/gtk4_porting_todos.md
Normal file
@ -0,0 +1,48 @@
|
||||
# GTK4 Porting TODOs
|
||||
|
||||
This checklist tracks remaining GTK4 work and notes what’s already landed in
|
||||
this branch. Keep it short and focused on actionable items.
|
||||
|
||||
## Done (GTK4 baseline in this branch)
|
||||
- [x] GTK4 close-request wiring in `engine/src/flutter/shell/platform/linux/fl_window_monitor.cc`.
|
||||
- [x] GTK4 lifecycle monitoring via `GdkToplevelState` + `GdkSurface::mapped` in
|
||||
`engine/src/flutter/shell/platform/linux/fl_window_state_monitor.cc`.
|
||||
- [x] GTK4 drawing area resize handling in `engine/src/flutter/shell/platform/linux/fl_view.cc`.
|
||||
- [x] GTK4 input controllers/gestures wired in `engine/src/flutter/shell/platform/linux/fl_view.cc`.
|
||||
- [x] GTK4 compositor sizing/orientation + output flip fixes in
|
||||
`engine/src/flutter/shell/platform/linux/fl_compositor_*`.
|
||||
- [x] GTK4 runner template updates in
|
||||
`packages/flutter_tools/templates/app/linux-gtk4.tmpl/*`.
|
||||
|
||||
## Window configure/state notifications (GTK4 signals)
|
||||
- [ ] Replace GTK3 `configure-event`/`window-state-event` in
|
||||
`engine/src/flutter/shell/platform/linux/fl_window_monitor.cc` with GTK4
|
||||
equivalents:
|
||||
- `GdkSurface::notify::width` / `notify::height` for size.
|
||||
- `GdkSurface::notify::state` for minimize/maximize/fullscreen.
|
||||
- [ ] Verify callbacks on Wayland and X11 (`on_configure`, `on_state_changed`).
|
||||
|
||||
## Defaults, compatibility, and tooling
|
||||
- [ ] Make GTK4 the default for new Linux apps (`flutter create --platforms=linux`).
|
||||
- [ ] Keep GTK3 opt-in (flag/env) and document the selection mechanism.
|
||||
- [ ] Decide how to keep/rename GTK3 runner templates (`linux-gtk3.tmpl`) so
|
||||
they’re available but not default.
|
||||
|
||||
## Accessibility (ATK removal)
|
||||
- [ ] Replace ATK-based implementation with GTK4 `GtkAccessible` (or bridge):
|
||||
- `engine/src/flutter/shell/platform/linux/fl_accessibility_handler.cc`
|
||||
- `engine/src/flutter/shell/platform/linux/fl_view_accessible.*`
|
||||
- `engine/src/flutter/shell/platform/linux/fl_accessible_*`
|
||||
- [ ] Update ATK-based tests to GTK4 equivalents in
|
||||
`engine/src/flutter/shell/platform/linux/*_test.cc`.
|
||||
|
||||
## GTK4 test coverage
|
||||
- [ ] Add GTK4 mocks in `engine/src/flutter/shell/platform/linux/testing/mock_gtk.*`
|
||||
for `GdkSurface` and GTK4-only APIs.
|
||||
- [ ] Add a GTK4 CI shard/build step in `dev/bots/test.dart` for
|
||||
`flutter_linux_unittests` with `use_gtk4=true`.
|
||||
|
||||
## Plugins & registrant
|
||||
- [ ] Audit `packages/flutter_tools/templates/plugin/linux-gtk4.tmpl/*`
|
||||
for deprecated GTK3 APIs and verify example builds.
|
||||
- [ ] Validate `flutter_linux` plugin registrar types against GTK4.
|
||||
@ -8,6 +8,7 @@ import("//flutter/examples/examples.gni")
|
||||
import("//flutter/shell/config.gni")
|
||||
import("//flutter/shell/platform/config.gni")
|
||||
import("//flutter/shell/platform/glfw/config.gni")
|
||||
import("//flutter/shell/platform/linux/config.gni")
|
||||
import("//flutter/testing/testing.gni")
|
||||
import("$dart_src/build/dart/copy_tree.gni")
|
||||
|
||||
@ -280,7 +281,7 @@ group("unittests") {
|
||||
public_deps += [ "//flutter/shell/platform/glfw/client_wrapper:client_wrapper_glfw_unittests" ]
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
if (is_linux && !use_gtk4) {
|
||||
public_deps +=
|
||||
[ "//flutter/shell/platform/linux:flutter_linux_unittests" ]
|
||||
if (build_glfw_shell) {
|
||||
|
||||
@ -6,6 +6,7 @@ assert(is_linux)
|
||||
|
||||
import("//flutter/build/zip_bundle.gni")
|
||||
import("//flutter/shell/platform/glfw/config.gni")
|
||||
import("//flutter/shell/platform/linux/config.gni")
|
||||
import("//flutter/testing/testing.gni")
|
||||
|
||||
group("linux") {
|
||||
@ -81,11 +82,20 @@ config("disable_warnings") {
|
||||
cflags_cc = warning_flags
|
||||
}
|
||||
|
||||
if (use_gtk4) {
|
||||
_gtk_config = "//flutter/shell/platform/linux/config:gtk4"
|
||||
_gtk_defines = [ "FLUTTER_LINUX_GTK4" ]
|
||||
} else {
|
||||
_gtk_config = "//flutter/shell/platform/linux/config:gtk"
|
||||
_gtk_defines = []
|
||||
}
|
||||
|
||||
source_set("flutter_linux_sources") {
|
||||
public = _public_headers + [
|
||||
"fl_binary_messenger_private.h",
|
||||
"fl_dart_project_private.h",
|
||||
"fl_engine_private.h",
|
||||
"fl_gtk.h",
|
||||
"fl_key_channel_responder.h",
|
||||
"fl_key_embedder_responder.h",
|
||||
"fl_key_embedder_responder_private.h",
|
||||
@ -104,15 +114,13 @@ source_set("flutter_linux_sources") {
|
||||
"key_mapping.h",
|
||||
]
|
||||
|
||||
configs += [ "//flutter/shell/platform/linux/config:gtk" ]
|
||||
configs += [ _gtk_config ]
|
||||
|
||||
public_configs = [ ":disable_warnings" ]
|
||||
|
||||
sources = [
|
||||
"fl_accessibility_channel.cc",
|
||||
"fl_accessibility_handler.cc",
|
||||
"fl_accessible_node.cc",
|
||||
"fl_accessible_text_field.cc",
|
||||
"fl_application.cc",
|
||||
"fl_basic_message_channel.cc",
|
||||
"fl_binary_codec.cc",
|
||||
@ -156,7 +164,6 @@ source_set("flutter_linux_sources") {
|
||||
"fl_settings_channel.cc",
|
||||
"fl_settings_handler.cc",
|
||||
"fl_settings_portal.cc",
|
||||
"fl_socket_accessible.cc",
|
||||
"fl_standard_message_codec.cc",
|
||||
"fl_standard_method_codec.cc",
|
||||
"fl_string_codec.cc",
|
||||
@ -169,17 +176,25 @@ source_set("flutter_linux_sources") {
|
||||
"fl_touch_manager.cc",
|
||||
"fl_value.cc",
|
||||
"fl_view.cc",
|
||||
"fl_view_accessible.cc",
|
||||
"fl_window_monitor.cc",
|
||||
"fl_window_state_monitor.cc",
|
||||
"key_mapping.g.cc",
|
||||
]
|
||||
|
||||
if (!use_gtk4) {
|
||||
sources += [
|
||||
"fl_accessible_node.cc",
|
||||
"fl_accessible_text_field.cc",
|
||||
"fl_socket_accessible.cc",
|
||||
"fl_view_accessible.cc",
|
||||
]
|
||||
}
|
||||
|
||||
# Set flag to stop headers being directly included (library users should not do this)
|
||||
defines = [
|
||||
"FLUTTER_LINUX_COMPILATION",
|
||||
"FLUTTER_ENGINE_NO_PROTOTYPES",
|
||||
]
|
||||
"FLUTTER_LINUX_COMPILATION",
|
||||
"FLUTTER_ENGINE_NO_PROTOTYPES",
|
||||
] + _gtk_defines
|
||||
|
||||
deps = [
|
||||
"$dart_src/runtime:dart_api",
|
||||
@ -195,7 +210,7 @@ source_set("flutter_linux_sources") {
|
||||
|
||||
source_set("flutter_linux") {
|
||||
configs += [
|
||||
"//flutter/shell/platform/linux/config:gtk",
|
||||
_gtk_config,
|
||||
"//flutter/shell/platform/linux/config:epoxy",
|
||||
]
|
||||
|
||||
@ -217,95 +232,97 @@ copy("flutter_linux_gschemas") {
|
||||
outputs = [ "$target_gen_dir/assets/{{source_name_part}}/gschemas.compiled" ]
|
||||
}
|
||||
|
||||
executable("flutter_linux_unittests") {
|
||||
testonly = true
|
||||
if (!use_gtk4) {
|
||||
executable("flutter_linux_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"fl_accessibility_handler_test.cc",
|
||||
"fl_accessible_node_test.cc",
|
||||
"fl_accessible_text_field_test.cc",
|
||||
"fl_application_test.cc",
|
||||
"fl_basic_message_channel_test.cc",
|
||||
"fl_binary_codec_test.cc",
|
||||
"fl_binary_messenger_test.cc",
|
||||
"fl_compositor_opengl_test.cc",
|
||||
"fl_compositor_software_test.cc",
|
||||
"fl_dart_project_test.cc",
|
||||
"fl_display_monitor_test.cc",
|
||||
"fl_engine_test.cc",
|
||||
"fl_event_channel_test.cc",
|
||||
"fl_framebuffer_test.cc",
|
||||
"fl_gnome_settings_test.cc",
|
||||
"fl_json_message_codec_test.cc",
|
||||
"fl_json_method_codec_test.cc",
|
||||
"fl_key_channel_responder_test.cc",
|
||||
"fl_key_embedder_responder_test.cc",
|
||||
"fl_keyboard_handler_test.cc",
|
||||
"fl_keyboard_layout_test.cc",
|
||||
"fl_keyboard_manager_test.cc",
|
||||
"fl_message_codec_test.cc",
|
||||
"fl_method_channel_test.cc",
|
||||
"fl_method_codec_test.cc",
|
||||
"fl_method_response_test.cc",
|
||||
"fl_pixel_buffer_texture_test.cc",
|
||||
"fl_platform_channel_test.cc",
|
||||
"fl_platform_handler_test.cc",
|
||||
"fl_plugin_registrar_test.cc",
|
||||
"fl_pointer_manager_test.cc",
|
||||
"fl_scrolling_manager_test.cc",
|
||||
"fl_settings_handler_test.cc",
|
||||
"fl_settings_portal_test.cc",
|
||||
"fl_standard_message_codec_test.cc",
|
||||
"fl_standard_method_codec_test.cc",
|
||||
"fl_string_codec_test.cc",
|
||||
"fl_text_input_handler_test.cc",
|
||||
"fl_texture_gl_test.cc",
|
||||
"fl_texture_registrar_test.cc",
|
||||
"fl_touch_manager_test.cc",
|
||||
"fl_value_test.cc",
|
||||
"fl_view_accessible_test.cc",
|
||||
"fl_view_test.cc",
|
||||
"fl_window_state_monitor_test.cc",
|
||||
"key_mapping_test.cc",
|
||||
"testing/fl_mock_binary_messenger.cc",
|
||||
"testing/fl_test.cc",
|
||||
"testing/fl_test_gtk_logs.cc",
|
||||
"testing/fl_test_gtk_logs.h",
|
||||
"testing/mock_engine.cc",
|
||||
"testing/mock_epoxy.cc",
|
||||
"testing/mock_gtk.cc",
|
||||
"testing/mock_plugin_registrar.cc",
|
||||
"testing/mock_renderable.cc",
|
||||
"testing/mock_settings.cc",
|
||||
"testing/mock_signal_handler.cc",
|
||||
"testing/mock_texture_registrar.cc",
|
||||
]
|
||||
sources = [
|
||||
"fl_accessibility_handler_test.cc",
|
||||
"fl_accessible_node_test.cc",
|
||||
"fl_accessible_text_field_test.cc",
|
||||
"fl_application_test.cc",
|
||||
"fl_basic_message_channel_test.cc",
|
||||
"fl_binary_codec_test.cc",
|
||||
"fl_binary_messenger_test.cc",
|
||||
"fl_compositor_opengl_test.cc",
|
||||
"fl_compositor_software_test.cc",
|
||||
"fl_dart_project_test.cc",
|
||||
"fl_display_monitor_test.cc",
|
||||
"fl_engine_test.cc",
|
||||
"fl_event_channel_test.cc",
|
||||
"fl_framebuffer_test.cc",
|
||||
"fl_gnome_settings_test.cc",
|
||||
"fl_json_message_codec_test.cc",
|
||||
"fl_json_method_codec_test.cc",
|
||||
"fl_key_channel_responder_test.cc",
|
||||
"fl_key_embedder_responder_test.cc",
|
||||
"fl_keyboard_handler_test.cc",
|
||||
"fl_keyboard_layout_test.cc",
|
||||
"fl_keyboard_manager_test.cc",
|
||||
"fl_message_codec_test.cc",
|
||||
"fl_method_channel_test.cc",
|
||||
"fl_method_codec_test.cc",
|
||||
"fl_method_response_test.cc",
|
||||
"fl_pixel_buffer_texture_test.cc",
|
||||
"fl_platform_channel_test.cc",
|
||||
"fl_platform_handler_test.cc",
|
||||
"fl_plugin_registrar_test.cc",
|
||||
"fl_pointer_manager_test.cc",
|
||||
"fl_scrolling_manager_test.cc",
|
||||
"fl_settings_handler_test.cc",
|
||||
"fl_settings_portal_test.cc",
|
||||
"fl_standard_message_codec_test.cc",
|
||||
"fl_standard_method_codec_test.cc",
|
||||
"fl_string_codec_test.cc",
|
||||
"fl_text_input_handler_test.cc",
|
||||
"fl_texture_gl_test.cc",
|
||||
"fl_texture_registrar_test.cc",
|
||||
"fl_touch_manager_test.cc",
|
||||
"fl_value_test.cc",
|
||||
"fl_view_accessible_test.cc",
|
||||
"fl_view_test.cc",
|
||||
"fl_window_state_monitor_test.cc",
|
||||
"key_mapping_test.cc",
|
||||
"testing/fl_mock_binary_messenger.cc",
|
||||
"testing/fl_test.cc",
|
||||
"testing/fl_test_gtk_logs.cc",
|
||||
"testing/fl_test_gtk_logs.h",
|
||||
"testing/mock_engine.cc",
|
||||
"testing/mock_epoxy.cc",
|
||||
"testing/mock_gtk.cc",
|
||||
"testing/mock_plugin_registrar.cc",
|
||||
"testing/mock_renderable.cc",
|
||||
"testing/mock_settings.cc",
|
||||
"testing/mock_signal_handler.cc",
|
||||
"testing/mock_texture_registrar.cc",
|
||||
]
|
||||
|
||||
public_configs = [ "//flutter:config" ]
|
||||
public_configs = [ "//flutter:config" ]
|
||||
|
||||
configs += [
|
||||
"//flutter/shell/platform/linux/config:gtk",
|
||||
"//flutter/shell/platform/linux/config:epoxy",
|
||||
]
|
||||
configs += [
|
||||
_gtk_config,
|
||||
"//flutter/shell/platform/linux/config:epoxy",
|
||||
]
|
||||
|
||||
defines = [
|
||||
"FLUTTER_ENGINE_NO_PROTOTYPES",
|
||||
defines = [
|
||||
"FLUTTER_ENGINE_NO_PROTOTYPES",
|
||||
|
||||
# Set flag to allow public headers to be directly included
|
||||
# (library users should not do this)
|
||||
"FLUTTER_LINUX_COMPILATION",
|
||||
]
|
||||
# Set flag to allow public headers to be directly included
|
||||
# (library users should not do this)
|
||||
"FLUTTER_LINUX_COMPILATION",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":flutter_linux_fixtures",
|
||||
":flutter_linux_gschemas",
|
||||
":flutter_linux_sources",
|
||||
"//flutter/runtime:libdart",
|
||||
"//flutter/shell/platform/common:common_cpp_enums",
|
||||
"//flutter/shell/platform/embedder:embedder_headers",
|
||||
"//flutter/shell/platform/embedder:embedder_test_utils",
|
||||
"//flutter/testing",
|
||||
]
|
||||
deps = [
|
||||
":flutter_linux_fixtures",
|
||||
":flutter_linux_gschemas",
|
||||
":flutter_linux_sources",
|
||||
"//flutter/runtime:libdart",
|
||||
"//flutter/shell/platform/common:common_cpp_enums",
|
||||
"//flutter/shell/platform/embedder:embedder_headers",
|
||||
"//flutter/shell/platform/embedder:embedder_test_utils",
|
||||
"//flutter/testing",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
shared_library("flutter_linux_gtk") {
|
||||
|
||||
8
engine/src/flutter/shell/platform/linux/config.gni
Normal file
8
engine/src/flutter/shell/platform/linux/config.gni
Normal file
@ -0,0 +1,8 @@
|
||||
# Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
declare_args() {
|
||||
# Build the Linux embedder against GTK4 instead of GTK3.
|
||||
use_gtk4 = false
|
||||
}
|
||||
@ -14,6 +14,10 @@ _pkg_configs = [
|
||||
name = "gtk"
|
||||
package = "gtk+-3.0"
|
||||
},
|
||||
{
|
||||
name = "gtk4"
|
||||
package = "gtk4"
|
||||
},
|
||||
{
|
||||
name = "egl"
|
||||
package = "egl"
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_accessibility_channel.h"
|
||||
#include "flutter/shell/platform/linux/fl_engine_private.h"
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include "flutter/shell/platform/linux/fl_view_private.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
GWeakRef engine;
|
||||
@ -22,6 +24,14 @@ static void send_announcement(int64_t view_id,
|
||||
FlTextDirection text_direction,
|
||||
FlAssertiveness assertiveness,
|
||||
gpointer user_data) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
(void)view_id;
|
||||
(void)message;
|
||||
(void)text_direction;
|
||||
(void)assertiveness;
|
||||
(void)user_data;
|
||||
return;
|
||||
#else
|
||||
FlAccessibilityHandler* self = FL_ACCESSIBILITY_HANDLER(user_data);
|
||||
FlAccessibilityHandlerPrivate* priv =
|
||||
reinterpret_cast<FlAccessibilityHandlerPrivate*>(
|
||||
@ -41,6 +51,7 @@ static void send_announcement(int64_t view_id,
|
||||
FlViewAccessible* accessible = fl_view_get_accessible(view);
|
||||
fl_view_accessible_send_announcement(
|
||||
accessible, message, assertiveness == FL_ASSERTIVENESS_ASSERTIVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void fl_accessibility_handler_dispose(GObject* object) {
|
||||
|
||||
@ -5,9 +5,11 @@
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_application.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_engine_private.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h"
|
||||
@ -34,7 +36,12 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication,
|
||||
|
||||
// Called when the first frame is received.
|
||||
static void first_frame_cb(FlApplication* self, FlView* view) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkRoot* root = gtk_widget_get_root(GTK_WIDGET(view));
|
||||
GtkWidget* window = root != nullptr ? GTK_WIDGET(root) : nullptr;
|
||||
#else
|
||||
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
|
||||
#endif
|
||||
|
||||
// Show the main window.
|
||||
if (window != nullptr && GTK_IS_WINDOW(window)) {
|
||||
@ -61,6 +68,7 @@ static GtkWindow* fl_application_create_window(FlApplication* self,
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(window));
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
@ -68,15 +76,24 @@ static GtkWindow* fl_application_create_window(FlApplication* self,
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_header_bar_set_show_title_buttons(header_bar, TRUE);
|
||||
#else
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
#endif
|
||||
gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header_bar));
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_window_set_child(GTK_WINDOW(window), GTK_WIDGET(view));
|
||||
#else
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
#endif
|
||||
|
||||
return GTK_WINDOW(window);
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ gboolean fl_compositor_present_layers(FlCompositor* self,
|
||||
|
||||
gboolean fl_compositor_render(FlCompositor* self,
|
||||
cairo_t* cr,
|
||||
GdkWindow* window) {
|
||||
FlGdkSurface* surface) {
|
||||
g_return_val_if_fail(FL_IS_COMPOSITOR(self), FALSE);
|
||||
return FL_COMPOSITOR_GET_CLASS(self)->render(self, cr, window);
|
||||
return FL_COMPOSITOR_GET_CLASS(self)->render(self, cr, surface);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/linux/fl_gtk.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -21,7 +22,9 @@ struct _FlCompositorClass {
|
||||
const FlutterLayer** layers,
|
||||
size_t layers_count);
|
||||
|
||||
gboolean (*render)(FlCompositor* compositor, cairo_t* cr, GdkWindow* window);
|
||||
gboolean (*render)(FlCompositor* compositor,
|
||||
cairo_t* cr,
|
||||
FlGdkSurface* surface);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -48,7 +51,7 @@ gboolean fl_compositor_present_layers(FlCompositor* compositor,
|
||||
* fl_compositor_render:
|
||||
* @compositor: an #FlCompositor.
|
||||
* @cr: a Cairo rendering context.
|
||||
* @window: window being rendered into.
|
||||
* @surface: surface being rendered into.
|
||||
*
|
||||
* Renders the current frame. Called from the GTK thread.
|
||||
*
|
||||
@ -56,7 +59,7 @@ gboolean fl_compositor_present_layers(FlCompositor* compositor,
|
||||
*/
|
||||
gboolean fl_compositor_render(FlCompositor* compositor,
|
||||
cairo_t* cr,
|
||||
GdkWindow* window);
|
||||
FlGdkSurface* surface);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@ -11,8 +11,10 @@
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/linux/fl_engine_private.h"
|
||||
#include "flutter/shell/platform/linux/fl_framebuffer.h"
|
||||
#include "flutter/shell/platform/linux/fl_gtk.h"
|
||||
|
||||
// Vertex shader to draw Flutter window contents.
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static const char* vertex_shader_src =
|
||||
"attribute vec2 position;\n"
|
||||
"attribute vec2 in_texcoord;\n"
|
||||
@ -24,6 +26,19 @@ static const char* vertex_shader_src =
|
||||
" gl_Position = vec4(offset + position * scale, 0, 1);\n"
|
||||
" texcoord = in_texcoord;\n"
|
||||
"}\n";
|
||||
#else
|
||||
static const char* vertex_shader_src =
|
||||
"attribute vec2 position;\n"
|
||||
"attribute vec2 in_texcoord;\n"
|
||||
"uniform vec2 offset;\n"
|
||||
"uniform vec2 scale;\n"
|
||||
"varying vec2 texcoord;\n"
|
||||
"\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = vec4(offset + position * scale, 0, 1);\n"
|
||||
" texcoord = in_texcoord;\n"
|
||||
"}\n";
|
||||
#endif
|
||||
|
||||
// Fragment shader to draw Flutter window contents.
|
||||
static const char* fragment_shader_src =
|
||||
@ -363,7 +378,7 @@ static gboolean fl_compositor_opengl_present_layers(FlCompositor* compositor,
|
||||
|
||||
static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
|
||||
cairo_t* cr,
|
||||
GdkWindow* window) {
|
||||
FlGdkSurface* surface) {
|
||||
FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
|
||||
|
||||
g_mutex_lock(&self->frame_mutex);
|
||||
@ -373,9 +388,22 @@ static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
|
||||
}
|
||||
|
||||
// If frame not ready, then wait for it.
|
||||
gint scale_factor = gdk_window_get_scale_factor(window);
|
||||
size_t width = gdk_window_get_width(window) * scale_factor;
|
||||
size_t height = gdk_window_get_height(window) * scale_factor;
|
||||
gint scale_factor = fl_gtk_surface_get_scale_factor(surface);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
// In GTK4, the draw surface is the toplevel. Use the Cairo clip
|
||||
// extents to get the drawing area size for this widget.
|
||||
double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
|
||||
cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
|
||||
size_t width = static_cast<size_t>(x2 - x1);
|
||||
size_t height = static_cast<size_t>(y2 - y1);
|
||||
if (width == 0 || height == 0) {
|
||||
width = fl_gtk_surface_get_width(surface);
|
||||
height = fl_gtk_surface_get_height(surface);
|
||||
}
|
||||
#else
|
||||
size_t width = fl_gtk_surface_get_width(surface) * scale_factor;
|
||||
size_t height = fl_gtk_surface_get_height(surface) * scale_factor;
|
||||
#endif
|
||||
while (fl_framebuffer_get_width(self->framebuffer) != width ||
|
||||
fl_framebuffer_get_height(self->framebuffer) != height) {
|
||||
g_mutex_unlock(&self->frame_mutex);
|
||||
@ -386,8 +414,18 @@ static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
|
||||
if (fl_framebuffer_get_shareable(self->framebuffer)) {
|
||||
g_autoptr(FlFramebuffer) sibling =
|
||||
fl_framebuffer_create_sibling(self->framebuffer);
|
||||
gdk_cairo_draw_from_gl(cr, window, fl_framebuffer_get_texture_id(sibling),
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
cairo_save(cr);
|
||||
cairo_translate(cr, 0.0, static_cast<double>(height));
|
||||
cairo_scale(cr, 1.0, -1.0);
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
#endif
|
||||
gdk_cairo_draw_from_gl(cr, surface, fl_framebuffer_get_texture_id(sibling),
|
||||
GL_TEXTURE, scale_factor, 0, 0, width, height);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
cairo_restore(cr);
|
||||
#endif
|
||||
} else {
|
||||
GLint saved_texture_binding;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
|
||||
@ -398,8 +436,18 @@ static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, self->pixels);
|
||||
|
||||
gdk_cairo_draw_from_gl(cr, window, texture_id, GL_TEXTURE, scale_factor, 0,
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
cairo_save(cr);
|
||||
cairo_translate(cr, 0.0, static_cast<double>(height));
|
||||
cairo_scale(cr, 1.0, -1.0);
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
#endif
|
||||
gdk_cairo_draw_from_gl(cr, surface, texture_id, GL_TEXTURE, scale_factor, 0,
|
||||
0, width, height);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
cairo_restore(cr);
|
||||
#endif
|
||||
|
||||
glDeleteTextures(1, &texture_id);
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
#include "fl_compositor_software.h"
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_gtk.h"
|
||||
|
||||
struct _FlCompositorSoftware {
|
||||
FlCompositor parent_instance;
|
||||
|
||||
@ -70,7 +72,7 @@ static gboolean fl_compositor_software_present_layers(
|
||||
|
||||
static gboolean fl_compositor_software_render(FlCompositor* compositor,
|
||||
cairo_t* cr,
|
||||
GdkWindow* window) {
|
||||
FlGdkSurface* surface) {
|
||||
FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(compositor);
|
||||
|
||||
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
|
||||
@ -80,9 +82,22 @@ static gboolean fl_compositor_software_render(FlCompositor* compositor,
|
||||
}
|
||||
|
||||
// If frame not ready, then wait for it.
|
||||
gint scale_factor = gdk_window_get_scale_factor(window);
|
||||
size_t width = gdk_window_get_width(window) * scale_factor;
|
||||
size_t height = gdk_window_get_height(window) * scale_factor;
|
||||
gint scale_factor = fl_gtk_surface_get_scale_factor(surface);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
// In GTK4, the draw surface is the toplevel. Use the Cairo clip
|
||||
// extents to get the drawing area size for this widget.
|
||||
double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
|
||||
cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
|
||||
size_t width = static_cast<size_t>(x2 - x1);
|
||||
size_t height = static_cast<size_t>(y2 - y1);
|
||||
if (width == 0 || height == 0) {
|
||||
width = fl_gtk_surface_get_width(surface);
|
||||
height = fl_gtk_surface_get_height(surface);
|
||||
}
|
||||
#else
|
||||
size_t width = fl_gtk_surface_get_width(surface) * scale_factor;
|
||||
size_t height = fl_gtk_surface_get_height(surface) * scale_factor;
|
||||
#endif
|
||||
while (self->width != width || self->height != height) {
|
||||
g_mutex_unlock(&self->frame_mutex);
|
||||
fl_task_runner_wait(self->task_runner);
|
||||
|
||||
@ -30,13 +30,22 @@ static void notify_display_update(FlDisplayMonitor* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GListModel* monitors = gdk_display_get_monitors(self->display);
|
||||
guint n_monitors = g_list_model_get_n_items(monitors);
|
||||
#else
|
||||
int n_monitors = gdk_display_get_n_monitors(self->display);
|
||||
#endif
|
||||
g_autofree FlutterEngineDisplay* displays =
|
||||
g_new0(FlutterEngineDisplay, n_monitors);
|
||||
for (int i = 0; i < n_monitors; i++) {
|
||||
for (guint i = 0; i < n_monitors; i++) {
|
||||
FlutterEngineDisplay* display = &displays[i];
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkMonitor* monitor = GDK_MONITOR(g_list_model_get_item(monitors, i));
|
||||
#else
|
||||
GdkMonitor* monitor = gdk_display_get_monitor(self->display, i);
|
||||
#endif
|
||||
FlutterEngineDisplayId display_id = GPOINTER_TO_INT(
|
||||
g_hash_table_lookup(self->display_ids_by_monitor, monitor));
|
||||
if (display_id == 0) {
|
||||
@ -56,11 +65,16 @@ static void notify_display_update(FlDisplayMonitor* self) {
|
||||
display->width = geometry.width;
|
||||
display->height = geometry.height;
|
||||
display->device_pixel_ratio = gdk_monitor_get_scale_factor(monitor);
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_object_unref(monitor);
|
||||
#endif
|
||||
}
|
||||
|
||||
fl_engine_notify_display_update(engine, displays, n_monitors);
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static void monitor_added_cb(FlDisplayMonitor* self, GdkMonitor* monitor) {
|
||||
notify_display_update(self);
|
||||
}
|
||||
@ -69,6 +83,50 @@ static void monitor_removed_cb(FlDisplayMonitor* self, GdkMonitor* monitor) {
|
||||
g_hash_table_remove(self->display_ids_by_monitor, monitor);
|
||||
notify_display_update(self);
|
||||
}
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static void prune_display_ids_for_current_monitors(FlDisplayMonitor* self) {
|
||||
GListModel* monitors = gdk_display_get_monitors(self->display);
|
||||
guint n_monitors = g_list_model_get_n_items(monitors);
|
||||
|
||||
GHashTable* current = g_hash_table_new(g_direct_hash, g_direct_equal);
|
||||
for (guint i = 0; i < n_monitors; i++) {
|
||||
GdkMonitor* monitor = GDK_MONITOR(g_list_model_get_item(monitors, i));
|
||||
g_hash_table_add(current, monitor);
|
||||
}
|
||||
|
||||
GHashTableIter iter;
|
||||
gpointer key = nullptr;
|
||||
g_hash_table_iter_init(&iter, self->display_ids_by_monitor);
|
||||
while (g_hash_table_iter_next(&iter, &key, nullptr)) {
|
||||
if (!g_hash_table_contains(current, key)) {
|
||||
g_hash_table_iter_remove(&iter);
|
||||
}
|
||||
}
|
||||
|
||||
GHashTableIter current_iter;
|
||||
g_hash_table_iter_init(¤t_iter, current);
|
||||
while (g_hash_table_iter_next(¤t_iter, &key, nullptr)) {
|
||||
g_object_unref(G_OBJECT(key));
|
||||
}
|
||||
g_hash_table_unref(current);
|
||||
}
|
||||
|
||||
static void monitors_changed_cb(GListModel* list,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added,
|
||||
gpointer user_data) {
|
||||
(void)list;
|
||||
(void)position;
|
||||
(void)removed;
|
||||
(void)added;
|
||||
FlDisplayMonitor* self = FL_DISPLAY_MONITOR(user_data);
|
||||
prune_display_ids_for_current_monitors(self);
|
||||
notify_display_update(self);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void fl_display_monitor_dispose(GObject* object) {
|
||||
FlDisplayMonitor* self = FL_DISPLAY_MONITOR(object);
|
||||
@ -103,12 +161,19 @@ FlDisplayMonitor* fl_display_monitor_new(FlEngine* engine,
|
||||
void fl_display_monitor_start(FlDisplayMonitor* self) {
|
||||
g_return_if_fail(FL_IS_DISPLAY_MONITOR(self));
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GListModel* monitors = gdk_display_get_monitors(self->display);
|
||||
g_signal_connect_object(monitors, "items-changed",
|
||||
G_CALLBACK(monitors_changed_cb), self,
|
||||
static_cast<GConnectFlags>(0));
|
||||
#else
|
||||
g_signal_connect_object(self->display, "monitor-added",
|
||||
G_CALLBACK(monitor_added_cb), self,
|
||||
G_CONNECT_SWAPPED);
|
||||
g_signal_connect_object(self->display, "monitor-removed",
|
||||
G_CALLBACK(monitor_removed_cb), self,
|
||||
G_CONNECT_SWAPPED);
|
||||
#endif
|
||||
notify_display_update(self);
|
||||
}
|
||||
|
||||
|
||||
91
engine/src/flutter/shell/platform/linux/fl_gtk.h
Normal file
91
engine/src/flutter/shell/platform/linux/fl_gtk.h
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_LINUX_FL_GTK_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GTK_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
typedef GdkSurface FlGdkSurface;
|
||||
|
||||
static inline FlGdkSurface* fl_gtk_widget_get_surface(GtkWidget* widget) {
|
||||
GtkNative* native = gtk_widget_get_native(widget);
|
||||
return native != nullptr ? gtk_native_get_surface(native) : nullptr;
|
||||
}
|
||||
|
||||
static inline GdkDisplay* fl_gtk_surface_get_display(FlGdkSurface* surface) {
|
||||
return gdk_surface_get_display(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_scale_factor(FlGdkSurface* surface) {
|
||||
return gdk_surface_get_scale_factor(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_width(FlGdkSurface* surface) {
|
||||
return gdk_surface_get_width(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_height(FlGdkSurface* surface) {
|
||||
return gdk_surface_get_height(surface);
|
||||
}
|
||||
|
||||
static inline GdkMonitor* fl_gtk_display_get_monitor_at_surface(
|
||||
GdkDisplay* display,
|
||||
FlGdkSurface* surface) {
|
||||
return gdk_display_get_monitor_at_surface(display, surface);
|
||||
}
|
||||
|
||||
static inline void fl_gtk_surface_set_cursor(FlGdkSurface* surface,
|
||||
GdkCursor* cursor) {
|
||||
gdk_surface_set_cursor(surface, cursor);
|
||||
}
|
||||
|
||||
static inline GdkGLContext* fl_gtk_surface_create_gl_context(
|
||||
FlGdkSurface* surface,
|
||||
GError** error) {
|
||||
return gdk_surface_create_gl_context(surface, error);
|
||||
}
|
||||
#else
|
||||
typedef GdkWindow FlGdkSurface;
|
||||
|
||||
static inline FlGdkSurface* fl_gtk_widget_get_surface(GtkWidget* widget) {
|
||||
return gtk_widget_get_window(widget);
|
||||
}
|
||||
|
||||
static inline GdkDisplay* fl_gtk_surface_get_display(FlGdkSurface* surface) {
|
||||
return gdk_window_get_display(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_scale_factor(FlGdkSurface* surface) {
|
||||
return gdk_window_get_scale_factor(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_width(FlGdkSurface* surface) {
|
||||
return gdk_window_get_width(surface);
|
||||
}
|
||||
|
||||
static inline gint fl_gtk_surface_get_height(FlGdkSurface* surface) {
|
||||
return gdk_window_get_height(surface);
|
||||
}
|
||||
|
||||
static inline GdkMonitor* fl_gtk_display_get_monitor_at_surface(
|
||||
GdkDisplay* display,
|
||||
FlGdkSurface* surface) {
|
||||
return gdk_display_get_monitor_at_window(display, surface);
|
||||
}
|
||||
|
||||
static inline void fl_gtk_surface_set_cursor(FlGdkSurface* surface,
|
||||
GdkCursor* cursor) {
|
||||
gdk_window_set_cursor(surface, cursor);
|
||||
}
|
||||
|
||||
static inline GdkGLContext* fl_gtk_surface_create_gl_context(
|
||||
FlGdkSurface* surface,
|
||||
GError** error) {
|
||||
return gdk_window_create_gl_context(surface, error);
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GTK_H_
|
||||
@ -107,8 +107,10 @@ void fl_key_channel_responder_handle_event(FlKeyChannelResponder* self,
|
||||
// interactions (for example, if shift-lock is on, tab traversal is broken).
|
||||
|
||||
// Remove lock states from state mask.
|
||||
guint state =
|
||||
fl_key_event_get_state(event) & ~(GDK_LOCK_MASK | GDK_MOD2_MASK);
|
||||
guint state = fl_key_event_get_state(event) & ~GDK_LOCK_MASK;
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
state &= ~GDK_MOD2_MASK;
|
||||
#endif
|
||||
|
||||
static bool shift_lock_pressed = FALSE;
|
||||
static bool caps_lock_pressed = FALSE;
|
||||
@ -128,7 +130,11 @@ void fl_key_channel_responder_handle_event(FlKeyChannelResponder* self,
|
||||
// Add back in the state matching the actual pressed state of the lock keys,
|
||||
// not the lock states.
|
||||
state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0;
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0;
|
||||
#else
|
||||
(void)num_lock_pressed;
|
||||
#endif
|
||||
|
||||
fl_key_event_channel_send(
|
||||
self->channel, type, scan_code, fl_key_event_get_keyval(event), state,
|
||||
|
||||
@ -59,19 +59,30 @@ FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* event) {
|
||||
nullptr);
|
||||
|
||||
guint16 keycode = 0;
|
||||
gdk_event_get_keycode(event, &keycode);
|
||||
guint keyval = 0;
|
||||
gdk_event_get_keyval(event, &keyval);
|
||||
GdkModifierType state = static_cast<GdkModifierType>(0);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
keycode = static_cast<guint16>(gdk_key_event_get_keycode(event));
|
||||
keyval = gdk_key_event_get_keyval(event);
|
||||
state = gdk_event_get_modifier_state(event);
|
||||
#else
|
||||
gdk_event_get_keycode(event, &keycode);
|
||||
gdk_event_get_keyval(event, &keyval);
|
||||
gdk_event_get_state(event, &state);
|
||||
#endif
|
||||
|
||||
self->time = gdk_event_get_time(event);
|
||||
self->is_press = type == GDK_KEY_PRESS;
|
||||
self->keycode = keycode;
|
||||
self->keyval = keyval;
|
||||
self->state = state;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
self->group = static_cast<guint8>(gdk_key_event_get_layout(event));
|
||||
self->origin = gdk_event_ref(event);
|
||||
#else
|
||||
self->group = event->key.group;
|
||||
self->origin = event;
|
||||
self->origin = gdk_event_copy(event);
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -114,7 +125,11 @@ GdkEvent* fl_key_event_get_origin(FlKeyEvent* self) {
|
||||
static void fl_key_event_dispose(GObject* object) {
|
||||
FlKeyEvent* self = FL_KEY_EVENT(object);
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_clear_pointer(&self->origin, gdk_event_unref);
|
||||
#else
|
||||
g_clear_pointer(&self->origin, gdk_event_free);
|
||||
#endif
|
||||
|
||||
G_OBJECT_CLASS(fl_key_event_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
@ -111,9 +111,13 @@ struct _FlKeyboardManager {
|
||||
std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
|
||||
logical_to_mandatory_goals;
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkDisplay* display;
|
||||
#else
|
||||
GdkKeymap* keymap;
|
||||
gulong keymap_keys_changed_cb_id; // Signal connection ID for
|
||||
// keymap-keys-changed
|
||||
#endif
|
||||
|
||||
GCancellable* cancellable;
|
||||
};
|
||||
@ -139,10 +143,12 @@ static gboolean event_is_redispatched(FlKeyboardManager* self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static void keymap_keys_changed_cb(FlKeyboardManager* self) {
|
||||
g_clear_object(&self->derived_layout);
|
||||
self->derived_layout = fl_keyboard_layout_new();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void complete_handle_event(FlKeyboardManager* self, GTask* task) {
|
||||
HandleEventData* data =
|
||||
@ -219,7 +225,19 @@ static uint16_t convert_key_to_char(FlKeyboardManager* self,
|
||||
if (self->lookup_key_handler != nullptr) {
|
||||
origin = self->lookup_key_handler(&key, self->lookup_key_handler_user_data);
|
||||
} else {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkModifierType state =
|
||||
level == 0 ? static_cast<GdkModifierType>(0) : GDK_SHIFT_MASK;
|
||||
guint keyval = 0;
|
||||
if (gdk_display_translate_key(self->display, keycode, state, group, &keyval,
|
||||
nullptr, nullptr, nullptr)) {
|
||||
origin = keyval;
|
||||
} else {
|
||||
origin = 0;
|
||||
}
|
||||
#else
|
||||
origin = gdk_keymap_lookup_key(self->keymap, &key);
|
||||
#endif
|
||||
}
|
||||
return origin < kBmpMax ? origin : 0xFFFF;
|
||||
}
|
||||
@ -318,10 +336,14 @@ static void fl_keyboard_manager_dispose(GObject* object) {
|
||||
g_clear_object(&self->key_embedder_responder);
|
||||
g_clear_object(&self->key_channel_responder);
|
||||
g_clear_object(&self->derived_layout);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_clear_object(&self->display);
|
||||
#else
|
||||
if (self->keymap_keys_changed_cb_id != 0) {
|
||||
g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
|
||||
self->keymap_keys_changed_cb_id = 0;
|
||||
}
|
||||
#endif
|
||||
g_clear_object(&self->cancellable);
|
||||
|
||||
G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object);
|
||||
@ -347,9 +369,13 @@ static void fl_keyboard_manager_init(FlKeyboardManager* self) {
|
||||
}
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
self->display = GDK_DISPLAY(g_object_ref(gdk_display_get_default()));
|
||||
#else
|
||||
self->keymap = gdk_keymap_get_for_display(gdk_display_get_default());
|
||||
self->keymap_keys_changed_cb_id = g_signal_connect_swapped(
|
||||
self->keymap, "keys-changed", G_CALLBACK(keymap_keys_changed_cb), self);
|
||||
#endif
|
||||
self->cancellable = g_cancellable_new();
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <epoxy/egl.h>
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#else
|
||||
#include <gdk/gdkwayland.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
|
||||
@ -34,6 +34,28 @@ struct _FlPlatformHandler {
|
||||
|
||||
G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT)
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
// Called when clipboard text received.
|
||||
static void clipboard_text_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
|
||||
g_autofree gchar* text =
|
||||
gdk_clipboard_read_text_finish(GDK_CLIPBOARD(object), result, nullptr);
|
||||
fl_platform_channel_respond_clipboard_get_data(method_call, text);
|
||||
}
|
||||
|
||||
// Called when clipboard text received during has_strings.
|
||||
static void clipboard_text_has_strings_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
|
||||
g_autofree gchar* text =
|
||||
gdk_clipboard_read_text_finish(GDK_CLIPBOARD(object), result, nullptr);
|
||||
fl_platform_channel_respond_clipboard_has_strings(
|
||||
method_call, text != nullptr && strlen(text) > 0);
|
||||
}
|
||||
#else
|
||||
// Called when clipboard text received.
|
||||
static void clipboard_text_cb(GtkClipboard* clipboard,
|
||||
const gchar* text,
|
||||
@ -50,14 +72,21 @@ static void clipboard_text_has_strings_cb(GtkClipboard* clipboard,
|
||||
fl_platform_channel_respond_clipboard_has_strings(
|
||||
method_call, text != nullptr && strlen(text) > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Called when Flutter wants to copy to the clipboard.
|
||||
static FlMethodResponse* clipboard_set_data(FlMethodCall* method_call,
|
||||
const gchar* text,
|
||||
gpointer user_data) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkClipboard* clipboard =
|
||||
gdk_display_get_clipboard(gdk_display_get_default());
|
||||
gdk_clipboard_set_text(clipboard, text);
|
||||
#else
|
||||
GtkClipboard* clipboard =
|
||||
gtk_clipboard_get_default(gdk_display_get_default());
|
||||
gtk_clipboard_set_text(clipboard, text, -1);
|
||||
#endif
|
||||
|
||||
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
||||
}
|
||||
@ -72,10 +101,17 @@ static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkClipboard* clipboard =
|
||||
gdk_display_get_clipboard(gdk_display_get_default());
|
||||
gdk_clipboard_read_text_async(clipboard, nullptr, clipboard_text_cb,
|
||||
g_object_ref(method_call));
|
||||
#else
|
||||
GtkClipboard* clipboard =
|
||||
gtk_clipboard_get_default(gdk_display_get_default());
|
||||
gtk_clipboard_request_text(clipboard, clipboard_text_cb,
|
||||
g_object_ref(method_call));
|
||||
#endif
|
||||
|
||||
// Will respond later.
|
||||
return nullptr;
|
||||
@ -85,10 +121,18 @@ static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call,
|
||||
// be pasted, without actually accessing the clipboard content itself.
|
||||
static FlMethodResponse* clipboard_has_strings(FlMethodCall* method_call,
|
||||
gpointer user_data) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkClipboard* clipboard =
|
||||
gdk_display_get_clipboard(gdk_display_get_default());
|
||||
gdk_clipboard_read_text_async(clipboard, nullptr,
|
||||
clipboard_text_has_strings_cb,
|
||||
g_object_ref(method_call));
|
||||
#else
|
||||
GtkClipboard* clipboard =
|
||||
gtk_clipboard_get_default(gdk_display_get_default());
|
||||
gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb,
|
||||
g_object_ref(method_call));
|
||||
#endif
|
||||
|
||||
// Will respond later.
|
||||
return nullptr;
|
||||
|
||||
@ -69,7 +69,7 @@ void fl_scrolling_manager_set_last_mouse_position(FlScrollingManager* self,
|
||||
}
|
||||
|
||||
void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self,
|
||||
GdkEventScroll* scroll_event,
|
||||
GdkEvent* event,
|
||||
gint scale_factor) {
|
||||
g_return_if_fail(FL_IS_SCROLLING_MANAGER(self));
|
||||
|
||||
@ -78,13 +78,33 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self,
|
||||
return;
|
||||
}
|
||||
|
||||
GdkEvent* event = reinterpret_cast<GdkEvent*>(scroll_event);
|
||||
|
||||
guint event_time = gdk_event_get_time(event);
|
||||
gdouble event_x = 0.0, event_y = 0.0;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gdk_event_get_position(event, &event_x, &event_y);
|
||||
#else
|
||||
gdk_event_get_coords(event, &event_x, &event_y);
|
||||
#endif
|
||||
gdouble scroll_delta_x = 0.0, scroll_delta_y = 0.0;
|
||||
GdkScrollDirection event_direction = GDK_SCROLL_SMOOTH;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
event_direction = gdk_scroll_event_get_direction(event);
|
||||
if (event_direction == GDK_SCROLL_UP) {
|
||||
scroll_delta_x = 0;
|
||||
scroll_delta_y = -1;
|
||||
} else if (event_direction == GDK_SCROLL_DOWN) {
|
||||
scroll_delta_x = 0;
|
||||
scroll_delta_y = 1;
|
||||
} else if (event_direction == GDK_SCROLL_LEFT) {
|
||||
scroll_delta_x = -1;
|
||||
scroll_delta_y = 0;
|
||||
} else if (event_direction == GDK_SCROLL_RIGHT) {
|
||||
scroll_delta_x = 1;
|
||||
scroll_delta_y = 0;
|
||||
} else {
|
||||
gdk_scroll_event_get_deltas(event, &scroll_delta_x, &scroll_delta_y);
|
||||
}
|
||||
#else
|
||||
if (gdk_event_get_scroll_direction(event, &event_direction)) {
|
||||
if (event_direction == GDK_SCROLL_UP) {
|
||||
scroll_delta_x = 0;
|
||||
@ -102,6 +122,7 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self,
|
||||
} else {
|
||||
gdk_event_get_scroll_deltas(event, &scroll_delta_x, &scroll_delta_y);
|
||||
}
|
||||
#endif
|
||||
|
||||
// The multiplier is taken from the Chromium source
|
||||
// (ui/events/x/events_x_utils.cc).
|
||||
@ -109,11 +130,19 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self,
|
||||
scroll_delta_x *= kScrollOffsetMultiplier * scale_factor;
|
||||
scroll_delta_y *= kScrollOffsetMultiplier * scale_factor;
|
||||
|
||||
if (gdk_device_get_source(gdk_event_get_source_device(event)) ==
|
||||
GDK_SOURCE_TOUCHPAD) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkDevice* source_device = gdk_event_get_device(event);
|
||||
#else
|
||||
GdkDevice* source_device = gdk_event_get_source_device(event);
|
||||
#endif
|
||||
if (gdk_device_get_source(source_device) == GDK_SOURCE_TOUCHPAD) {
|
||||
scroll_delta_x *= -1;
|
||||
scroll_delta_y *= -1;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
if (gdk_scroll_event_is_stop(event)) {
|
||||
#else
|
||||
if (gdk_event_is_scroll_stop_event(event)) {
|
||||
#endif
|
||||
fl_engine_send_pointer_pan_zoom_event(
|
||||
engine, self->view_id, event_time * kMicrosecondsPerMillisecond,
|
||||
event_x * scale_factor, event_y * scale_factor, kPanZoomEnd,
|
||||
|
||||
@ -52,7 +52,7 @@ void fl_scrolling_manager_set_last_mouse_position(FlScrollingManager* manager,
|
||||
* Inform the scrolling manager of a scroll event.
|
||||
*/
|
||||
void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* manager,
|
||||
GdkEventScroll* event,
|
||||
GdkEvent* event,
|
||||
gint scale_factor);
|
||||
|
||||
/**
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
TEST(FlScrollingManagerTest, DiscreteDirectional) {
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||
@ -44,7 +45,8 @@ TEST(FlScrollingManagerTest, DiscreteDirectional) {
|
||||
event->y = 8.0;
|
||||
event->device = mouse;
|
||||
event->direction = GDK_SCROLL_UP;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 1u);
|
||||
EXPECT_EQ(pointer_events[0].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[0].y, 8.0);
|
||||
@ -54,7 +56,8 @@ TEST(FlScrollingManagerTest, DiscreteDirectional) {
|
||||
EXPECT_EQ(pointer_events[0].scroll_delta_x, 0);
|
||||
EXPECT_EQ(pointer_events[0].scroll_delta_y, 53 * -1.0);
|
||||
event->direction = GDK_SCROLL_DOWN;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 2u);
|
||||
EXPECT_EQ(pointer_events[1].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[1].y, 8.0);
|
||||
@ -64,7 +67,8 @@ TEST(FlScrollingManagerTest, DiscreteDirectional) {
|
||||
EXPECT_EQ(pointer_events[1].scroll_delta_x, 0);
|
||||
EXPECT_EQ(pointer_events[1].scroll_delta_y, 53 * 1.0);
|
||||
event->direction = GDK_SCROLL_LEFT;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 3u);
|
||||
EXPECT_EQ(pointer_events[2].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[2].y, 8.0);
|
||||
@ -74,7 +78,8 @@ TEST(FlScrollingManagerTest, DiscreteDirectional) {
|
||||
EXPECT_EQ(pointer_events[2].scroll_delta_x, 53 * -1.0);
|
||||
EXPECT_EQ(pointer_events[2].scroll_delta_y, 0);
|
||||
event->direction = GDK_SCROLL_RIGHT;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 4u);
|
||||
EXPECT_EQ(pointer_events[3].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[3].y, 8.0);
|
||||
@ -119,7 +124,8 @@ TEST(FlScrollingManagerTest, DiscreteScrolling) {
|
||||
event->delta_y = 2.0;
|
||||
event->device = mouse;
|
||||
event->direction = GDK_SCROLL_SMOOTH;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 1u);
|
||||
EXPECT_EQ(pointer_events[0].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[0].y, 8.0);
|
||||
@ -164,7 +170,8 @@ TEST(FlScrollingManagerTest, Panning) {
|
||||
event->delta_y = 2.0;
|
||||
event->device = touchpad;
|
||||
event->direction = GDK_SCROLL_SMOOTH;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 2u);
|
||||
EXPECT_EQ(pointer_events[0].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[0].y, 8.0);
|
||||
@ -180,7 +187,8 @@ TEST(FlScrollingManagerTest, Panning) {
|
||||
EXPECT_EQ(pointer_events[1].pan_y, 53 * -2.0);
|
||||
EXPECT_EQ(pointer_events[1].scale, 1.0);
|
||||
EXPECT_EQ(pointer_events[1].rotation, 0.0);
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 3u);
|
||||
EXPECT_EQ(pointer_events[2].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[2].y, 8.0);
|
||||
@ -192,7 +200,8 @@ TEST(FlScrollingManagerTest, Panning) {
|
||||
EXPECT_EQ(pointer_events[2].scale, 1.0);
|
||||
EXPECT_EQ(pointer_events[2].rotation, 0.0);
|
||||
event->is_stop = true;
|
||||
fl_scrolling_manager_handle_scroll_event(manager, event, 1.0);
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
manager, reinterpret_cast<GdkEvent*>(event), 1.0);
|
||||
EXPECT_EQ(pointer_events.size(), 4u);
|
||||
EXPECT_EQ(pointer_events[3].x, 4.0);
|
||||
EXPECT_EQ(pointer_events[3].y, 8.0);
|
||||
@ -429,3 +438,5 @@ TEST(FlScrollingManagerTest, UnsynchronizedZoomingAndRotating) {
|
||||
EXPECT_EQ(pointer_events[4].phase, kPanZoomEnd);
|
||||
EXPECT_GE(pointer_events[4].timestamp, pointer_events[3].timestamp);
|
||||
}
|
||||
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
@ -216,9 +216,16 @@ static void im_preedit_end_cb(FlTextInputHandler* self) {
|
||||
// Signal handler for GtkIMContext::retrieve-surrounding
|
||||
static gboolean im_retrieve_surrounding_cb(FlTextInputHandler* self) {
|
||||
auto text = self->text_model->GetText();
|
||||
flutter::TextRange selection = self->text_model->selection();
|
||||
size_t cursor_offset = self->text_model->GetCursorOffset();
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_im_context_set_surrounding_with_selection(
|
||||
self->im_context, text.c_str(), -1, static_cast<int>(cursor_offset),
|
||||
static_cast<int>(selection.base()));
|
||||
#else
|
||||
gtk_im_context_set_surrounding(self->im_context, text.c_str(), -1,
|
||||
cursor_offset);
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -338,18 +345,32 @@ static void update_im_cursor_position(FlTextInputHandler* self) {
|
||||
|
||||
// Transform the x, y positions of the cursor from local coordinates to
|
||||
// Flutter view coordinates.
|
||||
gint x = self->composing_rect.x * self->editabletext_transform[0][0] +
|
||||
self->composing_rect.y * self->editabletext_transform[1][0] +
|
||||
self->editabletext_transform[3][0] + self->composing_rect.width;
|
||||
gint y = self->composing_rect.x * self->editabletext_transform[0][1] +
|
||||
self->composing_rect.y * self->editabletext_transform[1][1] +
|
||||
self->editabletext_transform[3][1] + self->composing_rect.height;
|
||||
double x = self->composing_rect.x * self->editabletext_transform[0][0] +
|
||||
self->composing_rect.y * self->editabletext_transform[1][0] +
|
||||
self->editabletext_transform[3][0] + self->composing_rect.width;
|
||||
double y = self->composing_rect.x * self->editabletext_transform[0][1] +
|
||||
self->composing_rect.y * self->editabletext_transform[1][1] +
|
||||
self->editabletext_transform[3][1] + self->composing_rect.height;
|
||||
|
||||
// Transform from Flutter view coordinates to GTK window coordinates.
|
||||
GdkRectangle preedit_rect = {};
|
||||
gtk_widget_translate_coordinates(self->widget,
|
||||
gtk_widget_get_toplevel(self->widget), x, y,
|
||||
&preedit_rect.x, &preedit_rect.y);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkWidget* toplevel = GTK_WIDGET(gtk_widget_get_root(self->widget));
|
||||
#else
|
||||
GtkWidget* toplevel = gtk_widget_get_toplevel(self->widget);
|
||||
#endif
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
double dest_x = 0.0;
|
||||
double dest_y = 0.0;
|
||||
gtk_widget_translate_coordinates(self->widget, toplevel, x, y, &dest_x,
|
||||
&dest_y);
|
||||
preedit_rect.x = static_cast<int>(dest_x);
|
||||
preedit_rect.y = static_cast<int>(dest_y);
|
||||
#else
|
||||
gtk_widget_translate_coordinates(self->widget, toplevel, static_cast<gint>(x),
|
||||
static_cast<gint>(y), &preedit_rect.x,
|
||||
&preedit_rect.y);
|
||||
#endif
|
||||
|
||||
// Set the cursor location in window coordinates so that GTK can position
|
||||
// any system input method windows.
|
||||
@ -480,8 +501,12 @@ void fl_text_input_handler_set_widget(FlTextInputHandler* self,
|
||||
GtkWidget* widget) {
|
||||
g_return_if_fail(FL_IS_TEXT_INPUT_HANDLER(self));
|
||||
self->widget = widget;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_im_context_set_client_widget(self->im_context, widget);
|
||||
#else
|
||||
gtk_im_context_set_client_window(self->im_context,
|
||||
gtk_widget_get_window(self->widget));
|
||||
#endif
|
||||
}
|
||||
|
||||
GtkWidget* fl_text_input_handler_get_widget(FlTextInputHandler* self) {
|
||||
@ -497,9 +522,13 @@ gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler* self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (gtk_im_context_filter_keypress(
|
||||
self->im_context,
|
||||
reinterpret_cast<GdkEventKey*>(fl_key_event_get_origin(event)))) {
|
||||
if (gtk_im_context_filter_keypress(self->im_context,
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
fl_key_event_get_origin(event))) {
|
||||
#else
|
||||
reinterpret_cast<GdkEventKey*>(
|
||||
fl_key_event_get_origin(event)))) {
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@ -129,7 +129,11 @@ static void release_number(_FlTouchManager* self, uint32_t number) {
|
||||
}
|
||||
|
||||
void fl_touch_manager_handle_touch_event(FlTouchManager* self,
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkEvent* event,
|
||||
#else
|
||||
GdkEventTouch* touch_event,
|
||||
#endif
|
||||
gint scale_factor) {
|
||||
g_return_if_fail(FL_IS_TOUCH_MANAGER(self));
|
||||
|
||||
@ -138,7 +142,9 @@ void fl_touch_manager_handle_touch_event(FlTouchManager* self,
|
||||
return;
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
GdkEvent* event = reinterpret_cast<GdkEvent*>(touch_event);
|
||||
#endif
|
||||
// get sequence id from GdkEvent
|
||||
GdkEventSequence* seq = gdk_event_get_event_sequence(event);
|
||||
// cast pointer to int to get unique id
|
||||
@ -150,7 +156,11 @@ void fl_touch_manager_handle_touch_event(FlTouchManager* self,
|
||||
static_cast<int32_t>(kFlutterPointerDeviceKindTouch) << 28 | touch_id;
|
||||
|
||||
gdouble event_x = 0.0, event_y = 0.0;
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gdk_event_get_position(event, &event_x, &event_y);
|
||||
#else
|
||||
gdk_event_get_coords(event, &event_x, &event_y);
|
||||
#endif
|
||||
|
||||
double x = event_x * scale_factor;
|
||||
double y = event_y * scale_factor;
|
||||
|
||||
@ -35,9 +35,15 @@ FlTouchManager* fl_touch_manager_new(FlEngine* engine, FlutterViewId view_id);
|
||||
* @event: the touch event.
|
||||
* @scale_factor: the GTK scaling factor of the window.
|
||||
*/
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
void fl_touch_manager_handle_touch_event(FlTouchManager* manager,
|
||||
GdkEvent* event,
|
||||
gint scale_factor);
|
||||
#else
|
||||
void fl_touch_manager_handle_touch_event(FlTouchManager* manager,
|
||||
GdkEventTouch* event,
|
||||
gint scale_factor);
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@ -4,25 +4,40 @@
|
||||
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include <atk/atk.h>
|
||||
#endif
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
#else
|
||||
#include <gdk/gdkwayland.h>
|
||||
#endif
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include <gtk/gtk-a11y.h>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "flutter/common/constants.h"
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include "flutter/shell/platform/linux/fl_accessible_node.h"
|
||||
#endif
|
||||
#include "flutter/shell/platform/linux/fl_compositor_opengl.h"
|
||||
#include "flutter/shell/platform/linux/fl_compositor_software.h"
|
||||
#include "flutter/shell/platform/linux/fl_engine_private.h"
|
||||
#include "flutter/shell/platform/linux/fl_gtk.h"
|
||||
#include "flutter/shell/platform/linux/fl_key_event.h"
|
||||
#include "flutter/shell/platform/linux/fl_opengl_manager.h"
|
||||
#include "flutter/shell/platform/linux/fl_plugin_registrar_private.h"
|
||||
#include "flutter/shell/platform/linux/fl_pointer_manager.h"
|
||||
#include "flutter/shell/platform/linux/fl_scrolling_manager.h"
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include "flutter/shell/platform/linux/fl_socket_accessible.h"
|
||||
#endif
|
||||
#include "flutter/shell/platform/linux/fl_touch_manager.h"
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include "flutter/shell/platform/linux/fl_view_accessible.h"
|
||||
#endif
|
||||
#include "flutter/shell/platform/linux/fl_view_private.h"
|
||||
#include "flutter/shell/platform/linux/fl_window_state_monitor.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
|
||||
@ -73,13 +88,24 @@ struct _FlView {
|
||||
// Manages touch events.
|
||||
FlTouchManager* touch_manager;
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
// Accessible tree from Flutter, exposed as an AtkPlug.
|
||||
FlViewAccessible* view_accessible;
|
||||
#endif
|
||||
|
||||
// Signal subscripton for cursor changes.
|
||||
guint cursor_changed_cb_id;
|
||||
|
||||
GCancellable* cancellable;
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkEventControllerMotion* motion_controller;
|
||||
GtkGestureClick* click_gesture;
|
||||
GtkEventControllerScroll* scroll_controller;
|
||||
GtkEventControllerKey* key_controller;
|
||||
GtkGestureZoom* zoom_gesture;
|
||||
GtkGestureRotate* rotate_gesture;
|
||||
#endif
|
||||
};
|
||||
|
||||
enum { SIGNAL_FIRST_FRAME, LAST_SIGNAL };
|
||||
@ -91,6 +117,14 @@ static void fl_renderable_iface_init(FlRenderableInterface* iface);
|
||||
static void fl_view_plugin_registry_iface_init(
|
||||
FlPluginRegistryInterface* iface);
|
||||
|
||||
static void log_once(bool* flag, const char* message) {
|
||||
if (*flag) {
|
||||
return;
|
||||
}
|
||||
*flag = true;
|
||||
g_warning("%s", message);
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(
|
||||
FlView,
|
||||
fl_view,
|
||||
@ -113,12 +147,38 @@ static gboolean redraw_cb(gpointer user_data) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Signal handler for GtkWidget::delete-event
|
||||
static GtkWidget* fl_view_get_toplevel_widget(FlView* self) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkRoot* root = gtk_widget_get_root(GTK_WIDGET(self));
|
||||
return root != nullptr ? GTK_WIDGET(root) : nullptr;
|
||||
#else
|
||||
return gtk_widget_get_toplevel(GTK_WIDGET(self));
|
||||
#endif
|
||||
}
|
||||
|
||||
static FlGdkSurface* fl_view_get_toplevel_surface(FlView* self) {
|
||||
GtkWidget* toplevel = fl_view_get_toplevel_widget(self);
|
||||
return toplevel != nullptr ? fl_gtk_widget_get_surface(toplevel) : nullptr;
|
||||
}
|
||||
|
||||
// Signal handler for GtkWidget::delete-event (GTK3 only)
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static gboolean window_delete_event_cb(FlView* self) {
|
||||
fl_engine_request_app_exit(self->engine);
|
||||
// Stop the event from propagating.
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
// Signal handler for GtkWindow::close-request.
|
||||
static gboolean window_close_request_cb(GtkWindow* window, FlView* self) {
|
||||
(void)window;
|
||||
fl_engine_request_app_exit(self->engine);
|
||||
// Allow the default handler to destroy the window if the engine doesn't.
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void init_scrolling(FlView* self) {
|
||||
g_clear_object(&self->scrolling_manager);
|
||||
@ -132,8 +192,26 @@ static void init_touch(FlView* self) {
|
||||
}
|
||||
|
||||
static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkDevice* device = gdk_event_get_device(event);
|
||||
#else
|
||||
GdkDevice* device = gdk_event_get_source_device(event);
|
||||
#endif
|
||||
GdkInputSource source = gdk_device_get_source(device);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
switch (source) {
|
||||
case GDK_SOURCE_PEN:
|
||||
case GDK_SOURCE_TABLET_PAD:
|
||||
return kFlutterPointerDeviceKindStylus;
|
||||
case GDK_SOURCE_TOUCHSCREEN:
|
||||
return kFlutterPointerDeviceKindTouch;
|
||||
case GDK_SOURCE_TOUCHPAD:
|
||||
case GDK_SOURCE_TRACKPOINT:
|
||||
case GDK_SOURCE_KEYBOARD:
|
||||
case GDK_SOURCE_MOUSE:
|
||||
return kFlutterPointerDeviceKindMouse;
|
||||
}
|
||||
#else
|
||||
switch (source) {
|
||||
case GDK_SOURCE_PEN:
|
||||
case GDK_SOURCE_ERASER:
|
||||
@ -148,6 +226,8 @@ static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) {
|
||||
case GDK_SOURCE_MOUSE:
|
||||
return kFlutterPointerDeviceKindMouse;
|
||||
}
|
||||
#endif
|
||||
return kFlutterPointerDeviceKindMouse;
|
||||
}
|
||||
|
||||
// Called when the mouse cursor changes.
|
||||
@ -155,11 +235,17 @@ static void cursor_changed_cb(FlView* self) {
|
||||
FlMouseCursorHandler* handler =
|
||||
fl_engine_get_mouse_cursor_handler(self->engine);
|
||||
const gchar* cursor_name = fl_mouse_cursor_handler_get_cursor_name(handler);
|
||||
GdkWindow* window =
|
||||
gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)));
|
||||
g_autoptr(GdkCursor) cursor =
|
||||
gdk_cursor_new_from_name(gdk_window_get_display(window), cursor_name);
|
||||
gdk_window_set_cursor(window, cursor);
|
||||
FlGdkSurface* surface = fl_view_get_toplevel_surface(self);
|
||||
if (surface == nullptr) {
|
||||
return;
|
||||
}
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_autoptr(GdkCursor) cursor = gdk_cursor_new_from_name(cursor_name, nullptr);
|
||||
#else
|
||||
g_autoptr(GdkCursor) cursor = gdk_cursor_new_from_name(
|
||||
fl_gtk_surface_get_display(surface), cursor_name);
|
||||
#endif
|
||||
fl_gtk_surface_set_cursor(surface, cursor);
|
||||
}
|
||||
|
||||
// Set the mouse cursor.
|
||||
@ -173,18 +259,32 @@ static void setup_cursor(FlView* self) {
|
||||
}
|
||||
|
||||
// Updates the engine with the current window metrics.
|
||||
static void handle_geometry_changed(FlView* self) {
|
||||
GtkAllocation allocation;
|
||||
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
|
||||
static void handle_geometry_changed_with_size(FlView* self,
|
||||
int width,
|
||||
int height) {
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
// Try to fall back to the toplevel surface size if available.
|
||||
FlGdkSurface* surface = fl_view_get_toplevel_surface(self);
|
||||
if (surface != nullptr) {
|
||||
width = fl_gtk_surface_get_width(surface);
|
||||
height = fl_gtk_surface_get_height(surface);
|
||||
}
|
||||
if (width == 0 || height == 0) {
|
||||
static bool logged_zero_allocation = false;
|
||||
log_once(&logged_zero_allocation,
|
||||
"handle_geometry_changed: zero-size allocation");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note we can't detect if a window is moved between monitors - this
|
||||
// information is provided by Wayland but GTK only notifies us if the scale
|
||||
// has changed, so moving between two monitors of the same scale doesn't
|
||||
// provide any information.
|
||||
|
||||
GdkWindow* window =
|
||||
gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)));
|
||||
FlGdkSurface* surface = fl_view_get_toplevel_surface(self);
|
||||
// NOTE(robert-ancell) If we haven't got a window we default to display 0.
|
||||
// This is probably indicating a problem with this code in that we
|
||||
// shouldn't be generating anything until the window is created.
|
||||
@ -193,15 +293,36 @@ static void handle_geometry_changed(FlView* self) {
|
||||
// probably shouldn't call handle_geometry_changed after the view is
|
||||
// added but only when the window is realized.
|
||||
FlutterEngineDisplayId display_id = 0;
|
||||
if (window != nullptr) {
|
||||
GdkMonitor* monitor = gdk_display_get_monitor_at_window(
|
||||
gtk_widget_get_display(GTK_WIDGET(self)), window);
|
||||
if (surface != nullptr) {
|
||||
GdkMonitor* monitor = fl_gtk_display_get_monitor_at_surface(
|
||||
fl_gtk_surface_get_display(surface), surface);
|
||||
display_id = fl_display_monitor_get_display_id(
|
||||
fl_engine_get_display_monitor(self->engine), monitor);
|
||||
}
|
||||
fl_engine_send_window_metrics_event(
|
||||
self->engine, display_id, self->view_id, allocation.width * scale_factor,
|
||||
allocation.height * scale_factor, scale_factor);
|
||||
fl_engine_send_window_metrics_event(self->engine, display_id, self->view_id,
|
||||
width * scale_factor,
|
||||
height * scale_factor, scale_factor);
|
||||
|
||||
{
|
||||
static bool logged_metrics = false;
|
||||
if (!logged_metrics) {
|
||||
logged_metrics = true;
|
||||
g_warning("handle_geometry_changed: metrics %d x %d (scale %d)", width,
|
||||
height, scale_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_geometry_changed(FlView* self) {
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
int width = gtk_widget_get_width(GTK_WIDGET(self->render_area));
|
||||
int height = gtk_widget_get_height(GTK_WIDGET(self->render_area));
|
||||
handle_geometry_changed_with_size(self, width, height);
|
||||
#else
|
||||
GtkAllocation allocation;
|
||||
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
|
||||
handle_geometry_changed_with_size(self, allocation.width, allocation.height);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void view_added_cb(GObject* object,
|
||||
@ -231,7 +352,12 @@ static void update_semantics_cb(FlView* self,
|
||||
return;
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
fl_view_accessible_handle_update_semantics(self->view_accessible, update);
|
||||
#else
|
||||
(void)self;
|
||||
(void)update;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Invoked by the engine right before the engine is restarted.
|
||||
@ -250,6 +376,18 @@ static void fl_view_present_layers(FlRenderable* renderable,
|
||||
size_t layers_count) {
|
||||
FlView* self = FL_VIEW(renderable);
|
||||
|
||||
if (layers_count > 0 && layers[0] != nullptr) {
|
||||
static bool logged_first_layers = false;
|
||||
log_once(&logged_first_layers,
|
||||
"fl_view_present_layers: received first frame layers");
|
||||
static bool logged_layer_size = false;
|
||||
if (!logged_layer_size) {
|
||||
logged_layer_size = true;
|
||||
g_warning("fl_view_present_layers: first layer size %g x %g",
|
||||
layers[0]->size.width, layers[0]->size.height);
|
||||
}
|
||||
}
|
||||
|
||||
fl_compositor_present_layers(self->compositor, layers, layers_count);
|
||||
|
||||
// Perform the redraw in the GTK thead.
|
||||
@ -278,8 +416,12 @@ static void fl_view_plugin_registry_iface_init(
|
||||
|
||||
static void sync_modifier_if_needed(FlView* self, GdkEvent* event) {
|
||||
guint event_time = gdk_event_get_time(event);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkModifierType event_state = gdk_event_get_modifier_state(event);
|
||||
#else
|
||||
GdkModifierType event_state = static_cast<GdkModifierType>(0);
|
||||
gdk_event_get_state(event, &event_state);
|
||||
#endif
|
||||
fl_keyboard_manager_sync_modifier_if_needed(
|
||||
fl_engine_get_keyboard_manager(self->engine), event_state, event_time);
|
||||
}
|
||||
@ -290,6 +432,7 @@ static void set_scrolling_position(FlView* self, gdouble x, gdouble y) {
|
||||
self->scrolling_manager, x * scale_factor, y * scale_factor);
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
// Signal handler for GtkWidget::button-press-event
|
||||
static gboolean button_press_event_cb(FlView* self,
|
||||
GdkEventButton* button_event) {
|
||||
@ -343,7 +486,7 @@ static gboolean scroll_event_cb(FlView* self, GdkEventScroll* event) {
|
||||
// depend on GTK 3.24.
|
||||
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
self->scrolling_manager, event,
|
||||
self->scrolling_manager, reinterpret_cast<GdkEvent*>(event),
|
||||
gtk_widget_get_scale_factor(GTK_WIDGET(self)));
|
||||
return TRUE;
|
||||
}
|
||||
@ -403,6 +546,193 @@ static gboolean leave_notify_event_cb(FlView* self,
|
||||
self->pointer_manager, gdk_event_get_time(event), get_device_kind(event),
|
||||
x * scale_factor, y * scale_factor);
|
||||
}
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static guint32 get_event_time_or_now(GdkEvent* event) {
|
||||
if (event != nullptr) {
|
||||
return gdk_event_get_time(event);
|
||||
}
|
||||
return static_cast<guint32>(g_get_real_time() / 1000);
|
||||
}
|
||||
|
||||
static GdkEvent* get_current_event(GtkEventController* controller) {
|
||||
return gtk_event_controller_get_current_event(controller);
|
||||
}
|
||||
|
||||
static FlutterPointerDeviceKind get_device_kind_or_default(GdkEvent* event) {
|
||||
if (event != nullptr) {
|
||||
return get_device_kind(event);
|
||||
}
|
||||
return kFlutterPointerDeviceKindMouse;
|
||||
}
|
||||
|
||||
static void motion_event_cb(FlView* self, gdouble x, gdouble y) {
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->motion_controller));
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
sync_modifier_if_needed(self, event);
|
||||
|
||||
// return if touch event
|
||||
auto event_type = gdk_event_get_event_type(event);
|
||||
if (event_type == GDK_TOUCH_BEGIN || event_type == GDK_TOUCH_UPDATE ||
|
||||
event_type == GDK_TOUCH_END || event_type == GDK_TOUCH_CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
fl_pointer_manager_handle_motion(
|
||||
self->pointer_manager, gdk_event_get_time(event), get_device_kind(event),
|
||||
x * scale_factor, y * scale_factor);
|
||||
}
|
||||
|
||||
static void enter_event_cb(FlView* self, gdouble x, gdouble y) {
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->motion_controller));
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
fl_pointer_manager_handle_enter(
|
||||
self->pointer_manager, get_event_time_or_now(event),
|
||||
get_device_kind_or_default(event), x * scale_factor, y * scale_factor);
|
||||
}
|
||||
|
||||
static void leave_event_cb(FlView* self, gdouble x, gdouble y) {
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->motion_controller));
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
fl_pointer_manager_handle_leave(
|
||||
self->pointer_manager, get_event_time_or_now(event),
|
||||
get_device_kind_or_default(event), x * scale_factor, y * scale_factor);
|
||||
}
|
||||
|
||||
static void click_pressed_cb(FlView* self, gint n_press, gdouble x, gdouble y) {
|
||||
if (n_press > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->click_gesture));
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
guint button = gtk_gesture_single_get_current_button(
|
||||
GTK_GESTURE_SINGLE(self->click_gesture));
|
||||
|
||||
set_scrolling_position(self, x, y);
|
||||
sync_modifier_if_needed(self, event);
|
||||
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
fl_pointer_manager_handle_button_press(
|
||||
self->pointer_manager, gdk_event_get_time(event), get_device_kind(event),
|
||||
x * scale_factor, y * scale_factor, button);
|
||||
}
|
||||
|
||||
static void click_released_cb(FlView* self,
|
||||
gint n_press,
|
||||
gdouble x,
|
||||
gdouble y) {
|
||||
(void)n_press;
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->click_gesture));
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
guint button = gtk_gesture_single_get_current_button(
|
||||
GTK_GESTURE_SINGLE(self->click_gesture));
|
||||
|
||||
set_scrolling_position(self, x, y);
|
||||
sync_modifier_if_needed(self, event);
|
||||
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
|
||||
fl_pointer_manager_handle_button_release(
|
||||
self->pointer_manager, gdk_event_get_time(event), get_device_kind(event),
|
||||
x * scale_factor, y * scale_factor, button);
|
||||
}
|
||||
|
||||
static gboolean scroll_event_cb(FlView* self, gdouble dx, gdouble dy) {
|
||||
(void)dx;
|
||||
(void)dy;
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->scroll_controller));
|
||||
if (event == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fl_scrolling_manager_handle_scroll_event(
|
||||
self->scrolling_manager, event,
|
||||
gtk_widget_get_scale_factor(GTK_WIDGET(self)));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean handle_key_event(FlView* self,
|
||||
GdkEvent* event,
|
||||
gboolean is_press,
|
||||
guint keyval,
|
||||
guint keycode,
|
||||
GdkModifierType state) {
|
||||
g_autoptr(FlKeyEvent) key_event = nullptr;
|
||||
if (event != nullptr) {
|
||||
key_event = fl_key_event_new_from_gdk_event(event);
|
||||
} else {
|
||||
key_event = fl_key_event_new(get_event_time_or_now(event), is_press,
|
||||
keycode, keyval, state, 0);
|
||||
}
|
||||
|
||||
fl_keyboard_manager_handle_event(
|
||||
fl_engine_get_keyboard_manager(self->engine), key_event,
|
||||
self->cancellable,
|
||||
[](GObject* object, GAsyncResult* result, gpointer user_data) {
|
||||
FlView* self = FL_VIEW(user_data);
|
||||
|
||||
g_autoptr(FlKeyEvent) redispatch_event = nullptr;
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!fl_keyboard_manager_handle_event_finish(
|
||||
FL_KEYBOARD_MANAGER(object), result, &redispatch_event,
|
||||
&error)) {
|
||||
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_warning("Failed to handle key event: %s", error->message);
|
||||
}
|
||||
|
||||
if (redispatch_event != nullptr) {
|
||||
if (!fl_text_input_handler_filter_keypress(
|
||||
fl_engine_get_text_input_handler(self->engine),
|
||||
redispatch_event)) {
|
||||
fl_keyboard_manager_add_redispatched_event(
|
||||
fl_engine_get_keyboard_manager(self->engine), redispatch_event);
|
||||
// TODO(gtk4): Redispatch unhandled events to GTK.
|
||||
}
|
||||
}
|
||||
},
|
||||
self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean key_pressed_cb(FlView* self,
|
||||
guint keyval,
|
||||
guint keycode,
|
||||
GdkModifierType state) {
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->key_controller));
|
||||
return handle_key_event(self, event, TRUE, keyval, keycode, state);
|
||||
}
|
||||
|
||||
static gboolean key_released_cb(FlView* self,
|
||||
guint keyval,
|
||||
guint keycode,
|
||||
GdkModifierType state) {
|
||||
GdkEvent* event =
|
||||
get_current_event(GTK_EVENT_CONTROLLER(self->key_controller));
|
||||
return handle_key_event(self, event, FALSE, keyval, keycode, state);
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
static void gesture_rotation_begin_cb(FlView* self) {
|
||||
fl_scrolling_manager_handle_rotation_begin(self->scrolling_manager);
|
||||
@ -434,8 +764,14 @@ static void gesture_zoom_end_cb(FlView* self) {
|
||||
static void setup_opengl(FlView* self) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
|
||||
self->render_context = gdk_window_create_gl_context(
|
||||
gtk_widget_get_window(GTK_WIDGET(self->render_area)), &error);
|
||||
FlGdkSurface* surface =
|
||||
fl_gtk_widget_get_surface(GTK_WIDGET(self->render_area));
|
||||
if (surface == nullptr) {
|
||||
g_warning("Failed to create OpenGL context: surface not available");
|
||||
return;
|
||||
}
|
||||
|
||||
self->render_context = fl_gtk_surface_create_gl_context(surface, &error);
|
||||
if (self->render_context == nullptr) {
|
||||
g_warning("Failed to create OpenGL context: %s", error->message);
|
||||
return;
|
||||
@ -450,7 +786,7 @@ static void setup_opengl(FlView* self) {
|
||||
// from the Flutter context using EGLImage. If not (i.e. X11 using GLX)
|
||||
// then we have to copy the texture via the CPU.
|
||||
gboolean shareable =
|
||||
GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(GTK_WIDGET(self)));
|
||||
GDK_IS_WAYLAND_DISPLAY(fl_gtk_surface_get_display(surface));
|
||||
self->compositor = FL_COMPOSITOR(fl_compositor_opengl_new(
|
||||
fl_engine_get_task_runner(self->engine),
|
||||
fl_engine_get_opengl_manager(self->engine), shareable));
|
||||
@ -478,15 +814,20 @@ static void realize_cb(FlView* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
|
||||
GtkWidget* toplevel_window = fl_view_get_toplevel_widget(self);
|
||||
|
||||
self->window_state_monitor =
|
||||
fl_window_state_monitor_new(fl_engine_get_binary_messenger(self->engine),
|
||||
GTK_WINDOW(toplevel_window));
|
||||
|
||||
// Handle requests by the user to close the application.
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_signal_connect(toplevel_window, "close-request",
|
||||
G_CALLBACK(window_close_request_cb), self);
|
||||
#else
|
||||
g_signal_connect_swapped(toplevel_window, "delete-event",
|
||||
G_CALLBACK(window_delete_event_cb), self);
|
||||
#endif
|
||||
|
||||
// Flutter engine will need to make the context current from raster thread
|
||||
// during initialization.
|
||||
@ -498,14 +839,32 @@ static void realize_cb(FlView* self) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
fl_text_input_handler_set_widget(
|
||||
fl_engine_get_text_input_handler(self->engine), GTK_WIDGET(self));
|
||||
#endif
|
||||
|
||||
setup_cursor(self);
|
||||
|
||||
handle_geometry_changed(self);
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static void size_allocate_cb(FlView* self) {
|
||||
handle_geometry_changed(self);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static void resize_cb(FlView* self, int width, int height) {
|
||||
if (width > 0 && height > 0) {
|
||||
static bool logged_resize = false;
|
||||
log_once(&logged_resize,
|
||||
"resize_cb: received non-zero size for render area");
|
||||
}
|
||||
handle_geometry_changed_with_size(self, width, height);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void paint_background(FlView* self, cairo_t* cr) {
|
||||
// Don't bother drawing if fully transparent - the widget above this will
|
||||
@ -526,17 +885,56 @@ static gboolean draw_cb(FlView* self, cairo_t* cr) {
|
||||
gdk_gl_context_make_current(self->render_context);
|
||||
}
|
||||
|
||||
gboolean result = fl_compositor_render(
|
||||
self->compositor, cr,
|
||||
gtk_widget_get_window(GTK_WIDGET(self->render_area)));
|
||||
FlGdkSurface* surface =
|
||||
fl_gtk_widget_get_surface(GTK_WIDGET(self->render_area));
|
||||
if (surface == nullptr) {
|
||||
static bool logged_surface_null = false;
|
||||
log_once(&logged_surface_null, "draw_cb: render area has no surface");
|
||||
if (self->render_context) {
|
||||
gdk_gl_context_clear_current();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
{
|
||||
static bool logged_surface_size = false;
|
||||
if (!logged_surface_size) {
|
||||
logged_surface_size = true;
|
||||
g_warning("draw_cb: surface size %d x %d (scale %d)",
|
||||
fl_gtk_surface_get_width(surface),
|
||||
fl_gtk_surface_get_height(surface),
|
||||
fl_gtk_surface_get_scale_factor(surface));
|
||||
}
|
||||
}
|
||||
|
||||
gboolean result = fl_compositor_render(self->compositor, cr, surface);
|
||||
|
||||
if (self->render_context) {
|
||||
gdk_gl_context_clear_current();
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
static bool logged_render_false = false;
|
||||
log_once(&logged_render_false, "draw_cb: compositor render returned false");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static void draw_cb_gtk4(GtkDrawingArea* area,
|
||||
cairo_t* cr,
|
||||
int width,
|
||||
int height,
|
||||
gpointer user_data) {
|
||||
(void)area;
|
||||
(void)width;
|
||||
(void)height;
|
||||
FlView* self = FL_VIEW(user_data);
|
||||
draw_cb(self, cr);
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
static void fl_view_notify(GObject* object, GParamSpec* pspec) {
|
||||
FlView* self = FL_VIEW(object);
|
||||
|
||||
@ -586,7 +984,17 @@ static void fl_view_dispose(GObject* object) {
|
||||
g_clear_object(&self->scrolling_manager);
|
||||
g_clear_object(&self->pointer_manager);
|
||||
g_clear_object(&self->touch_manager);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_clear_object(&self->motion_controller);
|
||||
g_clear_object(&self->click_gesture);
|
||||
g_clear_object(&self->scroll_controller);
|
||||
g_clear_object(&self->key_controller);
|
||||
g_clear_object(&self->zoom_gesture);
|
||||
g_clear_object(&self->rotate_gesture);
|
||||
#endif
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
g_clear_object(&self->view_accessible);
|
||||
#endif
|
||||
g_clear_object(&self->cancellable);
|
||||
|
||||
G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
|
||||
@ -594,17 +1002,22 @@ static void fl_view_dispose(GObject* object) {
|
||||
|
||||
// Implements GtkWidget::realize.
|
||||
static void fl_view_realize(GtkWidget* widget) {
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
FlView* self = FL_VIEW(widget);
|
||||
#endif
|
||||
|
||||
GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget);
|
||||
|
||||
// Realize the child widgets.
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
gtk_widget_realize(GTK_WIDGET(self->render_area));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static gboolean handle_key_event(FlView* self, GdkEventKey* key_event) {
|
||||
g_autoptr(FlKeyEvent) event = fl_key_event_new_from_gdk_event(
|
||||
gdk_event_copy(reinterpret_cast<GdkEvent*>(key_event)));
|
||||
g_autoptr(FlKeyEvent) event =
|
||||
fl_key_event_new_from_gdk_event(reinterpret_cast<GdkEvent*>(key_event));
|
||||
|
||||
fl_keyboard_manager_handle_event(
|
||||
fl_engine_get_keyboard_manager(self->engine), event, self->cancellable,
|
||||
@ -660,6 +1073,7 @@ static gboolean fl_view_key_release_event(GtkWidget* widget,
|
||||
FlView* self = FL_VIEW(widget);
|
||||
return handle_key_event(self, key_event);
|
||||
}
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
static void fl_view_class_init(FlViewClass* klass) {
|
||||
GObjectClass* object_class = G_OBJECT_CLASS(klass);
|
||||
@ -668,24 +1082,30 @@ static void fl_view_class_init(FlViewClass* klass) {
|
||||
|
||||
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
|
||||
widget_class->realize = fl_view_realize;
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
widget_class->focus_in_event = fl_view_focus_in_event;
|
||||
widget_class->key_press_event = fl_view_key_press_event;
|
||||
widget_class->key_release_event = fl_view_key_release_event;
|
||||
#endif
|
||||
|
||||
fl_view_signals[SIGNAL_FIRST_FRAME] =
|
||||
g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0,
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
|
||||
fl_socket_accessible_get_type());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Engine related construction.
|
||||
static void setup_engine(FlView* self) {
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
self->view_accessible = fl_view_accessible_new(self->engine, self->view_id);
|
||||
fl_socket_accessible_embed(
|
||||
FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))),
|
||||
atk_plug_get_id(ATK_PLUG(self->view_accessible)));
|
||||
#endif
|
||||
|
||||
self->pointer_manager = fl_pointer_manager_new(self->view_id, self->engine);
|
||||
|
||||
@ -702,7 +1122,11 @@ static void setup_engine(FlView* self) {
|
||||
static void fl_view_init(FlView* self) {
|
||||
self->cancellable = g_cancellable_new();
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_widget_set_focusable(GTK_WIDGET(self), TRUE);
|
||||
#else
|
||||
gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
|
||||
#endif
|
||||
|
||||
self->view_id = -1;
|
||||
|
||||
@ -710,6 +1134,7 @@ static void fl_view_init(FlView* self) {
|
||||
.red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
|
||||
self->background_color = gdk_rgba_copy(&default_background);
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
self->event_box = gtk_event_box_new();
|
||||
gtk_widget_set_hexpand(self->event_box, TRUE);
|
||||
gtk_widget_set_vexpand(self->event_box, TRUE);
|
||||
@ -747,17 +1172,91 @@ static void fl_view_init(FlView* self) {
|
||||
self);
|
||||
g_signal_connect_swapped(self->event_box, "touch-event",
|
||||
G_CALLBACK(touch_event_cb), self);
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
self->render_area = GTK_DRAWING_AREA(gtk_drawing_area_new());
|
||||
gtk_widget_show(GTK_WIDGET(self->render_area));
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->render_area), TRUE);
|
||||
gtk_widget_set_vexpand(GTK_WIDGET(self->render_area), TRUE);
|
||||
gtk_box_append(GTK_BOX(self), GTK_WIDGET(self->render_area));
|
||||
#else
|
||||
gtk_container_add(GTK_CONTAINER(self->event_box),
|
||||
GTK_WIDGET(self->render_area));
|
||||
#endif
|
||||
g_signal_connect_swapped(self->render_area, "realize", G_CALLBACK(realize_cb),
|
||||
self);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_signal_connect_swapped(self->render_area, "resize", G_CALLBACK(resize_cb),
|
||||
self);
|
||||
#else
|
||||
g_signal_connect_swapped(self->render_area, "size-allocate",
|
||||
G_CALLBACK(size_allocate_cb), self);
|
||||
#endif
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gtk_drawing_area_set_draw_func(self->render_area, draw_cb_gtk4, self,
|
||||
nullptr);
|
||||
#else
|
||||
g_signal_connect_swapped(self->render_area, "draw", G_CALLBACK(draw_cb),
|
||||
self);
|
||||
#endif
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
self->motion_controller =
|
||||
GTK_EVENT_CONTROLLER_MOTION(gtk_event_controller_motion_new());
|
||||
g_signal_connect_swapped(self->motion_controller, "motion",
|
||||
G_CALLBACK(motion_event_cb), self);
|
||||
g_signal_connect_swapped(self->motion_controller, "enter",
|
||||
G_CALLBACK(enter_event_cb), self);
|
||||
g_signal_connect_swapped(self->motion_controller, "leave",
|
||||
G_CALLBACK(leave_event_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->render_area),
|
||||
GTK_EVENT_CONTROLLER(self->motion_controller));
|
||||
|
||||
self->click_gesture = GTK_GESTURE_CLICK(gtk_gesture_click_new());
|
||||
g_signal_connect_swapped(self->click_gesture, "pressed",
|
||||
G_CALLBACK(click_pressed_cb), self);
|
||||
g_signal_connect_swapped(self->click_gesture, "released",
|
||||
G_CALLBACK(click_released_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->render_area),
|
||||
GTK_EVENT_CONTROLLER(self->click_gesture));
|
||||
|
||||
self->scroll_controller = GTK_EVENT_CONTROLLER_SCROLL(
|
||||
gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES));
|
||||
g_signal_connect_swapped(self->scroll_controller, "scroll",
|
||||
G_CALLBACK(scroll_event_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->render_area),
|
||||
GTK_EVENT_CONTROLLER(self->scroll_controller));
|
||||
|
||||
self->key_controller =
|
||||
GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
|
||||
g_signal_connect_swapped(self->key_controller, "key-pressed",
|
||||
G_CALLBACK(key_pressed_cb), self);
|
||||
g_signal_connect_swapped(self->key_controller, "key-released",
|
||||
G_CALLBACK(key_released_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self),
|
||||
GTK_EVENT_CONTROLLER(self->key_controller));
|
||||
|
||||
self->zoom_gesture = GTK_GESTURE_ZOOM(gtk_gesture_zoom_new());
|
||||
g_signal_connect_swapped(self->zoom_gesture, "begin",
|
||||
G_CALLBACK(gesture_zoom_begin_cb), self);
|
||||
g_signal_connect_swapped(self->zoom_gesture, "scale-changed",
|
||||
G_CALLBACK(gesture_zoom_update_cb), self);
|
||||
g_signal_connect_swapped(self->zoom_gesture, "end",
|
||||
G_CALLBACK(gesture_zoom_end_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->render_area),
|
||||
GTK_EVENT_CONTROLLER(self->zoom_gesture));
|
||||
|
||||
self->rotate_gesture = GTK_GESTURE_ROTATE(gtk_gesture_rotate_new());
|
||||
g_signal_connect_swapped(self->rotate_gesture, "begin",
|
||||
G_CALLBACK(gesture_rotation_begin_cb), self);
|
||||
g_signal_connect_swapped(self->rotate_gesture, "angle-changed",
|
||||
G_CALLBACK(gesture_rotation_update_cb), self);
|
||||
g_signal_connect_swapped(self->rotate_gesture, "end",
|
||||
G_CALLBACK(gesture_rotation_end_cb), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->render_area),
|
||||
GTK_EVENT_CONTROLLER(self->rotate_gesture));
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) {
|
||||
@ -805,7 +1304,9 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self,
|
||||
self->background_color = gdk_rgba_copy(color);
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
FlViewAccessible* fl_view_get_accessible(FlView* self) {
|
||||
g_return_val_if_fail(FL_IS_VIEW(self), nullptr);
|
||||
return self->view_accessible;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -5,11 +5,13 @@
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_view_accessible.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
#include "flutter/shell/platform/linux/fl_view_accessible.h"
|
||||
|
||||
/**
|
||||
* fl_view_get_accessible:
|
||||
* @view: an #FlView.
|
||||
@ -19,6 +21,7 @@ G_BEGIN_DECLS
|
||||
* Returns: an #FlViewAccessible.
|
||||
*/
|
||||
FlViewAccessible* fl_view_get_accessible(FlView* view);
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ struct _FlWindowMonitor {
|
||||
|
||||
G_DEFINE_TYPE(FlWindowMonitor, fl_window_monitor, G_TYPE_OBJECT)
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static gboolean configure_event_cb(FlWindowMonitor* self,
|
||||
GdkEventConfigure* event) {
|
||||
flutter::IsolateScope scope(self->isolate);
|
||||
@ -42,6 +43,7 @@ static gboolean window_state_event_cb(FlWindowMonitor* self,
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void is_active_notify_cb(FlWindowMonitor* self) {
|
||||
flutter::IsolateScope scope(self->isolate);
|
||||
@ -53,6 +55,13 @@ static void title_notify_cb(FlWindowMonitor* self) {
|
||||
self->on_title_notify();
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static gboolean close_request_cb(FlWindowMonitor* self) {
|
||||
flutter::IsolateScope scope(self->isolate);
|
||||
self->on_close();
|
||||
return FALSE;
|
||||
}
|
||||
#else
|
||||
static gboolean delete_event_cb(FlWindowMonitor* self, GdkEvent* event) {
|
||||
flutter::IsolateScope scope(self->isolate);
|
||||
self->on_close();
|
||||
@ -60,6 +69,7 @@ static gboolean delete_event_cb(FlWindowMonitor* self, GdkEvent* event) {
|
||||
// Stop default behaviour of destroying the window.
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void destroy_cb(FlWindowMonitor* self) {
|
||||
flutter::IsolateScope scope(self->isolate);
|
||||
@ -102,16 +112,23 @@ G_MODULE_EXPORT FlWindowMonitor* fl_window_monitor_new(
|
||||
self->on_title_notify = on_title_notify;
|
||||
self->on_close = on_close;
|
||||
self->on_destroy = on_destroy;
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
g_signal_connect_swapped(window, "configure-event",
|
||||
G_CALLBACK(configure_event_cb), self);
|
||||
g_signal_connect_swapped(window, "window-state-event",
|
||||
G_CALLBACK(window_state_event_cb), self);
|
||||
#endif
|
||||
g_signal_connect_swapped(window, "notify::is-active",
|
||||
G_CALLBACK(is_active_notify_cb), self);
|
||||
g_signal_connect_swapped(window, "notify::title", G_CALLBACK(title_notify_cb),
|
||||
self);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_signal_connect_swapped(window, "close-request",
|
||||
G_CALLBACK(close_request_cb), self);
|
||||
#else
|
||||
g_signal_connect_swapped(window, "delete-event", G_CALLBACK(delete_event_cb),
|
||||
self);
|
||||
#endif
|
||||
g_signal_connect_swapped(window, "destroy", G_CALLBACK(destroy_cb), self);
|
||||
|
||||
return self;
|
||||
|
||||
@ -27,10 +27,18 @@ struct _FlWindowStateMonitor {
|
||||
GtkWindow* window;
|
||||
|
||||
// Current state information.
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
GdkWindowState window_state;
|
||||
#endif
|
||||
|
||||
// Signal connection ID for window-state-changed
|
||||
gulong window_state_event_cb_id;
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gulong toplevel_state_notify_cb_id;
|
||||
gulong surface_mapped_notify_cb_id;
|
||||
gulong realize_cb_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FlWindowStateMonitor, fl_window_state_monitor, G_TYPE_OBJECT);
|
||||
@ -51,11 +59,18 @@ static void send_lifecycle_state(FlWindowStateMonitor* self,
|
||||
message, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
static gboolean is_hidden(GdkWindowState state) {
|
||||
return (state & GDK_WINDOW_STATE_WITHDRAWN) ||
|
||||
(state & GDK_WINDOW_STATE_ICONIFIED);
|
||||
}
|
||||
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static gboolean is_hidden(GdkToplevelState state, gboolean mapped) {
|
||||
return !mapped || (state & GDK_TOPLEVEL_STATE_MINIMIZED);
|
||||
}
|
||||
#endif
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
// Signal handler for GtkWindow::window-state-event
|
||||
static gboolean window_state_event_cb(FlWindowStateMonitor* self,
|
||||
GdkEvent* event) {
|
||||
@ -81,6 +96,68 @@ static gboolean window_state_event_cb(FlWindowStateMonitor* self,
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
static void update_lifecycle_state_gtk4(FlWindowStateMonitor* self) {
|
||||
GtkNative* native = gtk_widget_get_native(GTK_WIDGET(self->window));
|
||||
if (native == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkSurface* surface = gtk_native_get_surface(native);
|
||||
if (surface == nullptr || !GDK_IS_TOPLEVEL(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkToplevelState state = gdk_toplevel_get_state(GDK_TOPLEVEL(surface));
|
||||
gboolean mapped = gdk_surface_get_mapped(surface);
|
||||
bool visible = !is_hidden(state, mapped);
|
||||
bool focused = (state & GDK_TOPLEVEL_STATE_FOCUSED);
|
||||
|
||||
const gchar* lifecycle_state;
|
||||
if (visible) {
|
||||
lifecycle_state =
|
||||
focused ? kAppLifecycleStateResumed : kAppLifecycleStateInactive;
|
||||
} else {
|
||||
lifecycle_state = kAppLifecycleStateHidden;
|
||||
}
|
||||
|
||||
send_lifecycle_state(self, lifecycle_state);
|
||||
}
|
||||
|
||||
static void gtk4_surface_notify_cb(FlWindowStateMonitor* self) {
|
||||
update_lifecycle_state_gtk4(self);
|
||||
}
|
||||
|
||||
static void gtk4_setup_surface(FlWindowStateMonitor* self) {
|
||||
if (self->toplevel_state_notify_cb_id != 0 ||
|
||||
self->surface_mapped_notify_cb_id != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkNative* native = gtk_widget_get_native(GTK_WIDGET(self->window));
|
||||
if (native == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkSurface* surface = gtk_native_get_surface(native);
|
||||
if (surface == nullptr || !GDK_IS_TOPLEVEL(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->toplevel_state_notify_cb_id = g_signal_connect_swapped(
|
||||
surface, "notify::state", G_CALLBACK(gtk4_surface_notify_cb), self);
|
||||
self->surface_mapped_notify_cb_id = g_signal_connect_swapped(
|
||||
surface, "notify::mapped", G_CALLBACK(gtk4_surface_notify_cb), self);
|
||||
|
||||
update_lifecycle_state_gtk4(self);
|
||||
}
|
||||
|
||||
static void gtk4_realize_cb(FlWindowStateMonitor* self) {
|
||||
gtk4_setup_surface(self);
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
static void fl_window_state_monitor_dispose(GObject* object) {
|
||||
FlWindowStateMonitor* self = FL_WINDOW_STATE_MONITOR(object);
|
||||
@ -90,6 +167,26 @@ static void fl_window_state_monitor_dispose(GObject* object) {
|
||||
g_signal_handler_disconnect(self->window, self->window_state_event_cb_id);
|
||||
self->window_state_event_cb_id = 0;
|
||||
}
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkNative* native = gtk_widget_get_native(GTK_WIDGET(self->window));
|
||||
if (native != nullptr) {
|
||||
GdkSurface* surface = gtk_native_get_surface(native);
|
||||
if (surface != nullptr) {
|
||||
if (self->toplevel_state_notify_cb_id != 0) {
|
||||
g_signal_handler_disconnect(surface, self->toplevel_state_notify_cb_id);
|
||||
self->toplevel_state_notify_cb_id = 0;
|
||||
}
|
||||
if (self->surface_mapped_notify_cb_id != 0) {
|
||||
g_signal_handler_disconnect(surface, self->surface_mapped_notify_cb_id);
|
||||
self->surface_mapped_notify_cb_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self->realize_cb_id != 0) {
|
||||
g_signal_handler_disconnect(self->window, self->realize_cb_id);
|
||||
self->realize_cb_id = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
G_OBJECT_CLASS(fl_window_state_monitor_parent_class)->dispose(object);
|
||||
}
|
||||
@ -109,11 +206,20 @@ FlWindowStateMonitor* fl_window_state_monitor_new(FlBinaryMessenger* messenger,
|
||||
self->window = window;
|
||||
|
||||
// Listen to window state changes.
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
self->window_state_event_cb_id =
|
||||
g_signal_connect_swapped(self->window, "window-state-event",
|
||||
G_CALLBACK(window_state_event_cb), self);
|
||||
self->window_state =
|
||||
gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(self->window)));
|
||||
#else
|
||||
self->window_state_event_cb_id = 0;
|
||||
self->toplevel_state_notify_cb_id = 0;
|
||||
self->surface_mapped_notify_cb_id = 0;
|
||||
self->realize_cb_id = g_signal_connect_swapped(
|
||||
self->window, "realize", G_CALLBACK(gtk4_realize_cb), self);
|
||||
gtk4_setup_surface(self);
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -10,6 +10,12 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
TEST(FlWindowStateMonitorTest, GainFocus) {
|
||||
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
|
||||
::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
|
||||
@ -44,7 +50,6 @@ TEST(FlWindowStateMonitorTest, GainFocus) {
|
||||
|
||||
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
|
||||
}
|
||||
|
||||
TEST(FlWindowStateMonitorTest, LoseFocus) {
|
||||
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
|
||||
::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
|
||||
@ -287,3 +292,91 @@ TEST(FlWindowStateMonitorTest, LeaveWithdrawnFocused) {
|
||||
|
||||
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
|
||||
}
|
||||
|
||||
#endif // !FLUTTER_LINUX_GTK4
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
TEST(FlWindowStateMonitorTest, Gtk4FocusToInactive) {
|
||||
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
|
||||
::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
|
||||
|
||||
gtk_init(0, nullptr);
|
||||
|
||||
EXPECT_CALL(mock_gtk, gdk_surface_get_mapped)
|
||||
.WillRepeatedly(::testing::Return(TRUE));
|
||||
EXPECT_CALL(mock_gtk, gdk_toplevel_get_state)
|
||||
.WillOnce(::testing::Return(GDK_TOPLEVEL_STATE_FOCUSED))
|
||||
.WillOnce(::testing::Return(static_cast<GdkToplevelState>(0)));
|
||||
|
||||
std::vector<std::string> lifecycle_states;
|
||||
fl_mock_binary_messenger_set_string_message_channel(
|
||||
messenger, "flutter/lifecycle",
|
||||
[](FlMockBinaryMessenger* messenger, GTask* task, FlValue* message,
|
||||
gpointer user_data) {
|
||||
auto* states = static_cast<std::vector<std::string>*>(user_data);
|
||||
states->emplace_back(fl_value_get_string(message));
|
||||
return fl_value_new_string("");
|
||||
},
|
||||
&lifecycle_states);
|
||||
|
||||
GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
g_autoptr(FlWindowStateMonitor) monitor =
|
||||
fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window);
|
||||
|
||||
GtkNative* native = gtk_widget_get_native(GTK_WIDGET(window));
|
||||
GdkSurface* surface = gtk_native_get_surface(native);
|
||||
GParamSpec* state_pspec =
|
||||
g_object_class_find_property(G_OBJECT_GET_CLASS(surface), "state");
|
||||
ASSERT_NE(state_pspec, nullptr);
|
||||
g_object_notify_by_pspec(G_OBJECT(surface), state_pspec);
|
||||
|
||||
ASSERT_EQ(lifecycle_states.size(), 2u);
|
||||
EXPECT_EQ(lifecycle_states[0], "AppLifecycleState.resumed");
|
||||
EXPECT_EQ(lifecycle_states[1], "AppLifecycleState.inactive");
|
||||
|
||||
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
|
||||
}
|
||||
|
||||
TEST(FlWindowStateMonitorTest, Gtk4MappedToHidden) {
|
||||
g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
|
||||
::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
|
||||
|
||||
gtk_init(0, nullptr);
|
||||
|
||||
EXPECT_CALL(mock_gtk, gdk_surface_get_mapped)
|
||||
.WillOnce(::testing::Return(TRUE))
|
||||
.WillOnce(::testing::Return(FALSE));
|
||||
EXPECT_CALL(mock_gtk, gdk_toplevel_get_state)
|
||||
.WillRepeatedly(::testing::Return(static_cast<GdkToplevelState>(0)));
|
||||
|
||||
std::vector<std::string> lifecycle_states;
|
||||
fl_mock_binary_messenger_set_string_message_channel(
|
||||
messenger, "flutter/lifecycle",
|
||||
[](FlMockBinaryMessenger* messenger, GTask* task, FlValue* message,
|
||||
gpointer user_data) {
|
||||
auto* states = static_cast<std::vector<std::string>*>(user_data);
|
||||
states->emplace_back(fl_value_get_string(message));
|
||||
return fl_value_new_string("");
|
||||
},
|
||||
&lifecycle_states);
|
||||
|
||||
GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
g_autoptr(FlWindowStateMonitor) monitor =
|
||||
fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window);
|
||||
|
||||
GtkNative* native = gtk_widget_get_native(GTK_WIDGET(window));
|
||||
GdkSurface* surface = gtk_native_get_surface(native);
|
||||
GParamSpec* mapped_pspec =
|
||||
g_object_class_find_property(G_OBJECT_GET_CLASS(surface), "mapped");
|
||||
ASSERT_NE(mapped_pspec, nullptr);
|
||||
g_object_notify_by_pspec(G_OBJECT(surface), mapped_pspec);
|
||||
|
||||
ASSERT_EQ(lifecycle_states.size(), 2u);
|
||||
EXPECT_EQ(lifecycle_states[0], "AppLifecycleState.inactive");
|
||||
EXPECT_EQ(lifecycle_states[1], "AppLifecycleState.hidden");
|
||||
|
||||
fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
@ -429,7 +429,11 @@ void initialize_modifier_bit_to_checked_keys(GHashTable* table) {
|
||||
data->secondary_logical_key = 0x00200000101; // controlRight
|
||||
|
||||
data = g_new(FlKeyEmbedderCheckedKey, 1);
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
g_hash_table_insert(table, GUINT_TO_POINTER(GDK_ALT_MASK), data);
|
||||
#else
|
||||
g_hash_table_insert(table, GUINT_TO_POINTER(GDK_MOD1_MASK), data);
|
||||
#endif
|
||||
data->is_caps_lock = false;
|
||||
data->primary_physical_key = 0x0000700e2; // altLeft
|
||||
data->primary_logical_key = 0x00200000104; // altLeft
|
||||
@ -452,11 +456,13 @@ void initialize_lock_bit_to_checked_keys(GHashTable* table) {
|
||||
data->primary_physical_key = 0x000070039; // capsLock
|
||||
data->primary_logical_key = 0x00100000104; // capsLock
|
||||
|
||||
#if !FLUTTER_LINUX_GTK4
|
||||
data = g_new(FlKeyEmbedderCheckedKey, 1);
|
||||
g_hash_table_insert(table, GUINT_TO_POINTER(GDK_MOD2_MASK), data);
|
||||
data->is_caps_lock = false;
|
||||
data->primary_physical_key = 0x000070053; // numLock
|
||||
data->primary_logical_key = 0x0010000010a; // numLock
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::vector<LayoutGoal> layout_goals = {
|
||||
|
||||
@ -25,6 +25,89 @@ static void fl_mock_keymap_class_init(FlMockKeymapClass* klass) {
|
||||
|
||||
static void fl_mock_keymap_init(FlMockKeymap* self) {}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
G_DECLARE_FINAL_TYPE(FlMockGtk4Surface,
|
||||
fl_mock_gtk4_surface,
|
||||
FL,
|
||||
MOCK_GTK4_SURFACE,
|
||||
GObject)
|
||||
|
||||
struct _FlMockGtk4Surface {
|
||||
GObject parent_instance;
|
||||
GdkToplevelState state;
|
||||
gboolean mapped;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(FlMockGtk4Surface,
|
||||
fl_mock_gtk4_surface,
|
||||
G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE(GDK_TYPE_TOPLEVEL, nullptr))
|
||||
|
||||
enum {
|
||||
kPropState = 1,
|
||||
kPropMapped,
|
||||
kPropLast,
|
||||
};
|
||||
|
||||
static GParamSpec* g_properties[kPropLast];
|
||||
|
||||
static void fl_mock_gtk4_surface_set_property(GObject* object,
|
||||
guint prop_id,
|
||||
const GValue* value,
|
||||
GParamSpec* pspec) {
|
||||
FlMockGtk4Surface* self = FL_MOCK_GTK4_SURFACE(object);
|
||||
switch (prop_id) {
|
||||
case kPropState:
|
||||
self->state = static_cast<GdkToplevelState>(g_value_get_flags(value));
|
||||
break;
|
||||
case kPropMapped:
|
||||
self->mapped = g_value_get_boolean(value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void fl_mock_gtk4_surface_get_property(GObject* object,
|
||||
guint prop_id,
|
||||
GValue* value,
|
||||
GParamSpec* pspec) {
|
||||
FlMockGtk4Surface* self = FL_MOCK_GTK4_SURFACE(object);
|
||||
switch (prop_id) {
|
||||
case kPropState:
|
||||
g_value_set_flags(value, self->state);
|
||||
break;
|
||||
case kPropMapped:
|
||||
g_value_set_boolean(value, self->mapped);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void fl_mock_gtk4_surface_class_init(FlMockGtk4SurfaceClass* klass) {
|
||||
GObjectClass* object_class = G_OBJECT_CLASS(klass);
|
||||
object_class->set_property = fl_mock_gtk4_surface_set_property;
|
||||
object_class->get_property = fl_mock_gtk4_surface_get_property;
|
||||
|
||||
g_properties[kPropState] =
|
||||
g_param_spec_flags("state", "state", "state", GDK_TYPE_TOPLEVEL_STATE,
|
||||
static_cast<GdkToplevelState>(0),
|
||||
static_cast<GParamFlags>(G_PARAM_READWRITE));
|
||||
g_properties[kPropMapped] =
|
||||
g_param_spec_boolean("mapped", "mapped", "mapped", FALSE,
|
||||
static_cast<GParamFlags>(G_PARAM_READWRITE));
|
||||
g_object_class_install_properties(object_class, kPropLast, g_properties);
|
||||
}
|
||||
|
||||
static void fl_mock_gtk4_surface_init(FlMockGtk4Surface* self) {
|
||||
self->state = static_cast<GdkToplevelState>(0);
|
||||
self->mapped = FALSE;
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
// Override GdkKeymap
|
||||
GType gdk_keymap_get_type() {
|
||||
return fl_mock_keymap_get_type();
|
||||
@ -92,6 +175,17 @@ GdkWindowState gdk_window_get_state(GdkWindow* window) {
|
||||
return mock->gdk_window_get_state(window);
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GdkToplevelState gdk_toplevel_get_state(GdkToplevel* toplevel) {
|
||||
check_thread();
|
||||
if (mock != nullptr) {
|
||||
return mock->gdk_toplevel_get_state(toplevel);
|
||||
}
|
||||
FlMockGtk4Surface* surface = FL_MOCK_GTK4_SURFACE(toplevel);
|
||||
return surface->state;
|
||||
}
|
||||
#endif
|
||||
|
||||
GdkDisplay* gdk_window_get_display(GdkWindow* window) {
|
||||
check_thread();
|
||||
return GDK_DISPLAY(g_object_new(gdk_wayland_display_get_type(), nullptr));
|
||||
@ -141,6 +235,17 @@ GdkGLContext* gdk_window_create_gl_context(GdkWindow* window, GError** error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
gboolean gdk_surface_get_mapped(GdkSurface* surface) {
|
||||
check_thread();
|
||||
if (mock != nullptr) {
|
||||
return mock->gdk_surface_get_mapped(surface);
|
||||
}
|
||||
FlMockGtk4Surface* gtk4_surface = FL_MOCK_GTK4_SURFACE(surface);
|
||||
return gtk4_surface->mapped;
|
||||
}
|
||||
#endif
|
||||
|
||||
void gdk_cairo_set_source_rgba(cairo_t* cr, const GdkRGBA* rgba) {
|
||||
check_thread();
|
||||
}
|
||||
@ -301,6 +406,27 @@ GdkWindow* gtk_widget_get_window(GtkWidget* widget) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
GtkNative* gtk_widget_get_native(GtkWidget* widget) {
|
||||
check_thread();
|
||||
static GObject* mock_native = nullptr;
|
||||
if (mock_native == nullptr) {
|
||||
mock_native = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr));
|
||||
}
|
||||
return reinterpret_cast<GtkNative*>(mock_native);
|
||||
}
|
||||
|
||||
GdkSurface* gtk_native_get_surface(GtkNative* native) {
|
||||
check_thread();
|
||||
static FlMockGtk4Surface* mock_surface = nullptr;
|
||||
if (mock_surface == nullptr) {
|
||||
mock_surface = FL_MOCK_GTK4_SURFACE(
|
||||
g_object_new(fl_mock_gtk4_surface_get_type(), nullptr));
|
||||
}
|
||||
return reinterpret_cast<GdkSurface*>(mock_surface);
|
||||
}
|
||||
#endif // FLUTTER_LINUX_GTK4
|
||||
|
||||
void gtk_im_context_set_client_window(GtkIMContext* context,
|
||||
GdkWindow* window) {
|
||||
check_thread();
|
||||
|
||||
@ -23,6 +23,12 @@ class MockGtk {
|
||||
gdk_keymap_lookup_key,
|
||||
(GdkKeymap * keymap, const GdkKeymapKey* key));
|
||||
MOCK_METHOD(GdkWindowState, gdk_window_get_state, (GdkWindow * window));
|
||||
#if FLUTTER_LINUX_GTK4
|
||||
MOCK_METHOD(GdkToplevelState,
|
||||
gdk_toplevel_get_state,
|
||||
(GdkToplevel * toplevel));
|
||||
MOCK_METHOD(gboolean, gdk_surface_get_mapped, (GdkSurface * surface));
|
||||
#endif
|
||||
MOCK_METHOD(void, gtk_window_new, (GtkWindow * window, GtkWindowType type));
|
||||
MOCK_METHOD(void,
|
||||
gtk_window_set_default_size,
|
||||
|
||||
@ -83,6 +83,12 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
help:
|
||||
'The language to use for Android-specific code, either Kotlin (recommended) or Java (legacy).',
|
||||
);
|
||||
argParser.addOption(
|
||||
'linux-gtk',
|
||||
defaultsTo: 'gtk4',
|
||||
allowed: <String>['gtk4', 'gtk3'],
|
||||
help: 'Select the GTK version for Linux templates (gtk4 default, gtk3 opt-in).',
|
||||
);
|
||||
argParser.addFlag(
|
||||
'skip-name-checks',
|
||||
help:
|
||||
@ -461,6 +467,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
darwin: includeDarwin,
|
||||
web: includeWeb,
|
||||
linux: includeLinux,
|
||||
linuxGtkVersion: stringArg('linux-gtk') ?? 'gtk4',
|
||||
macos: includeMacos,
|
||||
windows: includeWindows,
|
||||
dartSdkVersionBounds: '^$dartSdk',
|
||||
|
||||
@ -327,6 +327,7 @@ mixin CreateBase on FlutterCommand {
|
||||
bool android = false,
|
||||
bool web = false,
|
||||
bool linux = false,
|
||||
String linuxGtkVersion = 'gtk4',
|
||||
bool macos = false,
|
||||
bool windows = false,
|
||||
bool darwin = false,
|
||||
@ -383,6 +384,7 @@ mixin CreateBase on FlutterCommand {
|
||||
'android': android,
|
||||
'web': web,
|
||||
'linux': linux,
|
||||
'linuxGtkVersion': linuxGtkVersion,
|
||||
'macos': macos,
|
||||
'darwin': darwin,
|
||||
'sharedDarwinSource': darwin,
|
||||
|
||||
@ -258,6 +258,18 @@ class Template {
|
||||
}
|
||||
// Only build a Linux project if explicitly asked.
|
||||
final bool linux = (context['linux'] as bool?) ?? false;
|
||||
final String linuxGtkVersion = (context['linuxGtkVersion'] as String?) ?? 'gtk4';
|
||||
final bool linuxGtk3Template = relativeDestinationPath.startsWith('linux-gtk3.tmpl');
|
||||
final bool linuxGtk4Template = relativeDestinationPath.startsWith('linux-gtk4.tmpl');
|
||||
if ((linuxGtk3Template || linuxGtk4Template) && !linux) {
|
||||
return null;
|
||||
}
|
||||
if (linuxGtk3Template && linuxGtkVersion != 'gtk3') {
|
||||
return null;
|
||||
}
|
||||
if (linuxGtk4Template && linuxGtkVersion != 'gtk4') {
|
||||
return null;
|
||||
}
|
||||
if (relativeDestinationPath.startsWith('linux.tmpl') && !linux) {
|
||||
return null;
|
||||
}
|
||||
|
||||
1
packages/flutter_tools/templates/app/linux-gtk4.tmpl/.gitignore
vendored
Normal file
1
packages/flutter_tools/templates/app/linux-gtk4.tmpl/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@ -0,0 +1,132 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "{{projectName}}")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "{{linuxIdentifier}}")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
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()
|
||||
|
||||
# Define build configuration 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.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
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()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk4)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
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"
|
||||
)
|
||||
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
# Enable the test target.
|
||||
set(include_{{pluginProjectName}}_tests TRUE)
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
||||
# 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)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# 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()
|
||||
@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
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 gtk4)
|
||||
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}
|
||||
)
|
||||
@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
@ -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);
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
(void)self;
|
||||
GtkRoot* root = gtk_widget_get_root(GTK_WIDGET(view));
|
||||
if (root == nullptr) {
|
||||
return;
|
||||
}
|
||||
gtk_window_present(GTK_WINDOW(root));
|
||||
}
|
||||
|
||||
// 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 as the default style for GNOME/GTK4 applications.
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_header_bar_set_show_title_buttons(header_bar, TRUE);
|
||||
GtkWidget* title_label = gtk_label_new("{{projectName}}");
|
||||
gtk_header_bar_set_title_widget(header_bar, title_label);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
gtk_window_set_title(window, "{{projectName}}");
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
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);
|
||||
GdkRGBA background_color;
|
||||
// Background defaults to black, override it here if necessary, e.g. #00000000
|
||||
// for transparent.
|
||||
gdk_rgba_parse(&background_color, "#000000");
|
||||
fl_view_set_background_color(view, &background_color);
|
||||
gtk_widget_set_focusable(GTK_WIDGET(view), TRUE);
|
||||
gtk_widget_set_visible(GTK_WIDGET(view), TRUE);
|
||||
gtk_window_set_child(window, GTK_WIDGET(view));
|
||||
|
||||
// Show the window when Flutter renders.
|
||||
// Requires the view to be realized so we can start rendering.
|
||||
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
self);
|
||||
gtk_widget_realize(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 GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// 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_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID, "flags",
|
||||
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#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_
|
||||
@ -0,0 +1,94 @@
|
||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
# installed. You should not increase this version, as doing so will cause
|
||||
# the plugin to fail to compile for some customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Project-level configuration.
|
||||
set(PROJECT_NAME "{{projectName}}")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
# This value is used when generating builds using this plugin, so it must
|
||||
# not be changed.
|
||||
set(PLUGIN_NAME "{{projectName}}_plugin")
|
||||
|
||||
# Any new source files that you add to the plugin should be added here.
|
||||
list(APPEND PLUGIN_SOURCES
|
||||
"{{pluginClassSnakeCase}}.cc"
|
||||
)
|
||||
|
||||
# Define the plugin library target. Its name must not be changed (see comment
|
||||
# on PLUGIN_NAME above).
|
||||
add_library(${PLUGIN_NAME} SHARED
|
||||
${PLUGIN_SOURCES}
|
||||
)
|
||||
|
||||
# Apply a standard set of build settings that are configured in the
|
||||
# application-level CMakeLists.txt. This can be removed for plugins that want
|
||||
# full control over build settings.
|
||||
apply_standard_settings(${PLUGIN_NAME})
|
||||
|
||||
# Symbols are hidden by default to reduce the chance of accidental conflicts
|
||||
# between plugins. This should not be removed; any symbols that should be
|
||||
# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro.
|
||||
set_target_properties(${PLUGIN_NAME} PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
|
||||
|
||||
# Source include directories and library dependencies. Add any plugin-specific
|
||||
# dependencies here.
|
||||
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.
|
||||
# This list could contain prebuilt libraries, or libraries created by an
|
||||
# external build triggered from this build file.
|
||||
set({{projectName}}_bundled_libraries
|
||||
""
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
# === Tests ===
|
||||
# These unit tests can be run from a terminal after building the example.
|
||||
|
||||
# Only enable test builds when building the example (which sets this variable)
|
||||
# so that plugin clients aren't building the tests.
|
||||
if (${include_${PROJECT_NAME}_tests})
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
message("Unit tests require CMake 3.11.0 or later")
|
||||
else()
|
||||
set(TEST_RUNNER "${PROJECT_NAME}_test")
|
||||
enable_testing()
|
||||
|
||||
# Add the Google Test dependency.
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/release-1.11.0.zip
|
||||
)
|
||||
# Prevent overriding the parent project's compiler/linker settings
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
# Disable install commands for gtest so it doesn't end up in the bundle.
|
||||
set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
# The plugin's exported API is not very useful for unit testing, so build the
|
||||
# sources directly into the test binary rather than using the shared library.
|
||||
add_executable(${TEST_RUNNER}
|
||||
test/{{pluginClassSnakeCase}}_test.cc
|
||||
${PLUGIN_SOURCES}
|
||||
)
|
||||
apply_standard_settings(${TEST_RUNNER})
|
||||
target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_link_libraries(${TEST_RUNNER} PRIVATE flutter)
|
||||
target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK)
|
||||
target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock)
|
||||
|
||||
# Enable automatic test discovery.
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(${TEST_RUNNER})
|
||||
|
||||
endif() # CMake version check
|
||||
endif() # include_${PROJECT_NAME}_tests
|
||||
@ -0,0 +1,26 @@
|
||||
#ifndef FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
|
||||
#define FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_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 _{{pluginClass}} {{pluginClass}};
|
||||
typedef struct {
|
||||
GObjectClass parent_class;
|
||||
} {{pluginClass}}Class;
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT GType {{pluginClassSnakeCase}}_get_type();
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void {{pluginClassSnakeCase}}_register_with_registrar(
|
||||
FlPluginRegistrar* registrar);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
|
||||
@ -0,0 +1,76 @@
|
||||
#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "{{pluginClassSnakeCase}}_private.h"
|
||||
|
||||
#define {{pluginClassCapitalSnakeCase}}(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), {{pluginClassSnakeCase}}_get_type(), \
|
||||
{{pluginClass}}))
|
||||
|
||||
struct _{{pluginClass}} {
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE({{pluginClass}}, {{pluginClassSnakeCase}}, g_object_get_type())
|
||||
|
||||
// Called when a method call is received from Flutter.
|
||||
static void {{pluginClassSnakeCase}}_handle_method_call(
|
||||
{{pluginClass}}* self,
|
||||
FlMethodCall* method_call) {
|
||||
g_autoptr(FlMethodResponse) response = nullptr;
|
||||
|
||||
const gchar* method = fl_method_call_get_name(method_call);
|
||||
|
||||
if (strcmp(method, "getPlatformVersion") == 0) {
|
||||
response = get_platform_version();
|
||||
} else {
|
||||
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
|
||||
}
|
||||
|
||||
fl_method_call_respond(method_call, response, nullptr);
|
||||
}
|
||||
|
||||
FlMethodResponse* get_platform_version() {
|
||||
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);
|
||||
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
}
|
||||
|
||||
static void {{pluginClassSnakeCase}}_dispose(GObject* object) {
|
||||
G_OBJECT_CLASS({{pluginClassSnakeCase}}_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void {{pluginClassSnakeCase}}_class_init({{pluginClass}}Class* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = {{pluginClassSnakeCase}}_dispose;
|
||||
}
|
||||
|
||||
static void {{pluginClassSnakeCase}}_init({{pluginClass}}* self) {}
|
||||
|
||||
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
|
||||
gpointer user_data) {
|
||||
{{pluginClass}}* plugin = {{pluginClassCapitalSnakeCase}}(user_data);
|
||||
{{pluginClassSnakeCase}}_handle_method_call(plugin, method_call);
|
||||
}
|
||||
|
||||
void {{pluginClassSnakeCase}}_register_with_registrar(FlPluginRegistrar* registrar) {
|
||||
{{pluginClass}}* plugin = {{pluginClassCapitalSnakeCase}}(
|
||||
g_object_new({{pluginClassSnakeCase}}_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),
|
||||
"{{projectName}}",
|
||||
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);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h"
|
||||
|
||||
// This file exposes some plugin internals for unit testing. See
|
||||
// https://github.com/flutter/flutter/issues/88724 for current limitations
|
||||
// in the unit-testable API.
|
||||
|
||||
// Handles the getPlatformVersion method call.
|
||||
FlMethodResponse *get_platform_version();
|
||||
@ -0,0 +1,31 @@
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h"
|
||||
#include "{{pluginClassSnakeCase}}_private.h"
|
||||
|
||||
// This demonstrates a simple unit test of the C portion of this plugin's
|
||||
// implementation.
|
||||
//
|
||||
// Once you have built the plugin's example app, you can run these tests
|
||||
// from the command line. For instance, for a plugin called my_plugin
|
||||
// built for x64 debug, run:
|
||||
// $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test
|
||||
|
||||
namespace {{projectName}} {
|
||||
namespace test {
|
||||
|
||||
TEST({{pluginClass}}, GetPlatformVersion) {
|
||||
g_autoptr(FlMethodResponse) response = get_platform_version();
|
||||
ASSERT_NE(response, nullptr);
|
||||
ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
|
||||
FlValue* result = fl_method_success_response_get_result(
|
||||
FL_METHOD_SUCCESS_RESPONSE(response));
|
||||
ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING);
|
||||
// The full string varies, so just validate that it has the right format.
|
||||
EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux "));
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace {{projectName}}
|
||||
@ -0,0 +1,22 @@
|
||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
# installed. You should not increase this version, as doing so will cause
|
||||
# the plugin to fail to compile for some customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Project-level configuration.
|
||||
set(PROJECT_NAME "{{projectName}}")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
# Invoke the build for native code shared with the other target platforms.
|
||||
# This can be changed to accommodate different builds.
|
||||
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
|
||||
|
||||
# List of absolute paths to libraries that should be bundled with the plugin.
|
||||
# This list could contain prebuilt libraries, or libraries created by an
|
||||
# external build triggered from this build file.
|
||||
set({{projectName}}_bundled_libraries
|
||||
# Defined in ../src/CMakeLists.txt.
|
||||
# This can be changed to accommodate different builds.
|
||||
$<TARGET_FILE:{{projectName}}>
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@ -76,13 +76,20 @@
|
||||
"templates/app/ios.tmpl/Runner/SceneDelegate.swift",
|
||||
"templates/app/ios.tmpl/RunnerTests/RunnerTests.swift.tmpl",
|
||||
"templates/app/lib/main.dart.tmpl",
|
||||
"templates/app/linux.tmpl/.gitignore",
|
||||
"templates/app/linux.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/app/linux.tmpl/flutter/CMakeLists.txt",
|
||||
"templates/app/linux.tmpl/runner/CMakeLists.txt",
|
||||
"templates/app/linux.tmpl/runner/main.cc",
|
||||
"templates/app/linux.tmpl/runner/my_application.cc.tmpl",
|
||||
"templates/app/linux.tmpl/runner/my_application.h",
|
||||
"templates/app/linux-gtk4.tmpl/.gitignore",
|
||||
"templates/app/linux-gtk3.tmpl/.gitignore",
|
||||
"templates/app/linux-gtk4.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/app/linux-gtk3.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/app/linux-gtk4.tmpl/flutter/CMakeLists.txt",
|
||||
"templates/app/linux-gtk3.tmpl/flutter/CMakeLists.txt",
|
||||
"templates/app/linux-gtk4.tmpl/runner/CMakeLists.txt",
|
||||
"templates/app/linux-gtk3.tmpl/runner/CMakeLists.txt",
|
||||
"templates/app/linux-gtk4.tmpl/runner/main.cc",
|
||||
"templates/app/linux-gtk3.tmpl/runner/main.cc",
|
||||
"templates/app/linux-gtk4.tmpl/runner/my_application.cc.tmpl",
|
||||
"templates/app/linux-gtk3.tmpl/runner/my_application.cc.tmpl",
|
||||
"templates/app/linux-gtk4.tmpl/runner/my_application.h",
|
||||
"templates/app/linux-gtk3.tmpl/runner/my_application.h",
|
||||
"templates/app/macos.tmpl/.gitignore",
|
||||
"templates/app/macos.tmpl/Flutter/Flutter-Debug.xcconfig",
|
||||
"templates/app/macos.tmpl/Flutter/Flutter-Release.xcconfig",
|
||||
@ -135,14 +142,10 @@
|
||||
"templates/app/windows.tmpl/runner/utils.h",
|
||||
"templates/app/windows.tmpl/runner/win32_window.cpp",
|
||||
"templates/app/windows.tmpl/runner/win32_window.h",
|
||||
|
||||
"templates/app_test_widget/test/widget_test.dart.tmpl",
|
||||
|
||||
"templates/app_integration_test/integration_test/plugin_integration_test.dart.tmpl",
|
||||
|
||||
"templates/cocoapods/Podfile-ios",
|
||||
"templates/cocoapods/Podfile-macos",
|
||||
|
||||
"templates/module/android/deferred_component/build.gradle.tmpl",
|
||||
"templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl",
|
||||
"templates/module/android/gradle/build.gradle.tmpl",
|
||||
@ -230,7 +233,6 @@
|
||||
"templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl",
|
||||
"templates/module/ios/library/Flutter.tmpl/README.md",
|
||||
"templates/module/README.md",
|
||||
|
||||
"templates/package/.gitignore.tmpl",
|
||||
"templates/package/.idea/libraries/Dart_SDK.xml.tmpl",
|
||||
"templates/package/.idea/modules.xml.tmpl",
|
||||
@ -244,7 +246,6 @@
|
||||
"templates/package/pubspec.yaml.tmpl",
|
||||
"templates/package/README.md.tmpl",
|
||||
"templates/package/test/projectName_test.dart.tmpl",
|
||||
|
||||
"templates/package_ffi/.gitignore.tmpl",
|
||||
"templates/package_ffi/.metadata.tmpl",
|
||||
"templates/package_ffi/analysis_options.yaml.tmpl",
|
||||
@ -259,7 +260,6 @@
|
||||
"templates/package_ffi/src.tmpl/projectName.c.tmpl",
|
||||
"templates/package_ffi/src.tmpl/projectName.h.tmpl",
|
||||
"templates/package_ffi/test/projectName_test.dart.tmpl",
|
||||
|
||||
"templates/plugin/android-java.tmpl/build.gradle.kts.tmpl",
|
||||
"templates/plugin/android-java.tmpl/projectName_android.iml.tmpl",
|
||||
"templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl",
|
||||
@ -278,11 +278,16 @@
|
||||
"templates/plugin/lib/projectName.dart.tmpl",
|
||||
"templates/plugin/lib/projectName_platform_interface.dart.tmpl",
|
||||
"templates/plugin/lib/projectName_method_channel.dart.tmpl",
|
||||
"templates/plugin/linux.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
|
||||
"templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl",
|
||||
"templates/plugin/linux.tmpl/pluginClassSnakeCase_private.h.tmpl",
|
||||
"templates/plugin/linux.tmpl/test/pluginClassSnakeCase_test.cc.tmpl",
|
||||
"templates/plugin/linux-gtk4.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin/linux-gtk3.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin/linux-gtk4.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
|
||||
"templates/plugin/linux-gtk3.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
|
||||
"templates/plugin/linux-gtk4.tmpl/pluginClassSnakeCase.cc.tmpl",
|
||||
"templates/plugin/linux-gtk3.tmpl/pluginClassSnakeCase.cc.tmpl",
|
||||
"templates/plugin/linux-gtk4.tmpl/pluginClassSnakeCase_private.h.tmpl",
|
||||
"templates/plugin/linux-gtk3.tmpl/pluginClassSnakeCase_private.h.tmpl",
|
||||
"templates/plugin/linux-gtk4.tmpl/test/pluginClassSnakeCase_test.cc.tmpl",
|
||||
"templates/plugin/linux-gtk3.tmpl/test/pluginClassSnakeCase_test.cc.tmpl",
|
||||
"templates/plugin/README.md.tmpl",
|
||||
"templates/plugin/test/projectName_test.dart.tmpl",
|
||||
"templates/plugin/test/projectName_method_channel_test.dart.tmpl",
|
||||
@ -293,7 +298,6 @@
|
||||
"templates/plugin/windows.tmpl/pluginClassSnakeCase.h.tmpl",
|
||||
"templates/plugin/windows.tmpl/pluginClassSnakeCase_c_api.cpp.tmpl",
|
||||
"templates/plugin/lib/projectName_web.dart.tmpl",
|
||||
|
||||
"templates/plugin_ffi/android.tmpl/build.gradle.tmpl",
|
||||
"templates/plugin_ffi/android.tmpl/projectName_android.iml.tmpl",
|
||||
"templates/plugin_ffi/ffigen.yaml.tmpl",
|
||||
@ -302,15 +306,16 @@
|
||||
"templates/plugin_ffi/ios.tmpl/projectName.podspec.tmpl",
|
||||
"templates/plugin_ffi/lib/projectName_bindings_generated.dart.tmpl",
|
||||
"templates/plugin_ffi/lib/projectName.dart.tmpl",
|
||||
"templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin_ffi/linux.tmpl/include/projectName.tmpl/plugin_ffiClassSnakeCase.h.tmpl",
|
||||
"templates/plugin_ffi/linux-gtk4.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin_ffi/linux-gtk3.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin_ffi/linux-gtk4.tmpl/include/projectName.tmpl/plugin_ffiClassSnakeCase.h.tmpl",
|
||||
"templates/plugin_ffi/linux-gtk3.tmpl/include/projectName.tmpl/plugin_ffiClassSnakeCase.h.tmpl",
|
||||
"templates/plugin_ffi/macos.tmpl/Classes/projectName.c.tmpl",
|
||||
"templates/plugin_ffi/README.md.tmpl",
|
||||
"templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl",
|
||||
"templates/plugin_ffi/src.tmpl/projectName.c.tmpl",
|
||||
"templates/plugin_ffi/src.tmpl/projectName.h.tmpl",
|
||||
"templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl",
|
||||
|
||||
"templates/plugin_shared/.gitignore.tmpl",
|
||||
"templates/plugin_shared/.idea/libraries/Dart_SDK.xml.tmpl",
|
||||
"templates/plugin_shared/.idea/modules.xml.tmpl",
|
||||
@ -326,24 +331,20 @@
|
||||
"templates/plugin_shared/projectName.iml.tmpl",
|
||||
"templates/plugin_shared/pubspec.yaml.tmpl",
|
||||
"templates/plugin_shared/windows.tmpl/.gitignore",
|
||||
|
||||
"templates/plugin_darwin_cocoapods/darwin.tmpl/.gitignore",
|
||||
"templates/plugin_darwin_cocoapods/darwin.tmpl/Classes/pluginClass.swift.tmpl",
|
||||
"templates/plugin_darwin_cocoapods/darwin.tmpl/Resources/PrivacyInfo.xcprivacy",
|
||||
"templates/plugin_darwin_cocoapods/darwin.tmpl/projectName.podspec.tmpl",
|
||||
|
||||
"templates/plugin_darwin_spm/darwin.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl",
|
||||
"templates/plugin_darwin_spm/darwin.tmpl/projectName.tmpl/Package.swift.tmpl",
|
||||
"templates/plugin_darwin_spm/darwin.tmpl/projectName.tmpl/Sources/projectName.tmpl/PrivacyInfo.xcprivacy",
|
||||
"templates/plugin_darwin_spm/darwin.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep",
|
||||
"templates/plugin_darwin_spm/darwin.tmpl/projectName.podspec.tmpl",
|
||||
|
||||
"templates/plugin_cocoapods/ios.tmpl/Classes/pluginClass.swift.tmpl",
|
||||
"templates/plugin_cocoapods/ios.tmpl/Assets/.gitkeep",
|
||||
"templates/plugin_cocoapods/ios.tmpl/Resources/PrivacyInfo.xcprivacy",
|
||||
"templates/plugin_cocoapods/macos.tmpl/Classes/pluginClass.swift.tmpl",
|
||||
"templates/plugin_cocoapods/macos.tmpl/Resources/PrivacyInfo.xcprivacy",
|
||||
|
||||
"templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl",
|
||||
"templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Package.swift.tmpl",
|
||||
"templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Sources/projectName.tmpl/PrivacyInfo.xcprivacy",
|
||||
@ -351,7 +352,6 @@
|
||||
"templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl",
|
||||
"templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Package.swift.tmpl",
|
||||
"templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/PrivacyInfo.xcprivacy",
|
||||
|
||||
"templates/widget_preview_scaffold/lib/main.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl",
|
||||
@ -375,7 +375,6 @@
|
||||
"templates/widget_preview_scaffold/lib/src/utils.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/pubspec.yaml.tmpl",
|
||||
"templates/widget_preview_scaffold/README.md.tmpl",
|
||||
|
||||
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata",
|
||||
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist",
|
||||
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user