├── .gitignore ├── .cargo └── config.toml ├── linux ├── icudtl.dat └── keymap.ods ├── src ├── flutter_bindings.rs ├── flutter_application │ ├── lifecycle.rs │ ├── platform_views.rs │ ├── task_runner.rs │ ├── compositor.rs │ ├── keyboard_event.rs │ ├── platform.rs │ ├── text_input.rs │ ├── mouse_cursor.rs │ ├── keyboard.rs │ └── message_codec.rs ├── action_key.rs ├── utils.rs ├── main.rs ├── keyboard_physical_key_map.rs ├── keyboard_logical_key_map.rs └── flutter_application.rs ├── Cargo.toml ├── .vscode └── launch.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /cache 3 | /windows 4 | /.cookies -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "link-args=-Wl,-rpath=linux/"] -------------------------------------------------------------------------------- /linux/icudtl.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anlumo/flutter_embedder/HEAD/linux/icudtl.dat -------------------------------------------------------------------------------- /linux/keymap.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anlumo/flutter_embedder/HEAD/linux/keymap.ods -------------------------------------------------------------------------------- /src/flutter_bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(unused)] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/embedder.rs")); 7 | -------------------------------------------------------------------------------- /src/flutter_application/lifecycle.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | #[serde(rename_all = "camelCase")] 5 | pub(super) enum LifecycleState { 6 | Resumed, 7 | Inactive, 8 | Paused, 9 | Detached, 10 | } 11 | -------------------------------------------------------------------------------- /src/action_key.rs: -------------------------------------------------------------------------------- 1 | use winit::keyboard::ModifiersState; 2 | 3 | pub trait ActionKey { 4 | fn action_key(&self) -> bool; 5 | } 6 | 7 | impl ActionKey for ModifiersState { 8 | #[cfg(not(any(target_os = "macos", target_os = "ios")))] 9 | fn action_key(&self) -> bool { 10 | self.control_key() 11 | } 12 | 13 | #[cfg(any(target_os = "macos", target_os = "ios"))] 14 | fn action_key(&self) -> bool { 15 | self.super_key() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | pub fn flutter_asset_bundle_is_valid(bundle_path: &Path) -> bool { 4 | if !bundle_path.exists() { 5 | log::error!("Bundle directory does not exist."); 6 | return false; 7 | } 8 | 9 | let mut kernel_path = bundle_path.to_path_buf(); 10 | kernel_path.push("kernel_blob.bin"); 11 | 12 | if !kernel_path.exists() { 13 | log::error!("Kernel blob {} does not exist.", kernel_path.display()); 14 | return false; 15 | } 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flutter_embedder" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "3.1.12", features = ["derive"] } 8 | log = { version = "0.4.17", features = ["std"] } 9 | env_logger = "0.9" 10 | # wgpu = "0.13.1" 11 | # wgpu-hal = { version = "0.13.1", features = ["vulkan"] } 12 | wgpu = { git = "https://github.com/gfx-rs/wgpu", branch = "master" } 13 | wgpu-hal = { git = "https://github.com/gfx-rs/wgpu", branch = "master", features = ["vulkan"] } 14 | # winit = "0.27.1" 15 | winit = { git = "https://github.com/anlumo/winit-new-keyboard.git", branch = "new-keyboard-linux", default-features = false, features = ["x11"] } 16 | tokio = { version = "1.19.2", features = ["full"] } 17 | ash = "0.37.0" 18 | serde = { version = "1.0.144", features = ["derive"] } 19 | serde_json = "1.0.85" 20 | arboard = "2.1.1" 21 | num-derive = "0.3.3" 22 | num-traits = "0.2.15" 23 | serde_variant = "0.1.1" 24 | 25 | [build-dependencies] 26 | bindgen = "0.60.1" 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'flutter_embedder'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=flutter_embedder", 15 | "--package=flutter_embedder" 16 | ], 17 | "filter": { 18 | "name": "flutter_embedder", 19 | "kind": "bin" 20 | } 21 | }, 22 | "env": { 23 | "RUST_LOG": "debug,wgpu_core=warn", 24 | "WAYLAND_DISPLAY": "" 25 | }, 26 | "args": ["../../homebox/fomebox/build/flutter_assets/"], 27 | "cwd": "${workspaceFolder}" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /src/flutter_application/platform_views.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value; 5 | 6 | #[derive(Debug, Serialize, Deserialize, Clone)] 7 | pub(super) struct FlutterSize { 8 | width: f64, 9 | height: f64, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize, Clone)] 13 | #[serde(tag = "method", content = "args", rename_all = "camelCase")] 14 | pub(super) enum PlatformViewMessage { 15 | Create(PlatformView), 16 | // ClearFocus { 17 | // id: i64, 18 | // }, 19 | // PointerEvent { 20 | // id: i64, 21 | // event: Value, 22 | // }, 23 | Dispose(i32), 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize, Clone)] 27 | #[serde(rename_all = "camelCase")] 28 | pub(super) struct PlatformView { 29 | id: i32, 30 | view_type: String, 31 | size: Option, 32 | } 33 | 34 | #[derive(Default)] 35 | pub(super) struct PlatformViewsHandler { 36 | views: HashMap, 37 | } 38 | 39 | impl PlatformViewsHandler { 40 | pub(super) fn handle_platform_views_message( 41 | &mut self, 42 | message: PlatformViewMessage, 43 | ) -> Option> { 44 | match message { 45 | PlatformViewMessage::Create(view) => { 46 | self.views.insert(view.id, view); 47 | Some(serde_json::to_vec(&Value::Array(vec![Value::Bool(true)])).unwrap()) 48 | } 49 | PlatformViewMessage::Dispose(id) => { 50 | self.views.remove(&id); 51 | Some(serde_json::to_vec(&Value::Array(vec![Value::Bool(true)])).unwrap()) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/flutter_application/task_runner.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, thread::ThreadId, time::Duration}; 2 | 3 | use tokio::{ 4 | runtime::Builder, 5 | sync::{mpsc, oneshot}, 6 | task::LocalSet, 7 | }; 8 | 9 | use crate::flutter_bindings::{FlutterEngine, FlutterEngineRunTask, FlutterTask}; 10 | 11 | use super::{FlutterApplication, SendFlutterTask}; 12 | 13 | pub(super) struct Task { 14 | task: SendFlutterTask, 15 | target_time_nanos: u64, 16 | } 17 | 18 | #[derive(Clone, Copy)] 19 | struct SendFlutterEngine(FlutterEngine); 20 | 21 | unsafe impl Send for SendFlutterEngine {} 22 | 23 | pub(super) struct TaskRunner { 24 | new_sender: Option>, 25 | sender: mpsc::UnboundedSender, 26 | thread_id: ThreadId, 27 | thread_name: String, 28 | } 29 | 30 | impl TaskRunner { 31 | pub(super) fn new(name: String) -> Self { 32 | let (new_sender, new_receiver) = oneshot::channel::(); 33 | let (sender, mut receiver) = mpsc::unbounded_channel::(); 34 | let join_handle = std::thread::Builder::new() 35 | .name(name.clone()) 36 | .spawn(move || { 37 | let engine = new_receiver.blocking_recv().unwrap(); 38 | let rt = Builder::new_current_thread().enable_time().build().unwrap(); 39 | let local = LocalSet::new(); 40 | local.block_on(&rt, async move { 41 | log::debug!("Waiting for tasks on {:?}", std::thread::current().name()); 42 | while let Some(Task { 43 | task, 44 | target_time_nanos, 45 | }) = receiver.recv().await 46 | { 47 | let now = FlutterApplication::current_time(); 48 | if now >= target_time_nanos { 49 | FlutterApplication::unwrap_result(unsafe { 50 | FlutterEngineRunTask(engine.0, &task.0) 51 | }); 52 | } else { 53 | tokio::task::spawn_local(async move { 54 | tokio::time::sleep(Duration::from_nanos(target_time_nanos - now)) 55 | .await; 56 | FlutterApplication::unwrap_result(unsafe { 57 | FlutterEngineRunTask(engine.0, &task.0) 58 | }); 59 | }); 60 | } 61 | } 62 | log::debug!( 63 | "Done receiving tasks on {:?}", 64 | std::thread::current().name() 65 | ); 66 | }); 67 | }) 68 | .unwrap(); 69 | 70 | Self { 71 | new_sender: Some(new_sender), 72 | sender, 73 | thread_id: join_handle.thread().id(), 74 | thread_name: name, 75 | } 76 | } 77 | 78 | pub(super) fn run(&mut self, engine: FlutterEngine) { 79 | let engine = SendFlutterEngine(engine); 80 | if let Some(sender) = self.new_sender.take() { 81 | sender.send(engine).ok().unwrap(); 82 | } 83 | } 84 | 85 | pub(super) extern "C" fn runs_task_on_current_thread_callback(user_data: *mut c_void) -> bool { 86 | let this = unsafe { &*(user_data as *const Self) as &Self }; 87 | this.thread_id == std::thread::current().id() 88 | } 89 | 90 | pub(super) extern "C" fn post_task_callback( 91 | task: FlutterTask, 92 | target_time_nanos: u64, 93 | user_data: *mut c_void, 94 | ) { 95 | let task = SendFlutterTask(task); 96 | let this = unsafe { &*(user_data as *const Self) as &Self }; 97 | this.sender 98 | .send(Task { 99 | task, 100 | target_time_nanos, 101 | }) 102 | .ok() 103 | .unwrap(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter_embedder 2 | 3 | In a nutshell, this is a project about integrating Flutter with wgpu. 4 | 5 | ## Flutter 6 | 7 | [Flutter](https://flutter.dev/) is a UI framework written by Google for mobile platforms that supports running on Android, iOS, macOS, Windows, Linux, Fuchsia and Web. It tries to get unified visualization and behavior of its UI by ignoring all of the built-in native UI and doing everything itself, with only a thin interface layer to the OS necessary. 8 | 9 | For this, it can use a range of rendering APIs, namely OpenGL, Metal, Vulkan and plain software rendering. 10 | 11 | On the application developer side, it uses Dart, a language developed by Google that can be compiled to machine code and JavaScript. The web variant uses the latter, while all other platforms use the former. 12 | 13 | Flutter is based on defining the UI in a declarative style similar to React. 14 | 15 | ## Rust 16 | 17 | [Rust](https://www.rust-lang.org/) is a systems language that emphasises reliability. For this project, one of the most important aspects is that it also supports a wide variety of platforms, including all mentioned above for Flutter. 18 | 19 | ## wgpu 20 | 21 | There is a rendering library for Rust called [wgpu](https://wgpu.rs/) that abstracts away native rendering APIs into a common API that's easier to use. Based on this, many rendering frameworks like [bevy](https://bevyengine.org/) have been developed. 22 | 23 | ## Goal 24 | 25 | The goal behind this project is to allow rendering frameworks like bevy to use a UI written in Flutter. The background is that all Rust-based UI frameworks are quite limited in their feature set. Google has invested a ton of time into writing Flutter, which can be leveraged for this. 26 | 27 | The special thing about Flutter is that it has full support for using it as a library to integrate it into any kind of application. It supplies a C-based embedder API (so it's easy to interface with Rust) that allows detailed control over the rendering process via a compositor. This even allows interleaving Flutter UI with natively-rendered widgets (called platform widgets in Flutter). 28 | 29 | Flutter and wgpu have two intersections for rendering APIs, which are Vulkan and Metal. Unfortunately, the official shells (these are the applications using the embedder) for native Flutter applications all use OpenGL, with the exception of iOS/macOS, which use Metal. Vulkan is only used on Fuchsia. 30 | 31 | One important rendering API missing from Flutter is DirectX12, which is the only preinstalled one on Windows. The official Windows shell alleviates this by using [ANGLE](https://github.com/google/angle), which is an OpenGL implementation that can translate the calls to DirectX11. This is not a direction this project wants to take. On Windows, Vulkan can be installed by endusers by installing the graphics drivers for their system. 32 | 33 | So, the goal of this project is to use the Vulkan rendering backend of Flutter in a wgpu context in order to integrate them both into a single window. 34 | 35 | Flutter shells also have to supply all of the interface with the native operating system. In the official shells, this is done manually with a lot of code, and all of them are completely separate projects. This is a lot of work and not feasible here. 36 | 37 | In the Rust world, there is [winit](https://github.com/rust-windowing/winit), which is an abstraction over various platforms to provide a single API for creating windows and receiving events (like mouse and keyboard input). This mostly matches the needs of the Flutter embedder. 38 | 39 | ### End Game 40 | 41 | Once all of this works, the plan is to create a library crate and publish on crates.io. If you want to create a wgpu-application with Flutter, you have to create a regular Rust binary project and add the crate as normal. Then in your code, you somewhere have to initialize the library and run it. This is also where you register your own Platform Widgets (which might use bevy or whatever you like). 42 | 43 | This is the same structure as used for the official shells. 44 | 45 | Note that the Flutter engine is built as a shared library, so it needs to be shipped along with your own binary and all of the Flutter resources. 46 | 47 | The web works completely differently there (since the engine is compiled to JavaScript, and there's HTML to bootstrap everything), so it's a non-goal for the project. However, there it's quite easy to use the official shell, because it allows defining a canvas as a platform widget, which then can be used for wgpu without any special preparations. 48 | 49 | ## Current State 50 | 51 | Everything is highly experimental. This project is far from being usable for real applications! 52 | 53 | - Opening the window and initializing the Flutter runtime works. 54 | - Rendering the first layer of the Flutter UI works. 55 | - Platform Views are missing. 56 | - Rendering multiple layers is not implemented correctly, because there is no blending (it's just bitwise copying the texture). 57 | - Resizing windows is buggy (buffer sizes are out of sync) 58 | - Mouse input works 59 | - Changing the mouse cursor works 60 | - Keyboard input is halfway there. 61 | - There are three different APIs in Flutter for this: keyevent, keydata, and textinput. Keydata is optional, the other two are necessary. 62 | - keyevent and textinput are implemented 63 | - They are implemented using an experimental winit API from a pull request, because the stable API does not supply the information necessary. 64 | - keydata is problematic, because it requires to supply the keyboard events in a specific platform-specific format, which we don't have. 65 | - textinput is a very complex API, because all of the complexity of handling text is offloaded to the shell. IME support is missing, as is autocomplete and dictionary support. 66 | - All relevant system channels are implemented. Some of them don't apply to desktop platforms and some aren't implemented by winit at the moment (like the system alert sound). 67 | - Only Linux is working in some aspects. The main reason is that the new winit API for keyboard handling hasn't been implemented for Windows yet. Also, there is no support for Metal right now for iOS/macOS. 68 | - Mobile is not a focus at the moment, but might come later. 69 | 70 | ## Accessibility 71 | 72 | This is always a big topic for UI frameworks. Flutter has full support for accessibility, including screenreaders. However, as expected this relies on the shell to provide the operating system interoperation. 73 | 74 | Accessibility APIs differ widely between different operating systems, so this is hard to achieve with a project like this, where having a single codebase for all systems is a primary goal. 75 | 76 | However, luckily there is a project called [AccessKit](https://github.com/AccessKit/accesskit) that aims to provide a way to add accessibility to winit as a cross-platform solution, offloading all platform-specific code to that crate. It is still in its early stages and the longevity is unclear, but _if_ it succeeds in providing what it aims to do, it is a prime candidate to solve the issue for this project. 77 | 78 | ## License 79 | 80 | This project is licensed under the Apache 2.0 license. See [LICENSE](./LICENSE) for details. 81 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![feature(once_cell, result_option_inspect)] 3 | use std::{path::PathBuf, sync::Arc}; 4 | 5 | use clap::Parser; 6 | use tokio::runtime::Builder; 7 | use wgpu::{ 8 | Backends, DeviceDescriptor, Features, Instance, Limits, PowerPreference, PresentMode, 9 | RequestAdapterOptions, SurfaceConfiguration, TextureFormat, TextureUsages, 10 | }; 11 | use winit::{ 12 | dpi::PhysicalPosition, 13 | event::{Event, WindowEvent}, 14 | event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, 15 | window::{Window, WindowBuilder}, 16 | }; 17 | 18 | mod flutter_application; 19 | use flutter_application::{FlutterApplication, FlutterApplicationCallback}; 20 | 21 | mod action_key; 22 | mod keyboard_logical_key_map; 23 | mod keyboard_physical_key_map; 24 | 25 | mod flutter_bindings; 26 | mod utils; 27 | 28 | #[derive(Parser, Debug)] 29 | #[clap(author, version, about, long_about = None)] 30 | struct Args { 31 | /// The Flutter application code needs to be snapshotted using 32 | /// the Flutter tools and the assets packaged in the appropriate 33 | /// location. This can be done for any Flutter application by 34 | /// running `flutter build bundle` while in the directory of a 35 | /// valid Flutter project. This should package all the code and 36 | /// assets in the "build/flutter_assets" directory. Specify this 37 | /// directory as the first argument to this utility. 38 | pub asset_bundle_path: PathBuf, 39 | /// Typically empty. These extra flags are passed directly to the 40 | /// Flutter engine. To see all supported flags, run 41 | /// `flutter_tester --help` using the test binary included in the 42 | /// Flutter tools. 43 | pub flutter_flags: Vec, 44 | } 45 | 46 | fn main() -> Result<(), std::io::Error> { 47 | env_logger::init(); 48 | let args = Args::parse(); 49 | 50 | let event_loop: EventLoop = 51 | EventLoopBuilder::with_user_event().build(); 52 | let window = WindowBuilder::new() 53 | .with_title("Flutter Embedder") 54 | // .with_inner_size(PhysicalSize::new(1024, 768)) 55 | .build(&event_loop) 56 | .unwrap(); 57 | // window.set_outer_position(PhysicalPosition::new(100, 100)); 58 | 59 | let rt = Arc::new(Builder::new_multi_thread().build()?); 60 | let inner_rt = rt.clone(); 61 | 62 | rt.block_on(async move { 63 | let instance = Instance::new(Backends::VULKAN); 64 | let surface = unsafe { instance.create_surface(&window) }; 65 | let adapter = instance 66 | .request_adapter(&RequestAdapterOptions { 67 | power_preference: PowerPreference::default(), 68 | compatible_surface: Some(&surface), 69 | force_fallback_adapter: false, 70 | }) 71 | .await 72 | .unwrap(); 73 | 74 | let (device, queue) = adapter 75 | .request_device( 76 | &DeviceDescriptor { 77 | label: None, 78 | features: Features::CLEAR_TEXTURE, 79 | limits: Limits::downlevel_defaults(), 80 | }, 81 | None, 82 | ) 83 | .await 84 | .expect("Failed to create device"); 85 | 86 | let size = window.inner_size(); 87 | 88 | log::debug!( 89 | "Supported formats: {:?}", 90 | surface.get_supported_formats(&adapter) 91 | ); 92 | let formats = surface.get_supported_formats(&adapter); 93 | let format = formats 94 | .into_iter() 95 | .find(|&format| format == TextureFormat::Bgra8Unorm) 96 | .expect("Adapter doesn't support BGRA8 render buffer."); 97 | 98 | surface.configure( 99 | &device, 100 | &SurfaceConfiguration { 101 | usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_DST, 102 | format, 103 | width: size.width, 104 | height: size.height, 105 | present_mode: PresentMode::Fifo, 106 | }, 107 | ); 108 | 109 | let window = Arc::new(window); 110 | let inner_window = window.clone(); 111 | 112 | let mut app = FlutterApplication::new( 113 | inner_rt, 114 | &args.asset_bundle_path, 115 | args.flutter_flags, 116 | surface, 117 | Arc::new(instance), 118 | device, 119 | queue, 120 | event_loop.create_proxy(), 121 | window.clone(), 122 | move |cursor| { 123 | if let Some(cursor) = cursor { 124 | inner_window.set_cursor_visible(true); 125 | inner_window.set_cursor_icon(cursor); 126 | } else { 127 | inner_window.set_cursor_visible(false); 128 | } 129 | }, 130 | ); 131 | 132 | app.run(); 133 | 134 | // Trigger a FlutterEngineSendWindowMetricsEvent to communicate the initial 135 | // size of the window. 136 | metrics_changed(&app, &window); 137 | 138 | event_loop.run(move |event, _, control_flow| { 139 | let _ = &adapter; 140 | 141 | *control_flow = ControlFlow::Wait; 142 | match event { 143 | Event::UserEvent(handler) => { 144 | if handler(&mut app) { 145 | *control_flow = ControlFlow::Exit; 146 | } 147 | } 148 | Event::RedrawRequested(_window_id) => { 149 | app.schedule_frame(); 150 | } 151 | Event::WindowEvent { event, .. } => match event { 152 | WindowEvent::CloseRequested => { 153 | *control_flow = ControlFlow::Exit; 154 | } 155 | WindowEvent::Moved(_) 156 | | WindowEvent::Resized(_) 157 | | WindowEvent::ScaleFactorChanged { .. } => { 158 | metrics_changed(&app, &window); 159 | } 160 | WindowEvent::MouseInput { 161 | device_id, 162 | state, 163 | button, 164 | .. 165 | } => { 166 | app.mouse_buttons(device_id, state, button); 167 | } 168 | WindowEvent::CursorEntered { device_id } => { 169 | app.mouse_entered(device_id); 170 | } 171 | WindowEvent::CursorLeft { device_id } => { 172 | app.mouse_left(device_id); 173 | } 174 | WindowEvent::CursorMoved { 175 | device_id, 176 | position, 177 | .. 178 | } => { 179 | app.mouse_moved(device_id, position); 180 | } 181 | WindowEvent::MouseWheel { 182 | device_id, 183 | delta, 184 | phase, 185 | .. 186 | } => { 187 | app.mouse_wheel(device_id, delta, phase); 188 | } 189 | WindowEvent::ModifiersChanged(state) => { 190 | app.modifiers_changed(state); 191 | } 192 | WindowEvent::KeyboardInput { 193 | event, 194 | device_id, 195 | is_synthetic, 196 | } => { 197 | app.key_event(device_id, event, is_synthetic); 198 | } 199 | WindowEvent::Focused(focused) => { 200 | app.focused(focused); 201 | } 202 | _ => {} 203 | }, 204 | _ => {} 205 | } 206 | }); 207 | }); 208 | Ok(()) 209 | } 210 | 211 | fn metrics_changed(application: &FlutterApplication, window: &Window) { 212 | let size = window.inner_size(); 213 | let position = window 214 | .inner_position() 215 | .unwrap_or(PhysicalPosition { x: 0, y: 0 }); 216 | log::debug!( 217 | "scale_factor = {:?}", 218 | window.scale_factor(), 219 | // window 220 | // .current_monitor() 221 | // .map(|monitor| monitor.scale_factor()) 222 | ); 223 | application.metrics_changed( 224 | size.width, 225 | size.height, 226 | window 227 | .current_monitor() 228 | .map(|monitor| monitor.scale_factor()) 229 | .unwrap_or(1.0), 230 | position.x, 231 | position.y, 232 | ); 233 | } 234 | -------------------------------------------------------------------------------- /src/flutter_application/compositor.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::Cell, ffi::c_void, mem::size_of, ptr::null_mut}; 2 | 3 | use ash::vk::Handle; 4 | use wgpu::{ 5 | CommandEncoderDescriptor, Extent3d, ImageCopyTextureBase, Origin3d, Texture, TextureAspect, 6 | TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, 7 | }; 8 | use wgpu_hal::api::Vulkan; 9 | 10 | use crate::{ 11 | flutter_application::FlutterApplication, 12 | flutter_bindings::{ 13 | size_t, FlutterBackingStore, FlutterBackingStoreConfig, 14 | FlutterBackingStoreType_kFlutterBackingStoreTypeVulkan, FlutterBackingStore__bindgen_ty_1, 15 | FlutterCompositor, FlutterLayer, 16 | FlutterLayerContentType_kFlutterLayerContentTypeBackingStore, 17 | FlutterLayerContentType_kFlutterLayerContentTypePlatformView, FlutterVulkanBackingStore, 18 | FlutterVulkanImage, 19 | }, 20 | }; 21 | 22 | use super::FlutterApplicationUserData; 23 | 24 | pub struct Compositor { 25 | platform_view_count: Cell, 26 | } 27 | 28 | impl Compositor { 29 | pub fn new() -> Self { 30 | Self { 31 | platform_view_count: Cell::new(0), 32 | } 33 | } 34 | 35 | pub fn flutter_compositor(&self, application: &FlutterApplication) -> FlutterCompositor { 36 | FlutterCompositor { 37 | struct_size: size_of::() as _, 38 | user_data: &*application.user_data as *const FlutterApplicationUserData as _, 39 | create_backing_store_callback: Some(Self::create_backing_store_callback), 40 | collect_backing_store_callback: Some(Self::backing_store_collect_callback), 41 | present_layers_callback: Some(Self::present_layers_callback), 42 | avoid_backing_store_cache: false, 43 | } 44 | } 45 | 46 | extern "C" fn create_backing_store_callback( 47 | config: *const FlutterBackingStoreConfig, 48 | backing_store_out: *mut FlutterBackingStore, 49 | user_data: *mut c_void, 50 | ) -> bool { 51 | let application_user_data = unsafe { 52 | &*(user_data as *const FlutterApplicationUserData) as &FlutterApplicationUserData 53 | }; 54 | 55 | let texture = application_user_data 56 | .device 57 | .create_texture(&TextureDescriptor { 58 | label: Some("Flutter Backing Store"), 59 | size: wgpu::Extent3d { 60 | width: unsafe { *config }.size.width as _, 61 | height: unsafe { *config }.size.height as _, 62 | depth_or_array_layers: 1, 63 | }, 64 | mip_level_count: 1, 65 | sample_count: 1, 66 | dimension: TextureDimension::D2, 67 | format: TextureFormat::Bgra8Unorm, 68 | usage: TextureUsages::COPY_SRC 69 | | TextureUsages::RENDER_ATTACHMENT 70 | | TextureUsages::TEXTURE_BINDING, 71 | }); 72 | 73 | let mut image = None; 74 | unsafe { 75 | texture.as_hal::(|texture| { 76 | let texture = texture.unwrap(); 77 | image = Some(FlutterVulkanImage { 78 | struct_size: size_of::() as _, 79 | image: texture.raw_handle().as_raw() as _, 80 | format: ash::vk::Format::B8G8R8A8_UNORM.as_raw() as _, 81 | }); 82 | }); 83 | } 84 | let image = image.unwrap(); 85 | let user_data = Box::new((texture, image)); 86 | let mut backing_store = unsafe { &mut *backing_store_out as &mut FlutterBackingStore }; 87 | backing_store.user_data = null_mut(); 88 | backing_store.type_ = FlutterBackingStoreType_kFlutterBackingStoreTypeVulkan; 89 | backing_store.did_update = true; 90 | backing_store.__bindgen_anon_1 = FlutterBackingStore__bindgen_ty_1 { 91 | vulkan: FlutterVulkanBackingStore { 92 | struct_size: size_of::() as _, 93 | image: &user_data.1, 94 | user_data: Box::into_raw(user_data) as _, 95 | destruction_callback: Some(Self::destroy_texture), 96 | }, 97 | }; 98 | true 99 | } 100 | extern "C" fn destroy_texture(user_data: *mut c_void) { 101 | let (texture, _image) = 102 | *unsafe { Box::from_raw(user_data as *mut (Texture, FlutterVulkanImage)) }; 103 | texture.destroy(); 104 | } 105 | extern "C" fn present_layers_callback( 106 | layers: *mut *const FlutterLayer, 107 | layers_count: size_t, 108 | user_data: *mut c_void, 109 | ) -> bool { 110 | let application_user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 111 | 112 | let frame = application_user_data 113 | .surface 114 | .get_current_texture() 115 | .expect("Failed to acquire next swap chain texture"); 116 | let mut encoder = application_user_data 117 | .device 118 | .create_command_encoder(&CommandEncoderDescriptor { label: None }); 119 | { 120 | // encoder.clear_texture(&frame.texture, &ImageSubresourceRange::default()); 121 | // encoder.begin_render_pass(&RenderPassDescriptor { 122 | // label: None, 123 | // color_attachments: &[Some(RenderPassColorAttachment { 124 | // view: &view, 125 | // resolve_target: None, 126 | // ops: Operations { 127 | // load: LoadOp::Clear(Color { 128 | // r: 0.0, 129 | // g: 1.0, 130 | // b: 0.0, 131 | // a: 1.0, 132 | // }), 133 | // store: true, 134 | // }, 135 | // })], 136 | // depth_stencil_attachment: None, 137 | // }); 138 | 139 | for (idx, &layer) in unsafe { std::slice::from_raw_parts(layers, layers_count as _) } 140 | .iter() 141 | .map(|&layer| unsafe { &*layer } as &FlutterLayer) 142 | .enumerate() 143 | { 144 | let offset = layer.offset; 145 | let size = layer.size; 146 | log::debug!("Layer {idx} type {}", layer.type_); 147 | match layer.type_ { 148 | x if x == FlutterLayerContentType_kFlutterLayerContentTypeBackingStore => { 149 | let backing_store = unsafe { &*layer.__bindgen_anon_1.backing_store }; 150 | assert_eq!( 151 | backing_store.type_, 152 | FlutterBackingStoreType_kFlutterBackingStoreTypeVulkan 153 | ); 154 | let backing_store = unsafe { &backing_store.__bindgen_anon_1.vulkan }; 155 | let texture = unsafe { &*(backing_store.user_data as *mut Texture) }; 156 | 157 | encoder.copy_texture_to_texture( 158 | ImageCopyTextureBase { 159 | texture, 160 | mip_level: 0, 161 | origin: Origin3d::ZERO, 162 | aspect: TextureAspect::All, 163 | }, 164 | ImageCopyTextureBase { 165 | texture: &frame.texture, 166 | mip_level: 0, 167 | origin: Origin3d { 168 | x: offset.x as _, 169 | y: offset.y as _, 170 | z: 0, 171 | }, 172 | aspect: TextureAspect::All, 173 | }, 174 | Extent3d { 175 | width: size.width as _, 176 | height: size.height as _, 177 | depth_or_array_layers: 1, 178 | }, 179 | ); 180 | } 181 | x if x == FlutterLayerContentType_kFlutterLayerContentTypePlatformView => { 182 | log::error!( 183 | "Rendering platform view {}: not implemented yet!", 184 | unsafe { &*layer.__bindgen_anon_1.platform_view }.identifier 185 | ); 186 | } 187 | _ => panic!("Invalid layer type"), 188 | } 189 | } 190 | } 191 | application_user_data.queue.submit(Some(encoder.finish())); 192 | frame.present(); 193 | true 194 | } 195 | extern "C" fn backing_store_collect_callback( 196 | _renderer: *const FlutterBackingStore, 197 | _user_data: *mut c_void, 198 | ) -> bool { 199 | // let _this = user_data as *const FlutterApplication; 200 | // destroy the user_data in FlutterBackingStore. Since we passed nullptr there, there's nothing to do 201 | true 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/flutter_application/keyboard_event.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, Copy)] 4 | #[serde(rename_all = "lowercase")] 5 | pub(super) enum LinuxToolkit { 6 | Glfw, 7 | Gtk, 8 | } 9 | 10 | #[derive(Debug, Serialize, Deserialize, Clone, Copy)] 11 | #[serde(rename_all = "lowercase")] 12 | pub(super) enum FlutterKeyboardEventType { 13 | KeyUp, 14 | KeyDown, 15 | } 16 | 17 | #[derive(Debug, Serialize, Deserialize, Clone)] 18 | #[serde(rename_all = "camelCase", tag = "keymap")] 19 | pub(super) enum FlutterKeyboardEvent { 20 | Android { 21 | r#type: FlutterKeyboardEventType, 22 | /// The current set of additional flags for this event. 23 | /// 24 | /// Flags indicate things like repeat state, etc. 25 | /// 26 | /// See 27 | /// for more information. 28 | flags: u64, 29 | /// The Unicode code point represented by the key event, if any. 30 | /// 31 | /// If there is no Unicode code point, this value is zero. 32 | /// 33 | /// Dead keys are represented as Unicode combining characters. 34 | /// 35 | /// See 36 | /// for more information. 37 | code_point: u64, 38 | /// The hardware key code corresponding to this key event. 39 | /// 40 | /// This is the physical key that was pressed, not the Unicode character. 41 | /// See [codePoint] for the Unicode character. 42 | /// 43 | /// See 44 | /// for more information. 45 | key_code: u64, 46 | /// The Unicode code point represented by the key event, if any, without 47 | /// regard to any modifier keys which are currently pressed. 48 | /// 49 | /// If there is no Unicode code point, this value is zero. 50 | /// 51 | /// Dead keys are represented as Unicode combining characters. 52 | /// 53 | /// This is the result of calling KeyEvent.getUnicodeChar(0) on Android. 54 | /// 55 | /// See 56 | /// for more information. 57 | plain_code_point: u64, 58 | /// The hardware scan code id corresponding to this key event. 59 | /// 60 | /// These values are not reliable and vary from device to device, so this 61 | /// information is mainly useful for debugging. 62 | /// 63 | /// See 64 | /// for more information. 65 | scan_code: u64, 66 | /// The modifiers that were present when the key event occurred. 67 | /// 68 | /// See 69 | /// for the numerical values of the `metaState`. Many of these constants are 70 | /// also replicated as static constants in this class. 71 | /// 72 | /// See also: 73 | /// 74 | /// * [modifiersPressed], which returns a Map of currently pressed modifiers 75 | /// and their keyboard side. 76 | /// * [isModifierPressed], to see if a specific modifier is pressed. 77 | /// * [isControlPressed], to see if a CTRL key is pressed. 78 | /// * [isShiftPressed], to see if a SHIFT key is pressed. 79 | /// * [isAltPressed], to see if an ALT key is pressed. 80 | /// * [isMetaPressed], to see if a META key is pressed. 81 | meta_state: u64, 82 | /// The source of the event. 83 | /// 84 | /// See 85 | /// for the numerical values of the `source`. Many of these constants are also 86 | /// replicated as static constants in this class. 87 | event_source: u64, 88 | /// The vendor ID of the device that produced the event. 89 | /// 90 | /// See 91 | /// for the numerical values of the `vendorId`. 92 | vendor_id: u64, 93 | /// The product ID of the device that produced the event. 94 | /// 95 | /// See 96 | /// for the numerical values of the `productId`. 97 | product_id: u64, 98 | /// The ID of the device that produced the event. 99 | /// 100 | /// See https://developer.android.com/reference/android/view/InputDevice.html#getId() 101 | device_id: u64, 102 | /// The repeat count of the event. 103 | /// 104 | /// See 105 | /// for more information. 106 | repeat_count: u64, 107 | }, 108 | Macos { 109 | r#type: FlutterKeyboardEventType, 110 | /// The Unicode characters associated with a key-up or key-down event. 111 | /// 112 | /// See also: 113 | /// 114 | /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534183-characters?language=objc) 115 | characters: String, 116 | /// The characters generated by a key event as if no modifier key (except for 117 | /// Shift) applies. 118 | /// 119 | /// See also: 120 | /// 121 | /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1524605-charactersignoringmodifiers?language=objc) 122 | characters_ignoring_modifiers: String, 123 | /// The virtual key code for the keyboard key associated with a key event. 124 | /// 125 | /// See also: 126 | /// 127 | /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534513-keycode?language=objc) 128 | key_code: u64, 129 | /// A mask of the current modifiers using the values in Modifier Flags. 130 | /// 131 | /// See also: 132 | /// 133 | /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1535211-modifierflags?language=objc) 134 | modifiers: u64, 135 | specified_logical_key: u64, 136 | }, 137 | Ios { 138 | r#type: FlutterKeyboardEventType, 139 | /// The Unicode characters associated with a key-up or key-down event. 140 | /// 141 | /// See also: 142 | /// 143 | /// * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526130-characters?language=objc) 144 | characters: String, 145 | /// The characters generated by a key event as if no modifier key (except for 146 | /// Shift) applies. 147 | /// 148 | /// See also: 149 | /// 150 | /// * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526131-charactersignoringmodifiers?language=objc) 151 | characters_ignoring_modifiers: String, 152 | /// The virtual key code for the keyboard key associated with a key event. 153 | /// 154 | /// See also: 155 | /// 156 | /// * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526132-keycode?language=objc) 157 | key_code: u64, 158 | /// A mask of the current modifiers using the values in Modifier Flags. 159 | /// 160 | /// See also: 161 | /// 162 | /// * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526133-modifierflags?language=objc) 163 | modifiers: u64, 164 | }, 165 | Linux { 166 | r#type: FlutterKeyboardEventType, 167 | /// There is no real concept of a "native" window toolkit on Linux, and each implementation 168 | /// (GLFW, GTK, QT, etc) may have a different key code mapping. 169 | toolkit: LinuxToolkit, 170 | /// An int with up to two Unicode scalar values generated by a single keystroke. An assertion 171 | /// will fire if more than two values are encoded in a single keystroke. 172 | /// 173 | /// This is typically the character that [keyCode] would produce without any modifier keys. 174 | /// For dead keys, it is typically the diacritic it would add to a character. Defaults to 0, 175 | /// asserted to be not null. 176 | unicode_scalar_values: u64, 177 | /// The hardware key code corresponding to this key event. 178 | /// 179 | /// This is the physical key that was pressed, not the Unicode character. 180 | /// This value may be different depending on the window toolkit used. See [KeyHelper]. 181 | key_code: u64, 182 | /// The hardware scan code id corresponding to this key event. 183 | /// 184 | /// These values are not reliable and vary from device to device, so this 185 | /// information is mainly useful for debugging. 186 | scan_code: u64, 187 | /// A mask of the current modifiers using the values in Modifier Flags. 188 | /// This value may be different depending on the window toolkit used. See [KeyHelper]. 189 | modifiers: u64, 190 | /// A logical key specified by the embedding that should be used instead of 191 | /// deriving from raw data. 192 | /// 193 | /// The GTK embedding detects the keyboard layout and maps some keys to 194 | /// logical keys in a way that can not be derived from per-key information. 195 | /// 196 | /// This is not part of the native GTK key event. 197 | specified_logical_key: u64, 198 | }, 199 | Windows { 200 | r#type: FlutterKeyboardEventType, 201 | /// The Unicode code point represented by the key event, if any. 202 | /// 203 | /// If there is no Unicode code point, this value is zero. 204 | character_code_point: u64, 205 | /// The hardware key code corresponding to this key event. 206 | /// 207 | /// This is the physical key that was pressed, not the Unicode character. 208 | /// See [characterCodePoint] for the Unicode character. 209 | key_code: u64, 210 | /// The hardware scan code id corresponding to this key event. 211 | /// 212 | /// These values are not reliable and vary from device to device, so this 213 | /// information is mainly useful for debugging. 214 | scan_code: u64, 215 | /// A mask of the current modifiers. The modifier values must be in sync with 216 | /// the ones defined in https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc 217 | modifiers: u64, 218 | }, 219 | Web { 220 | r#type: FlutterKeyboardEventType, 221 | /// The `KeyboardEvent.code` corresponding to this event. 222 | /// 223 | /// The [code] represents a physical key on the keyboard, a value that isn't 224 | /// altered by keyboard layout or the state of the modifier keys. 225 | /// 226 | /// See 227 | /// for more information. 228 | code: String, 229 | /// The `KeyboardEvent.key` corresponding to this event. 230 | /// 231 | /// The [key] represents the key pressed by the user, taking into 232 | /// consideration the state of modifier keys such as Shift as well as the 233 | /// keyboard locale and layout. 234 | /// 235 | /// See 236 | /// for more information. 237 | key: String, 238 | /// The `KeyboardEvent.location` corresponding to this event. 239 | /// 240 | /// The [location] represents the location of the key on the keyboard or other 241 | /// input device, such as left or right modifier keys, or Numpad keys. 242 | /// 243 | /// See 244 | /// for more information. 245 | location: u64, 246 | /// The modifiers that were present when the key event occurred. 247 | /// 248 | /// See `lib/src/engine/keyboard.dart` in the web engine for the numerical 249 | /// values of the `metaState`. These constants are also replicated as static 250 | /// constants in this class. 251 | meta_state: u64, 252 | /// The `KeyboardEvent.keyCode` corresponding to this event. 253 | /// 254 | /// See 255 | /// for more information. 256 | key_code: u64, 257 | }, 258 | } 259 | -------------------------------------------------------------------------------- /src/keyboard_physical_key_map.rs: -------------------------------------------------------------------------------- 1 | use winit::keyboard::KeyCode; 2 | 3 | pub fn translate_physical_key(scancode: KeyCode) -> Option { 4 | Some(match scancode { 5 | KeyCode::Hyper => 0x00000010, 6 | KeyCode::SuperLeft | KeyCode::SuperRight => 0x00000011, 7 | KeyCode::Fn => 0x00000012, 8 | KeyCode::FnLock => 0x00000013, 9 | KeyCode::Suspend => 0x00000014, 10 | KeyCode::Resume => 0x00000015, 11 | KeyCode::Turbo => 0x00000016, 12 | // KeyCode::Lock => 0x00000017, 13 | // KeyCode::MicrophoneVolumeMute => 0x00000018, 14 | KeyCode::Sleep => 0x00010082, 15 | KeyCode::WakeUp => 0x00010083, 16 | // KeyCode::DisplayToggleIntExt => 0x000100b5, 17 | // KeyCode::GameButton1 => 0x0005ff01, 18 | // KeyCode::GameButton2 => 0x0005ff02, 19 | // KeyCode::GameButton3 => 0x0005ff03, 20 | // KeyCode::GameButton4 => 0x0005ff04, 21 | // KeyCode::GameButton5 => 0x0005ff05, 22 | // KeyCode::GameButton6 => 0x0005ff06, 23 | // KeyCode::GameButton7 => 0x0005ff07, 24 | // KeyCode::GameButton8 => 0x0005ff08, 25 | // KeyCode::GameButton9 => 0x0005ff09, 26 | // KeyCode::GameButton10 => 0x0005ff0a, 27 | // KeyCode::GameButton11 => 0x0005ff0b, 28 | // KeyCode::GameButton12 => 0x0005ff0c, 29 | // KeyCode::GameButton13 => 0x0005ff0d, 30 | // KeyCode::GameButton14 => 0x0005ff0e, 31 | // KeyCode::GameButton15 => 0x0005ff0f, 32 | // KeyCode::GameButton16 => 0x0005ff10, 33 | // KeyCode::GameButtonA => 0x0005ff11, 34 | // KeyCode::GameButtonB => 0x0005ff12, 35 | // KeyCode::GameButtonC => 0x0005ff13, 36 | // KeyCode::GameButtonLeft1 => 0x0005ff14, 37 | // KeyCode::GameButtonLeft2 => 0x0005ff15, 38 | // KeyCode::GameButtonMode => 0x0005ff16, 39 | // KeyCode::GameButtonRight1 => 0x0005ff17, 40 | // KeyCode::GameButtonRight2 => 0x0005ff18, 41 | // KeyCode::GameButtonSelect => 0x0005ff19, 42 | // KeyCode::GameButtonStart => 0x0005ff1a, 43 | // KeyCode::GameButtonThumbLeft => 0x0005ff1b, 44 | // KeyCode::GameButtonThumbRight => 0x0005ff1c, 45 | // KeyCode::GameButtonX => 0x0005ff1d, 46 | // KeyCode::GameButtonY => 0x0005ff1e, 47 | // KeyCode::GameButtonZ => 0x0005ff1f, 48 | // KeyCode::UsbReserved => 0x00070000, 49 | // KeyCode::UsbErrorRollOver => 0x00070001, 50 | // KeyCode::UsbPostFail => 0x00070002, 51 | // KeyCode::UsbErrorUndefined => 0x00070003, 52 | KeyCode::KeyA => 0x00070004, 53 | KeyCode::KeyB => 0x00070005, 54 | KeyCode::KeyC => 0x00070006, 55 | KeyCode::KeyD => 0x00070007, 56 | KeyCode::KeyE => 0x00070008, 57 | KeyCode::KeyF => 0x00070009, 58 | KeyCode::KeyG => 0x0007000a, 59 | KeyCode::KeyH => 0x0007000b, 60 | KeyCode::KeyI => 0x0007000c, 61 | KeyCode::KeyJ => 0x0007000d, 62 | KeyCode::KeyK => 0x0007000e, 63 | KeyCode::KeyL => 0x0007000f, 64 | KeyCode::KeyM => 0x00070010, 65 | KeyCode::KeyN => 0x00070011, 66 | KeyCode::KeyO => 0x00070012, 67 | KeyCode::KeyP => 0x00070013, 68 | KeyCode::KeyQ => 0x00070014, 69 | KeyCode::KeyR => 0x00070015, 70 | KeyCode::KeyS => 0x00070016, 71 | KeyCode::KeyT => 0x00070017, 72 | KeyCode::KeyU => 0x00070018, 73 | KeyCode::KeyV => 0x00070019, 74 | KeyCode::KeyW => 0x0007001a, 75 | KeyCode::KeyX => 0x0007001b, 76 | KeyCode::KeyY => 0x0007001c, 77 | KeyCode::KeyZ => 0x0007001d, 78 | KeyCode::Digit1 => 0x0007001e, 79 | KeyCode::Digit2 => 0x0007001f, 80 | KeyCode::Digit3 => 0x00070020, 81 | KeyCode::Digit4 => 0x00070021, 82 | KeyCode::Digit5 => 0x00070022, 83 | KeyCode::Digit6 => 0x00070023, 84 | KeyCode::Digit7 => 0x00070024, 85 | KeyCode::Digit8 => 0x00070025, 86 | KeyCode::Digit9 => 0x00070026, 87 | KeyCode::Digit0 => 0x00070027, 88 | KeyCode::Enter => 0x00070028, 89 | KeyCode::Escape => 0x00070029, 90 | KeyCode::Backspace => 0x0007002a, 91 | KeyCode::Tab => 0x0007002b, 92 | KeyCode::Space => 0x0007002c, 93 | KeyCode::Minus => 0x0007002d, 94 | KeyCode::Equal => 0x0007002e, 95 | KeyCode::BracketLeft => 0x0007002f, 96 | KeyCode::BracketRight => 0x00070030, 97 | KeyCode::Backslash => 0x00070031, 98 | KeyCode::Semicolon => 0x00070033, 99 | KeyCode::Quote => 0x00070034, 100 | KeyCode::Backquote => 0x00070035, 101 | KeyCode::Comma => 0x00070036, 102 | KeyCode::Period => 0x00070037, 103 | KeyCode::Slash => 0x00070038, 104 | KeyCode::CapsLock => 0x00070039, 105 | KeyCode::F1 => 0x0007003a, 106 | KeyCode::F2 => 0x0007003b, 107 | KeyCode::F3 => 0x0007003c, 108 | KeyCode::F4 => 0x0007003d, 109 | KeyCode::F5 => 0x0007003e, 110 | KeyCode::F6 => 0x0007003f, 111 | KeyCode::F7 => 0x00070040, 112 | KeyCode::F8 => 0x00070041, 113 | KeyCode::F9 => 0x00070042, 114 | KeyCode::F10 => 0x00070043, 115 | KeyCode::F11 => 0x00070044, 116 | KeyCode::F12 => 0x00070045, 117 | KeyCode::PrintScreen => 0x00070046, 118 | KeyCode::ScrollLock => 0x00070047, 119 | KeyCode::Pause => 0x00070048, 120 | KeyCode::Insert => 0x00070049, 121 | KeyCode::Home => 0x0007004a, 122 | KeyCode::PageUp => 0x0007004b, 123 | KeyCode::Delete => 0x0007004c, 124 | KeyCode::End => 0x0007004d, 125 | KeyCode::PageDown => 0x0007004e, 126 | KeyCode::ArrowRight => 0x0007004f, 127 | KeyCode::ArrowLeft => 0x00070050, 128 | KeyCode::ArrowDown => 0x00070051, 129 | KeyCode::ArrowUp => 0x00070052, 130 | KeyCode::NumLock => 0x00070053, 131 | KeyCode::NumpadDivide => 0x00070054, 132 | KeyCode::NumpadMultiply => 0x00070055, 133 | KeyCode::NumpadSubtract => 0x00070056, 134 | KeyCode::NumpadAdd => 0x00070057, 135 | KeyCode::NumpadEnter => 0x00070058, 136 | KeyCode::Numpad1 => 0x00070059, 137 | KeyCode::Numpad2 => 0x0007005a, 138 | KeyCode::Numpad3 => 0x0007005b, 139 | KeyCode::Numpad4 => 0x0007005c, 140 | KeyCode::Numpad5 => 0x0007005d, 141 | KeyCode::Numpad6 => 0x0007005e, 142 | KeyCode::Numpad7 => 0x0007005f, 143 | KeyCode::Numpad8 => 0x00070060, 144 | KeyCode::Numpad9 => 0x00070061, 145 | KeyCode::Numpad0 => 0x00070062, 146 | KeyCode::NumpadDecimal => 0x00070063, 147 | KeyCode::IntlBackslash => 0x00070064, 148 | KeyCode::ContextMenu => 0x00070065, 149 | KeyCode::Power => 0x00070066, 150 | KeyCode::NumpadEqual => 0x00070067, 151 | KeyCode::F13 => 0x00070068, 152 | KeyCode::F14 => 0x00070069, 153 | KeyCode::F15 => 0x0007006a, 154 | KeyCode::F16 => 0x0007006b, 155 | KeyCode::F17 => 0x0007006c, 156 | KeyCode::F18 => 0x0007006d, 157 | KeyCode::F19 => 0x0007006e, 158 | KeyCode::F20 => 0x0007006f, 159 | KeyCode::F21 => 0x00070070, 160 | KeyCode::F22 => 0x00070071, 161 | KeyCode::F23 => 0x00070072, 162 | KeyCode::F24 => 0x00070073, 163 | KeyCode::Open => 0x00070074, 164 | KeyCode::Help => 0x00070075, 165 | KeyCode::Select => 0x00070077, 166 | KeyCode::Again => 0x00070079, 167 | KeyCode::Undo => 0x0007007a, 168 | KeyCode::Cut => 0x0007007b, 169 | KeyCode::Copy => 0x0007007c, 170 | KeyCode::Paste => 0x0007007d, 171 | KeyCode::Find => 0x0007007e, 172 | KeyCode::AudioVolumeMute => 0x0007007f, 173 | KeyCode::AudioVolumeUp => 0x00070080, 174 | KeyCode::AudioVolumeDown => 0x00070081, 175 | KeyCode::NumpadComma => 0x00070085, 176 | KeyCode::IntlRo => 0x00070087, 177 | KeyCode::KanaMode => 0x00070088, 178 | KeyCode::IntlYen => 0x00070089, 179 | KeyCode::Convert => 0x0007008a, 180 | KeyCode::NonConvert => 0x0007008b, 181 | KeyCode::Lang1 => 0x00070090, 182 | KeyCode::Lang2 => 0x00070091, 183 | KeyCode::Lang3 => 0x00070092, 184 | KeyCode::Lang4 => 0x00070093, 185 | KeyCode::Lang5 => 0x00070094, 186 | KeyCode::Abort => 0x0007009b, 187 | KeyCode::Props => 0x000700a3, 188 | KeyCode::NumpadParenLeft => 0x000700b6, 189 | KeyCode::NumpadParenRight => 0x000700b7, 190 | KeyCode::NumpadBackspace => 0x000700bb, 191 | KeyCode::NumpadMemoryStore => 0x000700d0, 192 | KeyCode::NumpadMemoryRecall => 0x000700d1, 193 | KeyCode::NumpadMemoryClear => 0x000700d2, 194 | KeyCode::NumpadMemoryAdd => 0x000700d3, 195 | KeyCode::NumpadMemorySubtract => 0x000700d4, 196 | // KeyCode::NumpadSignChange => 0x000700d7, 197 | KeyCode::NumpadClear => 0x000700d8, 198 | KeyCode::NumpadClearEntry => 0x000700d9, 199 | KeyCode::ControlLeft => 0x000700e0, 200 | KeyCode::ShiftLeft => 0x000700e1, 201 | KeyCode::AltLeft => 0x000700e2, 202 | KeyCode::Meta => 0x000700e3, 203 | KeyCode::ControlRight => 0x000700e4, 204 | KeyCode::ShiftRight => 0x000700e5, 205 | KeyCode::AltRight => 0x000700e6, 206 | // KeyCode::Info => 0x000c0060, 207 | // KeyCode::ClosedCaptionToggle => 0x000c0061, 208 | // KeyCode::BrightnessUp => 0x000c006f, 209 | // KeyCode::BrightnessDown => 0x000c0070, 210 | // KeyCode::BrightnessToggle => 0x000c0072, 211 | // KeyCode::BrightnessMinimum => 0x000c0073, 212 | // KeyCode::BrightnessMaximum => 0x000c0074, 213 | // KeyCode::BrightnessAuto => 0x000c0075, 214 | // KeyCode::KbdIllumUp => 0x000c0079, 215 | // KeyCode::KbdIllumDown => 0x000c007a, 216 | // KeyCode::MediaLast => 0x000c0083, 217 | // KeyCode::LaunchPhone => 0x000c008c, 218 | // KeyCode::ProgramGuide => 0x000c008d, 219 | // KeyCode::Exit => 0x000c0094, 220 | // KeyCode::ChannelUp => 0x000c009c, 221 | // KeyCode::ChannelDown => 0x000c009d, 222 | // KeyCode::MediaPlay => 0x000c00b0, 223 | // KeyCode::MediaPause => 0x000c00b1, 224 | // KeyCode::MediaRecord => 0x000c00b2, 225 | // KeyCode::MediaFastForward => 0x000c00b3, 226 | // KeyCode::MediaRewind => 0x000c00b4, 227 | KeyCode::MediaTrackNext => 0x000c00b5, 228 | KeyCode::MediaTrackPrevious => 0x000c00b6, 229 | KeyCode::MediaStop => 0x000c00b7, 230 | KeyCode::Eject => 0x000c00b8, 231 | KeyCode::MediaPlayPause => 0x000c00cd, 232 | // KeyCode::SpeechInputToggle => 0x000c00cf, 233 | // KeyCode::BassBoost => 0x000c00e5, 234 | KeyCode::MediaSelect => 0x000c0183, 235 | // KeyCode::LaunchWordProcessor => 0x000c0184, 236 | // KeyCode::LaunchSpreadsheet => 0x000c0186, 237 | KeyCode::LaunchMail => 0x000c018a, 238 | // KeyCode::LaunchContacts => 0x000c018d, 239 | // KeyCode::LaunchCalendar => 0x000c018e, 240 | KeyCode::LaunchApp2 => 0x000c0192, 241 | KeyCode::LaunchApp1 => 0x000c0194, 242 | // KeyCode::LaunchInternetBrowser => 0x000c0196, 243 | // KeyCode::LogOff => 0x000c019c, 244 | // KeyCode::LockScreen => 0x000c019e, 245 | // KeyCode::LaunchControlPanel => 0x000c019f, 246 | // KeyCode::SelectTask => 0x000c01a2, 247 | // KeyCode::LaunchDocuments => 0x000c01a7, 248 | // KeyCode::SpellCheck => 0x000c01ab, 249 | // KeyCode::LaunchKeyboardLayout => 0x000c01ae, 250 | // KeyCode::LaunchScreenSaver => 0x000c01b1, 251 | // KeyCode::LaunchAudioBrowser => 0x000c01b7, 252 | // KeyCode::LaunchAssistant => 0x000c01cb, 253 | // KeyCode::NewKey => 0x000c0201, 254 | // KeyCode::Close => 0x000c0203, 255 | // KeyCode::Save => 0x000c0207, 256 | // KeyCode::Print => 0x000c0208, 257 | KeyCode::BrowserSearch => 0x000c0221, 258 | KeyCode::BrowserHome => 0x000c0223, 259 | KeyCode::BrowserBack => 0x000c0224, 260 | KeyCode::BrowserForward => 0x000c0225, 261 | KeyCode::BrowserStop => 0x000c0226, 262 | KeyCode::BrowserRefresh => 0x000c0227, 263 | KeyCode::BrowserFavorites => 0x000c022a, 264 | // KeyCode::ZoomIn => 0x000c022d, 265 | // KeyCode::ZoomOut => 0x000c022e, 266 | // KeyCode::ZoomToggle => 0x000c0232, 267 | // KeyCode::Redo => 0x000c0279, 268 | // KeyCode::MailReply => 0x000c0289, 269 | // KeyCode::MailForward => 0x000c028b, 270 | // KeyCode::MailSend => 0x000c028c, 271 | // KeyCode::KeyboardLayoutSelect => 0x000c029d, 272 | // KeyCode::ShowAllWindows => 0x000c029f, 273 | // KeyCode::NumpadHash => return None, 274 | // KeyCode::NumpadStar => return None, 275 | // KeyCode::Hiragana => return None, 276 | // KeyCode::Katakana => return None, 277 | // KeyCode::F25 => return None, 278 | // KeyCode::F26 => return None, 279 | // KeyCode::F27 => return None, 280 | // KeyCode::F28 => return None, 281 | // KeyCode::F29 => return None, 282 | // KeyCode::F30 => return None, 283 | // KeyCode::F31 => return None, 284 | // KeyCode::F32 => return None, 285 | // KeyCode::F33 => return None, 286 | // KeyCode::F34 => return None, 287 | // KeyCode::F35 => return None, 288 | KeyCode::Unidentified(_) => return None, 289 | _ => return None, 290 | }) 291 | } 292 | 293 | // TODO: all other platforms 294 | -------------------------------------------------------------------------------- /src/flutter_application/platform.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use winit::window::{Fullscreen, UserAttentionType}; 3 | 4 | use crate::flutter_bindings::FlutterEngine; 5 | 6 | use super::FlutterApplication; 7 | 8 | pub(super) struct Platform; 9 | 10 | impl Platform { 11 | pub(super) fn handle_message( 12 | _engine: FlutterEngine, 13 | message: PlatformMessage, 14 | application: &FlutterApplication, 15 | ) -> Option> { 16 | log::debug!("Platform message: {message:?}"); 17 | match message { 18 | PlatformMessage::SystemChromeSetApplicationSwitcherDescription { label, .. } => { 19 | application.window.set_title(&label); 20 | } 21 | PlatformMessage::ClipboardSetData { text } => { 22 | application 23 | .clipboard 24 | .lock() 25 | .unwrap() 26 | .set_text(text) 27 | .expect("Failed setting clipboard"); 28 | } 29 | PlatformMessage::ClipboardGetData(_) => { 30 | let text = application 31 | .clipboard 32 | .lock() 33 | .unwrap() 34 | .get_text() 35 | .expect("Failed reading clipboard"); 36 | return Some( 37 | serde_json::to_vec(&serde_json::json!({ 38 | "text": text, 39 | })) 40 | .unwrap(), 41 | ); 42 | } 43 | PlatformMessage::ClipboardHasStrings(_) => { 44 | let has_strings = application.clipboard.lock().unwrap().get_text().is_ok(); 45 | return Some( 46 | serde_json::to_vec(&serde_json::json!({ 47 | "value": has_strings, 48 | })) 49 | .unwrap(), 50 | ); 51 | } 52 | PlatformMessage::HapticFeedbackVibrate(feedback_type) => match feedback_type { 53 | HapticFeedbackType::LightImpact => {} 54 | HapticFeedbackType::MediumImpact => application 55 | .window 56 | .request_user_attention(Some(UserAttentionType::Informational)), 57 | HapticFeedbackType::HeavyImpact => application 58 | .window 59 | .request_user_attention(Some(UserAttentionType::Critical)), 60 | HapticFeedbackType::SelectionClick => {} 61 | }, 62 | PlatformMessage::SystemSoundPlay(_) => { 63 | application 64 | .window 65 | .request_user_attention(Some(UserAttentionType::Critical)); 66 | } 67 | PlatformMessage::SystemNavigatorPop => { 68 | application 69 | .user_data 70 | .event_loop_proxy 71 | .lock() 72 | .unwrap() 73 | .send_event(Box::new(|_| true)) 74 | .ok() 75 | .unwrap(); 76 | } 77 | PlatformMessage::SystemChromeSetEnabledSystemUIMode(mode) => { 78 | if mode == SystemUiMode::Manual { 79 | application.window.set_fullscreen(None); 80 | } else { 81 | application 82 | .window 83 | .set_fullscreen(Some(Fullscreen::Borderless(None))); 84 | } 85 | } 86 | _ => {} 87 | } 88 | None 89 | } 90 | } 91 | 92 | #[derive(Debug, Serialize, Deserialize, Clone)] 93 | #[serde(tag = "method", content = "args")] 94 | pub(super) enum PlatformMessage { 95 | /// Places the data from the `text` entry of the argument. 96 | #[serde(rename = "Clipboard.setData")] 97 | ClipboardSetData { text: String }, 98 | /// Returns the data that has the format specified in 99 | /// the argument, a [String], from the system clipboard. 100 | #[serde(rename = "Clipboard.getData")] 101 | ClipboardGetData(ClipboardFormat), 102 | /// Should return `{"value":true}` iff the clipboard contains string data, 103 | /// otherwise `{"value":false}`. 104 | #[serde(rename = "Clipboard.hasStrings")] 105 | ClipboardHasStrings(ClipboardFormat), 106 | /// Triggers a system-default haptic response. 107 | #[serde(rename = "HapticFeedback.vibrate")] 108 | HapticFeedbackVibrate(HapticFeedbackType), 109 | /// Triggers a system audio effect. The argument must 110 | /// be a [String] describing the desired effect 111 | #[serde(rename = "SystemSound.play")] 112 | SystemSoundPlay(String), 113 | /// Informs the operating system of the desired orientation of the display. 114 | #[serde(rename = "SystemChrome.setPreferredOrientations")] 115 | SystemChromeSetPreferredOrientations(Vec), 116 | /// Informs the operating system of the desired label and color to be used 117 | /// to describe the application in any system-level application lists (e.g. 118 | /// application switchers). 119 | #[serde(rename = "SystemChrome.setApplicationSwitcherDescription")] 120 | SystemChromeSetApplicationSwitcherDescription { 121 | /// A label and description of the current state of the application. 122 | label: String, 123 | /// The application's primary color. 124 | /// 125 | /// This may influence the color that the operating system uses to represent 126 | /// the application. 127 | /// 128 | /// A 32 bit integer value 129 | /// (the lower eight bits being the blue channel, the next eight bits being 130 | /// the green channel, the next eight bits being the red channel, and the 131 | /// high eight bits being set, as from [Color.value] for an opaque color). 132 | /// The `primaryColor` can also be zero to indicate that the system default 133 | /// should be used. 134 | #[serde(rename = "primaryColor")] 135 | primary_color: u32, 136 | }, 137 | /// Specifies the set of system overlays to have visible when the application 138 | /// is running. 139 | #[serde(rename = "SystemChrome.setEnabledSystemUIOverlays")] 140 | SystemChromeSetEnabledSystemUIOverlays(Vec), 141 | /// Specifies the [SystemUiMode] for the application. 142 | #[serde(rename = "SystemChrome.setEnabledSystemUIMode")] 143 | SystemChromeSetEnabledSystemUIMode(SystemUiMode), 144 | /// Specifies whether system overlays (e.g. the status bar on Android or iOS) 145 | /// should be `light` or `dark`. 146 | #[serde(rename = "SystemChrome.setSystemUIOverlayStyle")] 147 | SystemChromeSetEnabledSystemUIOverlayStyle(SystemUiOverlayStyle), 148 | /// Tells the operating system to close the application, or the closest 149 | /// equivalent. 150 | #[serde(rename = "SystemNavigator.pop")] 151 | SystemNavigatorPop, 152 | /// Undocumented but sent when a listener for the event below is registered 153 | #[serde(rename = "SystemChrome.setSystemUIChangeListener")] 154 | SystemChromeSetSystemUIChangeListener, 155 | /// Outgoing. The user has changed the visibility of 156 | /// the system overlays. This is relevant when using [SystemUiMode]s 157 | /// through [SystemChrome.setEnabledSystemUIMode]. 158 | /// The boolean indicates whether the system overlays are visible 159 | /// (meaning that the application is not in fullscreen). 160 | #[serde(rename = "SystemChrome.systemUIChange")] 161 | SystemChromeSystemUIChange((bool,)), 162 | /// Restores the system overlays to the last settings provided via 163 | /// [SystemChromeSetEnabledSystemUIOverlays]. May be used when the platform force 164 | /// enables/disables UI elements. 165 | /// 166 | /// For example, when the Android keyboard disables hidden status and navigation bars, 167 | /// this can be called to re-disable the bars when the keyboard is closed. 168 | /// 169 | /// On Android, the system UI cannot be changed until 1 second after the previous 170 | /// change. This is to prevent malware from permanently hiding navigation buttons. 171 | #[serde(rename = "SystemChrome.restoreSystemUIOverlays")] 172 | SystemChromeRestoreSystemUIOverlays, 173 | } 174 | 175 | #[derive(Debug, Serialize, Deserialize, Clone)] 176 | pub(super) enum ClipboardFormat { 177 | #[serde(rename = "text/plain")] 178 | TextPlain, 179 | } 180 | 181 | #[derive(Debug, Serialize, Deserialize, Clone)] 182 | pub(super) enum HapticFeedbackType { 183 | #[serde(rename = "HapticFeedbackType.lightImpact")] 184 | LightImpact, 185 | #[serde(rename = "HapticFeedbackType.mediumImpact")] 186 | MediumImpact, 187 | #[serde(rename = "HapticFeedbackType.heavyImpact")] 188 | HeavyImpact, 189 | #[serde(rename = "HapticFeedbackType.selectionClick")] 190 | SelectionClick, 191 | } 192 | 193 | /// Specifies a particular device orientation. 194 | /// 195 | /// To determine which values correspond to which orientations, first position 196 | /// the device in its default orientation (this is the orientation that the 197 | /// system first uses for its boot logo, or the orientation in which the 198 | /// hardware logos or markings are upright, or the orientation in which the 199 | /// cameras are at the top). If this is a portrait orientation, then this is 200 | /// [portraitUp]. Otherwise, it's [landscapeLeft]. As you rotate the device by 201 | /// 90 degrees in a counter-clockwise direction around the axis that pierces the 202 | /// screen, you step through each value in this enum in the order given. 203 | /// 204 | /// For a device with a landscape default orientation, the orientation obtained 205 | /// by rotating the device 90 degrees clockwise from its default orientation is 206 | /// [portraitUp]. 207 | /// 208 | /// Used by [SystemChrome.setPreferredOrientations]. 209 | #[derive(Debug, Serialize, Deserialize, Clone)] 210 | #[serde(rename_all = "camelCase")] 211 | pub(super) enum DeviceOrientation { 212 | /// If the device shows its boot logo in portrait, then the boot logo is shown 213 | /// in [portraitUp]. Otherwise, the device shows its boot logo in landscape 214 | /// and this orientation is obtained by rotating the device 90 degrees 215 | /// clockwise from its boot orientation. 216 | PortraitUp, 217 | /// The orientation that is 90 degrees clockwise from [PortraitUp]. 218 | /// 219 | /// If the device shows its boot logo in landscape, then the boot logo is 220 | /// shown in [LandscapeLeft]. 221 | LandscapeLeft, 222 | /// The orientation that is 180 degrees from [PortraitUp]. 223 | PortraitDown, 224 | /// The orientation that is 90 degrees counterclockwise from [PortraitUp]. 225 | LandscapeRight, 226 | } 227 | 228 | /// Specifies a system overlay at a particular location. 229 | /// 230 | /// Used by [SystemChrome.setEnabledSystemUIOverlays]. 231 | #[derive(Debug, Serialize, Deserialize, Clone)] 232 | #[serde(rename_all = "camelCase")] 233 | pub(super) enum SystemUiOverlay { 234 | /// The status bar provided by the embedder on the top of the application 235 | /// surface, if any. 236 | Top, 237 | /// The status bar provided by the embedder on the bottom of the application 238 | /// surface, if any. 239 | Bottom, 240 | } 241 | 242 | /// Describes different display configurations for both Android and iOS. 243 | /// 244 | /// These modes mimic Android-specific display setups. 245 | /// 246 | /// Used by [SystemChrome.setEnabledSystemUIMode]. 247 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] 248 | #[serde(rename_all = "camelCase")] 249 | pub(super) enum SystemUiMode { 250 | /// Fullscreen display with status and navigation bars presentable by tapping 251 | /// anywhere on the display. 252 | /// 253 | /// Available starting at SDK 16 or Android J. Earlier versions of Android 254 | /// will not be affected by this setting. 255 | /// 256 | /// For applications running on iOS, the status bar and home indicator will be 257 | /// hidden for a similar fullscreen experience. 258 | /// 259 | /// Tapping on the screen displays overlays, this gesture is not received by 260 | /// the application. 261 | /// 262 | /// See also: 263 | /// 264 | /// * [SystemUiChangeCallback], used to listen and respond to the change in 265 | /// system overlays. 266 | LeanBack, 267 | 268 | /// Fullscreen display with status and navigation bars presentable through a 269 | /// swipe gesture at the edges of the display. 270 | /// 271 | /// Available starting at SDK 19 or Android K. Earlier versions of Android 272 | /// will not be affected by this setting. 273 | /// 274 | /// For applications running on iOS, the status bar and home indicator will be 275 | /// hidden for a similar fullscreen experience. 276 | /// 277 | /// A swipe gesture from the edge of the screen displays overlays. In contrast 278 | /// to [SystemUiMode.immersiveSticky], this gesture is not received by the 279 | /// application. 280 | /// 281 | /// See also: 282 | /// 283 | /// * [SystemUiChangeCallback], used to listen and respond to the change in 284 | /// system overlays. 285 | Immersive, 286 | 287 | /// Fullscreen display with status and navigation bars presentable through a 288 | /// swipe gesture at the edges of the display. 289 | /// 290 | /// Available starting at SDK 19 or Android K. Earlier versions of Android 291 | /// will not be affected by this setting. 292 | /// 293 | /// For applications running on iOS, the status bar and home indicator will be 294 | /// hidden for a similar fullscreen experience. 295 | /// 296 | /// A swipe gesture from the edge of the screen displays overlays. In contrast 297 | /// to [SystemUiMode.immersive], this gesture is received by the application. 298 | /// 299 | /// See also: 300 | /// 301 | /// * [SystemUiChangeCallback], used to listen and respond to the change in 302 | /// system overlays. 303 | ImmersiveSticky, 304 | 305 | /// Fullscreen display with status and navigation elements rendered over the 306 | /// application. 307 | /// 308 | /// Available starting at SDK 29 or Android 10. Earlier versions of Android 309 | /// will not be affected by this setting. 310 | /// 311 | /// For applications running on iOS, the status bar and home indicator will be 312 | /// visible. 313 | /// 314 | /// The system overlays will not disappear or reappear in this mode as they 315 | /// are permanently displayed on top of the application. 316 | /// 317 | /// See also: 318 | /// 319 | /// * [SystemUiOverlayStyle], can be used to configure transparent status and 320 | /// navigation bars with or without a contrast scrim. 321 | EdgeToEdge, 322 | 323 | /// Declares manually configured [SystemUiOverlay]s. 324 | /// 325 | /// When using this mode with [SystemChrome.setEnabledSystemUIMode], the 326 | /// preferred overlays must be set by the developer. 327 | /// 328 | /// When [SystemUiOverlay.top] is enabled, the status bar will remain visible 329 | /// on all platforms. Omitting this overlay will hide the status bar on iOS & 330 | /// Android. 331 | /// 332 | /// When [SystemUiOverlay.bottom] is enabled, the navigation bar and home 333 | /// indicator of Android and iOS applications will remain visible. Omitting this 334 | /// overlay will hide them. 335 | /// 336 | /// Omitting both overlays will result in the same configuration as 337 | /// [SystemUiMode.leanBack]. 338 | Manual, 339 | } 340 | 341 | /// Specifies a preference for the style of the system overlays. 342 | #[derive(Debug, Serialize, Deserialize, Clone)] 343 | #[serde(rename_all = "camelCase")] 344 | pub(super) enum SystemUiOverlayStyle { 345 | /// System overlays should be drawn with a light color. Intended for 346 | /// applications with a dark background. 347 | Light, 348 | /// System overlays should be drawn with a dark color. Intended for 349 | /// applications with a light background. 350 | Dark, 351 | } 352 | -------------------------------------------------------------------------------- /src/keyboard_logical_key_map.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use winit::keyboard::Key; 4 | 5 | pub fn translate_logical_key(key: Key<'static>) -> Option { 6 | Some(match key { 7 | Key::Character(c) => { 8 | let mut buffer = [0u8; 8]; 9 | c.as_bytes().read(&mut buffer).ok()?; 10 | u64::from_le_bytes(buffer) 11 | } 12 | Key::Space => 0x00000000020, 13 | Key::Backspace => 0x00100000008, 14 | Key::Tab => 0x00100000009, 15 | Key::Enter => 0x0010000000d, 16 | Key::Escape => 0x0010000001b, 17 | Key::Delete => 0x0010000007f, 18 | // Key::Accel => 0x00100000101, 19 | Key::AltGraph => 0x00100000103, 20 | Key::CapsLock => 0x00100000104, 21 | Key::Fn => 0x00100000106, 22 | Key::FnLock => 0x00100000107, 23 | Key::Hyper => 0x00100000108, 24 | Key::NumLock => 0x0010000010a, 25 | Key::ScrollLock => 0x0010000010c, 26 | Key::Super => 0x0010000010e, 27 | Key::Symbol => 0x0010000010f, 28 | Key::SymbolLock => 0x00100000110, 29 | // Key::ShiftLevel5 => 0x00100000111, 30 | Key::ArrowDown => 0x00100000301, 31 | Key::ArrowLeft => 0x00100000302, 32 | Key::ArrowRight => 0x00100000303, 33 | Key::ArrowUp => 0x00100000304, 34 | Key::End => 0x00100000305, 35 | Key::Home => 0x00100000306, 36 | Key::PageDown => 0x00100000307, 37 | Key::PageUp => 0x00100000308, 38 | Key::Clear => 0x00100000401, 39 | Key::Copy => 0x00100000402, 40 | Key::CrSel => 0x00100000403, 41 | Key::Cut => 0x00100000404, 42 | Key::EraseEof => 0x00100000405, 43 | Key::ExSel => 0x00100000406, 44 | Key::Insert => 0x00100000407, 45 | Key::Paste => 0x00100000408, 46 | Key::Redo => 0x00100000409, 47 | Key::Undo => 0x0010000040a, 48 | Key::Accept => 0x00100000501, 49 | Key::Again => 0x00100000502, 50 | Key::Attn => 0x00100000503, 51 | Key::Cancel => 0x00100000504, 52 | Key::ContextMenu => 0x00100000505, 53 | Key::Execute => 0x00100000506, 54 | Key::Find => 0x00100000507, 55 | Key::Help => 0x00100000508, 56 | Key::Pause => 0x00100000509, 57 | Key::Play => 0x0010000050a, 58 | Key::Props => 0x0010000050b, 59 | Key::Select => 0x0010000050c, 60 | Key::ZoomIn => 0x0010000050d, 61 | Key::ZoomOut => 0x0010000050e, 62 | Key::BrightnessDown => 0x00100000601, 63 | Key::BrightnessUp => 0x00100000602, 64 | Key::Camera => 0x00100000603, 65 | Key::Eject => 0x00100000604, 66 | Key::LogOff => 0x00100000605, 67 | Key::Power => 0x00100000606, 68 | Key::PowerOff => 0x00100000607, 69 | Key::PrintScreen => 0x00100000608, 70 | Key::Hibernate => 0x00100000609, 71 | Key::Standby => 0x0010000060a, 72 | Key::WakeUp => 0x0010000060b, 73 | Key::AllCandidates => 0x00100000701, 74 | Key::Alphanumeric => 0x00100000702, 75 | Key::CodeInput => 0x00100000703, 76 | Key::Compose => 0x00100000704, 77 | Key::Convert => 0x00100000705, 78 | Key::FinalMode => 0x00100000706, 79 | Key::GroupFirst => 0x00100000707, 80 | Key::GroupLast => 0x00100000708, 81 | Key::GroupNext => 0x00100000709, 82 | Key::GroupPrevious => 0x0010000070a, 83 | Key::ModeChange => 0x0010000070b, 84 | Key::NextCandidate => 0x0010000070c, 85 | Key::NonConvert => 0x0010000070d, 86 | Key::PreviousCandidate => 0x0010000070e, 87 | Key::Process => 0x0010000070f, 88 | Key::SingleCandidate => 0x00100000710, 89 | Key::HangulMode => 0x00100000711, 90 | Key::HanjaMode => 0x00100000712, 91 | Key::JunjaMode => 0x00100000713, 92 | Key::Eisu => 0x00100000714, 93 | Key::Hankaku => 0x00100000715, 94 | Key::Hiragana => 0x00100000716, 95 | Key::HiraganaKatakana => 0x00100000717, 96 | Key::KanaMode => 0x00100000718, 97 | Key::KanjiMode => 0x00100000719, 98 | Key::Katakana => 0x0010000071a, 99 | Key::Romaji => 0x0010000071b, 100 | Key::Zenkaku => 0x0010000071c, 101 | Key::ZenkakuHankaku => 0x0010000071d, 102 | Key::F1 => 0x00100000801, 103 | Key::F2 => 0x00100000802, 104 | Key::F3 => 0x00100000803, 105 | Key::F4 => 0x00100000804, 106 | Key::F5 => 0x00100000805, 107 | Key::F6 => 0x00100000806, 108 | Key::F7 => 0x00100000807, 109 | Key::F8 => 0x00100000808, 110 | Key::F9 => 0x00100000809, 111 | Key::F10 => 0x0010000080a, 112 | Key::F11 => 0x0010000080b, 113 | Key::F12 => 0x0010000080c, 114 | Key::F13 => 0x0010000080d, 115 | Key::F14 => 0x0010000080e, 116 | Key::F15 => 0x0010000080f, 117 | Key::F16 => 0x00100000810, 118 | Key::F17 => 0x00100000811, 119 | Key::F18 => 0x00100000812, 120 | Key::F19 => 0x00100000813, 121 | Key::F20 => 0x00100000814, 122 | Key::F21 => 0x00100000815, 123 | Key::F22 => 0x00100000816, 124 | Key::F23 => 0x00100000817, 125 | Key::F24 => 0x00100000818, 126 | Key::Soft1 => 0x00100000901, 127 | Key::Soft2 => 0x00100000902, 128 | Key::Soft3 => 0x00100000903, 129 | Key::Soft4 => 0x00100000904, 130 | // Key::Soft5 => 0x00100000905, 131 | // Key::Soft6 => 0x00100000906, 132 | // Key::Soft7 => 0x00100000907, 133 | // Key::Soft8 => 0x00100000908, 134 | Key::Close => 0x00100000a01, 135 | Key::MailForward => 0x00100000a02, 136 | Key::MailReply => 0x00100000a03, 137 | Key::MailSend => 0x00100000a04, 138 | Key::MediaPlayPause => 0x00100000a05, 139 | Key::MediaStop => 0x00100000a07, 140 | Key::MediaTrackNext => 0x00100000a08, 141 | Key::MediaTrackPrevious => 0x00100000a09, 142 | Key::New => 0x00100000a0a, 143 | Key::Open => 0x00100000a0b, 144 | Key::Print => 0x00100000a0c, 145 | Key::Save => 0x00100000a0d, 146 | Key::SpellCheck => 0x00100000a0e, 147 | Key::AudioVolumeDown => 0x00100000a0f, 148 | Key::AudioVolumeUp => 0x00100000a10, 149 | Key::AudioVolumeMute => 0x00100000a11, 150 | Key::LaunchApplication2 => 0x00100000b01, 151 | Key::LaunchCalendar => 0x00100000b02, 152 | Key::LaunchMail => 0x00100000b03, 153 | Key::LaunchMediaPlayer => 0x00100000b04, 154 | Key::LaunchMusicPlayer => 0x00100000b05, 155 | Key::LaunchApplication1 => 0x00100000b06, 156 | Key::LaunchScreenSaver => 0x00100000b07, 157 | Key::LaunchSpreadsheet => 0x00100000b08, 158 | Key::LaunchWebBrowser => 0x00100000b09, 159 | Key::LaunchWebCam => 0x00100000b0a, 160 | Key::LaunchWordProcessor => 0x00100000b0b, 161 | Key::LaunchContacts => 0x00100000b0c, 162 | Key::LaunchPhone => 0x00100000b0d, 163 | // Key::LaunchAssistant => 0x00100000b0e, 164 | // Key::LaunchControlPanel => 0x00100000b0f, 165 | Key::BrowserBack => 0x00100000c01, 166 | Key::BrowserFavorites => 0x00100000c02, 167 | Key::BrowserForward => 0x00100000c03, 168 | Key::BrowserHome => 0x00100000c04, 169 | Key::BrowserRefresh => 0x00100000c05, 170 | Key::BrowserSearch => 0x00100000c06, 171 | Key::BrowserStop => 0x00100000c07, 172 | Key::AudioBalanceLeft => 0x00100000d01, 173 | Key::AudioBalanceRight => 0x00100000d02, 174 | Key::AudioBassBoostDown => 0x00100000d03, 175 | Key::AudioBassBoostUp => 0x00100000d04, 176 | Key::AudioFaderFront => 0x00100000d05, 177 | Key::AudioFaderRear => 0x00100000d06, 178 | Key::AudioSurroundModeNext => 0x00100000d07, 179 | Key::AVRInput => 0x00100000d08, 180 | Key::AVRPower => 0x00100000d09, 181 | Key::ChannelDown => 0x00100000d0a, 182 | Key::ChannelUp => 0x00100000d0b, 183 | Key::ColorF0Red => 0x00100000d0c, 184 | Key::ColorF1Green => 0x00100000d0d, 185 | Key::ColorF2Yellow => 0x00100000d0e, 186 | Key::ColorF3Blue => 0x00100000d0f, 187 | Key::ColorF4Grey => 0x00100000d10, 188 | Key::ColorF5Brown => 0x00100000d11, 189 | Key::ClosedCaptionToggle => 0x00100000d12, 190 | Key::Dimmer => 0x00100000d13, 191 | Key::DisplaySwap => 0x00100000d14, 192 | Key::Exit => 0x00100000d15, 193 | Key::FavoriteClear0 => 0x00100000d16, 194 | Key::FavoriteClear1 => 0x00100000d17, 195 | Key::FavoriteClear2 => 0x00100000d18, 196 | Key::FavoriteClear3 => 0x00100000d19, 197 | Key::FavoriteRecall0 => 0x00100000d1a, 198 | Key::FavoriteRecall1 => 0x00100000d1b, 199 | Key::FavoriteRecall2 => 0x00100000d1c, 200 | Key::FavoriteRecall3 => 0x00100000d1d, 201 | Key::FavoriteStore0 => 0x00100000d1e, 202 | Key::FavoriteStore1 => 0x00100000d1f, 203 | Key::FavoriteStore2 => 0x00100000d20, 204 | Key::FavoriteStore3 => 0x00100000d21, 205 | Key::Guide => 0x00100000d22, 206 | Key::GuideNextDay => 0x00100000d23, 207 | Key::GuidePreviousDay => 0x00100000d24, 208 | Key::Info => 0x00100000d25, 209 | Key::InstantReplay => 0x00100000d26, 210 | Key::Link => 0x00100000d27, 211 | Key::ListProgram => 0x00100000d28, 212 | Key::LiveContent => 0x00100000d29, 213 | Key::Lock => 0x00100000d2a, 214 | Key::MediaApps => 0x00100000d2b, 215 | Key::MediaFastForward => 0x00100000d2c, 216 | Key::MediaLast => 0x00100000d2d, 217 | Key::MediaPause => 0x00100000d2e, 218 | Key::MediaPlay => 0x00100000d2f, 219 | Key::MediaRecord => 0x00100000d30, 220 | Key::MediaRewind => 0x00100000d31, 221 | Key::NextFavoriteChannel => 0x00100000d33, 222 | Key::NextUserProfile => 0x00100000d34, 223 | Key::OnDemand => 0x00100000d35, 224 | Key::PinPDown => 0x00100000d36, 225 | Key::PinPMove => 0x00100000d37, 226 | Key::PinPToggle => 0x00100000d38, 227 | Key::PinPUp => 0x00100000d39, 228 | Key::PlaySpeedDown => 0x00100000d3a, 229 | Key::PlaySpeedReset => 0x00100000d3b, 230 | Key::PlaySpeedUp => 0x00100000d3c, 231 | Key::RandomToggle => 0x00100000d3d, 232 | Key::RcLowBattery => 0x00100000d3e, 233 | Key::RecordSpeedNext => 0x00100000d3f, 234 | Key::RfBypass => 0x00100000d40, 235 | Key::ScanChannelsToggle => 0x00100000d41, 236 | Key::ScreenModeNext => 0x00100000d42, 237 | Key::Settings => 0x00100000d43, 238 | Key::SplitScreenToggle => 0x00100000d44, 239 | Key::STBInput => 0x00100000d45, 240 | Key::STBPower => 0x00100000d46, 241 | Key::Subtitle => 0x00100000d47, 242 | Key::Teletext => 0x00100000d48, 243 | Key::TV => 0x00100000d49, 244 | Key::TVInput => 0x00100000d4a, 245 | Key::TVPower => 0x00100000d4b, 246 | Key::VideoModeNext => 0x00100000d4c, 247 | Key::Wink => 0x00100000d4d, 248 | Key::ZoomToggle => 0x00100000d4e, 249 | Key::DVR => 0x00100000d4f, 250 | Key::MediaAudioTrack => 0x00100000d50, 251 | Key::MediaSkipBackward => 0x00100000d51, 252 | Key::MediaSkipForward => 0x00100000d52, 253 | Key::MediaStepBackward => 0x00100000d53, 254 | Key::MediaStepForward => 0x00100000d54, 255 | Key::MediaTopMenu => 0x00100000d55, 256 | Key::NavigateIn => 0x00100000d56, 257 | Key::NavigateNext => 0x00100000d57, 258 | Key::NavigateOut => 0x00100000d58, 259 | Key::NavigatePrevious => 0x00100000d59, 260 | Key::Pairing => 0x00100000d5a, 261 | Key::MediaClose => 0x00100000d5b, 262 | Key::AudioBassBoostToggle => 0x00100000e02, 263 | Key::AudioTrebleDown => 0x00100000e04, 264 | Key::AudioTrebleUp => 0x00100000e05, 265 | Key::MicrophoneToggle => 0x00100000e06, 266 | Key::MicrophoneVolumeDown => 0x00100000e07, 267 | Key::MicrophoneVolumeUp => 0x00100000e08, 268 | Key::MicrophoneVolumeMute => 0x00100000e09, 269 | Key::SpeechCorrectionList => 0x00100000f01, 270 | Key::SpeechInputToggle => 0x00100000f02, 271 | Key::AppSwitch => 0x00100001001, 272 | Key::Call => 0x00100001002, 273 | Key::CameraFocus => 0x00100001003, 274 | Key::EndCall => 0x00100001004, 275 | Key::GoBack => 0x00100001005, 276 | Key::GoHome => 0x00100001006, 277 | Key::HeadsetHook => 0x00100001007, 278 | Key::LastNumberRedial => 0x00100001008, 279 | Key::Notification => 0x00100001009, 280 | Key::MannerMode => 0x0010000100a, 281 | Key::VoiceDial => 0x0010000100b, 282 | Key::TV3DMode => 0x00100001101, 283 | Key::TVAntennaCable => 0x00100001102, 284 | Key::TVAudioDescription => 0x00100001103, 285 | Key::TVAudioDescriptionMixDown => 0x00100001104, 286 | Key::TVAudioDescriptionMixUp => 0x00100001105, 287 | Key::TVContentsMenu => 0x00100001106, 288 | Key::TVDataService => 0x00100001107, 289 | Key::TVInputComponent1 => 0x00100001108, 290 | Key::TVInputComponent2 => 0x00100001109, 291 | Key::TVInputComposite1 => 0x0010000110a, 292 | Key::TVInputComposite2 => 0x0010000110b, 293 | Key::TVInputHDMI1 => 0x0010000110c, 294 | Key::TVInputHDMI2 => 0x0010000110d, 295 | Key::TVInputHDMI3 => 0x0010000110e, 296 | Key::TVInputHDMI4 => 0x0010000110f, 297 | Key::TVInputVGA1 => 0x00100001110, 298 | Key::TVMediaContext => 0x00100001111, 299 | Key::TVNetwork => 0x00100001112, 300 | Key::TVNumberEntry => 0x00100001113, 301 | Key::TVRadioService => 0x00100001114, 302 | Key::TVSatellite => 0x00100001115, 303 | Key::TVSatelliteBS => 0x00100001116, 304 | Key::TVSatelliteCS => 0x00100001117, 305 | Key::TVSatelliteToggle => 0x00100001118, 306 | Key::TVTerrestrialAnalog => 0x00100001119, 307 | Key::TVTerrestrialDigital => 0x0010000111a, 308 | Key::TVTimer => 0x0010000111b, 309 | Key::Key11 => 0x00100001201, 310 | Key::Key12 => 0x00100001202, 311 | // Key::Standby => 0x00200000000, 312 | // Key::WakeUp => 0x00200000001, 313 | // Key::Sleep => 0x00200000002, 314 | // Key::Abort => 0x00200000003, 315 | // Key::Lang1 => 0x00200000010, 316 | // Key::Lang2 => 0x00200000011, 317 | // Key::Lang3 => 0x00200000012, 318 | // Key::Lang4 => 0x00200000013, 319 | // Key::Lang5 => 0x00200000014, 320 | Key::Control => 0x00200000100, 321 | Key::Shift => 0x00200000102, 322 | Key::Alt => 0x00200000104, 323 | Key::Meta => 0x00200000106, 324 | // Key::NumpadEnter => 0x0020000020d, 325 | // Key::NumpadParenLeft => 0x00200000228, 326 | // Key::NumpadParenRight => 0x00200000229, 327 | // Key::NumpadMultiply => 0x0020000022a, 328 | // Key::NumpadAdd => 0x0020000022b, 329 | // Key::NumpadComma => 0x0020000022c, 330 | // Key::NumpadSubtract => 0x0020000022d, 331 | // Key::NumpadDecimal => 0x0020000022e, 332 | // Key::NumpadDivide => 0x0020000022f, 333 | // Key::Numpad0 => 0x00200000230, 334 | // Key::Numpad1 => 0x00200000231, 335 | // Key::Numpad2 => 0x00200000232, 336 | // Key::Numpad3 => 0x00200000233, 337 | // Key::Numpad4 => 0x00200000234, 338 | // Key::Numpad5 => 0x00200000235, 339 | // Key::Numpad6 => 0x00200000236, 340 | // Key::Numpad7 => 0x00200000237, 341 | // Key::Numpad8 => 0x00200000238, 342 | // Key::Numpad9 => 0x00200000239, 343 | // Key::NumpadEqual => 0x0020000023d, 344 | // Key::GameButton1 => 0x00200000301, 345 | // Key::GameButton2 => 0x00200000302, 346 | // Key::GameButton3 => 0x00200000303, 347 | // Key::GameButton4 => 0x00200000304, 348 | // Key::GameButton5 => 0x00200000305, 349 | // Key::GameButton6 => 0x00200000306, 350 | // Key::GameButton7 => 0x00200000307, 351 | // Key::GameButton8 => 0x00200000308, 352 | // Key::GameButton9 => 0x00200000309, 353 | // Key::GameButton10 => 0x0020000030a, 354 | // Key::GameButton11 => 0x0020000030b, 355 | // Key::GameButton12 => 0x0020000030c, 356 | // Key::GameButton13 => 0x0020000030d, 357 | // Key::GameButton14 => 0x0020000030e, 358 | // Key::GameButton15 => 0x0020000030f, 359 | // Key::GameButton16 => 0x00200000310, 360 | // Key::GameButtonA => 0x00200000311, 361 | // Key::GameButtonB => 0x00200000312, 362 | // Key::GameButtonC => 0x00200000313, 363 | // Key::GameButtonLeft1 => 0x00200000314, 364 | // Key::GameButtonLeft2 => 0x00200000315, 365 | // Key::GameButtonMode => 0x00200000316, 366 | // Key::GameButtonRight1 => 0x00200000317, 367 | // Key::GameButtonRight2 => 0x00200000318, 368 | // Key::GameButtonSelect => 0x00200000319, 369 | // Key::GameButtonStart => 0x0020000031a, 370 | // Key::GameButtonThumbLeft => 0x0020000031b, 371 | // Key::GameButtonThumbRight => 0x0020000031c, 372 | // Key::GameButtonX => 0x0020000031d, 373 | // Key::GameButtonY => 0x0020000031e, 374 | // Key::GameButtonZ => 0x0020000031f, 375 | // Key::F25 => return None, 376 | // Key::F26 => return None, 377 | // Key::F27 => return None, 378 | // Key::F28 => return None, 379 | // Key::F29 => return None, 380 | // Key::F30 => return None, 381 | // Key::F31 => return None, 382 | // Key::F32 => return None, 383 | // Key::F33 => return None, 384 | // Key::F34 => return None, 385 | // Key::F35 => return None, 386 | // Key::Dead(_) => return None, 387 | Key::Unidentified(_) => 0x00100000001, 388 | _ => return None, 389 | }) 390 | } 391 | -------------------------------------------------------------------------------- /src/flutter_application/text_input.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | #[serde(tag = "name")] 5 | pub(super) enum TextInputType { 6 | #[serde(rename = "TextInputType.text")] 7 | Text, 8 | #[serde(rename = "TextInputType.multiline")] 9 | Multiline, 10 | #[serde(rename = "TextInputType.number")] 11 | Number { signed: bool, decimal: bool }, 12 | #[serde(rename = "TextInputType.phone")] 13 | Phone, 14 | #[serde(rename = "TextInputType.datetime")] 15 | Datetime, 16 | #[serde(rename = "TextInputType.emailAddress")] 17 | EmailAddress, 18 | #[serde(rename = "TextInputType.url")] 19 | Url, 20 | #[serde(rename = "TextInputType.visiblePassword")] 21 | VisiblePassword, 22 | #[serde(rename = "TextInputType.name")] 23 | Name, 24 | #[serde(rename = "TextInputType.address")] 25 | StreetAddress, 26 | #[serde(rename = "TextInputType.none")] 27 | None, 28 | } 29 | 30 | #[derive(Debug, Serialize, Deserialize, Clone)] 31 | pub(super) enum BinaryType { 32 | #[serde(rename = "0")] 33 | Disabled, 34 | #[serde(rename = "1")] 35 | Enabled, 36 | } 37 | 38 | #[derive(Debug, Serialize, Deserialize, Clone)] 39 | pub(super) enum TextCapitalization { 40 | #[serde(rename = "TextCapitalization.words")] 41 | Words, 42 | #[serde(rename = "TextCapitalization.sentences")] 43 | Sentences, 44 | #[serde(rename = "TextCapitalization.characters")] 45 | Characters, 46 | #[serde(rename = "TextCapitalization.none")] 47 | None, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize, Clone)] 51 | pub(super) enum Brightness { 52 | #[serde(rename = "Brightness.dark")] 53 | Dark, 54 | #[serde(rename = "Brightness.light")] 55 | Light, 56 | } 57 | 58 | #[derive(Debug, Serialize, Deserialize, Clone)] 59 | #[serde(rename_all = "camelCase")] 60 | pub(super) struct TextClientParameters { 61 | pub(super) input_type: TextInputType, 62 | pub(super) read_only: bool, 63 | pub(super) obscure_text: bool, 64 | pub(super) autocorrect: bool, 65 | pub(super) smart_dashes_type: BinaryType, 66 | pub(super) smart_quotes_type: BinaryType, 67 | pub(super) enable_suggestions: bool, 68 | pub(super) enable_interactive_selection: bool, 69 | pub(super) action_label: Option, 70 | pub(super) input_action: TextInputAction, 71 | pub(super) text_capitalization: TextCapitalization, 72 | pub(super) keyboard_appearance: Brightness, 73 | #[serde(rename = "enableIMEPersonalizedLearning")] 74 | pub(super) enable_ime_personalized_learning: bool, 75 | pub(super) enable_delta_model: bool, 76 | } 77 | 78 | #[derive(Debug, Serialize, Deserialize, Clone)] 79 | #[serde(tag = "method", content = "args")] 80 | pub(super) enum TextInput { 81 | /// Establishes a new transaction. The arguments is 82 | /// a [List] whose first value is an integer representing a previously 83 | /// unused transaction identifier, and the second is a [String] with a 84 | /// JSON-encoded object with five keys, as obtained from 85 | /// [TextInputConfiguration.toJson]. This method must be invoked before any 86 | /// others (except `TextInput.hide`). See [TextInput.attach]. 87 | #[serde(rename = "TextInput.setClient")] 88 | SetClient(u64, TextClientParameters), 89 | /// Show the keyboard. See [TextInputConnection.show]. 90 | #[serde(rename = "TextInput.show")] 91 | Show, 92 | /// Update the value in the text editing 93 | /// control. The argument is a [String] with a JSON-encoded object with 94 | /// seven keys, as obtained from [TextEditingValue.toJSON]. See 95 | /// [TextInputConnection.setEditingState]. 96 | #[serde(rename = "TextInput.setEditingState")] 97 | SetEditingState(TextEditingValue), 98 | /// End the current transaction. The next method 99 | /// called must be `TextInput.setClient` (or `TextInput.hide`). See 100 | /// [TextInputConnection.close]. 101 | #[serde(rename = "TextInput.clearClient")] 102 | ClearClient, 103 | /// Hide the keyboard. Unlike the other methods, this can 104 | /// be called at any time. See [TextInputConnection.close]. 105 | #[serde(rename = "TextInput.hide")] 106 | Hide, 107 | } 108 | 109 | #[derive(Debug, Serialize, Deserialize, Clone)] 110 | pub(super) enum TextAffinity { 111 | #[serde(rename = "TextAffinity.downstream")] 112 | Downstream, 113 | #[serde(rename = "TextAffinity.upstream")] 114 | Upstream, 115 | } 116 | 117 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 118 | #[serde(rename_all = "camelCase")] 119 | pub(super) struct TextEditingValue { 120 | pub(super) text: String, 121 | pub(super) selection_base: Option, 122 | pub(super) selection_extent: Option, 123 | pub(super) selection_affinity: Option, 124 | pub(super) selection_is_directional: Option, 125 | pub(super) composing_base: Option, 126 | pub(super) composing_extent: Option, 127 | } 128 | 129 | /// An action the user has requested the text input control to perform. 130 | /// 131 | /// Each action represents a logical meaning, and also configures the soft 132 | /// keyboard to display a certain kind of action button. The visual appearance 133 | /// of the action button might differ between versions of the same OS. 134 | /// 135 | /// Despite the logical meaning of each action, choosing a particular 136 | /// [TextInputAction] does not necessarily cause any specific behavior to 137 | /// happen, other than changing the focus when appropriate. It is up to the 138 | /// developer to ensure that the behavior that occurs when an action button is 139 | /// pressed is appropriate for the action button chosen. 140 | /// 141 | /// For example: If the user presses the keyboard action button on iOS when it 142 | /// reads "Emergency Call", the result should not be a focus change to the next 143 | /// TextField. This behavior is not logically appropriate for a button that says 144 | /// "Emergency Call". 145 | /// 146 | /// See [EditableText] for more information about customizing action button 147 | /// behavior. 148 | /// 149 | /// Most [TextInputAction]s are supported equally by both Android and iOS. 150 | /// However, there is not a complete, direct mapping between Android's IME input 151 | /// types and iOS's keyboard return types. Therefore, some [TextInputAction]s 152 | /// are inappropriate for one of the platforms. If a developer chooses an 153 | /// inappropriate [TextInputAction] when running in debug mode, an error will be 154 | /// thrown. If the same thing is done in release mode, then instead of sending 155 | /// the inappropriate value, Android will use "unspecified" on the platform 156 | /// side and iOS will use "default" on the platform side. 157 | /// 158 | /// See also: 159 | /// 160 | /// * [TextInput], which configures the platform's keyboard setup. 161 | /// * [EditableText], which invokes callbacks when the action button is pressed. 162 | // 163 | // This class has been cloned to `flutter_driver/lib/src/common/action.dart` as `TextInputAction`, 164 | // and must be kept in sync. 165 | #[derive(Debug, Serialize, Deserialize, Clone, Copy)] 166 | pub(super) enum TextInputAction { 167 | /// Logical meaning: There is no relevant input action for the current input 168 | /// source, e.g., [TextField]. 169 | /// 170 | /// Android: Corresponds to Android's "IME_ACTION_NONE". The keyboard setup 171 | /// is decided by the OS. The keyboard will likely show a return key. 172 | /// 173 | /// iOS: iOS does not have a keyboard return type of "none." It is 174 | /// inappropriate to choose this [TextInputAction] when running on iOS. 175 | #[serde(rename = "TextInputAction.none")] 176 | None, 177 | 178 | /// Logical meaning: Let the OS decide which action is most appropriate. 179 | /// 180 | /// Android: Corresponds to Android's "IME_ACTION_UNSPECIFIED". The OS chooses 181 | /// which keyboard action to display. The decision will likely be a done 182 | /// button or a return key. 183 | /// 184 | /// iOS: Corresponds to iOS's "UIReturnKeyDefault". The title displayed in 185 | /// the action button is "return". 186 | #[serde(rename = "TextInputAction.unspecified")] 187 | Unspecified, 188 | 189 | /// Logical meaning: The user is done providing input to a group of inputs 190 | /// (like a form). Some kind of finalization behavior should now take place. 191 | /// 192 | /// Android: Corresponds to Android's "IME_ACTION_DONE". The OS displays a 193 | /// button that represents completion, e.g., a checkmark button. 194 | /// 195 | /// iOS: Corresponds to iOS's "UIReturnKeyDone". The title displayed in the 196 | /// action button is "Done". 197 | #[serde(rename = "TextInputAction.done")] 198 | Done, 199 | 200 | /// Logical meaning: The user has entered some text that represents a 201 | /// destination, e.g., a restaurant name. The "go" button is intended to take 202 | /// the user to a part of the app that corresponds to this destination. 203 | /// 204 | /// Android: Corresponds to Android's "IME_ACTION_GO". The OS displays a 205 | /// button that represents taking "the user to the target of the text they 206 | /// typed", e.g., a right-facing arrow button. 207 | /// 208 | /// iOS: Corresponds to iOS's "UIReturnKeyGo". The title displayed in the 209 | /// action button is "Go". 210 | #[serde(rename = "TextInputAction.go")] 211 | Go, 212 | 213 | /// Logical meaning: Execute a search query. 214 | /// 215 | /// Android: Corresponds to Android's "IME_ACTION_SEARCH". The OS displays a 216 | /// button that represents a search, e.g., a magnifying glass button. 217 | /// 218 | /// iOS: Corresponds to iOS's "UIReturnKeySearch". The title displayed in the 219 | /// action button is "Search". 220 | #[serde(rename = "TextInputAction.search")] 221 | Search, 222 | 223 | /// Logical meaning: Sends something that the user has composed, e.g., an 224 | /// email or a text message. 225 | /// 226 | /// Android: Corresponds to Android's "IME_ACTION_SEND". The OS displays a 227 | /// button that represents sending something, e.g., a paper plane button. 228 | /// 229 | /// iOS: Corresponds to iOS's "UIReturnKeySend". The title displayed in the 230 | /// action button is "Send". 231 | #[serde(rename = "TextInputAction.send")] 232 | Send, 233 | 234 | /// Logical meaning: The user is done with the current input source and wants 235 | /// to move to the next one. 236 | /// 237 | /// Moves the focus to the next focusable item in the same [FocusScope]. 238 | /// 239 | /// Android: Corresponds to Android's "IME_ACTION_NEXT". The OS displays a 240 | /// button that represents moving forward, e.g., a right-facing arrow button. 241 | /// 242 | /// iOS: Corresponds to iOS's "UIReturnKeyNext". The title displayed in the 243 | /// action button is "Next". 244 | #[serde(rename = "TextInputAction.next")] 245 | Next, 246 | 247 | /// Logical meaning: The user wishes to return to the previous input source 248 | /// in the group, e.g., a form with multiple [TextField]s. 249 | /// 250 | /// Moves the focus to the previous focusable item in the same [FocusScope]. 251 | /// 252 | /// Android: Corresponds to Android's "IME_ACTION_PREVIOUS". The OS displays a 253 | /// button that represents moving backward, e.g., a left-facing arrow button. 254 | /// 255 | /// iOS: iOS does not have a keyboard return type of "previous." It is 256 | /// inappropriate to choose this [TextInputAction] when running on iOS. 257 | #[serde(rename = "TextInputAction.previous")] 258 | Previous, 259 | 260 | /// Logical meaning: In iOS apps, it is common for a "Back" button and 261 | /// "Continue" button to appear at the top of the screen. However, when the 262 | /// keyboard is open, these buttons are often hidden off-screen. Therefore, 263 | /// the purpose of the "Continue" return key on iOS is to make the "Continue" 264 | /// button available when the user is entering text. 265 | /// 266 | /// Historical context aside, [TextInputAction.continueAction] can be used any 267 | /// time that the term "Continue" seems most appropriate for the given action. 268 | /// 269 | /// Android: Android does not have an IME input type of "continue." It is 270 | /// inappropriate to choose this [TextInputAction] when running on Android. 271 | /// 272 | /// iOS: Corresponds to iOS's "UIReturnKeyContinue". The title displayed in the 273 | /// action button is "Continue". This action is only available on iOS 9.0+. 274 | /// 275 | /// The reason that this value has "Action" post-fixed to it is because 276 | /// "continue" is a reserved word in Dart, as well as many other languages. 277 | #[serde(rename = "TextInputAction.continueAction")] 278 | ContinueAction, 279 | 280 | /// Logical meaning: The user wants to join something, e.g., a wireless 281 | /// network. 282 | /// 283 | /// Android: Android does not have an IME input type of "join." It is 284 | /// inappropriate to choose this [TextInputAction] when running on Android. 285 | /// 286 | /// iOS: Corresponds to iOS's "UIReturnKeyJoin". The title displayed in the 287 | /// action button is "Join". 288 | #[serde(rename = "TextInputAction.join")] 289 | Join, 290 | 291 | /// Logical meaning: The user wants routing options, e.g., driving directions. 292 | /// 293 | /// Android: Android does not have an IME input type of "route." It is 294 | /// inappropriate to choose this [TextInputAction] when running on Android. 295 | /// 296 | /// iOS: Corresponds to iOS's "UIReturnKeyRoute". The title displayed in the 297 | /// action button is "Route". 298 | #[serde(rename = "TextInputAction.route")] 299 | Route, 300 | 301 | /// Logical meaning: Initiate a call to emergency services. 302 | /// 303 | /// Android: Android does not have an IME input type of "emergencyCall." It is 304 | /// inappropriate to choose this [TextInputAction] when running on Android. 305 | /// 306 | /// iOS: Corresponds to iOS's "UIReturnKeyEmergencyCall". The title displayed 307 | /// in the action button is "Emergency Call". 308 | #[serde(rename = "TextInputAction.emergencyCall")] 309 | EmergencyCall, 310 | 311 | /// Logical meaning: Insert a newline character in the focused text input, 312 | /// e.g., [TextField]. 313 | /// 314 | /// Android: Corresponds to Android's "IME_ACTION_NONE". The OS displays a 315 | /// button that represents a new line, e.g., a carriage return button. 316 | /// 317 | /// iOS: Corresponds to iOS's "UIReturnKeyDefault". The title displayed in the 318 | /// action button is "return". 319 | /// 320 | /// The term [TextInputAction.newline] exists in Flutter but not in Android 321 | /// or iOS. The reason for introducing this term is so that developers can 322 | /// achieve the common result of inserting new lines without needing to 323 | /// understand the various IME actions on Android and return keys on iOS. 324 | /// Thus, [TextInputAction.newline] is a convenience term that alleviates the 325 | /// need to understand the underlying platforms to achieve this common behavior. 326 | #[serde(rename = "TextInputAction.newline")] 327 | Newline, 328 | } 329 | 330 | /// The following incoming methods are defined for this channel (registered 331 | /// using [MethodChannel.setMethodCallHandler]). In each case, the first argument 332 | /// is a transaction identifier. Calls for stale transactions should be ignored. 333 | #[derive(Debug, Serialize, Deserialize, Clone)] 334 | #[serde(tag = "method", content = "args")] 335 | pub(super) enum TextInputClient { 336 | /// The user has changed the contents 337 | /// of the text control. The second argument is an object with seven keys, 338 | /// in the form expected by [TextEditingValue.fromJSON]. 339 | #[serde(rename = "TextInputClient.updateEditingState")] 340 | UpdateEditingState(u64, TextEditingValue), 341 | #[serde(rename = "TextInputClient.updateEditingStateWithDeltas")] 342 | UpdateEditingWithDeltas(u64, serde_json::Map), 343 | /// One or more text controls 344 | /// were autofilled by the platform's autofill service. The first argument 345 | /// (the client ID) is ignored, the second argument is a map of tags to 346 | /// objects in the form expected by [TextEditingValue.fromJSON]. See 347 | /// [AutofillScope.getAutofillClient] for details on the interpretation of 348 | /// the tag. 349 | #[serde(rename = "TextInputClient.updateEditingStateWithTag")] 350 | UpdateEditingStateWithTag(u64, serde_json::Map), 351 | /// The user has triggered an action. 352 | #[serde(rename = "TextInputClient.performAction")] 353 | PerformAction(u64, TextInputAction), 354 | #[serde(rename = "TextInputClient.performSelectors")] 355 | PerformSelectors(u64, Vec), 356 | /// The embedding may have 357 | /// lost its internal state about the current editing client, if there is 358 | /// one. The framework should call `TextInput.setClient` and 359 | /// `TextInput.setEditingState` again with its most recent information. If 360 | /// there is no existing state on the framework side, the call should 361 | /// fizzle. 362 | #[serde(rename = "TextInputClient.requestExistingInputState")] 363 | RequestExistingInputState, 364 | #[serde(rename = "TextInputClient.updateFloatingCursor")] 365 | UpdateFloatingCursor(u64, String), 366 | /// The text input connection closed 367 | /// on the platform side. For example the application is moved to 368 | /// background or used closed the virtual keyboard. This method informs 369 | /// [TextInputClient] to clear connection and finalize editing. 370 | /// `TextInput.clearClient` and `TextInput.hide` is not called after 371 | /// clearing the connection since on the platform side the connection is 372 | /// already finalized. 373 | #[serde(rename = "TextInputClient.onConnectionClosed")] 374 | OnConnectionClosed(u64), 375 | #[serde(rename = "TextInputClient.showAutocorrectionPromptRect")] 376 | ShowAutocorrectionPromptRect(u64), 377 | #[serde(rename = "TextInputClient.showToolbar")] 378 | ShowToolbar(u64), 379 | #[serde(rename = "TextInputClient.insertTextPlaceholder")] 380 | InsertTextPlaceholder(u64, f64, f64), 381 | #[serde(rename = "TextInputClient.removeTextPlaceholder")] 382 | RemoveTextPlaceholder(u64), 383 | } 384 | -------------------------------------------------------------------------------- /src/flutter_application/mouse_cursor.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use winit::window::CursorIcon; 3 | 4 | #[derive(Debug, Serialize, Deserialize, Clone)] 5 | #[serde(rename_all = "camelCase")] 6 | pub enum MouseCursor { 7 | ActivateSystemCursor { device: i32, kind: MouseCursorKind }, 8 | } 9 | 10 | #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] 11 | #[serde(rename_all = "camelCase")] 12 | pub enum MouseCursorKind { 13 | /// Hide the cursor. 14 | /// 15 | /// Any cursor other than [none] or [MouseCursor.uncontrolled] unhides the 16 | /// cursor. 17 | None, 18 | 19 | // STATUS 20 | /// The platform-dependent basic cursor. 21 | /// 22 | /// Typically the shape of an arrow. 23 | /// 24 | /// Corresponds to: 25 | /// 26 | /// * Android: TYPE_DEFAULT, TYPE_ARROW 27 | /// * Web: default 28 | /// * Windows: IDC_ARROW 29 | /// * Windows UWP: CoreCursorType::Arrow 30 | /// * Linux: default 31 | /// * macOS: arrowCursor 32 | Basic, 33 | 34 | /// A cursor that emphasizes an element being clickable, such as a hyperlink. 35 | /// 36 | /// Typically the shape of a pointing hand. 37 | /// 38 | /// Corresponds to: 39 | /// 40 | /// * Android: TYPE_HAND 41 | /// * Web: pointer 42 | /// * Windows: IDC_HAND 43 | /// * Windows UWP: CoreCursorType::Hand 44 | /// * Linux: pointer 45 | /// * macOS: pointingHandCursor 46 | Click, 47 | 48 | /// A cursor indicating an operation that will not be carried out. 49 | /// 50 | /// Typically the shape of a circle with a diagonal line. May fall back to 51 | /// [noDrop]. 52 | /// 53 | /// Corresponds to: 54 | /// 55 | /// * Android: TYPE_NO_DROP 56 | /// * Web: not-allowed 57 | /// * Windows: IDC_NO 58 | /// * Windows UWP: CoreCursorType::UniversalNo 59 | /// * Linux: not-allowed 60 | /// * macOS: operationNotAllowedCursor 61 | /// 62 | /// See also: 63 | /// 64 | /// * [noDrop], which indicates somewhere that the current item may not be 65 | /// dropped. 66 | Forbidden, 67 | 68 | /// A cursor indicating the status that the program is busy and therefore 69 | /// can not be interacted with. 70 | /// 71 | /// Typically the shape of an hourglass or a watch. 72 | /// 73 | /// This cursor is not available as a system cursor on macOS. Although macOS 74 | /// displays a "spinning ball" cursor when busy, it's handled by the OS and not 75 | /// exposed for applications to choose. 76 | /// 77 | /// Corresponds to: 78 | /// 79 | /// * Android: TYPE_WAIT 80 | /// * Windows: IDC_WAIT 81 | /// * Web: wait 82 | /// * Linux: wait 83 | /// 84 | /// See also: 85 | /// 86 | /// * [progress], which is similar to [wait] but the program can still be 87 | /// interacted with. 88 | Wait, 89 | 90 | /// A cursor indicating the status that the program is busy but can still be 91 | /// interacted with. 92 | /// 93 | /// Typically the shape of an arrow with an hourglass or a watch at the corner. 94 | /// Does *not* fall back to [wait] if unavailable. 95 | /// 96 | /// Corresponds to: 97 | /// 98 | /// * Web: progress 99 | /// * Windows: IDC_APPSTARTING 100 | /// * Linux: progress 101 | /// 102 | /// See also: 103 | /// 104 | /// * [wait], which is similar to [progress] but the program can not be 105 | /// interacted with. 106 | Progress, 107 | 108 | /// A cursor indicating somewhere the user can trigger a context menu. 109 | /// 110 | /// Typically the shape of an arrow with a small menu at the corner. 111 | /// 112 | /// Corresponds to: 113 | /// 114 | /// * Android: TYPE_CONTEXT_MENU 115 | /// * Web: context-menu 116 | /// * Linux: context-menu 117 | /// * macOS: contextualMenuCursor 118 | ContextMenu, 119 | 120 | /// A cursor indicating help information. 121 | /// 122 | /// Typically the shape of a question mark, or an arrow therewith. 123 | /// 124 | /// Corresponds to: 125 | /// 126 | /// * Android: TYPE_HELP 127 | /// * Windows: IDC_HELP 128 | /// * Windows UWP: CoreCursorType::Help 129 | /// * Web: help 130 | /// * Linux: help 131 | Help, 132 | 133 | // SELECTION 134 | /// A cursor indicating selectable text. 135 | /// 136 | /// Typically the shape of a capital I. 137 | /// 138 | /// Corresponds to: 139 | /// 140 | /// * Android: TYPE_TEXT 141 | /// * Web: text 142 | /// * Windows: IDC_IBEAM 143 | /// * Windows UWP: CoreCursorType::IBeam 144 | /// * Linux: text 145 | /// * macOS: IBeamCursor 146 | Text, 147 | 148 | /// A cursor indicating selectable vertical text. 149 | /// 150 | /// Typically the shape of a capital I rotated to be horizontal. May fall back 151 | /// to [text]. 152 | /// 153 | /// Corresponds to: 154 | /// 155 | /// * Android: TYPE_VERTICAL_TEXT 156 | /// * Web: vertical-text 157 | /// * Linux: vertical-text 158 | /// * macOS: IBeamCursorForVerticalLayout 159 | VerticalText, 160 | 161 | /// A cursor indicating selectable table cells. 162 | /// 163 | /// Typically the shape of a hollow plus sign. 164 | /// 165 | /// Corresponds to: 166 | /// 167 | /// * Android: TYPE_CELL 168 | /// * Web: cell 169 | /// * Linux: cell 170 | Cell, 171 | 172 | /// A cursor indicating precise selection, such as selecting a pixel in a 173 | /// bitmap. 174 | /// 175 | /// Typically the shape of a crosshair. 176 | /// 177 | /// Corresponds to: 178 | /// 179 | /// * Android: TYPE_CROSSHAIR 180 | /// * Web: crosshair 181 | /// * Windows: IDC_CROSS 182 | /// * Windows UWP: CoreCursorType::Cross 183 | /// * Linux: crosshair 184 | /// * macOS: crosshairCursor 185 | Precise, 186 | 187 | // DRAG-AND-DROP 188 | /// A cursor indicating moving something. 189 | /// 190 | /// Typically the shape of four-way arrow. May fall back to [allScroll]. 191 | /// 192 | /// Corresponds to: 193 | /// 194 | /// * Android: TYPE_ALL_SCROLL 195 | /// * Windows: IDC_SIZEALL 196 | /// * Windows UWP: CoreCursorType::SizeAll 197 | /// * Web: move 198 | /// * Linux: move 199 | Move, 200 | 201 | /// A cursor indicating something that can be dragged. 202 | /// 203 | /// Typically the shape of an open hand. 204 | /// 205 | /// Corresponds to: 206 | /// 207 | /// * Android: TYPE_GRAB 208 | /// * Web: grab 209 | /// * Linux: grab 210 | /// * macOS: openHandCursor 211 | Grab, 212 | 213 | /// A cursor indicating something that is being dragged. 214 | /// 215 | /// Typically the shape of a closed hand. 216 | /// 217 | /// Corresponds to: 218 | /// 219 | /// * Android: TYPE_GRABBING 220 | /// * Web: grabbing 221 | /// * Linux: grabbing 222 | /// * macOS: closedHandCursor 223 | Grabbing, 224 | 225 | /// A cursor indicating somewhere that the current item may not be dropped. 226 | /// 227 | /// Typically the shape of a hand with a [forbidden] sign at the corner. May 228 | /// fall back to [forbidden]. 229 | /// 230 | /// Corresponds to: 231 | /// 232 | /// * Android: TYPE_NO_DROP 233 | /// * Web: no-drop 234 | /// * Windows: IDC_NO 235 | /// * Windows UWP: CoreCursorType::UniversalNo 236 | /// * Linux: no-drop 237 | /// * macOS: operationNotAllowedCursor 238 | /// 239 | /// See also: 240 | /// 241 | /// * [forbidden], which indicates an action that will not be carried out. 242 | NoDrop, 243 | 244 | /// A cursor indicating that the current operation will create an alias of, or 245 | /// a shortcut of the item. 246 | /// 247 | /// Typically the shape of an arrow with a shortcut icon at the corner. 248 | /// 249 | /// Corresponds to: 250 | /// 251 | /// * Android: TYPE_ALIAS 252 | /// * Web: alias 253 | /// * Linux: alias 254 | /// * macOS: dragLinkCursor 255 | Alias, 256 | 257 | /// A cursor indicating that the current operation will copy the item. 258 | /// 259 | /// Typically the shape of an arrow with a boxed plus sign at the corner. 260 | /// 261 | /// Corresponds to: 262 | /// 263 | /// * Android: TYPE_COPY 264 | /// * Web: copy 265 | /// * Linux: copy 266 | /// * macOS: dragCopyCursor 267 | Copy, 268 | 269 | /// A cursor indicating that the current operation will result in the 270 | /// disappearance of the item. 271 | /// 272 | /// Typically the shape of an arrow with a cloud of smoke at the corner. 273 | /// 274 | /// Corresponds to: 275 | /// 276 | /// * macOS: disappearingItemCursor 277 | Disappearing, 278 | 279 | // RESIZING AND SCROLLING 280 | /// A cursor indicating scrolling in any direction. 281 | /// 282 | /// Typically the shape of a dot surrounded by 4 arrows. 283 | /// 284 | /// Corresponds to: 285 | /// 286 | /// * Android: TYPE_ALL_SCROLL 287 | /// * Windows: IDC_SIZEALL 288 | /// * Windows UWP: CoreCursorType::SizeAll 289 | /// * Web: all-scroll 290 | /// * Linux: all-scroll 291 | /// 292 | /// See also: 293 | /// 294 | /// * [move], which indicates moving in any direction. 295 | AllScroll, 296 | 297 | /// A cursor indicating resizing an object bidirectionally from its left or 298 | /// right edge. 299 | /// 300 | /// Typically the shape of a bidirectional arrow pointing left and right. 301 | /// 302 | /// Corresponds to: 303 | /// 304 | /// * Android: TYPE_HORIZONTAL_DOUBLE_ARROW 305 | /// * Web: ew-resize 306 | /// * Windows: IDC_SIZEWE 307 | /// * Windows UWP: CoreCursorType::SizeWestEast 308 | /// * Linux: ew-resize 309 | /// * macOS: resizeLeftRightCursor 310 | ResizeLeftRight, 311 | 312 | /// A cursor indicating resizing an object bidirectionally from its top or 313 | /// bottom edge. 314 | /// 315 | /// Typically the shape of a bidirectional arrow pointing up and down. 316 | /// 317 | /// Corresponds to: 318 | /// 319 | /// * Android: TYPE_VERTICAL_DOUBLE_ARROW 320 | /// * Web: ns-resize 321 | /// * Windows: IDC_SIZENS 322 | /// * Windows UWP: CoreCursorType::SizeNorthSouth 323 | /// * Linux: ns-resize 324 | /// * macOS: resizeUpDownCursor 325 | ResizeUpDown, 326 | 327 | /// A cursor indicating resizing an object bidirectionally from its top left or 328 | /// bottom right corner. 329 | /// 330 | /// Typically the shape of a bidirectional arrow pointing upper left and lower right. 331 | /// 332 | /// Corresponds to: 333 | /// 334 | /// * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 335 | /// * Web: nwse-resize 336 | /// * Windows: IDC_SIZENWSE 337 | /// * Windows UWP: CoreCursorType::SizeNorthwestSoutheast 338 | /// * Linux: nwse-resize 339 | ResizeUpLeftDownRight, 340 | 341 | /// A cursor indicating resizing an object bidirectionally from its top right or 342 | /// bottom left corner. 343 | /// 344 | /// Typically the shape of a bidirectional arrow pointing upper right and lower left. 345 | /// 346 | /// Corresponds to: 347 | /// 348 | /// * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 349 | /// * Windows: IDC_SIZENESW 350 | /// * Windows UWP: CoreCursorType::SizeNortheastSouthwest 351 | /// * Web: nesw-resize 352 | /// * Linux: nesw-resize 353 | ResizeUpRightDownLeft, 354 | 355 | /// A cursor indicating resizing an object from its top edge. 356 | /// 357 | /// Typically the shape of an arrow pointing up. May fallback to [resizeUpDown]. 358 | /// 359 | /// Corresponds to: 360 | /// 361 | /// * Android: TYPE_VERTICAL_DOUBLE_ARROW 362 | /// * Web: n-resize 363 | /// * Windows: IDC_SIZENS 364 | /// * Windows UWP: CoreCursorType::SizeNorthSouth 365 | /// * Linux: n-resize 366 | /// * macOS: resizeUpCursor 367 | ResizeUp, 368 | 369 | /// A cursor indicating resizing an object from its bottom edge. 370 | /// 371 | /// Typically the shape of an arrow pointing down. May fallback to [resizeUpDown]. 372 | /// 373 | /// Corresponds to: 374 | /// 375 | /// * Android: TYPE_VERTICAL_DOUBLE_ARROW 376 | /// * Web: s-resize 377 | /// * Windows: IDC_SIZENS 378 | /// * Windows UWP: CoreCursorType::SizeNorthSouth 379 | /// * Linux: s-resize 380 | /// * macOS: resizeDownCursor 381 | ResizeDown, 382 | 383 | /// A cursor indicating resizing an object from its left edge. 384 | /// 385 | /// Typically the shape of an arrow pointing left. May fallback to [resizeLeftRight]. 386 | /// 387 | /// Corresponds to: 388 | /// 389 | /// * Android: TYPE_HORIZONTAL_DOUBLE_ARROW 390 | /// * Web: w-resize 391 | /// * Windows: IDC_SIZEWE 392 | /// * Windows UWP: CoreCursorType::SizeWestEast 393 | /// * Linux: w-resize 394 | /// * macOS: resizeLeftCursor 395 | ResizeLeft, 396 | 397 | /// A cursor indicating resizing an object from its right edge. 398 | /// 399 | /// Typically the shape of an arrow pointing right. May fallback to [resizeLeftRight]. 400 | /// 401 | /// Corresponds to: 402 | /// 403 | /// * Android: TYPE_HORIZONTAL_DOUBLE_ARROW 404 | /// * Web: e-resize 405 | /// * Windows: IDC_SIZEWE 406 | /// * Windows UWP: CoreCursorType::SizeWestEast 407 | /// * Linux: e-resize 408 | /// * macOS: resizeRightCursor 409 | ResizeRight, 410 | 411 | /// A cursor indicating resizing an object from its top-left corner. 412 | /// 413 | /// Typically the shape of an arrow pointing upper left. May fallback to [resizeUpLeftDownRight]. 414 | /// 415 | /// Corresponds to: 416 | /// 417 | /// * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 418 | /// * Web: nw-resize 419 | /// * Windows: IDC_SIZENWSE 420 | /// * Windows UWP: CoreCursorType::SizeNorthwestSoutheast 421 | /// * Linux: nw-resize 422 | ResizeUpLeft, 423 | 424 | /// A cursor indicating resizing an object from its top-right corner. 425 | /// 426 | /// Typically the shape of an arrow pointing upper right. May fallback to [resizeUpRightDownLeft]. 427 | /// 428 | /// Corresponds to: 429 | /// 430 | /// * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 431 | /// * Web: ne-resize 432 | /// * Windows: IDC_SIZENESW 433 | /// * Windows UWP: CoreCursorType::SizeNortheastSouthwest 434 | /// * Linux: ne-resize 435 | ResizeUpRight, 436 | 437 | /// A cursor indicating resizing an object from its bottom-left corner. 438 | /// 439 | /// Typically the shape of an arrow pointing lower left. May fallback to [resizeUpRightDownLeft]. 440 | /// 441 | /// Corresponds to: 442 | /// 443 | /// * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 444 | /// * Web: sw-resize 445 | /// * Windows: IDC_SIZENESW 446 | /// * Windows UWP: CoreCursorType::SizeNortheastSouthwest 447 | /// * Linux: sw-resize 448 | ResizeDownLeft, 449 | 450 | /// A cursor indicating resizing an object from its bottom-right corner. 451 | /// 452 | /// Typically the shape of an arrow pointing lower right. May fallback to [resizeUpLeftDownRight]. 453 | /// 454 | /// Corresponds to: 455 | /// 456 | /// * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 457 | /// * Web: se-resize 458 | /// * Windows: IDC_SIZENWSE 459 | /// * Windows UWP: CoreCursorType::SizeNorthwestSoutheast 460 | /// * Linux: se-resize 461 | ResizeDownRight, 462 | 463 | /// A cursor indicating resizing a column, or an item horizontally. 464 | /// 465 | /// Typically the shape of arrows pointing left and right with a vertical bar 466 | /// separating them. May fallback to [resizeLeftRight]. 467 | /// 468 | /// Corresponds to: 469 | /// 470 | /// * Android: TYPE_HORIZONTAL_DOUBLE_ARROW 471 | /// * Web: col-resize 472 | /// * Windows: IDC_SIZEWE 473 | /// * Windows UWP: CoreCursorType::SizeWestEast 474 | /// * Linux: col-resize 475 | /// * macOS: resizeLeftRightCursor 476 | ResizeColumn, 477 | 478 | /// A cursor indicating resizing a row, or an item vertically. 479 | /// 480 | /// Typically the shape of arrows pointing up and down with a horizontal bar 481 | /// separating them. May fallback to [resizeUpDown]. 482 | /// 483 | /// Corresponds to: 484 | /// 485 | /// * Android: TYPE_VERTICAL_DOUBLE_ARROW 486 | /// * Web: row-resize 487 | /// * Windows: IDC_SIZENS 488 | /// * Windows UWP: CoreCursorType::SizeNorthSouth 489 | /// * Linux: row-resize 490 | /// * macOS: resizeUpDownCursor 491 | ResizeRow, 492 | 493 | // OTHER OPERATIONS 494 | /// A cursor indicating zooming in. 495 | /// 496 | /// Typically a magnifying glass with a plus sign. 497 | /// 498 | /// Corresponds to: 499 | /// 500 | /// * Android: TYPE_ZOOM_IN 501 | /// * Web: zoom-in 502 | /// * Linux: zoom-in 503 | ZoomIn, 504 | 505 | /// A cursor indicating zooming out. 506 | /// 507 | /// Typically a magnifying glass with a minus sign. 508 | /// 509 | /// Corresponds to: 510 | /// 511 | /// * Android: TYPE_ZOOM_OUT 512 | /// * Web: zoom-out 513 | /// * Linux: zoom-out 514 | ZoomOut, 515 | } 516 | 517 | impl Into> for MouseCursorKind { 518 | fn into(self) -> Option { 519 | Some(match self { 520 | MouseCursorKind::None => return None, 521 | MouseCursorKind::Basic => CursorIcon::Default, 522 | MouseCursorKind::Click => CursorIcon::Hand, 523 | MouseCursorKind::Forbidden => CursorIcon::NotAllowed, 524 | MouseCursorKind::Wait => CursorIcon::Wait, 525 | MouseCursorKind::Progress => CursorIcon::Progress, 526 | MouseCursorKind::ContextMenu => CursorIcon::ContextMenu, 527 | MouseCursorKind::Help => CursorIcon::Help, 528 | MouseCursorKind::Text => CursorIcon::Text, 529 | MouseCursorKind::VerticalText => CursorIcon::VerticalText, 530 | MouseCursorKind::Cell => CursorIcon::Cell, 531 | MouseCursorKind::Precise => CursorIcon::Crosshair, 532 | MouseCursorKind::Move => CursorIcon::Move, 533 | MouseCursorKind::Grab => CursorIcon::Grab, 534 | MouseCursorKind::Grabbing => CursorIcon::Grabbing, 535 | MouseCursorKind::NoDrop => CursorIcon::NoDrop, 536 | MouseCursorKind::Alias => CursorIcon::Alias, 537 | MouseCursorKind::Copy => CursorIcon::Copy, 538 | MouseCursorKind::Disappearing => unimplemented!(), 539 | MouseCursorKind::AllScroll => CursorIcon::AllScroll, 540 | MouseCursorKind::ResizeLeftRight => CursorIcon::NeResize, 541 | MouseCursorKind::ResizeUpDown => CursorIcon::NsResize, 542 | MouseCursorKind::ResizeUpLeftDownRight => CursorIcon::NwseResize, 543 | MouseCursorKind::ResizeUpRightDownLeft => CursorIcon::NeswResize, 544 | MouseCursorKind::ResizeUp => CursorIcon::NResize, 545 | MouseCursorKind::ResizeDown => CursorIcon::SResize, 546 | MouseCursorKind::ResizeLeft => CursorIcon::WResize, 547 | MouseCursorKind::ResizeRight => CursorIcon::EResize, 548 | MouseCursorKind::ResizeUpLeft => CursorIcon::NwResize, 549 | MouseCursorKind::ResizeUpRight => CursorIcon::NeResize, 550 | MouseCursorKind::ResizeDownLeft => CursorIcon::SwResize, 551 | MouseCursorKind::ResizeDownRight => CursorIcon::SwResize, 552 | MouseCursorKind::ResizeColumn => CursorIcon::ColResize, 553 | MouseCursorKind::ResizeRow => CursorIcon::RowResize, 554 | MouseCursorKind::ZoomIn => CursorIcon::ZoomIn, 555 | MouseCursorKind::ZoomOut => CursorIcon::ZoomOut, 556 | }) 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /src/flutter_application/keyboard.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CString, 3 | mem::size_of, 4 | ptr::{null, null_mut}, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use arboard::Clipboard; 9 | use winit::{ 10 | event::{ElementState, KeyEvent}, 11 | keyboard::{Key, ModifiersState}, 12 | }; 13 | 14 | use crate::{ 15 | action_key::ActionKey, 16 | flutter_application::{text_input::TextInputClient, FlutterApplication}, 17 | flutter_bindings::{ 18 | FlutterEngine, FlutterEngineSendKeyEvent, FlutterEngineSendPlatformMessage, 19 | FlutterKeyEvent, FlutterKeyEventType_kFlutterKeyEventTypeDown, 20 | FlutterKeyEventType_kFlutterKeyEventTypeRepeat, FlutterKeyEventType_kFlutterKeyEventTypeUp, 21 | FlutterPlatformMessage, 22 | }, 23 | keyboard_logical_key_map::translate_logical_key, 24 | keyboard_physical_key_map::translate_physical_key, 25 | }; 26 | 27 | use super::{ 28 | text_input::{TextEditingValue, TextInput, TextInputAction}, 29 | FLUTTER_TEXTINPUT_CHANNEL, 30 | }; 31 | 32 | pub struct Keyboard { 33 | client: Option, 34 | modifiers: ModifiersState, 35 | editing_state: TextEditingValue, 36 | clipboard: Arc>, 37 | input_action: TextInputAction, 38 | channel: CString, 39 | } 40 | 41 | impl Keyboard { 42 | pub(super) fn new(clipboard: Arc>) -> Self { 43 | Self { 44 | client: None, 45 | modifiers: Default::default(), 46 | editing_state: Default::default(), 47 | clipboard, 48 | input_action: TextInputAction::Unspecified, 49 | channel: CString::new(FLUTTER_TEXTINPUT_CHANNEL).unwrap(), 50 | } 51 | } 52 | pub(super) fn modifiers_changed(&mut self, state: ModifiersState) { 53 | self.modifiers = state; 54 | } 55 | 56 | fn move_home(&mut self) { 57 | self.editing_state.selection_base = Some(0); 58 | if !self.modifiers.shift_key() { 59 | self.editing_state.selection_extent = Some(0); 60 | } 61 | } 62 | 63 | fn move_end(&mut self) { 64 | let len = self.editing_state.text.chars().count(); 65 | self.editing_state.selection_extent = Some(len as _); 66 | if !self.modifiers.shift_key() { 67 | self.editing_state.selection_base = self.editing_state.selection_extent; 68 | } 69 | } 70 | 71 | fn insert_text(&mut self, text: &str) { 72 | let editing_state = &mut self.editing_state; 73 | let len = editing_state.text.chars().count(); 74 | let selection_base = editing_state.selection_base.unwrap_or(0) as usize; 75 | let selection_extent = editing_state.selection_extent.unwrap_or(0) as usize; 76 | let selection = selection_base.min(selection_extent)..selection_base.max(selection_extent); 77 | 78 | if len > 0 && selection.start < len { 79 | editing_state.text.replace_range(selection.clone(), text); 80 | editing_state.selection_base = Some((selection.start + text.chars().count()) as _); 81 | } else { 82 | editing_state.text.push_str(text); 83 | editing_state.selection_base = Some(editing_state.text.chars().count() as _); 84 | } 85 | editing_state.selection_extent = editing_state.selection_base; 86 | } 87 | 88 | pub(super) fn key_event(&mut self, engine: FlutterEngine, event: KeyEvent, synthesized: bool) { 89 | log::debug!( 90 | "keyboard input: logical {:?} physical {:?} (Translated {:?}, {:?})", 91 | event.logical_key, 92 | event.physical_key, 93 | translate_logical_key(event.logical_key), 94 | translate_physical_key(event.physical_key), 95 | ); 96 | if let (Some(logical), Some(physical)) = ( 97 | translate_logical_key(event.logical_key), 98 | translate_physical_key(event.physical_key), 99 | ) { 100 | // let flutter_event = FlutterKeyboardEvent::Linux { 101 | // r#type: match event.state { 102 | // ElementState::Pressed => FlutterKeyboardEventType::KeyDown, 103 | // ElementState::Released => FlutterKeyboardEventType::KeyUp, 104 | // }, 105 | // toolkit: LinuxToolkit::Gtk, 106 | // unicode_scalar_values: if let Some(character) = event.text { 107 | // let mut buffer = [0u8; 8]; 108 | // if character.as_bytes().read(&mut buffer).is_ok() { 109 | // u64::from_le_bytes(buffer) 110 | // } else { 111 | // 0 112 | // } 113 | // } else { 114 | // 0 115 | // }, 116 | // key_code: physical, 117 | // scan_code: logical, 118 | // modifiers: 0, 119 | // specified_logical_key: 0, 120 | // }; 121 | // let flutter_event = FlutterKeyboardEvent::Web { 122 | // r#type: match event.state { 123 | // ElementState::Pressed => FlutterKeyboardEventType::KeyDown, 124 | // ElementState::Released => FlutterKeyboardEventType::KeyUp, 125 | // }, 126 | // code: event.text.unwrap_or_default().to_owned(), 127 | // key: event.text.unwrap_or_default().to_owned(), 128 | // location: 0, 129 | // meta_state: 0, 130 | // key_code: 0, 131 | // }; 132 | 133 | // let json = serde_json::to_vec(&flutter_event).unwrap(); 134 | // log::debug!("keyevent: {:?}", String::from_utf8(json.clone())); 135 | // let channel = CStr::from_bytes_with_nul(b"flutter/keyevent\0").unwrap(); 136 | // let message = FlutterPlatformMessage { 137 | // struct_size: size_of::() as _, 138 | // channel: channel.as_ptr(), 139 | // message: json.as_ptr(), 140 | // message_size: json.len() as _, 141 | // response_handle: null(), 142 | // }; 143 | 144 | // Self::unwrap_result(unsafe { FlutterEngineSendPlatformMessage(self.engine, &message) }); 145 | 146 | // drop(message); 147 | // drop(channel); 148 | 149 | let type_ = match event.state { 150 | ElementState::Pressed => { 151 | if event.repeat { 152 | FlutterKeyEventType_kFlutterKeyEventTypeRepeat 153 | } else { 154 | FlutterKeyEventType_kFlutterKeyEventTypeDown 155 | } 156 | } 157 | ElementState::Released => FlutterKeyEventType_kFlutterKeyEventTypeUp, 158 | }; 159 | log::debug!( 160 | "keyboard event: physical {physical:#x} logical {logical:#x} text {:?}", 161 | event.text 162 | ); 163 | let character = event.text.map(|text| CString::new(text).unwrap()); 164 | let flutter_event = FlutterKeyEvent { 165 | struct_size: size_of::() as _, 166 | timestamp: FlutterApplication::current_time() as f64, 167 | type_, 168 | physical, 169 | logical, 170 | character: if event.state == ElementState::Released { 171 | null() 172 | } else if let Some(character) = &character { 173 | character.as_ptr() 174 | } else { 175 | null() 176 | }, 177 | synthesized, 178 | }; 179 | FlutterApplication::unwrap_result(unsafe { 180 | FlutterEngineSendKeyEvent(engine, &flutter_event, None, null_mut()) 181 | }); 182 | drop(character); 183 | 184 | log::debug!( 185 | "Updating editing state for keyboard client {:?}", 186 | self.client 187 | ); 188 | 189 | if event.state == ElementState::Pressed 190 | && self 191 | .editing_state 192 | .selection_base 193 | .map(|val| val >= 0) 194 | .unwrap_or(false) 195 | && self 196 | .editing_state 197 | .selection_extent 198 | .map(|val| val >= 0) 199 | .unwrap_or(false) 200 | { 201 | // send flutter/textinput message 202 | { 203 | let editing_state = &mut self.editing_state; 204 | let len = editing_state.text.chars().count(); 205 | let selection_base = editing_state.selection_base.unwrap_or(0) as usize; 206 | let selection_extent = editing_state.selection_extent.unwrap_or(0) as usize; 207 | let selection = 208 | selection_base.min(selection_extent)..selection_base.max(selection_extent); 209 | match event.logical_key { 210 | #[cfg(any(target_os = "macos", target_os = "ios"))] 211 | Key::ArrowLeft if self.keyboard_modifiers.meta_key() => { 212 | self.move_home(); 213 | } 214 | #[cfg(any(target_os = "macos", target_os = "ios"))] 215 | Key::ArrowRight if self.keyboard_modifiers.meta_key() => { 216 | self.move_end(); 217 | } 218 | Key::ArrowLeft => { 219 | if selection.start > 0 { 220 | if !self.modifiers.shift_key() && selection.start != selection.end { 221 | editing_state.selection_extent = editing_state.selection_base; 222 | } else { 223 | editing_state.selection_base = Some((selection.start - 1) as _); 224 | if !self.modifiers.shift_key() { 225 | editing_state.selection_extent = 226 | editing_state.selection_base; 227 | } 228 | } 229 | } else if !self.modifiers.shift_key() 230 | && selection.start != selection.end 231 | { 232 | editing_state.selection_extent = editing_state.selection_base; 233 | } 234 | } 235 | Key::ArrowRight => { 236 | if selection.end < len { 237 | if !self.modifiers.shift_key() && selection.start != selection.end { 238 | editing_state.selection_base = editing_state.selection_extent; 239 | } else { 240 | editing_state.selection_extent = Some((selection.end + 1) as _); 241 | if !self.modifiers.shift_key() { 242 | editing_state.selection_base = 243 | editing_state.selection_extent; 244 | } 245 | } 246 | } else if !self.modifiers.shift_key() 247 | && selection.start != selection.end 248 | { 249 | editing_state.selection_base = editing_state.selection_extent; 250 | } 251 | } 252 | Key::ArrowUp | Key::Home => { 253 | self.move_home(); 254 | } 255 | Key::ArrowDown | Key::End => { 256 | self.move_end(); 257 | } 258 | Key::Backspace => { 259 | if selection.start == selection.end { 260 | if selection.start > 0 { 261 | editing_state.text.remove(selection.start - 1); 262 | editing_state.selection_base = Some((selection.start - 1) as _); 263 | } 264 | editing_state.selection_extent = editing_state.selection_base; 265 | } else { 266 | editing_state.text.replace_range(selection.clone(), ""); 267 | editing_state.selection_extent = editing_state.selection_base; 268 | } 269 | } 270 | Key::Delete => { 271 | if selection.start == selection.end { 272 | if selection.start < len { 273 | editing_state.text.remove(selection.start); 274 | } 275 | } else { 276 | editing_state.text.replace_range(selection.clone(), ""); 277 | editing_state.selection_extent = editing_state.selection_base; 278 | } 279 | } 280 | Key::Character("a") if self.modifiers.action_key() => { 281 | editing_state.selection_base = Some(0); 282 | editing_state.selection_extent = Some(len as _); 283 | } 284 | #[cfg(any(target_os = "macos", target_os = "ios"))] 285 | Key::Character("a") if self.keyboard_modifiers.control_key() => { 286 | self.move_home(); 287 | } 288 | #[cfg(any(target_os = "macos", target_os = "ios"))] 289 | Key::Character("e") if self.modifiers.control_key() => { 290 | self.move_end(); 291 | } 292 | Key::Character("x") if self.modifiers.action_key() => { 293 | if selection.start != selection.end { 294 | let text = editing_state 295 | .text 296 | .chars() 297 | .skip(selection.start) 298 | .take(selection.end - selection.start) 299 | .collect(); 300 | editing_state.text.replace_range(selection.clone(), ""); 301 | editing_state.selection_extent = editing_state.selection_base; 302 | self.clipboard.lock().unwrap().set_text(text).unwrap(); 303 | } 304 | } 305 | Key::Character("c") if self.modifiers.action_key() => { 306 | if selection.start != selection.end { 307 | let text = editing_state 308 | .text 309 | .chars() 310 | .skip(selection.start) 311 | .take(selection.end - selection.start) 312 | .collect(); 313 | self.clipboard.lock().unwrap().set_text(text).unwrap(); 314 | } 315 | } 316 | Key::Character("v") if self.modifiers.action_key() => { 317 | let text = { 318 | let mut clipboard = self.clipboard.lock().unwrap(); 319 | clipboard.get_text() 320 | }; 321 | if let Ok(text) = text { 322 | self.insert_text(&text); 323 | } 324 | } 325 | Key::Enter => { 326 | self.send_action(engine, self.input_action); 327 | } 328 | Key::Tab => { 329 | if self.modifiers.shift_key() { 330 | self.send_action(engine, TextInputAction::Previous); 331 | } else { 332 | self.send_action(engine, TextInputAction::Next); 333 | } 334 | } 335 | _ if self.modifiers.control_key() || self.modifiers.super_key() => { 336 | // ignore 337 | } 338 | _ => { 339 | if let Some(text) = event.text { 340 | self.insert_text(text); 341 | } 342 | } 343 | } 344 | } 345 | self.update_editing_state(engine); 346 | } 347 | } 348 | } 349 | 350 | fn update_editing_state(&self, engine: FlutterEngine) { 351 | if let Some(client) = self.client { 352 | let message = TextInputClient::UpdateEditingState(client, self.editing_state.clone()); 353 | log::info!("update_editing_state message: {message:?}"); 354 | let message_json = serde_json::to_vec(&message).unwrap(); 355 | FlutterApplication::unwrap_result(unsafe { 356 | FlutterEngineSendPlatformMessage( 357 | engine, 358 | &FlutterPlatformMessage { 359 | struct_size: size_of::() as _, 360 | channel: self.channel.as_ptr(), 361 | message: message_json.as_ptr(), 362 | message_size: message_json.len() as _, 363 | response_handle: null(), 364 | }, 365 | ) 366 | }); 367 | } 368 | } 369 | 370 | fn send_action(&self, engine: FlutterEngine, action: TextInputAction) { 371 | if let Some(client) = self.client { 372 | let message = TextInputClient::PerformAction(client, action); 373 | let message_json = serde_json::to_vec(&message).unwrap(); 374 | FlutterApplication::unwrap_result(unsafe { 375 | FlutterEngineSendPlatformMessage( 376 | engine, 377 | &FlutterPlatformMessage { 378 | struct_size: size_of::() as _, 379 | channel: self.channel.as_ptr(), 380 | message: message_json.as_ptr(), 381 | message_size: message_json.len() as _, 382 | response_handle: null(), 383 | }, 384 | ) 385 | }); 386 | } 387 | } 388 | 389 | pub(super) fn handle_textinput_message(&mut self, textinput: TextInput) { 390 | match textinput { 391 | TextInput::SetClient(client_id, parameters) => { 392 | self.client = Some(client_id); 393 | self.input_action = parameters.input_action; 394 | log::debug!("Setting keyboard client to {:?}", client_id); 395 | } 396 | TextInput::ClearClient => { 397 | self.client = None; 398 | log::debug!("Setting keyboard client to None"); 399 | } 400 | TextInput::SetEditingState(state) => { 401 | log::debug!("set editing state: {:#?}", state); 402 | self.editing_state = state; 403 | } 404 | other => { 405 | log::warn!("Unhandled TextInput message: {:#?}", other); 406 | } 407 | } 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/flutter_application/message_codec.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | array::TryFromSliceError, marker::PhantomData, mem::size_of, num::TryFromIntError, 3 | str::Utf8Error, 4 | }; 5 | 6 | use num_derive::{FromPrimitive, ToPrimitive}; 7 | use num_traits::cast::FromPrimitive; 8 | use serde::{ 9 | de::{self, EnumAccess, IntoDeserializer, MapAccess, SeqAccess, VariantAccess}, 10 | Deserialize, 11 | }; 12 | 13 | #[derive(Debug)] 14 | pub enum Error { 15 | TupleLength, 16 | ExpectedNil, 17 | Utf8Error(Utf8Error), 18 | ValueOutOfRange(TryFromIntError), 19 | InvalidFieldType, 20 | TrailingCharacters, 21 | Eof, 22 | Message(String), 23 | } 24 | 25 | impl de::Error for Error { 26 | fn custom(msg: T) -> Self { 27 | Error::Message(msg.to_string()) 28 | } 29 | } 30 | 31 | impl From for Error { 32 | fn from(err: TryFromIntError) -> Self { 33 | Self::ValueOutOfRange(err) 34 | } 35 | } 36 | 37 | impl From for Error { 38 | fn from(err: Utf8Error) -> Self { 39 | Self::Utf8Error(err) 40 | } 41 | } 42 | 43 | impl std::fmt::Display for Error { 44 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 45 | match self { 46 | Error::TupleLength => formatter.write_str("Tuple length doesn't match list length"), 47 | Error::ExpectedNil => formatter.write_str("Expected nil"), 48 | Error::Utf8Error(err) => err.fmt(formatter), 49 | Error::ValueOutOfRange(err) => err.fmt(formatter), 50 | Error::InvalidFieldType => formatter.write_str("Invalid field type"), 51 | Error::Message(msg) => formatter.write_str(msg), 52 | Error::Eof => formatter.write_str("unexpected end of input"), 53 | Error::TrailingCharacters => formatter.write_str("trailing characters in input"), 54 | /* and so forth */ 55 | } 56 | } 57 | } 58 | 59 | impl std::error::Error for Error {} 60 | 61 | #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] 62 | #[repr(u8)] 63 | enum FlutterStandardField { 64 | Nil, 65 | True, 66 | False, 67 | Int32, 68 | Int64, 69 | IntHex, 70 | Float64, 71 | String, 72 | UInt8Data, 73 | Int32Data, 74 | Int64Data, 75 | Float64Data, 76 | List, 77 | Map, 78 | Float32Data, 79 | } 80 | 81 | pub struct Deserializer<'de> { 82 | input: &'de [u8], 83 | pos: usize, 84 | } 85 | 86 | impl<'de> Deserializer<'de> { 87 | pub fn from_slice(input: &'de [u8]) -> Self { 88 | Deserializer { input, pos: 0 } 89 | } 90 | } 91 | 92 | pub fn from_slice<'a, T>(b: &'a [u8]) -> Result 93 | where 94 | T: Deserialize<'a>, 95 | { 96 | let mut deserializer = Deserializer::from_slice(b); 97 | let t = T::deserialize(&mut deserializer)?; 98 | if deserializer.input.len() == deserializer.pos { 99 | Ok(t) 100 | } else { 101 | Err(Error::TrailingCharacters) 102 | } 103 | } 104 | 105 | impl<'de> Deserializer<'de> { 106 | fn read_bytes(&mut self) -> Result<[u8; N], Error> { 107 | if self.pos + N > self.input.len() { 108 | Err(Error::Eof) 109 | } else { 110 | let mut result = [0; N]; 111 | result.copy_from_slice(&self.input[self.pos..self.pos + N]); 112 | self.pos += N; 113 | Ok(result) 114 | } 115 | } 116 | fn read_byte(&mut self) -> Result { 117 | Ok(self.read_bytes::<1>()?[0]) 118 | } 119 | fn read_field_type(&mut self) -> Result { 120 | FlutterStandardField::from_u8(self.read_byte()?).ok_or(Error::InvalidFieldType) 121 | } 122 | fn peek_field_type(&mut self) -> Result { 123 | if self.pos >= self.input.len() { 124 | Err(Error::Eof) 125 | } else { 126 | FlutterStandardField::from_u8(self.input[self.pos]).ok_or(Error::InvalidFieldType) 127 | } 128 | } 129 | 130 | fn read_size(&mut self) -> Result { 131 | let byte = self.read_byte()?; 132 | if byte < 254 { 133 | return Ok(byte as _); 134 | } 135 | if byte == 254 { 136 | return Ok(u16::from_le_bytes(self.read_bytes()?) as _); 137 | } 138 | Ok(u32::from_le_bytes(self.read_bytes()?) as _) 139 | } 140 | 141 | fn read_alignment(&mut self, alignment: usize) { 142 | let offset = self.pos % alignment; 143 | if offset > 0 { 144 | self.pos += alignment - offset; 145 | } 146 | } 147 | 148 | fn read_data(&mut self, len: usize) -> Result<&[u8], Error> { 149 | if self.pos + len > self.input.len() { 150 | Err(Error::Eof) 151 | } else { 152 | let result = &self.input[self.pos..self.pos + len]; 153 | self.pos += len; 154 | Ok(result) 155 | } 156 | } 157 | } 158 | 159 | impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { 160 | type Error = Error; 161 | 162 | fn deserialize_any(self, visitor: V) -> Result 163 | where 164 | V: de::Visitor<'de>, 165 | { 166 | match self.peek_field_type()? { 167 | FlutterStandardField::Nil => self.deserialize_option(visitor), 168 | FlutterStandardField::True | FlutterStandardField::False => { 169 | self.deserialize_bool(visitor) 170 | } 171 | FlutterStandardField::Int32 => self.deserialize_i32(visitor), 172 | FlutterStandardField::Int64 => self.deserialize_i64(visitor), 173 | FlutterStandardField::IntHex => self.deserialize_str(visitor), 174 | FlutterStandardField::Float64 => self.deserialize_f64(visitor), 175 | FlutterStandardField::String => self.deserialize_str(visitor), 176 | FlutterStandardField::UInt8Data => self.deserialize_bytes(visitor), 177 | FlutterStandardField::Int32Data => self.deserialize_seq(visitor), 178 | FlutterStandardField::Int64Data => self.deserialize_seq(visitor), 179 | FlutterStandardField::Float32Data => self.deserialize_seq(visitor), 180 | FlutterStandardField::Float64Data => self.deserialize_seq(visitor), 181 | FlutterStandardField::List => self.deserialize_seq(visitor), 182 | FlutterStandardField::Map => self.deserialize_map(visitor), 183 | } 184 | } 185 | 186 | fn deserialize_bool(self, visitor: V) -> Result 187 | where 188 | V: de::Visitor<'de>, 189 | { 190 | match self.read_field_type()? { 191 | FlutterStandardField::True => visitor.visit_bool(true), 192 | FlutterStandardField::False => visitor.visit_bool(false), 193 | _ => Err(Error::InvalidFieldType), 194 | } 195 | } 196 | 197 | fn deserialize_i8(self, visitor: V) -> Result 198 | where 199 | V: de::Visitor<'de>, 200 | { 201 | if self.read_field_type()? == FlutterStandardField::Int32 { 202 | visitor.visit_i8(i32::from_le_bytes(self.read_bytes()?).try_into()?) 203 | } else { 204 | Err(Error::InvalidFieldType) 205 | } 206 | } 207 | 208 | fn deserialize_i16(self, visitor: V) -> Result 209 | where 210 | V: de::Visitor<'de>, 211 | { 212 | if self.read_field_type()? == FlutterStandardField::Int32 { 213 | visitor.visit_i16(i32::from_le_bytes(self.read_bytes()?).try_into()?) 214 | } else { 215 | Err(Error::InvalidFieldType) 216 | } 217 | } 218 | 219 | fn deserialize_i32(self, visitor: V) -> Result 220 | where 221 | V: de::Visitor<'de>, 222 | { 223 | if self.read_field_type()? == FlutterStandardField::Int32 { 224 | visitor.visit_i32(i32::from_le_bytes(self.read_bytes()?)) 225 | } else { 226 | Err(Error::InvalidFieldType) 227 | } 228 | } 229 | 230 | fn deserialize_i64(self, visitor: V) -> Result 231 | where 232 | V: de::Visitor<'de>, 233 | { 234 | if self.read_field_type()? == FlutterStandardField::Int64 { 235 | visitor.visit_i64(i64::from_le_bytes(self.read_bytes()?)) 236 | } else { 237 | Err(Error::InvalidFieldType) 238 | } 239 | } 240 | 241 | fn deserialize_u8(self, visitor: V) -> Result 242 | where 243 | V: de::Visitor<'de>, 244 | { 245 | if self.read_field_type()? == FlutterStandardField::Int32 { 246 | visitor.visit_u8(i32::from_le_bytes(self.read_bytes()?).try_into()?) 247 | } else { 248 | Err(Error::InvalidFieldType) 249 | } 250 | } 251 | 252 | fn deserialize_u16(self, visitor: V) -> Result 253 | where 254 | V: de::Visitor<'de>, 255 | { 256 | if self.read_field_type()? == FlutterStandardField::Int32 { 257 | visitor.visit_u16(i32::from_le_bytes(self.read_bytes()?).try_into()?) 258 | } else { 259 | Err(Error::InvalidFieldType) 260 | } 261 | } 262 | 263 | fn deserialize_u32(self, visitor: V) -> Result 264 | where 265 | V: de::Visitor<'de>, 266 | { 267 | if self.read_field_type()? == FlutterStandardField::Int64 { 268 | visitor.visit_u32(i64::from_le_bytes(self.read_bytes()?).try_into()?) 269 | } else { 270 | Err(Error::InvalidFieldType) 271 | } 272 | } 273 | 274 | fn deserialize_u64(self, visitor: V) -> Result 275 | where 276 | V: de::Visitor<'de>, 277 | { 278 | if self.read_field_type()? == FlutterStandardField::Int64 { 279 | visitor.visit_u32(i64::from_le_bytes(self.read_bytes()?).try_into()?) 280 | } else { 281 | Err(Error::InvalidFieldType) 282 | } 283 | } 284 | 285 | fn deserialize_f32(self, visitor: V) -> Result 286 | where 287 | V: de::Visitor<'de>, 288 | { 289 | if self.read_field_type()? == FlutterStandardField::Float64 { 290 | visitor.visit_f32(f64::from_le_bytes(self.read_bytes()?) as _) 291 | } else { 292 | Err(Error::InvalidFieldType) 293 | } 294 | } 295 | 296 | fn deserialize_f64(self, visitor: V) -> Result 297 | where 298 | V: de::Visitor<'de>, 299 | { 300 | if self.read_field_type()? == FlutterStandardField::Float64 { 301 | self.read_alignment(8); 302 | visitor.visit_f64(f64::from_le_bytes(self.read_bytes()?)) 303 | } else { 304 | Err(Error::InvalidFieldType) 305 | } 306 | } 307 | 308 | fn deserialize_char(self, _visitor: V) -> Result 309 | where 310 | V: de::Visitor<'de>, 311 | { 312 | unimplemented!() 313 | } 314 | 315 | fn deserialize_str(self, visitor: V) -> Result 316 | where 317 | V: de::Visitor<'de>, 318 | { 319 | match self.read_field_type()? { 320 | FlutterStandardField::IntHex | FlutterStandardField::String => { 321 | let len = self.read_size()?; 322 | let bytes = self.read_data(len)?; 323 | visitor.visit_str(std::str::from_utf8(bytes)?) 324 | } 325 | _ => Err(Error::InvalidFieldType), 326 | } 327 | } 328 | 329 | fn deserialize_string(self, visitor: V) -> Result 330 | where 331 | V: de::Visitor<'de>, 332 | { 333 | match self.read_field_type()? { 334 | FlutterStandardField::IntHex | FlutterStandardField::String => { 335 | let len = self.read_size()?; 336 | let bytes = self.read_data(len)?; 337 | visitor.visit_string(std::str::from_utf8(bytes)?.to_owned()) 338 | } 339 | _ => Err(Error::InvalidFieldType), 340 | } 341 | } 342 | 343 | fn deserialize_bytes(self, visitor: V) -> Result 344 | where 345 | V: de::Visitor<'de>, 346 | { 347 | if self.read_field_type()? == FlutterStandardField::UInt8Data { 348 | let len = self.read_size()?; 349 | visitor.visit_bytes(self.read_data(len)?) 350 | } else { 351 | Err(Error::InvalidFieldType) 352 | } 353 | } 354 | 355 | fn deserialize_byte_buf(self, visitor: V) -> Result 356 | where 357 | V: de::Visitor<'de>, 358 | { 359 | if self.read_field_type()? == FlutterStandardField::UInt8Data { 360 | let len = self.read_size()?; 361 | visitor.visit_byte_buf(self.read_data(len)?.to_vec()) 362 | } else { 363 | Err(Error::InvalidFieldType) 364 | } 365 | } 366 | 367 | fn deserialize_option(self, visitor: V) -> Result 368 | where 369 | V: de::Visitor<'de>, 370 | { 371 | if self.peek_field_type()? == FlutterStandardField::Nil { 372 | self.read_field_type()?; 373 | visitor.visit_none() 374 | } else { 375 | visitor.visit_some(self) 376 | } 377 | } 378 | 379 | fn deserialize_unit(self, visitor: V) -> Result 380 | where 381 | V: de::Visitor<'de>, 382 | { 383 | if self.read_field_type()? == FlutterStandardField::Nil { 384 | visitor.visit_unit() 385 | } else { 386 | Err(Error::ExpectedNil) 387 | } 388 | } 389 | 390 | fn deserialize_unit_struct( 391 | self, 392 | _name: &'static str, 393 | visitor: V, 394 | ) -> Result 395 | where 396 | V: de::Visitor<'de>, 397 | { 398 | self.deserialize_unit(visitor) 399 | } 400 | 401 | fn deserialize_newtype_struct( 402 | self, 403 | _name: &'static str, 404 | visitor: V, 405 | ) -> Result 406 | where 407 | V: de::Visitor<'de>, 408 | { 409 | visitor.visit_newtype_struct(self) 410 | } 411 | 412 | fn deserialize_seq(self, visitor: V) -> Result 413 | where 414 | V: de::Visitor<'de>, 415 | { 416 | match self.read_field_type()? { 417 | FlutterStandardField::List => { 418 | let len = self.read_size()?; 419 | visitor.visit_seq(ListDeserializer::new(self, len)) 420 | } 421 | FlutterStandardField::Int32Data => { 422 | let len = self.read_size()?; 423 | visitor.visit_seq(PrimitiveListDeserializer::::new(self, len)) 424 | } 425 | FlutterStandardField::Int64Data => { 426 | let len = self.read_size()?; 427 | visitor.visit_seq(PrimitiveListDeserializer::::new(self, len)) 428 | } 429 | FlutterStandardField::Float32Data => { 430 | let len = self.read_size()?; 431 | visitor.visit_seq(PrimitiveListDeserializer::::new(self, len)) 432 | } 433 | FlutterStandardField::Float64Data => { 434 | let len = self.read_size()?; 435 | visitor.visit_seq(PrimitiveListDeserializer::::new(self, len)) 436 | } 437 | _ => Err(Error::InvalidFieldType), 438 | } 439 | } 440 | 441 | fn deserialize_tuple(self, tuple_len: usize, visitor: V) -> Result 442 | where 443 | V: de::Visitor<'de>, 444 | { 445 | if self.read_field_type()? == FlutterStandardField::List { 446 | let len = self.read_size()?; 447 | if len != tuple_len { 448 | Err(Error::TupleLength) 449 | } else { 450 | visitor.visit_seq(ListDeserializer::new(self, len)) 451 | } 452 | } else { 453 | Err(Error::InvalidFieldType) 454 | } 455 | } 456 | 457 | fn deserialize_tuple_struct( 458 | self, 459 | _name: &'static str, 460 | len: usize, 461 | visitor: V, 462 | ) -> Result 463 | where 464 | V: de::Visitor<'de>, 465 | { 466 | self.deserialize_tuple(len, visitor) 467 | } 468 | 469 | fn deserialize_map(self, visitor: V) -> Result 470 | where 471 | V: de::Visitor<'de>, 472 | { 473 | if self.read_field_type()? == FlutterStandardField::Map { 474 | let len = self.read_size()?; 475 | visitor.visit_map(ListDeserializer::new(self, len)) 476 | } else { 477 | Err(Error::InvalidFieldType) 478 | } 479 | } 480 | 481 | fn deserialize_struct( 482 | self, 483 | _name: &'static str, 484 | _fields: &'static [&'static str], 485 | visitor: V, 486 | ) -> Result 487 | where 488 | V: de::Visitor<'de>, 489 | { 490 | self.deserialize_map(visitor) 491 | } 492 | 493 | fn deserialize_enum( 494 | self, 495 | _name: &'static str, 496 | _variants: &'static [&'static str], 497 | visitor: V, 498 | ) -> Result 499 | where 500 | V: de::Visitor<'de>, 501 | { 502 | log::trace!("deserialize_enum {_variants:?}"); 503 | visitor.visit_enum(Enum::new(self)) 504 | } 505 | 506 | fn deserialize_identifier(self, visitor: V) -> Result 507 | where 508 | V: de::Visitor<'de>, 509 | { 510 | log::debug!( 511 | "deserialize_identifier, peek = {:?}", 512 | self.peek_field_type()? 513 | ); 514 | self.deserialize_str(visitor) 515 | } 516 | 517 | fn deserialize_ignored_any(self, visitor: V) -> Result 518 | where 519 | V: de::Visitor<'de>, 520 | { 521 | self.deserialize_any(visitor) 522 | } 523 | } 524 | 525 | struct ListDeserializer<'a, 'de: 'a> { 526 | de: &'a mut Deserializer<'de>, 527 | len: usize, 528 | } 529 | 530 | impl<'a, 'de> ListDeserializer<'a, 'de> { 531 | fn new(de: &'a mut Deserializer<'de>, len: usize) -> Self { 532 | Self { de, len } 533 | } 534 | } 535 | 536 | impl<'de, 'a> SeqAccess<'de> for ListDeserializer<'a, 'de> { 537 | type Error = Error; 538 | 539 | fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> 540 | where 541 | T: de::DeserializeSeed<'de>, 542 | { 543 | if self.len == 0 { 544 | Ok(None) 545 | } else { 546 | self.len -= 1; 547 | seed.deserialize(&mut *self.de).map(Some) 548 | } 549 | } 550 | } 551 | 552 | impl<'de, 'a> MapAccess<'de> for ListDeserializer<'a, 'de> { 553 | type Error = Error; 554 | 555 | fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> 556 | where 557 | K: de::DeserializeSeed<'de>, 558 | { 559 | if self.len == 0 { 560 | Ok(None) 561 | } else { 562 | self.len -= 1; 563 | seed.deserialize(&mut *self.de).map(Some) 564 | } 565 | } 566 | 567 | fn next_value_seed(&mut self, seed: V) -> Result 568 | where 569 | V: de::DeserializeSeed<'de>, 570 | { 571 | seed.deserialize(&mut *self.de) 572 | } 573 | } 574 | 575 | trait EndianRead: Sized { 576 | type Array; 577 | fn from_le_bytes(bytes: Self::Array) -> Self; 578 | fn from_be_bytes(bytes: Self::Array) -> Self; 579 | fn try_from_le_bytes(bytes: &[u8]) -> Result; 580 | fn try_from_be_bytes(bytes: &[u8]) -> Result; 581 | } 582 | 583 | macro_rules! impl_EndianRead_for_nums (( $($num:ident),* ) => { 584 | $( 585 | impl EndianRead for $num { 586 | type Array = [u8; std::mem::size_of::()]; 587 | fn from_le_bytes(bytes: Self::Array) -> Self { Self::from_le_bytes(bytes) } 588 | fn from_be_bytes(bytes: Self::Array) -> Self { Self::from_be_bytes(bytes) } 589 | fn try_from_le_bytes(bytes: &[u8]) -> Result { 590 | Ok(Self::from_le_bytes(bytes.try_into()?)) 591 | } 592 | fn try_from_be_bytes(bytes: &[u8]) -> Result { 593 | Ok(Self::from_be_bytes(bytes.try_into()?)) 594 | } 595 | } 596 | )* 597 | }); 598 | 599 | impl_EndianRead_for_nums!(i32, i64, f32, f64); 600 | 601 | struct PrimitiveListDeserializer<'a, 'de: 'a, N: EndianRead + IntoDeserializer<'de>> { 602 | de: &'a mut Deserializer<'de>, 603 | len: usize, 604 | type_: PhantomData, 605 | } 606 | 607 | impl<'a, 'de, N: EndianRead + IntoDeserializer<'de>> PrimitiveListDeserializer<'a, 'de, N> { 608 | fn new(de: &'a mut Deserializer<'de>, len: usize) -> Self { 609 | let size = size_of::(); 610 | let modulo = de.pos % size; 611 | if modulo != 0 { 612 | de.pos += size - modulo; 613 | } 614 | 615 | Self { 616 | de, 617 | len, 618 | type_: PhantomData, 619 | } 620 | } 621 | } 622 | 623 | impl<'de, 'a, N: EndianRead + IntoDeserializer<'de>> SeqAccess<'de> 624 | for PrimitiveListDeserializer<'a, 'de, N> 625 | { 626 | type Error = Error; 627 | 628 | fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> 629 | where 630 | T: de::DeserializeSeed<'de>, 631 | { 632 | if self.len == 0 { 633 | Ok(None) 634 | } else { 635 | self.len -= 1; 636 | let bytes = self.de.read_data(size_of::())?; 637 | let number = N::try_from_le_bytes(&bytes).unwrap(); 638 | seed.deserialize(number.into_deserializer()) 639 | .map(Some) 640 | .map_err(|_| Error::InvalidFieldType) 641 | } 642 | } 643 | } 644 | 645 | struct Enum<'a, 'de: 'a> { 646 | de: &'a mut Deserializer<'de>, 647 | } 648 | 649 | impl<'a, 'de> Enum<'a, 'de> { 650 | fn new(de: &'a mut Deserializer<'de>) -> Self { 651 | Enum { de } 652 | } 653 | } 654 | 655 | impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> { 656 | type Error = Error; 657 | type Variant = Self; 658 | 659 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> 660 | where 661 | V: de::DeserializeSeed<'de>, 662 | { 663 | let val = seed.deserialize(&mut *self.de)?; 664 | Ok((val, self)) 665 | } 666 | } 667 | 668 | impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> { 669 | type Error = Error; 670 | 671 | fn unit_variant(self) -> Result<(), Self::Error> { 672 | // I don't think that the format has a way to verify this 673 | Ok(()) 674 | } 675 | 676 | fn newtype_variant_seed(self, seed: T) -> Result 677 | where 678 | T: de::DeserializeSeed<'de>, 679 | { 680 | seed.deserialize(self.de) 681 | } 682 | 683 | fn tuple_variant(self, len: usize, visitor: V) -> Result 684 | where 685 | V: de::Visitor<'de>, 686 | { 687 | de::Deserializer::deserialize_tuple(self.de, len, visitor) 688 | } 689 | 690 | fn struct_variant( 691 | self, 692 | _fields: &'static [&'static str], 693 | visitor: V, 694 | ) -> Result 695 | where 696 | V: de::Visitor<'de>, 697 | { 698 | de::Deserializer::deserialize_map(self.de, visitor) 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /src/flutter_application.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::{CStr, CString}, 4 | mem::{size_of, MaybeUninit}, 5 | os::{ 6 | raw::{c_char, c_void}, 7 | unix::prelude::OsStrExt, 8 | }, 9 | path::{Path, PathBuf}, 10 | ptr::{null, null_mut}, 11 | sync::{Arc, Mutex}, 12 | thread::ThreadId, 13 | time::Duration, 14 | }; 15 | 16 | use arboard::Clipboard; 17 | use ash::vk::Handle; 18 | use log::Level; 19 | use tokio::runtime::Runtime; 20 | use wgpu::{Device, Instance, Queue, Surface}; 21 | use wgpu_hal::api::Vulkan; 22 | use winit::{ 23 | dpi::PhysicalPosition, 24 | event::{DeviceId, ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase}, 25 | event_loop::EventLoopProxy, 26 | keyboard::ModifiersState, 27 | window::{CursorIcon, Window}, 28 | }; 29 | 30 | use crate::{ 31 | flutter_application::{mouse_cursor::MouseCursor, platform::Platform, text_input::TextInput}, 32 | flutter_bindings::{ 33 | FlutterCustomTaskRunners, FlutterEngine, FlutterEngineAOTData, FlutterEngineCollectAOTData, 34 | FlutterEngineGetCurrentTime, FlutterEngineInitialize, FlutterEngineOnVsync, 35 | FlutterEngineResult, FlutterEngineResult_kInternalInconsistency, 36 | FlutterEngineResult_kInvalidArguments, FlutterEngineResult_kInvalidLibraryVersion, 37 | FlutterEngineResult_kSuccess, FlutterEngineRunInitialized, FlutterEngineRunTask, 38 | FlutterEngineScheduleFrame, FlutterEngineSendPlatformMessage, 39 | FlutterEngineSendPlatformMessageResponse, FlutterEngineSendPointerEvent, 40 | FlutterEngineSendWindowMetricsEvent, FlutterEngineShutdown, FlutterFrameInfo, 41 | FlutterPlatformMessage, FlutterPlatformMessageResponseHandle, 42 | FlutterPointerDeviceKind_kFlutterPointerDeviceKindMouse, FlutterPointerEvent, 43 | FlutterPointerPhase, FlutterPointerPhase_kAdd, FlutterPointerPhase_kDown, 44 | FlutterPointerPhase_kHover, FlutterPointerPhase_kMove, FlutterPointerPhase_kRemove, 45 | FlutterPointerPhase_kUp, FlutterPointerSignalKind_kFlutterPointerSignalKindNone, 46 | FlutterPointerSignalKind_kFlutterPointerSignalKindScroll, FlutterProjectArgs, 47 | FlutterRendererConfig, FlutterRendererConfig__bindgen_ty_1, FlutterRendererType_kVulkan, 48 | FlutterSemanticsCustomAction, FlutterSemanticsNode, FlutterTask, 49 | FlutterTaskRunnerDescription, FlutterVulkanImage, FlutterVulkanInstanceHandle, 50 | FlutterVulkanRendererConfig, FlutterWindowMetricsEvent, FLUTTER_ENGINE_VERSION, 51 | }, 52 | utils::flutter_asset_bundle_is_valid, 53 | }; 54 | 55 | use self::{ 56 | keyboard::Keyboard, lifecycle::LifecycleState, platform_views::PlatformViewsHandler, 57 | task_runner::TaskRunner, 58 | }; 59 | 60 | // mod keyboard_event; 61 | // use keyboard_event::{FlutterKeyboardEvent, FlutterKeyboardEventType, LinuxToolkit}; 62 | mod compositor; 63 | mod keyboard; 64 | mod lifecycle; 65 | mod message_codec; 66 | mod mouse_cursor; 67 | mod platform; 68 | mod platform_views; 69 | mod task_runner; 70 | mod text_input; 71 | 72 | use compositor::Compositor; 73 | 74 | const PIXELS_PER_LINE: f64 = 10.0; 75 | const FLUTTER_TEXTINPUT_CHANNEL: &str = "flutter/textinput"; 76 | const FLUTTER_MOUSECURSOR_CHANNEL: &str = "flutter/mousecursor"; 77 | const FLUTTER_PLATFORM_CHANNEL: &str = "flutter/platform"; 78 | const FLUTTER_LIFECYCLE_CHANNEL: &str = "flutter/lifecycle"; 79 | const FLUTTER_PLATFORM_VIEWS_CHANNEL: &str = "flutter/platform_views"; 80 | 81 | struct PointerState { 82 | virtual_id: i32, 83 | position: PhysicalPosition, 84 | held_buttons: u64, 85 | } 86 | 87 | struct SendFlutterTask(FlutterTask); 88 | unsafe impl Send for SendFlutterTask {} 89 | 90 | struct SendFlutterPlatformMessageResponseHandle(*const FlutterPlatformMessageResponseHandle); 91 | unsafe impl Send for SendFlutterPlatformMessageResponseHandle {} 92 | 93 | pub type FlutterApplicationCallback = 94 | Box bool + 'static + Send>; 95 | 96 | struct FlutterApplicationUserData { 97 | event_loop_proxy: Mutex>, 98 | instance: Arc, 99 | runtime: Arc, 100 | device: Device, 101 | surface: Surface, 102 | queue: Queue, 103 | main_thread: ThreadId, 104 | render_task_runner: TaskRunner, 105 | } 106 | 107 | pub struct FlutterApplication { 108 | engine: FlutterEngine, 109 | compositor: Compositor, 110 | instance: Arc, 111 | aot_data: Vec, 112 | mice: HashMap, 113 | current_mouse_id: i32, 114 | runtime: Arc, 115 | clipboard: Arc>, 116 | keyboard: Keyboard, 117 | window: Arc, 118 | platform_views_handler: PlatformViewsHandler, 119 | user_data: Box, 120 | set_cursor_icon: Box) + 'static>, 121 | } 122 | 123 | impl FlutterApplication { 124 | pub fn new( 125 | runtime: Arc, 126 | asset_bundle_path: &Path, 127 | flutter_flags: Vec, 128 | surface: Surface, 129 | instance: Arc, 130 | device: Device, 131 | queue: Queue, 132 | event_loop_proxy: EventLoopProxy, 133 | window: Arc, 134 | set_cursor_icon: impl Fn(Option) + 'static, 135 | ) -> FlutterApplication { 136 | if !flutter_asset_bundle_is_valid(asset_bundle_path) { 137 | panic!("Flutter asset bundle was not valid."); 138 | } 139 | let mut icudtl_dat = PathBuf::new(); 140 | icudtl_dat.push("linux"); 141 | icudtl_dat.push("icudtl.dat"); 142 | if !icudtl_dat.exists() { 143 | panic!("{icudtl_dat:?} not found."); 144 | } 145 | let (raw_instance, version, instance_extensions) = unsafe { 146 | instance.as_hal::().map(|instance| { 147 | let raw_instance = instance.shared_instance().raw_instance(); 148 | let raw_handle = raw_instance.handle().as_raw(); 149 | ( 150 | raw_handle, 151 | 0, // skip check, we're using 1.3 but flutter only supports up to 1.2 right now //instance.shared_instance().driver_api_version(), 152 | instance 153 | .shared_instance() 154 | .extensions() 155 | .into_iter() 156 | .map(|&s| s.to_owned()) 157 | .collect::>(), 158 | ) 159 | }) 160 | } 161 | .expect("wgpu didn't choose Vulkan as rendering backend"); 162 | 163 | let (raw_device, raw_physical_device, queue_family_index, raw_queue, device_extensions) = 164 | unsafe { 165 | device.as_hal::(|device| { 166 | device.map(|device| { 167 | ( 168 | device.raw_device().handle().as_raw(), 169 | device.raw_physical_device().as_raw(), 170 | device.queue_family_index(), 171 | device.raw_queue().as_raw(), 172 | device 173 | .enabled_device_extensions() 174 | .into_iter() 175 | .map(|&s| s.to_owned()) 176 | .collect::>(), 177 | ) 178 | }) 179 | }) 180 | } 181 | .unwrap(); 182 | 183 | let mut enabled_device_extensions: Vec<*const c_char> = 184 | device_extensions.iter().map(|ext| ext.as_ptr()).collect(); 185 | let mut enabled_instance_extensions: Vec<*const c_char> = 186 | instance_extensions.iter().map(|ext| ext.as_ptr()).collect(); 187 | 188 | let config = FlutterRendererConfig { 189 | type_: FlutterRendererType_kVulkan, 190 | __bindgen_anon_1: FlutterRendererConfig__bindgen_ty_1 { 191 | vulkan: FlutterVulkanRendererConfig { 192 | struct_size: size_of::() as _, 193 | version, 194 | instance: raw_instance as _, 195 | physical_device: raw_physical_device as _, 196 | device: raw_device as _, 197 | queue_family_index, 198 | queue: raw_queue as _, 199 | enabled_instance_extension_count: enabled_instance_extensions.len() as _, 200 | enabled_instance_extensions: enabled_instance_extensions.as_mut_ptr(), 201 | enabled_device_extension_count: enabled_device_extensions.len() as _, 202 | enabled_device_extensions: enabled_device_extensions.as_mut_ptr(), 203 | get_instance_proc_address_callback: Some(Self::instance_proc_address_callback), 204 | get_next_image_callback: Some(Self::next_image), 205 | present_image_callback: Some(Self::present_image), 206 | }, 207 | }, 208 | }; 209 | 210 | let argv: Vec = flutter_flags 211 | .iter() 212 | .map(|arg| CString::new(arg.as_bytes()).unwrap()) 213 | .collect(); 214 | let argv_ptr: Vec<*const c_char> = argv 215 | .iter() 216 | .map(|arg| arg.as_bytes().as_ptr() as _) 217 | .collect(); 218 | 219 | let user_data = Box::new(FlutterApplicationUserData { 220 | event_loop_proxy: Mutex::new(event_loop_proxy), 221 | instance: instance.clone(), 222 | runtime: runtime.clone(), 223 | device, 224 | surface, 225 | queue, 226 | main_thread: std::thread::current().id(), 227 | render_task_runner: TaskRunner::new("renderer".to_owned()), 228 | }); 229 | 230 | let clipboard = Arc::new(Mutex::new(Clipboard::new().unwrap())); 231 | 232 | let mut instance = Self { 233 | engine: null_mut(), 234 | compositor: Compositor::new(), 235 | instance, 236 | aot_data: vec![], 237 | mice: Default::default(), 238 | current_mouse_id: 0, 239 | runtime, 240 | keyboard: Keyboard::new(clipboard.clone()), 241 | clipboard, 242 | platform_views_handler: Default::default(), 243 | user_data, 244 | window, 245 | set_cursor_icon: Box::new(set_cursor_icon), 246 | }; 247 | 248 | let flutter_compositor = instance.compositor.flutter_compositor(&instance); 249 | 250 | let platform_task_runner = FlutterTaskRunnerDescription { 251 | struct_size: size_of::() as _, 252 | user_data: &*instance.user_data as *const _ as _, 253 | runs_task_on_current_thread_callback: Some(Self::runs_task_on_current_thread_callback), 254 | post_task_callback: Some(Self::post_task_callback), 255 | identifier: 0, 256 | }; 257 | let render_task_runner = FlutterTaskRunnerDescription { 258 | struct_size: size_of::() as _, 259 | user_data: &instance.user_data.render_task_runner as *const _ as _, 260 | runs_task_on_current_thread_callback: Some( 261 | TaskRunner::runs_task_on_current_thread_callback, 262 | ), 263 | post_task_callback: Some(TaskRunner::post_task_callback), 264 | identifier: 2, 265 | }; 266 | let custom_task_runners = FlutterCustomTaskRunners { 267 | struct_size: size_of::() as _, 268 | platform_task_runner: &platform_task_runner, 269 | render_task_runner: &render_task_runner, 270 | thread_priority_setter: None, 271 | }; 272 | 273 | let icu_data_path = CString::new(icudtl_dat.as_os_str().as_bytes()).unwrap(); 274 | let mut args = unsafe { MaybeUninit::::zeroed().assume_init() }; 275 | args.struct_size = size_of::() as _; 276 | args.assets_path = asset_bundle_path.as_os_str().as_bytes().as_ptr() as _; 277 | args.icu_data_path = icu_data_path.as_ptr() as _; 278 | args.command_line_argc = flutter_flags.len() as _; 279 | args.command_line_argv = argv_ptr.as_ptr(); 280 | args.platform_message_callback = Some(Self::platform_message_callback); 281 | args.root_isolate_create_callback = Some(Self::root_isolate_create); 282 | args.update_semantics_node_callback = Some(Self::update_semantics_node); 283 | args.update_semantics_custom_action_callback = Some(Self::update_semantics_custom_action); 284 | args.vsync_callback = Some(Self::vsync_callback); 285 | args.custom_task_runners = &custom_task_runners; 286 | args.shutdown_dart_vm_when_done = true; 287 | args.compositor = &flutter_compositor as _; 288 | args.dart_old_gen_heap_size = -1; 289 | args.log_message_callback = Some(Self::log_message_callback); 290 | args.on_pre_engine_restart_callback = Some(Self::on_pre_engine_restart_callback); 291 | 292 | std::fs::create_dir("cache").ok(); 293 | args.persistent_cache_path = b"cache".as_ptr() as _; 294 | 295 | Self::unwrap_result(unsafe { 296 | FlutterEngineInitialize( 297 | FLUTTER_ENGINE_VERSION.into(), 298 | &config as _, 299 | &args as _, 300 | &*instance.user_data as *const _ as _, 301 | &mut instance.engine, 302 | ) 303 | }); 304 | 305 | instance.user_data.render_task_runner.run(instance.engine); 306 | 307 | drop(enabled_device_extensions); 308 | drop(enabled_instance_extensions); 309 | drop(instance_extensions); 310 | drop(device_extensions); 311 | drop(flutter_compositor); 312 | drop(custom_task_runners); 313 | drop(platform_task_runner); 314 | drop(render_task_runner); 315 | drop(argv); 316 | 317 | instance 318 | } 319 | 320 | pub fn run(&self) { 321 | Self::unwrap_result(unsafe { FlutterEngineRunInitialized(self.engine) }); 322 | } 323 | 324 | pub fn metrics_changed(&self, width: u32, height: u32, pixel_ratio: f64, x: i32, y: i32) { 325 | self.user_data 326 | .event_loop_proxy 327 | .lock() 328 | .unwrap() 329 | .send_event(Box::new(move |application| { 330 | let metrics = FlutterWindowMetricsEvent { 331 | struct_size: size_of::() as _, 332 | width: width as _, 333 | height: height as _, 334 | pixel_ratio, 335 | left: x.max(0) as _, 336 | top: y.max(0) as _, 337 | physical_view_inset_top: 0.0, 338 | physical_view_inset_right: 0.0, 339 | physical_view_inset_bottom: 0.0, 340 | physical_view_inset_left: 0.0, 341 | }; 342 | log::debug!("setting metrics to {metrics:?}"); 343 | Self::unwrap_result(unsafe { 344 | FlutterEngineSendWindowMetricsEvent(application.engine, &metrics) 345 | }); 346 | drop(metrics); 347 | false 348 | })) 349 | .ok() 350 | .unwrap(); 351 | } 352 | 353 | fn get_mouse(&mut self, device_id: DeviceId) -> &mut PointerState { 354 | if !self.mice.contains_key(&device_id) { 355 | let virtual_id = self.current_mouse_id; 356 | self.current_mouse_id += 1; 357 | self.mice.insert( 358 | device_id, 359 | PointerState { 360 | virtual_id, 361 | position: PhysicalPosition::new(0.0, 0.0), 362 | held_buttons: 0, 363 | }, 364 | ); 365 | self.send_pointer_event(device_id, FlutterPointerPhase_kAdd, None); 366 | } 367 | self.mice.get_mut(&device_id).unwrap() 368 | } 369 | 370 | pub fn mouse_buttons(&mut self, device_id: DeviceId, state: ElementState, button: MouseButton) { 371 | let mouse = self.get_mouse(device_id); 372 | let old_buttons_held = mouse.held_buttons != 0; 373 | let button_idx = match button { 374 | MouseButton::Left => 1, 375 | MouseButton::Right => 2, 376 | MouseButton::Middle => 4, 377 | MouseButton::Other(x) => 1 << x, 378 | }; 379 | match state { 380 | ElementState::Pressed => mouse.held_buttons ^= button_idx, 381 | ElementState::Released => mouse.held_buttons &= !button_idx, 382 | } 383 | let new_buttons_held = mouse.held_buttons != 0; 384 | 385 | self.send_pointer_event( 386 | device_id, 387 | if state == ElementState::Pressed { 388 | if old_buttons_held { 389 | FlutterPointerPhase_kMove 390 | } else { 391 | FlutterPointerPhase_kDown 392 | } 393 | } else { 394 | if new_buttons_held { 395 | FlutterPointerPhase_kMove 396 | } else { 397 | FlutterPointerPhase_kUp 398 | } 399 | }, 400 | None, 401 | ); 402 | } 403 | 404 | pub fn mouse_entered(&mut self, device_id: DeviceId) { 405 | self.get_mouse(device_id); 406 | } 407 | 408 | pub fn mouse_left(&mut self, device_id: DeviceId) { 409 | self.send_pointer_event(device_id, FlutterPointerPhase_kRemove, None); 410 | self.mice.remove(&device_id); 411 | } 412 | 413 | pub fn mouse_moved(&mut self, device_id: DeviceId, position: PhysicalPosition) { 414 | let mouse = self.get_mouse(device_id); 415 | mouse.position = position; 416 | let buttons = mouse.held_buttons; 417 | self.send_pointer_event( 418 | device_id, 419 | if buttons == 0 { 420 | FlutterPointerPhase_kHover 421 | } else { 422 | FlutterPointerPhase_kMove 423 | }, 424 | None, 425 | ); 426 | } 427 | 428 | pub fn mouse_wheel( 429 | &mut self, 430 | device_id: DeviceId, 431 | delta: MouseScrollDelta, 432 | _phase: TouchPhase, 433 | ) { 434 | let mouse = self.get_mouse(device_id); 435 | let buttons = mouse.held_buttons; 436 | self.send_pointer_event( 437 | device_id, 438 | if buttons == 0 { 439 | FlutterPointerPhase_kHover 440 | } else { 441 | FlutterPointerPhase_kMove 442 | }, 443 | Some(delta), 444 | ) 445 | } 446 | 447 | fn send_pointer_event( 448 | &self, 449 | device_id: DeviceId, 450 | phase: FlutterPointerPhase, 451 | scroll_delta: Option, 452 | ) { 453 | if let Some(mouse) = self.mice.get(&device_id) { 454 | let scroll_delta_px = { 455 | match scroll_delta { 456 | Some(MouseScrollDelta::LineDelta(x, y)) => PhysicalPosition::new( 457 | (x as f64) * PIXELS_PER_LINE, 458 | (y as f64) * PIXELS_PER_LINE, 459 | ), 460 | Some(MouseScrollDelta::PixelDelta(pt)) => pt, 461 | None => PhysicalPosition::new(0.0, 0.0), 462 | } 463 | }; 464 | let event = FlutterPointerEvent { 465 | struct_size: size_of::() as _, 466 | phase, 467 | timestamp: Self::current_time(), 468 | x: mouse.position.x, 469 | y: mouse.position.y, 470 | device: mouse.virtual_id, 471 | signal_kind: if scroll_delta.is_none() { 472 | FlutterPointerSignalKind_kFlutterPointerSignalKindNone 473 | } else { 474 | FlutterPointerSignalKind_kFlutterPointerSignalKindScroll 475 | }, 476 | scroll_delta_x: scroll_delta_px.x, 477 | scroll_delta_y: scroll_delta_px.y, 478 | device_kind: FlutterPointerDeviceKind_kFlutterPointerDeviceKindMouse, 479 | buttons: mouse.held_buttons as _, 480 | pan_x: 0.0, 481 | pan_y: 0.0, 482 | scale: 1.0, 483 | rotation: 0.0, 484 | }; 485 | self.user_data 486 | .event_loop_proxy 487 | .lock() 488 | .unwrap() 489 | .send_event(Box::new(move |application| { 490 | Self::unwrap_result(unsafe { 491 | FlutterEngineSendPointerEvent(application.engine, &event, 1) 492 | }); 493 | drop(event); 494 | false 495 | })) 496 | .ok() 497 | .unwrap(); 498 | } 499 | } 500 | 501 | pub fn modifiers_changed(&mut self, state: ModifiersState) { 502 | self.keyboard.modifiers_changed(state); 503 | } 504 | 505 | pub fn key_event(&mut self, _device_id: DeviceId, event: KeyEvent, synthesized: bool) { 506 | self.keyboard.key_event(self.engine, event, synthesized); 507 | } 508 | 509 | pub fn focused(&mut self, focused: bool) { 510 | let channel = CString::new(FLUTTER_LIFECYCLE_CHANNEL).unwrap(); 511 | let lifecycle = serde_variant::to_variant_name(if focused { 512 | &LifecycleState::Resumed 513 | } else { 514 | &LifecycleState::Inactive 515 | }) 516 | .unwrap() 517 | .as_bytes(); 518 | let message = FlutterPlatformMessage { 519 | struct_size: size_of::() as _, 520 | channel: channel.as_ptr(), 521 | message: lifecycle.as_ptr(), 522 | message_size: lifecycle.len() as _, 523 | response_handle: null(), 524 | }; 525 | Self::unwrap_result(unsafe { FlutterEngineSendPlatformMessage(self.engine, &message) }); 526 | drop(message); 527 | drop(channel); 528 | } 529 | 530 | pub fn schedule_frame(&self) { 531 | Self::unwrap_result(unsafe { FlutterEngineScheduleFrame(self.engine) }); 532 | } 533 | 534 | pub fn surface(&self) -> &Surface { 535 | &self.user_data.surface 536 | } 537 | pub fn instance(&self) -> &Instance { 538 | &self.instance 539 | } 540 | pub fn device(&self) -> &Device { 541 | &self.user_data.device 542 | } 543 | pub fn queue(&self) -> &Queue { 544 | &self.user_data.queue 545 | } 546 | 547 | pub fn current_time() -> u64 { 548 | unsafe { FlutterEngineGetCurrentTime() } 549 | } 550 | 551 | extern "C" fn platform_message_callback( 552 | message: *const FlutterPlatformMessage, 553 | user_data: *mut c_void, 554 | ) { 555 | let message = unsafe { &*message }; 556 | let channel = unsafe { CStr::from_ptr(message.channel) } 557 | .to_str() 558 | .to_owned(); 559 | let user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 560 | let response_handle = SendFlutterPlatformMessageResponseHandle(message.response_handle); 561 | let data = 562 | unsafe { std::slice::from_raw_parts(message.message, message.message_size as _) } 563 | .to_vec(); 564 | user_data.event_loop_proxy.lock().unwrap().send_event(Box::new(move |this| { 565 | if let Ok(channel) = channel { 566 | log::debug!("Platform message on channel {channel}."); 567 | let mut response = None; 568 | if channel == FLUTTER_TEXTINPUT_CHANNEL { 569 | if let Ok(text_input) = serde_json::from_slice::(&data) { 570 | this.keyboard.handle_textinput_message(text_input); 571 | } else { 572 | log::debug!("Unknown textinput message: {:?}", std::str::from_utf8(&data)); 573 | } 574 | } else if channel == FLUTTER_PLATFORM_CHANNEL { 575 | if let Ok(message) = serde_json::from_slice(&data) { 576 | response = Platform::handle_message(this.engine, message, this); 577 | } 578 | } else if channel == FLUTTER_MOUSECURSOR_CHANNEL { 579 | if let Ok(mouse_cursor) = message_codec::from_slice(&data) { 580 | let MouseCursor::ActivateSystemCursor { kind, .. } = mouse_cursor; 581 | log::debug!("Set mouse cursor to {kind:?}"); 582 | (this.set_cursor_icon)(kind.into()); 583 | } else { 584 | log::error!("Invalid mousecursor event received! {data:?}"); 585 | } 586 | } else if channel == FLUTTER_PLATFORM_VIEWS_CHANNEL { 587 | if let Ok(message) = serde_json::from_slice(&data) { 588 | log::debug!("Platform Views Message: {message:?}"); 589 | response = this.platform_views_handler.handle_platform_views_message(message); 590 | } else { 591 | log::error!("Failed decoding {FLUTTER_PLATFORM_VIEWS_CHANNEL} message {:?}", String::from_utf8(data)); 592 | } 593 | } else { 594 | log::debug!( 595 | "Unhandled platform message: channel = {channel}, message size = {}, message: {:?}", 596 | data.len(), 597 | data, 598 | ); 599 | } 600 | 601 | Self::unwrap_result(unsafe { 602 | FlutterEngineSendPlatformMessageResponse( 603 | this.engine, 604 | response_handle.0, 605 | response.as_ref().map(|response| response.as_ptr()).unwrap_or_else(null), 606 | response.as_ref().map(|response| response.len()).unwrap_or(0) as _, 607 | ) 608 | }); 609 | drop(response); 610 | } else { 611 | Self::unwrap_result(unsafe { 612 | FlutterEngineSendPlatformMessageResponse( 613 | this.engine, 614 | response_handle.0, 615 | null(), 616 | 0, 617 | ) 618 | }); 619 | } 620 | drop(response_handle); 621 | false 622 | })).ok().unwrap(); 623 | } 624 | 625 | extern "C" fn root_isolate_create(_user_data: *mut c_void) { 626 | log::trace!("root_isolate_create"); 627 | } 628 | 629 | extern "C" fn update_semantics_node( 630 | semantics_node: *const FlutterSemanticsNode, 631 | _user_data: *mut c_void, 632 | ) { 633 | log::trace!("update_semantics_node {:?}", unsafe { *semantics_node }); 634 | } 635 | extern "C" fn update_semantics_custom_action( 636 | semantics_custom_action: *const FlutterSemanticsCustomAction, 637 | _user_data: *mut c_void, 638 | ) { 639 | log::trace!("update_semantics_custom_action {:?}", unsafe { 640 | *semantics_custom_action 641 | }); 642 | } 643 | 644 | extern "C" fn vsync_callback(user_data: *mut c_void, baton: isize) { 645 | let user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 646 | 647 | user_data 648 | .event_loop_proxy 649 | .lock() 650 | .unwrap() 651 | .send_event(Box::new(move |this| { 652 | this.device().poll(wgpu::Maintain::Wait); 653 | let time = Self::current_time(); 654 | Self::unwrap_result(unsafe { 655 | FlutterEngineOnVsync(this.engine, baton, time, time + 1000000000 / 60) 656 | }); 657 | false 658 | })) 659 | .ok() 660 | .unwrap(); 661 | } 662 | 663 | extern "C" fn on_pre_engine_restart_callback(_user_data: *mut c_void) { 664 | todo!() 665 | } 666 | 667 | extern "C" fn log_message_callback( 668 | tag: *const c_char, 669 | message: *const c_char, 670 | _user_data: *mut c_void, 671 | ) { 672 | let tag = unsafe { CStr::from_ptr(tag) }; 673 | let message = unsafe { CStr::from_ptr(message) }; 674 | log::logger().log( 675 | &log::Record::builder() 676 | .level(Level::Info) 677 | .module_path(tag.to_str().ok()) 678 | .args(format_args!("{}", message.to_str().unwrap())) 679 | .build(), 680 | ); 681 | } 682 | 683 | extern "C" fn instance_proc_address_callback( 684 | user_data: *mut c_void, 685 | _instance: FlutterVulkanInstanceHandle, 686 | name: *const c_char, 687 | ) -> *mut c_void { 688 | let user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 689 | 690 | let result = unsafe { 691 | user_data.instance.as_hal::().and_then(|instance| { 692 | let shared = instance.shared_instance(); 693 | let entry = shared.entry(); 694 | let cname = CStr::from_ptr(name); 695 | if cname == CStr::from_bytes_with_nul(b"vkCreateInstance\0").unwrap() { 696 | Some(entry.fp_v1_0().create_instance as *mut c_void) 697 | } else if cname 698 | == CStr::from_bytes_with_nul(b"vkCreateDebugReportCallbackEXT\0").unwrap() 699 | { 700 | None 701 | } else if cname 702 | == CStr::from_bytes_with_nul(b"vkEnumerateInstanceExtensionProperties\0") 703 | .unwrap() 704 | { 705 | Some(entry.fp_v1_0().enumerate_instance_extension_properties as *mut c_void) 706 | } else if cname 707 | == CStr::from_bytes_with_nul(b"vkEnumerateInstanceLayerProperties\0").unwrap() 708 | { 709 | Some(entry.fp_v1_0().enumerate_instance_layer_properties as *mut c_void) 710 | } else { 711 | entry 712 | .get_instance_proc_addr(shared.raw_instance().handle(), name) 713 | .map(|f| f as *mut c_void) 714 | } 715 | }) 716 | } 717 | .unwrap_or_else(null_mut); 718 | log::trace!( 719 | "instance_proc_address_callback: {} -> {:?}", 720 | unsafe { CStr::from_ptr(name) }.to_str().unwrap(), 721 | result, 722 | ); 723 | result 724 | } 725 | 726 | extern "C" fn next_image( 727 | _user_data: *mut c_void, 728 | _frame_info: *const FlutterFrameInfo, 729 | ) -> FlutterVulkanImage { 730 | unimplemented!() 731 | // Not used if a FlutterCompositor is supplied in FlutterProjectArgs. 732 | } 733 | 734 | extern "C" fn present_image( 735 | _user_data: *mut c_void, 736 | _image: *const FlutterVulkanImage, 737 | ) -> bool { 738 | unimplemented!() 739 | // Not used if a FlutterCompositor is supplied in FlutterProjectArgs. 740 | } 741 | 742 | extern "C" fn runs_task_on_current_thread_callback(user_data: *mut c_void) -> bool { 743 | let user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 744 | user_data.main_thread == std::thread::current().id() 745 | } 746 | 747 | extern "C" fn post_task_callback( 748 | task: FlutterTask, 749 | target_time_nanos: u64, 750 | user_data: *mut c_void, 751 | ) { 752 | let user_data = unsafe { &*(user_data as *const FlutterApplicationUserData) }; 753 | let task = SendFlutterTask(task); 754 | 755 | if Self::current_time() >= target_time_nanos { 756 | user_data 757 | .event_loop_proxy 758 | .lock() 759 | .unwrap() 760 | .send_event(Box::new(move |application| unsafe { 761 | Self::unwrap_result(FlutterEngineRunTask(application.engine, &task.0)); 762 | drop(task); 763 | false 764 | })) 765 | .ok() 766 | .unwrap(); 767 | } else { 768 | let event_loop_proxy = user_data.event_loop_proxy.lock().unwrap().clone(); 769 | user_data.runtime.spawn(async move { 770 | tokio::time::sleep(Duration::from_nanos( 771 | target_time_nanos - Self::current_time(), 772 | )) 773 | .await; 774 | 775 | event_loop_proxy 776 | .send_event(Box::new(move |application| unsafe { 777 | Self::unwrap_result(FlutterEngineRunTask(application.engine, &task.0)); 778 | drop(task); 779 | false 780 | })) 781 | .ok() 782 | .unwrap(); 783 | }); 784 | } 785 | } 786 | 787 | fn unwrap_result(result: FlutterEngineResult) { 788 | #[allow(non_upper_case_globals)] 789 | match result { 790 | x if x == FlutterEngineResult_kSuccess => {} 791 | x if x == FlutterEngineResult_kInvalidLibraryVersion => { 792 | panic!("Invalid library version."); 793 | } 794 | x if x == FlutterEngineResult_kInvalidArguments => { 795 | panic!("Invalid arguments."); 796 | } 797 | x if x == FlutterEngineResult_kInternalInconsistency => { 798 | panic!("Internal inconsistency."); 799 | } 800 | x => { 801 | panic!("Unknown error {x}."); 802 | } 803 | } 804 | } 805 | } 806 | 807 | impl Drop for FlutterApplication { 808 | fn drop(&mut self) { 809 | Self::unwrap_result(unsafe { FlutterEngineShutdown(self.engine) }); 810 | for &aot_data in &self.aot_data { 811 | unsafe { 812 | FlutterEngineCollectAOTData(aot_data); 813 | } 814 | } 815 | } 816 | } 817 | --------------------------------------------------------------------------------